上一章 混合检索、Rerank 与知识可信 召回要宽、排序要准、引用要能被点开验证。这一章把关键词检索与向量检索拼成一条混合链路,用 Rerank 做精排,最后解决"凭什么相信这条知识"的问题。 IV · 知识引擎 · 第 04 章 · 深入 · 约 34 分钟
Agent 工程学习站 做棵大树 出品 主站 beatree.cn 免费 · 无需登录 · 进度存本机
IV 版 · 知识引擎 第 05 章 Freshness and Update
IV · 知识引擎 05 / 22 进阶

知识更新与时效性

Freshness and Update
预计阅读 30 分钟
难度 进阶
关键词 知识更新 · 时效性
本机状态 未读

知识库建好之后,真正的考验才刚开始:今天对的知识,明天可能就错了。而「过期的正确」比「明显的错误」更危险——因为它看起来仍然可信。这一章讲清知识怎么过期、怎么更新、怎么回滚,以及怎么让「这条还新不新」变成系统能判定的属性。

编辑部注 · 本章是知识引擎的最后一站:前面讲怎么建模、怎么检索、怎么保证可信,这一章讲怎么让这些知识在时间上不腐坏。

先看一个真实的事故:一个内部知识 Agent 回答「公司的报销标准」,给的是三年前的旧制度。

答案本身措辞严谨、引用齐全、置信度看起来很高——唯一的错误是:这份制度已经废止了。用户照着去报销,被财务驳回。比「答错」更糟的是,用户从此不再信任这个 Agent 的「引用」。

问题不在检索、不在生成,在于知识会过期,而系统没有把它当成一个必须持续处理的问题。前面四章讲「知识是什么、怎么取、怎么信」,这一章补上最后一块:知识在时间轴上怎么不腐坏

一、知识会以三种方式「变旧」

「过时」不是一个笼统的词,它至少有三种不同的机制,处理方式也完全不同:

知识变旧的三种机制
机制例子特征处理手段
事实被取代负责人从 A 换成 B;报销标准从 v2 改成 v3旧值不再成立,但「曾经成立」这件事有历史价值新值生效 + 旧值软删除留档(见第 5 章软删除)
事实失效一个临时活动结束了;一次促销只到月底旧值彻底失去意义,无需保留TTL 到期自动下线,或标记「已过期」
推断作废「Q3 下滑因为新客占比上升」——到 Q4 前提变了结论依赖的事实已更新,推理不再成立依据变更时批量标记「待复核」(见第 1 章推断层)

关键区别在于:「事实被取代」要保留历史(可回溯),「事实失效」可以干净地拿掉,「推断作废」要主动扫描依赖关系。 把三者混为一谈,就会要么把该删的历史留着占地方,要么把该留的证据物理删掉。

这三种「变旧」正好落在知识建模的三层上(见第 1 章):实体层(Entity)事实层(Fact)的知识会被「取代 / 失效」,而推断层(Inference)的知识会「作废」。所以时效机制不能只盯着事实,还得沿着依赖关系追到推断——这是这一章和第 1 章的分工:第 1 章定「知识的形状」,这一章定「形状怎么在时间里不塌」。

「过时」是三种不同的病,药不一样 都叫「旧了」,但「被取代 / 已失效 / 作废」的处理手段完全不同 ① 事实被取代 负责人 A → B;口径 v2 → v3 「曾成立」有历史价值 新值生效 + 旧值软删除留档 可回溯「曾经是 A,何时变成 B」 ② 事实失效 临时活动结束;促销到期 旧值彻底失去意义 TTL 到期下线 / 标记「已过期」 干净拿掉,不占检索与上下文 ③ 推断作废 结论依赖的事实已更新 推理不再成立 依据变更 → 批量标记「待复核」 主动扫描依赖,而非等它被问到时才慌 判据:先问「这是被取代、失效、还是作废」,再决定留不留 被取代 → 留历史(软删除);失效 → 干净下线(TTL);作废 → 标待复核(扫依赖)。 混为一谈的后果:该删的历史占着地方,该留的证据被物理删除,该复核的推断被当成现行结论。
图 1 「过时」不是一个动作,是三种病。分清「被取代 / 已失效 / 作废」,才能对每种用对药——这是知识更新设计的第一道分水岭。

