Published on

数据过期后,Agent 应该怎样刷新,而不是反复重试?

Authors
  • avatar
    Name
    hpoenixf
    Twitter

数据过期后,Agent 应该怎样刷新,而不是反复重试?

本文回看一次受控刷新:如何在保留来源拒绝和只读边界的前提下,生成、去重并原子发布一份新证据。

本文复述的是 2026-09-04 的受控刷新窗口,属于特定跨 root/app 方案的工程记录;不代表当前所有 Research Runtime 路径都采用同一刷新机制。

一、来源被拒后,普通重试不会造出新证据

来源拒绝后,问题不在于怎样把不可用文案说得更委婉,而在于怎样取得一份新的、经过批准的证据。对同一个未批准来源重复调用,不会改变来源身份;沿用旧快照,也不能把旧事实变成当前事实。

这次机制补上的正是安全刷新路径:使用固定操作、限制数据 scope、按 scope/fingerprint/时间窗去重,并且只有验证通过后才原子发布。来源仍被拒绝或刷新失败时,系统继续保留不可用状态。

在一次受控验证窗口内观察到:产物、工具、流式、持久化、重放、回读六个环节中,离线分组通过四比四,在线分组通过七比七,六类 fail-closed 行为全部按预期关闭,而非放行。

该结论仅描述该窗口的观测结果,不做普遍保证。换窗口、换环境、换负载后需重新采集,不可直接外推。

这些数字只对应该次受控验证窗口,不能外推成当前生产能力;细节见文末的历史记录。

二、两个核心概念

2.1 来源可信度:答案从哪里来

来源可信度回答的是:这个数字或结论,到底来自哪条可信通道。不是“模型觉得合理”,而是“哪份受信数据、在什么时间、以什么口径给出”。

可信通道通常同时满足三点。第一,职责明确,只读的行情与持仓通道不混入推测。第二,时效明确,新鲜或过期都有标记,过期数据不冒充现状。第三,可回查,每条关键断言能关联到具体证据,而不是孤立文本。

在这个模型里,模型文本默认是建议性的、不可信的。只有经过 allowlist 的受信工具输出,才能为数值与状态背书。文本再流畅,也不能替代这条链路。

这意味着评审重点不在措辞,而在链路。先问来源是否在名单,再问口径是否一致,最后才看表达是否清晰。顺序一反,质量观就歪了。

口径一致同样关键。同一指标可能有不同统计口径,不注明口径的数字即使来源受信,也无法直接用于决策。来源、时间、口径三者缺一不可。

2.2 不可用:刷新流程的起点

不可用表示当前证据不能支撑目标结论。它应保留目标和缺口,但不自动触发任意检索;只有通过明确授权的刷新入口,才可能生成新证据。

三、主流程:拒绝、不可用、受控刷新

下面是简化后的主流程。它有三条关键分支:来源拒绝、状态降级为不可用、受控刷新后恢复。另有一条显式未采集分支,见图例说明。

3.1 先定义五个机制词:它们在图中何处生效

机制词定义在主流程中的挂载点
候选限定仅接受根层候选,不接受旁路写入受控刷新入口:非候选直接拒绝
固定操作变更动作类型固定且受限,不支持任意写入刷新执行中:动作越界即失败
规范原子发布要么整体可见,要么整体不可见,不存在半可见刷新收尾:发布失败则整体回退
去重按范围、指纹、小时窗口去重发布前检查:命中即复用,不重复做
同会话精确一次同一会话内连续处理精确一次,不多做不少做去重通过后:保证会话内单次生效
graph TD
RootCandidate --> FixedOperations
FixedOperations --> AtomicPublish
AtomicPublish --> Deduplicate
Deduplicate --> ExactlyOnce

主图中的受控刷新节点,即展开为上面这条子链。任何一环不满足,刷新即按失败处理,回到不可用并记审计。

graph TD
UserQuestion --> ParseEvidenceNeed
ParseEvidenceNeed --> CheckSourceAllowlist
CheckSourceAllowlist --> RejectUntrustedSource
RejectUntrustedSource --> ReportUnavailable
CheckSourceAllowlist --> CheckEvidenceFreshness
CheckEvidenceFreshness --> ReportUnavailable
CheckEvidenceFreshness --> PublishCitedAnswer
ReportUnavailable --> CheckRefreshAllowed
CheckRefreshAllowed --> RemainUnavailable
CheckRefreshAllowed --> ControlledRefresh
ControlledRefresh --> CheckRefreshResult
CheckRefreshResult --> PublishCitedAnswer
CheckRefreshResult --> RemainUnavailable
ControlledRefresh --> MarkNotCollected
MarkNotCollected --> RemainUnavailable

关系或判断条件:

  • CheckSourceAllowlist → RejectUntrustedSource:否:未命中。
  • CheckSourceAllowlist → CheckEvidenceFreshness:是:命中。
  • CheckEvidenceFreshness → ReportUnavailable:过期或缺失。
  • CheckEvidenceFreshness → PublishCitedAnswer:新鲜且完整。
  • CheckRefreshAllowed → RemainUnavailable:否:普通问答不隐式触发。
  • CheckRefreshAllowed → ControlledRefresh:是:显式重试或定时采集且小时窗未超限。
  • CheckRefreshResult → PublishCitedAnswer:成功。
  • CheckRefreshResult → RemainUnavailable:失败。
  • ControlledRefresh → MarkNotCollected:需真实环境或超出受控窗。

