跳到主要内容

LLM 提示词中 “现在” 的五种定义

· 阅读需 13 分钟
Tian Pan
Software Engineer

一名客服代理告诉用户“根据我们截至今天的最新定价”,并引用了上季度的价格表。系统提示词正确地注入了 today is {current_date}。检索层提取了具有最高时效性评分(freshness score)的文档。模型回答得信心十足。每个组件都严格执行了其规定的任务,但用户得到了一个错误的答案,而值班工程师却无法复现这个问题,因为当他们在晚上 9 点回溯追踪记录(trace)时,“今天”已经变成了不同的一天。

这并不是一个罕见的 Bug。这是一种几乎存在于每一个生产级 LLM 流水线中的失效模式,因为“现在”隐式地存在于提示词的五个不同层级中,而这些层级是由不同的人在不同时间、针对不同的“现在”定义编写的。只要请求是从前端用户会话同步运行的,这些层级通常能达成一致。一旦请求被重放用于调试、在夜间进行批处理、在固定于 3 月的评估框架(eval harness)中运行,或者被排队并在一个小时后消费,各层级就开始产生分歧——模型产生了一个在提示词内部逻辑自洽但在外部环境中错误的答案。

产生这种 Bug 的团队并不缺乏严谨性。他们缺乏的是将“现在”视为一等变量(first-class variable)的词汇量。修复方法并不是“在系统提示词中添加日期”——大多数团队已经这么做了。修复方法是意识到在单个 LLM 调用中,“当前时间”至少有五种不同的含义,为每种含义命名,并在衔接处进行调和。

层级 1:系统提示词中注入的日期

如今几乎所有的生产环境提示词都包含类似 Today is {current_date}The current date is {now} 的内容。这是最显眼的时间变量,也是最看似无害的——直到你追问 current_date 究竟是哪个环节的当前日期。

是用户的挂钟时间(wall clock)?编排器的挂钟时间?请求入队时间,还是出队时间?是评估框架冻结的锚点时间?大多数团队从未明确区分这些,因为对于绝大多数情况——在几百毫秒内处理的前端请求——所有这些都在容差范围内。Bug 会一直潜伏,直到某些因素拉长了请求创建与提示词构建之间的时间。一个在死信队列(dead-letter queue)中存放了两天后重试的请求,会将“今天”注入为重试当天的日期,而用户提供的“昨天发生了什么”指的仍然是原始请求发起前的一天。此时,提示词在自欺欺人。

这种规范做法是不要将 current_date 视为字符串,而是一个元组:(timestamp, source, timezone)。来源很重要,因为基于 request_enqueue_time 构建的提示词在重放时表现稳定,而基于提示词构建时的 Date.now() 构建的则不然。明确选择一个定义,记录这个选择,并停止称其为“今天”。

层级 2:少样本示例中的隐式日期

少样本示例(Few-shot examples)几乎总是带有时间背景,即使提示词作者没有意识到自己嵌入了背景。一个写着“2026 年第四季度预测”的示例会将模型锚定在一个六个月后可能就过时的财政背景中。一个使用具体日期的示例(“用户在 3 月 14 日提问,回答参考了截至 3 月 13 日的事件”)教会了模型一种相对日期模式,而每当这些具体日期过去时,该模式就会变得错误。

更隐蔽的版本是那些看起来不受时间影响但实际上并非如此的示例。“用户想要续订订阅”不带显式日期,但示例回答中提到了“即将到来的定价变更”——这一表述在示例编写时是正确的,但在定价变更上线后就不再正确了。三个月后,模型仍在基于公司不再认为是“即将到来”的事件生成答案。

这种规范做法是为少样本示例标注编写日期版本,将其视为会腐烂的代码,并按节奏重构它们,而不是让它们无声无息地老化。“少样本时效性”(few-shot freshness)指标——即当前提示词中示例的中位年龄——是一个单行仪表盘,可以在模型行为恶化之前发现这种衰退。

层级 3:检索层的时效性窗口