二、TTL:给每条知识一个「保质期」

TTL(Time To Live)(Time To Live,生存时间)是最朴素也最有效的时效机制:给每条知识标一个「到什么时候不再新鲜」。它解决的是「系统不知道这条什么时候该重新确认」的问题。

TTL 不是拍脑袋设一个统一值,而是按知识的「变质速度」分级

不同知识类型的 TTL 参考(不是绝对值,是量级)
知识类型变质速度TTL 参考过期后动作
组织架构、负责人慢(季度级)30–90 天重新拉取主数据确认
业务指标口径中(随制度变更)7–30 天核对口径版本是否仍生效
价格、库存、活动快(小时/天级)1 小时–1 天实时查询,不缓存结论
推断、分析结论不确定(依赖依据)与依据 TTL 挂钩依据失效即作废,见第 1 章
TTL 的真正价值不在「自动删除」

很多人把 TTL 理解成「到期就删」,其实它更重要的价值是让「要不要重新确认」变成一个可触发的信号。一条知识 TTL 到期,不等于立刻删——而是触发一次「重新拉取源数据、比对是否变化」。没变就续期,变了就更新。

这比「等用户来问才发现错了」主动得多,也比「无脑定期全量刷新」便宜得多。

三、增量更新:别为了改一条把整个库重刷一遍

知识库会持续变化,但「变化」通常是局部的——今天只改了 3 个指标口径、换了 1 个负责人。如果每次都用「全量重建」应对,成本会失控,而且重建窗口里服务会退化为旧知识。

增量更新(Incremental Update)的核心思想:只更新变化的那部分,并记录「什么变了、为什么变、从哪个版本变到哪个版本」

全量重建 vs 增量更新 左:一次改 3 条也要把整库重刷;右:只动变化的部分,并留变更记录 全量重建 重建整个索引(哪怕只改了 3 条) 重建窗口内服务退化为旧知识 成本随全库规模线性增长,不可持续 增量更新 只重算变化的 3 条,其余原地不动 变更日志:什么变了 / 为什么 / 从 v 几到 v 几 成本随「变化量」增长,而非全库规模 只动 3 条 增量更新的三个硬前提 1. 每条知识有稳定主键,能定位「哪一条变了」——否则无从谈起增量。 2. 索引支持局部重算(向量库按 id upsert),不是只能整体重建。 3. 变更日志是审计与回滚的基础——没有它,改错了都回不去。
图 2 增量更新的收益不是「快」,而是成本随变化量增长、而非随全库规模增长——这是知识库能持续维护下去的前提。它的三个硬前提里,变更日志尤其重要,因为它是回滚的唯一依据。

四、回滚:改错了要能退回去

知识更新会出错——源数据错了、口径理解错了、自动化批量更新误伤了。这时候「能退回去」比「改得快」更重要。回滚(Rollback)依赖的就是上一节的变更日志和第一节的软删除(Soft Delete)

knowledge_versioning.pypython
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/更新机制配合,否则标签本身会过期
新鲜度是「相关性之内」的二次排序,不是替代相关性 先按相关性召回,再在同相关候选里按新鲜度排——避免「越新越靠前」误伤权威旧知识 ① 相关性召回 关键词 + 向量,先挑「相关」 半年前的制度 / 昨天的口径 只要相关都进候选,不看新旧 这一步「宁可宽」 ② 同相关内按新鲜度排 只对「已相关」的候选排先后 昨天的口径 > 半年前的旧口径 但「老却权威」不会被时间挤掉 这一步「排序要准」 ③ 输出 相关 + 新鲜 两条都要 缺一样就失真 反例:「越新越靠前」会怎样 把新鲜度当唯一排序键,昨天的「小道消息」会压过半年前的「制度原文」。 后果:相关性被新鲜度盖过,权威稳定知识被误伤——这正是纯时间降序的最大风险。 判据:先问「相关吗」,再在同相关里问「新吗」 新鲜度是相关性内的 tie-breaker,不是独立的第一排序键。
图 3 新鲜度排序的正确姿势是「相关性之内二次排序」:先召回相关候选,再在同相关里按新鲜度排先后。把它当成唯一排序键(越新越靠前)会把权威但略旧的稳定知识误伤——这是时效机制里最容易犯的一个错。

