Tutorial de inicio de LangGraph

LangGraph es un framework desarrollado por el equipo de LangChainMarco de orquestación de agentes de bajo nivel, diseñado específicamente para construir flujos de trabajo de IA con estado (Stateful) y de larga duración.

A diferencia de las cadenas lineales tradicionales de llamadas a LLM, LangGraph modela el flujo de trabajo comoun grafo dirigido (Directed Graph):

  • Nodo (Node): función que ejecuta una operación específica (como llamar a un LLM, ejecutar herramientas, procesar datos)
  • Arista (Edge): define las rutas de transición entre nodos, admite ramificaciones condicionales
  • Estado (State): datos compartidos y transmitidos a lo largo de todo el flujo de trabajo

Dirección del código abierto:https://github.com/langchain-ai/langgraph。

Imagina que estás dirigiendo una actuación sinfónica: la cadena tradicional de LLM es como tocar una pieza de principio a fin, solo se puede reproducir en orden; LangGraph, en cambio, es como un director de orquesta que puede ajustar el orden de la interpretación en cualquier momento según la reacción del público, hacer que un movimiento se repita o saltar a un pasaje específico. Esto da a los flujos de trabajo de IA la sabiduría de un "director": ser capaz de hacer bucles, ramificarse y retroceder, logrando realmente una toma de decisiones autónoma compleja.

¿Por qué elegir LangGraph?

La siguiente tabla compara las principales diferencias entre la cadena de LLM tradicional y LangGraph:

Característica Cadena de LLM tradicional LangGraph
Estructura del flujo de trabajo Lineal, ejecución en una sola dirección Estructura de grafo, admite bucles
Gestión de estado Requiere gestión manual Persistencia de estado integrada
Enrutamiento condicional Implementación compleja Soporte nativo
Colaboración humano-máquina Requiere desarrollo adicional Soporte integrado para interrupt
Coordinación de múltiples agentes Difícil de implementar Soporte de primer nivel
Herramientas de depuración Limitadas LangGraph Studio

Casos de uso

  • Chatbot: necesita recordar el contexto de conversaciones de múltiples turnos
  • Agente autónomo: capaz de planificar, usar herramientas, pensar de forma iterativa
  • Sistema multi-agente: múltiples IA colaboran para completar tareas complejas
  • Flujo de trabajo de aprobación: procesos automatizados que requieren revisión humana
  • Asistente de investigación: requiere razonamiento de múltiples pasos y recuperación de información
Fondo Título Arquitectura central de LangGraph Marco grande exterior: StateGraph StateGraph (grafo de estado) Capa State - se extiende por la parte superior State (estado compartido) - atraviesa todos los nodos Área de nodos START Nodo inicial Node A Nodo de procesamiento Node B Nodo de decisión END Nodo final Nodo de ramificación Node C Rama alternativa Arista: START -> Node A Arista: Node A -> Node B Arista: Node B -> END (arista condicional) Completado Arista: Node B -> Node C (arista condicional) Se necesita más procesamiento Arista de bucle: Node C -> Node A Bucle Definición de flechas

Conceptos clave

Antes de empezar a escribir código, comprendamos los tres conceptos clave de LangGraph.

Graph (Grafo)

Graph es el plano de todo el flujo de trabajo, define la estructura lógica completa del agente. Está compuesto por nodos (Nodes) y aristas (Edges):

StateGraph
   |-- Nodes(节点)
   |     |-- node_a
   |     |-- node_b
   |     +-- node_c
   +-- Edges(边)
         |-- START -> node_a
         |-- node_a -> node_b(条件边)
         |-- node_a -> node_c(条件边)
         +-- node_b -> END

State (Estado)

State es, a lo largo de todo el grafo, laestructura de datos compartida. Cada nodo puede leer y actualizar State; el State actualizado se pasará al siguiente nodo.

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

class MyState(TypedDict):
    messages: Annotated[list, add_messages]  # 消息列表(自动追加)
    user_name: str                            # 用户名称
    step_count: int                           # 步骤计数
Importante:Annotated[list, add_messages]indica que este campo usaadd_messagescomo reducer: los nuevos mensajes se añaden a la lista en lugar de sobrescribirlos. Este es el mecanismo central de la gestión de estado de LangGraph.
Fondo Título Diagrama de flujo de datos de State State inicial State {msg: []} Flecha Node A Procesar entrada Flecha + actualizar State State actualización 1 State {msg: [A]} Flecha Node B Generar respuesta Flecha State actualización 2 State {msg: [A,B]} Flecha END Nota inferior Cada nodo lee State, ejecuta operaciones y devuelve los campos parcialmente actualizados Definición de flechas

Nodes (Nodos)

Los nodos son funciones comunes de Python que reciben el State actual y devuelven el State actualizado (campos parciales).

def my_node(state: MyState) -> dict:
    # 读取状态
    messages = state["messages"]
    
    # 执行操作...
    result = "处理结果"
    
    # 返回更新的字段(不需要返回所有字段)
    return {"messages": [{"role": "ai", "content": result}]}

Edges (Aristas)

Las aristas definen la forma de transición entre nodos:

  • Arista normal: ruta fija,node_a -> node_b
  • Arista condicional: enrutamiento dinámico según State,node_a -> node_b o node_c
  • Arista de inicio:START -> 第一个节点
  • Arista de fin:某节点 -> END

Configuración del entorno

Instalación de dependencias

Instale LangGraph y las dependencias relacionadas usando un espejo nacional (mirror doméstico):

