
1. 一个AI旅游Agent到底要拆成几块先说说我为什么会去拆这个东西。去年年底接了个私活帮一家做定制游的工作室搭一套AI行程助手需求听起来不复杂用户用自然语言描述想去哪、玩几天、预算多少、带不带小孩Agent给出可执行的行程方案用户确认后直接下单支付。真动手才发现这玩意儿横跨了对话系统、工具调用协议、地图与POI数据、订单状态机、支付通道五个完全不同的技术域任何一个环节没接好用户体验就是断的。市面上讲AI Agent的文章大多停在“大模型加几个工具”的层面真到落地你会发现最难的不是让模型说话而是让它在正确的时间调用正确的工具、拿到正确的数据、再把结果安全地推进到支付环节。这篇就把我实际搭过的这套技术栈从头到尾拆一遍包括前端对话怎么设计、MCP在中间扮演什么角色、支付环节有哪些坑。适合正在做AI应用落地、或者想理解Agent工程化的朋友看不需要你懂大模型训练但需要你对前后端和支付有基本概念。我先把整体架构摆出来后面每一块再展开。整套系统分四层对话交互层前端、Agent编排层后端大脑、工具与数据层MCP协议连接的外部能力、交易履约层订单与支付。这四层之间是松耦合的好处是任何一层换实现都不影响其他层比如前端从Web换成小程序后端逻辑一行不用改。提示不要一上来就追求“全自动”。我第一版让Agent全自动下单结果测试时它给一个用户生成了三份重复订单。后来改成“Agent生成方案、用户确认、再进入支付”问题立刻消失。人在关键节点上的确认是AI产品最便宜的安全阀。2. 前端对话层别把它做成聊天框2.1 为什么纯聊天界面会失败很多人做AI旅游Agent第一反应是套一个聊天窗口用户打字、AI回复。我一开始也这么干上线三天数据就很难看用户平均对话轮次只有2.3轮就流失了。后来复盘发现旅游决策是个信息密集的场景用户要看行程、看价格、看地图、看酒店图片纯文字流根本承载不了。旅游Agent的前端本质不是聊天工具而是一个会对话的行程编辑器。聊天只是输入方式之一输出必须是结构化的卡片、时间轴、地图。这个认知转变之后我把前端重构成了三个区域左侧对话流、中间行程时间轴、右侧地图与详情面板。用户说“第三天想轻松点”Agent不是回一段文字而是直接调整中间时间轴的安排右侧地图同步更新路线。2.2 对话状态怎么管前端对话最难的是状态管理。用户可能说“把刚才那个酒店换成便宜点的”这里的“刚才那个”需要前端能追溯到上下文。我的做法是在前端维护一个会话上下文对象结构大概是这样const sessionContext { currentItinerary: { days: [...], totalBudget: 8000 }, lastMentionedEntity: { type: hotel, id: h_1024, name: 某某度假酒店 }, userPreferences: { pace: relaxed, childFriendly: true }, pendingAction: null }每次用户发消息这个对象随请求一起发给后端。后端Agent处理完返回的不只是文本还有结构化指令比如{ action: update_itinerary, payload: {...} }。前端根据指令类型决定是渲染卡片还是更新时间轴。这样前后端职责清晰后端负责决策前端负责呈现。2.3 流式输出的处理细节对话体验里流式输出是刚需。用户等3秒看到完整回复和边生成边看到文字蹦出来感受完全不同。但旅游场景的流式输出有个特殊问题Agent可能在生成过程中调用工具比如查航班这时候流不能断得让用户知道“正在查询”。我的处理方式是分阶段流式先流式输出一段“思考中”的提示文本工具调用完成后再流式输出最终结果。前端用SSE接收消息体里带type字段区分是thinking还是content。这里有个坑SSE连接在移动网络下容易断必须做自动重连加断点续传否则用户切个后台回来对话就卡死了。注意流式输出到文件或做持久化时要按消息块追加写入不要等全部生成完再写。我见过有团队用一次性写入结果长对话生成到一半服务重启整段内容全丢。3. MCP层Agent的手和脚3.1 MCP到底是什么用大白话讲MCP全称Model Context Protocol你可以把它理解成AI和外部工具之间的USB接口。在没有MCP之前每接一个工具查天气、查航班、调支付都要写一套专门的适配代码工具一多就是灾难。MCP定义了一套标准协议工具方按协议暴露自己的能力Agent按协议调用双方不用互相认识。放到旅游Agent里MCP连接的东西包括地图服务查POI、算路线、航班与酒店库存、天气、汇率、以及最后的支付通道。每个能力都是一个MCP ServerAgent作为Client去调用。这样我换一家地图供应商只要新的供应商实现了MCP ServerAgent侧代码完全不用动。3.2 MCP的工具描述怎么写才靠谱MCP的核心是工具描述Tool Schema。描述写得好不好直接决定Agent会不会用、用得对不对。我踩过的坑是一开始把工具描述写得太技术化比如“查询指定坐标半径内的POI”结果Agent经常传错参数。后来改成面向意图的描述{ name: search_attractions, description: 根据城市和用户偏好搜索景点适合用户说想去某地玩时调用, parameters: { city: 城市名称中文, preference: 偏好标签如亲子、文艺、自然风光, max_results: 返回数量默认10 } }关键点是description里要写什么时候调用而不只是这个工具做什么。大模型是靠语义匹配来决定调不调用的你把使用场景写清楚它的调用准确率会明显提升。实测下来加了场景描述之后工具误调用率从大概三成降到了一成以内。3.3 多工具编排的顺序问题旅游Agent经常需要连续调用多个工具。比如用户说“帮我安排一个三亚五日游”Agent要先查景点、再查酒店、再算路线、最后估预算。这些工具有依赖关系算路线依赖景点列表估预算依赖酒店和路线。我的做法是在Agent编排层做一个轻量的任务规划不是让模型自由发挥而是给它一个隐式的流程模板先收集信息类工具再计算类工具最后生成类工具。MCP本身不负责编排编排是Agent的职责。这里要控制好并发查景点和查酒店可以并行但算路线必须等前两个都回来。提示工具调用一定要设超时和降级。地图服务偶尔会抽风超时后不要让整个对话卡住返回一个“路线暂时无法计算先按直线距离估算”的降级结果用户体验会好很多。4. 支付层最不能出错的一环4.1 支付在Agent里的特殊位置支付和前面所有环节都不一样它是不可逆的。对话错了可以重来工具调错了可以重试钱付错了就是事故。所以我在架构上做了一个硬隔离Agent可以生成订单、可以发起支付请求但支付确认必须由用户在前端显式完成Agent不能代替用户点确认。具体流程是Agent生成订单草稿前端渲染成订单卡片用户点击“去支付”跳转到支付页面支付完成后回调通知后端后端更新订单状态再通知Agent。整个链路里Agent只参与前半段后半段是标准电商流程。4.2 订单状态机怎么设计订单状态机是支付环节的骨架。我用的状态流转是这样的状态含义可流转到draft草稿Agent生成pendingpending待支付paid / cancelled / expiredpaid已支付refunding / completedcompleted已完成refundingrefunding退款中refundedrefunded已退款终态cancelled已取消终态expired已过期终态关键点是幂等。支付回调可能重复到达必须用订单号加支付流水号做唯一约束重复回调直接返回成功但不重复处理。我见过有系统因为没做幂等用户付一次钱生成了两笔订单退款时对不上账。4.3 支付通道对接的实操细节对接支付通道时有几个细节特别容易翻车。第一是金额单位有的通道用分有的用元一定要在代码里统一成最小单位分展示时再转换。第二是签名验证回调必须验签否则有人伪造回调就能白嫖。第三是异步通知与同步跳转的关系用户支付成功后浏览器跳转回来不代表支付一定成功必须以异步通知为准。def handle_payment_callback(order_no, trade_no, amount, sign): if not verify_sign(sign): return {code: FAIL, msg: 签名错误} order get_order(order_no) if order.status paid: return {code: SUCCESS, msg: 重复通知} if order.amount ! amount: return {code: FAIL, msg: 金额不符} update_order_status(order_no, paid, trade_no) return {code: SUCCESS, msg: OK}这段逻辑看着简单但每一行都是血泪。金额校验、状态校验、验签一个都不能少。4.4 分布式场景下的一致性如果订单和支付是不同服务就涉及分布式事务。我的建议是别上重型分布式事务框架用本地消息表加定时补偿就够了。订单服务在本地事务里写订单和一条待发送消息然后异步发消息给支付服务支付服务处理完回执订单服务更新消息状态。如果消息发送失败定时任务扫描待发送消息重试。这套方案简单、可靠、好排查比两阶段提交实用得多。5. 把四层串起来一次完整对话的全链路5.1 从用户输入到订单生成我拿一个真实case走一遍。用户输入“下个月带爸妈去杭州玩三天预算一万节奏慢一点。”第一步前端把这句话加上会话上下文发给后端。第二步Agent解析出意图目的地杭州、时长三天、同行老人、预算一万、节奏慢。第三步Agent通过MCP调用景点搜索杭州、适合老人、酒店搜索杭州、无障碍、安静、天气查询下个月杭州。第四步Agent根据返回结果生成行程草稿调用路线计算工具算每天的通勤时间。第五步Agent把行程和预算估算返回前端前端渲染成时间轴加订单卡片。整个过程大概3到5秒其中工具调用占了大部分时间。所以前端一定要有分阶段的加载反馈让用户知道系统在干活而不是卡住了。5.2 从订单确认到支付完成用户看了行程说“酒店换成西湖边的”。Agent重新调用酒店搜索更新订单草稿。用户满意后点“确认下单”前端把订单草稿提交给订单服务订单服务创建正式订单状态为pending返回支付参数。前端调起支付用户完成支付支付通道异步通知订单服务订单服务更新状态为paid同时通过消息通知AgentAgent给用户发一条“预订成功”的确认消息。这条链路里Agent和订单服务之间是异步解耦的。Agent不关心支付怎么实现订单服务不关心行程怎么生成两边通过消息通信。这样任何一边出问题另一边不受影响。5.3 异常情况的兜底真实系统里异常才是常态。我列几个高频异常和兜底策略异常兜底策略工具调用超时返回降级结果标注“估算值”支付回调丢失定时任务主动查询支付状态订单创建成功但支付失败订单保留30分钟超时自动取消Agent生成内容违规前置内容审核拦截后转人工用户重复提交订单前端防抖加后端幂等键这些兜底不是锦上添花是系统能不能上线的分水岭。我第一版没做支付状态主动查询结果有一次回调丢了用户付了钱订单还是pending客服电话直接打爆。6. 实操中踩过的坑和排查技巧6.1 工具调用相关的坑最常见的坑是工具描述和实际行为不一致。比如工具描述说返回10条结果实际返回了50条Agent的上下文直接被撑爆。解决办法是在MCP Server侧做硬限制返回前截断不要指望Agent自己处理。另一个坑是参数类型不匹配。Agent可能把数字传成字符串把数组传成逗号分隔的字符串。MCP Server侧一定要做参数校验和类型转换不要直接信任输入。我现在的做法是每个工具入口都加一层schema校验不合法直接返回错误让Agent重试。6.2 支付相关的坑支付金额的坑前面说了再补充一个退款的坑。旅游场景退款很常见但退款不是简单调个接口。要区分全额退、部分退要处理已消费和未消费的部分还要考虑退款手续费。我的建议是退款逻辑单独做一个服务和支付解耦退款状态也走独立的状态机。还有一个坑是支付通道的限额。不同通道单笔限额不同大额订单可能要拆分或走特殊通道。这个必须在订单创建时就校验不能等用户点了支付才报错。6.3 排查技巧速查表现象可能原因排查方向Agent不调用工具工具描述不清晰检查description是否写了使用场景工具调用参数错误schema定义模糊检查参数类型和示例支付回调不生效验签失败或地址不通查回调日志和网络连通性订单状态卡住消息丢失或补偿未触发查消息表和定时任务日志对话上下文丢失前端未传或后端未存查请求体和会话存储提示所有涉及钱的接口日志一定要打全包括请求参数、返回结果、时间戳。出问题时这些日志就是救命稻草。我现在的习惯是支付相关操作全部单独打一个日志文件方便快速定位。7. 这套架构还能怎么扩展跑通基础链路之后我陆续加了几个扩展。第一个是多轮修改的版本管理用户每次修改行程都存一个版本可以回滚到任意版本。第二个是行程分享生成一个只读链接同行的人可以查看和评论。第三个是预算实时追踪用户每改一次行程预算条实时更新超支时高亮提醒。再往深了做可以接实时库存和动态定价让Agent根据实时价格调整推荐。也可以做多Agent协作一个负责行程、一个负责预算、一个负责预订互相协商。但这些都属于锦上添花基础链路不稳加再多功能都是空中楼阁。我个人在实际操作中的体会是AI旅游Agent的难点从来不在模型本身而在工程化的严谨程度。模型可以换、可以调但订单状态机、支付幂等、工具超时这些基础设施一旦设计错了后面改起来伤筋动骨。所以如果你正准备做类似的东西我的建议是先把支付和订单这条链路用最笨的办法跑通再往上叠AI能力顺序反了会很痛苦。