Implementación de Historial de Mensajes y Contexto en un Chat Local con LLM en Python
El chat por consola basado en LiteLLM y Ollama de la parte anterior procesa las solicitudes de forma independiente. El modelo no puede ver los mensajes anteriores, lo que rompe el diálogo: las referencias a respuestas pasadas se ignoran. La solución es almacenar el historial de mensajes en una lista de Python y pasarlo con cada nueva solicitud.
Esto convierte una secuencia de llamadas en una conversación coherente. El modelo recibe el contexto completo: la instrucción del sistema, los pares usuario/asistente anteriores y la entrada actual.
Estructura de Mensajes para el Contexto
El formato de mensaje es estándar para las API compatibles con OpenAI:
system: una instrucción persistente, añadida por separadouser: la consulta del usuarioassistant: la respuesta del modelo
El historial contiene solo pares usuario/asistente. Al enviar:
[
{"role": "system", "content": SYSTEM_PROMPT},
*conversation_history,
{"role": "user", "content": user_message}
]
El orden es crítico: el sistema establece el comportamiento, el historial proporciona contexto y el nuevo usuario es la consulta actual.
Código Actualizado de main.py
Script completo con historial y recorte de contexto:
# -*- coding: utf-8 -*-
import time
from typing import Optional
from litellm import completion
MODEL = "ollama_chat/qwen2.5:3b"
API_BASE = "http://localhost:11434"
SYSTEM_PROMPT = "Eres un asistente útil. Responde de forma concisa y al grano."
MAX_HISTORY_MESSAGES = 6
def trim_history(history: list, limit: int) -> list:
if len(history) <= limit:
return history
return history[-limit:]
def send_request_to_llm(user_message: str, history: list) -> Optional[str]:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
*history,
{"role": "user", "content": user_message},
]
try:
start_time = time.time()
response = completion(
model=MODEL,
messages=messages,
api_base=API_BASE,
request_timeout=120,
)
duration = time.time() - start_time
print(f"\nTiempo de generación: {duration:.2f} seg")
return response.choices[0].message.content
except Exception as e:
print(f"\nError en la solicitud: {e}")
return None
def main() -> None:
print("Asistente de IA local con memoria iniciado.")
print("Introduce una pregunta o 'exit' para salir.\n")
conversation_history = []
while True:
user_input = input("Tú: ").strip()
if user_input.lower() in ("exit", "quit"):
print("¡Hasta luego!")
break
if not user_input:
print("Por favor, introduce una pregunta.")
continue
print("\nEl modelo está pensando...")
answer = send_request_to_llm(user_input, conversation_history)
if answer is not None:
print(f"\nIA: {answer}\n")
conversation_history.append({"role": "user", "content": user_input})
conversation_history.append({"role": "assistant", "content": answer})
conversation_history = trim_history(conversation_history, MAX_HISTORY_MESSAGES)
else:
print("\nNo se pudo obtener una respuesta. Verifica si Ollama está en ejecución.\n")
if __name__ == "__main__":
main()
Cambios Lógicos Clave
- conversation_history: lista en main() que almacena pares de mensajes
- send_request_to_llm: acepta historial, se expande mediante
* - Guardar pares: después de una respuesta, se añaden usuario y asistente
- trim_history: recorta a MAX_HISTORY_MESSAGES (6 elementos — 3 diálogos)
| Componente | Antes de la Actualización | Después de la Actualización |
|------------|---------------------------|-----------------------------|
| mensajes | sistema + usuario | sistema + historial + usuario |
| Memoria | Ninguna | Lista en RAM |
| Tamaño | Ilimitado | Hasta 6 mensajes |
El recorte evita el desbordamiento de contexto: los mensajes antiguos aumentan los tokens y ralentizan la inferencia.
Prueba del Contexto
Diálogo con consultas dependientes:
- Tú: Nombra tres frameworks de desarrollo web en Python
- IA: Django, Flask, FastAPI
- Tú: Cuéntame más sobre el segundo
- IA: Flask es un microframework ligero...
El modelo referencia correctamente "el segundo" al ver la respuesta anterior.
Limitaciones de la Implementación Actual
- Historial en RAM: se pierde al reiniciar
- Límite fijo: 6 mensajes (configurable)
- Sin persistencia: requiere Redis/BD para producción
Para un bot de Telegram o una aplicación web, la lógica permanece: historial en la sesión del usuario.
Lo Importante
- Formato de mensaje: sistema por separado, historial solo usuario/asistente
- Recorte de historial: [-limit:] preserva los pares recientes
- Separación de responsabilidades: solicitudes LLM en send_request_to_llm, estado en main()
- Tiempos de espera: request_timeout=120 para contextos largos
- Registro: tiempo de generación para depuración de rendimiento
— Editorial Team
Aún no hay comentarios.