做 MindTrip 之前,我对 RAG 的理解其实很朴素:
用户问问题 → 向量数据库找几段资料 → 塞给大模型 → 大模型生成答案。
这个流程做知识库问答确实能跑。
但当我真正开始做一个旅行规划系统时,很快发现事情完全不是这样。
用户不会只问:
杭州有什么好玩的?
更多时候,他们的问题是:
我从宁波出发,准备去万宁玩 3 天,两个人,预算 5000 左右,不想每天赶路,希望安排海边和冲浪,其中一天最好轻松一点。
这已经不是普通问答了。
它同时包含:
- 城市约束;
- 时间约束;
- 空间约束;
- 预算约束;
- 用户偏好;
- 景点之间的距离;
- 每个景点的游玩时长;
- 行程节奏;
- 多轮修改。
所以 MindTrip 最后逐渐从一个简单的 RAG Demo,变成了一个:
RAG + 约束过滤 + 行程规划 + 多轮交互 + 本地大模型推理系统。
这个系列共四篇文章,记录整个架构演进以及其中几个让我印象非常深的坑。
一、MindTrip 到底想解决什么?
MindTrip 的目标其实不是”推荐景点”。
推荐景点很简单。
真正难的是:
生成一个用户第二天真的敢照着走的旅行计划。
比如用户说:
万宁三天,两个人,喜欢海边,不想太累。
一个纯 LLM 很容易生成:
第一天:
- 石梅湾
- 日月湾
- 加井岛
- 神州半岛
第二天:
- 大洲岛
- 山钦湾
- 巴厘村
- 兴隆植物园
看起来内容丰富。
但真正打开地图就可能发现:
这不是旅游规划,这是拉练。
所以我给 MindTrip 定的核心目标是:
- 尽量基于真实资料生成;
- 降低景点和信息幻觉;
- 不只做语义相关性,还要考虑时间和空间;
- 用户修改需求时,不必整份行程重新生成;
- 最终结果需要足够结构化,前端才能继续编辑。
二、数据层:先别急着上”大模型”
MindTrip 使用的数据主要包括:
- 352 个城市的景点数据;
- 47,124 条旅游问答数据;
- 景点介绍;
- 城市信息;
- 标签;
- 经纬度;
- 推荐游玩时长;
- 来源 URL 等结构化字段。
最开始我犯的第一个错误,就是:
把所有文本都当普通文档切 Chunk。
比如一条景点数据:
石梅湾位于海南万宁市……
开放时间……
推荐游玩时长……
适合情侣……
附近景点……
我最开始直接按照固定 Token 数切。
结果可能变成:
Chunk 1:
石梅湾位于海南万宁市……
Chunk 2:
推荐游玩时间为半天,适合情侣和亲子……
Chunk 3:
交通方式……
问题来了。
用户搜索:
万宁适合情侣去的海边
召回 Chunk 2。
但是 Chunk 2 里面甚至不一定还有”石梅湾”三个字。
对于普通知识库,这不算大问题。
对于旅游系统,却很麻烦。
因为最终我要知道的不是”一段相关文字”,而是一个完整的:
Place Entity
所以后来我调整了 Chunk 策略。
三、旅行 RAG 更适合”实体级 Chunk”
景点数据不再机械按照长度切,而是尽量保持:
{
"name": "石梅湾",
"city": "万宁",
"tags": ["海湾", "情侣", "摄影"],
"duration": "3-4小时",
"latitude": "...",
"longitude": "...",
"description": "...",
"source": "..."
}
作为一个完整知识单元。
对于 QA 数据,则基本保持:
一个问题 + 一个回答 = 一个 Chunk
而对于特别长的攻略,再根据:
- 标题;
- 二级标题;
- 自然段;
进行切分。
这让我意识到一个很重要的问题:
Chunking 不是 NLP 的附属步骤,而是 RAG 架构设计的一部分。
你的数据是什么,决定了你的 Chunk 应该是什么。
四、Embedding:为什么选择 BGE-M3
模型选型结论与当时的数据和查询集绑定,不能仅凭向量维度或模型名字迁移到另一项目。替换编码器时,索引中的旧向量通常也需要重建;查询向量与文档向量应使用匹配的编码规范和版本。
Embedding 使用的是:
BAAI/bge-m3
向量维度:
1024
最开始检索阶段,我用了一个非常直接的方案:
BGE-M3
↓
FAISS
↓
IndexFlatIP
向量归一化之后,使用 Inner Product:
faiss.IndexFlatIP(1024)
实际上等价于做余弦相似度检索。
这个方案最大的优点不是”技术先进”。
而是:
非常简单,而且非常适合第一版验证。
352 个城市和 4 万多条 QA 的规模,本身还没有大到一定必须使用复杂分布式向量数据库的程度。
早期系统如果直接上:
- Milvus Cluster;
- Elasticsearch;
- Redis Vector;
- 多路 Hybrid Search;
很容易出现一种典型工程问题:
RAG 效果还没验证,基础设施先建了半个月。
所以第一版我优先选择:
FAISS + 本地 Embedding。
先让系统真正跑起来。
下一篇我们聊检索优化:MindTrip RAG 架构演进(二):从语义检索到约束感知
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。