Inicio / Artículos

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.

Editor: REACTAPP.TOP16 min de lectura3597 palabras

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.

5. Razonamiento final

Mejoras posibles

Pensamientos finales

Muchas gracias por leer esto.

Si desea mantenerse en contacto o ver más de lo que hago, puede encontrarme aquí:

Lista de verificación operativa