OpenAI 与 Anthropic 缓存机制对比:多模型架构下的统一缓存层设计
OpenAI 与 Anthropic 缓存机制的基本原理、计费差异和在 Cursor/Claude Code 等工具里的开启方法,prompt caching 在国内中转下省成本指南 已经讲得很细。这篇文章不重复那些内容,而是聚焦一个更偏架构设计的问题:如果你的应用需要同时对接多个模型厂商(比如按任务类型路由到不同模型,或者做多厂商 Fallback),两种截然不同的缓存控制模型——OpenAI 的”自动、不可控”和 Anthropic 的”显式、可控”——会给统一架构设计带来什么麻烦,以及怎么处理。
一、控制粒度的本质差异,不只是”要不要手动标记”
表面上看,两者的差异是”OpenAI 不用改代码,Anthropic 要加 cache_control 字段”,但往深一层看,这个差异决定了你能对缓存行为施加多少控制:
| 控制维度 | Anthropic | OpenAI |
|---|---|---|
| 缓存边界 | 显式指定,最多 4 个断点 | 引擎自动判定 prefix,不可指定 |
| 是否命中的可预测性 | 高(边界固定,行为确定) | 较低(受 prefix 变化、路由等因素影响) |
| TTL 控制 | 可选标准 5 分钟或 extended 1 小时 | 不可配置,固定短 TTL |
| 强制预热 | 可以主动发一次请求预热缓存 | 无法主动预热,只能等待自然命中 |
对于只用单一厂商的团队,这种差异体现为”要不要多写几行代码”。但对于需要在多个模型之间路由请求的架构(比如按任务复杂度把简单任务路由到便宜模型、复杂任务路由到强模型),这个差异意味着你没法用同一套缓存策略同时服务两边——针对 Anthropic 精心设计的缓存断点位置,对 OpenAI 没有意义;而 OpenAI 依赖自动判定的机制,也没有对应的手段去主动干预。
二、统一 Prompt 结构规范:为两种机制同时兜底
即便控制方式不同,两家缓存机制有一个共同的前提:不变的内容要放在前面,易变的内容要放在后面。这是可以在架构层面统一约束的规范,不管路由到哪家都受益:
def build_messages(system_prompt, project_context, conversation_history, user_query):
# 稳定内容在前:system prompt → 项目上下文 → 历史对话 → 本次提问
# 这个顺序对 Anthropic 的 cache_control 断点划分和 OpenAI 的自动 prefix 缓存都有效
return {
"system": system_prompt, # 几乎不变
"context": project_context, # 偶尔变
"history": conversation_history, # 累加式变化
"query": user_query, # 每次都变,必须放最后
}
在这个统一结构之上,针对不同厂商做适配层:调用 Anthropic 时在 system 和 context 之后插入 cache_control 标记;调用 OpenAI 时不需要做任何事,因为顺序本身已经让稳定内容自然落在 prefix 里。这样上层业务代码只需要维护一份 Prompt 组装逻辑,不用为每个厂商写一套完全不同的缓存优化代码。
三、监控层:两种机制返回的信号完全不同,需要归一化
要衡量缓存到底有没有生效,两家 API 返回的字段名和语义都不一样:
def normalize_cache_metrics(provider, response):
if provider == "anthropic":
usage = response.usage
return {
"cache_hit_tokens": usage.cache_read_input_tokens,
"cache_write_tokens": usage.cache_creation_input_tokens,
"total_input_tokens": usage.input_tokens,
}
elif provider == "openai":
details = response.usage.prompt_tokens_details
return {
"cache_hit_tokens": details.cached_tokens,
"cache_write_tokens": None, # OpenAI 不暴露写入量
"total_input_tokens": response.usage.prompt_tokens,
}
如果你的监控面板需要展示”跨所有模型的整体缓存命中率”,必须先做这层归一化,否则原始字段名不一致,聚合查询会直接出错或者漏算。这里有个容易被忽略的点:OpenAI 不暴露缓存写入量,所以没法像 Anthropic 那样算出精确的”写入成本 vs 命中节省”的净收益,只能用命中 token 数做一个近似估算,做成本报表时要在文档里注明这个口径差异,避免团队后续误读数据。
四、路由策略与缓存命中率的冲突
多模型路由架构常见的一种设计是”根据当前负载或成本把请求动态分配到不同模型实例”,但这种动态路由如果不考虑缓存,会直接摧毁缓存命中率——同一个用户的连续对话如果被路由到不同的后端实例或者不同的模型版本,之前攒下的缓存直接作废。
解决方式是会话粘性(session affinity):同一个对话 ID 在缓存 TTL 有效期内,尽量路由到同一个后端路径,而不是每次请求都重新负载均衡:
def route_request(session_id, task_type, available_backends):
# 优先复用上次成功的后端,保持缓存命中链路
last_backend = session_affinity_cache.get(session_id)
if last_backend and last_backend in available_backends:
return last_backend
backend = select_backend_by_task(task_type, available_backends)
session_affinity_cache.set(session_id, backend, ttl=300) # 和缓存 TTL 对齐
return backend
这里的 ttl=300 特意和 Anthropic 标准缓存的 5 分钟 TTL 对齐——会话粘性的有效期不需要超过缓存本身的有效期,否则粘性策略保留的时间比缓存实际存活的时间还长,没有意义。如果用的是 extended 1 小时缓存,粘性 TTL 也应该相应调整到 1 小时。
五、测试策略:缓存命中率的回归防护
Prompt 结构一旦被后续需求改动(比如给 system prompt 加了一段新指令,插入位置不当),很容易在不知不觉中破坏缓存命中率,而这种劣化通常不会在功能测试里暴露——功能照样正确,只是成本悄悄涨上去了。建议在 CI 里加一个专门的缓存命中率回归测试:
def test_cache_hit_rate_regression():
session = start_test_session()
first_response = send_message(session, "第一条消息")
second_response = send_message(session, "第二条消息,同一会话")
hit_tokens = normalize_cache_metrics(PROVIDER, second_response)["cache_hit_tokens"]
assert hit_tokens > 0, "第二轮请求未命中缓存,检查 Prompt 结构是否被破坏"
这类测试不需要精确断言命中多少 token(不同厂商、不同时间点的具体数字会波动),只需要断言”命中量大于 0”,就能在 Prompt 结构被意外改动导致缓存完全失效时及时报警,避免上线之后才在账单上发现异常。
六、小结
OpenAI 和 Anthropic 的缓存机制在计费细节和开启方式上的差异,prompt caching 在国内中转下省成本指南 已经覆盖。对于需要同时对接多个厂商的架构,真正的挑战在于控制粒度不对等带来的设计约束:用统一的 Prompt 结构规范同时兼顾两种机制、把不同厂商的监控字段归一化、让动态路由策略考虑会话粘性避免破坏缓存链路,以及用轻量的回归测试防止 Prompt 改动悄悄拖高成本。这些工作的价值随着接入的模型数量增加而增加,如果只用单一厂商,不需要投入这套抽象。
七、相关阅读
如果你的架构需要在多个模型厂商之间做统一的缓存监控和成本核算,用 YoTradeApi 中转可以在同一个账单和后台里查看各家模型的缓存命中数据,省去分别对接多个厂商控制台的麻烦。