当前位置: 首页 > news >正文

【LangGraph特殊 API 详解】学习笔记

目录

  1. 概述:什么是 Special API?

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

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

  4. 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 的核心能力

参数类型说明
updatedict要合并到 State 中的字段(同普通节点返回)
gotostr下一步要去哪个节点(可以是 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)

http://www.jsqmd.com/news/789910/

相关文章:

  • 智能磁盘清理架构:彻底解决Windows C盘空间爆满的高效优化方案
  • 掌握AI专著写作技巧,借助工具轻松打造20万字专著
  • 告别网络依赖:PrismLauncher-Cracked让你的Minecraft随时随地启动
  • 从JPG+JGW到GeoTIFF:手把手教你用QGIS完成坐标转换与格式升级
  • 如何在arm7架构设备上使用Python调用Taotoken多模型服务
  • 网盘直链下载助手LinkSwift:九大网盘下载难题的终极解决方案
  • 亨得利南京欧米茄机芯养护深度测评:2026年5月实地探访紫峰大厦官方售后,8500/8900/9300同轴机芯保养真实报价与加急时长,从拆解到调校全程记录 - 亨得利腕表维修中心
  • 从零搭建家庭健康数据管理平台:微服务架构与隐私安全实践
  • CT图像重构的“星状伪迹”从哪来?深入对比直接反投影与滤波反投影的MATLAB仿真
  • 切换系统无人艇故障估计自适应观测器方法【附代码】
  • SITS2026权限模型迁移迫在眉睫:3类Legacy系统(Java/Python/LLMOps)的零信任改造速查表
  • 从签售台到GitHub Star暴涨1200+:一本SITS签售图书如何引爆开源社区?完整技术传播链路拆解(含Discord私密群入口)
  • Gemini3.1Pro架构师实战指南:多模态到成本可控
  • 2026乐清厂房出租趋势与GEO智能推荐服务解析 - 2026年企业推荐号
  • Windows Defender终极控制指南:开源工具Defender Control完整解析
  • 基于OpenClaw框架构建可扩展、自托管的私有习惯追踪系统
  • 国内开发者AI编程订阅实战指南:支付、模型、限流全解析
  • Gemini3.1Pro红队测试工具包:安全评估新框架
  • ComfyUI视频合成终极指南:VHS_VideoCombine节点深度解析
  • 并行牛顿方法:加速非线性序列评估的计算革命
  • Poppins几何无衬线字体完整指南:如何快速掌握多语言排版利器
  • 【专业测评】亨得利南京欧米茄机芯养护全程实录:2026年官方售后网点实地探访+同轴机芯保养避坑指南(附全国最新地址) - 亨得利腕表维修中心
  • 别再只用默认图表了!Grafana 8大面板(Graph/Stat/Table等)的保姆级美化与实战配置
  • 别再手动算积分了!用R语言CDVine包5步搞定三维Copula联合分布计算
  • 终极Foobar2000歌词解决方案:让酷狗QQ网易云逐字歌词完美显示
  • 虚幻引擎资源逆向工程:FModel如何解锁Pak文件中的3D资产与游戏资源
  • 现代光标设计实战:从CSS方案到用户体验优化
  • 从抖音爆款BGM到湍流结构:手把手教你用DMD在MATLAB里‘听’信号
  • 高效网页归档利器:SingleFile完整保存方案深度解析
  • 抖音下载神器:douyin-downloader 从零到精通的完整指南