语义检索

检索在两条路上跑: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 分块的价值主要在这里。