找得「像」不等于找得「对」 — 三种翻车与三种补救
这一章讲三件事: 「意思相近」会怎么骗你(三种方式); 书里给的三种补救各自治的是哪一种; 以及它们的共同点——全都在入库时做,不是在用户提问时做。 位置:入库线的最后一步。如果你已经搭了一个 RAG 系统、只是觉得它老答非所问,直接读这一章。
1. 先看现象:搜回来的东西「看着都挺相关」,可就是没用
你搭好了系统。用户提问,系统搜回来五块内容,模型照着写了个答案。
答案读起来很顺,但它是错的,或者答非所问。 你去看搜回来的那五块:
- 每一块读起来确实跟问题有关;
- 可其中三块根本不该被搬出来。
问题不在模型,在检索。 而检索没做错任何事—— 它忠实地找出了「意思最相近」的五块。
问题在于:「意思相近」不等于「对这个问题有用」。
书里那句话说得最准:
不是每一块跟用户问题语义相似度高的文本,都真的相关。1
2. 顶层全景:三种骗法,三种解法
骗法一:意思确实像,但对这个用户没用
└─ 解法:给每块贴标签,搜之前先把不该考虑的踢掉 ← 元数据过滤
骗法二:块脱离前后文,自己看不懂自己
└─ 解法:入库前把缩写、代词、模糊指代都补成全称 ← 让块自解释
骗法三:用户提的是问句,资料是表格/代码/聊天记录,形状对不上
└─ 解法:事先给每块编几个「用户可能会这么问」的问题 ← 假设性问题
图说:三种骗法互不重叠,三种解法也可以同时用。它们全都发生在入库那一侧。
这个「全都在入库时做」的共性,是这一章最值得记住的一件事—— 第 6 节会展开讲它为什么重要。
这一章的 主走查从第 3 节起立起来:一份招聘知识库里的一块。 三节各让它走一步,第 6 节把五步并起来再看一遍。
3. 骗法一:意思很像,但对这个用户没用
先立这一章的主走查:一份招聘知识库
从这一节开始,三招都拿同一份资料走,一直走到第 6 节。 输入是一家公司的招聘知识库:里面有简历、有面试记录, 还混进了一批从供应链部门抄送过来的采购文档。我们全程盯住其中一块:
块 c-64(现在原样躺在库里的样子):
「PO 已核准,候选人 c-64 进入终面。」
图说:一句十几个字的话。它一次踩中三种骗法 —— 下面三节各拆一种,第 6 节并起来看。
先声明:这份招聘知识库、上面这块的字串、以及后面出现的块数、距离、编出来的问题, 全部是为演示编的,不是真实数值。 书里在这一整章里只给了这么几样真东西:抗氧化剂那个对照例子、 「简历按岗位打标签」这个做法、
LSTM那张缩写表、以及「每块编 5 个问题」这个数。 其余是我们为了让每一步看得见而编的,不许引用。
骗法一在这块上长什么样
一个只管软件开发招聘的用户问:「PO 进展如何?」 系统搬回来的前五块里,有三块来自供应链部门的采购文档—— 那边的「PO」指的是采购单。这三块跟「PO」这两个字母确实高度相关, 可没有一块跟他要办的事有关。
书里给的旁证:抗氧化剂
书里用另一个例子说同一件事,而且更极端。同一个词「抗氧化剂」,两段内容都跟它高度相关:
- 一段讲抗氧化剂对人体的好处;
- 一段讲抗氧化剂如何改善原材料的性能。
书里的判据:
如果你是医生,你关心的是前者 。 如果你是工程师,你关心的是后者。 用户几乎不会问另一边的那些场景2。
书里紧接着给出的结论,值得一字不落地记:
乍一看又正确又相关的信息,一旦我们知道用户问题背后的完整背景, 可能就变得不相关、甚至是错的。3
作者还补了一句:这个例子很极端,但这类问题在真实的 RAG 应用里很常见—— 经常是两份文档乍看有关,细看之下有一个事实让它们完全不相干4。
解法:先缩小范围,再比相似度
做法:给每一块贴上标签(也就是第 02 章说的元数据), 搜之前先按标签把不该考虑的整片踢掉。
书里的说法:元数据能帮我们在做向量搜索之前先收窄搜索空间—— 「搜索空间」就是「这一次搜索,允许哪些块参赛」;收窄它, 既加快搜索,也把不相干的东西滤掉1。
不过滤:一万块,全部参与比相似度 → 前五名里混进三块供应链采购文档
先过滤:标签「岗位 = 软件开发」→ 只剩两千块参与比 → 前五名全是招聘相关的块
└─ 被踢掉的那八千块,不是「排名低」,是「根本没资格进入比较」
└─ 块 c-64 身上贴的正是这张标签,所以它留在场上
图说:这跟第 02 章「先找章标题再往下找」是同一个动作,只是过滤条件从「章名」换成了任意标签。
(一万块、两千块、八千块这三个数是为演示编的,不是真实数值;书里没有给任何数。)
书里给的其他可用标签:文档发表的时间段、作者名、正文里有没有提到某些人、 合同里含不含某些条款——具体用什么,完全取决于你的场景5。
元数据从哪儿来:三步,一步比一步费劲
书里把攒元数据拆成三步6:
| 步 | 做什么 | 举例 |
|---|---|---|
| 一 | 把文档自带的元数据抽出来 | PDF 里存着作者、创建时间、最后修改时间、标题、主题,还有生成它的软件7 |
| 二 | 自己补一些文件层面的信息 | 页数、文件大小、文件名、文件路径、抽出来的文字有多长8 |
| 三 | 让模型读正文,把缺的挖出来 | PDF 自带的「作者」那一栏经常是空的,但作者名往往就写在正文第一页上9 |
第三步才是这一节真正的技巧。
书里的例子很实在:一个招聘用的 RAG 系统里, 简历的 PDF 元数据里没有「这是什么岗位的简历」这一项——可这正是最该用来过滤的标签。
所以让模型读一遍正文,判断这份简历属于软件开发、平面设计还是市场营销。 这样用户问「软件开发的简历里常提到哪些技能」时,系统就能把非开发岗的简历全滤掉10。
让模型把结果吐成固定格式:结构化输出
第三步有个实际问题:模型是写文章的,它吐出来的是一段话,不是一张能直接入库的表。
书里用的办法叫结构化输出:事先声明「我要哪几栏、每一栏装什么样的东西」, 让模型只能按这个格式回答11。(这一栏一栏的东西,程序里叫字段。)
书里用一个叫 Pydantic 的工具来声明格式 (Pydantic 是 Python 里最常用的一个「数据格式校验」工具—— 你先写一张表格样板,列明「要姓名、要公司、要邮箱,姓名必须是一段文字」, 它负责把模型交上来的东西对着这张样板核一遍,不合规就打回)。
还要给模型一段指令,告诉它该干什么。 这段放在最前面的指令,书里叫「系统消息」12—— 它和用户的问题分开放,作用是设定模型的角色和任务。
4. 骗法二:块自己看不懂自己
问题的根子
书里给了一句定义式的话:
文本块是从一段更长的文字里切下来的片段, 孤零零地看的时候,可能很难理解。13
注意这句话跟第 05 章的关系: 第 05 章解决的是「在哪儿下刀」, 这一节解决的是「刀下完之后,这块自己站不站得住」。两件事,不能互相替代。
三个例子,一看就懂
| 块里写的 | 问题 |
|---|---|
| 「PO 已核准」 | 供应链文档里指采购单;换个场景可能指产品负责人,也可能指邮局14 |
| 「MJ 拿下了六冠」 | 体育文档里指迈克尔·乔丹;不熟悉篮球的人根本不知道 MJ 是谁14 |
| 「点击「设置」按钮」 | 教程类文档里的这句话,配图不在块里,你不知道那个按钮长什么样、在哪儿15 |
前两个是同一类:缩写脱离了行业背景就没有意义。 第三个是另一类:文字在指一个不在文字里的东西。
解法一(最朴素):入库前把缩写换成全称
做法就是准备一张缩写对照表,入库前做一次替换。 替换的写法有讲究:不是把缩写删掉换成全称,而是写成「全称(缩写)」。
书里的例子是一篇讲 Transformer(今天几乎所有大模型底层都在用的那套网络结构)的博客,里面全是缩写:
LSTM 变成 Long Short-Term Memory (LSTM)16。
为什么要两个都留? 书里给了理由,而且是这一节最实用的一句:
这样一来,不管用户用缩写问还是用全称问,系统都懂: 「什么是 LSTM?」和「什么是 Long Short-Term Memory?」17
只留全称:用户问「LSTM 是什么」 → 块里没有 LSTM 三个字母 → 相似度掉下来
只留缩写:用户问全称 → 同上,反过来
两个都留:两种问法都对得上 ← 这才是目的
图说:替换的目的不是让块「更通顺」,是让块能被两种问法同时命中。
主走查走到这一步: 块 c-64 在入库之前被改写了一次,原样存进去的不再是 「PO 已核准,候选人 c-64 进入终面。」,而是:
「**采购单(PO)**已核准,候选人 c-64 进入终面。」
注意这一改带来了什么: 用户拿「采购单」问,能命中;拿「PO」问,也能命中; 而上一节那个只管软件开发招聘的用户,再也不会因为「PO」两个字母 把供应链的采购文档整片搬回来——因为那些块也被改写过了, 它们现在明白写着「采购单」,而他要的是候选人。
解法二:内容太复杂就交给模型改写
对付充满专业术语、光看数字看不懂的内容(书里的例子是一张财务幻灯片), 朴素的词典替换不够,那就直接让模型来18。
书里那段提示词里有一句要求很妙,值得学:
要写得足够浅显,浅显到一个十岁的小学生也能看懂。19
书里给的四条写作建议
作者列了四条,本质是同一件事——把块里所有「要靠别处才能理解」的东西补齐20:
| 别写 | 要写 |
|---|---|
| 「它把效率提升了 40%。」 | 「新换的那套搜索做法把搜索效率提升了 40%。」 |
| 「它是去年上线的。」 | 「客户反馈系统是去年上线的。」 |
| 「谷歌 2019 年发布了它。」 | 「谷歌 2019 年发布了 BERT(一个语言模型的名字)。」 |
| 「系统现在更好用了。」 | 「系统的响应时间从 500 毫秒改善到 200 毫秒。」 |
四条的共同点:把代词、模糊指代、含糊的形容词,全都换成具体的东西。
一个更根本的坦白
书里在讨论里承认了一件事,而且这件事很少有人写出来。用我们自己的话复述: 理想情况下每一块自己就能读懂,可现实是大多数文档都默认读者带着一点背景知识—— 就像读一本进阶的 Python 书,默认你已经懂基础的 Python;不懂的地方,人可以自己去翻一本入门书补上。 而钉住这一整章的是下面这一句:
在基础的 RAG 系统里,模型只看得到向量搜索捞回来的那点东西。21
这是这一整章的元问题: 人有「去别处补课」的能力,而 RAG 里的模型没有。 所有的补救,本质上都是在入库时替它把课补好。