多智能体系统 将复杂应用拆分为多个专业化的智能体,这些智能体协同工作以解决问题。
相比于依赖单一智能体处理所有步骤,多智能体架构 允许你将多个小型、专注的智能体组合成一个协调的工作流程。
多智能体系统在以下情况下尤为有用:
- 单一智能体拥有过多工具,难以做出合理选择;
- 上下文或记忆内容过于庞大,单一智能体无法有效跟踪;
- 任务需要专业化分工(例如:规划者、研究员、数学专家)。
多智能体模式
| 模式 | 工作原理 | 控制流 | 示例应用场景 |
|---|
| 工具调用 | 中央智能体将其他智能体作为“工具”调用。“工具”智能体不直接与用户交互——它们仅执行任务并返回结果。 | 集中式:所有路由均通过调用智能体。 | 任务编排、结构化工作流。 |
| 交接控制 | 当前智能体决定将控制权转移给另一个智能体。活跃智能体随之切换,用户可直接与新智能体继续交互。 | 去中心化:智能体可自主决定切换活跃对象。 | 多领域对话、专家接管。 |
模式选择指南
| 问题 | 工具调用 | 交接控制 |
|---|
| 是否需要集中控制工作流? | ✅ 是 | ❌ 否 |
| 是否希望智能体直接与用户交互? | ❌ 否 | ✅ 是 |
| 是否需要专家间进行复杂、类人的对话? | ❌ 有限 | ✅ 强烈推荐 |
你可以混合使用两种模式——使用交接控制实现智能体切换,同时让每个智能体调用子智能体作为工具来处理特定任务。
自定义智能体上下文
多智能体设计的核心是上下文工程——决定每个智能体可见的信息。LangChain 提供细粒度控制,包括:
- 传递给每个智能体的对话或状态的哪些部分;
- 为子智能体定制的专用提示词;
- 包含或排除中间推理过程;
- 按智能体定制输入/输出格式。
系统的质量高度依赖于上下文工程。目标是确保每个智能体(无论是作为工具还是活跃智能体)都能获得完成其任务所需的正确数据。
工具调用
在工具调用模式中,一个智能体(“控制器”)将其他智能体视为工具,在需要时调用。控制器负责编排,工具智能体执行特定任务并返回结果。
流程:
- 控制器接收输入,决定调用哪个工具(子智能体)。
- 工具智能体根据控制器的指令执行任务。
- 工具智能体将结果返回给控制器。
- 控制器决定下一步操作或结束流程。
作为工具使用的智能体通常不应与用户直接对话。
它们的作用是执行任务并将结果返回给控制器智能体。
如果需要子智能体能与用户对话,请改用交接控制模式。
实现方式
以下是最小示例,主智能体通过工具定义访问单个子智能体:
from langchain.tools import tool
from langchain.agents import create_agent
subagent1 = create_agent(..)
@tool(
name="subagent1_name",
description="subagent1_description"
)
def call_subagent1(query: str):
result = subagent1.invoke({
"messages": [{"role": "user", "content": query}]
})
return result["messages"].text
agent = create_agent(..., tools=[call_subagent1])
在此模式中:
- 主智能体在判断任务匹配子智能体描述时,调用
call_subagent1。
- 子智能体独立运行并返回结果。
- 主智能体接收结果并继续编排。
可定制点
你可以在多个环节控制主智能体与子智能体之间的上下文传递:
- 子智能体名称 (
"subagent1_name"):主智能体引用子智能体的方式。名称影响提示词,需谨慎选择。
- 子智能体描述 (
"subagent1_description"):主智能体对子智能体的“认知”。直接影响主智能体何时调用它。
- 子智能体输入:可自定义输入,以更好引导子智能体理解任务。上例中我们直接传递智能体生成的
query。
- 子智能体输出:这是返回给主智能体的响应。可调整返回内容,以控制主智能体如何解读结果。上例中我们返回最终消息文本,但也可返回额外状态或元数据。
控制子智能体输入
控制主智能体传递给子智能体输入的两个主要方法:
- 修改提示词 – 调整主智能体提示词或工具元数据(即子智能体名称和描述),以更好引导调用时机和方式。
- 上下文注入 – 通过调整工具调用从智能体状态中提取信息,添加静态提示词难以捕捉的输入(如完整对话历史、先前结果、任务元数据)。
from typing import Annotated
from langchain.agents import AgentState
from langchain.agents.tool_node import InjectedState
@tool(
name="subagent1_name",
description="subagent1_description"
)
def call_subagent1(query: str, state: Annotated[CustomState, InjectedState]):
# 应用任何必要的逻辑,将消息转换为合适的输入
subagent_input = some_logic(query, state.messages)
result = subagent1.invoke({
"messages": subagent_input,
# 如有需要,也可传递其他状态键。
# 请确保在主智能体和子智能体的状态模式中均定义这些键。
"example_state_key": state.example_state_key
})
return result["messages"][-1].text
控制子智能体输出
塑造主智能体从子智能体接收内容的两种常见策略:
- 修改提示词 – 优化子智能体提示词,明确指定应返回的内容。
- 适用于输出不完整、过于冗长或缺少关键细节的情况。
- 常见失败模式:子智能体执行工具调用或推理,但未在最终消息中包含结果。需提醒子智能体:控制器(和用户)仅看到最终输出,因此所有相关信息必须包含在内。
- 自定义输出格式 – 在将子智能体响应交还主智能体前,通过代码调整或丰富响应。
- 示例:除最终文本外,向主智能体传递特定状态键。
- 这需要将结果包装在
Command(或等效结构)中,以便将自定义状态与子智能体响应合并。
from typing import Annotated
from langchain.agents import AgentState
from langchain_core.tools import InjectedToolCallId
from langgraph.types import Command
@tool(
name="subagent1_name",
description="subagent1_description"
)
# 需要将 `tool_call_id` 传递给子智能体,以便其使用该 ID 响应工具调用结果
def call_subagent1(
query: str,
tool_call_id: Annotated[str, InjectedToolCallId],
# 若要返回的不仅是最终工具调用,需返回 `Command` 对象
) -> Command:
result = subagent1.invoke({
"messages": [{"role": "user", "content": query}]
})
return Command(update={
# 这是我们传回的示例状态键
"example_state_key": result["example_state_key"],
"messages": [
ToolMessage(
# 需包含工具调用 ID,以便与正确的工具调用匹配
result["messages"][-1].text, tool_call_id=tool_call_id
)
]
})
交接控制
在交接控制模式中,智能体可直接将控制权传递给彼此。“活跃”智能体发生切换,用户与当前拥有控制权的智能体交互。
流程:
- 当前智能体判断需要另一智能体协助。
- 它将控制权(及状态)传递给下一智能体。
- 新智能体直接与用户交互,直至决定再次交接或完成任务。
实现方式(即将推出)