ADR-007: BM25 RAG 知识检索
修改记录
日期 |
变更 |
|---|---|
2026-07-18 |
初始创建 |
ADR-007++
状态 |
项 |
说明 |
验证 |
|---|---|---|---|
✅ |
BM25 全文检索 |
jieba 分词 + 停用词过滤 + 低分重试 |
|
✅ |
RAG 上下文注入 |
|
|
⏸ |
多轮对话融合 |
支持历史消息中的隐含问题,不止单轮 query |
未实现 |
⏸ |
索引热更新 |
docs/ 文件变更后自动重建索引 |
未实现 |
状态
✅ 已采纳。
背景
Agent 在对话中需要回答用户关于项目自身的问题(功能、技术栈、架构等)。纯靠 LLM 自身知识无法保证准确性和及时性,需要检索项目文档(docs/*.md + README.md)作为知识上下文。
三方案对比
方案 |
优点 |
缺点 |
|---|---|---|
A: BM25 全文检索(采纳) |
零外部依赖,首次检索即生效,项目文档量小 BM25 足够精准 |
无语义理解(同义词/近义词不匹配) |
B: ChromaDB 向量检索 |
语义匹配强,自动处理同义词 |
需 embedding API 或本地模型(首次构建慢),依赖重 |
C: API 嵌入(LLM 直接回答) |
最省事 |
知识更新不及时,prompt 长度有限,无法精确引用 |
BM25 被采纳的理由:
项目文档不足 50 个切块,体量极小,BM25 的精确匹配已覆盖全部需求
零依赖 vs ChromaDB(chromadb + embedding 模型约 50MB+)
首次构建即生效,无需等待 embedding 请求
后续若需语义增强,可叠加向量检索引擎(原生兼容,
search_rag()接口不变)
架构链路
用户: "这个项目可以做什么"
→ POST /api/chat { message }
→ build_chat_messages()
1. search_rag(query, k=5) → BM25 检索 docs/ 相关片段
2. 注入 system prompt:
"以下片段来自项目文档,请优先使用这些信息回答..."
[docs/structure/project.md#技术栈总览]
[docs/structure/backend.md#HTTP 接口层]
...
3. 组装 [system, user] messages
→ LLM 推理(无 tool_call 时 SSE 直接流式输出)
关键设计
分词策略
中文用 jieba.lcut() 分词,英文按空白拆分,过滤 50+ 常见停用词(的/了/是/可以/什么等)。
对比效果:
原始查询 |
旧方案(单字) |
新方案(jieba + 停用词) |
|---|---|---|
|
|
|
|
|
|
|
|
|
文档切块
按 ## 标题分割,每个块独立 BM25 索引。切块同时保留 [source#heading] 元信息供 LLM 引用。
低分重试
当 BM25 最高分 ≤ 0.5 时,自动过滤单字词后用剩余关键词重检。解决全停用词查询(如"这个项目是做什么的")分数趋零问题。
注入策略
检索量:
k=5,覆盖宽泛查询注入位置:system prompt 末尾,user 消息之前
指令措辞:
"以下片段来自项目文档,请优先使用这些信息回答用户关于项目本身的问题"CHAT_SYSTEM第 6 条配合:允许在项目文档存在时跳出旅行伴侣角色
影响范围
文件 |
变更 |
说明 |
|---|---|---|
|
新增 BM25 检索引擎 |
jieba 分词 + 停用词 + 低分重试 |
|
CHAT_SYSTEM 第 6 条加例外 |
项目文档存在时可跳出旅行伴侣角色 |
|
RAG 检索注入 |
k=5,强化引用指令 |
|
同步测试 |
适配 jieba 分词行为 |
|
新增依赖 |
jieba >=0.42.0 |
交叉引用
Agent 数据流:
docs/structure/agent.md#rag-检索流程BM25 实现:参考
backend/agent/tools/poi.py的 TOOL_REGISTRY 注册模式