RH
RouterHub AI API中转站哪家好?2026最新免费稳定大模型接口平台推荐
博客

AI API 降本教程:哪些内容该缓存,怎样提高缓存命中率

很多人听过“缓存能省钱”,但一落地就变成两种极端:要么什么都不缓存,要么什么都想缓存。真正有效的缓存策略没有那么玄,核心就一句话:把重复、稳定、够长的前缀放前面,别让每次请求都从零开始。本文会讲清楚什么值得缓存、什么不值得缓存,并给出可复制的 Python 代码片段。

muchacha1@163.com 2026-04-10 23:13

正文

这篇教程适合谁

适合:

  • 你有很长的 system prompt
  • 你每次都会重复传工具定义、业务规则、知识库说明
  • 你在做文档问答、代码仓分析、长上下文助手

不太适合:

  • 每次请求都很短
  • 每次请求内容完全不同
  • 你连 prompt 结构都还没稳定下来

先讲人话:缓存到底在省什么

缓存不是“把回答缓存起来”,而是把重复出现的输入前缀缓存起来。

也就是说,真正能省钱的通常是这些内容:

  • 固定 system prompt
  • 固定工具定义
  • 固定输出格式说明
  • 大段重复背景知识
  • 同一份长文档、同一份代码仓说明

真正不适合缓存的通常是:

  • 每次都变的用户提问
  • 带用户私有变量的动态段落
  • 很短的 prompt

各平台现在怎么做缓存

2026-04-13 可查到的官方文档,主流平台大致是这样:

  • OpenAI:自动 prompt caching,支持 prompt_cache_key
  • Anthropic:显式 cache_control,适合把可复用内容标出来
  • Gemini:同时有 implicit caching 和 explicit caching

这里最重要的共同点只有一个:

要想命中缓存,前缀必须稳定,而且最好放在最前面。

第一原则:静态内容放前面,动态内容放后面

这是缓存命中率的核心。

错误写法:

用户问题
系统说明
工具定义
输出格式

更合理的写法:

系统说明
工具定义
输出格式
用户问题

因为缓存命中的本质就是“前缀相同”。

一个最简单、最实用的 Python 写法

下面这段代码不依赖任何平台特性,先把 prompt 结构整理对。

from dataclasses import dataclass


@dataclass
class PromptParts:
    system_rules: str
    tool_spec: str
    output_schema: str
    user_input: str


def build_prompt(parts: PromptParts) -> str:
    # 静态内容放前面,动态内容放最后
    return f"""
【系统规则】
{parts.system_rules}

【工具定义】
{parts.tool_spec}

【输出格式】
{parts.output_schema}

【用户输入】
{parts.user_input}
""".strip()


parts = PromptParts(
    system_rules="你是企业客服助手,必须使用正式语气,不要编造政策。",
    tool_spec="你可以使用 order_lookup(order_id) 工具查询订单状态。",
    output_schema='返回 JSON:{"status": "", "reply": ""}',
    user_input="用户问:订单 12345 为什么还没发货?",
)

print(build_prompt(parts))

这段代码看起来很朴素,但它决定了你后面能不能吃到平台缓存红利。

第二原则:先缓存“最稳定的长前缀”

不是所有内容都值得缓存。 最优先考虑的是这三类:

1. 很长,而且几乎不变的 system prompt

比如企业助手规范、品牌语气规则、审核规则。

2. 很长的工具定义

如果你工具很多,工具 schema 本身就会吃掉很多 token。

3. 重复使用的大文档

比如:

  • 产品手册
  • 合同模板
  • 代码仓结构说明

第三原则:缓存是有成本的,不是默认全开

这件事一定要讲清楚。

  • OpenAI 的 prompt caching 是自动的,官方文档说明无需额外代码就可能命中
  • Anthropic 的 cache write 和 cache read 有不同价格倍率
  • Gemini 的 explicit caching 还涉及 TTL 和 storage 成本

所以缓存不是“白送魔法”,而是“在重复足够多时更划算”。

一个本地辅助缓存的 Python 示例

这段代码不是替代平台缓存,而是帮你在应用层避免重复拼装和重复上传静态内容。

import hashlib
import json
from pathlib import Path


CACHE_DIR = Path(".cache/prompts")
CACHE_DIR.mkdir(parents=True, exist_ok=True)


def make_cache_key(system_rules: str, tool_spec: str, output_schema: str) -> str:
    raw = json.dumps(
        {
            "system_rules": system_rules,
            "tool_spec": tool_spec,
            "output_schema": output_schema,
        },
        ensure_ascii=False,
        sort_keys=True,
    )
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()


