Un diseño estándar de siete secciones para cada agente de LangGraph.
Una estructura de archivos fija para los agentes de LangGraph, que abarca desde las importaciones y el estado hasta las herramientas, nodos, aristas, el ensamblaje y la invocación, con plantillas y una extensión para múltiples agentes.
Fuentes: The LangGraph Mental Model: A standardized architecture guide for every agent you’ll ever build
Cualquiera que haya estudiado LangGraph durante un tiempo conoce esa sensación extraña: la idea tiene sentido, pero el código sigue sin aparecer. Un grafo contiene nodos, los nodos realizan tareas, las aristas determinan a dónde ir a continuación y el estado almacena información. Se podría esbozar en una pizarra en cinco minutos. Luego se abre un archivo vacío y nada llega. Se olvidan las importaciones, la clase que se desea usar no tiene un lugar evidente, y parece haber cinco formas distintas de escribir lo mismo.
Por qué el código parece más difícil que el concepto
La razón subyacente es que LangGraph no impone ninguna estructura propia. Te proporciona bloques de construcción útiles y deja que tú decidas cómo organizarlos. Cada tutorial y cada repositorio presenta las cosas de manera diferente, por lo que tu memoria nunca se fija en un único esquema.
La solución propuesta aquí consiste en elegir un diseño estándar y utilizarlo para cada agente, ya sea un chatbot diminuto o una cadena de procesos con muchos agentes que colaboran. Considérelo como una plantilla que puede memorizar y recuperar sin esfuerzo, algo similar a los diagramas que muestran lo que un agente ya ha hecho, pero dirigidos al código fuente en lugar de al historial de ejecución.
El archivo completo de un vistazo
Cada aplicación LangGraph puede escribirse como un único archivo con siete secciones en un orden fijo, de arriba hacia abajo:
langgraph_agent.py
│
├── MODULE 1: IMPORTS & CONFIGURATION
│ └── All your libraries, API keys, model setup
│
├── MODULE 2: STATE
│ └── The TypedDict that defines your agent's memory
│
├── MODULE 3: TOOLS (optional, but common)
│ └── Functions decorated with @tool that the LLM can call
│
├── MODULE 4: NODES
│ └── Functions that do the actual work at each graph step
│
├── MODULE 5: EDGES & ROUTING
│ └── Functions that decide what happens next
│
├── MODULE 6: GRAPH ASSEMBLY
│ └── Where you build, wire, and compile the graph
│
└── MODULE 7: ENTRYPOINT
└── The __main__ block or invoke() call that runs everything
Ese es el esqueleto. Cuando inicies un proyecto, abre un archivo y escribe los siete títulos de sección como comentarios, luego rellénalos uno por uno. El resto de este texto aborda cada sección, explicando para qué sirve, los nombres que debes reconocer y un modelo a copiar.
Sección 1: importaciones y configuración
Para qué sirve
Considera esta sección como la caja de herramientas. Un carpintero organiza todas sus herramientas antes de construir algo, y aquí le indicas a Python qué capacidades externas necesitas y configuras el modelo que utilizará el agente.
Nombres importantes
ChatOpenAIoChatAnthropices la clase del modelo de lenguaje, el “cerebro” del agente. Créala una vez aquí y réutilízala en todas partes.StateGraphes la clase principal que construye el grafo, similar al plano que dibujas.ENDes una constante especial que representa el punto final. Un borde que apunte a ella detiene la ejecución.MemorySaveres un marcador de posición que mantiene la memoria a lo largo de varias interacciones.
Plantilla
# --- Standard Library ---
import os
from typing import TypedDict, Annotated, Literal
# --- LangChain Core ---
from langchain_openai import ChatOpenAI # or ChatAnthropic, ChatGroq, etc.
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, BaseMessage
from langchain_core.tools import tool
# --- LangGraph Core ---
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages # The message reducer
from langgraph.prebuilt import ToolNode # Pre-built node for tool execution
from langgraph.checkpoint.memory import MemorySaver
# --- Configuration ---
# Always name your model variable 'llm' - it's the standard in every node
llm = ChatOpenAI(
model="gpt-4o", # or "claude-3-5-sonnet-20241022", etc.
temperature=0, # 0 = deterministic; raise for creativity
api_key=os.environ.get("OPENAI_API_KEY")
)
Por qué está organizado de esta manera
Las importaciones se dividen en cuatro grupos: la biblioteca estándar, LangChain (la capa de modelos), LangGraph (la capa de grafos) y la configuración. Esto sigue la estructura en capas de las propias bibliotecas, donde LangChain proporciona primitivas de IA y LangGraph se encarga de la orquestación. Mantener los grupos separados permite ver rápidamente qué pertenece a dónde cuando surge algún problema.
La costumbre más útil aquí es llamar al modelo llm. Cada función de nodo hará referencia a esa variable más adelante, y si un proyecto utiliza model mientras otro usa chat, el código deja de parecer uniforme y resulta más difícil de revisar rápidamente.
Sección 2: estado
Para qué sirve
El State es la memoria de trabajo del agente, una única fuente de verdad que se distribuye por todo el grafo. Cada nodo puede leerlo y escribir en él, mientras que el motor gestiona su ciclo de vida. De todo lo que existe en LangGraph, hacer bien el State es probablemente lo más importante.
Una pizarra blanca en una oficina es una buena analogía: quienquiera que entre en la sala, es decir, cada nodo, puede leer lo que hay allí y añadir algo, y la pizarra permanece intacta durante toda la reunión, que corresponde al funcionamiento completo del grafo.
Nombres importantes
TypedDictes una clase de Python que describe la estructura del estado, algo similar a un esquema. Tu clase State hereda de ella.Annotatedes un envoltorio para indicadores de tipo. LangGraph lo utiliza para asociar un reductor a un campo, es decir, una función que determina cómo un nuevo valor se fusiona con lo que ya existe.
add_messages es el reductor integrado para el campo de mensajes. En lugar de reemplazar la lista cada vez que un nodo agrega un mensaje, este agrega al final de ella. Es el reductor que utilizarás con mayor frecuencia.operator.add es otro reductor común, para campos que deben acumularse mediante sumas, como una lista de resultados o un contador.Plantilla
# ============================================================
# MODULE 2: STATE
# ============================================================
class AgentState(TypedDict):
# 'messages' is the heartbeat of almost every LangGraph agent.
# Annotated[list, add_messages] means: "this is a list, and when
# a node writes to it, append - don't replace."
messages: Annotated[list[BaseMessage], add_messages]
# Add custom fields below for your specific agent's needs.
# Fields without a reducer are REPLACED each time a node writes to them.
# Example: a simple string field (gets replaced each write)
current_task: str
# Example: a list you want to accumulate (use operator.add as reducer)
# results: Annotated[list[str], operator.add]
# Example: a counter
# iteration_count: int
Pensando en los reducers
Los reducers confunden a muchos principiantes, así que aquí hay una analogía. Imagina el estado como un formulario en papel que se pasa por una oficina. El campo messages es un bloc de notas adhesivas: todos los que manejan el formulario añaden una nota, y nadie elimina las anteriores. Eso es add_messages.
Un campo sin reducer es un único cuadro de texto. Cada persona sobrescribe lo que escribió la anterior, que es la configuración por defecto de LangGraph: gana la última escritura.
Existe una regla sencilla. Asigna siempre al campo messages Annotated[list, add_messages], elige Annotated[list, operator.add] para cada lista adicional que deba seguir creciendo, y deja todos los campos restantes sin nada.
Sección 3: herramientas
Para qué sirve
Las herramientas son las manos del agente. El LLM es el cerebro, que razona y toma decisiones, pero por sí solo no puede actuar en el mundo. Con herramientas puede navegar por la web, acceder a una API, ejecutar código, buscar información en una base de datos o ejecutar cualquier función de Python que se defina.
Esta sección es opcional. Un agente que solo genera texto y nunca interactúa con sistemas externos puede omitirla, aunque la mayoría de los agentes reales necesitan herramientas.
Nombres importantes
@tooles un decorador de LangChain que convierte una función normal de Python en una herramienta que LangGraph comprende. La documentación de la función se convierte en la descripción que lee el LLM al decidir si la utiliza.ToolNodees un nodo listo para usar, que se encuentra enlanggraph.prebuilt, y ejecuta las herramientas que el LLM haya elegido llamar, de modo que no es necesario escribir la lógica de ejecución uno mismo.
bind_tools es un método en su instancia de LLM que adjunta herramientas a ella. Después, el modelo sabe qué herramientas existen y puede solicitar una llamada emitiendo un mensaje especial tool_calls en lugar de texto simple.Plantilla
# ============================================================
# MODULE 3: TOOLS
# ============================================================
@tool
def search_web(query: str) -> str:
"""Search the web for current information about a topic.
Use this when you need real-time information that is not in
your training data, such as recent news or live prices.
Args:
query: The search query string.
Returns:
A string containing search results.
"""
# Your actual implementation here (e.g., Tavily, SerpAPI, etc.)
# Placeholder for illustration:
return f"Search results for: {query}"
@tool
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression and return the result.
Use this for any arithmetic, algebra, or numerical computation.
Args:
expression: A valid Python math expression as a string, e.g. '2 + 2 * 10'
Returns:
The computed result as a string.
"""
try:
return str(eval(expression))
except Exception as e:
return f"Error: {e}"
# Collect all tools into a list - this is the pattern you always follow
tools = [search_web, calculate]
# Bind tools to the LLM so it knows they exist and can choose to call them
llm_with_tools = llm.bind_tools(tools)
# Create the pre-built ToolNode that will execute tool calls automatically
tool_node = ToolNode(tools)
La documentación es más importante de lo que parece
Los principiantes a menudo pasan por alto esto: el modelo depende de su documentación para elegir una herramienta. Lee el texto dentro de las comillas triples. Si la documentación es vaga o está ausente, el LLM no sabrá cuándo su herramienta es la adecuada, así que escriba descripciones claras y específicas que indiquen qué hace la herramienta y cuándo usarla.
Sección 4: nodos
Para qué sirven
Los nodos son los procesadores. Cada uno es una función de Python que recibe el Estado actual, realiza alguna acción como llamar a un LLM, procesar datos o invocar una herramienta, y devuelve un diccionario con las actualizaciones para el Estado. Esa es la descripción completa de su función.
Un nodo nunca decide qué ocurre después. Realiza su trabajo y actualiza el estado, y la gestión de la redirección se aborda en la sección 5.
Nombres importantes
statees el parámetro que recibe cada nodo, un diccionario con la misma estructura que tuAgentState. Dentro de él se pueden leer valores comostate["messages"]ostate["current_task"].HumanMessage,AIMessageySystemMessageson los tres tipos de mensajes que utilizarás constantemente. El primero corresponde a lo que dijo el usuario, el segundo es la respuesta del modelo, y el tercero contiene tus instrucciones para el modelo, es decir, el prompt del sistema.invokees la forma en que llamas al LLM, ollm_with_tools, para obtener una respuesta. Pasas una lista de mensajes y recibes unAIMessage.
AgentState. Sus valores son actualizaciones, no el estado completo, y LangGraph las fusiona utilizando los reductores que definiste.Plantilla
# ============================================================
# MODULE 4: NODES
# ============================================================
# ── Node: Agent (the reasoning brain) ───────────────────────
def agent_node(state: AgentState) -> dict:
"""The central reasoning node. Calls the LLM and decides
whether to respond or call a tool."""
# Build the message list to send to the LLM.
# Always include a system message to set behavior.
system_prompt = SystemMessage(content=(
"You are a helpful assistant. Use the available tools "
"when you need real-time information or computation. "
"Respond clearly and concisely."
))
# The LLM receives the system prompt + all previous messages in state
messages_to_send = [system_prompt] + state["messages"]
# Call the LLM. Use llm_with_tools if you have tools; plain llm if not.
response = llm_with_tools.invoke(messages_to_send)
# Return the LLM's response as a state update.
# add_messages will APPEND this AIMessage to state["messages"].
return {"messages": [response]}
# ── Node: Summarizer (example of a non-LLM processing node) ─
def summarize_node(state: AgentState) -> dict:
"""Summarizes the conversation so far to keep context short.
This shows that nodes don't have to call an LLM - they can
do any Python processing."""
all_messages = state["messages"]
# Summarize with the LLM (a different prompt, same LLM)
summary_prompt = [
SystemMessage(content="Summarize the following conversation in 2-3 sentences."),
HumanMessage(content=str(all_messages))
]
summary_response = llm.invoke(summary_prompt)
# Replace messages with a fresh start containing just the summary
return {
"messages": [AIMessage(content=f"[Summary] {summary_response.content}")]
}
Una imagen de los nodos
Imagínese un nodo como un corredor de relevo. Recibe el bastón, que representa el estado, corre su distancia, es decir, realiza el trabajo, y pasa el bastón con los contenidos actualizados. No elige al siguiente corredor; esa es la tarea de quien dirige la carrera, las aristas.
Toda firma de nodo tiene el mismo formato: def my_node(state: AgentState) -> dict. La entrada es todo el estado, y la salida es un diccionario que contiene solo los campos que desea modificar.
Sección 5: aristas y enrutamiento
Para qué sirven
Las aristas son la lógica de decisión del grafo. Conectan los nodos entre sí y determinan cómo fluye la ejecución. LangGraph cuenta con dos tipos de ellas.
Aristas estáticas unen un nodo a otro sin ninguna condición: “después del resumidor, ir al agente”. No hay decisión, solo una conexión directa.
Aristas condicionales analizan el estado y devuelven una cadena que indica el siguiente nodo. Aquí reside la inteligencia del agente respecto a su propio flujo: ¿debe llamar a una herramienta, finalizar o repetir el proceso?
Nombres importantes
add_edge(A, B)crea una arista estática, lo que significa que el control siempre va de A a B.add_conditional_edges(source, routing_function, mapping)crea un ramal que parte desource. La función de enrutamiento recibe el estado actual y devuelve una cadena, mientras que el diccionariomappingtraduce esa cadena al nombre real del siguiente nodo.
tools_condition es una función de enrutamiento lista para usar proveniente de langgraph.prebuilt. Examina el último mensaje del estado en busca de llamadas a herramientas. Si existen, dirige el flujo a "tools"; de lo contrario, a END. Este es el patrón estándar para los agentes que utilizan herramientas y merece ser memorizado.START es la constante que representa el inicio del grafo. Establecer un punto de entrada implica agregar un arco desde START hasta el primer nodo.Plantilla
# ============================================================
# MODULE 5: EDGES & ROUTING
# ============================================================
# Import the pre-built tool routing function
from langgraph.prebuilt import tools_condition
# ── Custom Routing Function Example ─────────────────────────
def should_continue(state: AgentState) -> Literal["tools", "summarize", "__end__"]:
"""Custom router for the agent node.
Routing functions always:
1. Receive the current state as input
2. Return a string that maps to the next node (or END)
The return values must match the keys in add_conditional_edges' mapping.
"""
last_message = state["messages"][-1] # Look at what the LLM just said
# Case 1: The LLM decided to call a tool
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
# Case 2: The conversation is getting long - summarize before continuing
if len(state["messages"]) > 20:
return "summarize"
# Case 3: The LLM gave a direct answer - we're done
return "__end__" # LangGraph's internal name for END
Una imagen para el enrutamiento
Imagínese una función de enrutamiento como un operador de señales ferroviarias. Un tren llega a una estación cuando un nodo termina su proceso. El operador verifica el estado actual, que corresponde al “manifiesto”, y activa el interruptor adecuado para que el tren tome el ramal correcto. El operador nunca mueve al tren; LangGraph se encarga de eso.
Una regla importante a recordar: las funciones de enrutamiento nunca modifican el estado. Solo lo leen. Cualquier cambio debe realizarse en un nodo; por lo tanto, si siente la necesidad de actualizar el estado durante el enrutamiento, transfiera esa lógica a un nodo.
Sección 6: montaje del grafo
Para qué sirve
Aquí es donde todo se une. Considere esta sección como el día de construcción. Las secciones del 1 al 5 fueron de preparación, en las que se definieron los materiales y se cortaron y dispusieron las piezas; ahora se une la estructura conectando esas piezas y compilando el resultado.
El ensamblaje sigue siempre los mismos cinco pasos, que vale la pena memorizar: inicializar, registrar nodos, establecer el punto de entrada, conectar bordes, compilar.
Nombres importantes
StateGraph(AgentState)crea el constructor utilizando tu clase State como esquema, por lo que cada nodo debe devolver actualizaciones compatibles con él.add_node(name, function)registra un nodo. El nombre es la cadena que utilizarás dentro de los bordes, y la función es el nodo de la sección 4.set_entry_point(name)indica qué nodo se ejecuta primero cuando se invoca el grafo.add_edge(A, B)establece un enlace directo e incondicional desde A hasta B.add_conditional_edges(source, router, mapping)agrega una rama condicional desde el origen, utilizando tu función de enrutamiento y un mapeo de las cadenas devueltas a los nombres de destino.compile(checkpointer=...)sella el grafo en un objeto ejecutable (CompiledGraph). No se pueden añadir nodos ni aristas posteriormente. El parámetro opcional checkpointer activa la memoria persistente.
Plantilla
# ============================================================
# MODULE 6: GRAPH ASSEMBLY
# ============================================================
# ── Step 1: Initialize ──────────────────────────────────────
# Always pass your State class to StateGraph
graph_builder = StateGraph(AgentState)
# ── Step 2: Register All Nodes ──────────────────────────────
# Format: add_node("node_name_as_string", node_function)
# The string name is what you use in ALL edge definitions
graph_builder.add_node("agent", agent_node)
graph_builder.add_node("tools", tool_node) # The pre-built ToolNode from Module 3
graph_builder.add_node("summarize", summarize_node)
# ── Step 3: Set Entry Point ─────────────────────────────────
# Which node runs first when we invoke the graph?
graph_builder.set_entry_point("agent")
# ── Step 4: Wire the Edges ──────────────────────────────────
# Conditional edge from agent: check if we need tools, a summary, or we're done
graph_builder.add_conditional_edges(
"agent", # Source node
should_continue, # Routing function from Module 5
{
"tools": "tools", # If router returns "tools" → go to tools node
"summarize": "summarize", # If router returns "summarize" → go to summarize node
"__end__": END, # If router returns "__end__" → stop the graph
}
)
# Static edge: after tools run, always go back to agent (the ReAct loop)
graph_builder.add_edge("tools", "agent")
# Static edge: after summarization, always return to agent
graph_builder.add_edge("summarize", "agent")
# ── Step 5: Compile ─────────────────────────────────────────
# Without checkpointer: no persistent memory (stateless per invocation)
# With checkpointer: memory persists across turns (stateful conversations)
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
Una imagen para la asamblea
Una placa de circuito es la mejor comparación. Cada llamada a add_node coloca un componente. add_edge y add_conditional_edges sueldan los cables entre los componentes. compile() realiza la inspección final y sella la placa. Después de eso, está lista para recibir energía, lo cual se logra con invoke, y para cumplir su función.
La plantilla contiene el bucle ReAct fundamental: agente → herramientas → agente → herramientas y así sucesivamente hasta que el agente decide que ha terminado y se dirige a END. Este ciclo de razonamiento y acción subyace en casi todos los agentes de LangGraph que utilizan herramientas, y reconocerlo explica la mayoría de los grafos que encontrarás.
Sección 7: punto de entrada e invocación
Para qué sirve
Esta es la plataforma de lanzamiento. Todo lo anterior fue la definición, y esta sección corresponde a la ejecución: el código que hace funcionar el grafo, proporciona entrada y recopila salida.
Existen dos formas de ejecutarlo: invoke para un único proceso y streaming para obtener resultados de forma progresiva. También se necesita config, el diccionario que contiene los parámetros en tiempo de ejecución, en particular thread_id que se utiliza para la gestión de la memoria.
Nombres importantes
graph.invoke(input, config)ejecuta todo el grafo y devuelve el estado final. Al ser síncrono, no se recibe nada hasta que finaliza cada paso.graph.stream(input, config)también ejecuta el grafo, pero devuelve actualizaciones parciales del estado a medida que ocurren, lo cual es ideal para aplicaciones orientadas al usuario que necesitan mostrar el progreso en tiempo real.
config es el diccionario que se pasa a cualquiera de las llamadas. Su entrada clave es {"configurable": {"thread_id": "some_id"}}. El ID del hilo funciona como un ID de sesión, lo cual indica al checkpointer qué memoria restaurar y almacenar. Dos usuarios con IDs de hilo diferentes nunca comparten memoria en absoluto.HumanMessage(content=...) es la forma en que se envuelve la entrada del usuario antes de entregársela al grafo.Plantilla
# ============================================================
# MODULE 7: ENTRYPOINT & INVOCATION
# ============================================================
if __name__ == "__main__":
# ── Config: defines this conversation's memory session ──
# Change thread_id to start a fresh conversation.
# Keep the same thread_id to continue an existing one.
config = {"configurable": {"thread_id": "user-session-001"}}
# ── Single Invocation (synchronous) ─────────────────────
user_input = "What is the current price of Bitcoin?"
result = graph.invoke(
input={"messages": [HumanMessage(content=user_input)]},
config=config
)
# The result is the final state dictionary.
# Access the last message to get the agent's final answer.
final_answer = result["messages"][-1].content
print(f"Agent: {final_answer}")
# ── Streaming Invocation (for real-time output) ──────────
for chunk in graph.stream(
input={"messages": [HumanMessage(content=user_input)]},
config=config,
stream_mode="values" # Yields the full state after each node runs
):
# Each chunk is a state snapshot. The last message shows progress.
latest = chunk["messages"][-1]
if hasattr(latest, "content") and latest.content:
print(f"[Streaming] {latest.content}")
# ── Multi-turn Conversation Loop ─────────────────────────
print("\n--- Starting Interactive Session ---")
while True:
user_text = input("You: ").strip()
if user_text.lower() in ("exit", "quit", "bye"):
break
response = graph.invoke(
input={"messages": [HumanMessage(content=user_text)]},
config=config # Same config = same memory thread
)
print(f"Agent: {response['messages'][-1].content}\n")
La plantilla completa
A continuación se muestra el esqueleto mínimo completo que puede copiarse a un proyecto nuevo. Es el único archivo que vale la pena marcar como favorito: sustituya las implementaciones de ejemplo por su propia lógica y tendrá la base de cualquier agente LangGraph.
# ============================================================
# LANGGRAPH CANONICAL AGENT TEMPLATE
# Modules: Imports → State → Tools → Nodes → Edges → Assembly → Entrypoint
# ============================================================
# ── MODULE 1: IMPORTS & CONFIGURATION ───────────────────────
import os
from typing import TypedDict, Annotated, Literal
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, BaseMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# ── MODULE 2: STATE ─────────────────────────────────────────
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
# Add your custom fields here
# ── MODULE 3: TOOLS ─────────────────────────────────────────
@tool
def my_tool(input: str) -> str:
"""Describe clearly what this tool does and when the LLM should use it."""
return f"Result for: {input}"
tools = [my_tool]
llm_with_tools = llm.bind_tools(tools)
tool_node = ToolNode(tools)
# ── MODULE 4: NODES ─────────────────────────────────────────
def agent_node(state: AgentState) -> dict:
"""The reasoning node. Calls the LLM, optionally triggers tool calls."""
messages = [SystemMessage(content="You are a helpful assistant.")] + state["messages"]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
# ── MODULE 5: EDGES & ROUTING ───────────────────────────────
def should_continue(state: AgentState) -> Literal["tools", "__end__"]:
"""Decide: did the LLM call a tool, or did it give a final answer?"""
last_message = state["messages"][-1]
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
return "__end__"
# ── MODULE 6: GRAPH ASSEMBLY ────────────────────────────────
graph_builder = StateGraph(AgentState)
graph_builder.add_node("agent", agent_node)
graph_builder.add_node("tools", tool_node)
graph_builder.set_entry_point("agent")
graph_builder.add_conditional_edges(
"agent",
should_continue,
{"tools": "tools", "__end__": END}
)
graph_builder.add_edge("tools", "agent")
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
# ── MODULE 7: ENTRYPOINT ────────────────────────────────────
if __name__ == "__main__":
config = {"configurable": {"thread_id": "session-001"}}
while True:
user_text = input("You: ").strip()
if not user_text or user_text.lower() in ("exit", "quit"):
break
response = graph.invoke(
{"messages": [HumanMessage(content=user_text)]},
config=config
)
print(f"Agent: {response['messages'][-1].content}\n")
Yendo más allá: sistemas multiagente
Una vez que el enfoque de un solo agente parece natural, los sistemas multiagente dejan de resultar intimidantes. La idea central es que cada agente es simplemente un grafo compilado, y un grafo compilado puede ser a su vez un nodo dentro de un grafo supervisor más grande.
Nombres importantes en el trabajo con sistemas multiagente
- Un nodo supervisor es un agente cuya tarea no es responder al usuario, sino decidir qué especialista debe encargarse del trabajo. Se encarga de dirigir la comunicación entre subagentes.
- Un subgrafo es un grafo compilado registrado como nodo en otro grafo. Recibe y devuelve estados al igual que cualquier función de nodo ordinaria.
Command es un patrón más reciente, disponible a partir de LangGraph 0.2, en el que un nodo devuelve tanto una actualización de estado como una instrucción de enrutamiento en un único valor. En algunos diseños multiagente, esto elimina la necesidad de aristas condicionales separadas.Plantilla estructural
# ── MULTI-AGENT PATTERN ─────────────────────────────────────
# Each specialist is a compiled graph (a subgraph)
# Sub-agent 1: A researcher
researcher_graph = StateGraph(AgentState)
# ... (built with its own nodes, edges, and tools)
researcher = researcher_graph.compile()
# Sub-agent 2: A writer
writer_graph = StateGraph(AgentState)
# ... (built with its own nodes, edges, and tools)
writer = writer_graph.compile()
# ── SUPERVISOR NODE ─────────────────────────────────────────
def supervisor_node(state: AgentState) -> dict:
"""Decides which sub-agent should handle the current task."""
# The supervisor LLM decides: "researcher" or "writer" or "FINISH"
response = supervisor_llm.invoke(state["messages"])
return {"messages": [response], "next_agent": response.content}
def route_to_agent(state: AgentState) -> Literal["researcher", "writer", "__end__"]:
"""Routes to the appropriate sub-agent based on supervisor's decision."""
return state.get("next_agent", "__end__")
# ── SUPERVISOR GRAPH ────────────────────────────────────────
supervisor_builder = StateGraph(AgentState)
supervisor_builder.add_node("supervisor", supervisor_node)
supervisor_builder.add_node("researcher", researcher) # Subgraph as a node!
supervisor_builder.add_node("writer", writer) # Subgraph as a node!
supervisor_builder.set_entry_point("supervisor")
supervisor_builder.add_conditional_edges(
"supervisor",
route_to_agent,
{"researcher": "researcher", "writer": "writer", "__end__": END}
)
supervisor_builder.add_edge("researcher", "supervisor")
supervisor_builder.add_edge("writer", "supervisor")
supervisor_graph = supervisor_builder.compile(checkpointer=MemorySaver())
La versión multiagente tiene la misma estructura que la de un solo agente, y se aplican las mismas siete secciones. La única diferencia es que algunos de los “nodos” son grafos completos compilados. El modelo mental escala sin problemas.
Referencia rápida
Aquí tienes una ficha resumida que puedes imprimir o fijar, con las ideas principales en un solo lugar.
- Estado:
TypedDictdefine su estructura.Annotated[T, reducer]asigna un reductor a un campo.add_messageses el reductor estándar para listas de mensajes y agrega nuevos elementos en lugar de reemplazarlos.operator.addacumula listas o números. - LLM:
ChatOpenAIoChatAnthropices la clase, siempre vinculada al nombrellm.bind_tools(tools)informa al modelo sobre las herramientas disponibles.invoke(messages)lo llama y devuelve una respuesta. - Mensajes:
HumanMessagecontiene la entrada del usuario,AIMessagecontiene la salida del modelo, ySystemMessagecontiene sus instrucciones. Siempre se pasan ainvokecomo una lista. - Herramientas:
@toolconvierte una función de Python en algo que pueda ser llamado por el modelo, utilizando la documentación como descripción.ToolNode(tools)ejecuta automáticamente las llamadas a las herramientas.tools_conditionverifica si el modelo solicitó una herramienta. - Gráfico:
StateGraph(State)crea el constructor;add_node(name, fn)registra un nodo,set_entry_point(name)elige el primero,add_edge(A, B)establece un enlace incondicional,add_conditional_edges(source, router, mapping)crea un enlace seleccionado según el valor de retorno del router,compile(checkpointer=...)finaliza el proceso, yENDmarca el punto final. - Invocación:
graph.invoke(input, config)devuelve el estado final,graph.stream(input, config)proporciona actualizaciones de forma progresiva,config = {"configurable": {"thread_id": "..."}}identifica la sesión de memoria, yMemorySaver()es un controlador de estado en memoria.
Hacerlo memoria muscular
El objetivo no es enseñar a LangGraph desde cero. Se trata de proporcionar a tu cerebro un andamio estable que puedas utilizar automáticamente, de la misma manera que un músico recurre a una progresión armónica familiar o un cocinero prepara los ingredientes antes de comenzar.
Siete secciones —importaciones, estado, herramientas, nodos, aristas, ensamblaje y punto de entrada— conforman esta estructura. Los nombres dentro de cada sección son las notas, y el template completo representa la partitura musical.
Tanto un chatbot básico como un pipeline con decenas de nodos pueden adaptarse al mismo esquema. La complejidad aumenta, pero la estructura no, y esa estabilidad es lo que lo convierte en un estándar.
Una forma sensata de comenzar es empezar pequeño con el bucle ReAct mínimo: un nodo de agente, un nodo de herramienta y una arista condicional que utilice tools_condition. Haz que funcione primero, luego añade otro nodo y, más tarde, otro agente. La estructura se mantiene en cualquier tamaño.
Para conocer más sobre los backends de persistencia, los patrones que involucran intervención humana y las opciones de despliegue, la documentación oficial de LangGraph es el siguiente lugar al que recurrir.