ARTICLE / AI Agent · Context Engineering

为什么 System Prompt 越稳定越好?从 KV Cache 理解 Agent 性能优化

System Prompt 里放一个当前时间戳,就可能让整段 Context 的缓存全部失效。本文从 KV Cache 的前缀复用原理出发,解释为什么越靠近开头的信息越要保持稳定、为什么 Tool Definitions 不该无意义重排,并给出 Minimal Stable Prefix 与「越稳定 → 越动态」的 Context 排列原则。

发布
阅读
约 6 分钟
作者
Nicolas Leigh
READS0次阅读

上一篇我们提到,一个比较合理的 Agent Context 可以抽象成:

Context = Stable Prefix + Dynamic Trajectory + Current State

其中 System Prompt 和 Tool Definitions 应该尽量稳定,而用户消息、工具调用结果和运行状态会不断变化。

但这里有一个很自然的问题:

为什么一定要强调"稳定"?只是为了 Context 结构更整齐吗?

并不是。它还直接关系到 Agent 的两个核心指标:成本和延迟

理解这一点,需要先认识一个非常重要的概念:KV Cache

为什么大模型每次读取长 Context 都很贵?

假设一个 Agent 已经执行了几十轮任务,现在 Context 有 50,000 个 Token。下一轮用户只新增了一句话:

继续执行测试。

直觉上可能会觉得:只新增了几个 Token,模型应该只需要处理这几个 Token。

但实际并没有这么简单。Transformer 在处理 Context 时,需要对前面的 Token 进行计算,并保存中间结果。如果每次调用都从头重新计算整个 50,000 Token Context:

50K + 50K + 50K + ...

Agent 的成本和响应时间都会迅速上升,于是就需要缓存。

什么是 KV Cache?

Transformer 中的 Attention 会为 Token 计算一系列中间结果,其中包括 KeyValue,因此通常称为 KV Cache

这里不需要深入数学公式。从 Agent 工程的角度,可以简单理解成:

模型把已经读过的前缀保存下来,下次遇到完全相同的前缀时,就不需要重新处理。

例如第一次请求 A B C D E F,模型把这段全算一遍:

A B C D E F
████████████

第二次请求变成 A B C D E F G H。因为前面的 A B C D E F 完全没有变化,所以理论上可以复用之前的计算,只处理新增的 G H

A B C D E F | G H
████████████ | ████
   cache      | new

这就是前缀缓存最直观的价值。

问题在于:缓存要求前缀相同

缓存有一个很重要的前提:前面的 Token 必须保持一致

假设原来的 Context 是 A B C D E F G H,下一轮却变成 A B X D E F G H。由于前缀在 X 这里发生了变化,后面的内容即使完全相同,也很难继续直接复用原来的前缀缓存。

这带来了一个非常重要的 Agent 设计原则:

越靠近 Context 开头的信息,越应该保持稳定。

因为前面一旦变化,影响的可能不是几个 Token,而是后面整段 Context 的缓存复用。

为什么 System Prompt 不应该放动态信息?

假设你写了这样一个 System Prompt:

You are a coding agent.

Current time:
2026-09-15 12:30:01

Current working directory:
/workspace/project

Please follow the rules below...

下一轮时间变成 2026-09-15 12:30:08,再下一轮变成 2026-09-15 12:30:15。表面看只是时间改变了几个字符。

但问题在于:它出现在 Context 最前面。于是本来应该稳定的 Prefix 每一轮都在变化。

更合理的设计是把高度动态的信息放到末尾:

System Prompt
Tool Definitions
Conversation History
Tool Results
...
Current Status

例如把状态信息放到最后:

<agent_status>
  <current_time>2026-09-15 12:30</current_time>
  <cwd>/workspace/project</cwd>
  <todo>run tests</todo>
</agent_status>

这样前面的 System Prompt 和 Tool Definitions 仍然可以保持长期稳定。

Tool Definitions 也应该保持稳定

很多 Agent Framework 会动态提供工具。假设某一轮提供的工具列表是:

1. read_file
2. write_file
3. search_code
4. run_shell

下一轮却变成:

1. search_code
2. run_shell
3. read_file
4. write_file

虽然工具完全相同,只是顺序发生了变化,但从 Token 序列来看,它们已经不是相同的 Prefix。

所以如果没有特殊原因,Tool Definitions 的内容和顺序都应该尽量稳定。甚至不要为了所谓的"智能排序",每轮根据模型预测动态调整工具顺序——这种优化可能省不了多少模型推理,却破坏了更有价值的 Prefix Cache。

当然,如果工具集合本身确实需要动态变化(例如进入数据库阶段后才开放数据库写工具),那么动态 Tool Set 是合理的。关键不是"永远不能变化",而是不要无意义地变化