def save_static_prefix(cache_key: str, prefix_text: str) -> Path:
    path = CACHE_DIR / f"{cache_key}.txt"
    path.write_text(prefix_text, encoding="utf-8")
    return path


def load_static_prefix(cache_key: str) -> str | None:
    path = CACHE_DIR / f"{cache_key}.txt"
    if not path.exists():
        return None
    return path.read_text(encoding="utf-8")


system_rules = "你是售后支持助手,回答必须引用工单规则。"
tool_spec = "你可以调用 ticket_lookup(ticket_id) 和 refund_policy()。"
output_schema = '{"risk_level": "", "answer": ""}'

cache_key = make_cache_key(system_rules, tool_spec, output_schema)

prefix = load_static_prefix(cache_key)
if prefix is None:
    prefix = f"{system_rules}\n\n{tool_spec}\n\n{output_schema}"
    save_static_prefix(cache_key, prefix)

user_input = "用户说:退款多久到账?"
final_prompt = f"{prefix}\n\n{user_input}"
print(final_prompt)

这段代码解决的是一个很现实的问题: 你在自己的应用层,也应该把“重复前缀”和“动态输入”分开管理。

如果你要接 OpenAI,可以怎么写

按当前 OpenAI 文档,prompt caching 会自动生效;如果你想让共享前缀更稳定地路由到同一类请求,可以使用 prompt_cache_key

import os
import httpx


API_KEY = os.getenv("OPENAI_API_KEY", "")

payload = {
    "model": "gpt-5.1",
    "input": [
        {
            "role": "system",
            "content": "你是企业知识库助手,必须先遵守下面的审核规则......"
        },
        {
            "role": "user",
            "content": "请总结这份文档里关于退款条件的部分。"
        }
    ],
    "prompt_cache_key": "kb_assistant_v1",
}

with httpx.Client(timeout=60) as client:
    response = client.post(
        "https://api.openai.com/v1/responses",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json=payload,
    )
    response.raise_for_status()
    data = response.json()
    print(data["usage"])

这里真正值得关注的是 usage 里的缓存命中情况,而不是“我觉得应该命中了”。

如果你要接 Anthropic,可以怎么写

Anthropic 的缓存思路更显式。你要把可复用内容标出来。

payload = {
    "model": "claude-sonnet-4-20250514",
    "max_tokens": 512,
    "system": [
        {
            "type": "text",
            "text": "你是客服质检助手,必须先遵守下面的审核准则。",
            "cache_control": {"type": "ephemeral"},
        }
    ],
    "messages": [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "请检查这段客服回复有没有违规承诺。"}
            ]
        }
    ],
}

这个例子最重要的不是具体字段,而是思路: 把可复用内容明确标成缓存边界。

如果你要接 Gemini,可以怎么写

Gemini 的 explicit caching 适合重复用长文档或大段上下文。

from google import genai
from google.genai import types

client = genai.Client()

cache = client.caches.create(
    model="gemini-2.5-flash",
    config=types.CreateCachedContentConfig(
        display_name="refund-policy-v1",
        system_instruction="你是退款政策助手,必须严格按文档回答。",
        contents=["这里放长文档或上传后的文件引用"],
        ttl="3600s",
    ),
)

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="请总结退款到账时间。",
    config=types.GenerateContentConfig(cached_content=cache.name),
)

print(response.usage_metadata)

最容易踩的 5 个坑

1. 把变量内容塞进前缀里

比如用户 ID、订单号、时间戳。 这些内容一变,缓存命中率就会明显下降。

2. prompt 顺序老变

今天把工具定义放前面,明天又换位置,缓存基本就白做了。

3. 短 prompt 硬做缓存

太短的请求,本来就吃不到多少收益。

4. 只看“开了缓存”,不看命中率

缓存不是布尔值,而是效果问题。 你要看:

  • 命中率
  • cached tokens
  • 延迟下降是否明显

5. 缓存了不该缓存的敏感动态内容

不要把用户强相关、一次性、敏感变量混进可复用前缀。

总结

  • 哪些 system prompt 固定
  • 哪些工具定义固定
  • 哪些文档值得缓存
  • 哪些内容绝对不能放进缓存前缀

最后一句话

缓存真正省钱的前提,不是你“打开了缓存”,而是你把 prompt 结构整理成了“静态在前,动态在后”。

如果结构没理顺,缓存就只是一个听起来很美的功能。

官方资料

  • OpenAI Prompt Caching

https://developers.openai.com/api/docs/guides/prompt-caching

  • Anthropic Prompt Caching

https://platform.claude.com/docs/en/build-with-claude/prompt-caching

  • Gemini Context Caching

https://ai.google.dev/gemini-api/docs/caching