# 创建虚拟环境(推荐)
python -m venv venv
source venv/bin/activate      # macOS/Linux
# venv\Scripts\activate       # Windows

# 安装 LangGraph 和 LangChain
pip install langgraph langchain langchain-openai python-dotenv -i https://mirrors.aliyun.com/pypi/simple/

# 可选:安装开发工具
pip install langgraph-cli jupyter -i https://mirrors.aliyun.com/pypi/simple/

Configuración de la API Key

Cree en la raíz del proyecto.envel archivo, y configure tanto OpenAI como DeepSeek:

# .env 文件内容

# OpenAI 配置(国外用户)
OPENAI_API_KEY=sk-xxx
OPENAI_BASE_URL=https://api.openai.com/v1

# DeepSeek 配置(国内用户推荐)
DEEPSEEK_API_KEY=sk-xxx
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-v4-pro
Consejo:Los ejemplos de este artículo son compatibles con OpenAI y DeepSeek. Se recomienda a los usuarios en China usar DeepSeek; la dirección de la API se configura comohttps://api.deepseek.com(sin /v1), LangChain completará automáticamente la ruta. La clave de API de DeepSeek se puede crear enhttps://platform.deepseek.com/api_keysCrear.

Verificar la instalación

import langgraph
print(f"LangGraph 版本: {langgraph.__version__}")

Primer programa LangGraph

Empecemos con el ejemplo más simple: un flujo de trabajo lineal con solo dos nodos.

Ejemplo

from langgraph.graph import StateGraph, START, END
from typing import TypedDict

# Paso 1: Definir State
class SimpleState(TypedDict):
    message: str
    processed: bool

# Paso 2: Definir las funciones de los nodos
def greet_node(state: SimpleState) -> dict:
    """Nodo de bienvenida: generar saludo"""
    print(f"[greet_node] Mensaje recibido: {state['message']}")
    return {"message": f"¡Hola! {state['message']}"}

def process_node(state: SimpleState) -> dict:
    """Nodo de procesamiento: marcar como procesado"""
    print(f"[process_node] Procesando mensaje: {state['message']}")
    return {"processed": True}

# Paso 3: Construir el grafo
builder = StateGraph(SimpleState)

# Añadir nodos
builder.add_node("greet", greet_node)
builder.add_node("process", process_node)

# Añadir aristas
builder.add_edge(START, "greet")
builder.add_edge("greet", "process")
builder.add_edge("process", END)

# Paso 4: Compilar el grafo
graph = builder.compile()

# Paso 5: Ejecutar
result = graph.invoke({
    "message": "mundo",
    "processed": False
})

print(f"\nResultado final: {result}")

Resultado de ejecución:

[greet_node] 收到消息: 世界
[process_node] 处理消息: 你好!世界

最终结果: {'message': '你好!世界', 'processed': True}

Visualizar la estructura del grafo

En Jupyter Notebook se puede visualizar directamente la estructura del grafo:

# 在 Jupyter Notebook 中可视化
from IPython.display import Image
Image(graph.get_graph().draw_mermaid_png())

# 或者打印 Mermaid 格式
print(graph.get_graph().draw_mermaid())

Gestión de State (estado)

Definir estado con TypedDict

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

class AgentState(TypedDict):
    # 消息历史(add_messages reducer 自动追加而非覆盖)
    messages: Annotated[list, add_messages]
    
    # 普通字段(直接覆盖)
    user_id: str
    session_id: str
    
    # 可选字段
    error: Optional[str]
    
    # 计数器(使用 operator.add 作为 reducer)
    retry_count: Annotated[int, lambda x, y: x + y]

Definir estado con Pydantic (recomendado para producción)

from pydantic import BaseModel, Field
from typing import Annotated
from langgraph.graph.message import add_messages

class ProductionState(BaseModel):
    messages: Annotated[list, add_messages] = Field(default_factory=list)
    user_id: str = ""
    confidence_score: float = 0.0
    
    class Config:
        arbitrary_types_allowed = True

MessagesState (estado abreviado integrado)

LangGraph proporciona un estado integradoMessagesState, diseñado específicamente para escenarios de diálogo:

from langgraph.graph import MessagesState

# MessagesState 等价于:
# class MessagesState(TypedDict):
#     messages: Annotated[list[AnyMessage], add_messages]

# 直接使用,无需自定义
builder = StateGraph(MessagesState)

Nodes (Nodos)

Nodos de función normal

def simple_node(state: AgentState) -> dict:
    # 读取状态
    last_message = state["messages"][-1]
    
    # 执行操作
    response = f"收到: {last_message.content}"
    
    # 返回部分状态更新
    return {
        "messages": [{"role": "assistant", "content": response}]
    }

Nodo de llamada a LLM

Ejemplo

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage

load_dotenv()

# Usar el modelo DeepSeek
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0
)

def llm_node(state: dict) -> dict:
    """Nodo que invoca al LLM"""
    system_prompt = SystemMessage(content="Eres un asistente útil.")
   
    # Combinar el mensaje del sistema con el historial de la conversación
    messages = [system_prompt] + state["messages"]
   
    # Invocar al LLM
    response = llm.invoke(messages)
   
    return {"messages": [response]}

Nodos asíncronos

import asyncio

