Tour Pass 的前几篇文章写的是一个 C++17 城市自由行行程规划服务:POI 图、Dijkstra/A*、Beam Search、BM25、候选对比、真实 POI 流水线和 Docker 演示。

6 月这轮更新之后,它的项目口径已经变了。现在的 Tour Pass 更像一个 C++ + Python 双引擎的 AI 行程平台:C++ 继续负责可解释、可验证的核心规划;Python Agent 负责自然语言理解、RAG、酒店锚点、每日规划编排和总结;React 编辑器负责把生成结果变成可编辑、可分享、可导出的行程。

线上演示地址已经放在仓库 README 里:tour-pass.onrender.com

C++ 规划引擎仍然是底座

我没有把路线决策完全交给 LLM。C++ 后端仍然负责核心结构化能力:

  • POI 图建模。
  • Dijkstra/A* 最短路。
  • Beam Search 时间槽规划。
  • 5 种策略候选。
  • Pareto 非支配排序。
  • 严格时间窗校验。
  • BM25 文本检索。
  • /trip/jobs 异步任务。
  • /metrics 运行时指标。

新增的一个关键抽象是 TravelTimeProvider。它让通勤时间可以在本地 edges.json 和高德实时路线 API 之间切换。这样 Tour Pass 不再只能讲“本地样例边”,也能说明外部地图数据怎么接入;但我仍然会区分:接了高德路线能力,不等于已经拥有生产级实时交通规划。

后端还补了用户注册/登录、邮箱验证码、JWT 鉴权、角色权限和查询配额。游客、普通用户和管理员能走不同访问边界,这让它更像一个可部署服务,而不是纯算法接口。

Python Agent 负责自然语言规划

新的 Agent 服务跑在 8090 端口,基于 LangGraph + LLM。

它的主链路大概是:

用户自然语言 -> 意图抽取 -> RAG 上下文检索 -> POI/酒店搜索
-> 酒店锚点选择 -> 每日规划 -> Beam Search 路线优化 -> 自然语言生成

这层不是为了替代 C++ 规划器,而是把“用户想怎么说”翻译成“规划器能稳定执行的结构化任务”。比如用户说“我想去北京玩 3 天,喜欢历史文化,不想太累”,Agent 负责解析城市、天数、偏好和节奏,再把候选 POI、酒店锚点和路线优化交给确定性链路处理。

Agent 里还有几个工程化点:

  • TF-IDF 轻量 RAG,避免引入重型 ML 依赖。
  • 10+ 维度评分,覆盖兴趣、策略、必去、通勤、类型多样性、区域多样性和时间适配。
  • DBSCAN 地理聚类,让每日路线空间上更连贯。
  • 推荐语角度轮换,减少“某某是热门景点,建议游览 90 分钟”的模板感。

这套结构的好处是,LLM 负责理解和表达,算法负责约束和可解释性。它不是“模型直接编路线”。

数据规模从长沙样例扩到 21 城

早期 Tour Pass 的可复现样例是 25 POI / 46 edges 的长沙数据。那适合讲算法闭环,但不够支撑“平台”口径。

现在 README 里的主口径是 21 个城市、15000+ POI,每个城市 1000-2000 条通勤边,高德真实路线占比 80%+。每个城市还有 guidebook.json,记录交通、美食、住宿和注意事项。

推荐语也做过专项清洗和重生成:模板化比例从 76.6% 降到 0%,93.5% 包含“景点介绍 + 实用建议”,平均约 37 字。

这里仍然要说清楚:这些数据能证明项目有多城市数据工程和质量门禁,不等于有真实用户行为学习、实时路况 SLA 或商业旅行平台的数据厚度。

React 编辑器把路线变成可操作对象

Tour Pass 以前的 Web 演示台主要是展示候选方案、时间线、算法解释和工具箱。最新版本新增了 React 行程编辑器。

编辑器覆盖了更完整的产品路径:

  • Wizard:选择城市、设置天数、选择酒店、细分行程段、生成行程、预览确认。
  • 拖拽排序:基于 dnd-kit 支持景点重排和跨天移动。
  • 高德地图:路线渲染、起终点标记、酒店地图选点和 POI 悬浮高亮。
  • Command 模式:添加、删除、重排、跨天移动、时间修改都有撤销/重做。
  • 协作管理:多人行程协作、冲突检测和权限管理。
  • 持久化与导出:LocalStorage 自动保存,支持 JSON 导入导出和 PDF 导出。
  • PWA 和移动端适配。

这部分让 Tour Pass 从“算法返回一段 JSON”变成“用户可以继续编辑的行程工作台”。对项目表达来说,这是很大的变化。

线上部署暴露了真正的问题

这次更新里我觉得最有价值的,不只是上线了 Render,而是线上部署暴露了 Agent 502 问题。

当时现象是:C++ 主服务正常,但 /agent/health/agent/plan 返回 502,响应体类似 Agent no response。CI 容器日志又显示 FastAPI Agent 已经启动并监听 127.0.0.1:8090

最后定位到两个问题:

第一,Linux/Render 下 C++ 到 Python Agent 的 /agent/* 反向代理走了手写 raw socket 读取,可靠性不够。修复后改为复用项目已有 httplib::Client,并补齐 query string 转发;Agent 不可达时返回结构化 AGENT_PROXY_ERROR 和底层错误原因。

第二,Agent 首个规划请求原来可能一次性加载 21 个城市的 RAG/攻略数据,对 Render 免费实例内存压力太大。修复后改为按当前请求城市懒加载 RAG:请求北京不会顺带索引上海。

这件事比“本地跑通”更能说明工程成熟度。部署会逼出本地看不到的进程通信、内存峰值、健康检查和冒烟测试盲区。

Docker 和 smoke 门禁也升级了

现在 Docker 镜像是多阶段构建,包含 C++ 后端和 Python Agent 服务。/agent/health 不再只是非致命警告,而是容器 smoke 的硬门禁。以后如果 C++ 主服务能启动、但 Agent 代理不可达,CI 会失败。

这比以前更符合双引擎项目的真实边界:只测 /health 不能证明 AI 行程规划能跑,必须把 C++ 到 Python 的代理链路也纳入验证。

我现在会怎么讲 Tour Pass

现在介绍 Tour Pass,我会把它拆成三层:

第一层是 C++ 确定性规划引擎。它负责图、时间窗、候选策略、Pareto 排序、检索、缓存、异步任务和指标。

第二层是 Python Agent 编排层。它负责自然语言、RAG、酒店锚点、地理聚类、多维评分和生成解释,但不绕开结构化规划约束。

第三层是 React 编辑器。它把规划结果变成可拖拽、可撤销、可协作、可导出、可在地图上检查的行程对象。

它仍然不是生产级旅行平台,也不该把 Render 免费实例演示包装成商业 SLA。但相比最初的 C++ 算法服务,它已经更接近一个完整作品:有算法底座,有 Agent 编排,有多城市数据,有前端编辑器,有部署和真实故障修复记录。