3. Construye tu primer chat CLI con streaming
En el Capítulo 1, hiciste tu primera llamada a un LLM y viste aparecer una respuesta completa de una vez. En el Capítulo 2, aprendiste los fundamentos conceptuales de la IA agéntica y por qué existe LangChain. Ahora es momento de construir algo práctico: una aplicación de chat con streaming que se sienta responsiva y profesional.
Por qué importa el streaming: Cuando le haces una pregunta compleja a un LLM, esperar 10-30 segundos por una respuesta completa se siente roto. El streaming permite que los tokens aparezcan a medida que se generan, creando un flujo conversacional natural. Este capítulo construye una aplicación de chat CLI con salida en streaming, gestión adecuada de configuración, capacidades de depuración y manejo robusto de errores.
Lo que construirás: Al final de este capítulo, tendrás un script chat.py funcional que:
- Transmite respuestas del LLM token por token a la terminal
- Carga claves API de forma segura desde variables de entorno
- Maneja diferentes tipos de modelos (chat vs modelos de razonamiento) con parámetros apropiados
- Proporciona herramientas de depuración para inspeccionar lo que realmente se envía al LLM
- Maneja errores comunes de forma elegante (claves API faltantes, fallos de red, entradas inválidas)
3.1) Crear una carpeta de trabajo e instalar paquetes
Antes de escribir cualquier código, necesitas una estructura de proyecto limpia y las dependencias correctas. Esta sección establece la base para un proyecto Python mantenible.
Estructura del proyecto
Crea un nuevo directorio para tu aplicación de chat:
mkdir langchain-chat
cd langchain-chatConfiguración del entorno Python
Crea un entorno virtual para aislar las dependencias:
# Crear entorno virtual
python -m venv venv
# Activarlo (macOS/Linux)
source venv/bin/activate
# Activarlo (Windows)
venv\Scripts\activate¿Por qué entornos virtuales? LangChain tiene muchas dependencias (por ejemplo, SDK de OpenAI, Pydantic, bibliotecas asíncronas). Un entorno virtual asegura:
- Tu Python del sistema permanece limpio
- Diferentes proyectos pueden usar diferentes versiones de LangChain
- Las dependencias son reproducibles (vía
requirements.txt)
Verás (venv) en tu prompt de terminal cuando esté activado.
Instalar LangChain
Instala los paquetes principales de LangChain:
pip install langchain-core==1.2.7 langchain-openai==1.1.7 python-dotenvDesglose de paquetes:
langchain-core: Abstracciones principales (mensajes, prompts, cadenas, runnables)langchain-openai: Implementaciones específicas de OpenAI (ChatOpenAI, embeddings)python-dotenv: Carga variables de entorno desde archivos.env
Nota sobre versiones: Este libro usa LangChain 1.2.x a partir de enero de 2026. Si estás leyendo esto en el futuro, consulta la documentación de LangChain para la última versión.
Verificar la instalación
Crea una prueba simple para confirmar que todo funciona:
# test_install.py
try:
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
print("✓ langchain-core: OK")
print("✓ langchain-openai: OK")
print("\nInstalación exitosa!")
except ImportError as e:
print(f"✗ Importación falló: {e}")
print("Asegúrate de que tu entorno virtual esté activado.")Ejecútalo:
python test_install.pySalida esperada:
✓ langchain-core: OK
✓ langchain-openai: OK
Instalación exitosa!Si ves "Instalación exitosa!", estás listo para continuar. Si obtienes un error de importación, verifica que:
- Tu entorno virtual esté activado (busca
(venv)en tu prompt) - Los paquetes se instalaron correctamente (intenta ejecutar
pip list)
Crear requirements.txt
Acabas de instalar paquetes con comandos pip install. Aunque esto funciona para aprender, hay una mejor forma: archivos requirements.txt. Esta es una práctica estándar en proyectos Python por varias razones:
¿Por qué usar requirements.txt?
- Reproducibilidad: Otros (o tú en 6 meses) pueden instalar exactamente las mismas versiones de paquetes
- Gestión clara de dependencias: Ver de un vistazo qué paquetes necesita tu proyecto
- Colaboración en equipo: Los miembros del equipo usan versiones idénticas, evitando problemas de "funciona en mi máquina"
- Automatización: Servidores o pipelines CI/CD pueden configurar el entorno con una línea:
pip install -r requirements.txt
Crea un archivo requirements.txt en la raíz de tu proyecto:
# requirements.txt
langchain-core==1.2.7
langchain-openai==1.1.7
python-dotenvNota sobre la sintaxis:
==1.2.7fija a una versión exacta (recomendado para reproducibilidad)- Sin especificador de versión (como
python-dotenv) instala la última versión estable - Las líneas que comienzan con
#son comentarios
Ahora cualquiera puede instalar todas las dependencias con un solo comando:
pip install -r requirements.txtEsto es mucho mejor que escribir cada paquete individualmente. Si un compañero clona tu proyecto, solo necesita:
- Crear un entorno virtual
- Ejecutar
pip install -r requirements.txt
No necesita recordar nombres o versiones de paquetes—todo está en el archivo.
Tu estructura de proyecto
Después de completar esta sección, tu carpeta debería verse así:
langchain-chat/
├── venv/ # Entorno virtual (no subir a git)
├── requirements.txt # Lista de dependencias
└── test_install.py # Script de verificación de instalaciónSiguiente: La Sección 3.2 muestra cómo cargar claves API de forma segura usando archivos .env.
3.2) Variables de entorno con .env
Las claves API son secretos. Codificarlas directamente en tu código es un riesgo de seguridad (especialmente si subes a git). Esta sección muestra el enfoque estándar: variables de entorno cargadas desde un archivo .env.
¿Por qué variables de entorno?
El problema con claves codificadas:
# ❌ NUNCA HAGAS ESTO
llm = ChatOpenAI(api_key="sk-proj-abc123...")Si subes este código a GitHub, tu clave API es pública. Cualquiera puede usarla, generar cargos en tu cuenta, o hacer que tu clave sea revocada.
La solución: Almacena secretos en variables de entorno, cárgalas en tiempo de ejecución.
Crear el archivo .env
Crea un archivo .env en la raíz de tu proyecto:
# .env
OPENAI_API_KEY=sk-proj-tu-clave-real-aquiObtén tu clave API:
- Ve a platform.openai.com/api-keys
- Crea una nueva clave secreta
- Cópiala inmediatamente (no podrás verla de nuevo)
- Pégala en tu archivo
.env, reemplazandosk-proj-tu-clave-real-aqui
Paso crítico de seguridad: Antes de hacer cualquier otra cosa, protege tu clave API de ser subida a git.
Crea un archivo .gitignore en la raíz de tu proyecto y agrega estas líneas:
# .gitignore
venv/
__pycache__/
*.pyc
.envLa línea .env le dice a git que ignore tu archivo de clave API. Esto previene subir accidentalmente secretos al control de versiones.
Tu estructura de proyecto ahora:
langchain-chat/
├── venv/
├── .env # Tu clave API (ignorada por git)
├── .gitignore # Contiene: .env, venv/, etc.
├── requirements.txt
└── test_install.pyCargar variables de entorno
El paquete python-dotenv carga archivos .env en os.environ:
# chat.py
import os
from dotenv import load_dotenv
# Cargar archivo .env
load_dotenv()
# Acceder a variables de entorno
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
raise ValueError("OPENAI_API_KEY no encontrada en el entorno")
print(f"Clave API cargada: {api_key[:8]}...") # Mostrar solo los primeros 8 caracteresCómo funciona load_dotenv():
- Busca un archivo
.envcomenzando desde donde ejecutas el script - Lee cada línea en el formato
CLAVE=valor - Agrega cada variable a
os.environ - Si una variable ya está configurada (por ejemplo, por tu plataforma de hosting), no será sobrescrita—el valor existente permanece
Usar la clave API con LangChain
Las implementaciones de OpenAI de LangChain (ChatOpenAI, etc.) automáticamente buscan OPENAI_API_KEY en os.environ:
from langchain_openai import ChatOpenAI
load_dotenv()
# Esto automáticamente usa os.environ["OPENAI_API_KEY"]
llm = ChatOpenAI(model="gpt-4o-mini")Convención de LangChain: Cuando creas ChatOpenAI() sin un parámetro api_key, automáticamente busca OPENAI_API_KEY en el entorno. Este es un patrón estándar en las integraciones de LangChain.
Clave API explícita (para pruebas o múltiples claves):
llm = ChatOpenAI(
model="gpt-4o-mini",
api_key=os.environ.get("OPENAI_API_KEY")
)Esto es útil cuando tienes múltiples claves API (desarrollo vs producción) o quieres ser explícito sobre qué clave se usa.
Variables de entorno en producción
En entornos de producción (plataformas en la nube, contenedores Docker), no usas archivos .env. En su lugar, configuras variables de entorno a través de la configuración de la plataforma:
- Docker: Usa la bandera
-eal ejecutar contenedores - Plataformas en la nube: Configura variables de entorno en paneles de configuración
- CI/CD: Usa herramientas de gestión de secretos
La parte importante: tu código no cambia. os.environ.get("OPENAI_API_KEY") funciona de la misma manera ya sea que la variable venga de un archivo .env o de una plataforma en la nube. Cubriremos el despliegue en detalle en capítulos posteriores.
Verificar tu configuración
Para confirmar que todo funciona, puedes probar el código de carga de variables de entorno mostrado anteriormente. Si tu archivo .env está configurado correctamente, os.environ.get("OPENAI_API_KEY") devolverá tu clave API.
Si os.environ.get("OPENAI_API_KEY") devuelve None, verifica que:
- Llamaste a
load_dotenv()antes de acceder a la variable de entorno .envexiste en la raíz del proyectoOPENAI_API_KEY=sk-proj-...está escrito correctamente en.env- Estás ejecutando desde el directorio raíz del proyecto
Siguiente: La Sección 3.3 implementa el bucle de chat real con salida en streaming.
3.3) Implementar el bucle de chat con salida en streaming
Ahora construirás el bucle de chat principal. Esta sección introduce el streaming - la diferencia clave entre un chatbot lento y uno responsivo.
Entender el streaming
Sin streaming (enfoque del Capítulo 1):
response = llm.invoke("Escribe un ensayo de 500 palabras sobre IA")
print(response.content) # Espera 20 segundos, luego aparece el ensayo completoCon streaming:
for chunk in llm.stream("Escribe un ensayo de 500 palabras sobre IA"):
print(chunk.content, end="", flush=True) # Los tokens aparecen a medida que se generanPor qué importa el streaming:
- Retroalimentación inmediata: En lugar de mirar una pantalla en blanco durante 20 segundos, ves palabras apareciendo de inmediato
- Sensación de conversación natural: Como hablar con una persona - las respuestas vienen progresivamente, no todas a la vez
- Ahorra tiempo y dinero: Si el LLM comienza a dar la respuesta incorrecta, puedes detenerlo temprano en lugar de esperar una respuesta completa (e inútil)
- Mejor depuración: Al construir aplicaciones, puedes detectar problemas (como errores de formato) mientras ocurren, no después de una larga espera
Qué es realmente el streaming: El streaming es la entrega incremental del mismo texto de respuesta. No expone razonamiento oculto o procesos internos del modelo - solo te muestra salida parcial a medida que está disponible desde la API. Piénsalo como descargar un archivo: ves el progreso a medida que llegan los fragmentos, pero el contenido del archivo es el mismo ya sea que lo descargues todo de una vez o en piezas.
Nota sobre límites de fragmentos: Los fragmentos no están garantizados para alinearse con palabras u oraciones. La API envía tokens en lotes pequeños por eficiencia, así que un fragmento podría ser "Hol", "a! Cóm", "o pue", "do ayu", "darte", "?". Esto es normal y esperado - no intentes analizar significado de fragmentos individuales.
El bucle de chat básico
Aquí hay un bucle de chat con streaming mínimo:
# chat.py
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
def main():
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
print("Chat iniciado. Escribe 'quit' o 'exit' para detener.\n")
while True:
user_input = input("Tú: ")
if user_input.lower() in ["quit", "exit"]:
print("¡Adiós!")
break
print("Asistente: ", end="", flush=True)
for chunk in llm.stream([HumanMessage(content=user_input)]):
print(chunk.content, end="", flush=True)
print("\n")
if __name__ == "__main__":
main()Cómo funciona esto:
while True:: Bucle infinito para conversación continuainput("Tú: "): Obtener entrada del usuario desde la terminalllm.stream([HumanMessage(...)]): Transmitir respuesta del LLM- Salida en streaming con parámetros especiales:
end="": No agregar nueva línea después de cada fragmento (mantiene la salida en la misma línea)flush=True: Forzar salida inmediata a la terminal sin buffering
¿Por qué [HumanMessage(content=user_input)]?
Los modelos de chat de LangChain esperan una lista de mensajes, no una cadena sin procesar. Cada mensaje tiene un rol:
- HumanMessage: Entrada del usuario
- AIMessage: Respuesta del LLM
- SystemMessage: Instrucciones para el LLM (cubierto en el Capítulo 4)
Incluso para un solo mensaje de usuario, pasas una lista: [HumanMessage(content="Hola")].
Limitación clave - Conversaciones de un solo turno: Este bucle de chat es intencionalmente sin estado. Cada solicitud envía solo el mensaje actual, no el historial de conversación previo. Esto significa:
- El LLM no recordará lo que preguntaste antes
- Preguntas de seguimiento como "¿Cuál es su población?" no funcionarán después de preguntar "¿Cuál es la capital de Francia?"
- Esta es una característica fundamental del LLM - no tienen memoria a menos que proporciones explícitamente el contexto
Ejemplo de la limitación:
Tú: ¿Cuál es la capital de Francia?
Asistente: París.
Tú: ¿Cuál es su población?
Asistente: No tengo suficiente contexto. ¿De qué ciudad estás preguntando?El bucle while True proporciona continuidad de UX (puedes seguir chateando), pero cada turno es independiente. Próximamente en el Capítulo 8: Implementaremos memoria de conversación almacenando y reenviando el historial de mensajes con cada solicitud.
Ejecutar el bucle de chat
python chat.pyEjemplo de interacción:
Chat iniciado. Escribe 'quit' o 'exit' para detener.
Tú: ¿Qué es LangChain?
Asistente: LangChain es un framework para desarrollar aplicaciones impulsadas por modelos de lenguaje. Proporciona herramientas para gestión de prompts, cadenas, agentes y memoria.
Tú: Dame un ejemplo simple
Asistente: Aquí hay un ejemplo básico: ...
Tú: quit
¡Adiós!Entender la API de streaming
¿Qué es un "fragmento"?
Cada fragmento es un objeto AIMessageChunk con:
content: Los tokens de texto generadosresponse_metadata: Información del modelo, conteos de tokens, etc.
for chunk in llm.stream([HumanMessage(content="Hola")]):
print(f"Fragmento: {chunk}")
print(f"Contenido: {chunk.content}")
print(f"Tipo: {type(chunk)}")Salida:
Fragmento: content='Hola' response_metadata={'model_provider': 'openai', ...}
Contenido: Hola
Tipo: <class 'langchain_core.messages.ai.AIMessageChunk'>
Fragmento: content='!' response_metadata={...}
Contenido: !
Tipo: <class 'langchain_core.messages.ai.AIMessageChunk'>
Fragmento: content=' Cómo' response_metadata={...}
Contenido: Cómo
Tipo: <class 'langchain_core.messages.ai.AIMessageChunk'>Acumular la respuesta completa
A veces necesitas la respuesta completa (para registro, pruebas o procesamiento adicional):
def chat_with_accumulation():
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
user_input = input("Tú: ")
full_response = ""
print("Asistente: ", end="", flush=True)
for chunk in llm.stream([HumanMessage(content=user_input)]):
print(chunk.content, end="", flush=True)
full_response += chunk.content
print("\n")
# Ahora tienes la respuesta completa
print(f"[DEBUG] Longitud de respuesta completa: {len(full_response)} caracteres")
return full_responseEste patrón es común cuando necesitas:
- Guardar la conversación en una base de datos
- Analizar la respuesta para datos estructurados
- Calcular uso de tokens o costos
Tu estructura de proyecto después de esta sección:
langchain-chat/
├── venv/
├── .env
├── .gitignore
├── requirements.txt
├── test_install.py
└── chat.py # Bucle de chat con streaming (¡nuevo!)Siguiente: La Sección 3.4 muestra cómo manejar diferentes tipos de modelos con configuración inteligente de parámetros.
3.4) Configuración inteligente: Manejar parámetros para modelos de razonamiento vs chat
OpenAI ofrece dos tipos de modelos con diferentes capacidades y mecanismos de control:
Modelos de chat (gpt-4o, gpt-4o-mini):
- Rápidos y conversacionales
- Soportan
temperaturepara controlar aleatoriedad y creatividad - Mejores para tareas generales, escritura creativa, codificación rutinaria
Modelos de razonamiento (o1, o3, GPT-5):
- Más lentos pero más lógicos y consistentes
- NO soportan
temperature(usan razonamiento interno en su lugar) - Mejores para matemáticas complejas, planificación de múltiples pasos, análisis formal
La diferencia clave: Los modelos de chat usan muestreo probabilístico (tú controlas la aleatoriedad), mientras que los modelos de razonamiento usan lógica interna determinística (el modelo controla su propio proceso de razonamiento).
Entender la temperatura (solo modelos de chat)
¿Qué es la temperatura?
La temperatura es un número entre 0.0 y 2.0 que controla qué tan creativas son las respuestas del modelo. En valores bajos (cerca de 0), obtienes respuestas consistentes y predecibles. En valores altos (cerca de 2.0), obtienes respuestas creativas y variadas. Piénsalo como un "dial de creatividad".
Cómo funciona: Al generar cada palabra, el modelo ve muchas posibles palabras siguientes con diferentes probabilidades. La temperatura afecta cómo el modelo elige:
- Temperatura baja (0.0): Casi siempre elige la palabra de mayor probabilidad → respuestas consistentes y enfocadas
- Temperatura alta (2.0): Más probable que elija palabras de menor probabilidad → respuestas diversas y creativas
Importante: La temperatura solo funciona con modelos de chat (gpt-4o, gpt-4o-mini). No aplica a modelos de razonamiento (GPT-5, o1, o3), que usan lógica interna en lugar de muestreo probabilístico.
Guía de valores de temperatura:
-
0.0: Altamente determinístico, enfocado y consistente
- Usar para: Q&A factual, generación de código rutinaria, salida estructurada
- Misma entrada → salida casi idéntica cada vez
- Ejemplo: "¿Cuánto es 2+2?" → Siempre "4"
-
0.7–1.0: Comportamiento de muestreo estándar (el predeterminado es 1.0)
- Usar para: conversación general, explicaciones, respuestas balanceadas
- Variación moderada en redacción y ejemplos
- Ejemplo: "Explica la fotosíntesis" → Diferente redacción cada vez, misma información central
-
1.2–2.0: Más creativo y diverso, menos predecible
- Usar para: escritura creativa, lluvia de ideas, ideación
- Alta variación en tono, estructura y redacción
- Ejemplo: "Escribe un poema sobre la luna" → Estilos muy diferentes cada vez
Nota: Valores por encima de 1.0 aumentan la creatividad pero pueden reducir la precisión factual y coherencia. El valor máximo es 2.0.
Ejemplo: Impacto de la temperatura (solo modelos de chat)
# Temperatura 0.0 - determinístico, misma respuesta cada vez
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
response = llm.invoke([HumanMessage(content="¿Cuánto es 2+2?")])
print(response.content) # Salida: 4
# Temperatura 1.0 - comportamiento predeterminado, ligera variación posible
llm = ChatOpenAI(model="gpt-4o-mini", temperature=1.0)
response = llm.invoke([HumanMessage(content="¿Cuánto es 2+2?")])
print(response.content) # Salida: 4 (puede incluir breve explicación)Para preguntas cerradas y factuales, la temperatura tiene poco efecto en la corrección.
Para tareas abiertas o creativas, la temperatura influye significativamente en la diversidad, tono y estilo.
¿Qué pasa si usas parámetros de modelo de chat en modelos de razonamiento?
Depende del modelo - algunos lo rechazan, otros lo ignoran silenciosamente:
# ❌ Esto fallará con modelos o3
llm = ChatOpenAI(model="o3-mini", temperature=0.7)Error:
BadRequestError: Temperature is not supported with this modelDiferentes modelos, diferentes políticas:
- Modelos o1 / o3: Rechazan explícitamente parámetros no soportados. Si se incluye temperature, la API devuelve un error 400 BadRequest inmediatamente.
- Modelos GPT-5: Más permisivos - el parámetro es aceptado pero ignorado silenciosamente. Tu solicitud tiene éxito, pero temperature no tiene efecto.
Por qué esto importa: Siempre verifica qué modelo estás usando y configura los parámetros en consecuencia. Usar los parámetros incorrectos puede causar errores o fallar silenciosamente, desperdiciando tiempo de depuración.
Cómo controlar el comportamiento del modelo de razonamiento
Ahora sabes que los modelos de chat usan temperature y los modelos de razonamiento no. Entonces, ¿cómo controlas los modelos de razonamiento?
Los modelos de razonamiento se ajustan a través del diseño de prompts, no de parámetros:
- Los modelos de razonamiento no exponen
temperatureo controles similares - En su lugar, guías el comportamiento por cómo escribes el prompt:
- Instrucciones explícitas: "Piensa paso a paso", "Muestra tu trabajo"
- Restricciones como reglas: "No debes asumir...", "Siempre verifica..."
- Requisitos estructurados: "Salida en formato JSON", "Incluye razonamiento antes de la respuesta"
- Lógica de decisión: "Si condición A, entonces haz X, de lo contrario haz Y"
Ejemplo: Parámetros de chat vs Prompts de razonamiento
# ❌ Enfoque de chat - no funcionará con modelos de razonamiento
llm = ChatOpenAI(model="o3-mini", temperature=0.5)
# Error: BadRequestError: Temperature is not supported
# ✅ Enfoque de razonamiento - guiar a través de la estructura del prompt
prompt = """
Resuelve este problema paso a paso:
1. Indica lo que sabes
2. Muestra tus cálculos
3. Verifica tu respuesta
Problema: Si x + 5 = 12, ¿cuál es x?
"""
llm = ChatOpenAI(model="o3-mini")
response = llm.invoke([HumanMessage(content=prompt)])
print(response.content)Salida:
1. Lo que sé: x + 5 = 12
2. Cálculos: x = 12 - 5 = 7
3. Verificación: 7 + 5 = 12 ✓
Respuesta: x = 7Idea clave: Los modelos de chat se controlan por parámetros, los modelos de razonamiento se controlan por prompts.
Tabla de decisión de selección de modelo
Ahora que entiendes cómo controlar ambos tipos de modelos, aquí está cuándo usar cada uno:
| Tipo de tarea | Modelo recomendado | Por qué |
|---|---|---|
| Conversación general | gpt-4o-mini | Rápido, bajo costo, conversacional |
| Q&A simple | gpt-4o-mini | Suficiente para búsqueda factual |
| Escritura creativa | gpt-4o-mini (temp 0.8–1.0) | Temperature habilita creatividad |
| Generación de código | GPT-5 | Mejor planificación lógica |
| Razonamiento complejo | GPT-5 | Optimizado para lógica de múltiples pasos |
| Problemas matemáticos | o3 / o1 | Modelos de razonamiento dedicados |
| Planificación de múltiples pasos | GPT-5 | Fuerte en planificación de largo horizonte |
| Análisis formal (legal/política) | o3 | Estrictamente determinístico |
Compensaciones de costo y latencia
Entender las compensaciones prácticas te ayuda a elegir el modelo correcto para tu caso de uso:
| Tipo de modelo | Velocidad (Latencia típica) | Costo (Relativo) | Mejor para |
|---|---|---|---|
| gpt-4o-mini | Muy rápido (<2s) | Muy bajo | Conversación general, tareas simples |
| gpt-4o | Rápido (1–4s) | Medio | Chat de mayor calidad, tareas multimodales |
| GPT-5 | Moderado (3–8s) | Alto | Razonamiento complejo, planificación |
| o1 / o3 | Más lento (5–15s+) | Más alto | Razonamiento determinístico, lógica formal |
Notas:
- La velocidad refleja latencia de respuesta típica (varía según longitud del prompt y complejidad)
- El costo es una comparación relativa - verifica precios actuales en el sitio web de OpenAI
- Los modelos de razonamiento intercambian velocidad y costo por consistencia y corrección
- Los modelos de chat priorizan capacidad de respuesta y eficiencia
Cuándo usar modelos de razonamiento (GPT-5, o1, o3):
- Problemas matemáticos y STEM de múltiples pasos que requieren pasos intermedios correctos
- Análisis lógico complejo con dependencias y restricciones
- Depuración de código con múltiples causas interactuantes
- Tareas de planificación con muchas reglas, casos extremos o compensaciones
- Flujos de trabajo de agentes que requieren consistencia y pensamiento de largo horizonte
Cuándo usar modelos de chat (gpt-4o, gpt-4o-mini):
- Conversación general y chat interactivo
- Q&A simple con profundidad de razonamiento limitada
- Generación de contenido (blogs, resúmenes, escritura creativa)
- Generación de código rutinaria y tareas repetitivas
- Aplicaciones donde la velocidad y el costo importan más que el razonamiento profundo
Siguiente: La Sección 3.5 muestra técnicas de depuración para inspeccionar lo que realmente se envía al LLM.
3.5) Depuración: Inspeccionar respuestas y uso de tokens
Cuando tu LLM se comporta inesperadamente, necesitas ver exactamente qué se envió y recibió. Esta sección muestra cómo inspeccionar llamadas al LLM y depurar problemas.
Por qué importa la depuración
Escenarios comunes de depuración:
- "¿Por qué el LLM dio esta respuesta?" → Verifica el prompt exacto
- "¿Cuánto costó esta solicitud?" → Verifica el uso de tokens
- "¿Por qué esto es tan lento?" → Mide la latencia
- "¿Mi formato de mensaje es correcto?" → Inspecciona la estructura del mensaje
El desafío: Cuando llamas a llm.invoke(), obtienes un objeto de respuesta. ¿Pero qué hay realmente en él? ¿Qué información está disponible para depuración?
Entender el objeto de respuesta
Antes de depurar, necesitas entender qué devuelve llm.invoke().
Estructura básica:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hola")])
# ¿Qué hay en la respuesta?
print(type(response)) # AIMessage
print(response.content) # El texto real
print(response.response_metadata) # Uso de tokens, información del modelo, etc.Salida:
<class 'langchain_core.messages.ai.AIMessage'>
¡Hola! ¿Cómo puedo ayudarte hoy?
{
'token_usage': {
'completion_tokens': 9,
'prompt_tokens': 8,
'total_tokens': 17
},
'model_name': 'gpt-4o-mini-2024-07-18',
'finish_reason': 'stop',
...
}Partes clave de la respuesta:
response.content: El texto que el LLM generóresponse.response_metadata: Diccionario con:token_usage: Cuántos tokens se usaron (para cálculo de costos)model_name: Versión exacta del modelo que respondiófinish_reason: Por qué se detuvo la generación (ver sección de Modo de depuración para detalles)
Acceder al uso de tokens:
token_usage = response.response_metadata['token_usage']
print(f"Tokens de prompt: {token_usage['prompt_tokens']}")
print(f"Tokens de respuesta: {token_usage['completion_tokens']}")
print(f"Total: {token_usage['total_tokens']}")Salida:
Tokens de prompt: 8
Tokens de respuesta: 9
Total: 17Por qué esto importa: Necesitas estos valores para depuración, seguimiento de costos y optimización de tus prompts.
Calcular costos desde el uso de tokens
El uso de tokens determina el costo. Cada modelo tiene precios diferentes:
GPT-4o-mini (a partir de enero de 2026):
- Entrada: $0.15 por 1M tokens
- Salida: $0.60 por 1M tokens
GPT-4o:
- Entrada: $2.50 por 1M tokens
- Salida: $10.00 por 1M tokens
Función de cálculo de costos:
def calculate_cost(token_usage, model_name):
"""Calcula el costo basado en el uso de tokens."""
prompt_tokens = token_usage.get('prompt_tokens', 0)
completion_tokens = token_usage.get('completion_tokens', 0)
# Precios por 1M tokens (a partir de enero de 2026)
pricing = {
'gpt-4o-mini': {'input': 0.15, 'output': 0.60},
'gpt-4o': {'input': 2.50, 'output': 10.00},
'gpt-5': {'input': 1.25, 'output': 10.00},
}
if model_name not in pricing:
return None
input_cost = (prompt_tokens / 1_000_000) * pricing[model_name]['input']
output_cost = (completion_tokens / 1_000_000) * pricing[model_name]['output']
return input_cost + output_cost
# Ejemplo
response = llm.invoke([HumanMessage(content="Explica la computación cuántica")])
token_usage = response.response_metadata['token_usage']
cost = calculate_cost(token_usage, "gpt-4o-mini")
print(f"Costo: ${cost:.6f}")Salida:
Costo: $0.000123Por qué esto importa: Las aplicaciones de producción pueden manejar 50,000+ solicitudes/día. A $0.002 por solicitud, eso es $3,000/mes. Usa el modelo incorrecto o prompts inflados, y los costos saltan a $30,000/mes. Un bug de bucle de reintentos puede quemar miles durante la noche. Rastrea el uso de tokens desde el día uno.
Habilitar modo de depuración (cuando necesitas detalles crudos de la API)
El objeto de respuesta y el wrapper personalizado manejan la mayoría de las necesidades de depuración. Pero a veces necesitas ver exactamente qué envía LangChain a OpenAI - la solicitud y respuesta JSON crudas.
Cuándo podrías necesitar esto:
- Depurar el formato de mensajes de LangChain
- Verificar que los parámetros de la API estén configurados correctamente
- Investigar errores inesperados de la API
- Entender el payload exacto de la API
LangChain tiene registro de depuración incorporado vía langchain_core.globals:
from langchain_core.globals import set_debug
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
set_debug(True)
# Ahora todas las llamadas al LLM imprimirán información de depuración
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hola")])Salida:
[llm/start] [llm:ChatOpenAI] Entering LLM run with input:
{
"prompts": [
"Human: Hola"
]
}
[llm/end] [llm:ChatOpenAI] [1.45s] Exiting LLM run with output:
{
"generations": [
[
{
"text": "¡Hola! ¿Cómo puedo ayudarte hoy?",
"generation_info": {
"finish_reason": "stop",
"logprobs": null
},
"type": "ChatGeneration",
...
}
]
],
"llm_output": {
"token_usage": {
"completion_tokens": 9,
"prompt_tokens": 8,
"total_tokens": 17,
...
},
"model_provider": "openai",
"model_name": "gpt-4o-mini-2024-07-18",
...
},
}Nota: El formato de salida varía según el proveedor de LLM. Este ejemplo muestra la estructura de OpenAI.
Lo que revela la salida de depuración:
El modo de depuración muestra el flujo completo de comunicación LangChain → OpenAI:
1. Transformación de formato de mensaje:
# Tu código
[HumanMessage(content="Hola")]
# Lo que ves en la salida de depuración
{
"prompts": ["Human: Hola"]
}El modo de depuración muestra cómo LangChain representa tu mensaje internamente antes de enviarlo al LLM.
2. Estado de finalización de generación:
"finish_reason": "stop"Por qué terminó la generación:
"stop": El modelo completó la respuesta naturalmente"length": La respuesta fue cortada porque alcanzó el límite de max_tokens"tool_calls": El modelo terminó la generación produciendo instrucciones de llamada a herramientas en lugar de una respuesta de texto final (Capítulo 12)"content_filter": La respuesta fue bloqueada o suprimida debido a reglas de seguridad o moderación de contenido
Si ves "length", aumenta max_tokens para obtener la respuesta completa.
3. Desglose de uso de tokens:
"token_usage": {
"completion_tokens": 9,
"prompt_tokens": 8,
"total_tokens": 17,
"completion_tokens_details": {
"reasoning_tokens": 0 # Para modelos de razonamiento (o1/o3, etc.)
},
"prompt_tokens_details": {
"cached_tokens": 0 # Caché de prompts (ahorra costos)
}
}Más allá de los conteos básicos, puedes ver:
- reasoning_tokens: Pasos de razonamiento interno (solo para modelos de razonamiento)
- cached_tokens: Cuántos tokens de prompt se sirvieron desde caché (reduce costo)
4. Versión del modelo y huella digital:
"model_name": "gpt-4o-mini-2024-07-18",
"system_fingerprint": "fp_8bbc38b4db"- model_name: Versión exacta del snapshot (explica por qué las respuestas cambian con el tiempo)
- system_fingerprint: ID de configuración del backend de OpenAI (cambia cuando actualizan sistemas)
5. Tiempo de solicitud:
[llm/end] [llm:ChatOpenAI] [1.56s]El [1.45s] muestra la duración total de la solicitud—útil para identificar consultas lentas.
Siguiente: La Sección 3.6 muestra cómo manejar errores comunes de forma elegante.
3.6) Manejar fallos (Simular y corregir errores comunes)
Las aplicaciones LLM de producción enfrentan modos de fallo predecibles: credenciales faltantes, tiempos de espera de red, límites de tasa y entradas inválidas. Esta sección te muestra cómo manejar estos errores de forma elegante y construir aplicaciones robustas desde el día uno.
Los seis errores comunes
1. Clave API faltante
Cuándo ocurre esto: Intentas crear una instancia de ChatOpenAI, pero OPENAI_API_KEY no está configurada en tu entorno.
Ejemplo:
# El archivo .env no existe, o OPENAI_API_KEY no está definida
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hola")])Error que verás:
OpenAIError: The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variableCómo corregir:
- Verifica que tu archivo
.envexiste en la raíz del proyecto - Verifica que el nombre de la clave es exactamente
OPENAI_API_KEY(error común:OPENAPI_KEY) - Asegúrate de que
load_dotenv()se llama antes de crear el LLM
2. Clave API incorrecta
Cuándo ocurre esto: Tu archivo .env contiene una clave API inválida, expirada o copiada incorrectamente.
Ejemplo:
# .env tiene: OPENAI_API_KEY=sk-invalid-key-12345
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hola")])Error que verás:
AuthenticationError: Incorrect API key providedCómo corregir:
- Ve a https://platform.openai.com/api-keys
- Verifica que tu clave aún está activa (no revocada o expirada)
- Genera una nueva clave si es necesario
- Copia la clave completa cuidadosamente (error común: faltan primeros/últimos caracteres)
- Pega en
.envsin espacios extra:
OPENAI_API_KEY=sk-proj-claveexactaaqui3. Fallos de red
Cuándo ocurre esto: Tu conexión a internet se cae, o los servidores de OpenAI están temporalmente inaccesibles durante una solicitud.
Ejemplo:
# WiFi se desconecta a mitad de solicitud, o la API de OpenAI está caída
response = llm.invoke([HumanMessage(content="Hola")])Error que verás:
APIConnectionError: Connection errorCómo corregir:
- Verifica tu conexión a internet
- Verifica el estado de OpenAI en https://status.openai.com
4. Límites de tasa
Cuándo ocurre esto: Envías demasiadas solicitudes en poco tiempo y excedes tu cuota de API.
Ejemplo:
# Enviando 1000 solicitudes instantáneamente
for i in range(1000):
llm.invoke([HumanMessage(content=f"Solicitud {i}")])Error que verás:
RateLimitError: Rate limit reached for requestsCómo corregir:
- Verifica tus límites de tasa en https://platform.openai.com/account/limits
- Actualiza tu plan si necesitas límites más altos
- Usa procesamiento por lotes para cargas de trabajo grandes (cubierto en el Capítulo 6)
5. Nombre de modelo inválido
Cuándo ocurre esto: Especificas un nombre de modelo que no existe o no está disponible en tu plan.
Ejemplo:
llm = ChatOpenAI(model="gpt-99-ultra") # No existe
response = llm.invoke([HumanMessage(content="Hola")])Error que verás:
NotFoundError: The model `gpt-99-ultra` does not exist or you do not have access to itCómo corregir:
- Verifica los modelos disponibles en tu plan en https://platform.openai.com/docs/models
6. Límite de tokens excedido
Cuándo ocurre esto: Tu prompt es demasiado largo y excede la ventana de contexto máxima del modelo.
Ejemplo:
# Creando un prompt de 1 millón de caracteres
huge_prompt = "x" * 1_000_000
response = llm.invoke([HumanMessage(content=huge_prompt)])Error que verás:
BadRequestError: This model's maximum context length is 128000 tokens. However, your messages resulted in 250000 tokens.Cómo corregir:
- Verifica la longitud de entrada antes de enviar
- Conoce los límites de tu modelo:
- gpt-4o-mini: 128K tokens
- gpt-4o: 128K tokens
- gpt-5: 400K tokens
- Para documentos largos, usa fragmentación o resumen (cubierto en el Capítulo 9)
Próximos pasos: El Capítulo 4 muestra cómo diseñar plantillas de prompts reutilizables que separan la ingeniería de prompts del código de la aplicación.