缓存命中率实战:把提示词拆成可复用前缀
先固定前缀,再讨论命中率
我给代码审查脚本加缓存时,先删掉了提示词开头的当前时间。缓存复用依赖相同前缀,不是两段文字意思接近就能命中。把项目规范、接口约定放前面,把本次问题、日志和补丁放最后,才有可重复验证的基础。([developers.openai.com](https://developers.openai.com/api/docs/guides/prompt-caching/diagnostics?utm_source=openai))
下面用 Fluxo 跑一次终端实验。它用一个密钥和 https://api.fluxorelay.com 这个基础地址接入多个模型系列,也支持 Claude Code、Codex、Gemini CLI 和 OpenAI SDK。不过,统一入口不代表不同模型共享缓存;实验期间先固定模型与请求结构。
第一步:准备密钥与真实模型标识
需要 Bash、curl、jq 和 Python 3。在项目根目录执行以下命令;密钥通过隐藏输入读取,不直接写进命令历史。
export OPENAI_BASE_URL='https://api.fluxorelay.com'; read -rsp 'Fluxo API key: ' OPENAI_API_KEY; echo; export OPENAI_API_KEY; mkdir -p /tmp/fluxo-cache-lab
下面按 OpenAI 兼容接口路径请求模型列表。先检查响应,再复制实际模型标识,不要把展示名称直接当成 API 标识。
curl --fail-with-body -sS "$OPENAI_BASE_URL/v1/models" -H "Authorization: Bearer $OPENAI_API_KEY" | jq .
以 GPT-6.1 Sol 为例,粘贴账户实际返回的对应标识。若接口路径或响应结构不同,先按账户文档调整,不要继续发送猜测的请求。
read -rp '实际模型 ID: ' MODEL; export MODEL
第二步:把仓库规范冻结成前缀
支持缓存的模型通常要求前缀达到最低长度,具体门槛随模型而异。不要为了命中率重复无用文字;应该放入每次任务确实需要的项目上下文。官方指南也建议将稳定内容放前面,将变化内容放后面。([developers.openai.com](https://developers.openai.com/api/docs/guides/prompt-caching?utm_source=openai))
下面读取仓库里真实存在的规范文件,并将结果冻结到实验目录。先确认文件不含密钥、客户信息或其他不应发送的数据。
python3 -c 'from pathlib import Path; files=[Path("AGENTS.md"),Path("README.md")]; parts=["你是项目代码审查助手。依据以下规范回答,明确区分事实与推测。"]+["文件:"+str(p)+"\n"+p.read_text(encoding="utf-8") for p in files if p.is_file()]; Path("/tmp/fluxo-cache-lab/prefix.txt").write_text("\n\n".join(parts),encoding="utf-8")'
以后更新规范时重新生成这个文件;不要在每次请求前添加时间戳、随机编号或不同顺序的文件清单。
第三步:只改变尾部问题
连续发五次请求,系统消息保持不变,用户消息中的问题编号变化。完整响应保存在本地,方便检查实际返回的用量字段。不要预设第一次一定写入缓存,也不要承诺第二次必然命中;长度、缓存状态与兼容设置都会影响结果。([developers.openai.com](https://developers.openai.com/api/docs/guides/prompt-caching/diagnostics?utm_source=openai))
for i in 1 2 3 4 5; do jq -n --arg model "$MODEL" --rawfile prefix /tmp/fluxo-cache-lab/prefix.txt --arg question "第${i}次检查:根据项目规范,列出代码审查时最值得关注的三个风险,简短回答。" '{model:$model,messages:[{role:"system",content:$prefix},{role:"user",content:$question}]}' > /tmp/fluxo-cache-lab/request.json; curl --fail-with-body -sS "$OPENAI_BASE_URL/v1/chat/completions" -H "Authorization: Bearer $OPENAI_API_KEY" -H 'Content-Type: application/json' --data-binary @/tmp/fluxo-cache-lab/request.json -o "/tmp/fluxo-cache-lab/response-$i.json" || break; sleep 2; done
这一步测试的是前缀复用,而不是缓存整段答案。不同问题仍然需要正常生成输出。
第四步:按令牌计算,而不是数成功请求
在 Chat Completions 的标准用量结构中,检查 usage.prompt_tokens_details.cached_tokens。先打印原始 usage;如果中继没有返回这个字段,应该记录“未知”,不能擅自记成零。([developers.openai.com](https://developers.openai.com/cookbook/examples/prompt_caching101?utm_source=openai))
jq '{usage, error}' /tmp/fluxo-cache-lab/response-*.json — 下面计算本轮输入令牌缓存占比:缓存读取令牌总数除以输入令牌总数。它不是 HTTP 成功率,也不是账单折扣率;缺失字段时命令会明确报错。
jq -s 'if all(.[]; (.usage.prompt_tokens | type) == "number" and (.usage.prompt_tokens_details.cached_tokens | type) == "number") then (map(.usage.prompt_tokens) | add) as $input | (map(.usage.prompt_tokens_details.cached_tokens) | add) as $cached | {input_tokens:$input,cached_tokens:$cached,token_hit_rate:(if $input > 0 then $cached / $input else null end)} else error("缓存用量字段缺失,请检查原始响应;不要按零计算") end' /tmp/fluxo-cache-lab/response-*.json
保留冷启动请求一起统计,可以看到这批任务的实际表现。若只分析预热后的请求,也要明确排除规则,别拿两个不同口径比较。
第五步:用前缀改动定位缓存回退
做对照实验时,每次只改一个变量:先保持前缀不变运行,再修改开头的一条规范重跑。前缀必须精确匹配;工具定义和兼容请求设置变化,也可能导致原本预期的复用失效。([developers.openai.com](https://developers.openai.com/api/docs/guides/prompt-caching/diagnostics?utm_source=openai))
生产里我会把前缀版本、模型标识、输入令牌、缓存读取量和请求耗时记在一起,而不是记录敏感提示词正文。Fluxo 按实际令牌计费,包含缓存读取与写入,没有月费,因此最终要同时看账单和延迟,不能只追一个漂亮的命中率。
如果你想用自己的仓库跑这组实验,可以创建一个 Fluxo 账户再试。邀请机制另外提供被邀请用户付费后的分成;它与这次缓存实验的统计口径无关。