async def async_node(state: AgentState) -> dict:
    """异步节点,适合 I/O 密集型操作"""
    # 模拟异步操作(如 API 调用、数据库查询)
    await asyncio.sleep(0.1)
    
    result = await some_async_api_call(state["messages"][-1].content)
    return {"messages": [{"role": "assistant", "content": result}]}

# 使用异步图
result = await graph.ainvoke({"messages": [...]})

Usar una clase como nodo

class RouterNode:
    def __init__(self, llm, system_prompt: str):
        self.llm = llm
        self.system_prompt = system_prompt
    
    def __call__(self, state: AgentState) -> dict:
        """类实例可以作为节点使用"""
        messages = [
            SystemMessage(content=self.system_prompt),
            *state["messages"]
        ]
        response = self.llm.invoke(messages)
        return {"messages": [response]}

# 添加类节点
router = RouterNode(llm, "你是一个专业的路由助手。")
builder.add_node("router", router)

Edges (Aristas) y enrutamiento condicional

Arista normal

# 固定路径:node_a 完成后始终执行 node_b
builder.add_edge("node_a", "node_b")

# 结束:node_a 完成后图结束
builder.add_edge("node_a", END)

Arista condicional

La arista condicional es una función central de LangGraph; decide dinámicamente el siguiente paso según el State actual.

def route_after_llm(state: AgentState) -> str:
    """
    路由函数:根据 LLM 的最新输出决定走哪条路径
    返回值必须是已注册节点名称或 END
    """
    last_message = state["messages"][-1]
    
    # 如果 LLM 请求使用工具
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    
    # 否则结束
    return END

# 添加条件边
builder.add_conditional_edges(
    "llm",              # 源节点
    route_after_llm,    # 路由函数
    {
        "tools": "tool_executor",   # 返回 "tools" 时 -> tool_executor 节点
        END: END                    # 返回 END 时 -> 结束
    }
)
Fondo Título Diagrama de enrutamiento condicional START Flecha Nodo Router Router Reconocimiento de intención Rama condicional Agente del clima weather_agent Consulta del clima Agente de código code_agent Ayuda con código Agente general general_agent Conversación general Despedida farewell Arista condicional "clima" "código" "otro" "adiós" END Todas las ramas van a END Definición de flechas

Ejecución en paralelo (Fan-out)

# 从一个节点并行分叉到多个节点
builder.add_edge("start_node", "branch_a")
builder.add_edge("start_node", "branch_b")
builder.add_edge("start_node", "branch_c")

# 多个节点汇聚到一个节点(Fan-in)
builder.add_edge("branch_a", "merge_node")
builder.add_edge("branch_b", "merge_node")
builder.add_edge("branch_c", "merge_node")

Ejemplo completo de enrutamiento condicional

Ejemplo

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

# Inicializar el LLM
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0.7
)

# Función de enrutamiento
def classify_intent(state: MessagesState) -> str:
    """Enruta a diferentes agentes según la intención del usuario"""
    last_message = state["messages"][-1]
    content = last_message.content.lower()
   
    if "clima" in content or "temperatura" in content:
        return "weather_agent"
    elif "código" in content or "programación" in content:
        return "code_agent"
    elif "adiós" in content or "salir" in content:
        return "farewell"
    else:
        return "general_agent"

# Definir los nodos de cada agente
def router_node(state: MessagesState) -> dict:
    """Nodo de enrutamiento: no procesa, solo se usa para activar la decisión de enrutamiento"""
    return {}

def weather_node(state: MessagesState) -> dict:
    """Agente del clima"""
    response = llm.invoke([
        SystemMessage(content="Eres un asistente del clima; responde amablemente a preguntas relacionadas con el clima. Si no tienes datos en tiempo real, puedes dar recomendaciones generales."),
        *state["messages"]
    ])
    return {"messages": [response]}

def code_node(state: MessagesState) -> dict:
    """Agente de código"""
    response = llm.invoke([
        SystemMessage(content="Eres un asistente de programación, experto en responder preguntas de código y proporcionar ejemplos de código claros."),
        *state["messages"]
    ])
    return {"messages": [response]}

def general_node(state: MessagesState) -> dict:
    """Agente general"""
    response = llm.invoke([
        SystemMessage(content="Eres un asistente de IA amable que puede responder varias preguntas."),
        *state["messages"]
    ])
    return {"messages": [response]}

def farewell_node(state: MessagesState) -> dict:
    """Nodo de despedida"""
    return {"messages": [{"role": "assistant", "content": "¡Adiós! Espero hablar contigo la próxima vez."}]}

# Construir el grafo
builder = StateGraph(MessagesState)

# Añadir nodos
builder.add_node("router", router_node)
builder.add_node("weather_agent", weather_node)
builder.add_node("code_agent", code_node)
builder.add_node("general_agent", general_node)
builder.add_node("farewell", farewell_node)

# Añadir aristas
builder.add_edge(START, "router")
builder.add_conditional_edges(
    "router",
    classify_intent,
    {
        "weather_agent": "weather_agent",
        "code_agent": "code_agent",
        "general_agent": "general_agent",
        "farewell": "farewell",
    }
)

# Terminar después de que todos los nodos agentes hayan procesado
for node in ["weather_agent", "code_agent", "general_agent", "farewell"]:
    builder.add_edge(node, END)

# Compilar el grafo
graph = builder.compile()

# Probar diferentes intenciones
test_inputs = [
    "¿Cómo está el clima hoy en Beijing?",
    "Ayúdame a escribir un quicksort en Python",
    "Hola, preséntate",
    "¡Adiós!"
]