这个流程的关键在于分叉点都很显式。拒绝发生在证据进入之前,不可用发生在回答生成之前,刷新发生在独立受控通道中。

任何一条分支都不会悄悄绕过。不可信标签进不来,过期数据顶不上去,刷新动作也不可混入普通问答路径。

图例补充:虚线分支为显式未采集出口。当刷新需要真实库、多进程或外部网关质量等受控窗外条件时,不硬判成功或失败,而是标记未采集并保持不可用。

触发规则只有两类合法来源:用户显式重试,以及定时采集任务。两者共享同一小时窗口限频。普通问答永不隐式触发刷新。

四、一次来源拒绝的教训

在早期联调中,出现过一个看似无害的内部来源标签,例如某类内部便签标签。它来自系统内部注释,本意是方便调试,却被误当成可引用的证据来源。

用结构化字段表达就是:source_label 为该内部标签,allowlisted 为假,因此 trusted_hit 按定义为假。allowlist 机制直接拒绝了它,相关状态声明统一降级为不可用。

这个拒绝看起来“过于严格”,却避免了一类系统性风险:内部便签、调试备注、模型转述,披着来源的外衣进入答案。宁可当时多几次不可用,也不能开这个口子。

事后看,严格反而加快了进度。因为边界清晰,联调时不用争论个案该不该放行,只需核对是否命中名单。争议从主观判断变成了客观检查。

与之对应的提案从零开始推进。到评审时累计形成二十五个任务项,逐项落实来源边界、降级文案、审计字段,而不是先上线再补。

任务拆细还有一个好处:每个任务都有可验收的证据。来源、降级、刷新、审计各自独立验证,避免把所有风险堆到最后一次集成时爆发。

五、从输入到结构化输出

为了让不可用可被机器处理,输入与输出都做了结构化约束。下面是教学级示意,不含任何真实数据。

{
  "input": {
    "question": "当前组合状态如何?",
    "need_numbers": true,
    "need_advice": false
  },
  "evidence": {
    "source_label": "internal-note",
    "allowlisted": false,
    "trusted_hit": false,
    "freshness": "unknown",
    "source_id": null
  },
  "output": {
    "status": "unavailable",
    "numbers": [],
    "disclosure": "当前无可用受信证据,未做状态断言。",
    "next": "可触发一次受控刷新后重试",
    "audit": {
      "action_tendency": "none",
      "execution_allowed": false
    }
  }
}

其中 trusted_hit 的定义是:当且仅当 source_label 非空且 allowlisted 为真,trusted_hit 才为真。它是派生字段,不可手填。need_numbers 和 need_advice 是这份历史示意中的类型化输入,不表示 Runtime 可以用规则识别自由文本意图。

伪代码只有四步,优先级固定不变:

trusted_hit = allowlisted and source_label in allowlist
if not trusted_hit: return unavailable
if 证据缺失或过期: return unavailable
if 数值无受信根:数字留空 + 固定披露
else: 生成带引用回答 + 审计落盘

上例中 source_label 为内部便签类标签,allowlisted 为假,故 trusted_hit 为假,直接走来源拒绝分支。

输入侧只关心问题是否需要数值与操作倾向。输出侧永远带状态、披露、下一步与审计。动作倾向在不可用路径下恒为无,不产生任何执行暗示。

这种约束让前端很好处理:有数字就展示数字与引用,无数字就展示披露与刷新入口,不存在半吊子文本需要猜测。

对排查同样友好。线上问题只需看结构化字段:来源是否命中、新鲜度为何值、动作为何清零。几秒即可定位是证据问题还是生成问题。

对审计更有价值。每次回答都留下相同的字段结构,事后可以批量统计不可用比例、刷新成功率、降级原因分布。质量从感觉变成了数字。

六、运行规则:输入、优先级、失败与安全

下图是运行期的操作规则摘要。阅读顺序即执行优先级,越靠前越先判断,不可跳步。

维度规则摘要
输入一:问题类型是否涉及数值与状态;涉及则强制走证据门
输入二:证据命中仅名单内通道算命中,其余视为未命中
输入三:新鲜度新鲜可用,过期未知缺失按不可用处理
优先级一:来源门来源拒绝优先于生成逻辑,先拦后答
优先级二:降级门门未过则生成固定披露,不自由发挥
优先级三:刷新门仅显式重试或定时采集可触发,小时窗限频,普通问答永不隐式触发

前六条解决“何时停”。输入决定是否进门,优先级决定停在哪一步。任何一步不满足,后续步骤不再执行,直接走向固定披露。

维度规则摘要
失败一:网关异常依赖失败直接降级,不猜测不顶旧数
失败二:部分证据缺关键字段即整体不可用,不拼接结论
降级一:文案固定不可用文案模板化,避免误解
降级二:动作清零动作倾向为无,可执行标志恒为假
安全一:只读边界问答路径无执行无变更能力
安全二:审计必记记录原因判定与下一步建议

