检索这一层:先把目录和预读做对,再谈向量库
「今年要不要上向量库」,我现在的答法是:先别答,把系统给检索用的那行清单拿出来看三眼——那句说明是作者手写的还是从正文抽的?它说清「这一篇回答什么问题」没有?答得不像读过正文的那一次,模型手上到底有没有正文?
前两问是目录层的事,第三问是预读层的事。我的判断:几十到几百篇这一档的企业知识库(制度、手册、合同模板),检索的瓶颈几乎不在语义相似度上,而在元数据没配对、命中的那一篇没被真读进去。 先上向量库,检索出来的还是一堆没有说明的标题——embedding 不会替你补那一句。下面逐层拆(同一写法见新系统从 0 到 1 怎么搭:一套上线几年的微服务架构,逐层拆开):每层只答一个问题,判据给到你能对着自己系统核对。
目录层:每篇先补一句能被当摘要用的说明
本站每篇的 abstract、description、keywords 不在内容主表上,住在一张「内容—字段—取值」的字段表里,一行一个取值。说明与正文因此是两条独立的写,改文案不动正文;代价是取的时候要配对。
配对有两种走法,我分开用:
- 单篇取:按内容主键捞字段行,再按字段 id 取回定义,一篇两查。只给详情页与 SEO 出口用——列表这么走,一页十条就是十条白查的语句。
- 一批取同一字段:按字段名捞一次定义,再按内容主键集合一次捞取值,两趟收口。清单要三十篇的一句话说明,第 1 种是六十条语句,第 2 种是两条。
第二种就是「批量配对」。那句说明空着时,模型只有标题可猜,「本站没提过」和替文章编结论那两类错话就是这么来的。判据一:清单里有几句说明是空的?空行不是排版问题,是检索层没有依据。
摘要的取用顺序也要定死:手写 abstract > description > 正文截断——正文开头那段几乎永远是背景铺垫,不是「这一篇回答什么」。判据二:挑五篇,只看那句说明认不认得出这篇讲什么。认不出,就是从正文抽的。
还有一条硬天花板:清单要装进上下文。本站只放最近三十篇,每篇一行(标题、截到一百二十个码点的说明、标识、地址);三十篇全文是几万 token,而一次问用到的通常只有一篇。
两处口径要对齐:
- 篇数。
content_count那一列是管理口径:派生表COUNT(*)、只按「已发布」过滤,不看定时也不分形态,且只刷本次受影响的术语 id;前台导航另走一条分组语句,数的是「访客真能读到的篇数」(限形态、状态、发布时间已到)。两处答的不是同一个问题,列表可见性口径一改就得同步——不同步不报错,只是数对不上。 核对时我就问一句:那个数是哪条语句给的。 - 缓存。前台取数那层有 TTL 缓存(只为「一次渲染别问后端七八趟」),副作用就是那句「改了怎么还没生效」,这一处要显式处理:留一个把 TTL 配成 0 的排查档,并规定哪种失败不许吃旧货——后端明确答「这一篇没有」时拿旧货顶上,已下架的稿子会一直留在公网上。助手侧相反:那份提示词每次现拼,不缓存。
术语分组也在这层:分了组才答得出「这里讲过哪几类事」;它的第一个坑是同名(见业务系统里的 Agent 这个词,早就有主了)。
预读层:回答之前,真把那一篇读进去
目录层搭好之后会出现一种新错法——说明本身够答一半问题了,于是模型只读目录就开口,末了还说一句「这篇正文我读过」。 本站提示词里现在就有一行,没开预读时写上「这一问本站没有替你读任何一篇正文,没读过的不许说已读取」。它只在没开火时补,开火时那句反倒成了假话。
「命中后要读正文」得落成判据才可用。本站这一道四步:
- 问句切二元组,算覆盖不算长度:中文按相邻两字切、连续西文与数字整串留一个,拿它比对每篇的「标题 + 说明」,只看问句被覆盖了多少——否则长文永远占便宜。
- 第一道门:一篇都够不上分数下限,就一篇都不喂。字面重合不等于讲的是一回事:库里压根没写过的那类问句(某个中间件默认值)字面也能捞到一点,模型读到看着相关的正文就更会顺着答。要收这一处得换判据,不是把这个数挪来挪去。
- 第二道门:喂进去那一组要和组外最高那一篇拉开一段差,拉不开也一篇都不喂。它挡的是「两三篇挨得很近、说不出哪一本才是要的那一本」——那时喂错了比喂晚了更糟。
- 同分一起读,最多三篇:同分说明字面判据分不出高下,替模型挑一本等于拿清单顺序当依据;每段点明出自哪一篇,不对题的那段只当没有。取正文串行:三篇并行只是三趟查询同时压向连接池,几十毫秒不值。
两道门都不过的,交给模型自己:清单每行单列一个「标识」,另有一格按标识读正文的工具,提示词里写明「手上没有它的正文,就在答这一句之前当场读掉」。标识必须单列,因为那一行上标题、地址都像答案,让模型猜就会把整条地址抄过去;一次给六千字符(约四千汉字),截断而不报错。
判据三(我最常用的一条):答得不像读过正文时,按顺序查,别跳步。 先查目录层:命中的那一篇对不对;再查预读层:对的那一篇进没进上下文。两处看完才轮到抱怨召回算法——多数「不像读过」停在第二处。
最后一条最值钱:这两道门要能被单独量。 取舍定在一个二元组的宽度上,门限挪一分一厘动的就是这一格,靠感觉调不出来,所以要有一张不连库也不连模型、把评测问句实际分数摆出来的表;比对要用配对拿回来的整段,不能拿截断后的清单行。这跟网关那篇的第三条同源(大模型选型之后:网关成了必选项):没有一套能跑的评测集,你就不敢真换。
三层各自解决什么,什么时候不够
| 层 | 只解决这一件 | 到顶的信号 |
|---|---|---|
| 目录(每篇一句说明 + 字段批量配对 + 术语分组) | 让清单能指路:这篇回答什么问题、要不要往下读 | 说明得有人写;几百篇时清单装不进上下文,必须先收敛候选 |
| 预读(命中后真读那一两篇,同分并读、拉不开就不喂) | 让答案落到正文上,而不是落到摘要上 | 判据是字面重合:换了说法的问法会漏,「喂不进」的情况变多 |
| 向量库(embedding 召回,配 rerank 收敛篇数) | 解字面不重合的语义召回,以及规模化后的候选收敛 | 它不补元数据也不替你读正文;前两层没做就上,只得到召回得更准的一堆空标题 |
该动手上向量库的判据,我给三条,够两条再动,只有一条就先把前两层补完:
- 问法与文档用词不重合的那一类占比明显上来:同一个东西三种叫法、口语问法对着书面制度。站内搜索走的是字面那条路(关键词转 LIKE 模式、空格当分隔、跨标题与正文两张表),答不了这类问句;但只有被它反复打断,才值得上一整套索引。
- 字面判据本身到顶:并读三篇还都不对题的情况变多。这是换判据的信号,不是把那一档砍到一篇的信号。
- 需求从「按篇」变成「按段」或「跨篇合成」:一个答案要拼三本手册里的不同条款。这时检索单位已经不是文章,前两层做再多也够不着。
三条能直接抄走的规矩:
- 每篇一句手写说明,独立成一个字段,并给「卡片 / 清单 / 检索摘要」定一条取用顺序:手写说明 > 描述字段 > 正文截断。顺序不写下来,最后一定是正文截断占多数。
- 清单与正文分开取,但每行带一个能拿去读的确切标识,并留一条「当场读掉」的路。只给清单不给路,模型会拿摘要当事实。
- 每一层单独可量:目录层数空说明的行数,预读层查命中那一篇进没进上下文,最后才端到端看答得好不好。只看端到端,你永远不知道是哪一层没干活。
一句收尾
向量库是买得到的能力,目录和预读是你库里那几列字段、和那一次真读了正文的读取。「答得不像读过」这一类,我按这两处查基本就能定位,剩下的那一成才轮到 embedding。先补字段、再补那一次读,最后评估要不要上向量库;顺序反过来,你是拿一套语义索引去盖一个没配对元数据的库。
本文作者:Aryee 发布时间:2026-09-27 13:26
本文出处:ARYEE.cn 固定链接:https://www.aryee.cn/archives/retrieval-catalog-before-vector.html
转载或引用请连这一行一起带走;文中的代码与结论按当时环境成立,组件升级后请以官方文档为准。