152.Agent-LangGraph-Command的参数

摘要 :本文深入解析 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适合知道当某个节点运行完了,才能知道下一个节点是谁


相关推荐
虎鲸不是鱼41 分钟前
【虎鲸进化论 41】AI 的盲区清单:责任、利害、定义、关系、最后一公里与主体性
人工智能·自动化·大模型·llm·自进化agent
跨境联盟1 小时前
实体大健康门店转型:精准营养服务如何跳出单纯卖产品的困局
大数据·人工智能·健康医疗·健康管理·精准营养
第404号工位1 小时前
“已彻底删除”的照片与文件到底去哪了?我用底层扇区扫描,扒出手机和电脑的幽灵数据
linux·python·架构
智购科技自动售卖机厂家1 小时前
双摄像头方案F1-score只有0.85,排查发现是上下视角的融合逻辑写反了~YH
python·单片机·嵌入式硬件·算法·eclipse
appsvip1 小时前
给社区系统加一套 AI 助手:从架构到落地的思考
大数据·人工智能·架构
田里的水稻1 小时前
FA_CNN卷积神经网络二---PyTorch
人工智能·pytorch·python·深度学习·cnn
镜象科技1 小时前
校园抑郁筛查实战:匿名、隐私与信任的平衡
人工智能
远航计算机1 小时前
AI 看不见图,它只读旁边的字
人工智能·chatgpt·aigc
tq10861 小时前
AI 时代 UX 的范式重构:从空间寻路到注意力分配
人工智能