Agent Harness 学习
← 返回对照矩阵

prompt cache

上下文

提示词缓存

前缀缓存怎么保住?什么操作会让它失效?

横向读这一行

同一个「缓存」,四家管的根本不是同一层:openJiuwen 管调度层(让子代理粘在同一个后端上,复用 KV),Codex 管请求层(逐字段比对,决定这次请求能不能接着上次的前缀),DeepSeek 管计量层(把命中与写入记成不相交的三份 token,喂给压缩阈值),Pi 管声明层(各家线路协议支持什么缓存能力写进配置)。要谈缓存优化,得先说清楚在哪一层谈。

对比
openJiuwen带代码

不管前缀,管子代理的 KV 缓存亲和性

四家里唯一不在「前缀复用」这一层解决问题的。它的 kv_cache 模块处理的是路由亲和:特定类型的子代理(browser_agent、verification_agent)被标为 sticky,配合 prefetch / finish / evict 三个生命周期钩子,让同一个子会话尽量落到同一个推理后端上,从而复用已有的 KV 缓存。子会话 id 由「父会话 id + 任务 id」推导,保证跨轮次稳定。这是把缓存当作调度问题而不是拼装问题。

def affinity_enabled(deep_agent: Any) -> bool:
    """Return without inspecting model/binding state when affinity is disabled."""
    deep_config = getattr(deep_agent, "deep_config", None)
    kv_config = getattr(deep_config, "kv_cache_affinity_config", None)
    return getattr(kv_config, "enable_kv_cache_affinity", False) is True


def is_sticky_subagent_type(subagent_type: str) -> bool:
    return str(subagent_type or "").strip() in ("browser_agent", "verification_agent")

# 同文件另有 prefetch_sticky_subagent / finish_subagent / evict_subagent
# 三个生命周期钩子(44-128 行),以及由父会话 id + task_id 推导的稳定子会话 id。
openjiuwen/harness/kv_cache/kv_cache_hooks.py:15-23@ fd6c47854201
Codex带代码

请求字段逐个比对,新字段强制显式表态

请求带 prompt_cache_key,但真正的工程做法在这个比较函数:判断两次请求能否复用连接与缓存前缀时,它把 ResponsesApiRequest 完整解构,逐字段比对——除 input 与 client_metadata 外全部必须相等。注释写明了为什么不实现 PartialEq,以及关键约束:保持解构穷尽,这样将来新增任何请求字段都必须显式决定它是否影响复用。缓存失效在这里是编译期问题,不是靠人记住。

// This is intentionally not a `PartialEq` implementation: request equality includes `input` and
// `client_metadata`, while websocket reuse compares the input separately and ignores metadata.
// Keep the destructuring exhaustive so new request fields require an explicit reuse decision.
fn responses_request_properties_match(
    previous: &ResponsesApiRequest,
    current: &ResponsesApiRequest,
) -> bool {
    // ...解构双方全部字段,逐个比对(318-361 行):
    //   model / instructions / tools / tool_choice / parallel_tool_calls /
    //   reasoning / store / stream / include / service_tier /
    //   prompt_cache_key / text
    // 只有 input 与 client_metadata 被显式忽略。
codex-rs/core/src/client.rs:307-313@ 4beea50e26dd

不放缓存断点,但把缓存 token 记成不相交三份

缓存位置交给厂商,Harness 负责把账算准:TokenUsage 规定 inputTokens 只计未命中缓存的输入,命中与写入分别记为 cacheReadTokens / cacheWriteTokens,三者不相交、相加才是计费输入。注释还点名了一个真实坑——有些厂商(包括 DeepSeek 自己的 prompt_tokens)把缓存命中折进总数里,适配器必须减回去。这份账直接喂给 ctx.tokenMeter,而压缩阈值又读 tokenMeter,所以缓存计量算错会连带压缩时机出错。

/**
 * Token accounting for one model call (cache fields are optional).
 *
 * Counts are DISJOINT: `inputTokens` is uncached input only; cached input is
 * reported separately as `cacheReadTokens`/`cacheWriteTokens` (billed input =
 * sum of the three). Adapters whose providers fold cache hits into a total
 * prompt count (DeepSeek's `prompt_tokens`) subtract them out.
 */
export interface TokenUsage {
  inputTokens: number
  outputTokens: number
  cacheReadTokens?: number
  cacheWriteTokens?: number
  reasoningTokens?: number
}
packages/llm/llm/src/types.ts:127-141@ b150a551b8d4
Pi带代码

按线路协议声明缓存能力,能力差异写进配置

缓存被当作模型能力来建模:每种 wire protocol 各有一张 compat schema,分别声明支持什么。三张 schema 都有 supportsLongCacheRetention,只有 Anthropic 那张多出 supportsCacheControlOnTools——因为只有它支持把缓存断点打在工具定义上。顶层还有 cacheControlFormat: "anthropic" 指明断点写法,以及 cost 里独立的 cacheRead / cacheWrite 费率(含分档)。新增一个厂商因此是改配置,不是改代码。

const AnthropicMessagesCompatSchema = Type.Object({
	supportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),
	supportsLongCacheRetention: Type.Optional(Type.Boolean()),
	sendSessionAffinityHeaders: Type.Optional(Type.Boolean()),
	supportsCacheControlOnTools: Type.Optional(Type.Boolean()),
	supportsTemperature: Type.Optional(Type.Boolean()),
	forceAdaptiveThinking: Type.Optional(Type.Boolean()),
	allowEmptySignature: Type.Optional(Type.Boolean()),

// 同文件另见:cacheControlFormat: Type.Literal("anthropic")(101 行)、
// OpenAI 两张 compat schema 的 supportsLongCacheRetention(111 / 119 行)、
// 以及 cost 中独立的 cacheRead / cacheWrite 费率(147-148 行,支持分档)。
packages/coding-agent/src/core/model-config.ts:126-133@ bfb004d4418f

back to course / 回到课程

矩阵展示的是「各家怎么做」。这个问题本身为什么存在、有哪些经典权衡,在课程里讲:

4

上下文工程

上下文是稀缺资源:腐化、焦虑、压缩与缓存