for user_input in test_inputs:
    print(f"\n"Usuario: {user_input}")
    result = graph.invoke({"messages": [HumanMessage(content=user_input)]})
    print(f"Asistente: {result['messages'][-1].content[:100]}...")
    print("-" * 50)

Ejemplo de resultado de ejecución:

Usuario: 北京今天天气怎么样?
Asistente: 很抱歉,我没有实时天气数据。不过北京现在是春季,建议您出门时关注天气预报...
--------------------------------------------------

Usuario: 帮我写一个 Python 快速排序
Asistente: 好的,下面是一个 Python 实现的快速排序算法:

```python
def quicksort(arr):
    if len(arr) <= 1:
        return arr
    ...
--------------------------------------------------

Usuario: 你好,介绍一下你自己
Asistente: 你好!我是一个 AI 助手,很高兴为你服务。我可以帮你回答各种问题...
--------------------------------------------------

Usuario: 再见啦!
Asistente: 再见!期待下次与你交流。
--------------------------------------------------
Nota:El valor de retorno de la función de enrutamiento condicional debe ser el nombre de un nodo ya registrado en el grafo oEND. Si se devuelve un nombre no registrado, el grafo lanzará un error durante la ejecución.

Construir un chatbot

Ahora, con lo aprendido, construyamos un robot que admita conversaciones de múltiples turnos. Este robot puede recordar el contexto de la conversación y ofrecer una experiencia de interacción continua.

Ejemplo

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

# Inicializar el LLM (usando DeepSeek)
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0.7
)

SYSTEM_PROMPT = """Eres un asistente de IA amable y profesional.
Tus respuestas deben:
- Ser concisas y claras
- Responder en chino
- Preguntar activamente al usuario cuando no estés seguro
"""


def chatbot_node(state: MessagesState) -> dict:
    """Nodo central de conversación"""
    system = SystemMessage(content=SYSTEM_PROMPT)
    messages = [system] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [response]}

# Construir el grafo
builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot_node)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)

graph = builder.compile()

# Función de conversación de múltiples turnos
def chat(conversation_history: list, user_input: str) -> tuple[str, list]:
    """Procesa un turno de conversación y devuelve la respuesta de IA y el historial actualizado"""
    conversation_history.append(HumanMessage(content=user_input))
    result = graph.invoke({"messages": conversation_history})
    conversation_history = result["messages"]
    ai_response = conversation_history[-1].content
    return ai_response, conversation_history

# Ejemplo de conversación de múltiples turnos
history = []
while True:
    user_input = input("Tú: ")
    if user_input.lower() in ["salir", "exit", "quit"]:
        print("¡Adiós!")
        break
   
    response, history = chat(history, user_input)
    print(f"Asistente: {response}\n")

Ejemplo de resultado de ejecución:

Tú: 你好,我叫小明
Asistente: 你好,小明!很高兴认识你。有什么我可以帮助你的吗?

Tú: 我想学习 Python 编程
Asistente: 太好了!Python 是一门很适合初学者的编程语言。我可以帮你从基础开始:
1. 首先了解变量和数据类型
2. 学习条件语句和循环
3. 掌握函数的定义和使用
你想从哪个部分开始呢?

Tú: 你还记得我叫什么吗?
Asistente: 当然记得,你叫小明!你刚才说想学习 Python 编程,我们可以继续这个话题。

Tú: 退出
再见!

Llamada a herramientas - ReAct Agent

Dotar a los agentes de la capacidad de usar herramientas externas es una de las características más potentes de LangGraph.ReAct(Reason + Act) es el patrón de agente más común: el LLM piensa -> elige una herramienta -> ejecuta la herramienta -> observa el resultado -> continúa pensando.

Fondo Título Flujo de trabajo del agente ReAct Nodo START START Flecha START -> Agent Nodo Agent Agent Razonamiento del LLM Diamante de decisión condicional ¿Hay llamada a herramienta? Flecha Agent -> decisión condicional Nodo Tool Tools Ejecutar herramienta Flecha Decisión condicional -> Tool (sí) Sí Flecha de bucle Tool -> Agent Devolver el resultado y continuar razonando Nodo END END Flecha Decisión condicional -> END (no) no Definición de flechas

Definir herramientas

Primero, definamos las herramientas que el agente puede usar:

from langchain_core.tools import tool

@tool
def search_web(query: str) -> str:
    """搜索网络获取最新信息。
    
    Args:
        query: 搜索关键词
    
    Returns:
        搜索结果摘要
    """
    # 实际项目中替换为真实搜索 API
    return f"关于 '{query}' 的搜索结果:这是模拟的搜索结果..."

@tool
def calculate(expression: str) -> str:
    """计算数学表达式。
    
    Args:
        expression: 数学表达式,如 '2 + 2' o  '100 * 0.8'
    
    Returns:
        计算结果
    """
    import ast
    import operator
    
    # 安全的运算符映射
    ops = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.Pow: operator.pow,
        ast.USub: operator.neg,
    }
    
    def safe_eval(node):
        if isinstance(node, ast.Expression):
            return safe_eval(node.body)
        elif isinstance(node, ast.Constant):
            return node.value
        elif isinstance(node, ast.BinOp):
            left = safe_eval(node.left)
            right = safe_eval(node.right)
            return ops[type(node.op)](left, right)
        elif isinstance(node, ast.UnaryOp):
            operand = safe_eval(node.operand)
            return ops[type(node.op)](operand)
        else:
            raise ValueError(f"不支持的表达式类型: {type(node)}")
    
    try:
        tree = ast.parse(expression, mode='eval')
        result = safe_eval(tree)
        return f"计算结果: {expression} = {result}"
    except Exception as e:
        return f"计算错误: {str(e)}"

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息。
    
    Args:
        city: 城市名称
    
    Returns:
        天气信息
    """
    # 实际项目中替换为真实天气 API
    return f"{city} 今日天气:晴,温度 22C,湿度 60%"

tools = [search_web, calculate, get_weather]
Advertencia de seguridad:La herramienta calculate utiliza el análisis con AST (árbol de sintaxis abstracta) como alternativaeval(). Usar directamenteeval()presenta graves riesgos de seguridad, porque puede ejecutar código Python arbitrario. En entornos de producción, asegúrese de usar un método seguro de análisis de expresiones.

Construir un ReAct Agent

Ejemplo

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
import ast
import operator

load_dotenv()

# Definir herramientas
@tool
def search_web(query: str) -> str:
    """Buscar en la web la información más reciente."""
    return f"Resultados de búsqueda para '{query}': estos son resultados de búsqueda simulados..."

@tool
def calculate(expression: str) -> str:
    """Calcular una expresión matemática."""
    ops = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.Pow: operator.pow,
        ast.USub: operator.neg,
    }
   
    def safe_eval(node):
        if isinstance(node, ast.Expression):
            return safe_eval(node.body)
        elif isinstance(node, ast.Constant):
            return node.value
        elif isinstance(node, ast.BinOp):
            left = safe_eval(node.left)
            right = safe_eval(node.right)
            return ops[type(node.op)](left, right)
        elif isinstance(node, ast.UnaryOp):
            operand = safe_eval(node.operand)
            return ops[type(node.op)](operand)
        else:
            raise ValueError(f"Tipo de expresión no compatible: {type(node)}")
   
    try:
        tree = ast.parse(expression, mode='eval')
        result = safe_eval(tree)
        return f"Resultado del cálculo: {expression} = {result}"
    except Exception as e:
        return f"Error de cálculo: {str(e)}"

@tool
def get_weather(city: str) -> str:
    """Obtener información del clima de una ciudad específica."""
    return f"Clima de {city} hoy: despejado, temperatura 22C, humedad 60%"

tools = [search_web, calculate, get_weather]

# Inicializar el LLM y vincular herramientas
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0
)
llm_with_tools = llm.bind_tools(tools)

def agent_node(state: MessagesState) -> dict:
    """Nodo de razonamiento del agente: invocar al LLM para decidir la siguiente acción"""
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

# Construir el grafo ReAct
builder = StateGraph(MessagesState)

# Añadir nodo
builder.add_node("agent", agent_node)
builder.add_node("tools", ToolNode(tools))  # El ToolNode integrado maneja automáticamente las llamadas a herramientas

# Añadir arista
builder.add_edge(START, "agent")

# Enrutamiento condicional: si el LLM solicita una herramienta, ejecutar la herramienta; de lo contrario, finalizar
builder.add_conditional_edges(
    "agent",
    tools_condition,  # Función de enrutamiento integrada
    {
        "tools": "tools",
        END: END
    }
)

# Después de que la herramienta se ejecute, volver al agente para continuar el razonamiento
builder.add_edge("tools", "agent")

graph = builder.compile()

# Prueba
result = graph.invoke({
    "messages": [HumanMessage(content="¿Cómo está el clima hoy en Beijing? Además, ayúdame a calcular 1234 * 5678")]
})

for message in result["messages"]:
    print(f"[{message.type}]: {message.content[:200] if message.content else '(llamada a herramienta)'}")

Transmisión en flujo del proceso de llamada a herramientas

# 流式观察 Agent 的每一步
for chunk in graph.stream(
    {"messages": [HumanMessage(content="搜索 LangGraph 的最新特性")]},
    stream_mode="updates"
):
    for node_name, updates in chunk.items():
        print(f"\n=== 节点: {node_name} ===")
        for msg in updates.get("messages", []):
            if hasattr(msg, "tool_calls") and msg.tool_calls:
                for tc in msg.tool_calls:
                    print(f"  -> 调用工具: {tc['name']}({tc['args']})")
            else:
                print(f"  -> 输出: {msg.content[:200] if msg.content else '(无内容)'}")
Sugerencia:ToolNodeytools_conditionEs un componente integrado de LangGraph.ToolNodeAnaliza automáticamente las llamadas a herramientas devueltas por el LLM y ejecuta la función de herramienta correspondiente;tools_conditionEs una función de enrutamiento que verifica si la salida del LLM contiene una solicitud de llamada a herramienta; si la tiene, devuelve "tools"; de lo contrario, devuelve END.

Human-in-the-Loop (colaboración humano-máquina)

LangGraph admite de forma nativa la pausa durante la ejecución del flujo de trabajo, esperando una revisión o entrada humana. Esto es muy útil para operaciones sensibles que requieren confirmación humana.

Usar interrupt para pausar la ejecución

from langgraph.types import interrupt
from langgraph.checkpoint.memory import MemorySaver

def sensitive_action_node(state: MessagesState) -> dict:
    """执行敏感操作前请求人工审批"""
    last_msg = state["messages"][-1].content
    
    # 暂停图的执行,等待人工决策
    human_decision = interrupt({
        "question": "是否批准执行以下操作?",
        "action": last_msg,
        "risk_level": "中等"
    })
    
    if human_decision == "approve":
        return {"messages": [{"role": "assistant", "content": "操作已批准并执行完毕。"}]}
    else:
        return {"messages": [{"role": "assistant", "content": "操作已取消。"}]}

# 必须使用 checkpointer 才能支持 interrupt
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

Flujo de trabajo de aprobación completo

Ejemplo

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import HumanMessage

load_dotenv()

def request_node(state: MessagesState) -> dict:
    """Recibir la solicitud del usuario"""
    return {"messages": state["messages"]}

def approval_node(state: MessagesState) -> dict:
    """Nodo de aprobación: pausar y esperar la aprobación humana"""
    last_msg = state["messages"][-1].content
   
    # Pausar la ejecución, esperando la decisión humana
    human_decision = interrupt({
        "question": "¿Se aprueba la ejecución de la siguiente operación?",
        "action": last_msg
    })
   
    if human_decision == "approve":
        return {"messages": [{"role": "assistant", "content": f"Operación aprobada: {last_msg}"}]}
    else:
        return {"messages": [{"role": "assistant", "content": "La operación ha sido rechazada."}]}

def execute_node(state: MessagesState) -> dict:
    """Nodo de ejecución"""
    return {"messages": [{"role": "assistant", "content": "¡Tarea completada!"}]}

# Construir el grafo
builder = StateGraph(MessagesState)
builder.add_node("request", request_node)
builder.add_node("approval", approval_node)
builder.add_node("execute", execute_node)

builder.add_edge(START, "request")
builder.add_edge("request", "approval")
builder.add_edge("approval", "execute")
builder.add_edge("execute", END)

# Compilar el grafo usando checkpointer
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

# Usar un thread_id único para cada conversación
config = {"configurable": {"thread_id": "approval-session-001"}}

# Paso 1: Iniciar el grafo, se pausará en interrupt
print("=== Paso 1: Enviar solicitud ===")
result = graph.invoke(
    {"messages": [HumanMessage(content="Por favor, elimina todos los datos de prueba de la base de datos")]},
    config=config
)
print("El grafo se ha pausado, esperando aprobación...")

# Paso 2: Después de la aprobación humana, reanudar la ejecución con Command
print("\n"=== Paso 2: Aprobación humana ===")
# Aprobar la operación
result = graph.invoke(
    Command(resume="approve"),
    config=config
)
print(f"Resultado final: {result['messages'][-1].content}")

# Si se desea rechazar, usar:
# graph.invoke(Command(resume="reject"), config=config)

Establecer puntos de interrupción en las aristas

# 另一种方式:在编译时指定断点
graph = builder.compile(
    checkpointer=checkpointer,
    interrupt_before=["sensitive_node"],   # 执行该节点前暂停
    # interrupt_after=["review_node"],     # 执行该节点后暂停
)
Importante:checkpointeres la condición previa para implementarinterruptla funcionalidad. Sin checkpointer, el grafo no puede guardar el estado de pausa y, por lo tanto, no puede continuar la ejecución al reanudarse. MemorySaver es adecuado para desarrollo y pruebas; en producción se recomienda usar SqliteSaver u otro almacenamiento persistente.

Memoria persistente

LangGraph proporciona un mecanismo integrado de persistencia de estado que permite al agente recordar el historial de conversación entre sesiones.

Almacenamiento en memoria (adecuado para desarrollo y pruebas)

from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

# 使用 thread_id 区分不同会话
config_user_a = {"configurable": {"thread_id": "user-alice"}}
config_user_b = {"configurable": {"thread_id": "user-bob"}}

# Alice 的对话
graph.invoke({"messages": [HumanMessage(content="我叫 Alice")]}, config=config_user_a)
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_a)
# Agent 能记住:你叫 Alice

# Bob 的对话完全独立
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_b)
# Agent 不知道 Bob 的名字(不同 thread_id)

Almacenamiento persistente con SQLite (adecuado para proyectos locales)

# 安装依赖
# pip install langgraph-checkpoint-sqlite

from langgraph.checkpoint.sqlite import SqliteSaver

# 数据持久化到文件,程序重启后对话历史仍存在
with SqliteSaver.from_conn_string("./chat_memory.db") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    
    config = {"configurable": {"thread_id": "persistent-chat"}}
    
    # 第一次运行
    graph.invoke({"messages": [HumanMessage(content="我叫张三")]}, config=config)
    
    # 程序重启后再次运行,记忆仍然存在
    result = graph.invoke(
        {"messages": [HumanMessage(content="你还记得我叫什么吗?")]},
        config=config
    )

Ver historial de conversación

# 获取某个 thread 的完整状态历史
history = list(graph.get_state_history(config))

for snapshot in history:
    print(f"时间: {snapshot.created_at}")
    print(f"消息数: {len(snapshot.values['messages'])}")
    print("---")

# 获取当前状态
current_state = graph.get_state(config)
print(f"当前消息数: {len(current_state.values['messages'])}")
Sugerencia:thread_idEs el identificador único de la sesión, utilizado para aislar el estado de diferentes usuarios o diferentes conversaciones. Todas las llamadas con el mismo thread_id comparten el historial de conversación; los diferentes thread_id son completamente independientes entre sí. En escenarios multiusuario, normalmente se usa el ID de usuario o el ID de sesión como thread_id.

Sistemas multi-agente

LangGraph destaca en coordinar múltiples agentes especializados para trabajar en conjunto. Al descomponer tareas complejas entre diferentes agentes expertos, se puede lograr una capacidad de resolución de problemas más potente.

Contexto Título Arquitectura de colaboración multi-agente (modo Supervisor) Nodo Supervisor Supervisor Supervisor - Asignación de tareas Research Agent Recopilación de información Writing Agent Creación de contenido Review Agent Revisión de calidad Flechas del Supervisor hacia cada Agente Flechas de retorno de cada Agente al Supervisor (línea discontinua) Texto explicativo Asignar tareas Asignar tareas Devolver resultado Devolver resultado Nota al pie El Supervisor coordina a los agentes expertos; cada agente, al finalizar, devuelve el resultado al Supervisor para que este decida el siguiente paso. Definición de flechas

Arquitectura maestro-esclavo (Patrón Supervisor)

Ejemplo

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

# Inicializar el LLM
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0
)

# Definir agentes expertos
def research_agent(state: MessagesState) -> dict:
    """Agente de investigación: encargado de la recopilación de información"""
    system = SystemMessage(content="Eres un investigador profesional, encargado de recopilar y organizar información. Resume de manera concisa la información clave.")
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

def writing_agent(state: MessagesState) -> dict:
    """Agente de redacción: encargado de la creación de contenido"""
    system = SystemMessage(content="Eres un escritor profesional, encargado de redactar contenido basado en la información existente. Mantén el contenido claro y fluido.")
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

def review_agent(state: MessagesState) -> dict:
    """Agente de revisión: encargado del control de calidad"""
    system = SystemMessage(content="Eres un editor profesional, encargado de revisar y mejorar la calidad del contenido. Señala los problemas y da sugerencias de mejora.")
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

# El agente supervisor decide el flujo
def supervisor_node(state: MessagesState) -> dict:
    """Supervisor: coordina el trabajo de los agentes expertos"""
    system = SystemMessage(content="""Eres un supervisor de flujo de trabajo.
Decide qué agente debe encargarse del siguiente paso según el progreso de la tarea.
Analiza el historial de conversación y devuelve solo uno de los siguientes: RESEARCH, WRITING, REVIEW, FINISH
- RESEARCH: se necesita recopilar más información
- WRITING: la información es suficiente, se puede comenzar a escribir
- REVIEW: la redacción está completa, se necesita revisión
- FINISH: la tarea está completa
"""
)
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

def route_by_supervisor(state: MessagesState) -> str:
    """Enrutar según la decisión del supervisor"""
    last_msg = state["messages"][-1].content.strip().upper()
   
    if "RESEARCH" in last_msg:
        return "research"
    elif "WRITING" in last_msg:
        return "writing"
    elif "REVIEW" in last_msg:
        return "review"
    else:
        return END

# Construir el grafo multi-agente
builder = StateGraph(MessagesState)
builder.add_node("supervisor", supervisor_node)
builder.add_node("research", research_agent)
builder.add_node("writing", writing_agent)
builder.add_node("review", review_agent)

builder.add_edge(START, "supervisor")
builder.add_conditional_edges("supervisor", route_by_supervisor)

# Cada experto regresa al supervisor al finalizar
for agent in ["research", "writing", "review"]:
    builder.add_edge(agent, "supervisor")

graph = builder.compile()

# Probar la colaboración multi-agente
result = graph.invoke({
    "messages": [HumanMessage(content="Por favor, ayúdame a escribir un breve artículo introductorio sobre los decoradores de Python")]
})

print("=== Colaboración multi-agente completada ===")
for i, msg in enumerate(result["messages"]):
    print(f"\n[{i+1}] {msg.type}: {msg.content[:150]}...")

Subgrafo (Subgraph)

Encapsular subprocesos complejos como subgrafos para reutilizarlos en el grafo principal:

# 将复杂子流程封装为子图,在主图中复用
sub_builder = StateGraph(MessagesState)
sub_builder.add_node("step1", step1_node)
sub_builder.add_node("step2", step2_node)
sub_builder.add_edge(START, "step1")
sub_builder.add_edge("step1", "step2")
sub_builder.add_edge("step2", END)
sub_graph = sub_builder.compile()

# 在主图中使用子图
main_builder = StateGraph(MessagesState)
main_builder.add_node("preprocessing", preprocess_node)
main_builder.add_node("sub_workflow", sub_graph)  # 直接使用编译好的子图
main_builder.add_node("postprocessing", postprocess_node)

main_builder.add_edge(START, "preprocessing")
main_builder.add_edge("preprocessing", "sub_workflow")
main_builder.add_edge("sub_workflow", "postprocessing")
main_builder.add_edge("postprocessing", END)

main_graph = main_builder.compile()

Depuración visual con LangGraph Studio

LangGraph Studio es el entorno de desarrollo visual proporcionado oficialmente, que te permite ver el proceso de ejecución del agente en tiempo real, mejorando significativamente la eficiencia de desarrollo y depuración.

Instalar LangGraph CLI

pip install langgraph-cli -i https://mirrors.aliyun.com/pypi/simple/

Crear archivo de configuración del proyecto

Crear en el directorio raíz del proyectolanggraph.jsonArchivo de configuración:

{
  "dependencies": ["."],
  "graphs": {
    "my_agent": "./my_agent.py:graph"
  },
  "env": ".env"
}

Iniciar servidor de desarrollo

langgraph dev

Después de iniciar, accede ahttp://localhost:8123para usar LangGraph Studio en el navegador.

Funciones principales de Studio

  • Visualización en tiempo real: muestra gráficamente el proceso de ejecución de los nodos, permitiendo comprender de forma intuitiva el estado del flujo de trabajo
  • Inspección de estado: pausar en cualquier nodo para ver el State actual, facilitando la resolución de problemas
  • Viaje en el tiempo: reproducir los pasos de ejecución históricos y rastrear los cambios de estado en cada paso
  • Recarga en caliente: actualiza automáticamente la estructura del grafo después de modificar el código, sin necesidad de reiniciar el servicio
Requisitos del entorno:LangGraph Studio requiere soporte del entorno Docker. Asegúrate de tener Docker Desktop instalado y funcionando correctamente antes de iniciar el servidor de desarrollo.

Mejores prácticas y preguntas frecuentes

Mejores prácticas

Puntos clave del diseño de estado

  • Mantener el State conciso, incluyendo solo los campos necesarios
  • Definir reducers claros para campos complejos (por ejemplo,add_messages)
  • Usar modelos Pydantic para validar los tipos de estado en entornos de producción
  • Evitar almacenar objetos demasiado grandes en el State; considerar el uso de almacenamiento externo

Puntos clave del diseño de nodos

  • Cada nodo debe tener una única responsabilidad, facilitando las pruebas y la reutilización
  • Las funciones de nodo deben ser idempotentes (la misma entrada produce la misma salida)
  • Evitar modificar directamente el state recibido en el nodo; en su lugar, devolver nuevos valores
  • Usar adecuadamente nodos asíncronos para operaciones intensivas en E/S

Manejo de errores

def robust_node(state: AgentState) -> dict:
    try:
        result = risky_operation(state)
        return {"messages": [result], "error": None}
    except Exception as e:
        return {
            "error": str(e),
            "messages": [{"role": "assistant", "content": f"操作失败: {e}"}]
        }

Evitar bucles infinitos

def route_with_limit(state: AgentState) -> str:
    # 设置最大重试次数,防止无限循环
    if state.get("retry_count", 0) >= 3:
        return END
    
    if needs_retry(state):
        return "retry_node"
    return END

Preguntas frecuentes (FAQ)

P1: ¿Qué hacer si el formato del valor de retorno del nodo no es correcto?

# 错误:直接修改 state 对象
def bad_node(state):
    state["messages"].append(...)  # 不要直接修改
    return state

# 正确:返回需要更新的字段
def good_node(state):
    return {"messages": [new_message]}  # 只返回变更字段

P2: ¿Cómo pasar datos temporales entre nodos?

Agregue datos temporales a la definición de State, o use la convención de prefijo de subrayado para campos internos:

from typing import TypedDict

class PublicState(TypedDict):
    messages: list  # 对外暴露

class PrivateState(TypedDict):
    messages: list
    _internal_cache: dict  # 以下划线开头约定为内部使用

Q3: ¿Cómo depurar el proceso de ejecución de nodos?

# 使用 stream 模式观察每个节点的输出
for event in graph.stream(initial_state, stream_mode="updates"):
    for node_name, state_update in event.items():
        print(f"\n[节点: {node_name}]")
        print(f"更新: {state_update}")

Q4: ¿Cuál es la diferencia entre StateGraph y MessageGraph?

MessageGraphEs una API de versiones anteriores con funciones limitadas. Ahora se recomienda usar de manera uniformeStateGraph, que es más flexible y completa. Si necesita procesar mensajes, useStateGraph(MessagesState)o personalice un State que contengamessagesel campo.

Nota:MessageGraphha quedado obsoleto, use de manera uniformeStateGraph. Si su código todavía usa MessageGraph, se recomienda migrar a StateGraph(MessagesState) lo antes posible para obtener un mejor soporte y más funciones.

Resumen

Este artículo presenta de manera sistemática los conceptos centrales y las aplicaciones prácticas del framework LangGraph. LangGraph, a través de la orquestación de flujos de trabajo basados en estructura de grafo, permite a los desarrolladores construir agentes de IA complejos con capacidades de bucle, ramificación y persistencia de estado. En comparación con las cadenas LLM lineales tradicionales, LangGraph tiene ventajas significativas en escenarios que requieren razonamiento de múltiples pasos, colaboración humano-máquina y coordinación de múltiples agentes.

Concepto Descripción Código clave
StateGraph Motor de flujo de trabajo de grafo dirigido, el núcleo de LangGraph StateGraph(MyState)
State Estructura de datos de estado compartida entre nodos TypedDict + Annotated
Nodes Nodos de función que ejecutan operaciones específicas builder.add_node()
Edges Rutas de transición entre nodos, con soporte para ramificaciones condicionales add_edge() / add_conditional_edges()
ReAct Agent Modo de bucle de razonamiento + acción ToolNode + tools_condition
Human-in-Loop Mecanismo de aprobación e intervención humana interrupt() + Command(resume=)
Persistencia Memoria de sesión y guardado de estado MemorySaver / SqliteSaver
Múltiples agentes Sistema de colaboración multi-experto Supervisor Pattern

Ruta de aprendizaje recomendada

Contexto Paso 1 Conceptos básicos Flecha 1 Paso 2 Flujo de trabajo simple Flecha 2 Paso 3 ReAct Agent Flecha 3 Paso 4 Colaboración humano-máquina Flecha 4 Paso 5 Múltiples agentes Flecha 5 Paso 6 LangGraph Studio Definición de flechas
Otras extensiones