k=1 召回 9/10,k=2 就到 10/10; 而 k 从 2 加到 10,召回一条没多,上下文成本从 222 token 涨到 1091 token(4.9 倍)。 再往上加的是成本,不是准确率。
上一节把嵌入模型配好了。这一节把文档塞进库里,并且定下检索时捞几段——这个数直接决定成本和效果。
一、一个知识库由三样东西决定
嵌入模型(怎么把文字变向量)、文档集合(放了什么进去)、切块参数(一段切多大、 重叠多少)。 这三样里,改动最频繁的是文档集合,影响最大的却常常是切块。一段切得太碎, 检索回来的片段缺上下文;切得太大,一段里混了好几个主题,命中精度掉。 经验值:中文 300 到 500 字一段,重叠 10% 到 15%。这个范围在绝大多数问答场景里都够用。 📋 实操清单(要在浏览器里点): 第一步,Page Assist 里打开知识库面板,点新建。 第二步,起名时一个库只放一个主题(见本节第三节)。 第三步,上传文档(PDF、md、txt 都行),等它显示向量化完成。 第四步,回设置里确认「检索条数」这个值——默认往往是 4,改成 3 或 5 再测。
二、k 不是越大越好
k 是每次检索捞回来的片段数。直觉上「多捞几段总没坏处」,但 Demo 的结果是反的:
| k | 召回 | 平均上下文成本 | 占 8k 窗口 |
|---|---|---|---|
| 1 | 9/10 | 131 token | 1.6% |
| 2 | 10/10 | 222 token | 2.7% |
| 3 | 10/10 | 407 token | 5.0% |
| 5 | 10/10 | 622 token | 7.6% |
| 10 | 10/10 | 1091 token | 13.3% |
| k=2 就到顶了,k 从 2 加到 10 召回一条没多,成本翻 4.9 倍。多出来的 8 段全是噪声, | |||
| 而且它们会挤占上下文窗口(下一节会看到这件事有多严重)。 | |||
| 所以默认值取 3 到 5 是有道理的:它留了一点余量,又不至于把窗口吃光。 想再提升, | |||
| 方向是「重排」而不是「多捞」——先捞 20 段,再用一个小模型重排取前 3,比直接捞 10 段准得多。 | |||
| 提醒一句:这个结论跟文档长度分布强相关。 | |||
| Demo 里最长一段 600 字, | |||
| 它一个人就吃掉 900 token,在 k=3 里占两个名额。你的库里如果全是长文档,k 要相应调小。 |
三、一个库只放一个主题
混库是效果变差最常见的原因,而且很难查。把「产品手册」和「人事制度」放进同一个库, 问「报销标准」时,检索可能捞回一段产品参数——因为它词面上也像。 分开建库的好处不只是准确率:可以分别设 k、分别换嵌入模型、分别重建索引。 人事制度一年改一次, 产品手册一周改一次,混在一起就得整体重建。 代价是调用时要先判断「这个问题该查哪个库」。 简单做法是按入口分(客服页面只挂客服库),而不是让模型自己去猜。 什么时候可以混?文档之间有强引用关系的时候——比如同一份合同的正文和附件。 因为它们本来就要一起读,分开反而丢上下文。
四、什么时候要重建索引
三件事变了就要重建:换了嵌入模型(向量空间变了,旧向量作废)、改了切块参数、文档大改。 只加了几个新文档,一般增量更新就行,不用整体重建。 但要注意:增量更新之后,旧文档的向量还是老模型算的——如果模型换过,就会出现两套向量混在一起, 相似度不可比。 还有一个坑:删文档要删干净。很多工具只从列表里移除,向量还在库里, 于是检索照样能捞到「已删除」的内容——用户看到过时答案,你却找不到源头。 建议做法:重建后拿同一批测试问题跑一遍命中率。数字没掉,才算这次重建没搞坏; 掉了就先别切,回头查切块或者文档。
五、动手 Demo:k=2 就到 10/10,加到 10 成本翻 4.9 倍
用上一节那份 12 段知识库和 10 个问题,对 k 取 1/2/3/5/10, 分别算「正确文档有没有进前 k」的召回率,以及这次检索带进上下文的平均 token 量。
for k in (1, 2, 3, 5, 10):
rec, cost = 0, 0.0
for j, (q, gi, _) in enumerate(QS):
top = np.argsort(-cos(A * idf, Q[j] * idf))[:k]
rec += (gi in top) # 正确文档进没进前 k
cost += tok[top].sum() / len(QS) # 这 k 段多少 token
print(k, rec, cost, 100.0 * cost / 8192)
实跑输出:
--------------------------------------------------------------------
召回条数 k:k=2 就到 10/10,加到 10 成本翻五倍
--------------------------------------------------------------------
k 召回 平均上下文成本 占 8k 窗口
----------------------------------------
1 9/10 131 token 1.6%
2 10/10 222 token 2.7%
3 10/10 407 token 5.0%
5 10/10 622 token 7.6%
10 10/10 1091 token 13.3%
k 从 1 加到 2,召回 9/10 涨到 10/10;**从 2 加到 10,一条没多,成本翻了 4.9 倍。**
这就是知识库默认值普遍取 3 到 5 的原因:**再往上加的是成本,不是准确率。**
这个结论跟文档长度分布强相关。这份库最长一段 600 字,
**它一个人就吃掉 900 token**,在 k=3 里占两个名额。
五行里最关键的是第二行:k=2 就到 10/10 了,后面三行召回一模一样。
成本却一路涨:222 → 407 → 622 → 1091 token。k=10 比 k=2 贵 4.9 倍,换来的准确率是零。
注意这里量的是召回,不是最终答案对不对。
另外一头还有一件事:捞回来的 8 段噪声,会把真正有用的那 2 段挤到提示词中间,
模型更容易忽略它们。所以 k 太大不只浪费钱,还可能让准确率下降。
诚实说明:这份库只有 12 段,所以 k=2 就覆盖了 10 个问题的答案。真实库有几千段,k 需要更大一些。
要学的是这条曲线的形状——先陡后平,不是这个具体数字。
还有一个读数上的提醒:占 8k 窗口 那一列看起来很小(13.3%),但那是按 8k 算的。
如果实际 num_ctx 是 2048,13.3% 会变成 53%。下一节专门讲这件事。
30 秒实验
- 把 k 的上限从 10 加到 12(等于全库),看召回和成本各是多少。 全库检索的成本,就是「长文本方案」的成本。
- 把 12 段里最长那段复制成两段(制造重复),再看召回。 重复内容会同时占掉多个名额,这是 k 被浪费的另一种情形。
- 给
cost那一列加上「单段最大 token」,看 k=3 里最大一段占多少。 一段顶两段的时候,k 的有效值就变小了。
常见误区
- 「k 调大点保险」。召回很早就到顶了,多捞的段只会挤占窗口、稀释注意力。先测曲线再定值。
- 「所有文档放一个库里省事」。混库会让检索捞错主题,而且没法分别设置、分别重建。
- 「删了文档就没了」。很多工具只删列表不删向量,检索照样能捞到——删完要验证一次。
自测三问(点开对答案)
1. 知识库的效果由哪三样东西决定? —— 嵌入模型、文档集合、切块参数(段大小与重叠)。 2. 为什么 k 太大可能让准确率下降? —— 噪声片段会挤占上下文窗口,还会把真正有用的片段挤到提示词中间,模型更容易忽略中间的内容。 3. 换了嵌入模型为什么必须重建索引? —— 因为向量空间变了,旧向量和新向量不可比。混在一起算相似度,排序就是错的。库建好了、k 定了,接下来是那个最容易出问题的环节:提示词。它和「上下文窗口」共用一个预算。
- ☐ (本节完,下一节 6.1.3 添加提示词)