note

  • 基于multi-agent的智能旅行助手:依次调用搜点、天气查询、酒店搜索的工具,最后planner_agent将大家的内容进行汇总生成旅游攻略
  • 数据流转过程如下:用户在前端填写表单 → 后端验证数据 → 调用智能体系统 → 智能体依次调用景点搜索、天气查询、酒店推荐、行程规划 Agent → 每个 Agent 通过 MCP 协议调用外部 API → 整合结果返回前端 → 前端渲染展示。
  • 不直接在 Agent 中调用高德地图的 HTTP API,而是使用MCP的原因:使Agent保持自主决策能力,直接使用http api则参数传递复杂(需要在prompt中写明每个参数含义)、api解析response的代码定制化、工具管理混乱等问题发生。

一、智能旅行助手需求

需求:个性化的旅行规划制定,需要在网上搜索景点信息,对比不同的攻略,查看天气预报,预订酒店,计算预算,规划路线。

这个智能旅行助手项目的功能如下:
(1)智能行程规划:用户输入目的地、日期、偏好等信息,系统自动生成包含景点、餐饮、酒店的完整行程计划。
(2)地图可视化:在地图上标注景点位置、绘制游览路线,让行程一目了然。
(3)预算计算:自动计算门票、酒店、餐饮、交通费用,显示预算明细。
(4)行程编辑:支持添加、删除、调整景点,实时更新地图。
(5)导出功能:支持导出为 PDF 或图片,方便保存和分享。

二、技术架构

项目代码的整体调用流程如下:
在这里插入图片描述
后端:API 路由、数据验证、业务编排
前端: 用户输入表单、地图可视化、行程显示
Agent:任务分解、工具调用、结果集成
外部接口:如MCP servers、REST APIs,数据提供(地图、天气、图片、LLM推理等)

1、前后端分离

采用前后端分离架构:
(1)前端层 (Vue3+TypeScript):负责用户交互和数据展示,包括表单输入、结果展示、地图可视化。
(2)后端层 (FastAPI):负责 API 路由、数据验证、业务逻辑。
(3)智能体层 (HelloAgents):负责任务分解、工具调用、结果整合。包含 4 个专门的 Agent。
(4)外部服务层:提供数据和能力,包括高德地图 API、Unsplash API、LLM API。

在这里插入图片描述

数据流转过程如下:用户在前端填写表单 → 后端验证数据 → 调用智能体系统 → 智能体依次调用景点搜索、天气查询、酒店推荐、行程规划 Agent → 每个 Agent 通过 MCP 协议调用外部 API → 整合结果返回前端 → 前端渲染展示。

2、数据模型

(1)数据转换过程

当用户在浏览器中点击"开始规划"按钮时,会发生什么:

  • 用户在前端填写的表单数据(目的地、日期、预算等)需要通过 HTTP 请求发送到后端服务器。
  • 后端接收到数据后,会调用智能体系统进行处理。
  • 智能体又会调用高德地图 API、Unsplash API 等外部服务获取数据。这些外部 API 返回的数据格式各不相同,有的用lng,有的用lon,有的用longitude
  • 最后,后端需要将处理好的数据返回给前端,前端再渲染成用户看到的页面。

在这个过程中,数据经历了多次转换:前端表单 → HTTP 请求 → 后端 Python 对象 → 外部 API 响应 → 后端 Python 对象 → HTTP 响应 → 前端 TypeScript 对象 → 页面展示。如果没有统一的数据格式,每一步转换都可能出错。

(2)Pydantic数据验证

Pydantic 提供了一个解决方案,是 Python 的数据验证库,可以用类来定义数据结构,并自动处理验证、转换和序列化。Pydantic 的基础是BaseModel类,所有的数据模型都需要继承这个类。每个字段都可以指定类型,Pydantic 会自动进行类型检查和转换。

以地理位置和景点信息举例:

class Location(BaseModel):
    """地理位置"""
    longitude: float = Field(..., description="经度")
    latitude: float = Field(..., description="纬度")


class Attraction(BaseModel):
    """景点信息"""
    name: str = Field(..., description="景点名称")
    address: str = Field(..., description="地址")
    location: Location = Field(..., description="经纬度坐标")
    visit_duration: int = Field(..., description="建议游览时间(分钟)")
    description: str = Field(..., description="景点描述")
    category: Optional[str] = Field(default="景点", description="景点类别")
    rating: Optional[float] = Field(default=None, description="评分")
    photos: Optional[List[str]] = Field(default_factory=list, description="景点图片URL列表")
    poi_id: Optional[str] = Field(default="", description="POI ID")
    image_url: Optional[str] = Field(default=None, description="图片URL")
    ticket_price: int = Field(default=0, description="门票价格(元)")

注意事项:

  • 字段定义使用Field函数,它可以指定默认值、描述、验证规则等。
  • ...表示这个字段是必填的,如果创建对象时没有提供这个字段,Pydantic 会抛出异常。我们也可以使用Optional来表示可选字段,或者直接提供默认值。
  • @field_validator:自定义验证器,用于处理特殊格式(如温度)