关键原则:新鲜度不能盖过「相关性和可信度」,但也不能被忽略。 最稳妥的做法是把它做成一个独立的排序信号,让它在「相关知识」里再排先后,而不是直接替代相关性排序。这样「半年前那个权威制度」不会被误伤,「昨天刚改的口径」也不会被淹没。

六、常见误区与追问

6.1 误区:知识库建好就一劳永逸,更新是后期的事

错在哪:把知识更新当成上线后「有空再做」的附加项。为什么自然:建库时所有知识都是新鲜的,看不出问题。正确做法:时效机制是知识库设计的一部分,不是补丁。判据:建库时就要给每条知识定 TTL、定更新来源、定回滚方案——否则上线后第一批「过期知识」出现时,你既没有触发更新的机制,也没有退回去的能力,只能手工救火。

6.2 误区:TTL 到期就直接删掉

错在哪:把 TTL 理解成「自动删除」。为什么自然:到期删除看起来是干净的自动清理。正确做法:TTL 到期触发的是「重新确认」,不是「删除」。判据:到期后重新拉源数据比对——没变就续期,变了才更新,只有「事实彻底失效」这类才真的下线。把「重新确认」和「删除」混为一谈,会导致大量本可续期的知识被误删,或反过来,该删的失效知识一直躺着。

6.3 误区:全量重建最省事,增量更新太复杂

错在哪:用「实现简单」掩盖「不可持续」。为什么自然:全量重建确实好写,一次跑完就完事。正确做法:成本要按「长期」算。判据:全量重建成本随全库规模线性增长,且重建窗口内服务退化为旧知识;增量更新成本只随变化量增长。知识库一旦超过某个规模,全量重建的窗口期会成为「服务不可用」的定时炸弹。

6.4 误区:回滚就是「把值改回去」

错在哪:把回滚当成一次普通的数据修改。为什么自然:改回去看起来就是「再写一次旧值」。正确做法:回滚要走版本链,且要联动下游。判据两条:一是回滚后旧版本要能被解释「为什么曾经是这个值、又为什么退回来」;二是引用这条事实的推断要一并标记待复核。只改值不留痕,等于把错误历史抹掉,审计时无从查起。

6.5 误区:新鲜度排序就是「越新越靠前」

错在哪:把新鲜度当成唯一排序键。为什么自然:新的看起来更对,直觉成立。正确做法:新鲜度是相关性之内的二次排序。判据:先按相关性召回,再在同相关的候选里按新鲜度排。直接「越新越靠前」会把「老但权威」的稳定知识(制度原文、主数据)压下去——而这些恰恰是不能被时间衰减误伤的东西。

七、自测

本章自测第 1 题是核心考点
Q1一条「负责人从 A 换成 B」的知识,正确更新方式是?
C。「被取代」的事实有历史价值(能回答「曾经是谁、何时换的」),所以不能物理删除,要软删除留档。A 直接删会丢历史,B 并列会让系统同时返回两个负责人造成冲突,D 用 TTL 处理「被取代」是把两种病混为一谈。
Q2TTL 到期的正确动作是什么?
B。TTL 的价值在「让重新确认变成可触发信号」,不是「自动删除」。到期不等于失效,大多数时候知识没变,重新确认后就能续期。A 会把本可续期的知识误删,D 则完全失去 TTL 的意义。
Q3回滚一条事实后,最容易漏掉的是哪一步?
D。事实被回滚后,引用它的推断不会自动跟着回滚——那些推断现在引用了一个已经「倒退」的前提。不回滚下游,就会继续产出基于旧前提的结论。A/B/C 都重要,但 D 是最容易被漏掉、后果也最隐蔽的一环。

八、小结

你要能回答的问题一句话答案
知识怎么变旧三种机制:事实被取代(留历史)、事实失效(下线)、推断作废(标待复核)
TTL 是干什么的给知识一个保质期,到期触发「重新确认」而非「删除」
为什么增量更新成本随变化量而非全库规模增长,且重建窗口不退化服务
回滚靠什么版本链 + 变更日志 + 软删除,三者缺一回滚就是空话
新鲜度怎么排作为相关性之内的二次排序,不替代相关性、也不被忽略

知识更新的本质,是把「这条还新不新」从人的记忆变成系统可判定的属性。做成了,知识库才不会在时间轴上悄悄腐坏。本刊编辑部

