AI 大模型进入旅游行业后,最早一批产品做的是“攻略生成器”:用户说一句话,模型回一段看起来很完整的行程文字。飞猪帮帮这类新一代旅行 AI 上线后,产品定位明显变了。从公开产品形态看,它要解决的不再是“生成一段内容”,而是让用户“一句话就出发”。这意味着系统既要完成行程规划,还要接着处理门票、酒店、航班、用车、提醒这些具体服务动作。
“能规划更能办事”听起来只是一句产品口号,落到技术层面却是一整套工程改造。规划需要的是信息组织和排序能力,办事需要的是工具调用、状态管理、权限控制、交易安全和异常恢复能力。两者缺一不可。这篇文章以旅行 AI Agent 为技术主线,结合飞猪帮帮这类产品形态,拆解一个可落地的系统应该怎么设计:先说清“能办事”和“会聊天”的差别,再按意图识别、行程规划、工具调用、状态管理、线上评估、排错与上线准备六个层次展开。适合正在做 AI 应用、智能体、本地生活或旅行产品方向的开发者阅读。
1. 先理解“能规划更能办事”对技术架构意味着什么
1.1 从“生成内容”到“执行任务”
传统大模型对话应用的核心链路是“输入文本 -> 模型生成文本 -> 输出文本”。用户问“杭州三天怎么玩”,模型返回一段 Markdown 行程。这个过程对知识要求高,但对系统能力要求低,因为模型只需要组织文字,不需要改变任何现实世界状态。
旅行 AI Agent 的差别在于,它需要改变现实世界状态。用户说“帮我预订下周三杭州到北京的往返高铁”,系统不能只回复“好的,建议你乘坐 9 点那班”,而是必须完成查询余票、创建订单、发起支付、确认出票这一串动作。这种系统属于任务执行型 Agent,核心特征是:
- 输出结构:面向机器可执行的结构化动作,而不是面向人阅读的自然语言段落。
- 副作用:调用真实业务接口会产生订单、锁定库存、扣款,必须有权限和确认机制。
- 可验证:任务是否完成,不能靠模型自我判断,要靠业务系统回执。
- 状态性:规划、确认、支付、售后这些阶段之间需要连续状态,不是一次问答就结束。
“能规划更能办事”实际是两套能力的组合。规划负责把用户意图转成可执行的方案,办事负责把方案转成真实的业务动作。如果只做规划,系统本质上还是内容生成;如果规划之后接不上办事,用户仍然要在多个 App 之间来回切换。
1.2 旅行场景为什么是 AI Agent 的典型战场
旅行是少数能把“多约束规划”和“多系统执行”耦合得特别紧的场景。用户需求天然包含时间、地点、人数、预算、偏好、体力等多种约束。
一个典型需求可能是:“下周三带爸妈去北京玩三天,预算八千,不想太累。”这条需求里至少包含:
- 出发日期:下周三,需要计算具体日期。
- 同行人员:父母,需要评估行程强度。
- 目的地:北京。
- 游玩时长:三天。
- 预算:八千。
- 节奏偏好:不想太累,需要降低每日景点数量和移动距离。
这些约束之间存在冲突。例如预算有限、时间又短、还要住得离景点近,系统必须做权衡。同时,真实服务还依赖实时库存和价格:周一闭馆的博物馆、售罄的景区票、涨价的酒店、临时取消的航班,都会让一个“看起来合理”的行程变成不可执行。
这也是为什么旅行场景适合 AI Agent 落地。因为它的需求复杂度足够高,调用工具种类足够多,且最终效果能被交易结果验证。用户成功出票、入住、入园,就是一次明确的完成信号。
1.3 一句话变成完整动作链路
从技术视角看,“一句话就出发”背后是这样一条链路:
用户输入 -> 意图识别与槽位抽取 -> 约束校验与信息补齐 -> 行程规划引擎生成多套候选方案 -> 方案展示并等待用户确认 -> 工具调度层拆解执行步骤 -> 查询库存/价格/余票 -> 创建订单并锁定资源 -> 发起支付或引导用户确认 -> 异步任务更新订单状态 -> 推送预订结果与出行提醒每一环都有可能失败。用户输入缺少日期,规划引擎无法生成方案;商家库存不足,订单无法创建;支付超时,资源被释放;用户中途修改人数,整个方案需要重新计算。生产环境里真正的难点不是在第一步生成一段漂亮话术,而是让后面的每一步都可控、可重试、可解释。
2. 旅行 AI Agent 的整体架构与核心模块
2.1 分层架构:接入、编排、工具、数据
一个面向生产环境的旅行 Agent,不建议把所有逻辑写在一个 Prompt 里。更稳妥的做法是分层设计,各层职责独立。
+------------------------------------------+ | 接入层:App / 小程序 / Web / 语音助手 | +------------------------------------------+ | 编排层:意图识别、槽位管理、Agent 调度 | +------------------------------------------+ | 工具层:航班、酒店、门票、列车、用车、攻略 | +------------------------------------------+ | 数据层:用户画像、知识库、订单状态、日志 | +------------------------------------------+接入层负责对话交互和渠道适配。编排层是 Agent 的大脑,决定当前该调用哪个模型、该问用户什么问题、该执行哪个工具。工具层把业务系统包装成模型可调用的函数或 API。数据层为规划提供攻略知识,为状态管理提供订单存储,为排查问题提供日志。
分层的好处是隔离复杂度。工具层接口发生变化时,编排层不需要改;新增一个“景区导览”服务时,只需要注册一个新工具;接一个语音入口时,接入层扩展即可。
2.2 核心模块不能只靠一个 LLM
实践中,一个完整的旅行 Agent 至少要包含这些模块:
- 意图识别与槽位抽取:判断用户要规划、查询、预订还是改签,并提取结构化参数。
- 对话管理:维护多轮上下文,决定是否需要主动反问。
- 规划引擎:基于约束生成行程候选,并对候选做排序。
- 工具调用:把模型输出的动作转成真实 API 请求。
- 状态管理:保存任务、订单、支付、售后的流转状态。
- 安全策略:确认、鉴权、幂等、风控和敏感信息过滤。
- 评价模块:记录任务完成情况,用于离线评估和线上监控。
- 人工兜底:Agent 无法处理时,无缝转接人工客服。
这几块不是独立系统。规划引擎产生的结果要交给工具层去验证,工具层的返回要回填到对话上下文中,状态管理又要记录工具层创建的订单。因此,模块之间必须有清晰的协议。
2.3 技术选型的核心判断
| 模块 | 常用技术思路 | 生产环境关注点 |
|---|---|---|
| 大模型 | 支持 Function Calling 的对话模型 | 输出格式稳定性、延迟、成本、上下文长度 |
| Agent 编排 | 当前常见工作流框架,通常基于图或状态机 | 重试策略、分支条件、可观测性 |
| 知识库 | 向量数据库或检索服务 | 攻略数据时效、更新频率、检索准确率 |
| 工具层 | 统一 API 网关,将业务接口包装为函数 | 协议一致性、鉴权、超时、限流 |
| 状态存储 | 关系型数据库或带事务的 KV 存储 | 状态流转一致性、幂等控制 |
| 消息队列 | 异步处理预订、支付回执、通知 | 消息不丢、不重、可追溯 |
选型时最容易犯的错误是追求“模型能力一步到位”。实际上,工具层协议的稳定性比模型聪明程度更重要。模型可以换,工具协议不可轻易变。
2.4 为什么需要独立规划引擎,而不是纯提示词
旅行行程本质是一个带约束的组合优化问题。用户希望三天玩得轻松,又要覆盖标志性景点,还要考虑景点之间通勤时间、营业时间、预约政策。这些问题由 LLM 直接生成文本,结果可能流畅但不可验证。
独立规划引擎负责把“行程生成”从“语言生成”中分离出来。先由模型做意图理解和偏好解析,再由规划引擎用规则、搜索或约束求解生成结构化方案,最后由模型把方案转成用户能看懂的自然语言。这样即使模型替换,行程质量仍然有规则层兜底。
3. 从“一句话”到结构化输入:意图识别与槽位抽取
3.1 一个请求要怎么“拆”
先看这条用户输入:
“下周三带爸妈从杭州去北京,玩三天,预算八千,不想太累。”
模型第一层输出应该是意图和槽位,而不是直接写行程。合理的解析结果类似:
{ "intent": "create_itinerary", "slots": { "departure_city": "杭州", "destination_city": "北京", "people": [ {"type": "adult", "count": 2, "relation": "parent"}, {"type": "self", "count": 1} ], "start_date": "2025-07-16", "duration_days": 3, "budget_total": 8000, "pace": "relaxed", "preferences": ["历史", "美食", "少走路"] } }这里有两个细节值得注意。第一,start_date不能由模型随便填,最好由规则层根据“下周三”计算,避免模型算错日期。第二,同行人员不能只记数字,要记录年龄和关系,因为带老人和带朋友出游的节奏完全不同。
3.2 槽位抽取必须带校验
生产系统中,模型抽取结果必须通过结构校验。可以用 JSON Schema 或 Pydantic 这类工具做约束。
from pydantic import BaseModel, Field, ValidationError class Traveler(BaseModel): type: str = Field(..., pattern="^(adult|child|senior)$") count: int = Field(..., ge=1, le=20) class ItineraryRequest(BaseModel): intent: str departure_city: str destination_city: str travelers: list[Traveler] start_date: str duration_days: int = Field(..., ge=1, le=30) budget_total: float = Field(..., gt=0) pace: str = Field("normal", pattern="^(relaxed|normal|intensive)$") try: req = ItineraryRequest(**parsed_from_llm) except ValidationError as e: # 进入提问澄清流程,而不是直接发下游服务 print(e.errors())校验拦截的不只是错误参数,还能避免模型幻觉产生的非法值。例如天数取到 999、人数填成负数,这些数据如果不拦截,下游预订接口很可能直接报错,甚至造成错误订单。
3.3 信息缺失时,优先问哪个问题
用户一开始不会提供全部信息。系统必须决定反问顺序。推荐的优先级是:
- 影响资源查