语义检索
检索在两条路上跑:sqlite-vec / pgvector 近似检索(快)与内存余弦相似度(兜底)。两条路都优先保证「不中断」,再保证「准」。
两路召回
| 路径 | 实现 |
| 笔记检索 | 先搜整篇笔记向量(vec_note_embeddings),再搜分块向量(vec_chunk_embeddings),两路各取 topK × 3 条候选 |
| 代码检索 | 独立的 vec_work_code_chunks;sqlite 侧候选量取 clamp(topK × 5, 20, 200)(抵消多项目共表的过滤损耗),PostgreSQL 侧用 ORDER BY Vector <=> @query LIMIT topK |
| 合并 | 同一知识项可能同时被笔记路与分块路命中:按 ID 去重,保留先出现的,最后截断到 topK |
| 回退 | 扩展缺失、虚表不存在、维度不符、无 pgvector → 内存余弦相似度 |
结果投影
| 来源 | 展示内容 |
| 分块命中 | 优先显示分块 Summary,没有摘要才截取正文片段 |
| 整篇命中 | 截取正文前 120 字 |
| 返回结构 | Id / Title / ContentSnippet / UpdatedAt——前端直接渲染命中列表,点击可跳转来源 |
谁在用
- 对话输入框的「知识库」开关:召回结果作为上下文注入,并显示命中列表。
- 知识库页「搜索测试」:输入查询、调
TopK(默认 10)、看命中片段与高亮,用来验证索引质量。
- Wiki 生成:用代码检索为每个模块页补充相关代码片段。
- 模型主动调用:检索工具让模型自己决定何时检索(受权限模式与工具白名单约束)。
失败与降级
| 情况 | 行为 |
查询为空 / topK ≤ 0 | 直接返回空结果 |
| 向量库不可用 | 回退内存计算(比较慢,但结果仍可用) |
| 统计接口失败 | 返回 -1 表示「未知」,不阻塞页面 |
| 取消 | 透传,不吞异常 |
检索质量实践
- 命中不准先看「索引管理」:该项是否已索引、分块数是否合理(1 块说明内容太短或被合并)。
- 多取候选再截断是刻意设计(笔记路 ×3、代码路 ×5):去重与业务过滤会消耗候选,宁可多取。
- 合并时不做二次按分数重排:命中顺序即两条路的原始相似度顺序,避免「分数不可比」带来的抖动。
- 分块摘要质量直接影响命中片段的可读性——LLM 分块的价值主要在这里。