如果你正在使用 RAG,那么在你的检索评分器中存在第三种“现在”的定义。时效性加权检索器通过语义相似度和新鲜度的某种组合对文档进行排序,而新鲜度分数是根据某个参考点计算的——通常是检索时的 now()。最近关于 RAG 时效性的研究表明,仅依靠语义接近度进行排序对于永恒的事实有效,但一旦问题具有时效性就会失效;典型的修复方法是将语义相似度与时效性先验(recency prior)融合,使较新的项目在时间敏感的查询中排在旧项目之前。

这种修复引入了一个新变量。时效性先验锚定在一个时钟上——而这个时钟现在成了你流水线中的第三个“现在”。大多数实现会默然使用检索时的 now(),这意味着晚上 9 点重放的请求所得到的时效性排名结果,与上午 9 点相同请求的结果不同,即使底层语料库没有变化。文档时间戳漂移过了陈旧阈值;从用户的角度来看是“近期”的证据,从重放的角度来看变成了“陈旧”的。

更深层的失败在于检索器的“近期”与系统提示词的“今天”不一致。用户询问“截至今天的最新定价”,系统提示词说今天是 5 月 10 日,但检索器返回的文档是根据其相对于三天前请求入队时间的新鲜度评分的。模型尽职尽责地将这份旧文档描述为最新文档,用户得到了错误信息,而追踪记录却无法解释原因。调试生产中 RAG 系统的工程师曾写道,这种静默失效模式比幻觉更严重,因为每一步看起来都“正常工作”了。

这种规范做法是记录每个查询使用的时效性窗口,以便事后你可以追问“从该请求的角度来看,什么是‘近期’”,并向检索器传递一个显式的 as_of_time,而不是让它直接调用 now()

第 4 层:基础模型的训练截止日期

基础模型有其自身隐含的“现在”,固定在其预训练或最后一次微调的数据截止日期。这是最稳定的一层——在单个模型的单次部署期间它不会改变——但它也是大多数团队最容易忽视的一层,而且它与其他层的不一致性也是最难修复的。

截止日期之所以重要,是因为它决定了模型在没有检索的情况下“知道”什么。一个训练数据截止到 2026 年 1 月的模型,会充满自信地对 2026 年 1 月之前的事件发表言论,对 2 月的事件含糊其辞地应对,而在那之后的事件则会拒绝回答或产生幻觉。如果你的系统提示词写着“今天是 2026 年 5 月 10 日”,而你的检索层没有提供任何关于近期产品发布的信息,模型就会退回到它的先验知识中——而它的先验知识认为现在是 1 月。于是,模型会写下“即将到来的第二季度发布会”,而实际上该发布会在两个月前就已经交付了。

一个广为人知的失败案例是:当 Agent 在没有任何其他上下文的情况下被询问当前日期时,它会返回其训练截止年份,而不是系统提示词提供的插值日期。系统提示词是正确的;模型只是更信任它的先验知识而不是提示词。这里的解决方法不是放弃提示词层面的日期注入,而是让这种不一致变得显性化——在发布的报告中,诸如“如果你的训练数据建议的年份与系统提示词的日期不同,请以系统提示词为准”之类的明确指令,可以显著减少这种失败模式。

这种规范做法是将训练截止日期与请求时间之间的差值作为一项部署指标进行跟踪。当这个差值超过模型的容错范围(通常是几个月)时,你需要更强大的检索覆盖来弥补,否则模型会悄悄地用其先验知识替代你的数据。

第 5 层:用户自己的时间引用

第五层是最混乱的一层,因为它存在于你无法控制的输入中。如果用户说“今天早上的会议”、“下周的截止日期”或“我们上季度签署的合同”,他们就在提示词中编码了自己隐含的“现在”——而这个“现在”是他们输入消息的时间,并不一定是请求到达模型的时间。

