LangGraph modela los agentes como grafos donde los nodos son funciones y las aristas son transiciones (incluyendo condicionales). Esto lo hace más explícito y debuggeable que los agentes de “caja negra” donde el modelo decide todo internamente.

En este tutorial construimos un agente de investigación que: busca información, evalúa si es suficiente, y decide si buscar más o responder.

Setup

pip install langgraph langchain-anthropic langchain-community

El estado del agente

En LangGraph, el estado es lo que se pasa entre nodos. Definimos explícitamente qué información lleva el agente:

from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    search_results: list[str]
    iterations: int
    final_answer: str | None

Los nodos del grafo

Cada nodo es una función que recibe el estado y devuelve actualizaciones:

from langchain_anthropic import ChatAnthropic
from langchain_community.tools import DuckDuckGoSearchRun

llm = ChatAnthropic(model="claude-sonnet-4-6")
search = DuckDuckGoSearchRun()

def search_node(state: AgentState) -> dict:
    """Busca información relevante para la query del usuario."""
    # Extraer la query del último mensaje del usuario
    query = state["messages"][-1].content
    
    # Generar queries de búsqueda con el LLM
    search_prompt = f"Genera 2 queries de búsqueda concisas para responder: {query}"
    queries_response = llm.invoke(search_prompt)
    
    # Ejecutar búsquedas
    results = []
    for line in queries_response.content.split('\n'):
        if line.strip():
            result = search.run(line.strip())
            results.append(result)
    
    return {
        "search_results": results,
        "iterations": state.get("iterations", 0) + 1
    }

def evaluate_node(state: AgentState) -> dict:
    """Evalúa si la información recopilada es suficiente."""
    query = state["messages"][-1].content
    results = "\n\n".join(state["search_results"])
    
    eval_prompt = f"""Tienes esta información:
{results}

¿Es suficiente para responder la pregunta: "{query}"?
Responde SOLO con "SUFICIENTE" o "INSUFICIENTE"."""
    
    evaluation = llm.invoke(eval_prompt)
    return {"messages": [{"role": "assistant", "content": evaluation.content}]}

def answer_node(state: AgentState) -> dict:
    """Genera la respuesta final basada en la investigación."""
    query = state["messages"][-1].content
    results = "\n\n".join(state["search_results"])
    
    answer_prompt = f"""Basándote en esta información:
{results}

Responde de forma completa y precisa: {query}"""
    
    answer = llm.invoke(answer_prompt)
    return {
        "messages": [{"role": "assistant", "content": answer.content}],
        "final_answer": answer.content
    }

La lógica de decisión

El nodo condicional decide si seguir buscando o responder:

def should_continue(state: AgentState) -> str:
    """Decide si buscar más o responder."""
    # Evitar loops infinitos
    if state.get("iterations", 0) >= 3:
        return "answer"
    
    # Revisar el último mensaje (evaluación)
    last_message = state["messages"][-1]
    if "SUFICIENTE" in last_message.content:
        return "answer"
    else:
        return "search"

Construir el grafo

from langgraph.graph import StateGraph, END

# Crear el grafo
workflow = StateGraph(AgentState)

# Añadir nodos
workflow.add_node("search", search_node)
workflow.add_node("evaluate", evaluate_node)
workflow.add_node("answer", answer_node)

# Definir el flujo
workflow.set_entry_point("search")
workflow.add_edge("search", "evaluate")

# Arista condicional desde evaluate
workflow.add_conditional_edges(
    "evaluate",
    should_continue,
    {
        "search": "search",  # vuelve a buscar
        "answer": "answer",  # genera respuesta
    }
)

workflow.add_edge("answer", END)

# Compilar
agent = workflow.compile()

Usar el agente

from langchain_core.messages import HumanMessage

def run_research_agent(question: str) -> str:
    result = agent.invoke({
        "messages": [HumanMessage(content=question)],
        "search_results": [],
        "iterations": 0,
        "final_answer": None,
    })
    return result["final_answer"]

# Ejemplo
respuesta = run_research_agent(
    "¿Cuáles son las principales diferencias entre Llama 4 y GPT-4.1?"
)
print(respuesta)

Visualizar el grafo

from IPython.display import Image, display

# En un notebook Jupyter
display(Image(agent.get_graph().draw_mermaid_png()))

Añadir persistencia

LangGraph soporta checkpointing para persistir el estado entre runs:

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()
agent_with_memory = workflow.compile(checkpointer=memory)

# Usar con thread_id para sesiones independientes
config = {"configurable": {"thread_id": "sesion-usuario-123"}}
result = agent_with_memory.invoke(initial_state, config=config)

Por qué LangGraph sobre alternativas

La ventaja de LangGraph sobre agentes basados en tool_use directo del modelo: el flujo de control es tuyo, no del modelo. El modelo puede ser reemplazado sin cambiar la lógica del agente. El grafo es debuggeable y testeable.

La desventaja: más boilerplate que dejar que el modelo decida todo. Para agentes simples con 2-3 herramientas, puede ser excesivo.


LangGraph v0.2+. Documentación completa en langchain-ai.github.io/langgraph.