旅行 Agent 实战:先解决酒店数据失真
作者记录了用 30 天构建旅行规划 Agent 的过程,主要难点是让酒店结果真实可核验,而非优化表达。实现采用带步骤预算和状态对象的工具调用循环,并要求无法验证的信息明确说明。
AI 深度解读
一个 AI 旅行规划器,能写出漂亮的行程,却推荐了早已关门的酒店。作者用 30 个晚上的业余时间,把项目交给 40 名陌生用户试用,最大的障碍不是模型不会规划,而是它根本没有可靠的酒店库存。
这次实验的结论很具体:涉及实时价格和可预订状态,提示词无法替代真实数据源。 接入库存之后,提示词变短,适配代码减少,用户也较少纠正酒店信息。但这份记录仍是小规模项目的经验,不能直接当成某个 MCP 服务的效果证明。
为什么能生成行程,却找不到真实酒店
作者给自己定了三条规则:不用框架,方便定位故障;不能编造数据,无法核实就明确告知;首版工具不超过 6 个,减少模型选错工具的机会。后来他承认,工具数量的限制过于乐观,而不编造数据才是整个项目最难兑现的要求。
前 7 天,作者先搭建受约束的执行循环:解析意图、调用工具、检查结果,再决定继续还是回答。下面这段代码解决的是执行次数和状态管理问题:
state = {"intent": None, "city": None, "dates": None, "hotels": []}
for step in range(8):
action = model.next_action(state)
if action.type == "final":
return action.text
call = tools[action.name]
result = call(**action.args)
state = reduce(state, action, result)
要看的是 range(8) 和 reduce:前者把执行限制在 8 步内,后者把结果写入结构化状态。调用哪个工具仍由模型选择,所以这里的「确定性」更准确地说是流程受控,并非每次执行路径完全相同。
第 3 天,系统就能处理「3 月去 Lisbon 住四晚,别太贵,靠近河边」这样的请求。但第 11 天,用户发现一间标价 148 EUR 的推荐酒店,预订链接返回 404,而且酒店早在 2023 年就关门了。
作者人工核查此前一周的 120 条推荐,发现 26% 对应已关闭、改名或不存在的酒店;价格抽查只有约三分之一对得上。结构化状态让他能够数清返回了什么,却不能保证那些内容是真的。
问题出在数据来源。酒店库存每天变化,模型能写出可信的介绍,不代表它掌握了当天的营业状态和房价。
三次修提示词,为什么都没解决问题
第 15 至 19 天,作者尝试了三种补救。
先加强指令,要求只推荐确定存在的酒店、禁止编造价格。错误酒店比例从 26% 降到 22%,有改善,但仍然无法交付。再要求引用来源,结果部分引用页面根本没提到对应酒店。最后换更大的模型,文字更好,失败率却没有改善,还消耗了一天。
这三次尝试共享一个前提:模型没有事实,但可以通过更谨慎的回答接近事实。实验没有支持这个前提。
引用也不自动构成核验。页面是否存在、是否真的支持酒店名称和价格,都需要检查。让模型给猜测附上出处,只增加了一个看起来可靠的外壳。
作者后来接入 RollingGo Hotel MCP。此前他对旅行 API 的印象停留在 2022 年:认证调试、XML 和不同供应商的适配代码,可能耗掉三周。这次接入用了一个下午,但这个耗时只是他的项目记录。
服务通过单一端点提供 streamable HTTP,配置如下:
{
"mcpServers": {
"rollinggo-hotel": {
"type": "http",
"url": "https://mcp.rollinggo.ai/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
这里要留意 Authorization:仍然需要 API key。按作者当时的描述,API key 层免费且没有调用次数上限;原文没有交代这些条件的长期保证。
真正替换猜测层的是三个工具:searchHotels 按地点、日期、星级和标签搜索;getHotelDetail 获取实时房型、每晚价格和取消规则;getHotelSearchTags 返回可用标签,避免模型自行发明 rooftop pool 这样的筛选项。
服务端接管参数定义后,提示词从 1,100 词缩到 340 词。作者删除了 1,900 行缓存、价格格式化、重试和模糊名称匹配代码,替换实现为 240 行,主要处理错误和展示货币。这不是 MCP 天然能压缩代码,而是原来由应用维护的职责转移到了供应方。
接上库存后,为什么还要重新查价
作者选择的是 redirect mode:每间酒店返回一个 bookingUrl,用户跳到 RollingGo 完成交易。因此,这个项目可以省去支付网关、取消流程和 PCI 相关工作,适合 30 天实验的边界。
这不等于已经具备完整的对话式预订能力。API key 层覆盖搜索和详情;OAuth 层增加订单查询和价格确认,还需要商务接入。想在聊天里展示订单状态,就要继续补这部分链路。
第 25 至 30 天,作者向 40 名用户开放测试,记录了 96% 的价格核查通过率,以及每次会话 0.3 次纠正。剩下 4% 的价格仍会漂移,因为用户点击时,报价可能已经改变。他因此在最终展示价格前增加复查,并提示以预订时价格为准。
实时数据也有保鲜期。
另一个坑是价格口径。API 返回币种和每晚价格,但税费处理会因酒店、国家而异。作者最终在所有视图都保留「税前每晚价格」标签,避免用户把不同口径的数字放在一起比较。
40 名用户中,11 人再次使用。这个结果说明项目有一些继续使用的价值,却还不足以判断长期留存。
我的判断
我认同作者把数据审计放在 UI 前面。那张人工核查 120 条推荐的表,直接揭示了产品是否可信;页面再完整,也很难弥补四分之一推荐存在事实问题。
但对供应商的判断需要保留距离。文中列出 MIT 许可、超过 200 万处物业、覆盖 200+ 国家、500+ 供应商和 110,000 份直签合同。这些属于作者转述的供应方信息,原文没有提供足够证据让读者复核全部规模。一次真实响应,也无法验证整个库存总量。
作者还明确交代,该服务只有 86 个 GitHub stars,没有第三方综述或基准;作为对照,Context7 有 63,000 个 stars。星标不能证明库存质量,项目年轻也不能直接证明不可靠,只能说明外部验证有限。
效果数据同样有边界:96% 没有交代样本量、抽样方法和核查间隔,0.3 次纠正也缺少可比的前期基线。更大模型的具体型号没有给出,自由规划导致步数翻倍、答案变差,也没有详细对照数据。这些都应当视为作者的经验记录。
更值得借鉴的是数据边界和验证方法,而不是直接照选供应商。 自建 API 适配层、接供应商 SDK 或使用 MCP,都可以获得真实库存;区别在于参数、重试和维护责任由谁承担。接入下午就完成的前提,是作者接受了跳转交易,也接受了供应方承接的大量职责。
能拿来做什么
- 做依赖实时信息的 AI 功能时,先列清哪些字段必须来自工具,哪些内容允许模型组织表达。酒店名称、房价和预订链接应保留来源,缺失时明确展示无法核实。
- 在 Node 编排层设置步数预算,把工具结果存成结构化状态,并保留可供抽查的记录。先审计一批输出,再投入复杂界面。
- 前端展示价格时,同时呈现币种、每晚口径和税费说明,在最终确认前重新查价。不要把搜索时取得的数字一直当成有效报价。
- 根据交易范围选择接入方式。只推荐并跳转,可以评估 redirect mode;需要在对话中查询订单、确认价格,就要把 OAuth 和商务接入成本计入计划。