04 / Retrieval Evaluation

算法组合容易
效果证明很难

词法、向量、图与排名融合都是候选手段。没有固定语料、相关性标注、消融实验和失败集,再合理的启发式也只是待验证假设。

案例融合RRF · K=60
向量存储LanceDB + Arrow
关键缺口可复现 Retrieval Eval
审计方法Baseline · Ablation · Failure Set

检索栈

01 / lexical关键词扫描

文件名、标题、短语、正文 Token 与中文二元组。

02 / semantic向量召回

页面分块 embedding,LanceDB 返回语义近邻。

03 / fusionRRF 融合

依据各自排名合并,不要求分数同量纲。

04 / graph一跳图扩展

为强结果补充链接邻居,并保留结构来源。

Rust 后端的 search_project_inner() 是桌面 UI、HTTP API 和 MCP 共享的检索核心;这减少了入口之间的实现分叉,但最终答案仍会受调用参数、上下文组装和模型生成影响。

词法检索:简单但针对知识库优化

系统递归扫描 wiki/ 的 Markdown,先从 frontmatter 或一级标题提取标题,再计算:

  • 文件 stem 与完整查询完全相等的高奖励。
  • 完整短语出现在标题或正文中的奖励。
  • 查询 Token 命中标题和正文的不同权重。
  • 从第一个命中附近生成片段,并提取页面中的图片引用。

中文为什么要特殊处理

空格分词对中文无效。tokenize_query() 会对长度大于 2 的 CJK Token 同时产生二元组、单字和原词,并过滤中英文停用词。

“知识图谱检索”
→ “知识” “识图” “图谱” “谱检” “检索”
→ “知” “识” “图” “谱” “检” “索”
→ “知识图谱检索”

这不是完整中文分词器,但不依赖词典、跨平台稳定。二元组和单字会扩大候选集,也会引入大量弱命中;是否真正提升专有词召回、是否损害 Precision@k,必须在中文查询集上测量。

向量检索:可选的增强层

Embedding 默认可关闭,端点、Key 和模型独立配置,支持 OpenAI 兼容接口。开启后,页面先按标题和语义边界切块,再写入 Rust 的 LanceDB。

分块而非整页

长页面的局部语义不会被整页平均;搜索结果可回到具体标题路径和片段。

增量更新

摄入后只处理新写页面;删除页面时同步清理向量;批量重建有进度与取消状态。

失败可降级

向量端点失败时打印诊断并继续返回关键词结果,不让可选能力拖垮基础检索。

维护动作

增量写入达到阈值后优化表;支持旧页面级表计数、迁移和删除。

为什么使用 RRF

关键词分数可能是“命中次数 + 奖励”,向量分数可能是余弦相似度。直接加权相加会受量纲和模型分布影响。RRF 只看名次:

RRF(d) = Σ 1 / (K + ranksignal(d))

某页同时排在词法和向量前列会自然升高;只被一种信号发现也仍能进入结果。代码还保留原始 vector_score 用于解释, 最终 mode 会标注 token / vector / graph 的实际参与情况。

适用边界

RRF 解决不同分数不可直接比较的问题,不会自动保证相关性。固定 K=60、候选窗口、信号权重和图扩展比例都是待调参数,应通过离线消融和分查询类型评测确定,而不是沿用经验常数。

图扩展:找到“没有同词”的相关页

初始搜索结果成为种子,系统为一跳图邻居预留最终窗口的 15%–30%。向量结果越稀疏,图扩展份额越大。 邻居并非简单塞入:它会记录 graph_related_to,告诉上层“这页是通过哪一个种子关系发现的”。

例子

查询“注意力机制”命中 Transformer 概念页后,图扩展可能带回没有出现同一关键词、但通过 wikilink 相连的 BERT、GPT 或序列建模历史页。

四信号关联度模型

