
多Agent的必要性
问题:复杂任务通常需要多个步骤和不同领域的专业知识。让一个Agent“全能”地处理所有事情会导致:
- 上下文臃肿:Agent在一次对话中可能调用很多工具,产生大量中间结果,这些都会挤占有限的上下文窗口,影响后续决策。
- 缺乏专业性:一个Agent的指令(System Prompt)难以兼顾所有 specialized 任务的最佳实践。
解决方案:多Agent系统。引入一个“主Agent”(Supervisor)负责协调,将特定子任务分配给具有专业能力的“子Agent”(Subagent)执行。子Agent在独立的环境中完成任务后,将精简的结果返回给主Agent。
核心价值 - 上下文隔离:这是DeepAgents多Agent设计的核心。子Agent内部复杂的工具调用和中间过程被隔离在其自己的会话中,不会污染主Agent的上下文窗口。主Agent只看到清晰、最终的结果。
何时使用子Agent?
- ✅ 适合:多步骤复杂任务、需要专业知识的领域、需要使用不同模型能力的任务、需要保持主Agent专注于高层协调时。
- ❌ 不适合:简单的单步任务、需要维护中间上下文的任务、Agent调用开销大于收益时。
DeepAgents 多Agent核心机制详解
子Agent(Subagent)是什么?
- 定义:子Agent是一个由主Agent创建和调用的、独立的Agent实例。它拥有自己独立的系统指令、工具集和会话上下文。
- 生命周期:由主Agent动态创建,执行特定任务,返回结果后其生命周期通常结束(资源被回收)。
- 类比:就像项目经理(主Agent)将“市场调研”这个任务分配给专业的市场分析师(子Agent)。分析师用自己的方法(工具)完成一份报告(结果)交给经理,经理不需要关心分析师具体查询了多少网站、整理了多少数据。
核心工具:task()
- 角色:
task()是DeepAgents框架自动提供给主Agent的一个特殊工具。当主Agent决定将工作委托出去时,就调用此工具。 - 参数:
name(str): 要调用哪个子Agent。例如"research-agent"。task(str): 给子Agent的具体任务描述。例如“详细研究一下LangChain框架在2024年的更新情况。”
- 调用效果:主Agent的思考过程暂停,系统启动(或复用)指定的子Agent,子Agent独立运行直至完成任务,并将结果以
ToolMessage的形式返回给主Agent。主Agent接着处理这个结果。
底层引擎:SubAgentMiddleware
- 定位:
SubAgentMiddleware是LangChain/DeepAgents的一个中间件,它是实现task()工具功能的底层支撑。 - 工作原理:
- 注册:在创建主Agent时,将
SubAgentMiddleware添加到中间件列表,并配置好可用的子Agent列表。 - 拦截与路由:当主Agent调用名为
task的工具时,该中间件会拦截这个调用。 - 子Agent实例化:根据
name参数,从预配置的子Agent列表中找出对应的配置(系统提示、工具、模型等)。 - 执行隔离:在一个全新的、隔离的上下文中,使用找到的配置启动一个子Agent,并将
task参数作为用户输入传递给它。 - 结果回传:子Agent运行结束,生成最终回复。中间件将子Agent的最终回复(一个或多个
AIMessage)包装成一个ToolMessage,作为task()工具的“结果”返回给主Agent的会话流。
- 注册:在创建主Agent时,将
同步 vs. 异步子Agent
- 本版本重点(同步):主Agent调用
task()后,会等待子Agent完全执行完毕并返回结果,然后再继续。这是最常见的模式,适用于大多数需要顺序执行的任务。 - 下一个版本(异步):DeepAgents也支持异步子Agent,允许主Agent同时发起多个
task()调用,子Agent并行执行。适用于相互独立的长任务。这需要更复杂的协调(如AsyncSubAgentMiddleware),是进阶内容。
创建SubAgent
DeepAgents 支持两种配置子Agent的方式:简单的字典配置(SubAgent)和复杂的预编译图(CompiledSubAgent)。
字典配置子Agent (SubAgent)
这是最常见的方式。通过一个包含特定字段的字典来定义子Agent。
核心字段说明:
name(str): 唯一标识符。主Agent通过此名称调用子Agent。description(str): 清晰描述子Agent的职责。主Agent据此决定是否委托。system_prompt(str): 子Agent的系统指令。不会继承自主Agent,必须自定义。tools(list): 子Agent可用的工具列表。不会继承自主Agent,应保持精简。model(str): 可选。指定子Agent使用的模型,覆盖主Agent的模型。# 1. 定义研究子Agent research_subagent = { "name": "research-agent", # 主Agent通过此名调用 "description": "用于深入研究问题,执行网络搜索并合成信息。", # 清晰的描述 "system_prompt": """你是一名专业研究员。请遵循以下步骤: 1. 理解用户的研究问题。 2. 使用`mock_internet_search`工具进行检索。 3. 从结果中提炼关键信息,合成一份结构化的摘要。 4. 务必保持回答简洁(不超过300字)。""", # 专属指令 "tools": [mock_internet_search], # 只授予它需要的工具 # `model` 字段被省略,因此将使用主Agent的模型 } # 2. 创建主Agent,并注册子Agent agent = create_deep_agent( model="gpt-4o", # 主Agent的模型 system_prompt="你是协调员。对于复杂的研究任务,请使用`task`工具委托给研究子Agent。", subagents=[research_subagent] # 传入子Agent列表 )
使用预编译图子Agent (CompiledSubAgent)
对于极其复杂、需要自定义工作流逻辑的任务,可以将一个完整的 LangGraph 的 StateGraph 作为子Agent,或者将一个完整的LangChain中的create_agent()作为一个子Agent。
# 案例2-2:使用一个自定义的LangGraph图作为子Agent
from deepagents import create_deep_agent, CompiledSubAgent
from langgraph.graph import StateGraph, MessagesState, START
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
# 1. 构建一个自定义的LangGraph(例如,一个先搜索后分析的两步图)
def search_node(state: MessagesState):
# 模拟搜索步骤
return {"messages": [HumanMessage(content=f"已搜索: {state['messages'][-1].content}")]}
def analysis_node(state: MessagesState):
# 模拟分析步骤
return {"messages": [HumanMessage(content=f"已分析结果。结论是: 潜力巨大。")]}
builder = StateGraph(MessagesState)
builder.add_node("search", search_node)
builder.add_node("analysis", analysis_node)
builder.add_edge(START, "search")
builder.add_edge("search", "analysis")
custom_graph = builder.compile()
# 2. 将编译好的图包装成 CompiledSubAgent
custom_subagent = CompiledSubAgent(
name="advanced-analyzer",
description="执行先搜索后深度分析的复杂工作流。",
runnable=custom_graph # 必须是已编译的Runnable
)
# 3. 创建主Agent
agent = create_deep_agent(
model="gpt-4o",
subagents=[custom_subagent]
)
# 主Agent可以通过 task(name=“advanced-analyzer”, ...) 来调用这个复杂工作流。
SubAgent的进阶
子Agent的精细化配置
专用模型:为特定任务的子Agent选择更合适的模型,默认是和主Agent保持一致(如代码任务用qwen3-coder,创意任务用glm-5)。
专用中间件:可以为子Agent单独配置中间件。例如,为
coder子Agent增加ToolCallLimitMiddleware来限制代码执行次数,确保安全。技能继承:DeepAgents的
skills机制。主Agent配置的技能默认不会被其他自定义子Agent继承。可以为子Agent单独配置skills参数。from langchain.agents.middleware import ToolCallLimitMiddleware coder_subagent = { "name": "safe-coder", # ... 其他配置 ... "middleware": [ ToolCallLimitMiddleware(tool_name="execute_python", run_limit=3) # 限制代码执行最多3次 ], "skills": ["skills目录"] }
上下文与配置的传递
自动传递:主Agent调用时的
config(包含context、metadata等)会自动传递给子Agent。这意味着子Agent及其工具可以访问主Agent会话的上下文信息(如用户ID)。访问方式:在工具函数中,可以通过
config参数获取。@tool def personalized_search(query: str, config) -> str: """根据当前用户上下文进行搜索。""" user_id = config.get("context", {}).get("user_id", "anonymous") return f"为用户{user_id}搜索: {query}。结果是..."Agent身份标识:在流式输出或日志中,可以通过消息元数据中的
lc_agent_name区分输出来自哪个Agent。
开发要点
- 描述清晰具体:子Agent的
description是主Agent选择的关键。避免模糊,要说明“在什么情况下使用”。- ✅ 好:“分析财务报表并计算关键比率。当问题涉及收入、利润、负债等数据时使用。”
- ❌ 差:“处理财务问题。”
- 系统提示详细:在子Agent的
system_prompt中明确指导其如何使用工具、如何格式化输出。强调返回“摘要”而非“原始数据”。 - 工具集最小化:遵循最小权限原则。只给子Agent完成任务所必需的工具,这能提高安全性、减少干扰。
- 返回结果简洁:这是实现“上下文隔离”收益的关键。务必在子Agent的指令中要求其提炼、总结,控制输出长度。