Cómo crear tu primer agente LangGraph: Una guía práctica para principiantes
El primer agente LangGraph para principiantes: por qué son importantes los agentes, qué aporta LangGraph a LangChain, configuración del proyecto, y luego los estados, nodos, aristas y un bucle de LLM funcional.
Fuentes: Building Your First LangGraph Agent: A Practical Beginner’s Guide
Esta guía reconstruye el proceso desde las materias primas hasta un sistema funcional para: Construyendo tu primer agente LangGraph: Una guía práctica para principiantes. El enfoque está en pasos operativos, verificaciones explícitas y código que se puede incorporar directamente a un repositorio sin necesidad de adivinar la intención. En la etapa de visión general, se definen las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Se deben registrar los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de una demostración a entornos compartidos.
Por qué son importantes los agentes
Al trabajar en la etapa de “Por qué son importantes los agentes”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Haga un punto de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intente nuevamente un nodo posterior.
User question → LLM → Answer
User question → Retrieve context → LLM → Answer
User question → Agent decides what to do → Calls tools → Updates state → Produces answer
¿Qué es LangGraph?
Al trabajar en la etapa de “¿Qué es LangGraph?”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Haga un punto de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.
Configuración del proyecto
Al trabajar en la fase de configuración del proyecto, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe referirse a una sola responsabilidad y no a un proceso complicado. Haga una verificación después de los pasos costosos. El sistema de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intente nuevamente un nodo posterior. Al trabajar en la fase de configuración del proyecto, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos.
pip install langgraph langchain langchain-openai langchain-community python-dotenv pydantic
pip install jupyter notebook ipython
OPENAI_API_KEY=your_api_key_here
import os
import json
from typing import Optional, List
from dotenv import load_dotenv
from pydantic import BaseModel
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain.tools import tool
load_dotenv()
llm = init_chat_model("openai:gpt-4o-mini")
Paso 1: Definir el estado del agente
El paso 1 de definición funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y dificultan la reanudación después de interrupciones.
class PlayerState(BaseModel):
question: str
selected_tools: Optional[List[str]] = None
name: Optional[str] = None
club: Optional[str] = None
country: Optional[str] = None
number: Optional[int] = None
rating: Optional[int] = None
goals: Optional[List[int]] = None
minutes_played: Optional[List[int]] = None
summary: Optional[str] = None
Paso 2: Crear herramientas
La etapa 2, Crear Herramientas, funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Exponga herramientas con esquemas limitados y etiquetas explícitas de efectos secundarios. Los administradores necesitan saber qué llamadas modifican el estado antes de aprobarlas automáticamente.
Herramienta 1: Obtener información del jugador
La etapa Tool 1 Fetch Player funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Exponga herramientas con esquemas limitados y etiquetas explícitas de efectos secundarios. Los administradores necesitan saber qué llamadas modifican el estado antes de aprobarlas automáticamente. La etapa Tool 1 Fetch Player funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos.
@tool
def fetch_player_information_tool(name: str):
"""
Returns the football club and country of a player.
"""
data = {
"Haaland": {
"club": "Manchester City",
"country": "Norway"
},
"Kane": {
"club": "Bayern Munich",
"country": "England"
},
"Lautaro": {
"club": "Inter Milan",
"country": "Argentina"
},
"Ronaldo": {
"club": "Al-Nassr",
"country": "Portugal"
}
}
return data.get(name, {
"club": "unknown",
"country": "unknown"
})
def fetch_player_information(state: PlayerState):
return fetch_player_information_tool.invoke({
"name": state.name
})
Herramienta 2: Obtener número de camiseta
Para la etapa Tool 2 Fetch Jersey, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Autentique en la pasarela y vuelva a autorizarlo en el plano de datos. Un token portador por sí solo no constituye un límite entre tenencias.
@tool
def fetch_player_jersey_number_tool(name: str):
"""
Returns the jersey number of a football player.
"""
data = {
"Haaland": 9,
"Kane": 9,
"Lautaro": 10,
"Ronaldo": 7
}
return {
"number": data.get(name, 0)
}
def fetch_player_jersey_number(state: PlayerState):
return fetch_player_jersey_number_tool.invoke({
"name": state.name
})
Herramienta 3: Obtener la calificación del jugador
Para la etapa Tool 3 Fetch Player, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso desde un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las intentonas, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Autentique en la pasarela y vuelva a autorizarlo en el plano de datos. Un token portador por sí solo no constituye un límite entre tenencias.
@tool
def fetch_player_rating_tool(name: str):
"""
Returns the player's football video game rating.
"""
data = {
"Haaland": 92,
"Kane": 89,
"Lautaro": 88,
"Ronaldo": 90
}
return {
"rating": data.get(name, 0)
}
def fetch_player_rating(state: PlayerState):
return fetch_player_rating_tool.invoke({
"name": state.name
})
Paso 3: Agregar nodos de recuperación de datos
En la etapa Paso 3: Agregar datos, defina las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando una etapa falla, el error debe indicar una única responsabilidad y no un proceso complicado. Cite los pasajes que realmente sustentan la respuesta. Sin citas, los operadores no pueden distinguir entre alucinaciones y fallos en el indexado. En la etapa Paso 3: Agregar datos, defina las entradas, el responsable de la etapa y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la etapa a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos.
Recuperar objetivos
Al trabajar en la etapa de Recuperar objetivos, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Mida la tasa de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
def retrieve_goals(state: PlayerState):
data = {
"Haaland": [25, 40, 28, 33, 36],
"Kane": [33, 37, 41, 38, 29],
"Lautaro": [19, 25, 27, 24, 25],
"Ronaldo": [27, 32, 28, 30, 36]
}
return {
"goals": data.get(state.name, [0])
}
Recuperar minutos jugados
Al trabajar en la etapa de recuperación de minutos jugados, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestos los cambios posteriores en el código. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mida el rendimiento de recuperación con un conjunto fijo de preguntas antes de ajustar los prompts. El cambio constante de prompts rara vez soluciona un sistema de recuperación deficiente.
def retrieve_minutes_played(state: PlayerState):
data = {
"Haaland": [2108, 3102, 3156, 2617, 2758],
"Kane": [2924, 2850, 3133, 2784, 2680],
"Lautaro": [2445, 2498, 2519, 2773],
"Ronaldo": [3001, 2560, 2804, 2487, 2771]
}
return {
"minutes_played": data.get(state.name, [0])
}
Paso 4: Extraer el nombre del jugador
Al trabajar en la Etapa 4: Extraer la fase, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando una etapa falla, el fallo debe referirse a una única responsabilidad y no a un proceso complicado. Haga una verificación después de las etapas costosas. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intente nuevamente un nodo posterior. Al trabajar en la Etapa 4: Extraer la fase, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos.
def extract_name(state: PlayerState):
prompt = f"""
You are a football name extraction assistant.
Extract only the surname of the football player mentioned in the question.
Question:
{state.question}
Return only one word.
"""
response = llm.invoke([
HumanMessage(content=prompt)
]).content.strip()
return {
"name": response
}
Will Erling Haaland win the World Cup with Norway?
{"name": "Haaland"}
Paso 5: Crear un nodo de planificador
La fase de creación del Paso 5 funciona mejor cuando se trata como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación después de las interrupciones.
def planner(state: PlayerState):
prompt = f"""
You are a football assistant planner.
Available tools:
[
"fetch_player_jersey_number",
"fetch_player_information",
"fetch_player_rating"
]
User question:
{state.question}
Choose which tools are needed to answer the question.
Return only a valid JSON array of tool names.
Example:
["fetch_player_information", "fetch_player_rating"]
"""
response = llm.invoke([
HumanMessage(content=prompt)
]).content.strip()
try:
selected_tools = json.loads(response)
except Exception:
selected_tools = []
return {
"selected_tools": selected_tools
}
Paso 6: Escribir el resumen final
La etapa 6, “Escribir la fase”, funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado de los gráficos simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación después de las interrupciones.
def write_summary(state: PlayerState):
player_data = {
"name": state.name,
"club": state.club,
"country": state.country,
"number": state.number,
"rating": state.rating,
"goals": state.goals,
"minutes_played": state.minutes_played
}
prompt = f"""
You are a football journalist.
Using the data below, write a concise markdown summary about the player.
Player data:
{json.dumps(player_data, indent=4)}
The summary should include:
- Full player name if it can be inferred
- Country with a flag emoji if available
- Club if available
- Jersey number if available
- Rating if available
- Total goals in the last three seasons
- Average minutes per goal over the available data
- A direct answer to the user's question
User question:
{state.question}
"""
response = llm.invoke([
HumanMessage(content=prompt)
]).content
return {
"summary": response
}
Paso 7: Construir el flujo de trabajo LangGraph
La etapa 7, “Construir la plataforma”, funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando falla un paso, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Mantenga el estado del grafo simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación del proceso. La etapa 7, “Construir la plataforma”, funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos.
graph_builder = StateGraph(PlayerState)
graph_builder.add_node("extract_name", extract_name)
graph_builder.add_node("planner", planner)
graph_builder.add_node("fetch_player_jersey_number", fetch_player_jersey_number)
graph_builder.add_node("fetch_player_information", fetch_player_information)
graph_builder.add_node("fetch_player_rating", fetch_player_rating)
graph_builder.add_node("retrieve_goals", retrieve_goals)
graph_builder.add_node("retrieve_minutes_played", retrieve_minutes_played)
graph_builder.add_node("write_summary", write_summary)
Paso 8: Agregar bordes
En la fase 8 “Agregar bordes”, defina las entradas, el responsable de dicha fase y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la fase a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Coloque la aprobación humana en aquellos bordes que generen gastos o modifiquen datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial.
graph_builder.add_edge(START, "extract_name")
graph_builder.add_edge("extract_name", "planner")
def route_tools(state: PlayerState):
if state.selected_tools:
return state.selected_tools
return ["retrieve_goals"]
graph_builder.add_conditional_edges(
"planner",
route_tools,
{
"fetch_player_jersey_number": "fetch_player_jersey_number",
"fetch_player_information": "fetch_player_information",
"fetch_player_rating": "fetch_player_rating",
"retrieve_goals": "retrieve_goals"
}
)
graph_builder.add_edge("fetch_player_jersey_number", "retrieve_goals")
graph_builder.add_edge("fetch_player_information", "retrieve_goals")
graph_builder.add_edge("fetch_player_rating", "retrieve_goals")
graph_builder.add_edge("retrieve_goals", "retrieve_minutes_played")
graph_builder.add_edge("retrieve_minutes_played", "write_summary")
graph_builder.add_edge("write_summary", END)
graph = graph_builder.compile()
Paso 9: Ejecutar el agente
Para la Etapa 9, antes de modificar el código, defina las entradas, el responsable de la etapa y los criterios de finalización. Los operadores deben poder volver a ejecutar la etapa desde un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
result = graph.invoke({
"question": "Will Haaland be able to win the FIFA World Cup for Norway based on his recent performance?"
})
print(result["summary"])
## Erling Haaland Performance Summary
- **Player:** Erling Haaland
- **Country:** Norway 🇳🇴
- **Club:** Manchester City
- **FIFA-style rating:** 92
- **Recent goals:** 28, 33, and 36 in the last three seasons
- **Total goals in last three seasons:** 97
- **Average minutes per goal:** Around 88 minutes per goal
Haaland’s individual numbers are outstanding. His scoring rate, consistency, and elite rating show that he is one of the most dangerous forwards in world football.
However, winning the FIFA World Cup depends on much more than one player. Norway would need strong squad depth, tactical discipline, defensive quality, and consistent performances throughout the tournament.
So, based on recent performance, Haaland can significantly improve Norway’s chances, but a World Cup title would still be difficult to predict from individual statistics alone.
result
Por qué es importante este ejemplo
En la fase de “Por qué este ejemplo es importante”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Incluya la aprobación humana en aquellas tareas que implican gastos o modificaciones en datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial. En la fase de “Por qué este ejemplo es importante”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de la versión de demostración al entorno compartido.
1. Estado
Al trabajar en la etapa de 1 Estado, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Haga un punto de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.
2. Uso de herramientas
Al trabajar en la etapa 2 de Uso de Herramientas, anote primero el contrato: los insumos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar bucles sin esa huella desperdicia horas.
3. Planificación
Al trabajar en la etapa de Planificación 3, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Haga puntos de control después de los pasos costosos. El sistema de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intente nuevamente un nodo posterior. Al trabajar en la etapa de Planificación 3, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos.
4. Enrutamiento condicional
La etapa de enrutamiento condicional 4 funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el diagrama.