HelloAgents 实战:从零构建基于多智能体与 MCP 的智能旅行助手 HelloAgents 实战从零构建基于多智能体与 MCP 的智能旅行助手【免费下载链接】hello-agents 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents本篇技术指南以《从零开始构建智能体》一书第十三章为核心骨架结合仓库中 helloagents-trip-planner 完整项目源码系统讲解如何基于 HelloAgents 框架搭建一个可运行的智能旅行助手涵盖前后端分离架构、Pydantic 数据模型、四 Agent 协作范式、高德地图 MCP 工具集成以及预算计算、行程编辑、导出等完整功能闭环。读完本文你将掌握多智能体任务分解 MCP 外部工具接入 全栈 Web 呈现这一可复用到任意领域应用购物助手、学习助手等的实战方案。项目概览为什么需要一个智能旅行助手规划一次旅行往往要在旅游网站、天气网站、酒店预订平台之间反复切换手动整合分散的信息通用攻略不考虑个人偏好、预算与出行时间想调整行程时景点顺序、时间安排、预算又相互关联牵一发而动全身。这些痛点催生了智能旅行助手用户只需输入目的地、日期、偏好、预算系统即可自动生成包含景点、餐饮、酒店、交通的完整行程并且支持地图可视化、预算明细、行程编辑与导出。本项目是对第一章简易智能体的全面升级从单循环演示进化为包含以下五大核心能力的完整应用智能行程规划根据目的地、日期、偏好自动生成含景点、餐饮、酒店的完整计划地图可视化在高德地图上标注景点、绘制路线行程一目了然预算计算自动汇总门票、酒店、餐饮、交通费用并显示明细行程编辑支持添加、删除、调整景点地图实时更新导出功能一键导出为 PDF 或图片方便保存分享。13.1 架构设计前后端分离的四层结构系统采用经典的前后端分离架构划分为四个层次如图 13-1 所示前端层Vue3 TypeScript负责用户交互与数据展示包括表单输入、结果展示、地图可视化后端层FastAPI负责 API 路由、数据验证与业务逻辑智能体层HelloAgents负责任务分解、工具调用与结果整合包含 4 个专门的 Agent外部服务层提供数据与能力包括高德地图 API、Unsplash API、LLM API。数据流转链路为用户前端填表 → 后端验证数据 → 调用智能体系统 → 智能体依次调用景点搜索、天气查询、酒店推荐、行程规划 Agent → 各 Agent 通过 MCP 协议调用外部 API → 整合结果返回前端 → 前端渲染展示。仓库中实际目录结构与文档描述一致见 backend/app 与 frontend/srchelloagents-trip-planner/ ├── backend/ # 后端代码 │ ├── app/ │ │ ├── agents/ # 智能体实现trip_planner_agent.py │ │ ├── api/ # API 路由main.py、routes/ │ │ ├── models/ # 数据模型schemas.py │ │ ├── services/ # 服务层llm/unsplash/amap_service.py │ │ └── config.py # 配置管理 │ └── requirements.txt # Python 依赖 └── frontend/ # 前端代码 ├── src/ │ ├── views/ # 页面组件Home.vue、Result.vue │ ├── services/ # API 服务api.ts │ ├── types/ # 类型定义index.ts │ └── router/ # 路由配置 └── package.json # npm 依赖5 分钟快速运行环境要求Python 3.10、Node.js 16.0、npm 8.0。准备 API 密钥LLM APIOpenAI、DeepSeek 等、高德地图 Web 服务 Key访问高德开放平台注册创建应用、Unsplash Access Key。全部放入.env文件。后端启动backend/requirements.txt 中声明了hello-agents[protocols]0.2.4,0.2.9、fastapi0.115.0、pydantic2.0.0等依赖cd helloagents-trip-planner/backend pip install -r requirements.txt cp .env.example .env # 编辑 .env 填入 API 密钥 uvicorn app.api.main:app --reload # 或 python run.py启动后访问 http://localhost:8000/docs 可查看 Swagger API 文档。前端启动cd helloagents-trip-planner/frontend npm install npm run dev访问 http://localhost:5173 即可使用。体验流程为首页表单填写目的地城市、旅行日期、偏好、预算、交通及住宿类型 → 点击开始规划系统显示加载进度条并生成结果页 → 结果页展示行程概览、预算明细、景点地图、每日行程详情与天气信息 → 可点击编辑行程自由调整顺序或删除景点通过导出行程菜单保存为图片或 PDF。说明文档早期示例中部分 MCP 启动命令使用npx而仓库实际实现采用uvx见 trip_planner_agent.py。两者设计理念一致仅生态不同npx面向 JavaScript/Node.jsnpm 包uvx面向 PythonPyPI 包无优劣之分按需选择即可。13.2 数据模型设计从字典到 PydanticWeb 应用中的数据流转问题用户在浏览器点击开始规划后表单数据经 HTTP 请求到达后端后端调用智能体智能体再调用高德、Unsplash 等外部服务。外部 API 返回格式各异——有的用lng有的用lon有的用longitude。数据经历多次转换前端表单 → HTTP 请求 → Python 对象 → 外部 API 响应 → Python 对象 → HTTP 响应 → TypeScript 对象 → 页面展示。没有统一数据格式每一步都可能出错。第一章原型用 Python 字典表示景点存在三大问题字段名不统一高德返回116.397128,39.916527字符串需手动分割Unsplash 可能用longitude/latitude类型不安全price误写为字符串60不会立即报错直到计算预算时才暴露维护性差新增rating字段需在多处修改遗漏即导致数据不一致。Pydantic 核心概念Pydantic 用类定义数据结构并自动处理验证、转换与序列化。其核心包括BaseModel所有模型的基类自动进行类型检查与转换Field函数指定默认值、描述、验证规则...表示必填可用Optional或默认值表示可选嵌套模型与列表一个模型可作为另一模型的字段类型构建复杂结构自定义验证器field_validator装饰器在创建对象前执行自定义转换。仓库的 schemas.py 中完整实现了这一设计且比文档示例更为健壮。自底向上的模型设计位置信息Location任何景点、酒店、餐厅都需要经纬度加上范围验证ge-180,le180/ge-90,le90class Location(BaseModel): 位置信息(经纬度坐标) longitude: float Field(..., description经度, ge-180, le180) latitude: float Field(..., description纬度, ge-90, le90)景点信息Attraction包含名称、地址、位置、游览时间、描述、类别、评分、图片与门票价格仓库版本还补充了photos图片 URL 列表与poi_idPOI ID字段便于前端直接渲染多图及关联高德 POIclass 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(defaultNone, description评分) photos: Optional[List[str]] Field(default_factorylist, description景点图片URL列表) poi_id: Optional[str] Field(default, descriptionPOI ID) image_url: Optional[str] Field(defaultNone, description图片URL) ticket_price: int Field(default0, description门票价格(元))餐饮与酒店信息Meal/Hotel结构相似均含名称、地址、位置与费用。Hotel增加了price_range价格范围、distance距景点距离、estimated_cost元/晚等字段为预算计算提供依据。预算信息Budget不含位置而是四项费用汇总景点门票、酒店、餐饮、交通与total总费用。单日行程DayPlan组合上述基础模型含日期、第几天、描述、交通方式、住宿安排、可选酒店、景点列表与餐饮列表。注意List[Attraction]搭配default_factorylist提供可变默认值避免共享可变对象的陷阱。天气信息WeatherInfo因高德返回温度格式不规范如16°C使用自定义验证器统一处理——仓库实现中用Union[int, str]兜底类型并做了容错解析失败返回 0field_validator(day_temp, night_temp, modebefore) classmethod def parse_temperature(cls, v): 解析温度,移除°C等单位 if isinstance(v, str): v v.replace(°C, ).replace(℃, ).replace(°, ).strip() try: return int(v) except ValueError: return 0 return v完整旅行计划TripPlan最顶层模型包含城市、起止日期、每日行程列表、天气信息列表、总体建议与可选预算class TripPlan(BaseModel): 旅行计划 city: str Field(..., description目的地城市) start_date: str Field(..., description开始日期) end_date: str Field(..., description结束日期) days: List[DayPlan] Field(..., description每日行程) weather_info: List[WeatherInfo] Field(default[], description天气信息) overall_suggestions: str Field(..., description总体建议) budget: Optional[Budget] Field(defaultNone, description预算信息)从Location→Attraction/Meal/Hotel→DayPlan→TripPlan形成清晰的层次结构。请求模型TripRequest同样使用 Pydantic 定义包含travel_daysge1,le30限制天数范围、preferences偏好标签列表、free_text_input额外要求等字段并通过json_schema_extra提供 Swagger 示例。模型在 Web 应用中的落地在 FastAPI 中Pydantic 模型直接用作请求与响应类型。仓库 trip.py 实际采用TripPlanResponse包装结构success/message/data相比文档示例增加了一层统一的响应协议router.post(/plan, response_modelTripPlanResponse, summary生成旅行计划) async def plan_trip(request: TripRequest): agent get_trip_planner_agent() trip_plan agent.plan_trip(request) return TripPlanResponse(successTrue, message旅行计划生成成功, datatrip_plan)FastAPI 自动完成请求数据验证TripRequest、响应数据验证TripPlanResponse、OpenAPI 文档生成。数据格式错误时自动返回 400 并指明错误位置。后端还提供了GET /trip/health健康检查接口与GET /health根健康检查便于运维监控见 api/main.py。前端定义对应的 TypeScript 类型frontend/src/types/index.ts与后端 Pydantic 模型字段一一对应可选字段用?标记实现前后端统一数据格式。13.3 多智能体协作设计为什么需要多智能体单 Agent 完成旅行规划会遇到三类问题工具调用限制SimpleAgent 每次run()只执行一个工具多次调用需手动传递中间结果代码复杂换用 ReactAgent 虽可一轮多工具但每轮思考都调用 LLM串行执行导致时间成本高提示词复杂度把所有任务塞进一个提示词会难以维护、容易出错LLM 混淆不同任务格式与参数、难以调试无法定位是搜索不准、查询失败还是整合逻辑问题并行能力缺失串行推理无法并发获取景点、天气、酒店数据。借鉴现实旅行社的分工模式景点顾问、酒店顾问、行程规划师各司其职将复杂任务分解为多个简单子任务让不同 Agent 各司其职是多 Agent 协作的核心思想。协作流程如图 13-6 所示四个 Agent 角色设计AttractionSearchAgent景点搜索专家输入城市与偏好历史文化自然风光调用高德 POI 搜索工具返回景点列表WeatherQueryAgent天气查询专家输入城市名调用天气查询工具返回未来几天预报任务明确几乎不出错HotelAgent酒店推荐专家理解住宿需求经济型豪华型调用 POI 搜索工具返回酒店列表PlannerAgent行程规划专家整合前三者输出与用户原始需求生成完整旅行计划自身不调用外部工具。每个提示词都遵循角色定位 工具调用格式 示例 强制约束的结构。仓库 trip_planner_agent.py 中的提示词比文档版本更强调禁止编造例如景点搜索ATTRACTION_AGENT_PROMPT 你是景点搜索专家。你的任务是根据城市和用户偏好搜索合适的景点。 **重要提示:** 你必须使用工具来搜索景点!不要自己编造景点信息! **工具调用格式:** 使用maps_text_search工具时,必须严格按照以下格式: [TOOL_CALL:amap_maps_text_search:keywords景点关键词,city城市名] **示例:** 用户: 搜索北京的历史文化景点 你的回复: [TOOL_CALL:amap_maps_text_search:keywords历史文化,city北京] **注意:** 1. 必须使用工具,不要直接回答 2. 格式必须完全正确,包括方括号和冒号 3. 参数用逗号分隔 PlannerAgent 的提示词内嵌完整 JSON Schema 示例含hotel、attractions、meals、weather_info、budget的结构化模板并明确要求weather_info必须包含每天天气、温度为纯数字、每天 2-3 个景点、考虑距离与游览时间、每天必须含早中晚三餐、必须包含预算信息。协作流程与查询构建仓库中MultiAgentTripPlanner.plan_trip()trip_planner_agent.py按五步执行# 步骤1: 景点搜索Agent搜索景点 attraction_response self.attraction_agent.run(self._build_attraction_query(request)) # 步骤2: 天气查询Agent查询天气 weather_response self.weather_agent.run(f请查询{request.city}的天气信息) # 步骤3: 酒店推荐Agent搜索酒店 hotel_response self.hotel_agent.run(f请搜索{request.city}的{request.accommodation}酒店) # 步骤4: 行程规划Agent整合信息生成计划 planner_query self._build_planner_query(request, attraction_response, weather_response, hotel_response) planner_response self.planner_agent.run(planner_query) # 步骤5: 解析JSON生成TripPlan trip_plan self._parse_response(planner_response, request)_build_planner_query将用户需求城市、日期、天数、交通、住宿、偏好、额外要求与三个子 Agent 的搜索结果组织为结构化上下文。实现上比文档更进一步景点查询直接拼入[TOOL_CALL:...]标记_build_attraction_query减少 LLM 生成工具调用格式的出错概率。两个值得借鉴的健壮性设计_parse_response多级 JSON 提取依次尝试json代码块、普通代码块、直接查找首尾花括号三种策略解析 LLM 输出_create_fallback_plan兜底方案当 Agent 调用或解析失败时自动生成一个含占位景点的基础行程保证 API 永远返回可用结构而非崩溃返回TripPlan而非异常。同时get_trip_planner_agent()采用单例模式管理全局多智能体实例避免每个请求重复初始化 MCP 服务器与 Agentllm_service.py 中get_llm()同理这是服务端部署的关键实践。13.4 MCP 工具集成详解为什么不直接调用 API直接调用高德 HTTP APIrequests.get(https://restapi.amap.com/v3/place/text, ...)看似简单实际存在四个问题Agent 无法自主调用HelloAgents 通过识别[TOOL_CALL:tool_name:arg1value1]标记调用工具直接写死在代码里会让 Agent 失去自主决策能力参数传递复杂POI 搜索有keywords、city、types、offset、page等十几个参数全部写进提示词会极度臃肿响应解析困难高德返回的 JSON 结构复杂解析代码随 API 变更需频繁维护工具管理混乱为每个 API 写函数再手动注册代码冗长且扩展困难。高德地图 MCP 集成MCPModel Context Protocol是连接 LLM 与外部工具的标准协议。项目使用amap-mcp-serverNode.js 实现仓库实际通过uvx启动提供 POI 搜索、天气查询、路线规划等工具图 13-7、表 13-1 展示了其工具分类。仓库中的 MCP 集成代码trip_planner_agent.pyself.amap_tool MCPTool( nameamap, description高德地图服务, server_command[uvx, amap-mcp-server], env{AMAP_MAPS_API_KEY: settings.amap_api_key}, auto_expandTrue ) self.amap_tool.expandable True要点解析server_command指定 MCP 服务器启动方式仓库用uvx从 PyPI 拉取运行env传递高德 API 密钥环境变量auto_expandTrue是核心创建MCPTool时自动查询服务器暴露的工具清单为每个工具生成独立 Tool 对象。因此只创建了一个MCPToolAgent 却获得了 16 个工具amap_maps_text_search、amap_maps_weather等expandableTrue显式声明可展开确保工具列表动态注册到 Agent。调用链路图 13-8Agent 生成[TOOL_CALL:amap_maps_text_search:keywords景点,city北京]→ HelloAgents 框架解析标记提取工具名与参数 →MCPTool构造 JSON-RPCtools/call消息经 stdin 发给服务器进程 → 服务器调用高德 HTTP API → 提取关键字段后经 stdout 返回文本结果 → Agent 继续生成回复。整个过程对 Agent 透明底层细节全部封装。MCP 使用进程间通信stdin/stdout而非 HTTP更高效且易管理。JSON-RPC 请求与响应示例如下{ jsonrpc: 2.0, method: tools/call, params: { name: amap_maps_text_search, arguments: { keywords: 景点, city: 北京 } } }共享 MCP 实例三个 Agent景点、天气、酒店都需要高德工具。若各自创建MCPTool实例会启动三个服务器进程重复调用 API 可能超限并浪费内存/CPU。因此仓库在MultiAgentTripPlanner.__init__中只创建一个共享实例再分别add_tool给三个 Agentself.attraction_agent.add_tool(self.amap_tool) # 共享 self.weather_agent.add_tool(self.amap_tool) # 共享 self.hotel_agent.add_tool(self.amap_tool) # 共享 # planner_agent 不需要工具专注信息整合初始化时打印各 Agent 工具数量确认展开结果len(self.attraction_agent.list_tools())。这样三个 Agent 共享底层单个 MCP 服务器进程节省资源且便于控制 API 调用频率。Unsplash 图片 API 集成为了让行程生动直观项目为景点补充图片。UnsplashServiceunsplash_service.py封装了两个方法search_photos(query, per_page5)搜索多张图片返回id、urlregular 尺寸、thumb、description、photographer字段get_photo_url(query)获取单张图片 URL。该服务同样以单例模式get_unsplash_service()提供。与 MCP 工具不同Unsplash 没有封装成 Tool——因为图片搜索只是确定性的数据增强步骤不需要 Agent 智能决策直接在 API 层调用即可。文档同时给出替代思路如需 Agent 自主决定是否取图或切换图片来源可将其封装为 Tool若追求搜索精度可考虑必应、百度或高德 POI 图片 API通常需付费。13.5 前端开发详解前后端分离架构与目录后端FastAPI只提供 JSON API前端Vue3 TypeScript是独立 SPA通过 HTTP 请求调用后端并渲染页面互不依赖、可独立部署测试。前端技术栈对应 package.jsonVue 3.5、TypeScript 5.7、Vite 6、Ant Design Vue 4、Axios、amap/amap-jsapi-loader、html2canvas、jsPDF、vue-router。目录结构frontend/src/ ├── views/ # 页面组件Home.vue 首页表单、Result.vue 结果页 ├── services/ # API 调用逻辑api.ts ├── types/ # TypeScript 类型定义index.ts └── router/ # 路由配置类型定义types/index.ts中Location、Attraction、DayPlan、TripPlan等与后端 Pydantic 模型一一对应TripFormData对应后端TripRequest含travel_days: number、preferences: string[]、free_text_input: stringTripPlanResponse对应后端统一响应包装success/message/data。类型检查保证调用 API 参数错误立即报错、后端结构变更前端即时感知、IDE 输入tripPlan.自动补全字段。API 服务封装api.ts 使用 Axios 实例统一管理请求baseURL通过import.meta.env.VITE_API_BASE_URL读取默认http://localhost:8000比文档写死的地址更灵活timeout: 1200002 分钟生成计划需多个 Agent 串行调用 LLM 与外部 API全程可能 10-30 秒超时过短会误中断请求/响应拦截器请求发送前与响应接收后统一打印日志方法、URL、状态码便于调试generateTripPlan(formData)为唯一入口函数错误时抛出带后端detail信息的异常。Home 表单与加载反馈Home 页使用 Vue3 Composition APIref定义formData含默认值days: 3、preferences: 历史文化、budget: 中等、transportation: 公共交通、accommodation: 经济型酒店与loading/loadingProgress/loadingStatus三个加载状态变量。提交逻辑设置loadingtrue→ 启动setInterval每 500ms 递增进度≤30% 搜索景点、≤50% 查询天气、≤70% 推荐酒店、其余 生成行程计划→await generateTripPlan→ 成功则清理定时器、进度置 100、router.push跳转结果页并携带state: { tripPlan }→ 失败弹出错误消息 →finally复位 loading。模拟进度虽非真实后端进度但能有效消除用户等待焦虑。模板使用 Ant Design Vue 组件a-form:modelformDatafinishhandleSubmit、a-inputv-model:value双向绑定、必填校验规则、提交按钮:loadingloading以及条件渲染的a-progress进度条。Result 页面地图可视化与导出结果页核心是高德地图可视化通过AMapLoader.load({ key, version: 2.0 })加载 SDK创建AMap.Map实例后遍历days[].attractions为每个景点添加带序号的AMap.Marker标记位置直接取attraction.location.longitude/latitude。导出功能基于html2canvasjsPDFconst exportAsImage async () { const canvas await html2canvas(element, { backgroundColor: #ffffff, scale: 2, useCORS: true }) const link document.createElement(a) link.download ${tripPlan.value.city}旅行计划.png link.href canvas.toDataURL(image/png) link.click() }scale: 22 倍分辨率输出更清晰useCORS: true允许跨域加载 Unsplash 景点图片PDF 导出额外计算 A4 宽高比宽 210mmpdf.addImage(imgData, PNG, 0, 0, imgWidth, imgHeight)后保存。13.6 核心功能实现详解预算计算交给 PlannerAgent 而非前端预算估算依赖景点的门票价格、酒店价格范围、餐饮标准等生成时才获取的信息因此实现于 PlannerAgent提示词明确要求输出budget结构total_attractions/total_hotels/total_meals/total_transportation/totalLLM 按行程内容估算各项费用——如故宫 60 元 天坛 15 元 颐和园 30 元门票合计 105 元3 天 2 晚经济型每晚 300 元则酒店 600 元。前端用 Ant Design Vue 栅格布局a-rowa-col :span6并排展示四项a-statistic分隔线下方用红色大字号value-style设置color: #cf1322, fontSize: 32px突出总费用。注意v-iftripPlan.budget条件渲染——Budget在 Pydantic 中是OptionalLLM 未生成时不渲染该卡片体现前端容错。加载进度条与行程编辑进度条逻辑见 13.5 节其核心价值在于把 10-30 秒的不可感知等待转化为可视反馈。行程编辑功能的实现要点状态管理维护editMode与originalPlan两个状态进入编辑模式时用JSON.parse(JSON.stringify(tripPlan.value))深拷贝保存原始计划JS 对象是引用类型直接赋值会共享同一对象导致无法还原移动景点ES6 解构赋值交换数组元素[a, b] [b, a]并做边界检查newIndex 0 newIndex attractions.length删除景点splice(attractionIndex, 1)保存/取消保存时initMap()重新初始化地图以反映位置变化取消时恢复originalPlanUI 联动编辑模式下每个景点旁渲染上移、下移、删除按钮。导出功能的兼容性权衡html2canvas处理嵌套 Canvas高德地图渲染存在兼容性问题项目实测多种方案地图 Canvas 转图片、allowTaint等后选择导出时简化内容暂隐地图部分只导出行程文字与景点信息保证功能可用。文档给出四条改进路径使用高德静态地图 API 生成静态图替代动态地图、前后端分开导出再合并、Puppeteer 无头浏览器服务端截图、导出时隐藏地图。实际落地可结合成本与场景择优。侧边导航与锚点跳转结果页内容长概览、预算、地图、每日行程、天气用 Ant Design Vuea-menumodeinlinev-model:selectedKeys绑定当前区块做侧边导航。点击菜单项调用scrollToSection({ key })更新activeSection后执行document.getElementById(key).scrollIntoView({ behavior: smooth, block: start })。各内容区块通过div idoverview、div idbudget、div idmap等 id 与菜单 key 对应实现平滑定位。13.7 结语从会跑到能用的全栈智能体应用本章项目的本质价值在于把前面章节的零散能力SimpleAgent、工具系统、MCP、上下文构建整合为一个真正可运行、可交互、可扩展的应用。回顾四条主线收获系统设计思维复杂问题分解为多个简单子任务搜索、天气、酒店、规划各司其职工程实践能力Pydantic 统一数据契约、单例共享资源、JSON 多级解析 兜底计划、配置校验config.py 中的validate_config启动时检查AMAP_API_KEY并警告缺失的 LLM 密钥等生产级细节全栈开发能力FastAPI Vue3 前后端分离、地图可视化、图片/PDF 导出AI 应用开发通过 MCP 标准协议把真实世界 API 接入 LLM 决策闭环。这是一个起点而非终点。后续可在此基础上添加餐厅推荐、交通规划 Agent扩展 UI 交互或迁移到智能购物、智能学习等平行领域并最终部署到生产环境服务真实用户。最好的学习方式是动手实践——修改、扩展、优化代码每一次实践都会加深对多 Agent 系统的理解。【免费下载链接】hello-agents 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考