时间上下文注入:让 LLM 真正知道今天是几号
你的 LLM 功能已经上线。用户开始问那些涉及时间的问题——"最新政策是什么?""帮我总结本周发生的事""这条信息还是最新的吗?"——模型自信、流畅地回答,却答错了。
模型不知道今天是几号。它从来都不知道。你熟悉的聊天界面让你忘了这件事,因为那些界面在背后悄悄注入了当前日期。但你的 API 集成不会。你发布的系统在不知道自己处于时间轴哪个位置的情况下,仍然在推理时间相关的问题——这是一类 bug,会在你还没想到去找它之前就出现在生产环境里。
这不是换一个更聪明的模型就能绕过去的 LLM 局限——这是一个需要显式工程手段来填补的架构空白。如何正确地做到这一点——不破坏提示缓存、不在分布式工作节点之间引入时区 bug、不把三年前的文档当成"最新"内容检索出来——正是本文要讲的。
模型没有时钟,而且这是设计如此
LLM 是在静态文本快照上训练出来的。训练过程产生的是一个将词元映射到可能下一词元的函数,而不是一个能查询系统状态的过程。它没有内部时钟,没有"现在"的概念。
有的是:一个基于训练数据中日期分布的强先验,用来判断哪些日期是合理的。当你在没有日期上下文的情况下问模型"现在是哪年?",它不会随机猜——它会给出一个看起来符合训练分布的答案。GPT-4o 的训练截止日期是 2023 年 10 月;截至 2026 年初,这已经是 29 个月的差距。Llama 4 的截止日期是 2024 年 8 月。这些模型不知道自己正在被 2026 年使用,除非你告诉它们。
那些让人感觉"紧跟时事"的聊天界面,其实在做的是一种可以称为"编排剧场"的操作:底层模型和 API 版本有着相同的训练截止日期;界面用一段系统提示把它包裹起来,写着"今天是 2026 年 4 月 20 日",有时还给它配备一个搜索工具。模型本身在时间上是冻结的,新鲜感是注入进去的。
一旦你理解了这一点,前进的路就很清晰:你也来注入日期。但朴素的注入会产生三种独特的生产故障模式,在写第一行代码之前值得先搞清楚。
三种会在生产环境找上门的故障模式
自信地给出错误日期。 一个团队构建了客户分析管道。系统提示里包含"最近的转化"和"当前 的流失风险"这类措辞。没有日期锚点,模型就根据训练数据的先验来理解"最近"——然后给出错误的判断,而且错得很难审计。六个月前的记录看起来是"最近的",上周的记录看起来是"非常近期的"。模型的输出连贯而错误,错误是隐形的,直到有人把 LLM 的分类结果和真实数据对比,才会被发现。
修复方法很简单:在系统提示前加一句 f"Today is {date.today().isoformat()}." ,再把提示里所有的"最近"替换成"过去 30 天内转化的"。但没有思考时间上下文的团队会先发布出问题的版本,因为它会静默地失败。
破坏缓存的时间戳。 一个团队意识到需要时间上下文,于是注入当前时间:f"Current date and time: {datetime.now()}"。这会产生每秒都不同的字符串,意味着每个请求都会导致提示缓存未命中。一个工程团队发现这个问题时,他们的 LLM 响应时间已经从几秒变成了 50 秒以上——缓存在每个请求上都失效了,模型每次都要重新处理完整的系统提示。另一个团队追查零缓存读取词元的原因,最终定位到一行代码:"Current date and time: ${dateTime}" 格式化到了秒级精度。
修复方法:只注入日期,不注入时间。2026-04-20 在 24 小时内是稳定的,能给模型提供绝大多数场景所需的时间上下文。如果确实需要时间精度,把时刻放到用户消息轮次里。
午夜跨日。 一个系统提示在晚上 11:45 组装并缓存,写着 "Today is April 19, 2026."。应用层缓存的 TTL 是一小时。到凌晨 12:15,从该缓存服务的请求看到的还是"4 月 19 日"。一个计算"明天"的智能体算出来是 4 月 20 日——也就是今天。调度功能 产生差一天的错误。这个 bug 是间歇性的(只在午夜前后两小时的窗口期出现),且依赖环境(只在高流量时段跨午夜的时区才会出现)。
这些不是只有设计糟糕的系统才会遇到的边缘情况,而是任何没有专门针对时间上下文进行设计的系统的默认行为。
正确的日期注入模式
生产安全的方案根据缓存稳定性来区分什么放哪里:
系统提示(稳定,对缓存友好): 用 ISO 8601 格式、以 UTC 为基准的日期。仅此而已。
system = f"Today's date is {datetime.utcnow().date().isoformat()} UTC.\n" + base_instructions
这使缓存每天失效一次,是可以接受的。它给模型提供了一个全局的日期参考系,也不会在不同区域运行的分布式工作节点之间造成时区混乱。
用户消息(易变,每次请求): 如果需要时刻或用户本地时区,放这里。
[Context: user's local time is 14:30, timezone: America/New_York]
这不会破坏系统提示缓存,同时能让模型回答时区相关的问题,而不会让稳定的前缀变得不稳定。
长时运行的会话 : 暴露一个 get_current_time(timezone) 工具供智能体调用。跨越午夜的会话需要新鲜的时间数据;工具调用是获取真实世界状态的正确机制,而不是依赖会话开始时注入的过期时间戳。
一个不太直观的细节:位置很重要。针对日期敏感查询的研究表明,把日期放在系统提示的开头——在其他指令之前——比放在末尾能产生更准确的时间推理。日期为模型解读后续指令提供了锚点。这是正确的心智模型:日期是上下文,后续提示应该在这个上下文中被解读。
另一个借鉴自 Anthropic 处理 Claude 自身系统提示方式的细节:如果你的模型有知识截止日期,在系统提示中声明一个略早于实际截止日期的日期。在实际截止日期前两个月的"缓冲区",是因为训练数据最后几个月的代表性不足。这段时期的事件存在于训练数据中,但覆盖率更低,使得模型的表示不够可靠。显式声明对该时期的不确定性,比隐含地表示有把握更准确。
最后:在提示中定义所有相对时间术语。没有锚点,"最近"、"最新"、"当前"、"即将"和"快要"都是未定义的。一个写着"分析近期客户活动"的提示,在没有日期上下文的情况下让模型无从处理。用明确的范围替换它们:"过去 30 天内"、"在 {start_date} 和 {end_date} 之间"。即使在你已经注入了当前日期之后,这条规则也适用——"最近"在没有定义时间窗口之前仍然是模糊的。
