**摘要:**本文围绕 LangGraph 的「主管---子 Agent」多智能体架构展开,讲解如何通过主管节点做任务规划与意图识别,把用户问题拆分为技术、销售、账户等多个子任务,再由条件边路由到对应的子图执行。文中结合完整 Python 代码,说明子图的标准 ReAct 结构、主管与子图之间不共享状态的数据交互方式、任务队列与循环闭环的调度机制,并给出跨领域多任务的测试示例,帮助读者快速搭建可循环协调多任务的多智能体系统。
内容参考于:图灵AI大模型全栈
主管架构说明
核心就是实现一个通过问题进行任务规划的功能
它的核心如下图红框,给主管节点添加一个条件边,通过条件边让主管跳转到子图

主管节点里面主要是返回一个任务,也就是子图的名字

返回什么子图名,是通过提示词向大模型提问获取的

任务规划的内容就是子图的名字列表,如下图红框

主管和子图的数据交互是通过不共享状态来实现的

代码
python
# ==================================================
# 导入模块:按功能分层导入,对应架构的不同层级
# ==================================================
# 导入字面量类型,用于严格约束路由函数的返回值只能是指定枚举值
# 【机制】做静态类型校验,避免路由返回非法节点名导致运行时报错
from typing import Literal
# 导入LangChain标准消息类:用户消息、AI回复消息、系统提示消息
# 【机制】所有对话内容都统一封装为消息对象,是MessagesState的基础数据单元
from langchain.messages import HumanMessage, AIMessage, SystemMessage
# 导入OpenAI协议兼容的大模型客户端
# 【作用】统一调用入口,可适配所有支持OpenAI格式的模型服务(此处为阿里云百炼)
from langchain_openai import ChatOpenAI
# 导入LangGraph图构建核心组件
# StateGraph:状态图构建器,所有工作流的基础构造类
# START/END:流程起止标记常量,代表图的入口和出口
# MessagesState:LangGraph内置的对话状态结构
# 【机制】自带messages字段 + add_messages归约器,默认自动追加消息,无需手动管理列表
from langgraph.graph import StateGraph, START, END, MessagesState
# 导入LangGraph预构建的工具调用组件
# ToolNode:通用工具执行节点,自动解析tool_calls并执行对应工具
# 【机制】无需手写工具调用逻辑,自动完成参数解析、函数执行、结果封装全流程
# tools_condition:内置条件路由函数,自动判断消息中是否包含工具调用
# 【机制】读取消息的tool_calls字段,有则返回"tools"分支,无则返回结束分支
from langgraph.prebuilt import ToolNode, tools_condition
# 导入工具装饰器:将普通Python函数包装为大模型可调用的标准工具
# 【机制】自动生成工具的JSON Schema描述、做入参类型校验、封装为LangChain工具对象
from langchain.tools import tool
import os
# 环境变量加载工具,用于读取.env中的敏感配置(如API密钥)
from dotenv import load_dotenv
# 执行环境变量加载,将.env文件中的配置注入系统环境变量
load_dotenv()
# ==============================================================================
# 数据层:模拟业务数据源,替代真实项目中的数据库/后端接口
# 【设计逻辑】分层解耦:工具层只调用数据,不关心数据存在哪里;替换数据源时无需修改工具代码
# ==============================================================================
# 技术知识库:问题关键词 → 标准解决方案
# 【业务作用】技术支持Agent的核心数据源,做关键词匹配的FAQ问答
# 【架构定位】数据层 → 仅技术工具可访问
KNOWLEDGE_BASE = {
"login": "请清除浏览器缓存并重新登录,或重置密码。",
"payment": "请检查银行卡余额,确认交易状态,或联系银行。",
"bug": "我们已记录此问题,技术团队将在24小时内处理。",
"network": "请检查网络连接,或尝试切换网络环境。",
"performance": "建议清理缓存、重启应用或检查系统资源使用情况。"
}
# 产品套餐库:套餐ID → 套餐详情(名称、价格、功能列表)
# 【业务作用】销售Agent的数据源,支撑产品介绍、价格查询、升级计算
# 【架构定位】数据层 → 仅销售工具可访问
PRODUCTS = {
"basic": {"name": "基础版", "price": 99, "features": ["基础功能", "邮件支持"]},
"pro": {"name": "专业版", "price": 299, "features": ["高级功能", "优先支持", "API访问"]},
"enterprise": {"name": "企业版", "price": 999, "features": ["企业功能", "专属客服", "定制开发"]}
}
# 用户账户库:用户ID → 账户信息(套餐、状态、权限、余额)
# 【业务作用】账户管理Agent的数据源,支撑查询、退款等账户操作
# 【架构定位】数据层 → 仅账户管理工具可访问
USER_DATABASE = {
"user123": {"plan": "pro", "status": "active", "support_level": "premium", "balance": 500},
"user456": {"plan": "basic", "status": "active", "support_level": "standard", "balance": 100}
}
# ==============================================================================
# 大模型初始化:全局模型实例,供所有Agent复用
# ==============================================================================
# 初始化通义千问大模型,使用阿里云百炼的OpenAI兼容接口
llm = ChatOpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"), # 从环境变量读取API密钥,避免硬编码
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", # 接口地址
model="qwen3.8-flash", # 指定模型版本
temperature=0.1 # 生成温度:值越低回答越稳定、随机性越小
# 【设计逻辑】客服/工具调用场景使用低温度,保证输出确定性,避免模型胡编乱造
)
# ==============================================================================
# 工具层:按业务领域分组,封装为大模型可调用的标准工具
# 【设计逻辑】职责隔离:每个领域的工具只对应该领域的Agent开放,从权限上避免越权
# ==============================================================================
# ----------------------------------------------------------------------
# 第一组:技术支持类工具 → 仅供技术Agent使用
# ----------------------------------------------------------------------
@tool
def search_knowledge_base(query: str) -> str:
"""
搜索技术知识库,根据问题关键词匹配解决方案
Args:
query: 用户的技术问题描述文本
Returns:
匹配到的解决方案字符串,未匹配则返回建单提示
"""
# 打印调试日志,便于追踪工具调用情况
print(f"[Tech] 搜索知识库: {query}")
# 将查询转为小写,实现不区分大小写的关键词匹配
query_lower = query.lower()
# 存放所有匹配到的解决方案
results = []
# 遍历知识库,逐个匹配关键词
for issue, solution in KNOWLEDGE_BASE.items():
# 只要关键词出现在查询文本中,就算匹配成功
if issue in query_lower:
results.append(f"{issue}: {solution}")
# 匹配到结果:拼接所有解决方案返回
if results:
return "找到以下解决方案:\n" + "\n".join(results)
# 未匹配到结果:返回创建工单的引导
return "未在知识库中找到相关解决方案,建议创建技术工单进行人工处理。"
@tool
def create_support_ticket(issue_description: str, priority: str = "normal") -> str:
"""
创建技术支持工单,生成唯一工单编号
Args:
issue_description: 问题详细描述
priority: 工单优先级,可选值 normal/high/urgent,默认为普通
Returns:
工单创建成功的回执信息
"""
# 导入uuid模块,生成唯一标识(按需导入,避免顶层不必要的依赖)
import uuid
# 生成8位大写的唯一工单ID,格式 TICKET-XXXXXXXX
ticket_id = f"TICKET-{str(uuid.uuid4())[:8].upper()}"
print(f"[Tech] 创建工单: {ticket_id}")
# 返回格式化的工单回执
return f"已创建支持工单: {ticket_id}\n问题描述: {issue_description}\n优先级: {priority}\n我们的技术团队将在24小时内处理您的问题。"
# ----------------------------------------------------------------------
# 第二组:销售类工具 → 仅供销售Agent使用
# ----------------------------------------------------------------------
@tool
def get_product_info(product_query: str = "") -> str:
"""
查询产品信息与价格,支持查单个产品或全量产品列表
Args:
product_query: 可选,产品ID或名称;不传则返回全部产品
Returns:
格式化的产品详情文本
"""
print(f"[Sales] 查询产品: {product_query or '全部'}")
# 分支1:没有传入查询词 → 返回所有产品的完整列表
if not product_query:
result = "我们的产品线包括:\n\n"
# 遍历产品字典,拼接每个产品的信息
for key, product in PRODUCTS.items():
result += f"**{product['name']}** - ¥{product['price']}/月\n"
result += f"功能: {', '.join(product['features'])}\n\n"
return result
# 分支2:有查询词 → 做关键词匹配,返回单个产品详情
query_lower = product_query.lower()
for key, product in PRODUCTS.items():
# 匹配规则:产品ID或产品名称包含查询词就算命中
if key in query_lower or product["name"] in query_lower:
return (
f"**{product['name']}**\n"
f"价格: ¥{product['price']}/月\n"
f"功能: {', '.join(product['features'])}"
)
# 未匹配到任何产品:返回提示信息
return f"未找到关于'{product_query}'的产品信息。请查看我们的完整产品列表。"
@tool
def calculate_upgrade_cost(current_plan: str, target_plan: str) -> str:
"""
计算套餐升级的差价与新增功能
Args:
current_plan: 当前套餐的ID
target_plan: 目标套餐的ID
Returns:
升级费用计算结果与新增功能列表
"""
print(f"[Sales] 计算升级: {current_plan} -> {target_plan}")
# 第一步:参数合法性校验,避免非法套餐ID导致后续报错
if current_plan not in PRODUCTS or target_plan not in PRODUCTS:
return "无效的套餐类型。请检查套餐名称。"
# 取出两个套餐的价格
current_price = PRODUCTS[current_plan]["price"]
target_price = PRODUCTS[target_plan]["price"]
# 分支:目标套餐价格 ≤ 当前套餐 → 不需要升级费用
if target_price <= current_price:
return (
f"目标套餐 ({PRODUCTS[target_plan]['name']}) "
f"价格不高于当前套餐 ({PRODUCTS[current_plan]['name']}),无需升级费用。"
)
# 计算升级差价
upgrade_cost = target_price - current_price
# 计算新增功能:用集合差集,得到目标套餐有但当前套餐没有的功能
new_features = set(PRODUCTS[target_plan]["features"]) - set(PRODUCTS[current_plan]["features"])
# 返回格式化的计算结果
return (
f"升级费用计算:\n"
f"当前套餐: {PRODUCTS[current_plan]['name']} (¥{current_price}/月)\n"
f"目标套餐: {PRODUCTS[target_plan]['name']} (¥{target_price}/月)\n"
f"升级费用: ¥{upgrade_cost}/月\n\n"
f"新增功能: {', '.join(new_features)}"
)
# ----------------------------------------------------------------------
# 第三组:账户管理类工具 → 仅供账户管理Agent使用
# ----------------------------------------------------------------------
@tool
def get_user_account_info(user_id: str) -> str:
"""
查询用户账户的详细信息
Args:
user_id: 用户的唯一标识ID
Returns:
格式化的账户信息文本
"""
print(f"[Admin] 查询账户: {user_id}")
# 校验:用户ID为空时直接返回提示
if not user_id:
return "请提供您的用户ID以查询账户信息。"
# 命中用户:拼接账户详情返回
if user_id in USER_DATABASE:
user_info = USER_DATABASE[user_id]
return (
f"账户信息:\n"
f"用户ID: {user_id}\n"
f"当前套餐: {user_info['plan']}\n"
f"账户状态: {user_info['status']}\n"
f"支持级别: {user_info['support_level']}\n"
f"账户余额: ¥{user_info['balance']}"
)
# 未命中用户:返回提示
return f"未找到用户ID '{user_id}' 的账户信息。"
@tool
def process_refund_request(user_id: str, reason: str) -> str:
"""
提交退款申请,校验账户状态并计算退款金额
Args:
user_id: 用户ID
reason: 退款原因
Returns:
退款申请受理结果
"""
print(f"[Admin] 处理退款: {user_id}")
# 第一步:校验用户ID有效性
if not user_id or user_id not in USER_DATABASE:
return "请提供有效的用户ID以处理退款请求。"
user_info = USER_DATABASE[user_id]
# 第二步:校验账户状态,仅活跃账户可申请退款
if user_info["status"] != "active":
return "只有活跃账户才能申请退款。"
# 计算退款金额:按月费标准计算(简化逻辑)
refund_amount = PRODUCTS[user_info["plan"]]["price"]
# 返回格式化的退款回执
return (
f"退款申请已提交:\n"
f"用户ID: {user_id}\n"
f"退款原因: {reason}\n"
f"退款金额: ¥{refund_amount}\n"
f"处理时间: 3-5个工作日\n"
f"退款将原路返回到您的支付账户。"
)
# ==============================================================================
# 执行层:子Agent子图(共3个,均为标准ReAct工具调用结构)
# 【架构设计】每个子Agent是一个独立编译的子图,职责单一、可独立测试、可复用
# 【核心机制】标准ReAct循环:模型思考 → 调用工具 → 拿到结果 → 模型再思考 → 输出答案
# ==============================================================================
# ----------------------------------------------------------------------
# 子图1:技术支持Agent子图
# ----------------------------------------------------------------------
def create_tech_agent_subgraph():
"""
创建技术支持子Agent,负责处理所有技术类问题
内部结构:标准ReAct闭环(模型决策 ↔ 工具执行)
Returns:
编译完成的子图实例
"""
# 定义该Agent专属的工具列表 → 从权限上限制Agent只能调用技术类工具
tech_tools = [search_knowledge_base, create_support_ticket]
def tech_model_node(state: MessagesState):
"""
技术Agent的模型决策节点:调用大模型,自主决定是否调用工具
【核心作用】ReAct循环的"思考"环节,是Agent的大脑
"""
print("[Tech] LLM 决策...")
# 系统提示词:明确角色、工作流程、职责边界、回答规范
# 【设计逻辑】通过提示词约束Agent行为,配合工具权限,实现双重职责隔离
system_prompt = """你是技术支持助手,负责解决技术问题。
**工作流程**:
1. 先用 search_knowledge_base 查知识库
2. 查到答案 → 直接告诉用户
3. 查不到 → 用 create_support_ticket 创建工单
**职责边界**:
处理:报错、bug、登录失败、网络问题、性能问题
不处理:价格咨询、账户余额、退款申请
**回答要求**:
- 简洁明了,直接给出解决方案
- 知识库有答案就快速回复
- 需要创建工单时,告知用户工单ID和预计处理时间
"""
# 拼接完整消息列表:系统提示词放在最前面,后面跟上历史对话消息
messages = [SystemMessage(content=system_prompt)] + state["messages"]
# 先给模型绑定工具定义,再调用模型
# 【机制】bind_tools会把工具的JSON Schema注入模型,让模型知道有哪些工具可用、参数是什么
ai_message = llm.bind_tools(tech_tools).invoke(messages)
# 返回AI消息,会自动追加到状态的messages列表中
# 【机制】MessagesState的messages字段使用add_messages归约器,默认是追加而非覆盖
return {"messages": [ai_message]}
# 创建子图的构建器,使用内置对话状态
builder = StateGraph(MessagesState)
# 添加两个核心节点
# 1. 模型决策节点:负责思考和决策
builder.add_node("tech_model", tech_model_node)
# 2. 工具执行节点:LangGraph内置通用节点,负责自动执行工具调用
# 【机制】ToolNode会自动读取消息中的tool_calls,找到对应函数执行,生成ToolMessage
builder.add_node("tech_tools", ToolNode(tech_tools))
# 添加起始边:流程启动后,先进入模型决策节点
builder.add_edge(START, "tech_model")
# 添加条件边:模型决策后,动态决定下一步走向
builder.add_conditional_edges(
"tech_model", # 源节点:模型决策节点
tools_condition, # 路由判断函数:内置工具调用检测器
# 分支映射:路由函数的返回值 → 目标节点
{"tools": "tech_tools", END: END}
# 【机制】有tool_calls → 走tools分支到工具节点;没有 → 走END结束
)
# 添加回边:工具执行完成后,回到模型决策节点
# 【核心机制】形成 ReAct 闭环:工具结果返回给模型,模型继续推理下一步
builder.add_edge("tech_tools", "tech_model")
# 编译子图,返回可执行实例
return builder.compile()
# ----------------------------------------------------------------------
# 子图2:销售顾问Agent子图
# 【结构说明】与技术Agent完全一致的标准ReAct结构,仅工具集和系统提示词不同
# ----------------------------------------------------------------------
def create_sales_agent_subgraph():
"""创建销售子Agent,负责产品介绍、价格查询、升级费用计算"""
# 销售专属工具集
sales_tools = [get_product_info, calculate_upgrade_cost]
def sales_model_node(state: MessagesState):
"""销售Agent的模型决策节点"""
print("[Sales] LLM 决策...")
# 销售专属系统提示词:明确销售职责边界
system_message = SystemMessage(content="""
你是销售顾问,**只负责**产品销售相关咨询。
你负责:
- 介绍产品功能和价格(使用 get_product_info)
- 计算套餐升级费用(使用 calculate_upgrade_cost)
- 推荐合适产品
- 告知促销活动
你**不负责**:
- 查询用户余额、账户信息
- 处理退款申请
- 技术支持问题
**重要**:如果用户询问职责范围外的问题,请礼貌地告知:
"关于【账户/余额/退款/技术】问题,我会转交给相关同事处理。"
然后继续处理销售相关问题。
原则:先了解需求,再推荐方案;诚实专业,不夸大产品。
""")
# 拼接系统消息 + 历史对话
messages = [system_message] + state["messages"]
# 绑定销售工具后调用模型
ai_message = llm.bind_tools(sales_tools).invoke(messages)
return {"messages": [ai_message]}
# 构建子图,结构同技术Agent
builder = StateGraph(MessagesState)
builder.add_node("sales_model", sales_model_node)
builder.add_node("sales_tools", ToolNode(sales_tools))
builder.add_edge(START, "sales_model")
builder.add_conditional_edges(
"sales_model",
tools_condition,
{"tools": "sales_tools", END: END}
)
builder.add_edge("sales_tools", "sales_model")
return builder.compile()
# ----------------------------------------------------------------------
# 子图3:账户管理Agent子图
# 【结构说明】标准ReAct结构,负责账户查询、退款等客户管理业务
# ----------------------------------------------------------------------
def create_admin_agent_subgraph():
"""创建客户管理子Agent,负责账户查询、退款申请等业务"""
# 账户管理专属工具集
admin_tools = [get_user_account_info, process_refund_request]
def admin_model_node(state: MessagesState):
"""账户管理Agent的模型决策节点"""
print("[Admin] LLM 决策...")
# 账户管理专属系统提示词
system_message = SystemMessage(content="""
你是客户管理助手,**只负责**账户和支付相关咨询。
你负责:
- 查询账户信息、余额(使用 get_user_account_info)
- 处理退款申请(使用 process_refund_request)
你**不负责**:
- 产品介绍、价格咨询
- 技术支持问题
- 升级费用计算
**重要**:如果用户询问职责范围外的问题,请礼貌地告知:
"关于【产品/技术】问题,我会转交给相关同事处理。"
然后继续处理账户相关问题。
请根据用户的问题,选择合适的工具来提供帮助。
""")
# 拼接消息并调用模型
messages = [system_message] + state["messages"]
ai_message = llm.bind_tools(admin_tools).invoke(messages)
return {"messages": [ai_message]}
# 构建子图
builder = StateGraph(MessagesState)
builder.add_node("admin_model", admin_model_node)
builder.add_node("admin_tools", ToolNode(admin_tools))
builder.add_edge(START, "admin_model")
builder.add_conditional_edges(
"admin_model",
tools_condition,
{"tools": "admin_tools", END: END}
)
builder.add_edge("admin_tools", "admin_model")
return builder.compile()
# ==============================================================================
# 调度层:主管调度图(整个系统的核心大脑)
# 【核心架构】主管-工人模式(Supervisor-Worker)
# 【核心机制】任务队列 + 循环闭环 + 手动调用子图
# 1. 任务队列:识别所有任务后放入队列,逐个取出执行
# 2. 循环闭环:子Agent执行完回到主管,继续取下一个任务,直到队列为空
# 3. 手动调用子图:在节点内主动invoke子图,灵活控制传参和状态合并
# ==============================================================================
def create_supervisor_graph():
"""
创建主管调度总图,负责任务识别、分配、循环协调
支持单个用户问题包含多个跨领域任务的串行处理
Returns:
编译完成的主管总图实例
"""
# 第一步:实例化三个子Agent子图,作为主管可调度的执行单元
tech_subgraph = create_tech_agent_subgraph()
sales_subgraph = create_sales_agent_subgraph()
admin_subgraph = create_admin_agent_subgraph()
# --------------------------------------------------------------------------
# 定义主管状态:在对话状态基础上,扩展调度专用字段
# 【设计逻辑】继承MessagesState获得对话能力,新增4个字段用于任务调度管理
# --------------------------------------------------------------------------
class SupervisorState(MessagesState):
"""
主管全局状态结构
Attributes:
current_agent: 当前正在执行的子Agent名称,用于日志追踪
pending_tasks: 待处理任务队列,按顺序存放待执行的Agent名称
completed_tasks: 已完成任务列表,记录已执行过的Agent,避免重复
next_agent: 下一个要执行的Agent,由主管节点设置,供路由函数读取
"""
current_agent: str
pending_tasks: list[str]
completed_tasks: list[str]
next_agent: str | None = None # 默认值为None,表示无下一个任务
# --------------------------------------------------------------------------
# 主管核心节点:任务识别、队列管理、调度分发
# 【三分支逻辑】
# 1. 有待办任务 → 取队首执行
# 2. 首次执行无待办 → LLM意图识别,生成任务队列
# 3. 无任务 → 结束流程
# --------------------------------------------------------------------------
def supervisor_node(state: SupervisorState) -> dict:
"""
主管调度节点:整个系统的调度中枢
每次进入都会根据当前队列状态,决定下一步动作
"""
# 从状态中取出调度相关字段,缺省值为空列表
pending = state.get("pending_tasks", [])
completed = state.get("completed_tasks", [])
messages = state["messages"]
# 取最后一条消息,用于判断是否是新的用户输入
last_msg = messages[-1] if messages else None
# ============== 分支1:队列中有待处理任务 → 直接取出第一个执行 ==============
# 【触发场景】子Agent执行完回到主管,队列里还有剩余任务
if pending:
next_task = pending[0] # 取出队首第一个任务
remaining = pending[1:] # 剩下的任务保留在队列中
print(f"\n[Supervisor] 取出待处理任务: {next_task}, 剩余: {remaining}")
return {
"pending_tasks": remaining, # 更新剩余任务队列
"next_agent": next_task # 标记下一个要执行的Agent
}
# ============== 分支2:队列为空,且是新用户消息 → LLM做意图识别 ==============
# 【触发场景】首次进入主管,用户刚发了问题,还没做过任务拆分
if isinstance(last_msg, HumanMessage):
print(f"\n[Supervisor] 使用 LLM 进行意图识别...")
# 意图识别提示词:明确Agent分类标准,要求模型输出任务列表
routing_prompt = f"""你是意图识别专家。根据用户消息,决定调用哪个 Agent 来处理。
Agent 类型:
- tech_agent: 技术支持(报错、bug、故障、登录问题、网络问题、性能问题、技术咨询等)
- sales_agent: 销售服务(价格咨询、套餐升级、产品信息、购买咨询、升级费用等)
- admin_agent: 账户管理(查询余额、账户信息、退款申请、账户状态等)
注意:一个问题可能需要多个 Agent 处理。
用户消息: {last_msg.content}
请分析用户消息,返回需要调用的 Agent 列表(最多3个,按处理顺序排列)。
只返回 Agent 名称,用逗号分隔。例如:tech_agent,sales_agent"""
# 调用大模型进行意图分类
response = llm.invoke(routing_prompt)
# 兼容处理:取出模型返回的文本内容
response_text = response.content if hasattr(response, 'content') else str(response)
print(f"response_text========={response_text}")
# 解析模型输出,提取有效的Agent名称
new_tasks = []
# 遍历三个标准Agent名,检查是否出现在模型返回文本中
for agent in ["tech_agent", "sales_agent", "admin_agent"]:
# 两个条件:模型返回包含该Agent 且 该任务未完成过
if agent in response_text and agent not in completed:
new_tasks.append(agent)
# 识别到有效任务:初始化任务队列,取出第一个执行
if new_tasks:
print(f"[Supervisor] LLM 识别到任务: {new_tasks}")
next_task = new_tasks[0]
remaining = new_tasks[1:]
return {
"pending_tasks": remaining, # 剩余任务放入待办队列
"next_agent": next_task # 第一个任务作为下一步执行
}
# ============== 分支3:无任务可执行 → 结束流程 ==============
# 【触发场景】队列为空,也没有新的用户输入,所有任务都处理完了
print("\n[Supervisor] 所有任务已完成,结束对话")
return {"pending_tasks": [], "next_agent": None}
# --------------------------------------------------------------------------
# 路由函数:根据主管设置的next_agent,跳转到对应子Agent节点
# 【设计逻辑】将"决策"和"跳转"分离,主管节点只做决策写状态,路由函数只读状态做跳转
# --------------------------------------------------------------------------
def route_by_pending(state: SupervisorState) -> Literal["tech_agent", "sales_agent", "admin_agent", END]:
"""
条件路由函数:读取next_agent字段,返回对应的目标节点名称
"""
next_agent = state.get("next_agent")
if next_agent == "tech_agent":
return "tech_agent"
elif next_agent == "sales_agent":
return "sales_agent"
elif next_agent == "admin_agent":
return "admin_agent"
else:
# next_agent为空时,返回结束标记
return END
# --------------------------------------------------------------------------
# 子Agent调用节点:手动执行子图,合并结果到主管状态
# 【设计选择】手动调用子图,而非直接嵌套子图为节点
# 优势1:可以自定义传参,灵活控制给子图传递哪些状态
# 优势2:可以自定义状态合并逻辑,同时更新调度字段
# 优势3:父子图状态结构不同时,可手动做字段映射
# --------------------------------------------------------------------------
def call_tech_agent(state: SupervisorState) -> SupervisorState:
"""调用技术支持子Agent,执行完成后合并结果"""
print("[Supervisor] 调用 Tech 子图...")
# 保留原有的调度状态,子图执行不修改待办队列
pending = state.get("pending_tasks", [])
completed = state.get("completed_tasks", [])
# 手动调用子图:传入当前对话历史,获取子图执行后的完整状态
# 【机制】子图独立执行,内部走完完整的ReAct循环后返回结果
result = tech_subgraph.invoke({"messages": state["messages"]})
# 更新主管状态:合并消息、标记任务已完成、记录当前Agent
return {
"messages": result["messages"], # 合并子图产生的新消息到全局对话
"pending_tasks": pending, # 待办队列保持不变,回到主管后再取
"completed_tasks": completed + ["tech"], # 标记技术任务已完成
"current_agent": "tech" # 记录当前执行的Agent
}
def call_sales_agent(state: SupervisorState) -> SupervisorState:
"""调用销售顾问子Agent"""
print("[Supervisor] 调用 Sales 子图...")
pending = state.get("pending_tasks", [])
completed = state.get("completed_tasks", [])
result = sales_subgraph.invoke({"messages": state["messages"]})
return {
"messages": result["messages"],
"pending_tasks": pending,
"completed_tasks": completed + ["sales"],
"current_agent": "sales"
}
def call_admin_agent(state: SupervisorState) -> SupervisorState:
"""调用账户管理子Agent"""
print("[Supervisor] 调用 Admin 子图...")
pending = state.get("pending_tasks", [])
completed = state.get("completed_tasks", [])
result = admin_subgraph.invoke({"messages": state["messages"]})
return {
"messages": result["messages"],
"pending_tasks": pending,
"completed_tasks": completed + ["admin"],
"current_agent": "admin"
}
# --------------------------------------------------------------------------
# 构建主管总图
# --------------------------------------------------------------------------
builder = StateGraph(SupervisorState)
# 添加所有节点:1个主管调度节点 + 3个子Agent调用节点
builder.add_node("supervisor", supervisor_node)
builder.add_node("tech_agent", call_tech_agent)
builder.add_node("sales_agent", call_sales_agent)
builder.add_node("admin_agent", call_admin_agent)
# 起始边:流程启动后,首先进入主管节点做任务识别
builder.add_edge(START, "supervisor")
# 条件边:主管决策后,根据路由函数跳转到对应子Agent或结束
builder.add_conditional_edges(
"supervisor",
route_by_pending,
{
"tech_agent": "tech_agent",
"sales_agent": "sales_agent",
"admin_agent": "admin_agent",
END: END
}
)
# 三条回边:所有子Agent执行完成后,都回到主管节点
# 【核心机制】形成 主管→子Agent→主管 的闭环,实现循环调度
# 每次回到主管,都会重新检查任务队列,还有任务就继续分发,没有就结束
builder.add_edge("tech_agent", "supervisor")
builder.add_edge("sales_agent", "supervisor")
builder.add_edge("admin_agent", "supervisor")
# 编译主管总图,返回可执行实例
return builder.compile()
# ==============================================================================
# 测试运行模块
# ==============================================================================
def run_examples():
"""
运行测试用例,验证多智能体系统的效果
覆盖单领域任务、跨领域多任务等典型场景
"""
print("=" * 70)
print("主管架构多智能体系统 (支持循环协调多任务)")
print("=" * 70)
# 创建并编译主管总图
graph = create_supervisor_graph()
# 测试用例列表,可按需注释开启
test_cases = [
# {"message": "我的应用登录有问题,显示error 500"}, # 单领域:技术
# {"message": "我想了解专业版的价格"}, # 单领域:销售
# {"message": "查询一下我的余额,用户ID是user123"}, # 单领域:账户
# 跨领域多任务:同时包含销售+账户两个领域的需求
{"message": "我想了解专业版的价格。我的用户ID是user123帮我查询一下我的余额"},
]
# 遍历执行每个测试用例
for i, test_case in enumerate(test_cases, 1):
print(f"\n{'=' * 70}")
print(f"测试案例 {i}")
print(f"{'=' * 70}")
print(f"用户: {test_case['message']}")
print("-" * 50)
# 构造初始状态:包含用户消息 + 调度字段初始值
initial_state = {
"messages": [HumanMessage(content=test_case["message"])],
"current_agent": "supervisor", # 初始当前角色为主管
"pending_tasks": [], # 待办队列初始为空
"completed_tasks": [], # 已完成列表初始为空
"next_agent": None # 无下一步执行目标
}
try:
# 调用工作流执行,同步等待完整结果
result = graph.invoke(initial_state)
# 打印AI最终回复内容(只打印AI消息,过滤掉工具消息等)
print("\nAI 回复:")
for msg in result["messages"]:
if isinstance(msg, AIMessage) and msg.content:
print(f" {msg.content}")
# 打印任务执行统计
print(f"\n已完成任务: {result.get('completed_tasks', [])}")
except Exception as e:
# 异常捕获:打印错误信息和堆栈,便于调试
print(f"处理失败: {e}")
import traceback
traceback.print_exc()
# ==============================================================================
# 程序入口
# ==============================================================================
if __name__ == "__main__":
# 主程序启动,运行测试示例
run_examples()
