摘要 :本文深入解析 LangGraph 中
Command的核心机制。Command是 LangGraph 节点的标准返回格式,通过update更新状态、goto动态指定下一跳节点,替代了传统条件边的静态分支逻辑。文章结合完整代码示例,展示了Command在意图识别路由(结合goto跳转)和并行任务分发(结合Send数组)两大场景下的应用,并对比了Command与条件边的适用场景:条件边适合流程固定的情况,而Command适合运行中动态产生流程的情况。
内容参考于:图灵AI大模型全栈
Command有三个功能
修改流程图运行状态
可以控制流程的走向,可以指定下一个节点或者多个执行节点
衔接中断恢复、人机交互、调用工具等
Command的参数
update用来更新状态
goto在节点内部用来跳转到指定节点,类似条件边
graph用来从子图定位到父图
resume中断后,可以提供一个值来继续执行
结合代码
如下图红框 classify_and_route 函数的作用是识别用户的问题,判断该问题要使用什么节点,然后通过update修改状态,goto跳转到节点
如下图使用了Command只需要创建节点,不需要创建边了,边的功能都是通过Command的goto参数来跳转的
效果图:
py# 从Python标准库typing导入类型工具: # TypedDict:用于定义结构化字典的类型,规范字典的键名和值类型 # Literal:用于限定变量只能取指定的字面量值,做类型约束 from typing import TypedDict, Literal # 从LangGraph图模块导入核心类: # StateGraph:状态图构建器,用于搭建带状态传递的工作流有向图 # END:工作流结束标记常量,代表流程终止节点 from langgraph.graph import StateGraph, END # 从LangGraph类型模块导入Command类: # Command是LangGraph节点的标准返回格式,用于同时完成「更新状态」和「指定下一跳节点」 from langgraph.types import Command # 从LangChain聊天模型模块导入初始化函数,可统一适配不同厂商的大模型接口 from langchain.chat_models import init_chat_model # 从LangChain核心模块导入聊天提示词模板类(本代码中导入未实际使用,用于结构化构建提示词) from langchain_core.prompts import ChatPromptTemplate # 从dotenv库导入环境变量加载函数,用于读取.env文件中的配置(如API密钥) from dotenv import load_dotenv # 执行环境变量加载,读取项目根目录下.env文件中的配置,避免密钥硬编码 load_dotenv() # 定义工作流的状态结构,继承TypedDict # 作用:规范整个工作流中所有节点共享、传递的状态字段和类型,保证读写一致 class State(TypedDict): # 状态字段:用户输入的问题文本,字符串类型,是整个流程的输入源 question: str # 状态字段:识别出的用户意图/对应部门,由分类节点填充 intent: str # 状态字段:最终返回给用户的回复内容,由业务处理节点填充 response: str # 初始化大语言模型实例,赋值给model变量 model = init_chat_model( "qwen3.7-flash", # 指定调用的模型名称:通义千问 qwen3.7-flash openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1", # OpenAI兼容格式的API接口地址(阿里云百炼兼容端点) model_provider="openai", # 指定适配协议为OpenAI格式,让初始化函数按OpenAI规范调用接口 extra_body={"thinking": {"type": "disabled"}} # 额外请求参数:关闭模型的思考/推理过程输出,确保返回纯文本结果,适配结构化输出场景 ) # 定义「分类路由节点」函数,是工作流的入口处理逻辑 # 入参:当前工作流状态 State # 返回值:Command对象,并用Literal限定只能跳转到指定的4个部门节点 def classify_and_route(state: State) -> Command[ Literal["return_department", "price_department", "tech_department", "general_department"]]: """使用大模型进行用户意图识别,并决策路由到对应部门""" # 从当前状态中提取用户问题文本 question = state["question"] # 构造给大模型的提示词,通过f-string嵌入用户问题 prompt = f""" 你是一个智能路由助手。根据用户问题,决定应该由哪个部门处理。 可选部门: - return_department: 退货、退款、售后问题 - price_department: 价格、优惠、费用查询 - tech_department: 技术故障、使用问题 - general_department: 其他一般咨询 请只返回部门名称,不要有其他内容 用户问题:{question} """ # 调用大模型,传入提示词,获取模型响应结果 response = model.invoke(prompt) # 提取模型返回的文本内容,去除首尾空白并转为小写,得到标准化的部门名称 intent = response.content.strip().lower() # 控制台打印路由决策,用于调试观察 print(f"AI 决策: 路由到 {intent}") # 返回Command对象,控制工作流的状态更新和节点跳转 return Command( update={"intent": intent}, # update参数:更新工作流状态,将识别结果写入intent字段 goto=intent # goto参数:指定下一个要执行的节点,直接使用模型返回的部门名 ) # 定义「退货售后部门」处理节点函数 # 返回值限定为Command[END],代表该节点执行完后流程直接结束 def return_department(state: State) -> Command[END]: # 控制台打印当前执行节点,用于调试 print("退货部门处理...") return Command( # 更新状态:写入退货部门的标准回复内容 update={"response": "退货流程:请提供订单号和退货原因"}, goto=END # 跳转到结束节点,终止工作流 ) # 定义「价格咨询部门」处理节点函数 def price_department(state: State) -> Command[END]: print("价格部门处理...") return Command( update={"response": "价格信息:当前商品价格请查看官网"}, goto=END ) # 定义「技术支持部门」处理节点函数 def tech_department(state: State) -> Command[END]: print("技术部门处理...") return Command( update={"response": "技术问题:请描述具体的错误现象"}, goto=END ) # 定义「综合咨询部门」处理节点函数,处理其他通用问题 def general_department(state: State) -> Command[END]: print("综合部门处理...") return Command( update={"response": "感谢咨询,我们会尽快回复"}, goto=END ) # ========== 构建并编译工作流图 ========== # 创建状态图构建器实例,传入自定义的State类型作为全局状态规范 builder = StateGraph(State) # 向图中添加分类路由节点,第一个参数是节点名称,第二个是对应处理函数 builder.add_node("classify_and_route", classify_and_route) # 添加退货部门节点 builder.add_node("return_department", return_department) # 添加价格部门节点 builder.add_node("price_department", price_department) # 添加技术部门节点 builder.add_node("tech_department", tech_department) # 添加综合部门节点 builder.add_node("general_department", general_department) # 设置工作流的入口节点:流程启动后第一个执行的节点 builder.set_entry_point("classify_and_route") # 编译构建好的状态图,生成可执行的工作流实例 graph = builder.compile() # ========== 测试运行 ========== # 定义测试问题列表,覆盖4类典型场景 test_questions = [ "我想退货,收到商品有质量问题", # 退货售后场景 "这个商品现在多少钱?", # 价格咨询场景 "软件打不开了,怎么办?", # 技术故障场景 "你们公司在哪里?" # 通用咨询场景 ] # 遍历所有测试问题,逐个执行工作流 for q in test_questions: # 打印分隔线,优化控制台输出可读性 print(f"\n{'=' * 50}") # 打印当前测试的用户问题 print(f"用户问题: {q}") # 调用工作流的invoke方法执行,传入初始状态(仅包含用户问题),返回最终状态 result = graph.invoke({"question": q}) # 打印工作流最终的回复结果 print(f"响应: {result['response']}")
带Send
如下图红框,goto参数可以传递Send数组
如下图并行执行的节点需要手动设在它的下一个节点,也就是要设置好归并函数
效果图:
python# 从Python标准库typing导入类型工具 # TypedDict:定义结构化字典类型,规范状态字段 # Dict:字典类型注解 # Literal:限定字面量取值 # Annotated:为类型附加元数据,LangGraph用它声明状态的归约规则 # List:列表类型注解 from typing import TypedDict, Dict, Literal, Annotated, List # 从LangGraph图模块导入状态图构建器和流程结束标记 from langgraph.graph import StateGraph, END # 从LangGraph类型模块导入核心控制类 # Command:节点返回值标准格式,用于更新状态+指定跳转目标 # Send:并行分发指令,用于向多个节点同时发送任务,实现并行分支 from langgraph.types import Command, Send # 从operator导入add函数,用作列表状态的归约/合并逻辑 # 配合Annotated使用:多个节点返回的列表会通过add(列表拼接)自动合并 from operator import add # 导入LangChain大模型统一初始化函数 from langchain.chat_models import init_chat_model # 导入环境变量加载工具 from dotenv import load_dotenv # 加载.env文件中的环境变量(如API密钥) load_dotenv() # 定义工作流全局状态结构 class AdvancedState(TypedDict): # 用户原始问题,整个流程的输入 question: str # 拆解后的子任务映射:键=处理器节点名,值=子任务内容 subtasks: Dict[str, str] # 各并行节点的处理结果列表 # Annotated + add 是关键:多个并行节点返回的 results 列表会自动拼接合并 # 每个节点返回 {"results": [xxx]},最终状态里会汇总成一个大列表 results: Annotated[list[Dict[str, str]], add] # 最终汇总后的总结内容 summary: str # 定义结构化输出的Schema,约束大模型返回的JSON格式 # 用于让模型严格按照指定结构返回任务拆解结果 class SplitTasksSchema(TypedDict): tasks: dict[str, str] # 初始化大语言模型实例 model = init_chat_model( "deepseek-v4-flash", # 指定模型名称 extra_body={"thinking": {"type": "disabled"}} # 关闭思考过程,确保输出纯净 ) # 定义「任务分析拆解节点」,是工作流的入口 # 返回值类型 Command[List[Send]]:代表会并行分发到多个节点 def analyze_and_split(state: AdvancedState) -> Command[List[Send]]: """AI 分析用户问题,拆解为多个子任务,并行分发到对应处理器""" # 从状态中取出用户原始问题 question = state["question"] # 构造任务拆解的提示词,明确要求返回指定JSON格式 prompt = f""" 你是一个任务分析专家。分析用户问题,拆解成多个独立的子任务。 返回 JSON 格式: {{ "tasks": {{ "math_handler": "数学计算子问题", "knowledge_handler": "知识查询子问题", "search_handler": "信息搜索子问题" }} }} 可用处理器: - math_handler: 处理数学计算 - knowledge_handler: 处理知识问答 - search_handler: 处理信息搜索 - default_handler: 处理其他问题 根据问题内容,选择合适的处理器,至少选择1个。 用户问题:{question} """ # 给模型绑定结构化输出能力,强制返回符合 SplitTasksSchema 的JSON structured_model = model.with_structured_output(SplitTasksSchema) # 调用模型,得到结构化的任务拆解结果 result = structured_model.invoke(prompt) # 提取任务字典,兜底默认用default_handler处理原问题 tasks = result.get("tasks", {"default_handler": question}) # 控制台打印拆解出的任务节点,便于调试 print(f"AI 拆解任务: {list(tasks.keys())}") # 返回Command,并行分发任务 return Command( # 更新全局状态:保存拆解后的子任务映射 update={ "subtasks": tasks }, # goto 传入 Send 对象列表,实现并行分支 # Send(节点名, 传入该节点的局部状态) # 每个子任务会独立启动对应节点,节点间并行执行 goto=[ Send(node_name, {"task": task_content}) for node_name, task_content in tasks.items() ] ) # 定义「数学计算处理器」节点 def math_handler(state: AdvancedState): # 从当前节点的局部状态中取出分配的子任务 task = state["task"] print(f"数学处理: {task}") # 调用大模型处理数学问题 prompt = f""" 你是一个数学专家,计算以下数学问题,只返回结果。 问题: {task} """ result = model.invoke(prompt) # 返回结果,会通过 add 操作符合并到全局状态的 results 列表中 # 注意返回格式是 {"results": [单个结果字典]},便于列表拼接 return {"results": [{"math": result.content}]} # 定义「知识问答处理器」节点 def knowledge_handler(state: AdvancedState): task = state["task"] print(f"知识查询: {task}") prompt = f""" 你是一个知识专家,回答以下知识性问题,简洁准确 问题: {task} """ result = model.invoke(prompt) return {"results": [{"knowledge": result.content}]} # 定义「信息搜索处理器」节点 def search_handler(state: AdvancedState): task = state["task"] print(f"信息搜索: {task}") prompt = f""" 你是一个搜索专家,提供相关信息 问题: {task} """ result = model.invoke(prompt) return {"results": [{"search": result.content}]} # 定义「默认处理器」节点,处理未匹配到专项能力的问题 def default_handler(state: AdvancedState): task = state["task"] print(f"默认处理: {task}") prompt = f""" 你是一个通用助手,处理用户的咨询。 问题: {task} """ result = model.invoke(prompt) return {"results": [{"default": result.content}]} # 定义「汇总总结节点」,所有并行节点完成后执行 def summarize(state: AdvancedState) -> Command[Literal[END]]: """汇总所有并行节点的处理结果,生成统一回答""" # 从全局状态中取出所有节点的结果(已自动合并为列表) results = state.get("results", {}) # 构造总结提示词,把原始问题和所有子结果都传给模型 summary_prompt = f""" 你是一个总结多个任务返回结果的专家 用户问题:{state['question']} 多个并行节点返回的答案:{results} """ summary = model.invoke(summary_prompt) print(f"\n汇总结果: {summary}") # 更新总结内容,跳转至结束 return Command( update={"summary": summary.content}, goto=END ) # ========== 构建工作流图 ========== # 创建状态图构建器,传入全局状态类型 builder = StateGraph(AdvancedState) # 添加入口节点:任务分析拆解 builder.add_node("analyze_and_split", analyze_and_split) # 添加4个并行处理节点 builder.add_node("math_handler", math_handler) builder.add_node("knowledge_handler", knowledge_handler) builder.add_node("search_handler", search_handler) builder.add_node("default_handler", default_handler) # 添加汇总节点 builder.add_node("summarize", summarize) # 设置工作流入口节点 builder.set_entry_point("analyze_and_split") # 添加边:所有处理节点完成后,都流向汇总节点(扇入结构) # LangGraph会等待所有并行分支执行完毕,再进入summarize builder.add_edge("math_handler", "summarize") builder.add_edge("knowledge_handler", "summarize") builder.add_edge("search_handler", "summarize") builder.add_edge("default_handler", "summarize") # 编译图,生成可执行的工作流实例 graph = builder.compile() # ========== 测试运行 ========== # 构造包含多类问题的复合测试用例 test_question = """ 我想知道: 1. 25 * 36 等于多少? 2. 李白的代表作品有哪些? 3. 长沙在清朝的地名? """ print("用户问题:", test_question) print("\n" + "=" * 50) # 执行工作流,传入初始状态 result = graph.invoke({"question": test_question}) print("\n" + "=" * 50) # 打印最终总结结果 print("最终总结:", result['summary'])
在Command的源码中,如下图红框它有写goto都可以写什么值

在LangGraph中条件边适合提前固定好的流程分支,就是说条件边它适合不需要运行就知道下一个节点的情况,Command适合运行中动态产生流程,就是说Command适合知道当某个节点运行完了,才能知道下一个节点是谁






