MindTrip RAG 架构演进(一):数据层与实体级 Chunk 设计

· 约 4 分钟 ·
本文目录(4 节)

做 MindTrip 之前,我对 RAG 的理解其实很朴素:

用户问问题 → 向量数据库找几段资料 → 塞给大模型 → 大模型生成答案。

这个流程做知识库问答确实能跑。

但当我真正开始做一个旅行规划系统时,很快发现事情完全不是这样。

用户不会只问:

杭州有什么好玩的?

更多时候,他们的问题是:

我从宁波出发,准备去万宁玩 3 天,两个人,预算 5000 左右,不想每天赶路,希望安排海边和冲浪,其中一天最好轻松一点。

这已经不是普通问答了。

它同时包含:

  • 城市约束;
  • 时间约束;
  • 空间约束;
  • 预算约束;
  • 用户偏好;
  • 景点之间的距离;
  • 每个景点的游玩时长;
  • 行程节奏;
  • 多轮修改。

所以 MindTrip 最后逐渐从一个简单的 RAG Demo,变成了一个:

RAG + 约束过滤 + 行程规划 + 多轮交互 + 本地大模型推理系统。

这个系列共四篇文章,记录整个架构演进以及其中几个让我印象非常深的坑。


一、MindTrip 到底想解决什么?

MindTrip 的目标其实不是”推荐景点”。

推荐景点很简单。

真正难的是:

生成一个用户第二天真的敢照着走的旅行计划。

比如用户说:

万宁三天,两个人,喜欢海边,不想太累。

一个纯 LLM 很容易生成:

第一天:

  • 石梅湾
  • 日月湾
  • 加井岛
  • 神州半岛

第二天:

  • 大洲岛
  • 山钦湾
  • 巴厘村
  • 兴隆植物园

看起来内容丰富。

但真正打开地图就可能发现:

这不是旅游规划,这是拉练。

所以我给 MindTrip 定的核心目标是:

  1. 尽量基于真实资料生成;
  2. 降低景点和信息幻觉;
  3. 不只做语义相关性,还要考虑时间和空间;
  4. 用户修改需求时,不必整份行程重新生成;
  5. 最终结果需要足够结构化,前端才能继续编辑。

二、数据层:先别急着上”大模型”

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 架构演进(二):从语义检索到约束感知

💬 评论

这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。

正在连接评论服务,首次访问可能需要几秒…