跳到主要内容

你的 Prompt 时钟是正确性边界,而非日志字段

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个调度代理将客户的入职电话订在了周二,而不是周三。调查花费了两天时间。Prompt 没问题。模型没问题。日历工具也没问题。错误在于系统 Prompt 携带了一个早一小时的 current_time 字段,当时请求正通过一个在 UTC 午夜前刚刚构建的缓存前缀(cached prefix)进行路由。当代理解析出“明天上午 10 点”并调用预订工具时,“明天”所指的日期对于东京的用户来说已经是“今天”了。

代理根本无法察觉。它没有任何感知手段。LLM 没有时钟。它们只有你在 Prompt 中提供给它们的字符串,并且它们会像对待用户问题一样权威地对待这个字符串——也就是说,完全信任,不加怀疑,也没有第二个来源可以进行交叉比对。

大多数团队在抽象层面都知道这一点,但仍然将注入的时间戳视为日志字段:某种有则更好、渲染到系统 Prompt 中提供上下文、不属于任何人的明确责任、不属于任何人的正确性边界的内容。这种构想是错误的。时间戳是一个正确性边界。每一个依赖于“现在”的代理行为——调度、过期、重试窗口、“最近”、“明天”、“五分钟内”、检索文档的新鲜度检查——都运行在你生成的时间管道之上,并继承了该管道所拥有的每一个 Bug。

你的时钟欺骗代理的四种方式

有四种不同的 Bug 会在模型内部产生错误的“现在”,且它们的失败方式各不相同。修复了其中一个并发布产品的团队通常会认为他们解决了整个类别的问题。

时区默认值。你的服务器运行在 UTC,因为这是明智的服务器默认设置。有人在 Prompt 组装代码中写了 datetime.now(),但没有提供时区参数,因为“服务器是 UTC,所以 UTC 就行”。对于当地时间晚上 10 点的加州用户,UTC 已经是明天了;代理请求“今天的日程”现在会获取错误的日历日期。对于早上 7 点的东京用户,UTC 还是昨天;“我今天完成锻炼了吗”会返回昨天的锻炼记录。这个 Bug 根据一天中的时间和用户位置而具有确定性,这意味着它对某些用户复现,而对另一些用户不复现,这导致它需要很长时间才能暴露出来。

陈旧的缓存 Prompt。出于成本考虑,你会缓存系统 Prompt。Prompt 缓存通过匹配前缀工作;任何字节的变化都会使缓存失效。一个天真的实现会在每次请求时用新鲜的时间戳重新构建系统 Prompt,导致缓存永远无法命中——于是有人将时间戳移到了一个稳定的位置“以保持缓存热度”,现在时间戳成了缓存键的前缀,这意味着它被冻结在了构建前缀时的状态。代理现在得到的是一个看起来权威但实际上已经延迟了数分钟、数小时,甚至在极端情况下延迟了整整一天的 current_time。这里存在真正的架构权衡冲突,团队往往走向相反的方向,而这两个方向都不正确:要么你在每次调用时破坏缓存,要么你提供陈旧的时间。正确的答案是稳定前缀不包含任何时间,而“现在”存在于它所属的逐轮用户消息中——但这需要有人意识到这种冲突的存在。

夏令时(DST)切换。在仍保留夏令时的地区,当地时钟每年会跳动两次,每次一小时。代理推断“将此安排在明天下午 3 点”,它从注入的日期中提取“明天”,加上“15:00”,然后将其交给日历 API,而 API 会根据被告知使用的任何时区来解释 15:00。如果代理的时间字符串是在夏令时切换前构建的,而预订在切换后触发,那么小时数就是错的。同样的 Bug 也适用于过期时间:“此链接 24 小时内有效”如果在夏令时切换的错误一侧打上时间戳,会提前或延后一小时过期,导致会话在用户预期前 40 分钟失效,从而引发支持电话。

长期运行代理中的上下文过期。现代代理不再是简单的请求-响应模式——它们会运行数分钟,有时甚至数小时,经历多次工具调用。系统 Prompt 在第 0 步组装。在第 40 步,工具调用触发。除非你刻意去刷新,否则代理在第 40 步推断所用的 current_time 值就是第 0 步时的值。在第 0 步决定并在第 40 步执行的“5 分钟后重试”,现在变成了 45 分钟前或 45 分钟后,具体取决于代理如何解释它自己的指令。MCP 会话和 OAuth 令牌在这些长期运行中也会过期,原因是一样的:计算“何时失效”是针对一个已不再适用的时间快照进行的。

这里的每一个都是一类独立的事故。“我们修复了时区 Bug”是一个真实的陈述,但并不意味着“我们修复了时间 Bug”。

时间是事实,而非装饰

关键的重构在于:像对待代理预期正确推断的任何其他事实一样对待“现在”。事实有来源,有新鲜度窗口,有明确的类型,并且出现在 Prompt 中代理预期会实际读取而非随眼一扫的位置。

