ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI旅游Agent开发实战:从MCP工具链到支付订单全链路

AI旅游Agent开发实战:从MCP工具链到支付订单全链路 1. 需求与整体架构AI旅游Agent到底该长什么样聊到AI旅游Agent很多人第一反应是“套个GPT壳子让用户打字问旅行攻略”。实际上线跑过一轮你就会发现用户真正想要的不是聊天是“把事办成”说一句“帮我订下周去成都的机票和酒店预算四千左右”Agent得能听懂需求、检索实时库存、给出组合方案、确认支付、生成订单全程可能跨十几次工具调用。这已经不是单纯的对话系统而是一个带着自然语言交互界面的交易系统。我开发这个项目的初衷就是想把这条链路完整打通分享给同样在折腾AI应用落地的朋友。这个项目的完整技术栈我把它分成五个层次来设计前端对话层、Agent中枢层、MCP工具层、支付与订单层、基础设施层。前端对话层解决“用户怎么和Agent说话”的问题Agent中枢层解决“怎么理解需求、怎么规划任务、怎么串联工具”的问题MCP工具层解决“Agent怎么安全地拿到酒店、航班、景点、天气这些实时数据”的问题支付与订单层解决“怎么把钱收进来、订单状态怎么流转”的问题基础设施层则承载会话存储、日志追踪、限流降级这些横切能力。选型上我坚持一个原则能拆的服务不揉在一起能用协议打通的不自己做私有接口。MCPModel Context Protocol模型上下文协议目前已经是大模型应用接入外部工具的事实标准它把“工具的能力描述、参数格式、调用结果”统一成一个标准协议Agent不需要为每一家酒店供应商单独写一套适配逻辑。这个收益前期不明显等到接入第三家、第四家数据源的时候你会感谢当初这个决定。说到这里顺便给个整体架构图景方便下面各章对号入座浏览器/小程序前端通过WebSocket或SSE和网关通信网关背后是Agent编排服务负责调用大模型、管理会话、维护工具调用栈再往下是若干个MCP Server分别对应酒店、机票、景点、天气、政策文档等数据域支付环节独立成一个服务Agent只生成“待支付单”真正的资金操作必须由用户在前端确认Redis负责会话缓存和分布式锁PostgreSQL存订单和用户数据向量数据库放RAG检索用的知识库。2. 前端对话层流式响应与工具过程的可视化2.1 为什么前端不只是“聊天框”我见过不少团队做AI应用前端就是现成的聊天组件套一下后端吐什么就渲染什么。但旅游Agent的交互远比纯聊天复杂因为用户需要在对话中看到工具调用的过程和结构化结果。比如Agent说“正在为你查询周二成都飞深圳的航班”这个状态不是普通文本它得是一个单独的进度消息再比如Agent查到三个航班前端不能只输出一大段文字而要渲染一个航班卡片列表用户点一下就能选中。所以前端对话层的第一件事是设计一套消息类型协议。我实际使用了一套基于枚举加JSON Payload的结构消息类型包括text、tool_call、tool_result、card、confirm、form等。text是普通聊天文本tool_call表示Agent正在调用某个工具通常会带工具名和调用状态tool_result是工具返回结果的摘要card是富卡片比如机票列表、酒店详情、行程安排confirm是支付确认卡片包含订单金额、收款方、具体行程信息form用来收集用户没说完的必要参数比如出发日期、出行人数。前端拿到消息后按type字段走不同渲染分支。这套机制的好处是展示层和Agent逻辑彻底解耦后端Agent哪怕换了模型只要协议不变前端一行不用改。坏处是前期要写不少渲染组件尤其卡片组件要做成通用JSON渲染器字段多的时候容易失控。2.2 SSR还是CSR、小程序还是Web前端形态上我最终选了WebH5为主小程序适配为辅。技术上用React 18 TypeScript组件库选的是Ant Design的定制主题对话区域用一个轻量虚拟列表组件来处理长会话的内存问题。没有上重型的SSR框架因为Agent对话场景实时性很强服务端渲染反而拖慢首屏交互倒是用了Service Worker做了离线缓存用户在电梯里断网时至少能看到历史会话。小程序端是另一个工作量来源它没有Web那些成熟的流式渲染方案只能用WebSocket逐帧推送文本片段再拼接到视图层。踩过的坑是小程序setData频繁更新同一段消息容易触发性能告警尤其消息内容长的时候。解决办法是用增量patch而不是全量替换每次只在已有内容末尾追加新文本片段。还有一个经验是小程序里不要一次性渲染上百条历史消息要做分页加载一次拉最近50条往上滑再加载更早的。2.3 流式输出与中断控制流式输出是AI对话体验的底线。旅游Agent的回复往往分两种一种是大模型生成的规划文案需要像打字机一样逐字输出另一种是工具结果卡片应该一次性完整渲染。我用的方案是Agent服务端把大模型输出按SSEServer-Sent Events推给网关每条event带一个msgId和type字段前端按msgId分组做增量渲染。中断控制是很容易被忽略的点。用户觉得Agent说错了、不想等了会点“停止生成”。这个操作不能只是前端停掉渲染必须向后端发一条取消信号让Agent编排层终止当前的大模型调用和后续工具链。我做了一个requestId贯穿全链路的设计取消时通过Redis发布一个取消事件Agent循环里每次检查取消标记发现取消就立即返回并释放资源。实测下来这个机制比单纯断开连接干净得多至少不会出现用户停止了、后台还傻乎乎把酒店订金订单建出来的事故。2.4 前端传参与会话恢复的工程细节前端和后端的接口设计上我最满意的是一个会话统一入口的设计。所有交互不管是用户发消息、点卡片按钮、提交支付确认都是POST /api/conversation/{id}/events这一个接口请求体里带eventType和payload。这样做的好处是路由极简全链路都在一个会话上下文里处理不容易出现边角遗漏。会话恢复涉及权限和上下文重建前端在本地存sessionToken重新打开页面时先调GET /api/conversation/{id}拿到会话元数据再按时间拉取历史消息。这里注意历史消息中的tool_result这类过程数据可以不全量返回只给摘要前端渲染成“已为你查询成都天气”这类折叠记录即可。我现在是这么做的历史列表里把工具过程折叠成一行小字只有最新的关键卡片默认展开。3. Agent中枢层理解、规划、执行与并发3.1 意图理解与参数抽取Agent中枢的核心是把一句口语话拆解成结构化任务。我用的不是单独一个意图分类模型而是让大模型在对话上下文中直接产出结构化意图JSON。比如“我下周三去北京想住西单附近不要太贵机票也一起看看”模型输出的意图大致是destination北京time下周三preference西单附近/经济型need酒店机票。这一步的工程关键在Schema设计。意图JSON的Schema定得好不好直接决定后续工具调用顺不顺畅。我的建议是字段别太细、别太嵌套尽量扁平化能复用枚举就复用枚举。比如出行目的我定义了商务、亲子、情侣、独自、闺蜜几类模型做多选或单选都比自由文本稳定得多。参数不足的时候要触发追问流程但这个追问不是随便问问得结合当前已有的数据和最大信息增益来问。比如用户没说出发城市就去用户画像里找常用出发地找不到才追问而不是把所有缺失字段一次性问一遍。3.2 RAG在旅游领域怎么用旅游Agent不是纯靠模型记忆回答的它必须检索实时和权威信息。我的方案是搭了双路检索结构化数据走MCP工具直接查询非结构化文档走RAG。前者管机票、酒店、天气、景区余票这种东西后者管“签证材料有哪些”“当地防疫政策”“目的地游玩攻略”这类文档型知识。RAG这块我吃了不少亏。最初图省事把网上爬的攻略一股脑塞进向量库结果检索质量很差模型引用了过时、错误的内容体验完全不行。后来改成按知识点切块人工清洗每个城市建一个知识域攻略内容按主题切成300字左右的块并给每块打上来源、更新时间、适用季节标签。检索时先按意图里的目的地、时间过滤再做向量相似度排序。这个改动之后回答准确率明显提升。向量库选型上项目规模不大时不要一上来就上分布式数据库用单机支持向量检索的SQLite扩展或者轻量向量库就够。真正到了百万级知识块、多租户并发检索的时候再考虑换专业向量数据库也不迟前期省下的运维成本非常可观。3.3 任务规划与工具调用的循环控制Agent的每一次回复本质上是一个**“思考-调用-观察-再思考”的循环。我用的是类似ReAct的架构但做了两个重要的工程化改造。第一个是引入最大步数限制和超时兜底**默认最多执行8个工具调用、总时长不超过60秒防止模型陷入死循环。第二个是工具调用结果不是全量塞回上下文而是做摘要压缩只把关键字段和结论放回去否则多轮工具调用很快就把上下文窗口撑爆token成本也飙升。工具调用的并发策略也要想清楚。有些工具互相之间没有依赖比如查酒店和查天气完全可以并行有些必须串行比如先查航班再锁定座位。我在工具Schema里给每个工具标注了依赖关系和预估耗时编排层拿到模型规划的调用序列后先做依赖分析能并行的就放到一个Promise.all里跑能显著缩短整体响应时间。实测一个“机票酒店天气攻略”四步查询串行要20秒左右改成并行后压到9秒体验提升是肉眼可见的。3.4 Agent服务并发能力怎么扛热词里有人问“AI Agent怎么扛并发”这个确实是个高频痛点。Agent服务最大的问题不是大模型接口的QPS而是单次会话内多步工具调用的资源占用以及长连接对网关的长久占用。我的处理思路分三层。第一层是入口限流网关按用户维度做并发控制一个用户同时只能有一个活跃会话在跑来了新的请求直接排队或者提示用户“上一个任务还没完成”。第二层是编排层异步化Agent编排服务本身无状态所有会话状态都放Redis服务节点可以横向扩容。每次大模型调用和工具调用都走异步任务通过消息队列驱动这样编排节点不会因为某个会话长时间等待而阻塞线程。第三层是工具层连接池MCP Client到MCP Server的连接复用避免每个请求都重新握手。这一步优化收益特别大尤其当MCP Server是HTTPSSE模式时连接建立的成本不可忽视。4. MCP层深玩工具即服务连接即协议4.1 MCP到底是什么和普通API有什么区别MCP全称Model Context Protocol它是大模型应用和外部工具之间的标准化协议作用类似给AI世界统一了“插头规格”。你有一个酒店查询API想让它能被AI调用传统做法是你给API写一份OpenAPI文档再在Agent代码里写一段调用逻辑下次接天气API又得再写一段。有了MCP只要把酒店查询包装成一个MCP Server定义好工具名、参数结构、返回结构任何支持MCP的Agent客户端都能直接发现并调用它。我刚开始接触MCP时也犯嘀咕这玩意儿不就是个RPC框架吗后来深入用下来MCP真正的价值不止是远程调用它有四个很关键的能力工具发现客户端可以动态获取Server支持了哪些工具、JSON Schema参数校验工具入参出参结构严谨、标准化错误处理错误码、重试语义统一、上下文管理工具调用的中间结果能按上下文返回给模型。这四个能力合在一起相当于给“AI调用工具”这件事立了一套完整的规范而不是各家自己凑一套。4.2 一个MCP Server的落地解剖我以酒店查询MCP Server为例拆解一下怎么落地。技术栈选的Python FastAPI用官方MCP SDK搭建。首先定义工具的JSON Schemasearch_hotels工具的入参包括city、checkIn、checkOut、rooms、guests、priceRange、sortBy等字段其中city和checkIn是必填priceRange和sortBy带默认值。然后把工具实现注册到Server里内部再调用供应商的HTTP接口拉数据做字段映射、过滤排序后返回统一结构。这里有一个工程上很重要的小细节返回给大模型的结果不要带营销字段比如“好评率92%、赠送早餐券”这种模型会拿这些信息当卖点去推荐反而干扰决策。我做的结果是结构化的酒店名、地址、距离市中心距离、评分、房型、价格、剩余房间数、取消政策。模型基于这个结构化结果去做推荐再结合用户偏好生成文案比让模型自己理解一坨营销文案靠谱得多。4.3 stdio还是HTTPSSE传输方式怎么选MCP支持多种传输方式最常用的是stdio和HTTPSSE。stdio模式适合本地进程比如开发调试、本机数据处理Claude Code这类本地工具用的就是stdio。但生产环境下的Agent服务MCP Server往往部署在独立节点甚至不同机房必须走网络传输这时候就要用HTTPSSE模式。我的建议是开发期用stdio部署期用HTTPSSE但代码层把传输方式做成配置项。这样本地调试不用起服务、不用配鉴权跑起来飞快上了测试环境再切HTTP模式问题也容易定位。鉴权方面生产环境的MCP Server必须要求身份认证我用的是OAuth2 Client CredentialsAgent服务持有一个服务账号MCP Server验过token才开放工具。工具级权限也要分开同一个MCP Server里可以定义不同权限组的工具内部分级调用避免一个普通对话Agent直接调用到“取消订单”“退款”这类高权限工具。4.4 MCP Server的稳定性与降级策略MCP Server再稳也有挂的时候而且它不是直接面向用户的系统挂了用户不会立刻知道只会在Agent那边表现为“工具调用超时”或者“返回数据异常”。所以我把MCP Server的可观测性做得很重每个工具调用都打日志和trace记录入参出参、耗时、上游状态码配了Prometheus指标和Grafana看板一旦P95耗时超过阈值就告警。降级策略分三种。第一种是静默降级比如天气服务挂了Agent不告诉用户“天气服务故障”而是换用兜底数据源或直接不聊天气这个话题第二种是明确兜底比如酒店服务超时Agent主动降低实时性要求返回缓存中的酒店推荐并标注“数据仅供参考请以预订时确认为准”第三种是局部熔断连续5次调用同一个MCP Server失败后直接熔断该域工具后续请求不再调用等恢复窗口过后再试。这套策略上线后至少没再出现过因为一个供应商接口抖动导致整个Agent不可用的情况。5. 支付与订单层Agent可以提方案但钱必须由用户拍板5.1 支付链路的分工边界AI Agent做支付第一原则是Agent绝对不能擅自划款。哪怕用户说“你看着办直接下单就行”系统也不能允许Agent绕过用户确认执行扣款。这不是技术限制是产品责任的底线。所以我把支付拆成了独立服务Agent做的事情只到“生成订单草稿”为止草稿包含完整的行程、金额明细、供应商信息前端收到草稿卡片后用户点击“确认支付”才真正调起收银台。收银台的实现Web端用标准支付收银台小程序端用小程序支付。用户完成支付后支付渠道异步回调到订单服务订单服务验签、更新状态、通知Agent编排层编排层再给用户推一条“支付成功订单已确认”的消息。整个环节Agent不接触用户支付密码、不保存任何卡号信息所有敏感操作都在支付服务内部完成。5.2 订单状态机设计订单状态机是交易系统的命根子我在设计时尽量让它简单牢靠。核心状态是PENDING待支付- PAID已支付- CONFIRMED已确认- COMPLETED已完成/ REFUNDED已退款/ CANCELLED已取消。PENDING和PAID之间有一个“支付中”的过渡态因为支付结果可能延迟PAID到CONFIRMED之间则留给供应商确认环节比如酒店确认有房。状态机的每一跳都记状态变更日志并且用分布式锁保证同一个订单不会被两个并发请求同时改状态。这里有个特别容易踩的坑支付回调可能重复到达如果不做幂等一个订单会被重复入账。我处理的办法是每次支付回调先查支付流水表同一渠道流水号只允许关联一个订单重复回调直接丢弃返回成功。5.3 退款与异常场景处理旅游Agent一定会遇到退款行程改期、用户不去了、供应商超售都是常见触发点。我的处理是**退款必须人工审核*哪怕用户已经在对话里明确要求退款流程也只会走到“退款审批单”提交由人工在后台确认后走原路退回。这样做不是不相信用户而是防止大模型被恶意提示词诱导把不应该退的钱退了。异常场景里最高频的是支付超时。用户打开收银台但是没付钱订单一直挂在PENDING状态这时候要有一个定时任务扫表超过约定时间我设的是30分钟把订单状态改为CLOSED释放占用的库存。闭单时如果发现供应商那边已经锁了库存、预留了价格还要调用供应商的取消接口回滚这一步做不好很容易变成“用户没付钱但供应商扣了你的款”的坏账。6. 常见问题与排查技巧实录6.1 MCP Server响应慢Agent等不及乱答怎么办我最早遇到这个问题是天气查询MCP Server上游供应商接口偶发要8秒才返回Agent在等待窗口里没拿到结果就自作主张回了句“今天天气大概是晴天”。用户一看就发现是编的体验很差。排查路径是先看trace确认是上游慢还是MCP Server自身逻辑慢再确认超时设置我用的是大模型工具调用超时3秒、MCP连接超时2秒套上之后确实覆盖了大多数慢请求。但真正的解法是给工具调用加“基于时间的兜底策略”如果Agent发现工具调用即将超时就等待一个短buffer比如500毫秒拿到了最好拿不到就走降级回答模板“我暂时查不到实时天气建议出行前一天再确认”而不是任意编造。这个兜底模板必须和普通回答分开明确标注为“不确定信息”。6.2 大模型连续调用同一个工具产生脏数据有段时间我观察日志发现Agent在推行程时可能连续三次调用同一个查景点工具每次传入参数一模一样白烧token还可能导致数据不一致。原因是大模型的工具调用规划里没有做去重和记忆。我改了两处。第一处编排层维护一个简单的工具调用缓存表键是“工具名入参哈希”值是上次结果摘要命中缓存就直接返回不再真正调用MCP Server。第二处在System Prompt里明确写“如果同一个工具的相同参数已经在上下文中出现过不要重复调用”双管齐下之后重复调用率降了七成。6.3 支付回调丢失订单卡在PENDING支付渠道回调偶尔会因为网络问题丢了这是做支付必然面对的事。解决办法是主动查单订单服务每2分钟扫描一次超过5分钟未收到回调的PENDING订单向支付渠道发主动查单请求确认支付状态后再更新订单。查单逻辑也要有幂等锁防止查单和回调同时到达时状态错乱。这套主动查单还有一个隐藏好处能发现“用户支付成功但渠道没回调”的隐性坏账及时补单避免用户钱扣了、订单却显示超时关闭。我上线查单任务后支付对账异常率从千分之三降到万分之二以下明显改善。6.4 前端卡片渲染错乱终端类型多的时候同一个卡片JSON在不同端渲染效果不一样最常见的是Web正常、小程序样式崩了。问题出在卡片JSON里可自由文本字段太多不同团队对空值、长度超限处理不一致。我把卡片Schema改成全字段枚举固定长度限制展示标签、按钮行为都走枚举文本字段设最大长度并在溢出时省略。改完之后基本没再出过渲染错乱。另外提醒一句卡片组件做回归测试时一定要准备一份“全字段异常测试卡”缺title、缺action、超长文本、图片URL失效全测一遍。这个测试脚本建议自动化每次改渲染组件跑一次能省下不少和前端扯皮的时间。6.5 排查与避坑速查表症状可能原因排查方式与解法避坑经验Agent回答在编数据工具调用超时后模型自由发挥给工具调用加超时兜底模板结果区分“实时数据”与“不确定信息”兜底模板词要固定不要给模型发挥空间多次调用同一工具模型规划未去重加工具结果缓存按“工具名入参哈希”去重缓存的过期时间设短一点实时数据别缓存太久支付成功但订单PENDING渠道回调丢失、延迟主动查单任务超过5分钟轮询渠道确认查单和回调都要做幂等锁住订单IDMCP Server大面积慢上游API抖动熔断器连续失败后断开该域工具熔断恢复用半开状态探活不要立即全量放开长会话token爆表上下文窗口放不下历史工具结果工具结果摘要压缩只回传关键字段定期清掉对话中的tool_call中间消息小程序渲染错乱自由文本字段不受控严格JSON Schema枚举长度限制自动化异常卡测试脚本7. 一些真实体会这个项目走下来我最大的体会是AI旅游Agent的技术难点压根不在一句“你好我想去成都”能多流畅地答上来而在外围工程。前端要承受流式渲染和卡片协议Agent中枢要管理会话、规划、超时、并发MCP层要做到工具服务化和稳定可观测支付层更是一点差错都不能出。每一步拆开看都不算惊天动地但串在一起就是一个复杂度远超普通聊天机器人的系统。如果你也想做类似的项目我给三个实际的建议。第一先做窄场景打通再横向扩展别一上来就酒店、机票、签证、景点全做先把一个领域从对话到支付完整跑通再复制到其他领域。第二MCP是好东西但别过度设计工具不多的时候老老实实写一层Adapter也能跑工具到三四个以上再上MCP收益才开始显现。第三永远给Agent留人工兜底的口子支付、退款、供应商确认这些环节必须有人的介入点这不是技术保守是对用户负责。最后分享一个小技巧Agent系统调试时我习惯把每次完整会话的全链路trace导出来按时间线回放“用户说了什么-模型怎么想-调了哪个工具-拿到了什么结果-最终回了什么”。这个回放脚本救了我很多次很多看似玄学的问题回放一遍基本就定位了。希望这篇拆解能帮你少走一些我已经走过的弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进