第 4 章 · 03 时间注入与上下文管理


文档摘要

第 4 章 · 03 时间注入与上下文管理 本节摘要:本节讲清两个容易被忽略的工程细节——时间注入与上下文管理。先看 顶部那段 :它在模块加载时用 生成一行「Current date and time ...」并拼到每个 Agent 的 systemprompt 末尾,让 LLM 知道「现在」。这看似不起眼,却对交易场景至关重要——LLM 的训练数据有截止日期,不告诉它「今天」,它会把「最新」当成训练时的新鲜事。再看上下文管理:五个 Agent 都设 (单轮推理)、三个专家设 (限上下文)。本节讲清这两项如何共同决定一次 Agent 调用的成本与稳定性。读完本节,你理解 LLM 「没有时间感」这一固有缺陷,以及 AutoHedge 用一行字符串修补它的巧思与局限。

第 4 章 · 03 时间注入与上下文管理

本节摘要:本节讲清两个容易被忽略的工程细节——时间注入上下文管理。先看 workers.py 顶部那段 _SYSTEM_SUFFIX:它在模块加载时用 datetime.now() 生成一行「Current date and time ...」并拼到每个 Agent 的 system_prompt 末尾,让 LLM 知道「现在」。这看似不起眼,却对交易场景至关重要——LLM 的训练数据有截止日期,不告诉它「今天」,它会把「最新」当成训练时的新鲜事。再看上下文管理:五个 Agent 都设 max_loops=1(单轮推理)、三个专家设 context_length=16000(限上下文)。本节讲清这两项如何共同决定一次 Agent 调用的成本与稳定性。读完本节,你理解 LLM 「没有时间感」这一固有缺陷,以及 AutoHedge 用一行字符串修补它的巧思与局限。

内容来源:原项目源码 autohedge/workers.py(第 19-24 行 _SYSTEM_SUFFIX、各 Agent 的 max_loops/context_length),精读并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 逐行读懂 _SYSTEM_SUFFIX 的构造代码。
  2. 解释为什么 LLM 需要被告诉「现在」
  3. 指出时间注入的局限(模块加载时刻,非调用时刻)。
  4. 说清 max_loops=1context_length=16000成本含义
  5. 理解 system_prompt 末尾位置对 LLM 注意力的意义。

一、_SYSTEM_SUFFIX 的构造代码

workers.py 第 19-24 行,模块级代码:

_NOW = datetime.now() # Exact date and time for all agent system prompts (simple, single line) _DATE_TIME_LINE = _NOW.strftime("%A, %B %d, %Y at %H:%M") if _NOW.tzinfo: _DATE_TIME_LINE += f" {_NOW.tzname() or ''}" _SYSTEM_SUFFIX = f"\n\nCurrent date and time (use this as now): {_DATE_TIME_LINE.strip()}"

逐行解读:

  • _NOW = datetime.now():取模块加载时刻的本地时间(无时区)。
  • _NOW.strftime("%A, %B %d, %Y at %H:%M"):格式化成类似 Tuesday, August 05, 2026 at 14:30 的字符串。%A 星期、%B 月份、%d 日、%Y 年、%H:%M 时分。
  • if _NOW.tzinfo::如果时间带时区,追加时区缩写(如 UTCCST)。但 datetime.now() 默认不带时区,所以这分支通常走不到——除非系统或上层设了 tzinfo。
  • _SYSTEM_SUFFIX = ...:把格式化后的时间行包进一句指令:「Current date and time (use this as now): ...(当前日期时间,把它当作 now)」。注意前导 \n\n——保证它与原 prompt 之间有空行分隔。

最终 _SYSTEM_SUFFIX 形如:

Current date and time (use this as now): Tuesday, August 05, 2026 at 14:30

二、每个 Agent 都被注入了时间

看 workers.py 里五个 Agent 的 system_prompt,每一个都拼了 _SYSTEM_SUFFIX:

sentiment_agent = Agent(..., system_prompt=SENTIMENT_PROMPT + _SYSTEM_SUFFIX, ...) risk_agent = Agent(..., system_prompt=RISK_PROMPT.strip() + "..." + _SYSTEM_SUFFIX, ...) execution_agent = Agent(..., system_prompt=EXECUTION_PROMPT.strip() + "..." + _SYSTEM_SUFFIX, ...) quant_agent = Agent(..., system_prompt=QUANT_PROMPT.strip() + "..." + _SYSTEM_SUFFIX, ...) director_agent = Agent(..., system_prompt=DIRECTOR_PROMPT + _SYSTEM_SUFFIX, ...)

无论角色如何,每个 Agent 都被告知「现在」。这反映作者的判断:时间感对所有 Agent 都重要——情绪要判断「这条新闻是不是过时」、量化要判断「这个指标是不是最新」、风控要判断「这个事件是否已发生」、执行要判断「这个报价是否还有效」。

三、为什么 LLM 需要被告诉「现在」

这是 LLM 应用的一个固有缺陷:

  • LLM 的知识来自训练数据,训练数据有截止日期(比如某模型训练到 2024 年底)。
  • LLM 没有内置时钟——它不知道「今天」是哪天。
  • 如果不告诉它「现在」,它会用训练数据里的「最新」当参考,可能滞后几个月甚至几年。

在交易场景,这个缺陷是致命的。举例:

  • 任务:「分析 NVDA 最近的表现」。LLM 不知道「最近」是哪天,可能用训练时(比如 2023 年)的 NVDA 数据,完全脱离当下。
  • 情绪分析:如果 LLM 不知道现在是 2026 年,它判断「最新新闻」时会错位。
  • 链上交易:如果 LLM 不知道当前时间,它对「报价时效」的判断会失真。

