1. 扁平结构的失效机理
把文档切块、嵌入、入库之后,块与块之间只剩下相似度这一个关系:没有从属、没有版本、没有有效期。结构和时间被一起去掉,检索能做的只有"找相似的",而相似不等于正确、不等于最新、不等于属于同一层。
三种失效由此产生。
重复挤占。 同一份文档被不同的人上传三次,得到三组近乎相同的片段。它们的相似度几乎一样高,会同时进入前 k 条,把名额占满。检索算法默认每条结果独立,而重复条目的相似度高度相关,这个假设在这里失效。
新旧并存。 旧版政策没有下线,和新版一起被召回。系统没有"这条已被取代"的信息,因此无法在下发前剔除,模型只能自己从两条矛盾的说法里挑一条。
层次丢失。 "退款流程"与"退款政策"在向量空间里很近,但前者是操作步骤、后者是条款依据。扁平的块无法表达"这是某个条款下的一个步骤",检索也就无法按层次导航。
加上第四个工程后果:更新一次文档要重刷整批嵌入,成本高到没人愿意改文档,库逐渐腐烂。
1.1 为什么重复会互相挤占
重复条目挤占排名的机制值得算一下。假设查询与三份近乎相同的片段相似度都是 0.82,与一份真正相关的其他片段相似度是 0.80。取前 2 条时,两条重复项占满名额,真正相关的被挤掉。
这里的问题不在相似度排序错了,而在"多样性"没有被纳入排序。检索算法默认每条结果独立,而重复条目的相似度是高度相关的。
常见的缓解手段有三种。按来源去重(同一文档只保留一条);按内容指纹去重(文本哈希相近的合并);做多样性重排(在相似度相近的候选里优先选来源不同的)。三种的成本依次上升,效果也依次更好。
第一种最便宜但不够:同一份文档的不同版本来源相同,去重后仍然只剩一条,而这条可能是旧版。
2. 三种组织方式
2.1 树状抽象
把文档切成叶子块,把语义相近的叶子聚类,为每类生成一个更高层的摘要作为父节点,递归向上,形成一棵从细节到概括的树。检索可以在任意层级进行。
这类结构解决的是"从概念钻进细节"的查询。问"某指令集是什么",先在高层摘要里定位到相关章节,再沿树向下取到具体指令的说明。扁平检索做不到这一点,因为它只能返回若干平级的片段,无法表达"先看整体再看局部"。
成本可以估。设叶子节点 \(L\) 个,每层聚类把节点数减少 \(c\) 倍(通常取 5 到 10),则树高 \(h \approx \log_c L\),总节点数约 \(L \cdot c/(c-1)\)。\(L = 23000\)、\(c = 8\) 时,树高约 5 层,总节点约 26000 个。
摘要调用次数等于非叶节点数,约 3000 次。这个量级与块数同阶,所以树状抽象的索引成本大约是基础嵌入成本的两到三倍。
更大的代价在更新。改一个叶子,要重算它到根的整条路径:\(h\) 个摘要。改一批文档时,路径会重叠,实际重算次数介于"改动的叶子数"与"叶子数乘树高"之间。这个成本让很多团队在半年后放弃树状结构——效果仍然好,只是没人愿意承担更新的代价。
2.2 实体关系图
把文档知识建模成实体与关系构成的三元组网络。实体是人物、组织、概念、术语,关系是实体之间的联系。
这类结构解决两种查询。一是多跳:问"我的医生所在医院的地址"需要沿"用户→医生→医院→地址"走三步,扁平存储要么多次检索再拼接,要么根本表达不了。二是同名消歧:两个"张医生"靠关系边连到不同的人和机构,在图里天然是两个节点。
代价有两处。索引期要抽取实体与关系,再对紧密的实体群做社区发现与摘要。更根本的代价是语义降级:把自然语言转成三元组会丢掉条件与时间——"如果下周还下雨,我就取消去海边,改成去博物馆"拆成三元组后只剩孤立的事实,条件逻辑全部消失。
语义降级的成因是三元组的表达能力有限。主谓宾三个位置装不下"在什么条件下""到什么时候为止""如果不成立怎么办"。这些信息在自然语言里靠从句与连接词承载,抽取时被丢掉了。
一个缓解办法是给关系边加属性:时间范围、成立条件、置信度。代价是抽取成本上升,而且抽取时就要判断"这条关系的有效期到什么时候",模型往往判断不准。
所以图适合作为专项索引,不适合作为通用存储。常见组合是:自然语言保存核心信息以保留语义完整,结构化字段承担索引与过滤,图用在需要多跳与消歧的垂直场景。
2.3 文件系统
不建独立索引结构,用目录表达从属、用文件名表达实体,所有条目映射为可寻址的路径。
这类结构解决的是"人可读、可维护"的问题。纯文本意味着用户可以打开、修改、修正;可以进版本控制、diff、回滚;可以用现有的搜索工具直接查。
代价在组织纪律上。文件系统本身不提供语义,如果条目之间没有链接,除了全文扫描与向量检索之外无从导航——知识越多,平铺的文件反而越难找。要让这套结构真正可用,必须有两条纪律。
第一,条目之间互相链接。提到另一个条目时给出指向它的链接,形成双向可达的引用网络。
第二,有入口页与索引页。每个目录有一份索引,说明这一层有什么、从哪开始读。
还有一条实践细节:不同模型主动建立链接的意愿差别很大。能力强的模型写新条目时会自发回指已有条目、顺手更新索引;不少模型只会孤立地追加。所以"每新增一个条目,先检索并链接到相关已有条目,并更新所在目录的索引页"这条要求必须写进写入提示,不能指望模型自觉。
三层摘要的加载方式可以叠加在这套结构上:每个条目维护一句话摘要、一份概览与全文;检索先看摘要判断相关性,需要时加载概览,确定要用了才读全文。这套做法与技能系统的渐进披露、与记忆的索引加主题文件是同一个模式。
2.4 三种方式的成本对比
| 方式 | 索引期成本 | 查询期成本 | 更新成本 | 可维护性 |
|---|---|---|---|---|
| 树状抽象 | 每层聚类加摘要,约为嵌入成本的 2–3 倍 | 低,逐层下降 | 高,改叶子连带改祖先 | 中 |
| 实体关系图 | 抽取加社区摘要,高 | 中,需要图遍历 | 中,新增实体与边 | 中 |
| 文件系统 | 几乎为零 | 低,目录遍历加快照 | 低,改一个文件即可 | 高 |
这张表解释了一个常见现象:团队一开始选树状抽象,效果很好,但半年后没人愿意更新知识库。原因是更新成本太高——改一个事实要重算它所在分支的所有摘要。文件系统看起来最朴素,但更新成本最低,长期维护的可行性最高。
可行的组合是按更新频率分层:稳定的主体知识用树状抽象或图,频繁变动的操作细节用文件系统。两层之间靠链接连接,检索时先查稳定层定位,再查变动层取细节。
3. 什么时候需要结构化
三种结构都不是默认选项。混合检索(稠密加稀疏加重排)已经能覆盖大多数需求。判断标准是查询的形状:
| 查询形状 | 需要的结构 |
|---|---|
| 找到包含某信息的片段 | 混合检索足够 |
| 跨文档综合(比较两份材料在某方面的差异) | 树状抽象,或预生成的对比摘要 |
| 多层次导航(从整体架构到具体参数) | 树状抽象 |
| 实体关系(A 与 B 是什么关系) | 实体关系图 |
| 同名消歧(两个同名的人分别是谁) | 实体关系图 |
| 需要人工维护与版本控制 | 文件系统 |
判断顺序可以简化成三个问题:这个查询需要多跳吗?需要聚合吗?需要人改吗?三个都不需要,混合检索就够了。
3.1 两个判断的量化形式
"需要聚合"可以量化:设单次检索能覆盖 \(k\) 条,而回答需要 \(M\) 条才能完整,则当 \(M \gg k\) 时必须靠预生成。
举例:知识库有 500 条工单,问"这类申请的通过率是多少"。取前 20 条,看到的通过率可能与真实值差很远;即使调大到 100 条,仍有 400 条没看到。这类问题的 \(M = 500\),远大于任何合理的 \(k\)。
"需要多跳"可以量化:设查询需要的跳数 \(h\),单次检索只能覆盖一跳,则至少需要 \(h\) 次检索加中间的实体对齐。当 \(h \ge 3\) 时,纯检索方案的失败率上升明显——每一步都可能对错实体,而错一步后面全错。
4. 增量更新
更新有两条路径,各自解决不同的问题。增量更新处理"刚出现一条新证据,该对当前知识作什么局部修改";定期整理处理"多次局部正确之后累积出的全局问题"。
增量更新的做法可以概括成一句话:把知识库当代码库,每次变更当一次提交。
这条类比带来的不只是版本控制,还有一整套已经验证过的流程:
- 提议方从原始证据中发现新事实、冲突或过期内容,在工作分支上提交一个尽可能小的差异。它不会把最新一次对话直接追加到末尾:先检索相关已有条目,再增删改,同时维护链接、索引与时间元数据。
- 审核方拿到变更前的知识、差异与原始证据,独立检查每个新断言是否有证据支撑、是否遗漏限定条件、是否与其他条目冲突、删除或改写是否过度。审核不通过时给出指向具体证据与位置的意见,避免笼统地说"再改改"。
- 双方迭代到收敛,设置最大轮次或成本预算。超出上限转人工,不默认放行。
- 合入后重建受影响的索引。索引是可重建的派生物,仓库里已审核的知识才是真源。
两个角色都应当是智能体而不仅仅是两次固定的模型调用。提议方往往要主动搜索其他相关条目,审核方要追溯证据、对比多份文档、运行检查。这需要工具,而现成的 Coding Agent 通常就能胜任。
有一条经验值得单独说:提议方与审核方最好用能力相近但来自不同家族的模型。不同的训练数据与推理习惯能降低两者在同一处犯同类错误的概率。能力不宜悬殊,否则审核方跟不上提议方的复杂度。这种异源互审增加独立性,但不能替代原始证据——审核方应主要核对证据与差异,不沿着提议方的结论再讲一遍。
权限上要强制分工:提议方只能写工作分支,审核方只读证据并提交审核结果,只有合并流程能更新主库与线上索引。
4.1 增量更新的最小差异
"提交一个尽可能小的差异"这句话需要落到具体形状上,否则会被理解成"随便改改"。
一条新证据进来时,最小差异由四个判断决定。
改动面判断。 一条"退款申请现在需要上传物流单号"可能涉及三个已有条目:退款流程(多一步上传)、退款材料清单(多一项材料)、退款审核时限(起算点仍然是"材料齐全后",不受影响)。前两个要改,第三个不改。判据是"这条证据是否改变该条目的断言",而不是"它是否与这个条目相关"。
条件保留。 新条目要写清"从某日起""针对线上订单"。不写条件的代价是可预期的:三个月后线下订单的客服会照着这条执行。
来源绑定。 附上通话记录的标识。没有来源,后续无法判断是规则变了还是当时理解错了。
下游更新。 清单条目改动后,流程条目里指向它的链接要保持有效;新增独立条目时,索引页要加一行。
四步做完,差异通常只有十几行。它比"把通话记录摘要追加到库末尾"多花十分钟,但它让下一次检索能命中正确条目,也让三个月后能判断这条规则的来源与范围。
反面的做法是把整段摘要追加成一条新记忆。短期看信息没丢,长期看库里多了一条与三个条目都部分重复的记录,检索时会与它们竞争排名。这正是「为什么重复会互相挤占」描述的成因。
4.2 为什么增量更新不能只追加
只追加的做法有三个隐含假设,三个都不成立。
假设一:旧内容会自然过期。实际上不会——没有有效期标记的内容永远"有效",检索时会与新内容一起被召回。
假设二:检索能靠相似度区分新旧。实际上不能——同一事实的两种说法相似度极高,检索无法判断哪个更新。
假设三:内容变多不影响精度。实际上会——重复互相挤占就是直接后果。
三个假设不成立,所以"只追加"必然导致知识库腐败。修补的办法在写入时就维护结构:替换旧条目、标记有效期、维护链接与索引,不走只追加的路子。更频繁地全量重建只是把问题推后。
5. 定期整理
增量更新的盲区是它每次只看到一个局部。长期运行后,多次局部正确的修改仍可能累积出全局问题:同一事实散落在多个条目里,新旧说法并存,摘要逐渐偏离原始证据,目录结构不再适合当前规模。
定期整理至少包含三项工作。
去重、去旧与合并。 全量扫描,识别语义重复、已被取代、过度碎片化的条目,删除或归并。同时重建链接、入口页与索引页,必要时拆分过大的文件、合并过小的文件、调整目录层次。要删除的是可服务的知识表达,不是下层只增不改的原始证据。
回到原始数据核查。 这一项最容易偷懒也最重要。只在已有摘要之间互相改写,早期的遗漏与误读会一代代传下去。整理过程需要逐段对照原始对话、执行轨迹、业务文档与工具输出,检查旧摘要是否遗漏关键事实、丢失否定词或时间条件、把推测当成事实。大规模知识库可以按目录、时间或主题分批,但必须保留覆盖清单,确保最终覆盖全量,不能随机抽样。
冲突解决与场景限定。 遇到互相矛盾的说法,不简单地"保留最新"或让模型猜。要追溯各自的原始来源,检查它们是否分别在不同时间、对象、地域、任务或前置条件下成立。都有效时应当把各自的适用场景写进知识,删掉其中一条会丢信息;证据不足时保留冲突与待确认状态,不强行收敛。
5.1 摘要代际漂移
"回到原始数据核查"这一项值得展开,因为它对应一个不易察觉的失效模式。
设原始证据为 \(E\),第一代摘要 \(S_1 = f(E)\)。如果下一次整理只看 \(S_1\) 而不看 \(E\),得到 \(S_2 = f(S_1)\)。多代之后,\(S_n = f^{(n)}(E)\)。
摘要是有损变换,信息量单调下降:\(I(S_1) \le I(E)\),\(I(S_2) \le I(S_1)\)。但更严重的是误差累积——如果 \(S_1\) 把"必须提供订单号"误读成"建议提供订单号",\(S_2\) 会继承这个错误,而且后续每一代都可能进一步弱化它("建议"变"可选")。
这个过程不可逆:后面几代无法恢复"必须"这个强度,因为"必须"的信息在 \(S_1\) 里已经丢了。
防范办法只有一条:整理时必须回到 \(E\),不能只基于上一代摘要。代价是整理成本高(要读原始材料),但这正是"定期整理"存在的理由——它是唯一能纠正代际漂移的环节。
实用的折中是:日常整理基于摘要(快),定期整理(例如每季度一次)回到原始证据(慢但纠错)。
5.2 覆盖清单怎么用
"分批但不漏"这件事靠的不是自觉,是一份可核对的清单。
做法是:整理开始时先枚举全量条目,按目录或主题切成若干批,每批记录条目标识与范围。每批完成后标记状态,最后核对"已处理条目数等于总条目数"。
清单要记录三件事,缺一件就无法核对:
- 批次的定义(哪些标识属于这一批);
- 处理状态(未处理、已处理、跳过及跳过理由);
- 产物位置(这批的差异提交在哪个分支或哪次提交里)。
第三项在出问题时最关键。如果某次整理之后发现某个主题的知识消失了,需要能定位是哪个批次、哪次提交造成的。
Codex 在记忆整理上用了另一条思路达到同样目的:整个记忆目录是一个 git 工作区,每次整合前生成"自上次成功整合以来的差异",整合后重置基线。差异文件本身就是覆盖清单——它列出了所有变动,且天然带可回滚能力。
这条思路可以推广:与其维护一份独立的清单,不如让变更本身可枚举、可回滚。前者依赖记录纪律,后者依赖工具保证。
5.3 整理之后的回放
整理完成后必须回放一组典型检索与问答用例。原因是整理改变了结构,而结构改变会让某些原本能找到的知识变得不可见——不是内容丢了,是路径变了。
回放集不需要很大,十几条就够,但要覆盖三种情况:原本能答对的(确认没退化)、原本答错的(确认修好了)、边缘主题的(确认没有因为批次划分被漏掉)。
这一步常被跳过,因为整理者倾向于认为"我只是重组,没删内容"。实际上重组会改变索引、链接与摘要层级,三者任一变化都可能让某条知识从可达变成不可达。
有一个具体的失效例子:整理时把一个过大的条目拆成三个,但只更新了索引中的一行(指向其中最大的那个)。原本检索能命中整条,现在只有三分之一可被命中。内容一点没丢,可达性损失了三分之二。
5.4 触发条件
整理可以由三种信号触发:时间(每周或每月)、规模(新增条目数超过阈值)、质量(检索失败率或冲突数上升)。
时间触发最可靠但可能过度——没有新变化时整理是浪费。规模与质量触发更精准,但需要监控指标。实践中常见组合是"月度定时加质量触发":每月至少跑一次,出现质量下降时提前跑。
6. 失效过滤与隔离
失效内容要在下发前过滤。 一篇被新版取代的旧政策若仍留在库里,检索时可能与新版一起被召回。生产系统通常给每个片段附版本号与生效/失效时间,在检索阶段就过滤掉已失效的内容,或在摘要里显式标注"此条已于某日废止"。
权限过滤要下推到检索层。 知识库面向所有用户共享,但"所有用户"不等于"所有内容对所有人可见"。关键原则是按调用者的权限过滤,越权文档不能进入上下文。一旦敏感内容进入了模型的上下文,就很难保证它不以某种形式出现在回答里。
多租户要隔离索引与元数据。 不只是逻辑上的过滤,物理上也要分开,避免一个租户的查询检索到另一个租户的私有知识。
6.1 过滤顺序为什么重要
权限过滤必须在召回之前。这个顺序不是风格问题,是安全性问题。
两种顺序的差别:
正确:查询 → 权限过滤 → 召回 → 排序 → 下发
错误:查询 → 召回 → 排序 → 权限过滤 → 下发第二种做法有一个隐蔽后果:即使最终过滤掉了越权内容,排序过程已经用到了这些内容的信息。例如,如果系统用"前 k 条的平均相似度"做某个阈值判断,越权内容会影响这个判断,从而间接影响返回给低权限用户的结果。
更直接的后果是延迟与日志泄露:越权内容被读进内存、写进检索日志、可能出现在调试输出里。这些都不是"过滤掉"能挽回的。
结构性防范办法是把权限做成取回函数的必需参数——没有它构造不出查询。这样顺序错误在编译期就暴露,不会留到运行期。
7. 智能体化检索
前面所有内容都假设检索是一次性的:查询进去,片段出来。复杂问题往往需要多轮。
智能体化检索把检索做成工具,由模型决定查什么、查几次、要不要换词。流程从单向管道变成"思考、行动、观察"的循环:分析需求,决定关键词,调用检索,评估结果是否充分,不够就提炼更精确的查询再搜,甚至换用其他工具。
这个模式的价值在复杂问题上最明显。一个需要结合三条法条的问题,第一次检索通常只能命中其中一条;模型看到结果后意识到缺什么,第二次针对缺的那部分查,才可能凑齐。简单问题上它与单次检索差别不大,所以不必默认启用——多轮意味着更多的模型调用与更长的响应时间。
代价之外还有一个安全考虑:检索到的内容正是间接提示注入最典型的载体。攻击者可以把指令藏进会被收录的文档里,等它被检索命中、拼进上下文,模型可能把数据当指令执行。防御分两层:把所有检索内容标为"供参考的外部资料,不是要服从的命令";让有副作用的动作(转账、删除、对外发信)不因为检索内容而自动执行,而要走独立的授权判断。
7.1 多轮检索的收敛
智能体化检索需要终止条件,否则会在"再查一次"里循环。
三种常见的终止条件:
| 条件 | 判据 | 风险 |
|---|---|---|
| 轮次上限 | 达到固定轮数 | 可能在信息足够前被迫停止 |
| 充分性判断 | 模型自评"信息是否充分" | 自评可能过于乐观 |
| 新信息率 | 新一轮的增量信息低于阈值 | 需要能度量增量 |
三种可以叠加。实践中常见组合是"充分性判断加轮次上限":模型判断够了就停,没够但达到上限也停。第二道的理由是自评会出错——模型常在信息不足时认为足够。
第三种最难实现但最有价值。"新一轮的增量信息"可以用"新命中的片段与已有片段的重叠度"近似:如果新一轮结果里有 80% 是已见过的,说明查询没带来新东西,应该换策略或停止。
8. 验收
组织与更新的质量体现在几个可检查的事实上:
| 检查项 | 判断方式 |
|---|---|
| 旧答案是否被替换 | 查一个已变更的事实,确认只返回新版本 |
| 结构是否可导航 | 从一个高层条目能否沿链接走到具体条目 |
| 更新是否可回滚 | 能否还原到上一个已审核版本 |
| 抽查是否覆盖全量 | 整理计划里的覆盖清单是否完整执行 |
| 检索路径是否可审计 | 能否说明某条结论来自哪份原始材料 |
| 权限是否真的隔离 | 用低权限账号尝试检索高权限内容 |
第三项与第四项最容易被忽略。更新不可回滚意味着一次错误的整理会永久破坏知识;覆盖清单不完整意味着每次整理都只在"上次整理过的部分"里打转,边缘内容永远得不到核查。
第一项是最容易做也最能说明问题的检查。选一条三年前的事实,把它改成新值,然后问一个相关问题:如果检索返回旧值,说明整条更新链路(写入、索引重建、失效过滤)有一处断了。
第六项需要一个专门的测试账号。用低权限账号跑一组只有高权限可见的查询,确认全部返回空。这个测试应当作为上线门禁的一部分,因为它验证的是安全属性。
这三件事——找得准、改得动、退得回——里,存储容量早就不是约束,真正难的是「摘要代际漂移」:摘要的摘要会让信息单调衰减,过程不可逆,而它不会在任何一个单项检查里暴露出来。