Prompt Cache 和 KV Cache 是一回事吗?

在实际使用各种大模型 API 时,你还会经常看到另一个词:Prompt Caching

它和 KV Cache 密切相关,但从工程视角上可以稍微区分:

  • KV Cache —— 模型底层如何复用 Attention 中间结果。
  • Prompt Cache —— 服务商如何把"相同 Prompt 前缀可复用"暴露成工程能力和计费优化。

对于 Agent 开发者来说,不一定需要关心底层具体如何实现。真正重要的是:相同前缀通常更容易被缓存和复用

因此无论底层具体采用哪种缓存机制,稳定前缀都是一个非常有价值的设计原则。

这为什么对 Agent 特别重要?

普通 Chatbot 一次对话可能只有几轮。Agent 却可能运行 20 轮、50 轮甚至 100 轮,而每一轮都需要重新把大量 Context 提供给模型。

例如:

System Prompt          5K
Tool Definitions       4K
User Task              1K
Trajectory            30K
Current State          1K
--------------------------
Total                  41K

如果前面的 System Prompt + Tools 每轮都能稳定复用,大量输入 Token 的处理成本就有机会被减少。但如果每轮都因为一个时间戳、随机 ID 或 Tool 顺序改变导致 Prefix changed,原本可以复用的大量 Context 就可能失去缓存价值。

所以对于 Agent 来说:Prompt Stability 本身就是一种性能优化

哪些信息不应该放进 Stable Prefix?

一个非常实用的判断方法是:这个信息每轮会不会发生变化? 如果答案是"经常会",它通常不应该出现在 Stable Prefix。

例如:

  • 当前时间
  • 当前目录
  • 剩余 Token
  • 当前 TODO
  • 工具调用次数
  • 最近错误
  • Git 状态
  • 测试结果
  • 当前文件
  • 任务进度

这些更适合放到 Current State,或者最新的 Tool Result 中。

相反,以下信息通常比较适合作为 Stable Prefix:

  • Agent 的身份
  • 核心行为规范
  • 安全规则
  • 输出约束
  • 固定 Tool Schema
  • 通用工作流程

可以简单记成:Stable Prefix = 长期规则,Dynamic Context = 当前事实

不要为了"个性化"频繁改 System Prompt

还有一种常见写法:

System Prompt:

You are helping user Nicolas.
Today is Monday.
The user is currently working on Go.
The last task was...
The current repository is...

乍看之下信息非常完整。但很多内容其实属于 Session State,而不是 System Prompt。

更好的做法是:

System Prompt:        固定身份、规则、行为边界
User / Memory Context: 用户长期偏好
Current State:         当前项目、当前目录、当前任务

这样不仅结构更清晰,也能够减少 System Prompt 的频繁变化。

这体现了一个很重要的原则:不要把所有"重要信息"都塞进 System Prompt。重要,不代表稳定。

稳定前缀并不是越大越好

说到这里,还要避免另一个误区:既然 Stable Prefix 可以缓存,是不是应该把大量信息全部放进去?

也不是。

假设一个 Agent 有 200 个工具。如果把所有工具永久放进 Context:

200 Tool Schemas

即使它们都很稳定,也会产生两个问题:

  • Context 太大;
  • 大量无关工具干扰模型。

所以真正理想的策略不是"尽可能大的 Stable Prefix",而是尽可能小 + 尽可能稳定 + 足够完成任务——也就是 Minimal Stable Prefix

这也是为什么后面会出现 Agent Skills 和 Progressive Disclosure:不需要的能力,不要提前加载。

一个更合理的 Context 排列方式

综合起来,可以把 Agent Context 设计成:

System Prompt              ← Stable Prefix(固定身份 / 规则 / SOP)
Tool Definitions           ← Stable Prefix(固定工具 / 固定顺序)
─────────
User Task
Dynamic Trajectory:        Assistant → Tool Call → Tool Result → ...
─────────
Current State              ← Time / TODO / Environment / Recent Error

核心规律其实只有一句话:越稳定 → 越动态。这不仅是一种结构设计,也是一种性能设计。

结语

Agent 的性能优化并不只发生在模型选择、并发调用、网络请求、推理参数这些层面。Context 本身的组织方式,也会直接影响成本和延迟。

其中一个非常实用的原则就是:让 Context 的前缀尽可能稳定

具体来说:

  • System Prompt 不要塞入频繁变化的信息;
  • Tool Definitions 不要无意义地重新排序;
  • 时间、任务进度、错误状态等动态数据尽量放在后面。

最终可以把这个原则概括成:

Stable Prefix + Append-only Trajectory + Dynamic Tail

这样做的意义不只是让 Context 更整洁,更重要的是它能够让 Agent 更充分地利用缓存,从而在长任务中降低重复计算。

参考资料