信号权重含义
直接链接3.0 × 方向数A 链到 B 或 B 链到 A;双向链接得到两份分数。
来源重叠4.0 × 共同来源数两个页面由同一原始材料贡献,作为同源先验;是否相关仍需验证。
共同邻居1.5 × Adamic–Adar共享稀有邻居比共享超级 Hub 更有信息量,贡献为 1 / log(degree)。
类型亲和1.0 × 矩阵值entity↔concept、concept↔synthesis 等组合有不同先验。
relevance(A,B) = 3·direct + 4·sharedSources + 1.5·AdamicAdar + typeAffinity

来源重叠可以作为关联先验,但不是相关性的证据:一份长报告可能同时支撑互不相关的页面,LLM 自动生成的 Wikilink 也会把摄入偏差反馈进图。直接链接、共同来源和类型亲和都应被标注为启发式信号,并接受消融验证。

可视化图谱

buildWikiGraph() 读取 Markdown、frontmatter、wikilink 与来源字段,生成节点、边和社区。为了规模化,它设置了清晰边界:

  • 并发读取 Wiki 文件,缓存最近两个项目的图。
  • 3,000 节点以内计算完整加权图;更大规模限制代价高的全对关联。
  • 3,000 节点以上把社区分析送入 Worker,避免阻塞 UI。
  • Sigma.js 渲染,节点大小按链接数平方根缩放,位置缓存防止数据更新后跳动。
  • 支持按页面类型或 Louvain 社区着色,ForceAtlas2 负责结构布局。

图谱不是海报,而是诊断工具

惊奇连接

跨社区、跨类型、边缘节点连接 Hub、低权重但在图中存在的边会获得惊奇分。结构页 index/log/overview 被排除,避免它们制造伪洞察。

知识空白

  • 孤立页:度 ≤ 1,说明几乎没有进入知识网络。
  • 稀疏社区:至少 3 页且内聚度 < 0.15,说明同一领域内部链接不足。
  • 桥接节点:连接 3 个以上社区,是跨领域关键枢纽,需要重点维护。

洞察卡可直接高亮图中节点,并把空白转成 Deep Research 主题,形成反馈闭环。但孤立、稀疏、桥接和“惊奇”描述的是当前图结构,不等于知识真的缺失、重要或正确;把这些标签直接展示为事实会制造算法权威感。

更严谨的定位

图谱同时服务检索、可视化和治理,这是有价值的复用;图算法输出应被称为“调查线索”,需要证据或人工判断后才能升级为知识结论。

没有评测,就没有“提升”

证据缺口

README 声称组合检索将召回率从 58.2% 提升到 71.4%,但本次在固定提交中未找到配套数据集、查询标注、评测脚本或运行报告。因此该数字只能列为项目声明,不能当作已复现结论。

评测层最小做法关键指标
检索相关性冻结语料与 query→relevant chunk 标注,按中文、英文、实体、概念、时间问题分层。Recall@k、nDCG@k、MRR、Precision@k
信号消融分别运行词法、向量、图、词法+向量、全部信号,固定候选窗口。增益、退化查询数、延迟与成本
答案忠实度要求句子级引用,验证回答是否由召回证据支持。citation precision、support rate、unsupported claim rate
回归与漂移模型、Prompt、Schema、Embedding 或图规则升级时重跑固定集。版本差异、语言切片、失败集修复率
线上反馈记录无结果、改写查询、用户打开来源与纠错,不记录敏感原文。zero-result rate、reformulation rate、source-open rate

最有价值的产物不是一个总分,而是一组长期保留的失败查询。每次调权重、换模型或改图规则,都必须证明旧失败减少且没有在关键切片制造新退化。

Next Chapter05 · Agent 能力如何建立安全边界
案例证据:src-tauri/src/commands/search.rssrc/lib/embedding.tswiki-graph.tsgraph-relevance.tsgraph-insights.ts 与 README 的检索指标声明。评测矩阵是本次审计提出的生产要求。