这是知识引擎的最后一章。到这里,从「知识怎么建模」到「知识怎么不过期」的完整链条就闭环了。

九、参考与延伸

本章的方法论分散在工业实践与规范里,下面按「先看工业实现、再看规范、最后读实践总结」排了三份材料。全站不做原文转载,这里只登记链接与「为什么值得读」。

先看工业实现(别人怎么做版本化与增量)

  • dbt 官方文档 · About MetricFlow —— 指标口径的版本化与增量刷新。读本章第二节卡住时来这儿:它把「指标定义」当成可版本化、可增量更新的对象,而不是散落的表注释。

再看规范(来源与版本的本体定义)

  • W3C · PROV-O: The PROV Ontology —— 来源(provenance)与版本(wasRevisionOf)的官方本体。只需要看 prov:wasRevisionOfprov:invalidatedAtTime 两个关系——本章的「版本链」和「软删除」在规范里就是它们。

最后读实践总结(工程模式全景)

面试官会怎么问

共 3 条 · 其中 1 条高频 · 先自己答一遍,再展开对照

一条知识过期了,是直接删掉还是标记失效?为什么? 高频
默认标记失效(软删除),不物理删除
原因有三:① 可解释性——用户质疑「你为什么觉得口径是 X」时,你要能拿出「2025-03-12 由 A 提供、2025-09-01 被 B 取代」这条链,物理删除就断链了;② 可回滚——新版本可能是错的,标记失效才能随时回退到旧版本;③ 合规——软删除只是默认行为,用户行使「被遗忘权」时仍要能硬删除。
实现上是给每条知识加 superseded_at 字段,删除时只填它而不是从库里抹掉。
一个反直觉的点:过期 ≠ 无用——过期知识仍可能含有可用的历史线索,召回时应降权而非直接丢弃,把时效判断权交给模型(给它 written_at 这个依据,而不是替它删掉)。

追问链

  1. 软删除和回滚是一回事吗?
  2. 什么情况下必须硬删除?
加分点:把「软删除是默认、硬删除是合规兜底」和「过期降权而非丢弃」两点说全,是这道题的完整度所在。
知识库体量大了以后,为什么全量重建不可行?增量更新难在哪?
全量重建的问题在成本随库体量线性上涨:每次上游数据变化都把整库重新抽取/重算一遍,延迟和算力都扛不住。
增量更新便宜但难,难点是级联失效:一条事实变了,依赖它的推断也要跟着失效。
解法是把依赖关系显式记录(对应知识引擎的「事实层 / 推断层」分层):上游发变更事件,只重算受影响的条目,其余沿用。
核心判据:能不能靠静态知识永久成立——能就不需要 TTL,不能(价格、排期、余额)就一定要带 valid_until
工程折中是变更订阅 + 局部失效,而不是「改一条就全量重来」。

追问链

  1. 稳定事实和易变事实怎么区分?
  2. 级联失效如果不处理会怎样?
加分点:说出「级联失效」和「依赖关系要显式记录」,说明你理解的不只是「少算一点」。
怎么判断一条召回的知识「还新不新鲜」?能不能只按写入时间倒序排?
不能只按写入时间倒序——写入晚不代表新鲜(可能是把旧数据重新写了一遍),来源权威也不代表永远新鲜。
正确做法是把新鲜度拆成一组可计算的信号:写入时间、来源的更新频率、是否命中 TTL 过期、被引用的新鲜程度。
召回时把这些信号交给排序层(Rerank)综合判断,而不是硬编码「新的就优先」——因为你无法替用户判断「一条稍旧但来源极权威的知识」和「一条很新但来源可疑的知识」哪个更该信。
本质:把「时效判断权交给模型,把判断依据交给系统——你提供 written_at / source_freshness / ttl_status 这些信号,不替它下结论。

追问链

  1. 为什么「写入晚」不等于「新鲜」?
  2. 过期知识直接删掉会丢什么?
加分点:「新鲜度是特征集而非布尔值」「判断权给模型、依据给系统」是这道题的两个加分点。
答这类题的通用结构

结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes
    全书终点 去看学习进度与复习计划 总完成度、到期复习队列、全部笔记汇总,以及导出 JSON 备份,都在这一页。 复习队列 · 笔记汇总 · 导出备份