对于前台聊天会话,这个差距小到可以忽略不计。但对于异步作业——例如用户给 Agent 发邮件、运行时间较长的批处理任务、定时报告——这个差距会变成数小时或数天。用户的“今天早上”是周二早上。Agent 在周三下午处理请求。模型会根据系统提示词的插值日期(周三)来解释“今天早上”,并自信地回答一个用户并非本意的事件。

这是大多数团队放弃的一层,因为“用户的意图”感觉难以捉摸。事实并非如此。规范做法是在摄取时捕获用户的提交时间戳,并将其作为一个明确的字段在提示词中传递——用户在 {user_submission_time} 提交了此消息——并指示模型根据该时间戳而不是请求处理时间来解析相对引用。当变量被命名且指令明确时,模型在这方面表现得异常出色;失败的原因从来不是推理能力,而是缺失了上下文。

实践中的样子

一个经过时间连贯性审核的提示词不会只有一个日期变量。它有多个变量,每个都有命名,每个都源自记录在案的地方,且每个在构建上都保持一致:

  • request_enqueue_time — 用户请求进入系统的时间,用于解析用户的相对时间引用
  • prompt_construction_time — 提示词组装的时间,用于检索新鲜度评分(明确传递给检索器,而不是通过 now() 隐式获取)
  • eval_anchor_time — 仅出现在评估上下文中,用于固定时间,使半年前的评估仍具有实际评分意义
  • corpus_freshness_window — 检索器应用的新鲜度窗口,按查询记录
  • model_training_cutoff — 关于所部署模型的静态事实,呈现在提示词中,以便模型在发现其先验知识可能与提供的数据不一致时进行标记

评估框架(eval harness)值得特别关注。评估数据集随时间积累并成为团队的共有知识——2024 年的黄金数据集在 2026 年仍在为 LLM 评分是一个常见模式,现在的最佳实践建议除非重新验证,否则在 90 天后将黄金数据行标记为陈旧。但数据集的陈旧只是问题的一半;另一半是评估提示词本身包含一个“当前日期”,如果该日期根据框架运行时间进行插值,会导致 3 月编写的评估在 6 月失败,而原因与模型退化毫无关系。将 eval_anchor_time 固定在数据集的编写日期,可以让评估针对一个固定的时刻进行推理,而不是随墙上时钟漂移。

一个值得添加的回归测试:使用日期偏移包装器重新运行提示词,并断言答案仅在应改变的地方发生改变。将“今天”向前推进一年,看看会发生什么。浮现出的 Bug — 硬编码了日期的 Few-shot 示例、针对错误锚点评分的检索器、自信地从已陈旧的先验知识中推导出的模型响应 — 全都是那些可能会在不被察觉的情况下发布到生产环境的 Bug。

架构实现

在你的提示词中,“现在”(Now)是一个具有多重含义的变量,就像“会话”(session)或“用户”(user)一样。如果一个团队允许 current_user 在五个不同的层级中代表五种不同的含义,他们肯定无法交付该流水线。然而,同一个团队却经常交付包含五个不同 current_time 定义的系统,并在智能体引用上季度价格却坚称是“截至今天”时感到惊讶。

时间层级并非提示词作者的对手 —— 它们只是在按设计运行。每一层都在利用其本地时钟执行本地任务。漏洞存在于层与层之间的衔接处,而修复方法并不在任何单一层级中。这需要一种自律:显式地命名“现在”,从有文档记录的地方获取每个实例,并让各层通过结构化设计而非巧合达成一致。

触发此漏洞的是提示词作者从未建模的枯燥运维事件:回放(replay)、批处理(batch)、评估(eval)、异步(async)和重试(retry)。它们也是生产流量默认生成的事件。如果一个流水线只有在每个时钟相互误差保持在几百毫秒之内时才能正常运行,那么这个流水线只适合演示,在实际运维中必将失败。将“现在”视为复数。将其作为数据传递。在衔接处进行调和。当你不再假装模型只有一个时钟时,模型就会停止“时空穿越”。

References:Let's stay in touch and Follow me for more thoughts and updates