SubAgent 子智能体

image-20260629151340795

多Agent的必要性

问题:复杂任务通常需要多个步骤和不同领域的专业知识。让一个Agent“全能”地处理所有事情会导致:

  1. 上下文臃肿:Agent在一次对话中可能调用很多工具,产生大量中间结果,这些都会挤占有限的上下文窗口,影响后续决策。
  2. 缺乏专业性:一个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的会话流。

同步 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(包含contextmetadata等)会自动传递给子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。

开发要点

  1. 描述清晰具体:子Agent的description是主Agent选择的关键。避免模糊,要说明“在什么情况下使用”。
    1. ✅ 好:“分析财务报表并计算关键比率。当问题涉及收入、利润、负债等数据时使用。”
    2. ❌ 差:“处理财务问题。”
  2. 系统提示详细:在子Agent的system_prompt中明确指导其如何使用工具、如何格式化输出。强调返回“摘要”而非“原始数据”。
  3. 工具集最小化:遵循最小权限原则。只给子Agent完成任务所必需的工具,这能提高安全性、减少干扰。
  4. 返回结果简洁:这是实现“上下文隔离”收益的关键。务必在子Agent的指令中要求其提炼、总结,控制输出长度。
--- 本文结束 The End ---