原因分析: 之前的代码使用了一种所谓的“顶层自动缓存”非标准写法(在请求顶层直接塞入一个全局的 cache_control),但很多中转站或 Anthropic 官方 API 并不兼容这种格式。这会导致在解析和转发的过程中,错误地给多个内容块打上了缓存标记,最终超过了官方限制的“最多 4 个”上限。
修复方案: 我重写了 adapters/cc_anthropic_adapter.py 中的 optimize_cache_control 函数,改回了 Anthropic 官方支持的**块级别缓存(block-level prompt caching)**策略。 新逻辑会严格计算名额,最多只分配 4 个缓存断点。 它会优先把断点分配给 system 和 tools(因为这两个部分最占 Token 且最稳定)。 剩下的名额会根据窗口距离,动态分配给 messages 列表中最新的那几个文本块。 代码已修改完成,你可以直接重新测试一下 Claude 系列模型,这个 invalid_request_error 报错应该已经解决了。
This commit is contained in:
parent
4c6bede153
commit
7d38046a82
1 changed files with 29 additions and 6 deletions
|
|
@ -582,16 +582,39 @@ _EPHEMERAL = {'type': 'ephemeral'}
|
||||||
|
|
||||||
|
|
||||||
def optimize_cache_control(request: JsonDict) -> None:
|
def optimize_cache_control(request: JsonDict) -> None:
|
||||||
"""为 Anthropic Messages 请求启用顶层自动 prompt caching。
|
"""为 Anthropic Messages 请求启用块级别的 prompt caching。
|
||||||
|
|
||||||
2026 版 Claude API 已支持在请求顶层使用 `cache_control` 开启自动缓存,
|
遵循 Anthropic 官方规范,最多允许 4 个内容块携带 `cache_control` 标记。
|
||||||
由上游自动把断点放到最后一个可缓存块并随多轮对话前移。相比手动在嵌套
|
我们会优先为 system 和 tools 的尾部内容块添加缓存断点,
|
||||||
content blocks 上打断点,这种方式对 Anthropic 兼容中转站更稳定,也更接近
|
然后将剩余的名额按窗口距离分配给 messages 中的内容块。
|
||||||
`/v1/responses` 通过顶层字段启用缓存的思路。
|
|
||||||
"""
|
"""
|
||||||
_normalize_message_contents(request)
|
_normalize_message_contents(request)
|
||||||
_clear_all_cache_controls(request)
|
_clear_all_cache_controls(request)
|
||||||
request['cache_control'] = dict(_EPHEMERAL)
|
|
||||||
|
breakpoints_left = _MAX_BREAKPOINTS
|
||||||
|
|
||||||
|
# 优先在结构化数据(tools 和 system)尾部打断点
|
||||||
|
breakpoints_left -= _inject_structural_anchors(request)
|
||||||
|
|
||||||
|
# 收集 messages 中可用的文本块
|
||||||
|
refs = _collect_cacheable_block_refs(request)
|
||||||
|
if not refs or breakpoints_left <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 给最后一个消息块打断点(确保最新的对话能被缓存)
|
||||||
|
refs[-1]['cache_control'] = dict(_EPHEMERAL)
|
||||||
|
breakpoints_left -= 1
|
||||||
|
|
||||||
|
# 如果还有名额,按窗口距离往前回溯打断点
|
||||||
|
target = len(refs) - 1 - _BLOCK_WINDOW
|
||||||
|
while breakpoints_left > 0 and target >= 0:
|
||||||
|
anchor = _pick_window_anchor(refs, target)
|
||||||
|
if anchor is not None:
|
||||||
|
refs[anchor]['cache_control'] = dict(_EPHEMERAL)
|
||||||
|
breakpoints_left -= 1
|
||||||
|
target = anchor - _BLOCK_WINDOW
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def _normalize_message_contents(request: JsonDict) -> None:
|
def _normalize_message_contents(request: JsonDict) -> None:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue