知识库建好之后,真正的考验才刚开始:今天对的知识,明天可能就错了。而「过期的正确」比「明显的错误」更危险——因为它看起来仍然可信。这一章讲清知识怎么过期、怎么更新、怎么回滚,以及怎么让「这条还新不新」变成系统能判定的属性。
编辑部注 · 本章是知识引擎的最后一站:前面讲怎么建模、怎么检索、怎么保证可信,这一章讲怎么让这些知识在时间上不腐坏。
先看一个真实的事故:一个内部知识 Agent 回答「公司的报销标准」,给的是三年前的旧制度。
答案本身措辞严谨、引用齐全、置信度看起来很高——唯一的错误是:这份制度已经废止了。用户照着去报销,被财务驳回。比「答错」更糟的是,用户从此不再信任这个 Agent 的「引用」。
问题不在检索、不在生成,在于知识会过期,而系统没有把它当成一个必须持续处理的问题。前面四章讲「知识是什么、怎么取、怎么信」,这一章补上最后一块:知识在时间轴上怎么不腐坏。
一、知识会以三种方式「变旧」
「过时」不是一个笼统的词,它至少有三种不同的机制,处理方式也完全不同:
| 机制 | 例子 | 特征 | 处理手段 |
|---|---|---|---|
| 事实被取代 | 负责人从 A 换成 B;报销标准从 v2 改成 v3 | 旧值不再成立,但「曾经成立」这件事有历史价值 | 新值生效 + 旧值软删除留档(见第 5 章软删除) |
| 事实失效 | 一个临时活动结束了;一次促销只到月底 | 旧值彻底失去意义,无需保留 | TTL 到期自动下线,或标记「已过期」 |
| 推断作废 | 「Q3 下滑因为新客占比上升」——到 Q4 前提变了 | 结论依赖的事实已更新,推理不再成立 | 依据变更时批量标记「待复核」(见第 1 章推断层) |
关键区别在于:「事实被取代」要保留历史(可回溯),「事实失效」可以干净地拿掉,「推断作废」要主动扫描依赖关系。 把三者混为一谈,就会要么把该删的历史留着占地方,要么把该留的证据物理删掉。
这三种「变旧」正好落在知识建模的三层上(见第 1 章):实体层(Entity)和事实层(Fact)的知识会被「取代 / 失效」,而推断层(Inference)的知识会「作废」。所以时效机制不能只盯着事实,还得沿着依赖关系追到推断——这是这一章和第 1 章的分工:第 1 章定「知识的形状」,这一章定「形状怎么在时间里不塌」。
二、TTL:给每条知识一个「保质期」
TTL(Time To Live)(Time To Live,生存时间)是最朴素也最有效的时效机制:给每条知识标一个「到什么时候不再新鲜」。它解决的是「系统不知道这条什么时候该重新确认」的问题。
TTL 不是拍脑袋设一个统一值,而是按知识的「变质速度」分级:
| 知识类型 | 变质速度 | TTL 参考 | 过期后动作 |
|---|---|---|---|
| 组织架构、负责人 | 慢(季度级) | 30–90 天 | 重新拉取主数据确认 |
| 业务指标口径 | 中(随制度变更) | 7–30 天 | 核对口径版本是否仍生效 |
| 价格、库存、活动 | 快(小时/天级) | 1 小时–1 天 | 实时查询,不缓存结论 |
| 推断、分析结论 | 不确定(依赖依据) | 与依据 TTL 挂钩 | 依据失效即作废,见第 1 章 |
很多人把 TTL 理解成「到期就删」,其实它更重要的价值是让「要不要重新确认」变成一个可触发的信号。一条知识 TTL 到期,不等于立刻删——而是触发一次「重新拉取源数据、比对是否变化」。没变就续期,变了就更新。
这比「等用户来问才发现错了」主动得多,也比「无脑定期全量刷新」便宜得多。
三、增量更新:别为了改一条把整个库重刷一遍
知识库会持续变化,但「变化」通常是局部的——今天只改了 3 个指标口径、换了 1 个负责人。如果每次都用「全量重建」应对,成本会失控,而且重建窗口里服务会退化为旧知识。
增量更新(Incremental Update)的核心思想:只更新变化的那部分,并记录「什么变了、为什么变、从哪个版本变到哪个版本」。
四、回滚:改错了要能退回去
知识更新会出错——源数据错了、口径理解错了、自动化批量更新误伤了。这时候「能退回去」比「改得快」更重要。回滚(Rollback)依赖的就是上一节的变更日志和第一节的软删除(Soft Delete)。
from dataclasses import dataclass, field
from datetime import datetime, timedelta
@dataclass
class KnowledgeVersion:
version: int
value: str
updated_at: str
updated_by: str
change_reason: str # 为什么变:口径变更 / 源修正 / 误操作回滚
superseded_at: str | None = None
@dataclass
class KnowledgeItem:
id: str # 稳定主键:如 "metric:conversion_rate"
ttl: timedelta # 保质期
versions: list = field(default_factory=list) # 版本链,最新在末位
def current(self):
"""当前生效版本 = 版本链里最后一条未 superseded 的(被软删除的自动跳过)"""
for v in reversed(self.versions):
if v.superseded_at is None:
return v
return None
def is_fresh(self) -> bool:
cur = self.current()
if not cur:
return False
expires = datetime.fromisoformat(cur.updated_at) + self.ttl
return datetime.now() < expires
def update(self, new_value: str, reason: str, by: str) -> None:
"""新增版本:旧的标记 superseded,而不是物理删除"""
for v in self.versions:
if v.superseded_at is None:
v.superseded_at = datetime.now().isoformat(timespec="seconds")
self.versions.append(KnowledgeVersion(
version=len(self.versions) + 1,
value=new_value,
updated_at=datetime.now().isoformat(timespec="seconds"),
updated_by=by, change_reason=reason,
))
def rollback(self) -> bool:
"""回滚 = 把最新(错误)版本软删除,让 current() 指回上一版"""
if len(self.versions) < 2:
return False
bad = self.versions[-1] # 最新(错误)版本
bad.superseded_at = datetime.now().isoformat(timespec="seconds")
# 不移出列表:它仍在版本链里,但已标记 superseded;
# current() 返回的是「最后一条未 superseded 的版本」,因此自动指回上一版
return True三个必须同时成立、少一个回滚就是空话的环节:
- 版本链:每次更新不是覆盖,而是追加一个新版本、把旧的标
superseded_at。这样任何时刻都能回答「这条曾经是什么值」。 - 变更日志:每次变更记下
why(为什么变)和by(谁/什么系统改的)。没有why的回滚,等于把错误改成一个「不知道为什么」的新状态。 - 回滚本身也要留痕:回滚不是「删掉错误版本」,而是「标记它错误、退回上一版」。这样审计时能看到「曾经改错、又退了回来」的完整轨迹。
一条事实被回滚,但引用它的推断不会自动跟着回滚。比如你把「转化率口径」回滚到 v2,但已经用 v3 口径产出的几条分析结论还躺在推断层里,它们现在引用了一个已经「倒退」的前提。
所以回滚要和第 1 章的「依据变更 → 批量标记待复核」联动:回滚任何一条事实,都要扫描引用了它的推断,一并标记为待复核,否则下游会继续引用一个已经不成立的前提。
五、新鲜度信号:检索时怎么排序
即使知识更新机制都对了,检索时还要面对一个问题:召回了两条都「有效」的知识,但一条是昨天刚更新的、一条是半年前的,该怎么排? 这就需要一个显式的 新鲜度信号(Freshness Signal)。
| 策略 | 做法 | 适用 | 风险 |
|---|---|---|---|
| 纯时间降序 | 越新越靠前 | 新闻、行情这类「新就是好」的场景 | 误伤「老但权威」的稳定知识(如制度原文) |
| 时间衰减加权 | 相关性分数 × 时间衰减因子 | 大多数通用知识库 | 衰减系数要调,调不好要么基本无效、要么盖过相关性 |
| 显式新鲜度标签 | 检索时把「是否新鲜」作为独立过滤/排序维度 | 对时效敏感的业务(价格、库存、口径) | 需要 TTL/更新机制配合,否则标签本身会过期 |
关键原则:新鲜度不能盖过「相关性和可信度」,但也不能被忽略。 最稳妥的做法是把它做成一个独立的排序信号,让它在「相关知识」里再排先后,而不是直接替代相关性排序。这样「半年前那个权威制度」不会被误伤,「昨天刚改的口径」也不会被淹没。
六、常见误区与追问
6.1 误区:知识库建好就一劳永逸,更新是后期的事
错在哪:把知识更新当成上线后「有空再做」的附加项。为什么自然:建库时所有知识都是新鲜的,看不出问题。正确做法:时效机制是知识库设计的一部分,不是补丁。判据:建库时就要给每条知识定 TTL、定更新来源、定回滚方案——否则上线后第一批「过期知识」出现时,你既没有触发更新的机制,也没有退回去的能力,只能手工救火。
6.2 误区:TTL 到期就直接删掉
错在哪:把 TTL 理解成「自动删除」。为什么自然:到期删除看起来是干净的自动清理。正确做法:TTL 到期触发的是「重新确认」,不是「删除」。判据:到期后重新拉源数据比对——没变就续期,变了才更新,只有「事实彻底失效」这类才真的下线。把「重新确认」和「删除」混为一谈,会导致大量本可续期的知识被误删,或反过来,该删的失效知识一直躺着。
6.3 误区:全量重建最省事,增量更新太复杂
错在哪:用「实现简单」掩盖「不可持续」。为什么自然:全量重建确实好写,一次跑完就完事。正确做法:成本要按「长期」算。判据:全量重建成本随全库规模线性增长,且重建窗口内服务退化为旧知识;增量更新成本只随变化量增长。知识库一旦超过某个规模,全量重建的窗口期会成为「服务不可用」的定时炸弹。
6.4 误区:回滚就是「把值改回去」
错在哪:把回滚当成一次普通的数据修改。为什么自然:改回去看起来就是「再写一次旧值」。正确做法:回滚要走版本链,且要联动下游。判据两条:一是回滚后旧版本要能被解释「为什么曾经是这个值、又为什么退回来」;二是引用这条事实的推断要一并标记待复核。只改值不留痕,等于把错误历史抹掉,审计时无从查起。
6.5 误区:新鲜度排序就是「越新越靠前」
错在哪:把新鲜度当成唯一排序键。为什么自然:新的看起来更对,直觉成立。正确做法:新鲜度是相关性之内的二次排序。判据:先按相关性召回,再在同相关的候选里按新鲜度排。直接「越新越靠前」会把「老但权威」的稳定知识(制度原文、主数据)压下去——而这些恰恰是不能被时间衰减误伤的东西。
七、自测
八、小结
| 你要能回答的问题 | 一句话答案 |
|---|---|
| 知识怎么变旧 | 三种机制:事实被取代(留历史)、事实失效(下线)、推断作废(标待复核) |
| TTL 是干什么的 | 给知识一个保质期,到期触发「重新确认」而非「删除」 |
| 为什么增量更新 | 成本随变化量而非全库规模增长,且重建窗口不退化服务 |
| 回滚靠什么 | 版本链 + 变更日志 + 软删除,三者缺一回滚就是空话 |
| 新鲜度怎么排 | 作为相关性之内的二次排序,不替代相关性、也不被忽略 |
知识更新的本质,是把「这条还新不新」从人的记忆变成系统可判定的属性。做成了,知识库才不会在时间轴上悄悄腐坏。本刊编辑部
这是知识引擎的最后一章。到这里,从「知识怎么建模」到「知识怎么不过期」的完整链条就闭环了。
九、参考与延伸
本章的方法论分散在工业实践与规范里,下面按「先看工业实现、再看规范、最后读实践总结」排了三份材料。全站不做原文转载,这里只登记链接与「为什么值得读」。
先看工业实现(别人怎么做版本化与增量)
- dbt 官方文档 · About MetricFlow —— 指标口径的版本化与增量刷新。读本章第二节卡住时来这儿:它把「指标定义」当成可版本化、可增量更新的对象,而不是散落的表注释。
再看规范(来源与版本的本体定义)
- W3C · PROV-O: The PROV Ontology —— 来源(provenance)与版本(wasRevisionOf)的官方本体。只需要看
prov:wasRevisionOf与prov:invalidatedAtTime两个关系——本章的「版本链」和「软删除」在规范里就是它们。
最后读实践总结(工程模式全景)
- Eugene Yan · Patterns for Building LLM-based Systems & Products —— 从评测、RAG 到 guardrails 的工程模式总览。重点看「数据新鲜度与更新」落在流水线的哪一段——本章的 TTL / 增量 / 回滚正是这类模式的具体化。
◇ 面试官会怎么问
共 3 条 · 其中 1 条高频 · 先自己答一遍,再展开对照
一条知识过期了,是直接删掉还是标记失效?为什么? 高频
原因有三:① 可解释性——用户质疑「你为什么觉得口径是 X」时,你要能拿出「2025-03-12 由 A 提供、2025-09-01 被 B 取代」这条链,物理删除就断链了;② 可回滚——新版本可能是错的,标记失效才能随时回退到旧版本;③ 合规——软删除只是默认行为,用户行使「被遗忘权」时仍要能硬删除。
实现上是给每条知识加
superseded_at 字段,删除时只填它而不是从库里抹掉。一个反直觉的点:过期 ≠ 无用——过期知识仍可能含有可用的历史线索,召回时应降权而非直接丢弃,把时效判断权交给模型(给它 written_at 这个依据,而不是替它删掉)。
追问链
- 软删除和回滚是一回事吗?
- 什么情况下必须硬删除?
知识库体量大了以后,为什么全量重建不可行?增量更新难在哪?
增量更新便宜但难,难点是级联失效:一条事实变了,依赖它的推断也要跟着失效。
解法是把依赖关系显式记录(对应知识引擎的「事实层 / 推断层」分层):上游发变更事件,只重算受影响的条目,其余沿用。
核心判据:能不能靠静态知识永久成立——能就不需要 TTL,不能(价格、排期、余额)就一定要带
valid_until。工程折中是变更订阅 + 局部失效,而不是「改一条就全量重来」。
追问链
- 稳定事实和易变事实怎么区分?
- 级联失效如果不处理会怎样?
怎么判断一条召回的知识「还新不新鲜」?能不能只按写入时间倒序排?
正确做法是把新鲜度拆成一组可计算的信号:写入时间、来源的更新频率、是否命中 TTL 过期、被引用的新鲜程度。
召回时把这些信号交给排序层(Rerank)综合判断,而不是硬编码「新的就优先」——因为你无法替用户判断「一条稍旧但来源极权威的知识」和「一条很新但来源可疑的知识」哪个更该信。
本质:把「时效判断权交给模型,把判断依据交给系统——你提供 written_at / source_freshness / ttl_status 这些信号,不替它下结论。
追问链
- 为什么「写入晚」不等于「新鲜」?
- 过期知识直接删掉会丢什么?
结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。