Skip to content

feat(anthropic): configurable prompt-cache TTL, still 5m by default - #61

Merged
zhanghanduo merged 2 commits into
mainfrom
feat/anthropic-prompt-cache-ttl
Oct 5, 2026
Merged

zhanghanduo merged 2 commits into
mainfrom
feat/anthropic-prompt-cache-ttl

Conversation

@zhanghanduo

Copy link
Copy Markdown
Collaborator

为什么

AnthropicClient 自己放两个 ephemeral 断点(system 前缀 + 最后一条持久消息),但一直只用 API 缺省的 5 分钟。那个窗口的参照物不是任务多长,而是一轮多长 —— 缓存项在最后一次使用后才开始计时,所以只要相邻两次调用的间隔超过 TTL,整条前缀就作废并按写价重写。

ApodexHarness 2026-10-05 的 GDPval 批次量到了代价(6056 次 claude-opus-5-5 effort=max 调用):

相邻调用间隔 p90 355s,12.9% 超过 300s
间隔 >300s 的冷率 35.7%
间隔 ≤300s 的冷率 9.5%
turn>3 且写入 >30k 的冷调用 498 次,中位 81k,合计 61.6M 写 token

改了什么

  • prompt_cache_ttl("5m" | "1h")加到 AnthropicClient 构造器,并由 build_protocol_client 从 cfg 透传 —— 这是 profile 级的键,一个模型的缓存寿命跟着选它的 profile 走,而不是跟着部署走。
  • 空值与 5m 都不发 ttl 字段:5m 本就是 API 缺省,不发能让所有现有消费者的请求逐字节不变。
  • ANTHROPIC_PROMPT_CACHE_TTL 作为整次运行的兜底(给 profile 够不到的构造点),client 自己声明的值优先。
  • 非法值(30m、数字、带空格的单位)在构造期抛 ValueError,不回落到 5m —— 回落只会表现为"命中率下降",而命中率没人盯着,正是这个旋钮要修的那种静默。
  • TTL 只决定已有断点的寿命,不会放断点:ANTHROPIC_PROMPT_CACHE=0 依然通杀。
  • 计费不用改:_anthropic_cache_write_tokens 早就把 cache_creation.ephemeral_1h_input_tokens 算进去了。

消费者注意:该按模型开,不要全局开

1h 的写价是基础输入的 2×,5m 是 1.25×。所以只有"一轮可能比 5 分钟还长"的慢模型才赚;快模型没有冷重写可省,只会多付写价。判据和上面那组实测数字都写进了 docs/provider-substrate-boundary.md。

网关实测(llm-hub,2026-10-05)

网关必须真的转发该字段,所以实测过 —— 而且查了两项,因为 ttl 被悄悄剥掉时,5 秒回读的表现和成功完全一样:

arm HTTP write 记到的桶 548s 后回读
{"type":"ephemeral"}(现状) 200 ephemeral_5m 40615 失效,整条重写
+ ttl:"1h" 200 ephemeral_1h 43215 完整命中,creation 0
+ ttl:"1h" + beta 头 200 ephemeral_1h 38014 完整命中,creation 0

anthropic-beta: extended-cache-ttl-2025-04-11 不需要(已 GA)。

测试

新增 8 个:缺省不发字段、1h 落在两个断点上、显式 5m 不上线、env 兜底与 client 优先、非法值在构造期/env 下抛错、kill switch 仍通杀、builder 透传。

全量 1913 passed / 2 skipped;ruff 全清;pyright(改动模块)0 error。按 docs/versioning.md,feature PR 不动版本号与 CHANGELOG,只加 changes/61.feature.md。

🤖 Generated with Claude Code

zhanghanduo and others added 2 commits October 5, 2026 19:11
`AnthropicClient` 自己放两个 ephemeral 断点(system 前缀 + 最后一条持久消息),
但一直只用 API 缺省的 5 分钟。那个窗口的参照物不是任务多长,而是**一轮**多长:
缓存项在最后一次使用后才开始计时,所以只要相邻两次调用的间隔超过 TTL,整条
前缀就作废并按写价重写。

ApodexHarness 2026-10-05 的 GDPval 批次量到了代价(6056 次 claude-opus-5-5
effort=max 调用):相邻调用间隔 p90 = 355s、**12.9% 超过 300s**,这些调用冷率
**35.7%**,而间隔 ≤300s 的只有 9.5%;turn>3 且写入 >30k 的冷调用 498 次、
中位 81k,合计 61.6M 写 token。

- `prompt_cache_ttl`("5m" | "1h")加到构造器,并由 `build_protocol_client`
  从 cfg 透传,所以这是**profile 级**的键 —— 一个模型的缓存寿命跟着选它的
  profile 走,而不是跟着部署走。
- 空值与 "5m" 都**不发** ttl 字段:5m 本就是 API 缺省,不发能让所有现有消费者
  的请求逐字节不变。
- `ANTHROPIC_PROMPT_CACHE_TTL` 作为整次运行的兜底(给 profile 够不到的构造点),
  client 自己声明的值优先。
- 非法值(`30m`、数字、带空格的单位)在构造期抛 `ValueError`,不回落到 5m ——
  回落只会表现为"命中率下降",而命中率没人盯着,正是这个旋钮要修的那种静默。
- TTL 只决定已有断点的寿命,不会放断点:`ANTHROPIC_PROMPT_CACHE=0` 依然通杀。
- 计费不用改:`_anthropic_cache_write_tokens` 早就把
  `cache_creation.ephemeral_1h_input_tokens` 算进去了。

**该按模型开,不要全局开**:1h 的写价是基础输入的 2×,5m 是 1.25×,所以只有
"一轮可能比 5 分钟还长"的慢模型才赚;快模型没有冷重写可省,只会多付写价。
文档里把这条判据和实测数字都写下来了。

网关必须真的转发该字段,所以 llm-hub 是实测过的(2026-10-05,
temp/2026-10-05_llmhub-1h-ttl-实测.md):带 `ttl:"1h"` 返回 HTTP 200,服务端把
write 记进 **1h 桶**而非 5m 桶(43215 vs 对照组 40615 进 5m),548s 后回读
**完整命中**(creation 0),而未配置的对照组已失效并整条重写。
`anthropic-beta: extended-cache-ttl-2025-04-11` **不需要**。这里两项都查了:
ttl 被网关悄悄剥掉时,5 秒回读的表现和成功完全一样,只有计费桶和跨窗口回读
能区分。

测试:新增 8 个(缺省不发字段、1h 落在两个断点上、显式 5m 不上线、env 兜底与
client 优先、非法值在构造期/env 下抛错、kill switch 仍通杀、builder 透传)。
全量 1913 passed / 2 skipped;ruff 与 pyright(改动模块)清。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zhanghanduo
zhanghanduo merged commit 21f2f6f into main Oct 5, 2026
5 checks passed
@zhanghanduo
zhanghanduo deleted the feat/anthropic-prompt-cache-ttl branch October 5, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant