13.3.1 为何需要多智能体
在第七章中,我们学习了如何使用 SimpleAgent 来构建智能体。SimpleAgent 的设计理念是简单直接:每次调用run()方法时,Agent 会分析用户的问题,决定是否需要调用工具,然后返回结果。这种设计在处理简单任务时非常有效,但当面对旅行规划这样的任务时,就会遇到一些问题。
如果用单个 Agent 来完成旅行规划。这个 Agent 需要做什么呢?首先,它要搜索景点信息,这需要调用高德地图的 POI 搜索工具。然后,它要查询天气信息,这需要调用天气查询工具。接着,它要搜索酒店信息,这又需要调用 POI 搜索工具。最后,它要把所有这些信息整合起来,生成一个完整的旅行计划。
这听起来很简单,但实际操作时会遇到第一个问题:工具调用的限制。SimpleAgent 每次run()调用只能执行一个工具。这意味着我们需要多次调用run()方法,每次调用处理一个任务。但这样做会带来一个新问题:如何在多次调用之间传递信息?第一次调用得到的景点信息,如何传递给第二次调用?我们需要手动管理这些中间结果,代码会变得很复杂。
当然,我们可以使用 ReactAgent 来解决这个问题。ReactAgent 可以在一次调用中执行多个工具,它会自动进行多轮思考和行动。但这又带来了新的问题:时间成本。ReactAgent 的每一轮思考都需要调用 LLM,如果需要调用三个工具,就需要至少三轮思考,这意味着至少三次 LLM 调用。而且这些调用是串行的,必须等前一个完成才能开始下一个,总时间会很长。
第二个问题是提示词的复杂度。如果我们要让一个 Agent 完成所有任务,就需要在提示词中详细描述每个任务的执行逻辑。比如:
COMPLEX_PROMPT = """你是旅行规划助手。你需要:
1. 使用maps_text_search搜索景点,关键词根据用户偏好确定
2. 使用maps_weather查询天气,获取未来几天的天气预报
3. 使用maps_text_search搜索酒店,类型根据用户需求确定
4. 整合所有信息生成旅行计划,包括每天的景点、餐饮、住宿安排
注意:必须按顺序执行,每个工具只能调用一次,输出必须是JSON格式...
"""
这样的提示词有几个问题。首先是难以维护。如果我们想修改景点搜索的逻辑(比如增加评分筛选),就需要修改整个提示词,很容易影响到其他部分。其次是容易出错。LLM 需要同时理解多个任务的要求,很容易搞混不同任务的格式和参数。最后是难以调试。当生成的计划不符合预期时,我们很难知道是哪个环节出了问题,是景点搜索不准确,还是天气查询失败,还是整合逻辑有问题?
面对这些问题,一个自然的想法是:能不能把复杂的任务分解成多个简单的任务,让不同的 Agent 各司其职?这就是多 Agent 协作的核心思想。
想象一下现实世界中的旅行社。当你去旅行社咨询旅行计划时,不会只有一个人为你服务。通常会有专门的景点顾问,负责推荐景点;有酒店顾问,负责预订酒店;还有行程规划师,负责把所有信息整合成完整的行程。每个人都专注于自己擅长的领域,最后由行程规划师把所有信息汇总。这种分工协作的方式,比让一个人做所有事情要高效得多。
13.3.2 Agent 角色设计
基于任务分解原则,我们设计了四个专门的 Agent,如图 13.6 所示:
图 13.6 多智能体协作流程
-
AttractionSearchAgent(景点搜索专家)专注于搜索景点信息。它只需要理解用户的偏好(比如"历史文化"、"自然风光"),然后调用高德地图的 POI 搜索工具,返回相关的景点列表。它的提示词很简单,只需要说明如何根据偏好选择关键词,如何调用工具。
-
WeatherQueryAgent(天气查询专家)专注于查询天气信息。它只需要知道城市名称,然后调用天气查询工具,返回未来几天的天气预报。它的任务非常明确,几乎不会出错。
-
HotelAgent(酒店推荐专家)专注于搜索酒店信息。它需要理解用户的住宿需求(比如"经济型"、"豪华型"),然后调用 POI 搜索工具,返回符合要求的酒店列表。
-
PlannerAgent(行程规划专家)负责整合所有信息。它接收前三个 Agent 的输出,加上用户的原始需求(日期、预算等),然后生成完整的旅行计划。它不需要调用任何外部工具,只需要专注于信息的整合和行程的安排。
现在让我们详细设计每个 Agent 的角色和提示词。设计提示词时,我们需要考虑几个关键问题:这个 Agent 需要什么输入?它应该产生什么输出?它需要调用什么工具?它可能遇到什么问题?
AttractionSearchAgent的任务是根据用户偏好搜索景点。它的输入是城市名称和用户偏好(比如"历史文化"、"自然风光")。它需要调用amap_maps_text_search工具,参数是关键词和城市。它的输出是景点列表,包含名称、地址、评分等信息。
ATTRACTION_AGENT_PROMPT = """你是景点搜索专家。
**工具调用格式:**
`[TOOL_CALL:amap_maps_text_search:keywords=景点,city=城市名]`
**示例:**
- `[TOOL_CALL:amap_maps_text_search:keywords=景点,city=北京]`
- `[TOOL_CALL:amap_maps_text_search:keywords=博物馆,city=上海]`
**重要:**
- 必须使用工具搜索,不要编造信息
- 根据用户偏好({preferences})搜索{city}的景点
"""
这个提示词很简洁,但包含了所有必要的信息。它明确说明了工具调用的格式,提供了具体的示例,还强调了两个重要原则:必须使用工具(不能编造),要根据用户偏好搜索。
WeatherQueryAgent的任务更简单,只需要查询天气。它的输入是城市名称,输出是天气信息。
WEATHER_AGENT_PROMPT = """你是天气查询专家。
**工具调用格式:**
`[TOOL_CALL:amap_maps_weather:city=城市名]`
请查询{city}的天气信息。
"""
HotelAgent的任务是搜索酒店。它的输入是城市名称和住宿类型,输出是酒店列表。
HOTEL_AGENT_PROMPT = """你是酒店推荐专家。
**工具调用格式:**
`[TOOL_CALL:amap_maps_text_search:keywords=酒店,city=城市名]`
请搜索{city}的{accommodation}酒店。
"""
PlannerAgent是最复杂的,因为它需要整合所有信息。它的输入是用户需求和前三个 Agent 的输出,输出是完整的旅行计划(JSON 格式)。
PLANNER_AGENT_PROMPT = """你是行程规划专家。
**输出格式:**
严格按照以下JSON格式返回:
{
"city": "城市名称",
"start_date": "YYYY-MM-DD",
"end_date": "YYYY-MM-DD",
"days": [...],
"weather_info": [...],
"overall_suggestions": "总体建议",
"budget": {...}
}
**规划要求:**
1. weather_info必须包含每天的天气
2. 温度为纯数字(不带°C)
3. 每天安排2-3个景点
4. 考虑景点距离和游览时间
5. 包含早中晚三餐
6. 提供实用建议
7. 包含预算信息
"""
13.3.3 Agent 协作流程
现在让我们看看这四个 Agent 如何协作完成旅行规划任务。整个流程可以分为五个步骤:
class TripPlannerAgent:
def __init__(self):
self.attraction_agent = SimpleAgent(name="景点搜索"prompt=ATTRACTION_PROMPT)
self.weather_agent = SimpleAgent(name="天气查询", prompt=WEATHER_PROMPT)
self.hotel_agent = SimpleAgent(name="酒店推荐", prompt=HOTEL_PROMPT)
self.planner_agent = SimpleAgent(name="行程规划", prompt=PLANNER_PROMPT)
def plan_trip(self, request: TripPlanRequest) -> TripPlan:
# 步骤1: 景点搜索
attraction_response = self.attraction_agent.run(
f"请搜索{request.city}的{request.preferences}景点"
)
# 步骤2: 天气查询
weather_response = self.weather_agent.run(
f"请查询{request.city}的天气"
)
# 步骤3: 酒店推荐
hotel_response = self.hotel_agent.run(
f"请搜索{request.city}的{request.accommodation}酒店"
)
# 步骤4: 整合生成计划
planner_query = self._build_planner_query(
request, attraction_response, weather_response, hotel_response
)
planner_response = self.planner_agent.run(planner_query)
# 步骤5: 解析JSON
trip_plan = self._parse_trip_plan(planner_response)
return trip_plan
这个流程顺序执行四个步骤,每个步骤的输出作为下一个步骤的输入。注意我们使用了TripPlanRequest和TripPlan这两个 Pydantic 模型,这是在 13.2 节中定义的。
13.3.4 查询构建
PlannerAgent 需要整合所有信息,这个查询需要包含所有必要的信息,而且要组织得清晰有序,让 LLM 能够准确理解。
def _build_planner_query(
self,
request: TripPlanRequest,
attraction_response: str,
weather_response: str,
hotel_response: str
) -> str:
"""构建规划Agent的查询"""
return f"""
请根据以下信息生成{request.city}的{request.days}日旅行计划:
**用户需求:**
- 目的地: {request.city}
- 日期: {request.start_date} 至 {request.end_date}
- 天数: {request.days}天
- 偏好: {request.preferences}
- 预算: {request.budget}
- 交通方式: {request.transportation}
- 住宿类型: {request.accommodation}
**景点信息:**
{attraction_response}
**天气信息:**
{weather_response}
**酒店信息:**
{hotel_response}
请生成详细的旅行计划,包括每天的景点安排、餐饮推荐、住宿信息和预算明细。
"""
通过这种多 Agent 协作的设计,我们把一个复杂的旅行规划任务分解成了四个简单的子任务。每个 Agent 都专注于自己擅长的领域,也为未来的功能扩展(比如添加餐厅推荐 Agent、交通规划 Agent)打下了良好的基础。