Prompt Cache 与成本
复用提示词前缀,并通过用量字段确认缓存命中。
Prompt Cache 复用重复输入的前缀计算,不复用上一条回答。是否命中要看返回的缓存 token 数,不能凭回答相同或速度更快判断。
本页的 OpenAI 示例使用 deepseek-flash,不添加缓存控制参数。2026-10-12 实测:26,016 个输入 token 的第二次请求报告 25,856 个缓存 token;这是一组观测值,不是每次调用的保证。
1. 准备终端
将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY;获取方式见 Key 与鉴权。不要把 Key 写入代码。准备 Python 和 uv;TypeScript 示例使用 Bun。
test -n "$PRAXIS_API_KEY"期望:退出码为 0。示例默认使用 https://api.deepraxis.ai。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。
将每段 Python 保存为标明的文件名,以 uv run -q --no-project --with openai --with anthropic python <文件名> 执行。各段代码独立运行。
2. 连续请求相同前缀
将不常变的指令和资料放在前面,当前问题放在后面,保持模型、前缀内容和顺序一致。下面生成较长的演示资料并连续请求两次,会产生两次模型调用费用。
保存为 prompt-cache.py,运行 uv run -q --no-project --with openai --with anthropic python prompt-cache.py。
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.ai') + '/openai/v1')
prefix = '\n'.join(f'Record {i}: Paris is in France. The demo temperature is 22 Celsius.' for i in range(1500))
hits = []
for turn in range(2):
response = client.chat.completions.create(model='deepseek-flash',
messages=[{'role':'system','content':prefix},
{'role':'user','content':'What country is Paris in? Reply with one word.'}],
reasoning_effort='none', max_tokens=64)
print('X-Request-Id:', response._request_id)
assert response.choices[0].finish_reason == 'stop'
usage = response.usage
assert usage and usage.prompt_tokens_details is not None
cached = usage.prompt_tokens_details.cached_tokens
assert cached is not None
hits.append(cached)
print(turn + 1, 'prompt_tokens:', usage.prompt_tokens, 'cached_tokens:', cached)
assert hits[1] > 0, 'No cache hit observed on the second request'期望:输出两次输入和缓存 token 数,第二次 cached_tokens > 0,退出码为 0。重复运行时第一次也可能命中,不要求第一次为零。
usage.prompt_tokens_details.cached_tokens 是输入中的缓存部分,已包含在 prompt_tokens 内,不能再相加。缓存只减少符合条件的输入费用,不使输出免费;成本按所选模型的缓存输入、普通输入和输出单价计算,不套用固定折扣。缓存最短前缀和保留时间由模型决定。
3. 用 TypeScript 检查命中
保存为 prompt-cache.ts,运行 bun prompt-cache.ts;无需安装 SDK。
import assert from 'node:assert/strict';
const base = process.env.PRAXIS_API_BASE ?? 'https://api.deepraxis.ai';
const prefix = Array.from({length: 1500}, (_, i) =>
`Record ${i}: Paris is in France. The demo temperature is 22 Celsius.`).join('\n');
const hits: number[] = [];
for (let turn = 0; turn < 2; turn++) {
const response = await fetch(`${base}/openai/v1/chat/completions`, {
method: 'POST', headers: {'Authorization': `Bearer ${process.env.PRAXIS_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({model: 'deepseek-flash', max_tokens: 64, reasoning_effort: 'none',
messages: [{role: 'system', content: prefix},
{role: 'user', content: 'What country is Paris in? Reply with one word.'}]}),
});
console.log('X-Request-Id:', response.headers.get('x-request-id'));
const data = await response.json();
assert(response.ok, JSON.stringify(data));
assert.equal(data.choices[0].finish_reason, 'stop');
const cached = data.usage.prompt_tokens_details?.cached_tokens;
assert.equal(typeof cached, 'number');
hits.push(cached);
console.log(turn + 1, 'prompt_tokens:', data.usage.prompt_tokens, 'cached_tokens:', cached);
}
assert(hits[1] > 0, 'No cache hit observed on the second request');期望:第二次报告正数 cached_tokens,退出码为 0。
4. 观察 Anthropic cache_control
Messages 可在稳定文本块上设置 cache_control: {type: "ephemeral"}。本例使用 claude-opus-5,连续调用后分别读取 cache_creation_input_tokens(写入)和 cache_read_input_tokens(读取)。Messages 的 input_tokens 是未计入读写缓存部分的输入;总输入为这三项之和。
本次实测请求被接受,但两次读写缓存数均为零,因此此例用于观察缓存状态,不承诺命中。GET /openai/v1/models 的 supported_parameters 可查询参数声明,但不是每次缓存命中的证明;最终仍以用量字段为准。
保存为 prompt-cache-anthropic.py,运行 uv run -q --no-project --with openai --with anthropic python prompt-cache-anthropic.py。
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.ai') + '/anthropic')
prefix = '\n'.join(f'Record {i}: Paris is in France. The demo temperature is 22 Celsius.' for i in range(1500))
hits = []
for turn in range(2):
raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=64,
system=[{'type':'text','text':prefix,'cache_control':{'type':'ephemeral'}}],
messages=[{'role':'user','content':'What country is Paris in? Reply with one word.'}])
print('X-Request-Id:', raw.headers.get('x-request-id'))
message = raw.parse()
assert message.stop_reason == 'end_turn'
usage = message.usage
assert usage.cache_read_input_tokens is not None
assert usage.cache_creation_input_tokens is not None
hits.append(usage.cache_read_input_tokens)
print(turn + 1, 'input:', usage.input_tokens, 'write:', usage.cache_creation_input_tokens,
'read:', usage.cache_read_input_tokens)
print('HIT' if hits[1] > 0 else 'MISS: no cached input reported')期望:两次请求完成,输出读写统计和 HIT 或 MISS: no cached input reported,退出码为 0。只有读取 token 为正数才算观察到命中;退出码 0 只证明请求和统计读取成功。
实测未命中时怎么处理
本次 Messages 示例的实际结果是 cache_creation_input_tokens=0、cache_read_input_tokens=0,没有 HTTP 报错。不要把请求被接受等同于缓存生效。需要复现已验证的缓存命中时,运行上面的 deepseek-flash OpenAI 示例,并检查正数 cached_tokens。
为保持前缀可复用,确认两次请求使用相同模型和完整前缀;不要在前缀中加入时间戳或每次变化的标识。保留用量和 X-Request-Id 以便核对,不能用相同回答代替命中证据。