第 01 模块 · 2 节

实现可解释的本地检索

《知识库问答入门》01 检索基线 · 本节时长 25 分钟

本节目标

运行一个零第三方依赖的检索程序,看到“查询 → 分词 → 评分 → 来源”的完整路径。本节不调用语言模型,返回的是资料片段。

课程页下载程序。需要 Node.js 18 或更高版本,在终端运行:

node rag-lab.cjs "报销需要什么材料"

默认前三条候选中,报销材料应位于第一名;输出含 id、source、version、score 和 text。相同程序与输入应返回相同结果。

中文如何做一个最小词面基线

示例把连续中文按相邻两个字符切分,例如“报销需要”得到“报销、销需、需要”。英文和数字按连续单词提取。用 Set 去重后计算:

分数 = 查询 token 与文档 token 的交集数量 / 查询 token 数量

这是教学用的重叠系数,不是 BM25,不是向量相似度,也不是正确概率。程序用标题和正文共同匹配,分数大于零才返回候选。相同分数按资料 id 排序,便于复现实验。

读懂关键函数

function score(queryTokens, documentTokens) {
  if (!queryTokens.size) return 0;
  const matches = [...queryTokens].filter(t => documentTokens.has(t));
  return matches.length / queryTokens.size;
}

程序为每条文档预先生成 token 集合;查询时扫描全部记录。对三条练习资料足够,数据增大后全量扫描会成为瓶颈。建立倒排索引或接入现成检索服务属于后续工程步骤,不能把这个小例子直接当大型知识库。

观察一个必然出现的限制

运行:

node rag-lab.cjs "忘记口令怎么办"

资料中写的是“密码重置”。词面检索可能无法识别“口令”和“密码”之间的关系。这个失败说明需要同义词、更好的词法分析或语义检索,而不是再要求生成模型“认真读题”。

接入语义检索后,也要用同一问题集测试:相似表述可能召回正确资料,也可能把主题接近但条件不同的文档混在一起。

不要把命中当作事实答案

查询“密码重置需要发票吗”可能因为“密码”“发票”同时出现在不同资料中而返回多条结果。当前程序不能理解否定或条件关系,不能回答“需要”。

因此本节的正确产品行为是展示候选资料与来源。接入生成模型后还需验证答案是否真的受候选片段支持;没有支持时应说明资料不足。

动手练习

新增上一节的“出差审批”,为它写三种问法:原文关键词、同义表达、条件矛盾表达。记录哪些能找到,哪些找不到。不要只保留成功的问题。

本节交付:能运行的程序、三种问法的输出,以及至少一个失败原因。下一节把它们变成可重复执行的评测。