后六条解决“停后怎么办”。失败侧保证不硬撑,降级侧保证表达一致,安全侧保证无副作用且可追溯。两组规则合在一起,才是完整闭环。

补充四条原子性规则。第一,候选证据只接受根层候选,不接受旁路写入。第二,变更操作固定且受限,不支持任意写入。第三,发布采用规范原子发布,要么整体可见,要么整体不可见。第四,去重按范围、指纹、小时窗口执行,同一会话内保证精确一次的连续处理。

受控刷新在受控窗口内观测到:根层示例六比六通过,应用层示例三十一比三十一通过。该计数是示例范围,不做普遍保证。它没有复用执行级的每日作业流,而是走独立受控通道。

触发规则重申一次:只有用户显式重试与定时采集任务能触发刷新,共享小时窗口限频。普通问答无论证据多缺,都永不隐式触发。

隔离设计值得单独强调。问答需要高频、低延迟、可重试,而资金链路需要低频、强校验、重审计。两者混用只会互相拖累,分开反而各自更稳。

去重策略也是为稳服务的。按范围限定影响面,按指纹避免重复劳动,按小时窗口防止频繁刷新。同一会话内的连续处理保证精确一次,不多做也不少做。

七、三个对比:什么时候该说不知道

案例一:过期快照 vs 坦白不可用

假设受信通道短暂不可达,手头只有一份过期快照。一种做法是拿旧数顶上,加一句“仅供参考”。另一种做法是直接返回不可用,说明缺少新鲜证据。

前者看似体贴,实则危险。用户很可能忽略后缀,把旧数当成现状。后者虽然多了一步刷新操作,却守住了时效语义。过期数据只能解释过去,不能断言现在。

过期场景有明确的禁用说法。禁止写“根据最新数据,当前状态为……”,因为此时并无新鲜证据。应写“暂无新鲜受信证据,未做当前状态断言,可显式重试刷新”。

案例二:内部便签 vs 正式拒绝

假设某个内部标签写着看似正确的结论,引用它只需一行代码。一种做法是为了通过率先放行,事后再补 allowlist。另一种做法是当场拒绝,保持不可用直到合法来源就绪。

前者打开了先例:今天放一个便签,明天就会放更多。后者用一次短期的不可用,换来了长期的来源纯洁性。这正是前文拒绝事件的泛化教训。

案例三:猜测补全 vs 等待刷新

假设证据缺了一个关键字段,但其余字段齐全。一种做法是按常识补全缺口,让回答看起来完整。另一种做法是承认缺口,触发受控刷新后再答。

前者把模型推测混入了受信断言,审计也无法区分哪句来自证据、哪句来自猜测。后者保留了可恢复性,刷新成功即可解除,失败则继续保持诚实状态。

八、已验证与未采集:一张诚实的清单

设计评审与运行验证分开记录。前者只说明方案是否评审通过,后者只说明受控窗口内观测到什么。

设计评审项状态
来源边界与降级文案已评审:提案从零起步,累计二十五个任务项
候选、操作、发布、去重、会话语义已评审:机制定义落实到流程与规则
运行验证项受控窗口观测
产物到回读六环节端到端离线四比四,在线七比七
六类关闭式失败行为全部按预期拒绝或降级
不可信来源标签拒绝名单拦截并降级为不可用
受控刷新示例分层根层示例六比六,应用层示例三十一比三十一

其中根层与应用层是示例架构分层标签,用来区分底层证据候选与上层问答装配。六与三十一是该次示例范围的用例计数,不是通用规模声明。

未采集项状态
真实数据库与多进程行为未采集:未做真实库压测与并发验证
外部服务质量与长尾延迟未采集:真实网关质量指标缺失
生产级长时间运行表现未采集:仅覆盖受控验证窗口

这张图特意保留了未采集部分。因为验证窗口之外的结论,不应被当成已证明。不可用的精神同样适用于复盘写作:没测过,就写没测过。

这种写法看似示弱,实则增强了可信度。读者能清楚区分已证事实与待补短板,不会高估系统边界。后续补测也有明确清单。

未采集项主要集中在真实运行环境。受控验证能证明逻辑正确,却证明不了高并发、长延迟、真实网关抖动下的表现。这部分必须留白。

特别说明两点。第一,受控刷新未复用执行级每日作业流,两者隔离是刻意设计。第二,会话内精确一次连续处理仅覆盖同一会话的受控范围,不扩展为全局恰好一次承诺。

九、结论:刷新本身也要有边界

受控刷新解决的是“怎样合法地产生并发布新证据”,不是“如何把所有不可用都自动修好”。触发来源、scope、fingerprint、时间窗、原子发布和失败审计都必须保持显式;超出该验证窗口或缺少真实环境证据的能力继续记为未采集。

证据边界与材料来源

  • 受控证据刷新复盘:记录只读入口、candidate 校验、原子发布、scope/hour 去重和受控验证结果。
  • 本文数字仅描述该历史窗口的测试样本;真实 PostgreSQL、多进程、provider 质量和生产长时间运行仍未采集。