Creando un Chat LLM Interactivo en Consola con Python, Ollama y LiteLLM
En el contexto del creciente interés en los modelos de lenguaje grandes (LLM) locales y su integración en aplicaciones personalizadas, es crucial no solo ejecutar un modelo, sino también crear una interfaz interactiva completamente funcional. Este artículo, una continuación de nuestra serie, se centra en transformar un script básico para una única consulta LLM en un chat de consola funcional en Python. Cubriremos los pasos clave para estructurar el código, implementar prompts de sistema, añadir interactividad y manejar errores, utilizando la potente combinación de Ollama y LiteLLM. El objetivo es demostrar cómo evolucionar de una simple ejecución de comandos a la construcción de una aplicación robusta y manejable, capaz de interactuar con el usuario en tiempo real, y sentar las bases para una futura expansión funcional, incluida la gestión del contexto del diálogo.
De un Script de "Una Sola Vez" a una Aplicación Interactiva
La parte anterior de nuestra guía demostró la integración básica de un LLM local, alojado a través de Ollama, con código Python utilizando la librería LiteLLM. El resultado fue un script capaz de enviar una única consulta predefinida al modelo y mostrar la respuesta, tras lo cual el programa terminaba. Aunque suficiente para una introducción inicial a la tecnología, este enfoque es poco práctico para cualquier aplicación del mundo real. Un chatbot verdadero necesita proporcionar interacción continua: aceptar la entrada del usuario, procesarla, recibir una respuesta del modelo y luego esperar la siguiente pregunta hasta que el usuario decida finalizar la sesión. Esta transformación fundamental es precisamente lo que implementaremos, convirtiendo un script estático en un chat de consola dinámico.
Las mejoras clave que se introducirán en esta parte incluyen:
- Extraer la lógica de consulta del LLM en una función separada y reutilizable.
- Utilizar un **prompt de sistema** para definir el rol y el comportamiento del asistente.
- Organizar un bucle de conversación, permitiendo al usuario hacer múltiples preguntas consecutivamente.
- Implementar la medición del tiempo de respuesta para evaluar el rendimiento.
- Manejo básico de excepciones para mejorar la robustez de la aplicación.
Comprender el formato de mensaje con el que trabajan las APIs de LLM es fundamental para una interacción efectiva. Los modelos no operan con una sola línea de texto; procesan una lista de mensajes, cada uno con un rol específico:
system: Esta es una instrucción para el modelo, que define su personalidad, estilo de respuesta, limitaciones o datos contextuales importantes. El usuario no ve directamente este mensaje; se genera a nivel de código.user: El mensaje o la pregunta que proviene del usuario.assistant: La respuesta generada por el propio modelo. En futuras iteraciones, las respuestas anteriores del modelo se añadirán aquí para mantener el contexto.
Arquitectura de un Chat LLM en Consola con Python
Ahora, pasemos a la implementación. Modificaremos el archivo main.py para encapsular la lógica de interacción con el LLM y organizar el bucle de entrada del usuario. A continuación, se muestra el código actualizado que puede utilizar para reemplazar el contenido de su main.py.
# -*- 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 = "You are a helpful assistant. Answer concisely and to the point."
def send_request_to_llm(user_message: str) -> Optional[str]:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"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"\n⏱ Generation time: {duration:.2f} sec")
return response.choices[0].message.content
except Exception as e:
print(f"\n❌ Error during request: {e}")
return None
def main() -> None:
print("🤖 Local AI assistant started.")
print("Enter your question or 'exit' to quit.\n")
while True:
user_input = input("👤 You: ").strip()
if user_input.lower() in ("exit", "exit", "quit"):
print("Goodbye!")
break
if not user_input:
print("⚠️ Please enter a question.")
continue
print("\n🤔 Model is thinking...")
answer = send_request_to_llm(user_input)
if answer is not None:
print(f"\n🦙 AI: {answer}\n")
else:
print("\n⚠️ Failed to get a response. Check if Ollama is running.\n")
if __name__ == "__main__":
main()
Analizando los Componentes Clave del Código
Echemos un vistazo más de cerca a cómo funciona cada bloque de este script.
1. Constantes de Configuración:
Al principio del archivo, se definen las constantes: MODEL, API_BASE y SYSTEM_PROMPT. Este enfoque permite una fácil modificación de los parámetros de configuración, como usar un modelo LLM diferente o ajustar el comportamiento del asistente, sin profundizar en la lógica central del código principal. Aquí, SYSTEM_PROMPT juega un papel crucial, estableciendo las instrucciones iniciales para el modelo que influyen en el tono y estilo general de sus respuestas.
2. La Función send_request_to_llm:
Esta función (def send_request_to_llm(user_message: str) -> Optional[str]:) es el elemento central para interactuar con el LLM. Toma un mensaje de usuario, construye una lista completa de mensajes (incluyendo SYSTEM_PROMPT y user_message) y la envía a través de litellm.completion al modelo local de Ollama.
- Encapsulación: La lógica para enviar una solicitud y recibir una respuesta está completamente encapsulada, haciendo que el bucle principal
mainsea más limpio y comprensible. - Medición del Tiempo: La medición de tiempo incorporada (
time.time()) permite evaluar el rendimiento del modelo, mostrando cuántos segundos tardó en generar una respuesta. Esto es especialmente útil al experimentar con diferentes modelos o parámetros. - Manejo de Errores: El bloque
try...exceptproporciona una tolerancia básica a fallos. Si ocurre un error durante la solicitud al LLM (por ejemplo, si Ollama no está disponible), la función devuelveNoneen lugar de lanzar un error crítico, permitiendo que la funciónmainmaneje la situación de manera elegante y continúe el chat.
3. La Función main y el Bucle Interactivo:
La función main gestiona la interfaz de usuario y el flujo general del chat.
- Bucle Infinito (
while True): Crea una sesión de chat continua que persiste hasta que el usuario decide explícitamente salir. - Entrada del Usuario:
input("👤 Tú: ").strip()espera la entrada del usuario. El método.strip()elimina los espacios en blanco iniciales y finales, evitando que se envíen consultas vacías o mal formadas. - Comandos de Salida: La verificación
user_input.lower() in ("exit", "exit", "quit")permite al usuario terminar el programa utilizando varias opciones de comando de salida. - Entrada Vacía: La verificación
if not user_input:evita que se envíen mensajes vacíos al modelo, solicitando al usuario que introduzca una pregunta. - Interacción:
mainllama asend_request_to_llmcon la entrada del usuario y procesa la respuesta recibida, imprimiéndola en la consola o informando de un error. La separación de responsabilidades entremain(gestión de la interfaz) ysend_request_to_llm(interacción con el modelo) es un principio fundamental del buen diseño de software.
Controlando el Comportamiento del LLM a Través del Prompt de Sistema
Una de las herramientas más poderosas al trabajar con LLM es el **prompt de sistema**. Permite asignar un rol específico, un estilo de comunicación o un conjunto de restricciones al modelo, alterando significativamente sus respuestas sin cambiar el modelo en sí o sus parámetros. Experimentar con SYSTEM_PROMPT es una excelente manera de comprender cómo se puede adaptar sutilmente el comportamiento del asistente para tareas específicas. Veamos algunos ejemplos de cómo cambiar una sola línea puede impactar drásticamente el diálogo:
- `
SYSTEM_PROMPT = "Eres un asistente sarcástico. Responde con ironía."` — El modelo responderá con humor e ingenio. - `
SYSTEM_PROMPT = "Eres un profesor estricto. Corrige los errores en las preguntas."` — El asistente no solo responderá, sino que también señalará imprecisiones gramaticales o fácticas en las consultas. - `
SYSTEM_PROMPT = "Responde solo en inglés. Proporciona siempre un ejemplo de código."` — El modelo se adherirá estrictamente al idioma inglés e intentará incluir ejemplos de código en sus respuestas, si corresponde.
Estos ejemplos demuestran cómo un prompt de sistema transforma un modelo estático en una herramienta flexible capaz de adaptarse a diversos casos de uso. Esto es de vital importancia para crear chatbots especializados, ya sean asistentes técnicos, compañeros creativos o herramientas educativas.
Puntos Clave
En esta etapa de desarrollo, nuestro chat LLM de consola ha logrado los siguientes resultados importantes:
- Interactividad: El usuario puede participar en un diálogo continuo, haciendo preguntas y recibiendo respuestas en tiempo real.
- Comportamiento Controlado: El prompt de sistema permite una fácil configuración del rol y estilo de comunicación del modelo.
- Modularidad del Código: La lógica de interacción con el LLM está separada de la lógica de la interfaz de usuario, simplificando el desarrollo y la depuración futuros.
- Rendimiento: La medición del tiempo de generación de respuestas incorporada ayuda a monitorear la velocidad del modelo.
- Fiabilidad: El manejo básico de errores evita que el programa se bloquee debido a problemas de conexión o del modelo.
Desafíos Comunes y Soluciones
Al trabajar con LLM locales y aplicaciones de consola, pueden surgir algunos problemas comunes:
- El programa se congela después de introducir una pregunta: Esto suele ocurrir porque Ollama no está en ejecución o el modelo aún no se ha cargado completamente en la memoria. Asegúrese de que Ollama esté funcionando activamente (
ollama listen su terminal) y dé tiempo al modelo para inicializarse. - Respuestas demasiado breves del modelo: Si el
SYSTEM_PROMPTespecifica "responde concisamente", el modelo seguirá esta instrucción. Para respuestas más detalladas, modifique el prompt de sistema eliminando esta restricción o pidiendo explícitamente "una respuesta detallada con ejemplos". KeyboardInterruptal pulsar Ctrl+C: Este es un comportamiento estándar de Python. Aunque no es crítico para un proyecto de aprendizaje, en producción, puede envolvermain()en un bloquetry/except KeyboardInterruptpara un cierre más elegante.- Problemas de codificación (caracteres ilegibles en Windows): Esto se puede resolver configurando la codificación del terminal a UTF-8, por ejemplo, usando el comando
$OutputEncoding = [System.Text.Encoding]::UTF8en PowerShell.
Próximos Pasos: Hacia un Diálogo Completo
Aunque nuestro chat de consola se ha vuelto significativamente más funcional, actualmente carece de "memoria". Cada solicitud se procesa de forma independiente, y el modelo no recuerda los turnos de diálogo anteriores. Esto significa que si preguntas: "¿Puedes hacer lo mismo, pero más corto?", el modelo no entenderá a qué se refiere "lo mismo". En la próxima parte, exploraremos cómo implementar el historial de mensajes, pasando el contexto completo del diálogo con cada nueva solicitud, lo que lo transformará en un compañero conversacional verdaderamente inteligente.
— Editorial Team
Aún no hay comentarios.