3、多智能体协作设计

AttractionSearchAgent(景点搜索专家)专注于搜索景点信息。它只需要理解用户的偏好(比如"历史文化"、“自然风光”),然后调用高德地图的 POI 搜索工具,返回相关的景点列表。它的提示词很简单,只需要说明如何根据偏好选择关键词,如何调用工具。

WeatherQueryAgent(天气查询专家)专注于查询天气信息。它只需要知道城市名称,然后调用天气查询工具,返回未来几天的天气预报。它的任务非常明确,几乎不会出错。

HotelAgent(酒店推荐专家)专注于搜索酒店信息。它需要理解用户的住宿需求(比如"经济型"、“豪华型”),然后调用 POI 搜索工具,返回符合要求的酒店列表。

PlannerAgent(行程规划专家)负责整合所有信息。它接收前三个 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

4、MCP工具集成

1、架构流程:
在这里插入图片描述
对应的工具分类:
在这里插入图片描述

2、MCP使用进程间通信:
当我们创建MCPTool对象时,它会在后台启动 MCP 服务器进程,并通过标准输入输出(stdin/stdout)与服务器通信。这是 MCP 协议的一个特点:使用进程间通信而不是 HTTP,这样更高效,也更容易管理

3、MCP工具调用举例:假设用户想搜索北京的景点,AttractionSearchAgent 接收到查询"请搜索北京的历史文化景点"。相关流程如下图。

  • Agent 分析这个查询,决定调用amap_maps_text_search工具,参数是keywords=景点,city=北京。
  • Agent 生成工具调用标记:[TOOL_CALL:amap_maps_text_search:keywords=景点,city=北京]。HelloAgents 框架解析这个标记,提取工具名称和参数,然后调用对应的 Tool 对象
  • Tool 对象是MCPTool自动创建的,它会把调用请求发送给 MCP 服务器。具体来说,它会构造一个 JSON-RPC 格式的消息,通过 stdin 发送给服务器进程
  • MCP 服务器接收到这个消息,解析参数,然后调用高德地图的 HTTP API。它会构造 HTTP 请求,添加 API 密钥,发送请求,接收响应
  • 高德地图 API 返回 JSON 格式的数据,包含景点列表、地址、坐标等信息。MCP 服务器解析这些数据,提取关键字段,然后构造响应消息,通过 stdout返回给MCPTool
  • MCPTool接收到响应,提取文本内容,返回给 Agent。Agent 把这个结果作为工具调用的输出,继续生成最终的回复

在这里插入图片描述

4、问题:为什么不直接在 Agent 中调用高德地图的 HTTP API?
原因:

  • HelloAgents 框架中,Agent 通过识别提示词中的工具调用标记(比如[TOOL_CALL:tool_name:arg1=value1])来调用工具。如果我们直接在代码中调用 API,Agent 就失去了自主决策的能力,变成了一个简单的函数调用
  • 参数传递复杂:高德地图的 API 有很多参数,比如 POI 搜索有keywords、city、types、offset、page等十几个参数。如果我们要让 Agent 能够灵活使用这些参数,就需要在提示词中详细说明每个参数的含义和格式,这会让提示词变得非常复杂
  • 响应解析困难:高德地图 API 返回的是 JSON 格式的数据,结构比较复杂。我们需要编写代码来解析这些数据,提取我们需要的字段。如果 API 的响应格式发生变化,我们就需要修改解析代码。
  • 工具管理混乱:高德地图提供了十几个不同的 API(POI 搜索、天气查询、路线规划等),如果我们为每个 API 都编写一个函数,然后手动注册到 Agent 的工具列表中,代码会变得很冗长

5、前端开发

HTTP 请求调用后端 API,获取数据后渲染页面。这种架构有几个明显的优势:前端和后端可以独立开发、独立部署、独立测试;前端可以是 Web 应用、移动应用或桌面应用,都使用同一套后端 API;前端可以使用现代的框架和工具链,提供更好的用户体验。

在我们的智能旅行助手项目中,后端是用 Python 和 FastAPI 实现的,提供了一个核心 API 接口POST /api/trip/plan,接收旅行需求,返回旅行计划。前端是用 Vue 3 和 TypeScript 实现的,是一个单页应用(SPA),用户在浏览器中填写表单,点击"开始规划"按钮,前端发送 HTTP 请求到后端,等待响应,然后渲染结果页面。整个过程中,页面不会刷新,用户体验很流畅。

在这里插入图片描述

启动前端和后端服务后,在前端界面中输入景点和响应需求选项,可以在后端日志中看到服务后台跑起来了,可以看到依次调用搜点、天气查询、酒店搜索的工具,最后planner_agent将大家的内容进行汇总生成旅游攻略:
在这里插入图片描述

Reference

[1] https://datawhalechina.github.io/hello-agents
[2] [问题/Issue] 章节13:前端代码没有引用.env文件的VITE_AMAP_WEB_KEY,导致最终展示无地图展示,只有景点标识 #168:https://github.com/datawhalechina/hello-agents/issues/168
[3] HelloAgents 旅行规划助手:从零到一的完整实战教程

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