【LangGraph的工作流编排能力】学习笔记

目录

  1. [概述:什么是 Special API?](#概述:什么是 Special API?)

  2. [Command:状态更新 + 流程控制的统一接口](#Command:状态更新 + 流程控制的统一接口)

  3. [Context Schema:运行时上下文注入](#Context Schema:运行时上下文注入)

  4. [Send:动态分支与 Map-Reduce 模式](#Send:动态分支与 Map-Reduce 模式)

  5. 总结对比


1. 概述:什么是 Special API?

LangGraph 在基础概念(State、Node、Edge、Graph)之上,提供了三个特殊 API,用于解决标准图模式难以处理的场景:

API 解决的核心问题 引入版本
Command 节点返回值中同时更新状态 + 控制流程去向 1.0.x
context_schema 向节点传递不属于 State 的运行时依赖(模型名、DB 连接、API Key) 1.0.x
Send 动态并行分支:运行时根据 State 内容创建 N 个并行节点实例 1.0.x

这三个 API 让 LangGraph 从**「固定的有向图」** 升级为**「运行时自适应的动态图」**。


2. Command:状态更新 + 流程控制的统一接口

2.1 传统模式 vs Command 模式

传统模式 中,节点的返回值只做一件事:更新 State。流程控制完全由 Edge 决定。

复制代码
# 传统节点:只更新 State
def my_node(state) -> dict:
    return {"result": "some_value"}
​
# 流程控制由边负责
graph.add_conditional_edges("my_node", routing_fn, {...})

Command 模式中,节点既更新 State,又指定下一步去哪个节点:

复制代码
from langgraph.types import Command
​
def my_node(state) -> Command[AgentState]:
    return Command(
        update={"messages": [("system", "处理完成")]},  # 状态更新
        goto="next_node"                                   # 流程控制
    )

2.2 Command 的核心能力

参数 类型 说明
update dict 要合并到 State 中的字段(同普通节点返回)
goto str 下一步要去哪个节点(可以是 END)

关键洞察 :Command 把「更新状态」和「控制流程」两个职责打包成一个返回值 ,使得中心路由节点可以在一处完成所有决策。

2.3 完整示例:多 Agent 路由

复制代码
from langgraph.types import Command
from langgraph.graph import StateGraph, START, END
​
class AgentState(TypedDict):
    messages: Annotated[list, lambda x, y: x + y]
    current_agent: str
    task_completed: bool
​
# 核心路由节点:根据消息内容动态路由
def decision_agent(state: AgentState) -> Command[AgentState]:
    # 情况1:任务已完成 → 终止流程
    if state["task_completed"]:
        return Command(
            update={"messages": [("system", "所有任务处理完成")]},
            goto=END
        )
​
    last_message = state["messages"][-1]
    last_msg_content = last_message[1]
​
    # 情况2:数学任务 → 路由到数学代理
    if "数学" in last_msg_content:
        return Command(
            update={
                "messages": [("system", "路由到数学代理")],
                "current_agent": "math_agent"
            },
            goto="math_agent"
        )
    # 情况3:翻译任务 → 路由到翻译代理
    elif "翻译" in last_msg_content:
        return Command(
            update={
                "messages": [("system", "路由到翻译代理")],
                "current_agent": "translation_agent"
            },
            goto="translation_agent"
        )
    # 情况4:未识别 → 标记完成并结束
    else:
        return Command(
            update={"messages": [("system", "任务完成")], "task_completed": True},
            goto=END
        )

2.4 Command 的两种跳转方式

方式一:无条件跳转(固定 goto)

业务节点执行完固定跳转到路由节点:

复制代码
def math_agent(state: AgentState) -> Command[AgentState]:
    result = "2 + 2 = 4"
    return Command(
        update={
            "messages": [("assistant", f"数学计算结果: {result}")],
            "task_completed": True
        },
        goto="decision_agent"   # 做完固定回到路由节点
    )

方式二:条件跳转(结合条件判断)

路由节点根据 State 内容动态选择 goto 目标(如上文的 decision_agent)。

2.5 执行流程

复制代码
START
  │
  ▼
decision_agent ──("数学")──→ math_agent
  │                              │
  │                              ▼
  │ ←───("task_completed")─── decision_agent → END
  │
  │──("翻译")──→ translation_agent
  │                  │
  │                  ▼
  │ ←───("task_completed")─── decision_agent → END
  │
  └──("其他")──→ END

2.6 测试输出

复制代码
【测试1:数学任务】
START → decision_agent → math_agent(2+2=4) → decision_agent → END
最终: current_agent=decision_agent, task_completed=True
​
【测试2:翻译任务】
START → decision_agent → translation_agent(Hello->你好) → decision_agent → END
最终: current_agent=decision_agent, task_completed=True
​
【测试3:未识别任务】
START → decision_agent → (标记完成) → END
最终: current_agent=user, task_completed=True

2.7 Command 使用场景

  • 中心化路由 :一个路由节点负责所有分支决策

  • 工作流引擎 :每个步骤执行完后指定下一步

  • 防循环设计 :用 task_completed 标志配合 Command(goto=END) 防止死循环

  • 多 Agent 编排:Supervisor Agent 用 Command 动态分派任务


3. Context Schema:运行时上下文注入

3.1 为什么需要 Context Schema?

标准 LangGraph 节点只能访问 State 中的数据。但有些信息不适合放在 State 中

  • API Key、数据库连接字符串 → 敏感信息,不应随 State 流转

  • 模型名称、配置参数 → 属于运行环境而非业务状态

  • 日志记录器、监控客户端 → 属于依赖注入

Context Schema 解决了这个问题:它允许在 invoke() 时传入额外的上下文对象 ,节点通过 runtime.context 访问。

3.2 三步使用法

第一步:定义上下文结构

使用 @dataclass 定义上下文类型:

复制代码
from dataclasses import dataclass
​
@dataclass
class ContextSchema:
    model_name: str
    db_connection: str
    api_key: str

第二步:在构建图时指定 context_schema

复制代码
builder = StateGraph(AgentState, context_schema=ContextSchema)

第三步:节点函数接收 runtime 参数

节点函数增加第二个参数 runtime: Runtime[ContextSchema]

复制代码
from langgraph.runtime import Runtime
​
def process_message(state: AgentState, runtime: Runtime[ContextSchema]) -> dict:
    # 通过 runtime.context 访问上下文
    model_name = runtime.context.model_name
    db_connection = runtime.context.db_connection
    api_key = runtime.context.api_key
​
    print(f"使用模型: {model_name}")
    response = f"使用 {model_name} 处理了您的请求,已连接到 {db_connection}"
    return {"messages": [AIMessage(content=response)], "response": response}

第四步:在 invoke() 时传递 context

复制代码
context = ContextSchema(
    model_name="gpt-4-turbo",
    db_connection="postgresql://user:***@localhost:5432/orders_db",
    api_key="sk-abc...3456"
)
​
result = graph.invoke(initial_state, context=context)

3.3 Context 与 State 的对比

维度 State(状态) Context(上下文)
变更 节点函数可以修改 节点只能,不能修改
传递 在节点间自动流转 通过 invoke(context=...) 传入
生命周期 随图执行过程变化 整次调用固定不变
敏感信息 可能被序列化/持久化 仅运行时存在
适合存放 业务数据、用户输入 配置、凭证、依赖

3.4 完整执行流程

复制代码
invoke(initial_state, context=ContextSchema)
            │
            ▼
┌─────────────────────────────────┐
│   process_message(state, runtime) │
│   ├── 读取 context.model_name     │
│   ├── 读取 context.db_connection  │
│   ├── 读取 context.api_key        │
│   └── 返回更新状态                 │
└──────────┬──────────────────────┘
           ▼
┌──────────────────────────────────┐
│ generate_response(state, runtime) │
│   ├── 读取 context.model_name     │
│   └── 返回最终响应                 │
└──────────┬───────────────────────┘
           ▼
         END

3.5 测试输出

复制代码
初始状态: {'messages': [HumanMessage("请帮我查询最新的订单信息")], 'response': ''}
​
上下文信息:
    model_name: gpt-4-turbo
    db_connection: postgresql://user:***@localhost:5432/orders_db
    api_key: sk-ab***
​
执行节点: process_message
用户消息: 请帮我查询最新的订单信息
使用的模型: gpt-4-turbo
数据库连接: postgresql://user:***@localhost:5432/orders_db
API密钥前缀: sk-ab***
​
执行节点: generate_response
使用模型 gpt-4-turbo 生成最终响应
​
最终响应:
使用 gpt-4-turbo 处理了您的请求,已连接到 postgresql://user:***@localhost:5432/orders_db
​
这是使用 gpt-4-turbo 生成的完整响应。

3.6 实用场景

  • 多环境切换(dev/staging/prod 使用不同 DB 连接)

  • A/B 测试不同模型(通过 context 传入不同 model_name)

  • 依赖注入(传入日志器、缓存客户端、监控工具)

  • 多租户系统(每个租户传入不同的 API Key 和配置)


4. Send:动态分支与 Map-Reduce 模式

4.1 为什么需要 Send?

普通条件边只能选择一个分支执行。但有些场景需要并行执行多个分支

  • 用户提问后,同时搜索多个知识库

  • 给 N 个客户同时发邮件

  • 对列表中每个元素做相同的处理

Send 对象解决了这个问题:它允许条件边返回多个 Send 对象,每个 Send 指向同一个(或不同)节点并传入不同的 State 切片。

4.2 Send 的工作机制

复制代码
条件边函数返回 List[Send]  →  每个 Send 创建一个独立的节点实例并行执行
复制代码
from langgraph.types import Send
​
def map_subjects_to_jokes(state: AtguiguState) -> List[Send]:
    subjects = state["subjects"]  # ["猫", "狗", "程序员"]
    return [
        Send("make_joke", {"subject": subject})  # 每个主题创建一个 Send
        for subject in subjects
    ]

每个 Send(node, arg) 包含:

  • node:目标节点名称

  • arg:传给该节点实例的 State 数据(可以是 State 的子集)

4.3 完整 Map-Reduce 示例

复制代码
from langgraph.types import Send
from typing import Annotated, List
​
class AtguiguState(TypedDict):
    subjects: List[str]
    jokes: Annotated[List[str], lambda x, y: x + y]  # 自动合并
​
# === Map 阶段:生成主题列表 ===
def generate_subjects(state: AtguiguState) -> dict:
    subjects = ["猫", "狗", "程序员"]
    return {"subjects": subjects}
​
# === Map 阶段:并行处理每个主题 ===
def make_joke(state: AtguiguState) -> dict:
    subject = state.get("subject", "未知")
    jokes_map = {
        "猫": "为什么猫不喜欢在线购物?因为它们更喜欢实体店!",
        "狗": "为什么狗不喜欢计算机?因为它们害怕被鼠标咬!",
        "程序员": "为什么程序员喜欢洗衣服?因为他们在寻找bugs!",
    }
    joke = jokes_map.get(subject, "这是一个神秘笑话。")
    return {"jokes": [joke]}
​
# === 路由函数:将主题列表展开为 Send 列表 ===
def map_subjects_to_jokes(state: AtguiguState) -> List[Send]:
    return [Send("make_joke", {"subject": s}) for s in state["subjects"]]
​
# === 构建图 ===
builder = StateGraph(AtguiguState)
builder.add_node("generate_subjects", generate_subjects)
builder.add_node("make_joke", make_joke)
​
builder.add_edge(START, "generate_subjects")
​
# 条件边返回 Send 列表 → 动态并行
builder.add_conditional_edges(
    "generate_subjects",
    map_subjects_to_jokes   # 返回 List[Send]
)
​
builder.add_edge("make_joke", END)
graph = builder.compile()

4.4 执行流程

复制代码
START
  │
  ▼
generate_subjects  →  subjects = ["猫", "狗", "程序员"]
  │
  ▼
map_subjects_to_jokes  →  返回 3 个 Send 对象
  │
  ├── Send(make_joke, {subject: "猫"})      ──→ make_joke("猫")
  ├── Send(make_joke, {subject: "狗"})      ──→ make_joke("狗")     ← 并行执行
  └── Send(make_joke, {subject: "程序员"})  ──→ make_joke("程序员")
                                                    │
                                                    ▼
                                            jokes 自动合并(Reducer)
                                                    │
                                                    ▼
                                                  END

4.5 测试输出

复制代码
生成主题列表: ['猫', '狗', '程序员']
映射主题到joke任务: ['猫', '狗', '程序员']
生成Send对象列表: [Send("make_joke", {subject: "猫"}), Send("make_joke", {subject: "狗"}), Send("make_joke", {subject: "程序员"})]
​
执行节点: make_joke,处理主题: 猫
执行节点: make_joke,处理主题: 狗       ← 注意:并行执行
执行节点: make_joke,处理主题: 程序员
​
最终结果:
{
    'subjects': ['猫', '狗', '程序员'],
    'jokes': [
        '为什么猫不喜欢在线购物?因为它们更喜欢实体店!',
        '为什么狗不喜欢计算机?因为它们害怕被鼠标咬!',
        '为什么程序员喜欢洗衣服?因为他们在寻找bugs!'
    ]
}

4.6 Send 的关键注意点

要点 说明
Reducer 必须支持合并 因为多个 Send 实例返回的结果需要合并,State 中对应的字段必须使用合适的 Reducer(如 operator.addadd_messages 或自定义合并函数)
Send 的 arg 是 State 切片 传给每个实例的 State 只包含该实例需要的数据,不需要传整个 State
动态数量 Send 的数量在运行时才确定,由条件边函数的返回值决定
并行执行 多个 Send 实例是并行执行的(除非设置了最大并发限制)
节点间独立 每个 Send 实例之间的 State 不共享,各自独立处理

4.7 Send vs 普通条件边

对比维度 普通条件边 Send 条件边
返回值类型 str(目标节点名) List[Send](多个目标)
并行能力 否,走一个分支 是,可同时走 N 个分支
动态数量 固定映射 运行时根据 State 内容决定数量
适用场景 if/else 分支决策 批量处理、Map-Reduce

4.8 实用场景

  • 批量数据处理:数据列表 → 并行处理每个元素 → 合并结果

  • 多知识库检索:查询 → 同时检索多个知识库 → 汇总结果

  • 多模型投票:问题 → 同时询问多个模型 → 投票选出最佳答案

  • 并行工具调用:Agent 决定同时调用多个工具 → 收集结果


5. 总结对比

5.1 三大 Special API 一览

API 核心功能 类比 解决什么问题
Command 返回值中同时包含 update + goto 函数返回 (结果, 下一步) 中心化路由节点需要同时更新状态和指定流程
context_schema 运行时向节点注入非 State 依赖 依赖注入 / 配置注入 不希望放在 State 中的环境信息、凭证、配置
Send 条件边返回多个并行目标 Map-Reduce 的 Map 阶段 运行时动态创建 N 个并行执行实例

5.2 建议的学习路线

复制代码
基础图模式 (State + Node + Edge)
        │
        ▼
    Command           →  中心化路由 / 工作流引擎
  (状态+流程合一)
        │
        ▼
  context_schema      →  生产部署 / 多环境 / 依赖注入
  (运行时上下文)
        │
        ▼
      Send            →  批量处理 / 并行检索 / Map-Reduce
  (动态并行分支)

5.3 三种模式可以组合使用

复制代码
# Command + context_schema + Send 可以共存于同一个图中
class MyState(TypedDict):
    messages: Annotated[list, add_messages]
    tasks: List[str]
​
@dataclass
class MyContext:
    api_key: str
    model: str
​
# 节点用 Command 控制流程
def router(state: MyState, runtime: Runtime[MyContext]) -> Command[MyState]:
    model = runtime.context.model  # 从上下文读取
    if state["tasks"]:
        # 用 Send 并行处理
        return Command(
            update={"messages": [("system", f"使用 {model} 处理")]},
            goto=[Send("worker", {"task": t}) for t in state["tasks"]]
        )
    return Command(goto=END)

相关推荐
程序边界1 小时前
表空间目录自动创建:从一个小开关聊到云原生存储的那些事
数据库·oracle·dba
j_xxx404_1 小时前
【Linux进程间通信】硬核剖析:消息队列、信号量、内核IPC资源统一管理与mmap加餐
linux·运维·开发语言·c++·人工智能·ai
qingy_20461 小时前
Redis Zset 底层数据结构及其使用场景
数据结构·数据库·redis
深度学习lover1 小时前
<数据集>yolo 货车识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·货车识别
她说可以呀1 小时前
JWT令牌检验用户是否登录
java·spring boot·spring·java-ee·maven
jimy11 小时前
Oracle的e2.1.micro免费实例安装tailscale后,设置为出口节点(Exit Node)
服务器·网络·oracle
一氧化二氢.h1 小时前
【简单理解】数组、数组列表、集合
java
金玉满堂@bj1 小时前
大模型(AI应用开发)完整学习路线|零基础可落地版
人工智能·学习
哆啦A梦15881 小时前
11,Springboot3+vue3个人中心,修改密码
java·前端·javascript·数据库·vue3