具体而言,这意味着时间上下文应该是:

  • 明确断言,而不是埋在序言中。一行写着 当前时间是 2026-04-23T14:22:00-07:00 (太平洋夏令时间)。用户的当地日期是周四。 是代理可以引用的事实。而在样板代码块中打上的模糊的“今天是 2026 年 4 月 23 日”只是装饰。
  • 带时区限定,同时包含偏移量和时区名称。仅靠偏移量 (-07:00) 无法在夏令时切换期间消除歧义。IANA 时区名称可以,并且它携带了未来日期的转换规则。
  • 分解成代理实际使用的形式。代理询问“今天是星期几”的频率远超你的想象,而 LLM 在根据纯日期计算星期几方面表现极差。在注入的事实中包含星期几,这样模型就不必进行它不擅长的模数运算。
  • 在步骤边界刷新,而不仅仅是在对话开始时。每一个非平凡的规划步骤、每一个依赖于新鲜度的工具调用、每一个循环迭代——都要重新注入一个新鲜、明确的“现在”作为该步骤用户消息的一部分。成本只是几十个 token。正确性的收益是代理的同步偏差不会超过一个步骤。
  • 移出缓存前缀。稳定的系统 Prompt 描述代理的角色、工具和不变性。它不描述时间。时间属于逐轮用户可见的载荷 (payload),在这里它可以更改而不会导致昂贵的 KV 缓存前缀失效,且代理会将其视为新鲜上下文而非环境真理。

这种纪律听起来很繁琐,直到你排查事故复盘并发现,每一个事故都可以追溯到某个版本的“代理信任了一个陈旧且描述不足的时间字符串”。

大多数团队不运行的评估(evals)

如果时间是正确性的边界,评估(evals)就应该像探测其他正确性边界一样,以对抗性的方式对其进行探测。但大多数评估套件都没有做到这一点。黄金案例(Golden cases)使用的“当前时间”往往等于评估运行的时间,但这恰恰是生产环境中的 Bug 不会遇到的情况。像 Test of Time 这样的学术研究已经指出,调度和多步时间推理是前沿模型表现最差的类别,而生产评估套件却在系统性地规避对这些能力的测试。

一个有用的对抗性时钟固定套件(clock-fixture suite)如下所示:

  • 时区不匹配:服务器位于 UTC,用户位于 UTC+12,用户位于 UTC-11。针对这三种情况运行相同的提示词,并验证答案在用户的参考系(而非服务器的参考系)中是否保持一致。
  • 夏令时切换边界:用户所在时区的冬夏令时切换日。包括切换当天以及切换前后的那一小时。如果智能体跨越此边界进行调度,其偏移量计算必须经得起考验。
  • 星期更替:用户当地午夜前后的 15 分钟。在每个采样点询问“今天”和“明天”,并进行对比。
  • 过时上下文:使用 5 分钟前、1 小时前和 1 天前的时间戳组装提示词,并验证智能体是 (a) 通过工具调用获取最新时间,还是 (b) 明确标记其时间上下文已过时。如果智能体静默接受了过时的时间,则评估失败。
  • 长期运行偏移:在第 0 步注入时间戳,运行 40 个合成步骤,检查智能体在第 40 步时是仍在根据第 0 步的时间进行推理,还是已经更新了时间。

这些案例的合成成本很低。它们能发现那些在大多数团队已经运行的“请求日志回放”评估框架中从未出现的 Bug。

真正有效的工具调用模式

如果时间已过时,智能体需要一种方法来发现过时并进行刷新。行之有效的模式是提供一个小型、廉价的 get_current_time 工具——实现简单,返回带有时区的 ISO-8601 字符串——并辅以系统指令,告知智能体在调用任何语义依赖于绝对时间的工具(如预订、过期检查、重试调度、时效性过滤)之前,先调用该工具。

有两种失败模式需要防范。如果你指令的语气过于强烈,智能体会过度调用该工具——导致每次响应都无故从查询时钟开始,浪费 Token 并增加延迟。如果系统提示词看起来已经拥有可靠的时钟,智能体就会调用不足——注入的时间戳(即便已经过时)会发出“你已经有这个了,不用再问”的信号。指令必须具体:当工具调用的正确性取决于分钟级的时间准确度时,才调用该工具,而不是在用户随口提到“今天”时就去调用。

更深层的一点是,工具的存在使时间具备了自我修正的能力。一个能够随时刷新时钟的智能体可以在过时的系统提示词中存活;而一个不能刷新时钟的智能体只能寄希望于系统提示词被正确组装,但“希望”是无法规模化的。

谁负责时钟

这是故障事件不断暴露出的组织问题。智能体团队负责提示词。平台团队负责部署环境的时区配置。集成团队负责日历和预订 API,而这些 API 各自对“现在”都有不同的理解。评估团队负责测试调度的框架——而该框架又是基于其运行时的任意时钟。

当智能体预订会议的日期出错时,每个团队都可以如实说他们的组件运行正常。Bug 存在于它们之间的缝隙中,这等同于说没人负责,也就意味着它会不断发生。必须有人——通常是为第三次“周二还是周三”故障撰写复盘报告的人——写下:提示词中的时钟是提示词组装代码、缓存层、智能体工具和评估框架之间的正确性契约,并且必须由一个负责人对整个契约负责。

在这个角色出现之前,这类 Bug 将一直存在。智能体将继续自信地根据一个数小时前生成的字符串时钟进行调度,而日历出错的用户则会一边假设 AI 正变得越来越聪明,一边纳闷为什么他们的周二会议被定在了周三。

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