Week 3 -- Day 3:多智能体协作系统
Day 3:多智能体协作系统
引言:单Agent的天花板
在前两天的学习中,我们使用 LangGraph 构建了从简单 ReAct 循环到复杂多节点流水线的 Agent 工作流,这些图在单个 Agent 的范畴内已经相当强大。然而当你把越来越多工具塞进同一个 Agent,或者让同一个 Agent 同时处理代码审查、数据分析、客户沟通这些性质迥异的任务时,单 Agent 架构的瓶颈会逐渐暴露。最直观的问题是上下文膨胀,每次工具调用,尤其是网页搜索、数据库查询、文件读取这类会返回大量内容的操作,都会在消息历史中堆积数据,很快逼近模型的上下文窗口上限。更深层的问题是工具选择退化,当可选工具超过十几个时,模型在决定调用哪个工具这件事上的准确率会显著下降,它会开始混淆相似工具、忽略冷门工具,甚至为了避免选错而根本不调用任何工具。还有一个工程层面的问题,不同业务领域需要不同的系统提示、不同的工具集、甚至不同的模型,把所有这些塞进一个 Agent 的提示里,等于让模型在每次推理时都要消化一套庞杂的指令。
这些问题指向一个共同的解决方案,把复杂的单一 Agent 拆分成多个专门的子Agent,每个子Agent 负责一个明确的领域,由主 Agent 担任协调者来调度它们。这就是多智能体协作系统的核心思想。LangChain 官方文档特别指出,多智能体设计的本质是上下文工程,决定每个 Agent 看到什么信息、不知道什么信息。一个子Agent 只获得它完成任务所需的最小工具集和最小上下文,主Agent 只看到子Agent 返回的最终结果而非中间过程,上下文因此被天然隔离。
在 LangChain 生态中,多智能体架构有两个实现路径。一条是 LangChain 核心库提供的底层模式,你用 create_agent() 创建子Agent,再用 @tool 装饰器或 as_tool() 方法把它们包装成工具,手动构建主Agent 和子Agent 之间的协调逻辑。另一条是 Deep Agents SDK 提供的高层封装,它内置了子Agent 生成、上下文压缩、任务规划、虚拟文件系统等全套基础设施,让多智能体系统从"手写协调逻辑"升级为"声明式配置"。理解底层模式有助于掌握多智能体的本质,而使用 Deep Agents 则可以大幅降低生产级多智能体系统的开发成本。
多智能体架构的四种模式
LangChain 官方文档将多智能体架构归纳为四种正式模式,每种在控制权、上下文隔离、开发复杂度上各有侧重。这四种模式分别对应了顺序执行、条件路由、并行协作和层级调度这几类经典场景。
Subagents
Subagents(子Agent作为工具)是最直观也最常用的模式。在这种架构中,一个主Agent(常被称为 Supervisor)维护完整的对话上下文和记忆,子Agent 以工具的形式暴露给主Agent 调用。当主Agent 判断某个任务属于某个子Agent 的专长领域时,它调用对应的工具,子Agent 在一个全新的隔离上下文中执行任务,完成后将结果返回给主Agent,主Agent 再将结果整合进自己的回复中。子Agent 是无状态的,每次调用都是"一次性"的,它们不记住之前的对话,所有会话记忆由主Agent 统一管理。这种模式的优势在于集中控制,主Agent 全权决定调用谁、传什么参数、如何组合结果。同时由于子Agent 上下文隔离,主Agent 的对话历史不会被子Agent 的中间过程污染。官方文档的性能对比数据显示,对于单次任务 Subagents 模式需要约 4 次模型调用(比 Handoffs 和 Skills 多 1 次,因为结果需要回流经过主Agent),但对于多领域并行任务它表现最优,因为多个子Agent 可以在同一轮中被并行调用。层级调度本质上就是 Subagents 模式的具体实现,主Agent 分配任务给不同的子Agent,子Agent 各自完成后再汇总。
Handoffs
Handoffs(状态驱动切换)与 Subagents 的核心区别在于控制权的转移方式。Handoffs 不是把子Agent 当作工具来调用,而是通过工具调用修改一个状态变量,这个状态变量的变化触发 LangGraph 的路由逻辑,将执行流从一个Agent 切换到另一个Agent。被激活的 Agent 直接接手对话,它可以继续与用户交互,也可以继续切换到下一个 Agent。这种模式的精髓在于"行为随状态改变",系统在运行时根据当前任务的进展动态决定哪个Agent 应该获得控制权。条件路由就是 Handoffs 模式的典型应用,审核节点根据审核结果决定是输出成品还是退回修改,路由器根据任务类型将用户请求分发给不同的专门Agent。
Skills
Skills(技能按需加载)是一种轻量级的专业化方式,它不涉及多个Agent 之间的协作,而是让单个 Agent 在需要时动态加载专业技能。每个 Skill 是一个包含特定领域知识、工作流指导和工具使用说明的文件包,Agent 启动时只读取 Skill 的元数据(名称和简介),只有当判断某个 Skill 与当前任务相关时,才加载其完整内容到上下文中。这种渐进式信息披露机制在不引入多Agent 复杂度的前提下实现了专业化,适合任务类型相对集中但需要深入领域知识的场景。
Router
Router(路由分发)是最简单的多Agent 模式。一个分类步骤(通常是一次 LLM 调用)分析用户输入,将其路由到一个或多个专门Agent,各Agent 的结果被汇总成最终回复。Router 与 Supervisor 的关键区别在于,Router 只在入口处做一次分类,它不维护持续的对话状态,不记得到第二轮对话中用户说了什么。这使得 Router 模式实现简单、延迟低,但无法处理需要跨多轮跟踪上下文变化的复杂任务。
明白这四种模式的原理后,一个自然的问题是什么时候该用哪种?官方文档给出了一份清晰的决策框架。如果你的子Agent 不需要直接与用户对话、你希望所有控制流经过一个中心节点,Subagents 是最佳选择。如果你需要在 Agent 之间传递控制权、支持多跳协作,比如先让搜索Agent 收集资料,再让分析Agent 处理结果,最后让写作Agent 生成报告,那么Handoffs 更合适。如果你的需求是让单个 Agent 在需要时"变专业"而不增加系统架构复杂度,Skills 是最轻量的方案。如果你只是想把不同类型的请求分发给不同的专门Agent 且不需要跨轮记忆,Router 足够用了。重要的是这些模式可以混合使用,比如一个 Subagents 架构中的子Agent 内部可以使用 Skills 来加载专业知识,或者一个 Handoffs 流程中的某个 Agent 本身就是一个 Router,将任务进一步分发。
Deep Agents SDK:多智能体的生产级基础设施
如果说上一节讨论的四种模式是建筑图纸,Deep Agents SDK 就是一套预制的混凝土结构,它把这些模式的最佳实践固化为开箱即用的组件。create_deep_agent() 是它的入口 API,表面上和 create_agent() 很像,但内部集成了一个完整的中间件栈,为 Agent 提供了一系列生产级能力。
任务规划
任务规划是 Deep Agents 最基础的内置能力。每个 Deep Agent 自动获得一个 write_todos 工具,它让 Agent 在执行复杂任务前先把目标分解成步骤列表、追踪每个步骤的完成状态、并根据中间结果动态调整计划。这不是一个简单的 todo list 面板,中间件会在系统提示中注入规划指导,教模型何时创建计划、如何标记完成、遇到阻碍时如何重排优先级。对于多智能体系统来说,规划能力尤为重要,主Agent 可以先写出"收集数据→分析数据→生成报告"的计划,然后将每个步骤委派给不同的子Agent 执行,最后汇总成果。
上下文压缩
上下文压缩是 Deep Agents 解决上下文膨胀问题的内置机制。压缩分为两层,offloading(卸载)和 summarization(摘要),它们自动触发,无需开发者手动干预。Offloading 处理的是工具调用的输入和输出,当一个工具调用的参数或返回值超过 20000 个 token 时,Deep Agents 会把这个内容保存到虚拟文件系统中,然后在消息历史里用一个文件路径引用和一个前 10 行的预览来替代原始内容。Agent 之后可以通过 read_file 或 grep 工具按需检索这些被卸载的内容。Summarization 处理的是对话历史本身,当会话上下文达到模型最大输入 token 的 85% 且没有更多内容可被卸载时,Deep Agents 会调用一个 LLM 生成对话的结构化摘要,包括会话目标、已创建的产出物、下一步计划,用这个摘要替换完整的消息历史,同时将原始对话记录写入文件系统作为存档。这种双轨机制让 Agent 在长期会话中既保持了对目标和进展的感知,又没有丢失原始细节,需要时可以回到文件系统中翻查。如果你希望 Agent 在任务之间主动触发压缩而不是等待 85% 阈值,还可以启用 compact_conversation 工具让模型自主决定压缩时机。
虚拟文件系统
虚拟文件系统是 Deep Agents 最被低估但极其重要的能力。它提供了 ls、read_file、write_file、edit_file 四个基础文件操作工具,Agent 可以用这些工具在会话中创建和管理文件。文件系统的价值在于它为 Agent 提供了一个"外部硬盘",大型工具结果被 offload 到这里、对话历史被存档到这里、子Agent 的产出物可以写入这里供主Agent 读取。文件系统后端是可插拔的,默认使用 StateBackend(文件存储在图的 checkpoint 状态中,随会话结束而消亡),你可以通过 CompositeBackend 将特定路径(如 /memories/)路由到 StoreBackend(持久化到 LangGraph Store),实现跨会话的长期记忆。
子Agent 生成
子Agent 生成是 Deep Agents 多智能体能力的核心。每个 Deep Agent 通过一个内置的 task 工具来创建和调用子Agent,这比手动用 @tool 包装子Agent 要简洁得多。子Agent 在独立的上下文中运行,完成后只返回最终结果给主Agent,中间的工具调用和推理过程完全隔离。Deep Agents 自动包含一个名为 general-purpose 的默认子Agent,它继承主Agent 的所有工具和模型配置,专门用于卸载那些会膨胀主Agent 上下文的多步骤任务。你可以通过 subagents 参数配置额外的专门子Agent,每个子Agent 拥有独立的名称、描述、系统提示、工具集、模型、中间件、Skills 和结构化输出 Schema。
子Agent 委派:从原生方式到 Deep Agents
理解子Agent 委派的最佳方式是从 LangChain 的原生实现开始,然后过渡到 Deep Agents 的声明式配置。这两种方式在底层原理上是一致的,子Agent 被包装为工具,主Agent 通过调用工具来委派任务,只是抽象层级不同。
原生方式的核心是一个简单的概念,用 create_agent() 创建一个子Agent,然后用一个函数把它包装成 @tool,这个函数负责将主Agent 传入的查询转发给子Agent,并将子Agent 的最终回复返回给主Agent。代码如下:
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
load_dotenv()
model = init_chat_model("Qwen/Qwen3.6-27B", model_provider="openai")
# 定义子Agent使用的工具
@tool
def web_search(query: str) -> str:
"""在互联网上搜索指定关键词,返回相关信息的摘要。"""
return f'关于"{query}"的搜索结果:这是一份模拟的搜索摘要,实际使用时请替换为真实的搜索API。'
@tool
def file_reader(file_path: str) -> str:
"""读取指定文件的内容并返回。"""
return f'文件"{file_path}"的内容:这是一份模拟的文件内容,实际使用时请替换为真实的文件读取逻辑。'
@tool
def calculator(expression: str) -> str:
"""计算数学表达式并返回结果。支持四则运算和基本函数。"""
try:
result = eval(expression)
return f"计算结果:{expression} = {result}"
except Exception as e:
return f"计算错误:{e}"
@tool
def chart_generator(data_description: str) -> str:
"""根据数据描述生成可视化图表的描述信息。"""
return f'根据"{data_description}"生成的图表:这是一份模拟的图表描述,实际使用时请替换为真实的图表生成逻辑。'
# 创建研究子Agent
research_agent = create_agent(
model=model,
tools=[web_search, file_reader],
system_prompt="你是一个研究员,负责收集和整理信息。返回结构化的研究笔记。"
)
# 创建分析子Agent
analysis_agent = create_agent(
model=model,
tools=[calculator, chart_generator],
system_prompt="你是一个数据分析师,基于研究笔记提炼核心观点。"
)
# 将子Agent包装为工具
@tool("research",description="需要搜索和收集某个主题的详细资料时使用此工具")
def call_research(query: str) -> str:
"""调用研究Agent进行信息收集"""
result = research_agent.invoke({
"messages":[{"role":"user","content":query}]
})
return result
@tool("analysis",description="当需要对研究数据进行计算和分析时使用此工具")
def call_analysis(data: str) -> str:
"""调用分析Agent进行数据分析"""
result = analysis_agent.invoke({
"messages":[{"role":"user","content":data}]
})
return result
# 主Agent以子Agent为工具
main_agent = create_agent(
model=model,
tools=[call_research, call_analysis],
system_prompt="你是一个项目经理,通过调用研究和分析工具来完成复杂的信息处理任务。"
)
result = main_agent.invoke(
{"messages": [{"role": "user", "content": "调研量子计算的最新进展并分析其商业影响"}]}
)
print(result["messages"][-1].content)
这段代码揭示了原生方式的几个关键设计点。第一,子Agent 是通过 create_agent() 创建的独立 Agent 实例,每个有自己的系统提示和工具集,它们之间完全隔离。第二,包装函数(call_research 和 call_analysis)充当了主Agent 和子Agent 之间的翻译层,它接收主Agent 传入的字符串查询,构造消息字典传给子Agent,再从子Agent 的消息历史中提取最后一条消息的内容作为返回值。这个翻译层隐藏了子Agent 内部的工具调用过程,主Agent 只知道"我发了一个查询,得到了一个结果",子Agent 内部经历了多少次思考-行动循环对主Agent 完全不可见。这正是上下文隔离的精髓。第三,工具的描述(@tool 的 description 参数)是主Agent 决定何时调用哪个子Agent 的唯一依据,因此描述必须精确、具体、行动导向。"当需要搜索和收集某个主题的详细资料时使用此工具"比"帮助研究"好得多。
如果你有多个子Agent,也可以不写多个包装函数,而是使用 as_tool() 将子Agent 直接转换为工具,这与大纲中的代码示例完全一致:
# 将子Agent直接转换为工具
main_agent = create_agent(
model=model,
tools=[
research_agent.as_tool(
name="research",
description="当需要搜索和收集某个主题的详细资料时使用此工具"
),
analysis_agent.as_tool(
name="analysis",
description="当需要对研究数据进行计算和分析时使用此工具"
),
],
system_prompt="你是一个项目经理,通过调用研究和分析工具来完成复杂的信息处理任务。"
)
as_tool() 方法将子Agent 内部的所有调用逻辑封装为一个标准的 LangChain Tool 对象,自动处理消息构造和结果提取,开发者只需指定名称和描述。这种方式比手动写包装函数更加简洁,也是官方推荐的原生多Agent 集成方式。
原生方式的优点是透明,你能精确控制子Agent 的输入输出格式、状态传递和错误处理。缺点也很明显,你需要手动管理子Agent 的上下文隔离(确保每次调用的消息历史是干净的)、处理中断恢复(如果子Agent 内部需要人工审批怎么办)、以及在并行调用多个子Agent 时做并发控制。这些正是 Deep Agents 帮你解决的问题。
Deep Agents 的 create_deep_agent() 将子Agent 配置简化为声明式的字典或 CompiledSubAgent 对象:
from pydantic import BaseModel, Field
from deepagents import create_deep_agent
from dotenv import load_dotenv
from langchain.tools import tool
from langchain.chat_models import init_chat_model
load_dotenv()
# 初始化模型(避免 create_deep_agent 内部使用 Responses API)
main_model = init_chat_model("Qwen/Qwen3.6-27B", model_provider="openai")
analysis_model = init_chat_model("deepseek-ai/DeepSeek-V4-Pro", model_provider="openai")
# 定义分析子Agent的结构化输出格式
class AnalysisResult(BaseModel):
"""分析子Agent的结构化输出"""
core_points: list[str] = Field(description="核心观点列表(3-5个)")
data_support: str = Field(description="数据支撑说明")
confidence: float = Field(description="置信度评估,0到1之间")
@tool
def web_search(query: str) -> str:
"""在互联网上搜索指定关键词,返回相关信息的摘要。"""
return f'关于"{query}"的搜索结果:这是一份模拟的搜索摘要,实际使用时请替换为真实的搜索API。'
@tool
def calculator(expression: str) -> str:
"""计算数学表达式并返回结果。支持四则运算和基本函数。"""
try:
result = eval(expression)
return f"计算结果:{expression} = {result}"
except Exception as e:
return f"计算错误:{e}"
research_subagent = {
"name": "research-agent",
"description": "收集某个主题的详细资料并返回结构化的研究笔记。当需要深入调研时使用。",
"system_prompt": (
"你是一个资深研究员。收到任务后,使用搜索工具收集相关信息,"
"对信息进行交叉验证,最后以结构化笔记的形式返回。"
"返回格式:1) 核心事实(3-5条)2) 信息来源 3) 值得深入的方向。"
"控制在 500 字以内。"
),
"tools": [web_search]
}
analysis_subagent = {
"name": "analysis-agent",
"description": "对研究笔记和数据进行定量分析和逻辑推理。当需要对信息进行深度分析时使用。",
"system_prompt": (
"你是一个数据分析师。基于提供的研究笔记,进行逻辑分析和数据计算。"
"返回格式:1) 核心观点(3-5个)2) 数据支撑 3) 置信度评估。"
"控制在 400 字以内。"
),
"tools": [calculator],
"model": analysis_model,
"response_format": AnalysisResult,
}
main_agent = create_deep_agent(
model=main_model,
system_prompt=(
"你是一个项目经理,协调研究Agent和分析Agent完成任务。"
"对于复杂问题,先用研究Agent收集信息,再用分析Agent提炼观点。"
"不要自己直接回答需要调研的问题,始终委派给专门的子Agent。"
),
subagents=[research_subagent, analysis_subagent]
)
result = main_agent.invoke({
"messages": [{"role": "user", "content": "调研量子计算的最新进展并分析其商业影响"}]
})
print(result["messages"][-1].content)
看看这里发生了什么。create_deep_agent() 内部自动为每个配置字典中的子Agent 编译了一个完整的 Agent 图,包括它自己的 ReAct 循环、工具执行、上下文管理中间件,并将它们注册到内置的 task 工具上。主Agent 看到的只是一个名为 task 的工具,调用时传入子Agent 名称和任务描述,子Agent 在隔离的上下文中自主完成任务,返回浓缩的结果。不需要手工写任何包装函数。
子Agent 配置字典的字段完整映射了子Agent 的个性化空间,name 是唯一标识符和主Agent 调度时的引用,description 是主Agent 决定是否委派的核心依据,system_prompt 是子Agent 的独立行为指导(不会追加主Agent 的系统提示),tools 是子Agent 的专属工具集(不给它就不会用),model 可以为不同子Agent 指定不同模型,比如研究用 Gemini 的大上下文窗口,分析用 GPT 的推理能力,middleware 可以为子Agent 追加额外的中间件。还有一个特别有用的字段是 response_format,它让子Agent 以结构化 JSON 返回结果而非自由文本,主Agent 收到的 ToolMessage 内容将是一个 JSON 字符串,可以被下游逻辑可靠地解析。
如果你有一个已经编译好的 LangGraph 图想作为子Agent 使用,可以用 CompiledSubAgent 包装它:
from deepagents import CompiledSubAgent
# 假设你有一个预编译的自定义分析图
custom_graph = create_agent(
model=model,
tools=[specialized_tools],
prompt="你是一个专门的分析Agent..."
)
compiled_subagent = CompiledSubAgent(
name="advanced-analysis",
description="使用高级分析管线处理复杂数据",
runnable=custom_graph
)
这让你可以将任何已有的 LangGraph 工作流无缝集成到 Deep Agents 的多智能体系统中。子Agent 被编译后以外挂工具的形式接入主Agent,但配置过程是声明式的。
关于子Agent 的最佳实践,官方文档给出了几条非常实用的建议。让每个子Agent 返回简洁的摘要而非原始数据,在子Agent 的系统提示中明确要求"控制在 X 字以内,不要包含原始搜索结果",否则上下文隔离的优势就会被冗长的回复抵消。给子Agent 只提供它确实需要的工具,工具越多模型的选择准确率越低,这和单 Agent 一样适用。为不同任务的子Agent 选择不同的模型,利用各大模型在不同维度的优势。最后,在主Agent 的系统提示中明确指示它应该委派而非自己动手,这是最容易踩的坑,主Agent 有时会"自信"地自己回答问题而不调用子Agent,需要在提示中明确引导。
并行协作与架构选型
并行协作是多智能体系统中最能体现性能优势的模式。在 LangGraph 层面,Send 机制是实现并行分发的核心原语。当一个节点需要将数据拆分成多份、分别发给同一个下游节点的多个并行实例时,使用普通边或 Command 都难以胜任,因为并行实例的数量在运行时才能确定。Send 让条件边函数返回多个 Send 对象,每个对象携带不同的状态片段去触发一次目标节点的执行,所有这些执行在同一个 super-step 中并行发生:
from langgraph.types import Send
def continue_to_analysis(state: PipelineState):
"""研究完成后,为每篇资料创建独立的分析任务。"""
articles = state.get("articles", [])
return [Send("analysis", {"current_article": article}) for article in articles]
pipeline.add_conditional_edges("research", continue_to_analysis)
每个 Send("analysis", {"current_article": article}) 都会触发分析节点的一次执行。如果 research 节点产出了 5 篇文章,那就会有 5 个 analysis 实例在同一个 super-step 中并行处理,所有并行任务完成后图继续向前推进。将这种能力与 Deep Agents 的 Subagents 模式结合,你可以构建出主Agent 在单轮中并行调用多个子Agent 的高效系统,搜索Agent 同时查多个数据源、分析Agent 同时审阅多份文档、审核Agent 同时检查多个模块,总延迟由最慢的那个决定而非所有任务的总和。
回到架构选型的整体视角,当你面对一个具体的业务需求时,可以从四个维度来评估,控制权归属、上下文隔离需求、并行化需求和开发复杂度。顺序执行模式对应最简单的线性流水线,节点按固定次序执行,前一个的输出是后一个的输入,文档处理、数据 ETL 这类步骤固定、依赖明确的任务最适合。条件路由模式在流水线中引入决策点,根据运行时状态决定走哪条分支,适合需要审核-重试循环、多级审批、根据复杂度选择不同处理策略的场景。并行协作模式通过 Send 或 Subagents 的多路并发来加速,适合搜索聚合、批量评估、多视角分析等可以"分而治之"的任务。层级调度模式由主Agent 统筹规划、动态分配子任务给不同的子Agent,适合需要灵活任务分解和跨领域协调的复杂项目。
练习任务
- 实现一个主Agent调度两个子Agent的系统
- 探索 Deep Agents 的 auto_compress 功能
- 设计一个并行协作模式的多Agent流程
考核点 ✅
- 子Agent委派:提交主Agent + 2 个子Agent 的调度代码,含
as_tool()集成 - Deep Agents 验证:提交使用
DeepAgent的代码,展示auto_compress的实际效果 - 并行模式:提交并行协作模式的 LangGraph 代码,展示多路并发输出
- 架构选型:口头对比顺序/条件/并行/层级四种多Agent模式的适用场景

浙公网安备 33010602011771号