LangChain Agents
GuΓa completa de creaciΓ³n de agentes inteligentes con LangChain y LangGraph. Cubre desde agentes bΓ‘sicos con tools hasta flujos multi-paso con LangGraph, memoria persistente, RAG y deployment.
LangChain Basics
LangChain proporciona abstracciones para interactuar con LLMs: prompts, chains, tools y agents. El flujo bΓ‘sico es: input β prompt β LLM β output parser.
Componentes de LangChain
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LangChain Stack β
β β
β ββββββββββββ ββββββββββββββ ββββββββββββββββββββββββ β
β β Prompts β β Models β β Output Parsers β β
β β Templatesβ β ChatGPT β β JSON, Pydantic β β
β β Few-shot β β Claude β β Structured Output β β
β ββββββββββββ β Ollama β ββββββββββββββββββββββββ β
β ββββββββββββββ β
β ββββββββββββ ββββββββββββββ ββββββββββββββββββββββββ β
β β Chains β β Agents β β Memory β β
β β LCEL β β ReAct β β Buffer, Summary β β
β β RunnableSβ β Tools β β Redis, PostgreSQL β β
β ββββββββββββ ββββββββββββββ ββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Retrievers β Vector stores, BM25, Ensemble β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# ββ Chain bΓ‘sico con LCEL (LangChain Expression Language) ββ
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", "Eres un asistente tΓ©cnico experto en {domain}."),
("human", "{question}"),
])
# LCEL: pipe operator para componer componentes
chain = prompt | llm | StrOutputParser()
# Invocar
response = chain.invoke({
"domain": "Kubernetes",
"question": "ΒΏCΓ³mo funciona el HPA?",
}) Tools & Function Calling
Las tools extienden al LLM con capacidades del mundo real: consultar bases de datos, llamar APIs, ejecutar cΓ³digo, buscar documentos, etc.
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
# ββ Definir tools ββ
@tool
def search_database(query: str) -> str:
"""Busca informaciΓ³n en la base de datos. Usar para consultas de datos."""
# Ejecutar query SQL...
return f"Resultados para: {query}"
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""EnvΓa un email. Usar cuando el usuario pida notificar a alguien."""
# Enviar email...
return f"Email enviado a {to}"
@tool
def get_weather(city: str) -> str:
"""Obtiene el clima actual de una ciudad."""
# Llamar API de clima...
return f"Clima en {city}: 22Β°C, soleado"
# ββ Crear agente con tools ββ
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools([search_database, send_email, get_weather])
# El LLM decide quΓ© tool usar basΓ‘ndose en la pregunta
response = llm_with_tools.invoke("ΒΏQuΓ© clima hace en Madrid?")
print(response.tool_calls)
# [{'name': 'get_weather', 'args': {'city': 'Madrid'}}] Docstrings de tools
El LLM usa el docstring de cada tool para decidir cuΓ‘ndo usarla. Escribe docstrings claros y especΓficos. Incluye cuΓ‘ndo usar la tool y cuΓ‘ndo no.
LangGraph Workflows
LangGraph construye agentes como grafos de estados con ciclos y branching. Es la evoluciΓ³n de los agents legacy de LangChain, con control explΓcito del flujo de ejecuciΓ³n.
Flujo de un agente LangGraph (ReAct pattern)
βββββββββββββββ
β START β
ββββββββ¬βββββββ
β
ββββββββΌβββββββ
βββββββ Reason βββββββββββββββ
β β (LLM call) β β
β ββββββββ¬βββββββ β
β β β
β ββββββββΌβββββββ β
β β ΒΏTool call?β β
β ββββ¬βββββββ¬ββββ β
β No β β SΓ β
β β β β
ββββββββΌβββ β βββββΌββββββββββ β
β END β β β Execute β β
β(respond)β β β Tool(s) ββββββββ
βββββββββββ β βββββββββββββββ
β
ββββββββΌβββ
β END β
β(respond)β
βββββββββββ from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
# ββ Definir modelo y tools ββ
llm = ChatOpenAI(model="gpt-4o")
tools = [search_database, get_weather, send_email]
llm_with_tools = llm.bind_tools(tools)
# ββ Nodo de reasoning ββ
def reasoning_node(state: MessagesState):
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# ββ Construir grafo ββ
graph = StateGraph(MessagesState)
graph.add_node("reason", reasoning_node)
graph.add_node("tools", ToolNode(tools))
# Flujo: START β reason β [tools β reason] β END
graph.add_edge(START, "reason")
graph.add_conditional_edges("reason", tools_condition)
graph.add_edge("tools", "reason")
# Compilar
agent = graph.compile()
# Ejecutar
result = agent.invoke({
"messages": [("human", "ΒΏQuΓ© clima hace en Tokyo?")]
}) Memory & Persistence
Los agentes necesitan memoria para mantener contexto entre conversaciones. LangGraph soporta checkpointing para persistir el estado del grafo.
from langgraph.checkpoint.postgres import PostgresSaver
# ββ Persistir estado en PostgreSQL ββ
DB_URI = "postgresql://user:pass@localhost/agents"
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
agent = graph.compile(checkpointer=checkpointer)
# ConversaciΓ³n con thread_id para identificar sesiones
config = {"configurable": {"thread_id": "user-123-session-1"}}
# Primer mensaje
result = agent.invoke(
{"messages": [("human", "Mi nombre es Diego")]},
config,
)
# Segundo mensaje β el agente recuerda el nombre
result = agent.invoke(
{"messages": [("human", "ΒΏCΓ³mo me llamo?")]},
config,
)
# β "Tu nombre es Diego" Opciones de checkpointer
LangGraph ofrece checkpointers para PostgreSQL,
SQLite y Memory (desarrollo).
En producciΓ³n, siempre usa PostgreSQL para durabilidad.
RAG Integration
Retrieval-Augmented Generation (RAG) permite al agente consultar bases de conocimiento propias antes de responder, mejorando la precisiΓ³n y reduciendo alucinaciones.
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_core.tools import tool
# ββ Crear vector store ββ
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory="./chroma_db",
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# ββ Tool de RAG para el agente ββ
@tool
def search_knowledge_base(query: str) -> str:
"""Busca en la base de conocimiento interna. Usar para preguntas
sobre documentaciΓ³n, polΓticas o procedimientos de la empresa."""
docs = retriever.invoke(query)
return "\\n\\n".join(d.page_content for d in docs)
# AΓ±adir la tool de RAG al agente
tools = [search_knowledge_base, search_database, send_email] Streaming & Error Handling
En producciΓ³n, los agentes necesitan streaming para dar feedback en tiempo real al usuario y manejo robusto de errores para recuperarse de fallos en tools o en el LLM sin perder el contexto de la conversaciΓ³n.
Flujo de streaming con recuperaciΓ³n de errores
ββββββββββββ βββββββββββββ ββββββββββββββββ
β Client βββββββ Streaming βββββββ Agent β
β (SSE) β β Events β β LangGraph β
ββββββββββββ βββββββββββββ ββββββββ¬ββββββββ
β
ββββββββΌββββββββ
β Tool Call β
ββββββββ¬ββββββββ
β
βββββββββββΌββββββββββ
SΓ ββββ ΒΏError / Timeout? ββββΆ No
β βββββββββββββββββββββ β
ββββββββΌβββββββ ββββββββΌβββββββ
β Retry con β β Continuar β
β backoff β β flujo β
ββββββββ¬βββββββ βββββββββββββββ
β
ββββββββΌβββββββ
β ΒΏMax retry? ββββΆ SΓ β Fallback / Error graceful
ββββββββ¬βββββββ
No β
Reintentar Streaming de respuestas con astream_events
El mΓ©todo astream_events de LangGraph permite enviar al frontend cada paso del agente en tiempo real: el razonamiento del LLM, las llamadas a tools y los resultados parciales.
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
# ββ Agente con streaming completo ββ
llm = ChatOpenAI(model="gpt-4o", streaming=True)
tools = [search_database, get_weather, send_email]
llm_with_tools = llm.bind_tools(tools)
def reasoning_node(state: MessagesState):
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
graph = StateGraph(MessagesState)
graph.add_node("reason", reasoning_node)
graph.add_node("tools", ToolNode(tools))
graph.add_edge(START, "reason")
graph.add_conditional_edges("reason", tools_condition)
graph.add_edge("tools", "reason")
agent = graph.compile()
# ββ Streaming de eventos en tiempo real ββ
async def stream_agent_response(user_message: str):
"""Stream completo del agente: tokens, tool calls, resultados."""
input_msg = {"messages": [("human", user_message)]}
async for event in agent.astream_events(input_msg, version="v2"):
kind = event["event"]
# Tokens del LLM llegando uno a uno
if kind == "on_chat_model_stream":
chunk = event["data"]["chunk"]
if chunk.content:
print(chunk.content, end="", flush=True)
# El agente decidiΓ³ usar una tool
elif kind == "on_tool_start":
tool_name = event["name"]
tool_input = event["data"].get("input", {})
print(f"\n[Tool] {tool_name}({tool_input})")
# Resultado de la tool
elif kind == "on_tool_end":
tool_output = event["data"].get("output", "")
print(f"[Result] {tool_output[:200]}")
# Ejecutar
import asyncio
asyncio.run(stream_agent_response("Busca el clima en Madrid y envΓa un email")) Manejo de errores en tools con ToolException
Cuando una tool falla, el agente debe recibir un mensaje de error comprensible para decidir si reintentar, usar otra tool o informar al usuario. ToolException permite comunicar errores al LLM de forma controlada.
from langchain_core.tools import tool, ToolException
# ββ Tool con manejo de errores ββ
@tool(handle_tool_error=True)
def query_api(endpoint: str) -> str:
"""Consulta la API interna. Devuelve datos JSON del endpoint solicitado."""
import httpx
try:
response = httpx.get(
f"https://api.internal.com{endpoint}",
timeout=10.0,
)
response.raise_for_status()
return response.text
except httpx.TimeoutException:
raise ToolException(
f"Timeout al consultar {endpoint}. "
"La API no respondiΓ³ en 10s. Intenta con otro endpoint o espera."
)
except httpx.HTTPStatusError as e:
raise ToolException(
f"Error HTTP {e.response.status_code} en {endpoint}. "
f"Mensaje: {e.response.text[:200]}"
)
# ββ Tool con fallback automΓ‘tico ββ
@tool
def search_primary(query: str) -> str:
"""Busca en el motor de bΓΊsqueda principal."""
# Si falla, el agente usarΓ‘ search_fallback
raise ToolException("Motor de bΓΊsqueda principal no disponible")
@tool
def search_fallback(query: str) -> str:
"""Busca en el motor de bΓΊsqueda secundario. Usar si el principal falla."""
return f"Resultados de backup para: {query}" Retry con backoff exponencial para llamadas LLM
Las APIs de LLM pueden fallar por rate limiting (429) o errores transitorios (500, 503). Implementar retry con backoff exponencial es esencial en producciΓ³n.
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnableConfig
import asyncio
# ββ LLM con retry integrado ββ
# ChatOpenAI ya soporta retry nativo via max_retries
llm = ChatOpenAI(
model="gpt-4o",
temperature=0,
max_retries=3, # Reintentos automΓ‘ticos para 429/500/503
request_timeout=30, # Timeout por request individual
)
# ββ Retry personalizado a nivel de nodo del grafo ββ
from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type,
)
from openai import RateLimitError, APITimeoutError
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=30),
retry=retry_if_exception_type((RateLimitError, APITimeoutError)),
before_sleep=lambda info: print(
f"Retry {info.attempt_number}/3 en {info.idle_for:.1f}s..."
),
)
def reasoning_with_retry(state: MessagesState):
"""Nodo de reasoning con retry y backoff exponencial."""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]} ConfiguraciΓ³n de timeouts por nodo
LangGraph permite configurar timeouts globales y por nodo para evitar que un agente quede colgado indefinidamente esperando una tool lenta o un LLM saturado.
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
import asyncio
# ββ Nodo con timeout individual ββ
async def reasoning_with_timeout(state: MessagesState):
"""Reasoning node con timeout de 60 segundos."""
try:
response = await asyncio.wait_for(
llm_with_tools.ainvoke(state["messages"]),
timeout=60.0,
)
return {"messages": [response]}
except asyncio.TimeoutError:
# Devolver mensaje de error al agente para que se recupere
from langchain_core.messages import AIMessage
return {
"messages": [
AIMessage(content="El LLM tardΓ³ demasiado. Simplificando la consulta...")
]
}
# ββ Timeout global para toda la ejecuciΓ³n del agente ββ
async def run_agent_with_global_timeout(user_message: str, timeout: float = 120.0):
"""Ejecutar agente con timeout mΓ‘ximo de 2 minutos."""
try:
result = await asyncio.wait_for(
agent.ainvoke({"messages": [("human", user_message)]}),
timeout=timeout,
)
return {"status": "success", "result": result}
except asyncio.TimeoutError:
return {
"status": "timeout",
"error": f"El agente no completΓ³ en {timeout}s",
}
# ββ Agente completo con streaming, retry y error recovery ββ
async def production_agent(user_message: str):
"""Agente production-ready con todas las protecciones."""
config = RunnableConfig(
configurable={"thread_id": "session-001"},
recursion_limit=15, # MΓ‘ximo 15 iteraciones del grafo
)
try:
async for event in agent.astream_events(
{"messages": [("human", user_message)]},
config=config,
version="v2",
):
yield event
except Exception as e:
yield {
"event": "on_error",
"data": {"error": str(e), "type": type(e).__name__},
} | ParΓ‘metro | Valor recomendado | PropΓ³sito |
|---|---|---|
max_retries | 3 | Reintentos automΓ‘ticos para errores transitorios del LLM |
request_timeout | 30s | Timeout por llamada individual al LLM |
recursion_limit | 10-25 | MΓ‘ximo de iteraciones del grafo para evitar loops infinitos |
| Global timeout | 120s | Timeout total de la ejecuciΓ³n del agente |
handle_tool_error | True | Pasar errores de tools al LLM en vez de crashear |
Recursion limit en producciΓ³n
Siempre configura recursion_limit en LangGraph. Sin lΓmite, un agente
con tools defectuosas puede entrar en un loop infinito de llamadas al LLM,
consumiendo tokens y dinero sin control. Un valor de 15-25 es razonable
para la mayorΓa de casos de uso.