AutoHedge 的解法很轻量——一行字符串注入:Current date and time (use this as now): ...。LLM 读到这句,会把这行时间作为「现在」的参考,后续推理就能锚定时间。

💡 核心心法:LLM 是「凝固在训练时刻的大脑」——它的知识停在某个日期,推理能力是通用的,但「事实」是过期的。给 system_prompt 注入当前时间,相当于给这个大脑「装一个时钟」,让它能把通用推理锚定到当下。这是几乎所有 LLM 应用都该做的基本功(也是 ChatGPT 系统消息里都有时间行的原因)。

四、时间注入的局限

AutoHedge 这套注入有个重要局限:时间是模块加载时刻,不是 Agent 调用时刻。

看代码:_NOW = datetime.now()模块级语句,在 import autohedge.workers 时执行一次,之后 _SYSTEM_SUFFIX 就固定了。如果你启动一个长驻进程(比如 REPL),早上 9 点 import,到下午 3 点调 Agent,注入的时间还是早上 9 点——差了 6 小时。

这个局限的影响:

  • 短命进程(跑 example.py 几分钟跑完):无影响。
  • 长驻 REPL(cli.py 一直开):时间会逐渐偏离,几小时后就有明显偏差。
  • 跨日运行:最严重——周一启动,周三还在用周一的时间。

要修正也不难——把时间注入从模块级移到 Agent.run 调用前(每次调用重新生成)。但 AutoHedge 没这么做,这是它的一个工程粗糙点

⚠️ 现实澄清:对教学项目,这个局限无伤大雅(你不会让 REPL 开几天)。但如果你想把这套架构用于生产,时间注入必须改成每次调用时刷新,否则长驻服务的时间感会失真,交易决策的时效判断会出错。

五、max_loops=1:单轮推理的成本含义

五个 Agent 都设 max_loops=1(第 3 章已提)。从上下文管理角度,它的含义:

  • 每轮 = 一次完整的 LLM 调用(可能含 function call 的多步)。
  • max_loops=1 意味着 Agent 收到任务后,只做一轮推理就产出结果,不自我反思迭代。

成本含义:

  • 省钱:每轮都要调 LLM(花钱),单轮最省。
  • 省时:不迭代,响应快。
  • 上下文不膨胀:多轮推理会把「自己的中间思考」累加进上下文,轮数越多上下文越长;单轮则不累积。

代价是质量:复杂问题一次想不清楚,可能需要多轮「反思→修正」。AutoHedge 选单轮是教学项目的合理简化。

六、context_length=16000:上下文窗口的取舍

风控/执行/量化三个专家设了 context_length=16000(token 数)。含义:

  • 上下文窗口是 LLM 一次能「看到」的全部文本(system_prompt + 输入 + 历史 + 输出)。
  • 16000 token 约等于 12000 英文单词——够装一份长量化分析 + 假设 + system_prompt。
  • gpt-4.1 实际支持更大窗口(128K+),但这里主动设小。

为什么设小?

  • 限成本:上下文越长,LLM 调用越贵(OpenAI 按 token 计费)。设 16K 是主动封顶单次成本。
  • 防分心:上下文太长,LLM 容易被无关信息干扰(「lost in the middle」现象)。设小能聚焦。
  • 够用:风控/执行/量化的输入(假设 + 上游分析)通常不超过几 K,16K 绰绰有余。

注意情绪 Agent 没设 context_length(用 swarms 默认),Director 也没设。这又是一处定义不一致——大概是因为 Director 的输入是用户任务(短),情绪的输入是新闻摘要(也不太长),作者觉得不用显式设。但不一致本身是代码异味。

七、system_prompt 末尾位置的意义

注意 _SYSTEM_SUFFIX 总是拼在 system_prompt 的最末尾(在追加约定之后)。这不是随意选择:

  • LLM 对 system_prompt 的开头与结尾注意力更集中(序列模型的位置效应)。
  • 把时间放在末尾,LLM 在开始推理前最后看到的就是时间,印象更深。
  • 如果放在开头,可能被中间的长 prompt 稀释。

这是 prompt 工程的小技巧:重要信息放头尾,次要信息放中间。时间属于「重要且简短」的信息,放末尾合适。

八、本节在全景图的位置

system_prompt = 基础角色(prompts.py) + 运行时约定(workers.py 追加) + 时间注入(_SYSTEM_SUFFIX) ↑ max_loops=1, context_length=16000 共同约束一次调用

至此第 4 章讲完了 prompt 工程的三块:「写什么内容」(§01)、「怎么约束输出」(§02)、「怎么管理上下文」(§03)。下一章我们看用户怎么和这套系统交互。

本节要点回顾

  1. _SYSTEM_SUFFIX 构造:datetime.now()strftime 格式化 → 包进「Current date and time (use this as now): ...」;五个 Agent 全部注入。
  2. LLM 需要时钟:训练数据有截止、无内置时钟;不告诉「现在」,LLM 用过期「最新」当参考;交易场景对时效敏感,注入时间是基本功。
  3. 重要局限:时间是模块加载时刻(import 时执行一次),长驻进程会逐渐偏离;生产需改成每次调用刷新。
  4. max_loops=1:单轮推理,省钱省时、上下文不膨胀;代价是质量(复杂问题难一次想清)。
  5. context_length=16000:主动封顶上下文(~12K 词),限成本、防分心、够用;情绪与 Director 漏设是不一致。
  6. 末尾位置:时间放 system_prompt 末尾,利用 LLM 对结尾的注意力优势;重要信息放头尾是 prompt 工程通用技巧。

下一章,我们离开 Agent 内部,看用户怎么和 AutoHedge 交互——从 cli.py 的 Rich 美化 REPL 开始。


发布者: 作者: 灏天文库 转发
评论区 (0)
U