ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

高德API+MCP+Dify实战:从零搭建会调用工具的旅游规划Agent

高德API+MCP+Dify实战:从零搭建会调用工具的旅游规划Agent 如果你和我一样试过让ChatGPT或者各类大模型帮你做一份旅游攻略大概都会经历同一个过程刚拿到手的瞬间觉得“哇好详细”等真到了景区门口才发现——它推荐的饭馆可能早就关门了它规划的路线可能根本没有公交直达。这不是模型不够聪明而是它压根没有实时数据也没有地图坐标概念。所以当我开始做旅游规划Agent的时候就定了一条原则Agent必须“会用手”不能只靠脑子里那点训练数据编内容。我选择的技术栈是高德API Dify MCP高德API提供实时POI、路线规划、天气这些能力MCP负责把高德API包装成让大模型可以调用的标准工具接口Dify负责把多模型对话、多轮工具调用和最终输出编排成完整服务。最终产出的是一个能生成可执行旅游规划清单的Agent——什么时间出门、先去哪后去哪、坐什么车、大概花多少钱都能给到具体答案而不是一篇“看似专业实则无用的散文”。这篇文章不聊概念只讲我从0到1怎么把这三个东西串起来中间踩了哪些坑、哪些配置是文档里不会写清楚的全部记录下来。1. 为什么是“规划清单”而不是“攻略文章”旅游场景对Agent的真正考验先泼一盆冷水如果你只是想要一篇“XX城市旅游攻略”现在任何一个大模型都能写得像模像样根本不需要Agent也不需要API。Agent的价值不在于“会写”而在于“能查到、能算出来、能落地”。旅游规划这个场景本质上是一个强工具依赖的任务。用户问“从故宫到颐和园怎么走最方便”模型光靠参数记忆是答不准的它需要实时知道今天的公交运行情况、路线距离、预计耗时。用户问“附近有什么适合带小孩的景点”模型也不知道“附近”到底指哪里它需要拿到一个坐标再做周边检索。这些问题在传统对话式AI里是无解的但在Agent架构下是有标准答案的。这也是我选择高德API而不是让模型自由发挥的根本原因。旅游规划里最核心的几类信息——地点是否存在、坐标在哪里、距离多远、通行时间多长、今天天气怎么样——都是高德API能直接返回的硬数据。把这些硬数据交给Agent它生成的清单才有可执行性。再说MCP在这里面的角色。MCPModel Context Protocol本质上是一个工具调用协议它解决的是“模型怎么用外部工具”的标准化问题。以前接一个API要写插件、写函数调用规范、写错误处理每换一个模型就得重来一遍。MCP把工具封装成标准的“Tool”定义好输入输出SchemaDify这类平台直接读取MCP服务器暴露的工具列表就能让大模型在对话中自动决定何时调用哪个工具。这意味着我只需要维护一份高德API的MCP封装所有的模型不管是GPT、Claude还是国产模型都能通过Dify直接使用。所以整个项目可以拆成三层数据层高德API负责一切和地理位置、交通、天气相关的实时数据工具层MCP服务器把高德API封装成标准工具暴露给上层调用编排层Dify工作流负责任务拆解、多轮对话、工具调度、结果格式化这三层每层都有各自的门道。下面逐个说。2. 高德API能力盘点与账号准备Agent的“数据地基”2.1 控制台里最容易被忽略的两个配置去高德开放平台lbs.amap.com注册开发者账号在控制台创建应用拿到Key这步不难。但有两点我建议大家一开始就留意否则后面排查问题会很痛苦。第一个是IP白名单。创建Key的时候高德会让你选择“域名白名单”还是“IP白名单”。这个配置直接影响后续Dify后端调用能否成功。很多人第一次配置时顺手填了localhost结果部署到服务器上之后Dify容器里发出去的请求被高德拒绝报错信息还很模糊。我的做法是如果Dify跑在服务器上就填服务器出口IP如果只是本机测试先用“/0”这种宽松配置跑通流程正式上线前再收紧。第二个是接口权限。高德开放平台的Key会绑定一组服务默认情况下你可能只开通了“地理位置/逆地理编码”而路线规划、天气查询这些服务是分开的。如果你在测试阶段发现某个接口老是返回“INVALID_USER_KEY”别急着怀疑代码先去控制台确认一下该服务有没有开通。2.2 这次用到的API清单我把从需求梳理到实际编码用到的几个核心接口列个表方便大家按图索骥用途接口名称需要传入的核心参数返回的核心字段按关键词搜索POI/v3/place/textkeywords、cityPOI名称、地址、经纬度、电话、类型周边搜索POI/v3/place/aroundlocation经纬度、radiusPOI列表、距离、评分步行路径规划/v3/direction/walkingorigin、destination距离、步行耗时、路线坐标驾车路径规划/v3/direction/drivingorigin、destination距离、预计时间、过路费公交/地铁规划/v3/direction/transit/integratedorigin、destination、city换乘方案、总耗时、步行距离逆地理编码/v3/geocode/regeolocation街道名称、所在区县、商圈天气查询/v3/weather/weatherinfocityadcode天气现象、温度、风力静态地图/v3/staticmapmarkers、zoom静态图URL我在实际项目中用得最多的是place/text和direction两个系列。place系列负责“找地点”direction系列负责“算路线”这两个一组合行程编排的核心逻辑就出来了。有个细节值得注意天气接口接收的city参数不是城市名而是城市的adcode编码比如北京是110000。刚开始我自己写MCP封装时没有加“城市名转adcode”这一步直接传了“北京”两个字结果天气一直查不出来。后来在MCP工具内部先调用一次地理编码API把中文城市名转成adcode再传给天气接口问题才解决。这个“工具内部串联”的思路在Agent开发里比想象中重要得多。2.3 选型为什么不换别的地图服务聊到地图选型肯定有人会问为什么是高德。我的理由有三点。第一高德的POI数据在旅游场景下最全。热门景区、小众打卡点、附近洗手间这一类POI数据高德的覆盖广度是明显优于其他家的。第二路线规划接口稳定。我实测过同一条路线在不同服务商的返回结果高德的公交方案数量和步行指引细节最接近真实情况。第三周边检索的radius筛选很灵活可以按500米、1公里、3公里逐级搜索这对Agent做“附近还有什么”这种动态决策特别友好。当然高德也有它的短板比如QPS限制相对严格个人开发者默认并发额度不够高这个问题我在后面“踩坑”章节会细说。3. MCP协议接入把高德API变成Agent的“外设”3.1 MCP到底解决了什么我用一个最朴素的类比解释MCP大模型相当于人脑它很聪明但它没有手脚也不长眼睛耳朵。MCP就是给大脑接上“USB口”把高德API这些外部能力变成“外设”——想查天气就插上温度计想规划路线就接上导航仪。以前没有MCP的时候接入外部工具是这么干的把高德API封装成普通HTTP接口然后在Agent提示词里写“当用户问路线规划时调用这个函数函数签名如下...”。这种方案最大的问题是每换一个模型它对“函数说明”的理解力和遵循度都不一样有些模型根本不按你写的格式调用导致工作流经常莫名其妙的空返回。MCP的好处是它把工具定义变成了一种“模型共识”Dify在底层处理了工具发现、参数校验、结果回传这些脏活我只需要关心业务逻辑本身。3.2 用Python快速搭建一个高德MCP服务器MCP服务器的搭建方式有很多种我用的是基于FastMCP的Python实现。原因很简单Python生态里请求高德API最方便而且FastMCP写起来最快。先安装依赖pip install fastmcp httpx然后写一个最简的高德MCP服务器。下面这个代码相当于Demo里面只暴露了两个工具一个是关键词搜索POI一个是步行路径规划核心逻辑已经完整了。from fastmcp import FastMCP import httpx AMAP_KEY 你的高德Key mcp FastMCP(amap-travel) mcp.tool() def search_places(keyword: str, city: str) - dict: 按关键词搜索指定城市的POI列表返回匹配的地点名称、地址、经纬度。 params { key: AMAP_KEY, keywords: keyword, city: city, offset: 10, extensions: base } resp httpx.get( https://restapi.amap.com/v3/place/text, paramsparams, timeout15 ) return resp.json() mcp.tool() def walking_route(origin: str, destination: str) - dict: 计算两个经纬度坐标之间的步行路线返回总距离、预计耗时。 params { key: AMAP_KEY, origin: origin, destination: destination } resp httpx.get( https://restapi.amap.com/v3/direction/walking, paramsparams, timeout15 ) return resp.json() if __name__ __main__: mcp.run(transportstreamable-http)最后一行非常关键。我在测试阶段试过用stdio方式启动结果只能在命令行里跑Dify那边完全连不上。后来换成了streamable-http让MCP服务器作为一个HTTP服务跑在某个端口上Dify才能正常发现工具。3.3 参数Schema和工具描述决定Agent智商上限的隐藏细节这是我整个项目里体会最深的一点。MCP工具能不能被Agent正确使用很大程度上不取决于代码逻辑而取决于“你告诉模型这个工具是干什么的”。举个反例。我最开始写的搜索POI工具描述是“search_places(keyword, city) — 搜索地点”。就这简单一句话。结果在实测中发现Agent在用户问“推荐一下北京的博物馆”时会把“北京博物馆”五个字整个塞进keyword参数再把city也填成“北京”。搜索回来的数据看起来是对的但细看会发现排名靠前的全是“北京博物馆预约中心”“北京汽车博物馆”这类没有区分景点类型。后来我把描述改成了“search_places(keyword, city) — 搜索指定城市的兴趣点POIkeyword应为地点类型或名称关键词例如博物馆、主题公园、美食街city应为城市中文名例如北京、成都。结果按相关性排序最多返回10条。”改完之后Agent的调用明显聪明了它会自己把“北京的博物馆”拆解成keyword“博物馆”city“北京”。还有一点参数里尽量用一致的坐标系。高德API返回的经纬度默认是高德坐标gcj-02而你在网上看到的很多POI数据源可能是wgs84坐标两者看似接近但误差足以把地图标记偏几百米。我把坐标转换逻辑直接坐在MCP服务层所有工具统一输出gcj-02坐标省了很多麻烦。4. Dify工作流编排让Agent真正在“规划行程”MCP服务器跑起来之后下一步就是把Agent搭在Dify上。这里有个前置条件Dify社区版需要开启插件市场并安装MCP相关的插件不同版本入口略有差异。我自己用的版本里MCP是作为“工具插件”被加载的安装完成后在设置里面填MCP服务器的URL之后就能在Agent节点里直接选中这些工具了。4.1 在Dify里接入MCP服务器接入的路径大概是插件市场搜索MCP → 安装 → 在Dify设置里填服务器地址如http://你的服务器IP:8000/mcp→ 测试连接成功 → 工具列表自动刷新。这里有个小坑如果Dify和MCP服务器跑在同一台机器上尽量用容器网络的内部地址或者公网地址别用localhost。Dify容器里的localhost指的是容器自己不是宿主机。我一开始在Dify控制台填http://127.0.0.1:8000/mcp结果一直报连接失败排查了半天才意识到是两个容器之间的网络隔离问题。连接测试通过后Dify会自动拉取MCP服务器上所有暴露的工具包括工具名称、描述、参数Schema。建议在控制台挨个检查一遍工具描述有没有被正确解析参数类型对不对。这一步如果出错后面Agent调用工具时会有大量的“参数无效”报错。4.2 Agent节点里怎么写角色提示词Dify的Agent节点本质上是“大模型 工具列表 提示词”的组合。提示词的质量直接影响Agent能不能把工具串起来用。我的第一版提示词写得很简单“你是一个旅游规划助手请根据用户需求规划行程。”结果Agent经常只调用一次工具就草草收场用户要三天行程它只搜了一个景点列表就开始编内容了。后来我重新设计了提示词核心思路是把“必须分步骤调用工具”这个行为约束写死并且明确每一步的产出物。这里分享一个我感觉还比较好用的提示词框架你是一个专业旅游规划助手。你的任务是根据用户的需求生成一份可执行的旅游清单。 流程要求如下 第一如果用户没有提供城市或旅行天数你必须先追问不要假设。 第二如果用户提供了城市但没说明偏好你需要同时调用search_places搜索热门景点、美食、商圈并调用weather查询当地天气作为行程安排的依据。 第三在安排每天的路线时你必须先确定当天每个景点的经纬度然后用walking_route、driving_route或transit_route计算景点之间的通行时间确保行程时间合理、不走回头路。 第四最终输出必须是结构化清单包含日期、时间节点、地点名称、交通方式、预计耗时。 注意如果某个工具返回的数据为空请换一个关键词重新搜索不要自行编造地点。这个提示词立竿见影。核心在于“必须先用天气数据再排行程”和“必须计算通行时间”这两个约束能让Agent自动进入多工具协同的模式而不是搜索到一个地点列表就结束任务。4.3 工作流设计意图识别、并行调用与多轮工具协作Dify里的Agent应用有两种玩法一种是纯对话型直接让Agent自己在循环里反复调用工具另一种是工作流型我用显式的节点把任务拆开。我用的是后者更可控。整体工作流大概是这样的对话入口接收用户输入原始需求意图识别节点让模型判断用户到底要“查景点”“查路线”还是“生成完整行程”工具编排节点如果是生成完整行程就并行调用MCP里的多个工具POI搜索、天气查询、路线规划变量聚合器把多个工具的返回结果汇总成一个统一的JSON结构Prompt格式化节点把JSON结构塞进输出模板生成最终清单并行调用这个设计特别重要。在Dify里你可以把多个工具节点放在一个并行分支里Agent会同时发起多个请求。比如用户输入“给我规划成都三天游”并行节点会同时触发“成都热门景点搜索”“成都美食搜索”“成都天气查询”三个工具调用省掉了串行调用时的等待时间。4.4 变量聚合器和上下文超长的处理聊到上下文超长这是我在网上搜的时候看到很多人问的问题。Agent节点每轮对话都会把历史消息带进上下文如果工具返回的结果又长又多高德搜索一次可能带回10个POI每个POI还有一段描述几轮下来上下文就爆了。我的处理办法是三层第一层把工具返回结果尽量精简。在MCP封装层就把结果字段挑好只保留Agent真正需要的名称、经纬度、耗时那些没用的描述字段一开始就不要传。第二层在Dify里用变量聚合器把零散结果压缩。这招我在实测中发现特别好用——变量聚合器可以把多分支的结果拼成一个数组再配合一个“摘要提示词”让模型把几十条POI数据压缩成关键信息摘要。第三层控制记忆窗口。在Dify的Agent节点配置里把历史消息轮次限制在6轮以内防止早期上下文被反复塞进新请求。5. 全链路实测一次真实的“北京三日游”调用过程理论说再多不如放一次真实的调用记录。下面我用一个典型的用户输入完整复盘Agent从收到请求到输出清单的全过程。5.1 用户原始输入“给我规划一下北京三天游我和女朋友去预算适中想去故宫也想去一些比较文艺的地方怕下雨尽量看天气安排。”这个输入信息量很大城市明确北京、时长明确三天、同行人情侣、预算适中、偏好故宫文艺打卡地、隐藏需求天气影响行程。5.2 Agent的推理与工具调用过程第一步Agent判断用户没有指定具体出发日期于是先追问“请问你计划哪几天来北京我好帮你安排天气和路线。”这一步非常关键。没有日期就无法查天气没有天气就无法“根据天气安排行程”。用户补充“下周三到周五”之后Agent开始行动。第二步Agent在并行节点里同时发起了四个MCP工具调用{ tool: search_places, arguments: {keyword: 故宫, city: 北京} } { tool: search_places, arguments: {keyword: 文艺街区 胡同, city: 北京} } { tool: weather, arguments: {city: 110000, date: 2025-11-05} } { tool: weather, arguments: {city: 110000, date: 2025-11-06} }注意这里的细节Agent分别搜索了“故宫”和“文艺街区 胡同”这说明模型理解了用户需求里同时有“故宫”和“文艺”两个偏好而不是笼统地搜索一次“北京景点”。第三步工具返回后Agent开始规划路线。它先确定了故宫的坐标116.397637, 39.916527再用walking_route计算从故宫到南锣鼓巷的步行时间得到大约1.2公里、步行18分钟。接着又用transit_route计算了从南锣鼓巷到798艺术区的公交方案大约需要50分钟。第四步Agent综合天气数据、路线耗时和预算约束开始生成清单。5.3 最终输出的清单长什么样这是Agent最终输出的简化版结构上我做了脱敏和精简但格式是真实的北京三天情侣游行程清预算适中 Day 1周三晴18℃ - 上午故宫博物院8:30开门建议7:45出发门票60元/人需提前小程序预约 - 中午景山公园故宫北门步行5分钟登高看故宫全景门票2元 - 下午南锣鼓巷从景山打车约15分钟逛胡同文艺小店 - 晚上后海酒吧街南锣鼓巷步行10分钟沿湖散步看夜景 Day 2周四多云转小雨15-22℃ - 上午798艺术区因下午有雨把户外景点集中到上午 - 中午望京小街798步行15分钟有很多性价比餐厅 - 下午中国电影博物馆室内场馆避雨 - 晚上三里屯太古里地铁14号线直达约40分钟晚餐购物 Day 3周五小雨转晴10-17℃ - 上午颐和园雨后的园林人少景美地铁4号线北宫门站 - 下午北京大学/清华外观打卡颐和园打车约15分钟 - 傍晚五道营胡同地铁2号线约35分钟收尾文艺行程 - 晚上回程我第一眼看到这份清单的时候还挺惊讶的它真的做到了“看天行事”——第二天下雨Agent主动把户外景点排在上午、下午换成室内场馆。这就是把天气查询接进Agent之后的价值比任何“根据经验推荐”都靠谱。5.4 这份清单里哪些信息最容易被忽略但最值钱仔细看这份清单有三个细节是纯大模型生成“攻略”时给不出来的第一是预约提醒。故宫每天限量需要提前预约Agent能查到POI数据里的开放时间字段并把预约要求写进清单。第二是动态避雨。把天气数据和行程进行交叉约束这在传统静态攻略里不可能实现。第三是游客密度逻辑。Agent在下午把798改成室内场馆本质上是做了一次“根据天气因子的动态行程重排”这种解决问题的能力来自工具返回的数据而不是模型自带的泛化知识。6. 从踩坑到稳定四个常见故障与排查链路项目上线测试这一个月里我陆陆续续踩了不少坑。很多问题单独看很弱智但串联起来就是新手劝退三连。我把它们整理成四个高频问题每个都给排查思路。6.1 Dify控制台报“An error occurred during credentials validation”这个报错是Dify在配置工具或应用接入时的经典错误。字面意思是“凭据验证时出错”但实际原因千奇百怪。我遇到的两次分别是第一次是高德Key填错了或者权限没开高德控制台里每个Key都有服务权限列表如果你签发的Key只开通了Web服务却拿它去调Web服务以外的接口就会报这个错。第二次是高德Key配了IP白名单但Dify出网IP不匹配这时候高德返回的错误是USERKEY_PLAT_NOMATCHDify界面会包装成“credentials validation failed”。排查建议先去高德控制台发一个测试请求用浏览器直接访问高德API看是否返回正常。如果浏览器正常、Dify报错那基本断定是IP白名单或Dify内部网络出口IP的问题。6.2 SSL握手失败或HTTP连接被拒Dify部署在Docker容器时MCP服务器fastmcp监听在某个端口上外部服务连不上通常有两个原因。第一个是防火墙没开第二个是最容易被忽视的——fastmcp默认走的是明文HTTP而Dify在发起外部连接时如果强制校验SSL证书就会报SSL握手失败。我当时在Dify那边配置MCP连接时填了http://开头的地址但Dify内部有一个选项默认认为是https://导致握手失败。解决办法是在MCP的服务器地址里写完整的http://ip:port/mcp同时确认FastMCP的CORS中间件是开启的否则浏览器端调试也会被跨域拦截。6.3 Agent工作流上下文超长这个问题的根本原因是工具返回内容太大。高德POI搜索一次返回10条数据每条包含名称、地址、经纬度、类型、电话、评分。十个POI加起来就有两千多字如果Agent在多轮对话里反复调用上下文很快就会被撑爆。我的最终方案是做“工具返回内容瘦身”。在MCP工具内部把返回的JSON过滤得只剩下名称、经纬度和距离其他全去掉。这一步看似小但对上下文长度的节省非常可观。瘦身之后一次POI搜索的返回从两千字变成三百字Agent的响应速度和准确性都有提升。如果你不想改MCP代码也可以用Dify的变量聚合器。把所有工具结果聚合到一个变量里再用一个“关键信息提取”节点让模型重新总结一遍输出一个压缩版摘要给后续节点使用效果差不多。6.4 高德API的QPS超限高德个人开发者默认QPS好像是3到5这个额度对Agent的多工具并行调用来说真的不够看。用户一句“规划行程”可能触发4个并行请求瞬间就把额度打满了。我的缓解手段有三个第一是缓存对于同一天、同一城市的天气查询第一次请求后缓存结果后续直接复用不再重复打接口。第二是错峰在MCP工具内部加一个简单的限流队列同一个Key的请求按顺序排队避免并发打爆。第三是申请配额在高德控制台可以提额个人开发者申请到20QPS不是太难企业认证配额更高。最后说点个人的体会。复盘整个项目最值得分享的一点倒不是某个具体的配置技巧而是“Agent开发里工具描述的重要性被大多数人低估了”。我见过很多人费尽心思调提示词、换大模型结果Agent表现不稳定其实问题出在MCP工具的参数设计上——让模型更容易理解工具、更容易生成正确参数才是Agent可用性的核心。如果你也准备做类似的东西我建议按这个顺序动手先跑通高德API本身确认数据没问题再写MCP封装用命令行调通工具最后才接到Dify里配Agent。每层单独验证顺畅了再逐层拼接排查问题会轻松很多。另外常用城市的热门POI数据完全可以做静态缓存第一次跑完之后存起来后面直接复用能省下大量API配额响应速度也能从秒级降到毫秒级。
返回列表