9.3 KiB
Knowledge Core 与 Retrieval Core 开发说明
本文档用于团队开发和模块联调,记录 Knowledge Core / Retrieval Core 已经落地的 模块边界、数据模型、接口与使用方式,对应分工表中的杨星萱。
更新日期:2026-09-02。第一阶段 Knowledge/Retrieval 主链路已经完成,并已接入 Agent Tool Registry;完整后端回归基线为 136 项测试通过。
当前实现
当前已经建立第一条可运行的检索链路:
Markdown Vault
→ Markdown Parser(Block 切分 / heading_path / offset)
→ SQLite(notes / blocks / FTS5)+ sqlite-vec(vec_blocks)
→ FTS5(BM25) + Vector(cosine) 双路召回
→ RRF 融合 → Reranker 精排 → Metadata Filter → 分页
→ Citation + Snippet
对应代码:
backend/app/
├── constants.py EMBEDDING_DIM = 128
├── config.py Settings(data_dir / db_path / vault_path)
├── textutils.py 分词、FTS 查询串、摘要片段
├── repository.py notes / blocks / blocks_fts 读写(领域记录层)
├── database/
│ ├── db.py SQLite 连接 + 事务 + 加载 sqlite-vec
│ └── migrations.py 轻量迁移(建 notes / blocks / blocks_fts / vec_blocks)
├── knowledge/
│ └── parser.py Markdown → ParsedNote / NoteBlock
├── retrieval/
│ ├── embedding.py EmbeddingProvider 接口 + HashEmbeddingProvider
│ ├── reranker.py RerankerProvider 接口 + LexicalReranker
│ ├── vectorstore.py VectorStore 接口 + SqliteVecStore
│ ├── hybrid.py RRF 融合、分数归一化
│ └── engine.py RetrievalEngine(编排检索全流程)
└── services/
├── note_service.py Note CRUD + 索引编排
└── index_service.py 全量重建、索引状态、任务查询
Router(backend/app/routes.py)只负责 HTTP 与错误转换;/api/notes、/api/search、
/api/index/* 已接入上述服务,其余端点仍由对应模块负责。
模块边界
本模块负责(杨星萱):
- Markdown Vault、Note / NoteBlock 数据模型与解析;
- SQLite 元数据、FTS5 全文检索、sqlite-vec 向量检索;
- Embedding、Hybrid RAG、RRF、Reranker、Metadata Filter;
- Citation 与笔记定位;
- 检索测试数据。
以下内容保持接口,不在本模块实现:
- Agent Runtime、Tool Registry、Permission:由 Agent Core 提供;
- Provider Adapter、多模型协议:由 Model Core 提供;
- Skill / Plugin 生命周期:由 Extension Core 提供;
- 文件系统与 API Key 明文读取:由 Rust Host 提供。
数据模型与稳定 ID
- 笔记元数据存
notes表;正文切成 Block 存blocks表;blocks_fts是 FTS5 虚拟表;vec_blocks是 sqlite-vec 的vec0虚拟表。 - 稳定 ID(内容/路径不变则 ID 不变):
note_id = "note_" + sha256(rel_path)[:16]
block_id = "blk_" + sha256(note_id | heading_path | content)[:16]
citation_id = "cit_" + block_id
注意:
note_id是稳定业务 ID;移动接口会保留原 ID,文件路径不能代替业务实体 ID。
每个 Block 记录 heading_path(章节路径)、start_offset / end_offset(相对原文的
字符偏移,用于前端跳转高亮)、content_hash、token_count。
分层依赖
按团队约定,依赖方向为:
Router(HTTP/错误转换)
→ Service(note_service / index_service 编排)
→ Repository(notes/blocks/FTS5 访问) ← 只在此层访问 SQLite
→ Retrieval Infra(embedding/reranker/vectorstore) ← vec0 只在 vectorstore 层访问
- 检索 Executor 调用
RetrievalEngine,不直接拼接 FTS5 或 sqlite-vec SQL; - 向量实现只在
retrieval/vectorstore.py;DB 访问只在repository.py。
分词与中文检索
FTS5 默认 unicode61 不切分中文,因此统一预分词:ASCII 单词 + CJK 单字 + CJK 相邻双字。
写入与查询走同一套拆分(textutils.segment / textutils.match_query),实现中文子串/词级召回。
Embedding / Reranker(轻量实现,接口可替换)
当前是「统一接口 + 轻量实现」,后续接入真实模型时替换实例即可,不改变上层调用:
EmbeddingProvider(embed_documents/embed_query)→HashEmbeddingProvider: 确定性特征哈希 + L2 归一化,dim = 128,model_id = "hash-v1"。RerankerProvider(rerank)→LexicalReranker:分数归一化 + 词重叠加权,model_id = "lexical-v1"。VectorStore(upsert/delete/search/clear)→SqliteVecStore: sqlite-vecvec0,相似度取余弦score = 1 - distance² / 2。
检索流程
RetrievalEngine.search(request):
- 按
mode收集候选:fts/vector各取 TopCANDIDATE_POOL = 50; hybrid用 RRF(k = 60)融合两路排序;- Metadata Filter:
folders/note_ids/tags/ 时间范围; hybrid再经 Reranker 精排,其余模式按分数排序;- 分数归一化 → 分页 → 组装
Citation与Snippet。
模块级单例 engine = RetrievalEngine(HashEmbeddingProvider(), LexicalReranker(), SqliteVecStore()),
检索入口统一为 engine.search(request)。
接口清单
Note
GET /api/notes?limit=&offset=&folder=&tag=
POST /api/notes
GET /api/notes/{note_id}
PATCH /api/notes/{note_id}
DELETE /api/notes/{note_id}
POST /api/notes/{note_id}/move (已实现,保留 note_id)
创建笔记:
POST /api/notes
{"title": "Python 基础", "markdown": "# 变量\n\nPython 是动态类型语言。", "folder": "编程", "tags": ["python"]}
Search
POST /api/search
{"query": "向量数据库", "mode": "hybrid", "limit": 10}
mode 取 fts / vector / hybrid;可选 folders / note_ids / tags / 时间范围 /
include_snippet。结果项含 score、snippet 与 citation(citation_id、file_path、
heading_path、start_offset、end_offset)。
Index
GET /api/index/status
POST /api/index/rebuild
GET /api/index/jobs/{job_id}
重建(MVP 同步执行,直接返回 completed):
POST /api/index/rebuild
{"scope": "all"}
检索测试数据
样例 Vault 位于 backend/data/vault/,覆盖中英文、多级标题、frontmatter、子目录与不同 tags:
项目说明.md
编程/Python 基础语法.md
编程/向量数据库与相似度检索.md
产品/RAG 检索增强与引用定位.md
日记/2026-08-27 周会.md
示例查询:
POST /api/search {"query": "向量数据库", "mode": "hybrid"} → 命中《向量数据库与相似度检索》
POST /api/search {"query": "检索", "mode": "fts", "folders": ["产品"]} → 只返回 产品/ 下笔记
POST /api/search {"query": "向量", "mode": "hybrid", "tags": ["向量"]} → 按 tag 过滤
测试
cd backend
uv run pytest -q
当前后端完整测试共 218 个用例通过(单元 + 端到端)。测试通过 tests/conftest.py 的 autouse fixture 把
数据目录/DB/Vault 重定向到临时目录,不读写真实 backend/data,任何本机状态下结果确定。
配置
APP_DATA_DIR 默认 backend/data
APP_DB_PATH 默认 backend/data/app.db
APP_VAULT_PATH 默认 backend/data/vault
APP_ATTACHMENTS_PATH 默认 backend/data/attachments
运行期生成的 backend/data/*.db* 已被 .gitignore 忽略,vault 下的 Markdown 测试数据会提交。
接入约定(Agent / 其他模块)
Agent 通过注册 Tool 接入本模块,不让 Agent Runtime 直接依赖具体实现:
notes.search / notes.read / notes.create / notes.update / notes.list / notes.move
rag.search
- 写操作 Executor 调用
note_service,不直接访问 SQLite; - 检索 Executor 调用
engine.search(request),不直接拼接 FTS5 或 sqlite-vec SQL。
当前限制与下一步
move接口已实现,移动文件后保持原note_id,同时原子更新 Block、FTS 和向量索引。- Citation 的
start_offset/end_offset使用 UTF-16 code unit,直接兼容浏览器编辑器。 - Markdown 分块会识别 fenced code block,不会把代码中的
#注释误判为标题。 - Embedding / Reranker 为轻量实现,后续替换为真实模型(接口不变)。
- 小语料下 hybrid 检索召回偏宽(向量 Top-K 覆盖全部 block),可加相关性阈值收紧。
- 重建为同步 + 全量,后续接入增量索引与异步任务队列。
- RAG Benchmark 已建立:
POST /api/benchmarks/rag/runs创建即返回 queued、后台 Task 执行, 通过 SSE 实时推送进度,报告含逐 Case 结果与total_cases/successful_cases/failed_cases/failure_rate。 - Agent Benchmark 暂缓,待 Agent Runtime 完成后交付。
2026-09-06 实现补充
上文的 MVP 同步说明是早期基线。当前打开 Vault 和 HTTP PATCH 保存已拆分即时元数据 / FTS 与后台向量计算;手动全量重建仍等待完成。生产 Embedding 使用实际模型和隔离向量空间,HashEmbedding 仅用于测试。
后续维护以 工作区后台索引与保存开发说明 的实现边界、状态和验证步骤为准。