资讯详情

资讯详情

AI旅游Agent技术栈拆解:从对话交互到MCP协议与支付闭环

1. 为什么我要拆这个AI旅游Agent的技术栈去年年底我开始接触AI旅游Agent这个方向起因很简单我自己出去玩的时候做攻略这件事太痛苦了。查景点、比酒店、算预算、排行程一套流程下来少说三四个小时。当时就想能不能做一个对话式的Agent用户说一句“帮我安排三天两晚的成都行程预算三千带老人”它就能把机票、酒店、门票、行程全搞定最后直接跳到支付。想法很美好真动手做的时候才发现这东西的技术栈比我想象的复杂得多。它不是简单的“前端聊天框 后端调个大模型API”就完事了。一个能真正跑通的AI旅游Agent至少涉及四层前端对话交互层、Agent编排与工具调用层、MCP协议对接层、支付与订单履约层。每一层都有各自的坑层与层之间的衔接更是容易出问题。我前后迭代了三个版本第一版是个玩具只能聊天不能办事第二版接入了几个旅游API但工具调用经常乱套第三版才把MCP这套协议用起来把支付链路打通算是能跑通完整闭环了。这篇文章就是把这三次迭代的经验完整拆开从用户输入第一句话开始一直讲到支付成功回调把每一层的技术选型、核心实现、踩过的坑都讲清楚。适合谁看如果你正在做AI Agent相关的产品尤其是涉及多工具调用、外部服务对接、支付闭环的场景这篇会有直接参考价值。如果你只是想了解MCP到底是什么、怎么用文章里也有专门的章节。前端、后端、全栈都能找到自己关心的部分。我不讲虚的每个技术决策都会说清楚为什么这么选以及不这么选会出什么问题。2. 整体架构设计与分层思路2.1 四层架构的划分逻辑先上一张我实际用的架构分层从下往上看层级职责核心技术典型问题前端对话层用户输入、流式渲染、多轮上下文WebSocket/SSE、流式UI打字机效果卡顿、上下文丢失Agent编排层意图识别、任务规划、工具调度LLM Function Calling工具选错、参数幻觉MCP对接层标准化工具接入、资源管理MCP协议、Server/Client协议版本不兼容、超时支付履约层订单生成、支付跳转、回调处理支付网关、异步通知重复支付、回调丢失为什么这么分因为每一层的变更频率和稳定性要求完全不同。前端对话层可能一周改三次UIAgent编排层的Prompt可能天天调但MCP对接层和支付履约层一旦跑通最好半年别动。分层之后改UI不会影响支付逻辑调Prompt不会把订单搞乱。这里有个关键决策Agent编排层和MCP对接层要不要合并我一开始是合并的Agent直接调各个旅游API的SDK。结果就是每接一个新服务比如从携程换成飞猪都要改Agent的代码工具描述、参数格式、错误处理全得重写。后来拆出MCP层Agent只认MCP协议定义的工具具体对接哪个服务由MCP Server负责Agent代码一行不用改。这个解耦带来的收益非常大。2.2 为什么选MCP而不是自己写工具调用MCPModel Context Protocol这两年被讨论得很多但很多人还是没搞明白它到底解决什么问题。我用一句话说MCP是Agent和外部工具之间的USB-C接口。在没有MCP之前我接一个酒店查询API是这样的在Agent的Prompt里写一段工具描述定义参数schema然后在代码里写一个函数去调API再把结果格式化返回给LLM。每接一个服务这套流程重复一遍。问题是不同LLM对工具描述的理解不一样参数格式稍微变一下就可能调错。MCP把这套东西标准化了。它定义了三类原语Tools可调用的函数、Resources可读取的数据、Prompts预设的提示模板。Agent作为MCP Client只需要知道“有一个叫search_hotels的工具接受city、checkin、checkout三个参数”具体这个工具背后是调携程还是调飞猪Agent不关心。MCP Server负责把实际的服务包装成标准协议。我实测下来用MCP之后工具调用的准确率从大概70%提升到了90%以上。原因很简单MCP Server可以在工具描述里写非常详细的参数说明和示例而且协议本身对参数类型有严格校验LLM不容易产生幻觉参数。2.3 支付层为什么必须独立支付这件事我踩过最大的坑就是把它和订单逻辑混在一起。第一版的时候Agent生成订单后直接调支付接口结果用户中途取消、网络超时、重复点击各种边界情况把订单状态搞得一团糟。后来我把支付层完全独立出来核心原则是订单状态和支付状态分离用异步回调驱动状态流转。Agent只负责创建订单状态为pending支付层负责发起支付、接收回调、更新支付状态订单服务监听支付状态变化来推进履约。这样即使支付回调延迟或者丢失订单也不会乱可以通过对账补偿。这个设计参考了电商领域成熟的分布式事务方案但旅游场景比电商简单一些不需要那么重的补偿机制用本地消息表 定时对账就够了。3. 前端对话层的核心实现3.1 流式输出与打字机效果前端对话层看起来简单但要做好体验不容易。核心需求有三个流式输出、多轮上下文、工具调用状态展示。流式输出我用的是SSEServer-Sent Events没用WebSocket。原因是我这个场景是单向的服务端推给客户端SSE更轻量浏览器原生支持EventSource断线重连也简单。WebSocket适合双向实时通信旅游Agent用不上。但SSE有个坑Nginx默认会缓冲响应导致流式效果变成一次性输出。解决办法是在Nginx配置里加proxy_buffering off;和X-Accel-Buffering: no响应头。这个坑我调了大半天才找到原因。打字机效果不是简单的逐字显示。LLM返回的是token流一个token可能是半个词直接逐token渲染会出现断词。我的做法是维护一个缓冲区按标点符号和空格切分保证每次渲染的是完整语义单元。代码大概长这样// 流式渲染缓冲区处理 let buffer ; function onToken(token) { buffer token; // 遇到标点或空格才渲染避免断词 if (/[。\s]/.test(token)) { renderText(buffer); buffer ; } }3.2 多轮上下文的管理策略多轮上下文是另一个容易出问题的地方。旅游Agent的对话可能很长用户会反复修改需求“改成五天”“预算加到五千”“把酒店换成民宿”。如果每轮都把完整历史传给LLMtoken消耗巨大而且早期的不相关信息会干扰判断。我的策略是分层上下文最近3轮对话完整保留更早的对话做摘要压缩。摘要不是简单截断而是用一个小模型把历史对话提炼成结构化的需求描述比如“用户想去成都三天两晚预算3000带老人偏好安静酒店”。这个结构化需求作为系统提示的一部分比原始对话更省token也更准确。这里有个细节工具调用的结果要不要放进上下文我的做法是只放关键结果比如酒店查询返回了20个结果上下文里只保留前3个的摘要完整结果存在服务端需要时再取。否则上下文会被工具返回的大量数据撑爆。3.3 工具调用状态的可视化用户发起一个复杂请求Agent可能要调五六个工具耗时十几秒。如果前端只显示一个loading用户会以为卡死了。我的做法是把工具调用过程可视化显示“正在查询航班...”“正在比价酒店...”“正在生成行程...”每个步骤完成后打勾。这个体验提升非常明显。用户能看到Agent在干活等待焦虑大大降低。实现上Agent每调用一个工具就通过SSE推一个事件给前端前端更新状态列表。这个设计后来成了我们产品的差异化亮点很多用户反馈说“看着它一步步安排行程很安心”。4. Agent编排层的核心逻辑4.1 意图识别与任务规划Agent编排层是整个系统的大脑。用户说“帮我安排成都三天两晚”这句话背后要拆解成一系列子任务确定日期、查询航班、查询酒店、查询景点、生成行程、计算预算、创建订单。我的做法是两阶段规划先用LLM做意图识别和任务拆解生成一个任务列表然后按顺序执行任务每个任务可能对应一个或多个MCP工具调用。为什么不用ReAct那种边想边做的方式因为旅游场景的任务依赖关系比较明确先查航班才能定酒店日期先定行程才能算门票。提前规划好任务顺序比让LLM每步都重新思考更稳定也更省token。任务规划的Prompt我调了很多版核心是要求LLM输出结构化的JSON包含任务类型、依赖关系、参数。这里有个技巧给LLM几个few-shot示例尤其是包含“用户修改需求”的示例能显著提升规划准确率。4.2 工具调度的参数校验工具调用最容易出的问题是参数幻觉。用户说“下周三去成都”LLM可能把日期算错或者把“下周三”直接当成字符串传给工具。我的做法是在MCP Server层做严格的参数校验日期必须是ISO格式城市必须是标准城市名预算必须是数字。校验不通过怎么办不是直接报错而是把错误信息返回给LLM让它重新生成参数。这个重试机制很关键我设置了最多3次重试超过3次才向用户报错。实测下来大部分参数错误在第一次重试就能修正。这里有个经验工具描述里要写清楚参数格式和示例。比如checkin参数描述里写“入住日期格式YYYY-MM-DD例如2024-06-15”。LLM看到示例生成正确格式的概率高很多。4.3 多工具并行与串行编排有些工具可以并行调用比如查询航班和查询酒店没有依赖关系可以同时发起。有些必须串行比如必须先确认航班到达时间才能安排接机。我的编排引擎支持DAG有向无环图任务依赖。每个任务声明依赖哪些前置任务引擎自动判断哪些可以并行。并行调用能把总耗时从十几秒降到五六秒体验提升明显。但并行也有坑多个工具同时调用可能触发外部API的限流。我的做法是给每个外部服务设置并发上限超过就排队。这个在MCP Server层实现Agent层不用关心。5. MCP对接层的落地细节5.1 MCP Server的设计与实现MCP Server是我整个架构里最花心思的部分。它要做的核心事情是把各种旅游服务航班、酒店、景点、支付包装成标准的MCP Tools。一个典型的MCP Tool定义包含三部分name工具名、description描述、inputSchema参数schema。description要写得非常详细因为LLM就是靠这个描述来决定什么时候调用这个工具、怎么传参数。我举个例子酒店搜索工具的定义{ name: search_hotels, description: 根据城市、入住日期、退房日期搜索可用酒店。返回酒店名称、价格、评分、位置。适用于用户需要预订酒店的场景。, inputSchema: { type: object, properties: { city: {type: string, description: 城市名称如成都、北京}, checkin: {type: string, description: 入住日期格式YYYY-MM-DD}, checkout: {type: string, description: 退房日期格式YYYY-MM-DD}, max_price: {type: number, description: 每晚最高价格可选} }, required: [city, checkin, checkout] } }这个description我改了七八版最后发现明确写出“适用于什么场景”对LLM的判断帮助最大。之前没写这句LLM经常在用户只是问“成都有什么好玩的”时就去调酒店搜索浪费一次调用。5.2 MCP Resource的妙用MCP除了Tools还有Resources。Resources是只读的数据Agent可以主动读取。我在旅游Agent里用Resources存了两类数据用户偏好和城市知识库。用户偏好Resource存的是用户的历史选择比如“偏好经济型酒店”“不喜欢红眼航班”。Agent在规划任务时会先读这个Resource把偏好作为约束条件。城市知识库存的是每个城市的景点介绍、交通信息、最佳游玩季节Agent生成行程时会读取。Resources的好处是不占用对话上下文。这些数据量不小如果塞进Prompt会撑爆token但作为Resource按需读取就很优雅。5.3 MCP协议版本兼容的坑MCP协议还在演进不同版本之间有差异。我遇到过最坑的问题是Client和Server的协议版本不匹配导致工具调用静默失败。Client发出去的请求Server收到了但返回格式对不上Client解析失败又不报错就卡在那里。解决办法是在MCP Server启动时做一次握手确认协议版本。如果版本不匹配要么升级要么降级兼容。我在Server里做了多版本适配根据Client声明的版本返回对应格式。这个在文档里没写是我抓包对比才发现的。6. 支付履约层的完整链路6.1 订单创建与状态机设计支付层的第一步是订单创建。Agent生成行程后会把行程里的航班、酒店、门票拆成订单项创建一个主订单。订单状态机我设计得很简单pending待支付→ paid已支付→ fulfilled已履约→ completed已完成中间还有两个异常状态cancelled已取消和refunded已退款。状态流转只能单向不能回退。比如paid不能变回pending要取消只能走refunded。这个约束避免了状态混乱。订单创建时有个关键点库存预占。旅游产品尤其是酒店和机票库存是有限的用户下单到支付之间有时间差如果不预占库存可能支付成功但没库存了。我的做法是创建订单时调用库存服务预占预占有效期15分钟超时自动释放。6.2 支付网关的对接与跳转支付对接我用的是标准的支付网关方案支持多种支付方式。核心流程是创建支付单 → 获取支付链接/二维码 → 用户支付 → 异步回调 → 更新订单状态。这里有个重要设计支付单和订单分离。一个订单可能对应多次支付尝试用户第一次支付失败第二次成功所以支付单是独立的实体通过订单号关联。这样对账的时候清晰不会因为重复支付搞乱订单。支付跳转的体验也要注意。移动端优先唤起支付App唤起失败降级到H5支付页。这个降级逻辑要处理好否则用户点了支付没反应直接流失。6.3 异步回调的可靠性保障支付回调是整个链路最容易出问题的地方。回调可能延迟、可能重复、可能丢失。我的处理原则是回调只做状态更新不做业务逻辑。具体来说收到回调后先验签然后更新支付单状态再发一条消息到消息队列订单服务消费消息来推进订单状态。这样即使回调重复因为状态更新是幂等的已支付的不再更新不会出问题。回调丢失的话有定时对账任务兜底每5分钟查一次支付网关的订单状态补偿更新。这里有个坑回调验签的密钥管理。密钥不能硬编码在代码里要用配置中心或者密钥管理服务。我第一版硬编码了后来换环境的时候忘了改导致回调验签全失败排查了半天。7. 常见问题与排查技巧实录7.1 工具调用相关的问题问题现象可能原因排查方法解决方案Agent不调用工具直接回答工具描述不清晰查看LLM的思考过程优化description明确适用场景工具参数格式错误缺少参数示例打印实际调用参数在schema里加example字段工具调用超时外部API慢看MCP Server日志设置超时重试降级调用了错误的工具工具描述重叠对比相似工具的description明确区分各工具适用场景工具调用这块我踩的最大的坑是工具描述重叠。比如“搜索酒店”和“搜索民宿”两个工具描述写得太像LLM经常搞混。后来我在描述里明确写“酒店指传统酒店民宿指短租公寓”问题就解决了。7.2 支付链路的排查要点支付问题排查有个基本原则先看支付网关的订单状态再看自己系统的状态。很多时候是两边状态不一致以支付网关为准。常见问题速查支付成功但订单未更新检查回调是否收到验签是否通过消息队列是否正常重复支付检查支付单是否复用了订单号是否唯一回调延迟检查网络检查支付网关的配置必要时主动查询金额不一致检查订单金额计算逻辑注意浮点数精度问题金额计算我强烈建议用整数分存储不要用浮点数。0.1 0.2 0.30000000000000004这种问题在支付场景是致命的。7.3 我踩过的三个大坑第一个坑MCP Server的内存泄漏。MCP Server是长驻进程我一开始没注意连接池的管理跑了两天内存就爆了。后来加了连接池上限和定期清理问题解决。第二个坑SSE连接数限制。浏览器对同一域名的SSE连接数有限制通常是6个如果用户开了多个标签页可能连不上。解决办法是用一个共享的SSE连接或者用WebSocket。第三个坑支付回调的幂等性。我第一版没做幂等用户重复点击支付回调来了两次订单被履约了两次多订了一间房。后来加了幂等键用支付单号问题解决。这个坑让我赔了一晚酒店钱记忆深刻。8. 一些实操心得和后续扩展方向这套架构跑了大半年整体稳定。如果让我重新做一遍有几个地方会调整。MCP Server的粒度可以更细。我现在是按服务类型分航班Server、酒店Server、支付Server但实际运行下来按业务场景分可能更好比如“行程规划Server”包含所有规划相关的工具。这样Agent选择Server的时候更直观。Agent编排层可以引入更强的规划能力。现在的两阶段规划对简单场景够用但用户需求复杂的时候比如“帮我安排一个适合带老人和小孩的行程中间要有休息日”规划质量会下降。后续想试试用更强的模型做规划或者引入规划模板库。支付层可以支持更多支付方式。现在只接了主流支付后续想加分期、信用支付等。但每加一种支付方式对账逻辑都要改这块要提前设计好扩展点。最后分享一个小技巧给Agent加一个“确认”环节。在创建订单前让Agent把行程和价格汇总给用户确认用户点确认才创建订单。这个环节大大降低了误下单的概率也给了用户修改的机会。实现上就是在Agent流程里加一个“等待用户确认”的状态前端展示确认按钮。这个AI旅游Agent的技术栈拆解到这里就差不多了。从对话到MCP到支付每一层都有各自的挑战但分层清晰之后每层的问题都可以独立解决。如果你也在做类似的东西希望这些经验能帮你少踩几个坑。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →