Skip to content

第 5 章 · 上下文:给模型正确的信息

pixiu 锚点:agent/prompts.py:build_messages / _build_memory_contextagent/conversation.pytools.py:load_context 关键案例:W2-5(画像注入 eval 20%→100%)、W2-6(会话→记忆反向缺口)

开篇:agent 健忘,先别改 prompt

你一定遇到过这种事:agent 明明上一步刚查过某个信息,下一步就忘了,重新查一遍;或者历史对话里说过一个关键约束,它到后面就开始忽略。

新手的第一反应是改 prompt——加一句"注意参考历史对话""不要重复查询"。基本没用。因为这些规则只是在"提示"模型要记住,但模型实际能看到的历史信息,可能早就被截断、或者淹没在无关内容里了。

问题不在"提示它记住",在"它那个瞬间到底能看到什么"。 这就是 context 层。阶 2 的核心认知只有一句:模型的决策质量,取决于它决策那个瞬间能看到的信息。

这一章,我们打开 pixiu 的 prompts.py,看一个生产级 agent 是怎么组装上下文的。

pixiu 的上下文组装:build_messages

build_messagesprompts.py:397)是 pixiu 上下文的总装车间。它最终产出一组消息,送给 LLM。结构是这样:

1. system:静态模板(system.md)
2. system:场景 prompt 片段(按意图动态加载,可选)
3. user:[上下文信息] 时间 + 持仓 + 人格 + 记忆 + 摘要
4. user:用户真实消息

这里有个极其重要、但很多人不知道的设计:为什么静态的 system.md 放在第一条 system 消息,而动态信息(持仓、记忆、时间)放在第三条 user 消息?

答案是为了 prompt cache(提示缓存)

DeepSeek(以及大多数现代 LLM API)有 prompt 缓存机制:如果两次请求的前缀相同,第二次能命中缓存,省钱省时。pixiu 的 llm.py 里专门有 CacheStats 统计缓存命中率(prompt_cache_hit_tokens / prompt_cache_miss_tokens)。

如果我把"当前时间""当前持仓"这些每次都变的东西塞进 system 消息,那么每次请求 system 都不一样,缓存永远命不中。pixiu 的做法是:system 只放静态的 system.md(角色、纪律、框架——这些几乎不变),把动态信息放到带 [上下文信息] 标记的 user 消息里。这样 system 前缀稳定,缓存命中率拉满。

这就是 context 工程的一个隐藏考点:你往哪条消息塞信息,不只是语义问题,还是缓存效率问题。 静态的往前放(system),动态的往后放(user),是 prompt cache 的基本套路。

还有那个 [上下文信息] 标记——它告诉 LLM"这一段是背景资料,不是用户的真实问题"。模型能据此区分"参考资料"和"要回答的问题",不会被一大段持仓数据带偏。

_build_memory_context:6 类结构化记忆

动态上下文里最复杂的是 _build_memory_contextprompts.py:250)。它从数据库注入 6 类记忆,每一类都是"按需、限量"的:

记忆类内容限量
近期报告结论近 3 天的报告要点3 条
近期操作决策近 7 天的买卖记录5 条
标的上下文画像 + 动态 + 融合信号 + 持仓关联按对话出现的标的
持仓画像概览组合级问题时的全部持仓画像(W2-5)全部持仓
待办预警近 1 天触发的 alert5 条
用户观点/计划/纪律user_note(W2-6)近 30 天 5 条

注意每一类都限量——这不是偷懒,是控制上下文膨胀。你把所有历史全塞进去,上下文窗口炸了不说,模型还会被无关信息干扰。"按需、限量"是 context 管理的纪律。

progressive disclosure:该看到时才看到

_build_memory_context 里有个设计叫 progressive disclosure(渐进式披露),我觉得特别值得学。

看标的上下文那段(prompts.py:293-340):它不是把所有股票的画像、信号、动态一股脑全注入,而是只有当某个标的在用户消息里出现时(通过 _extract_symbols 提取代码或持仓名称),才加载这一个标的的相关上下文。

python
held = _held_positions_map()
for sym in _extract_symbols(user_message, held):  # 只处理对话里提到的标的
    # 加载这个标的的:回测画像 + 近期动态 + 融合信号 + 持仓关联
    ...

这个思路和 load_context 工具(第 4 章提过)形成互补:load_context被动的(模型主动调用才加载知识),progressive disclosure 是主动的(系统检测到标的就自动加载)。两者都是为了同一个目标:该看到的时候让它看到,不该看到的时候别占地方。

这是 context 工程的核心手艺:不是"给得越多越好",而是"在正确的时刻,给刚好够用的信息"。

W2-5:一个 eval 把 pass_rate 从 20% 做到 100%

讲一个我用 eval 逼出 context 缺口的故事,标注 W2-5

我给 pixiu 加了画像 eval——测试 agent 能不能正确回答关于持仓画像的问题。跑出来,pass_rate 只有 20%。这个数字很难看,我去查为什么。

查到一个 context 层的 bug:_extract_symbols 只能提取用户消息里出现的具体股票代码或持仓名称。但用户问组合级问题时——比如"我的持仓哪只适合核心仓""我的仓位配比怎么样"——消息里没有任何具体标的_extract_symbols 返回空,于是画像一段都注入不进去。agent 拿不到任何画像数据,只能退回基本面分析,自然答不对。

注意,这个问题不是 prompt 的错,也不是工具的错——get_stock_profiles 能查到画像,工具没问题。问题是该注入的 context 没注入

修复在 prompts.py:342-367,加了"持仓画像概览"段:

python
# 组合级问题画像概览(W2 画像 eval 发现):用户问"我的持仓/核心仓/配比"等
# 组合级问题但未点名具体标的时,_extract_symbols 返回空 → 画像注入不上。
# 此处补:组合级意图时批量注入所有持仓的回测画像
if held and re.search(r"持仓|组合|核心仓|卫星仓|我的仓|仓位|配比|配置|哪只|哪一只", user_message):
    for sym, info in held.items():
        profiles = get_stock_profiles(sym)
        # ... 注入每只持仓的画像

它用正则识别"组合级意图",一旦命中,就批量注入所有持仓的画像(只查 DB,不触发昂贵的 fusion 扫描)。

修复后,画像 eval pass_rate 从 20% 拉到 100%。

这件事的价值不在那个数字,在于方法论:当你发现 agent 在某类问题上大面积答错,先别急着改 prompt 或加工具——用 eval 定位,很可能是一个 context 缺口。 把缺口补上,pass_rate 立竿见影。

W2-6:会话→记忆的反向缺口

W2-5 是"系统该注入的没注入",W2-6 是反过来的方向——用户在对话里表达的东西,没落进记忆。标注 W2-6

原来的 pixiu 有个不对称:工具触发的 event(买卖、回测)会回流进 memory_event,但用户在自由对话里随口说的观点、计划、纪律(比如"海康估值偏高,回踩 30 元再加仓""我以后不再追涨杀跌")——这些东西不落库。对话成了"只写文件不进记忆"的孤岛。下次对话,agent 完全不记得用户上周说过什么。

修复是加了一个 remember 工具(第 4 章提过它的 schema):当 agent 识别到用户表达了一个值得长期记住的判断/计划/规则时,主动调用 remember,把它写进 memory_event(user_note)。然后 _build_memory_context 会注入近 30 天的 user_note(上表第 6 类)。

这样就形成了闭环:用户说 → agent 记 → 下次对话注入 → agent 记得。 会话不再是孤岛。

这是一个高阶的 context 认知:context 不只是"系统有什么给模型看什么",还包括"把散落在对话里的信息主动沉淀成结构化记忆"。 记忆是 context 的蓄水池。

信息层次:不是所有信息都该同等对待

docs/technical/智能体应用框架.md 里有个信息四层次分类,是 context 工程的判断框架:

层次类型处理方式
客观事实快讯、政策、行情、持仓原样记录,分析基础
逻辑工具策略信号、回测决策辅助,不单独作为依据
情绪观点大 V 观点、评论参考但剥离情绪
混合信息行业解读区分其中的事实与情绪

为什么要分层次?因为如果模型把"某大 V 看多"和"公司财报数据"同等对待,它的判断就会被情绪带偏。pixiu 在 system.md 里要求"区分事实和判断",就是这个意思——context 工程不只是"塞什么",还包括"怎么标注这些信息的可信度层次"。

这一章的工具:context 自检

  • [ ] 你的 agent 健忘时,你是改 prompt,还是会去查"它那个瞬间能看到什么"?
  • [ ] 你的 system 消息里有动态信息吗?(有的话,prompt cache 大概率在白白浪费)
  • [ ] 你有没有"按需、限量"地注入记忆?还是全塞 / 全不塞?
  • [ ] 有没有 progressive disclosure?(对话提到 X 时,自动加载 X 的上下文)
  • [ ] 你有没有用 eval 发现过 context 缺口?(某类问题大面积答错,往往是 context 没注入)
  • [ ] 对话里的用户观点,有没有沉淀成结构化记忆?(还是聊完就丢)

小结

context 层是 agent"聪明与否"的真正分水岭。prompt 调得再好,模型看不见关键信息也白搭。

pixiu 的 context 工程有三个值得带走的点:静态/动态分离(为缓存)、progressive disclosure(按需加载)、eval 驱动补缺口(W2-5 的 20%→100%)。

而且你会发现——这一章几乎没有"调模型"的内容,全是信息架构、数据流、检索策略。这又是传统程序员的主场。

下一章,我们终于要讲那个把一切串起来的东西——循环

下一章

第 6 章 · 循环:简单循环 + 显式护栏 —— 为什么 pixiu 的循环体几乎没有 if-else。