ADR-007: BM25 RAG 知识检索

修改记录

日期

变更

2026-07-18

初始创建

ADR-007++

状态

说明

验证

BM25 全文检索

jieba 分词 + 停用词过滤 + 低分重试

rag.py

RAG 上下文注入

build_chat_messages() 自动检索 + 注入 system prompt

chat.py

多轮对话融合

支持历史消息中的隐含问题,不止单轮 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 条配合:允许在项目文档存在时跳出旅行伴侣角色

影响范围

文件

变更

说明

backend/agent/tools/rag.py

新增 BM25 检索引擎

jieba 分词 + 停用词 + 低分重试

backend/agent/tools/prompts.py

CHAT_SYSTEM 第 6 条加例外

项目文档存在时可跳出旅行伴侣角色

backend/agent/chat.py

RAG 检索注入

k=5,强化引用指令

tests/test_agent/test_tokenize.py

同步测试

适配 jieba 分词行为

pyproject.toml

新增依赖

jieba >=0.42.0

交叉引用