02 · Prompt Caching 配置参考¶
本文列出所有跟"prompt caching 路由黏性"相关的真实有效配置项、它们的代码出处、默认值、以及网上常见的幻觉字段(看起来合理但代码里根本不存在)。
本文不讨论上游 provider 的
cache_control字段配置 —— 那个由客户端在 request body 里传,与 LiteLLM 配置无关。详见 03-provider-matrix.md.
0. 改哪里?先确认你的部署模式¶
LiteLLM 的配置来源有 3 路并存,不是非此即彼:
| 来源 | 存放位置 | 何时优先 |
|---|---|---|
| YAML 文件 | 镜像内 /app/proxy_server_config.yaml(默认)或 CONFIG_FILE_PATH env 指向的路径 |
启动时加载为 base |
DB(LiteLLM_Config 表) |
PGSQL,param_name='router_settings' 行 |
启动时合并到 YAML,DB 字段覆盖 YAML 同名字段 |
运行时 API(/config/update) |
写入同一个 DB 表 | 等同于改 DB |
合并代码:litellm/proxy/proxy_server.py:3886-3909:
db_router_settings = await prisma_client.db.litellm_config.find_first(
where={"param_name": "router_settings"}
)
config_router_settings = config_data.get("router_settings", {}) # YAML
if config_router_settings and db_router_settings:
combined_router_settings = _update_dictionary(
config_router_settings, # base: YAML
db_router_settings.param_value # 覆盖: DB
)
合并语义看 litellm/utils.py:2700-2714 的 _update_dictionary:
- DB 字段覆盖 YAML 同名字段
- YAML 独有字段保留
- 嵌套 dict 做浅合并
0.1 推荐改哪里?¶
Q: optional_pre_call_checks 字段当前在你的 DB 里存在吗?
(查询: curl /router/settings -> current_values 含此字段?)
│
├── 是 → 必须改 DB(YAML 改了会被 DB 覆盖)
│ 走 §10 Admin API 路径
│
└── 否 (绝大多数情况, 因为 UI 不渲染此字段) → 二选一:
│
├── 你能改镜像内的 YAML 并重新发版?
│ 是 → 改 YAML(最稳, 配置即代码)
│ 看 §0.2 找到 YAML 的实际路径
│
└── 否 → 走 §10 Admin API,
修改即生效, 不需要发版
0.2 确认线上 proxy 实际读哪个 YAML¶
启动时配置文件查找顺序(proxy_server.py:758-786):
CONFIG_FILE_PATH环境变量 —— 最高优先级,如果设了 proxy 只读这个WORKER_CONFIG环境变量 —— 次优先级--config命令行参数 —— DockerfileCMD里默认是/app/proxy_server_config.yaml(对应仓库根目录 proxy_server_config.yaml,Dockerfile:42,54-55)
怎么知道你们用了哪条:
# 方法 A: 看 K8s manifest (如果你有访问)
kubectl describe deployment <litellm-proxy> | grep -i 'CONFIG_FILE_PATH\|WORKER_CONFIG\|--config'
# 方法 B: 问运维同事 "Deployment yaml 里有没有注入 CONFIG_FILE_PATH 这个 env"
# 方法 C: HTTP-only 自检
# 没暴露 /info 端点能直接看; 但可以反推:
# 比对线上 /router/settings 的 current_values 跟仓库 proxy_server_config.yaml 的 router_settings 段
# 如果完全对得上 -> 大概率读的就是仓库这个文件(经 docker build COPY 进容器)
经验法则:
- 用 Dockerfile 默认(
docker run/ 简单 K8s 部署,没显式注入 env)→ 改仓库根目录 proxy_server_config.yaml,发新镜像 - 用 ConfigMap 挂载 → 改 K8s ConfigMap,触发 rollout
0.3 改 YAML 一定要发版才能生效¶
router_settings 在 proxy_server.py:3886-3909 的 _add_router_settings_from_db_to_proxy 是启动时调一次,YAML 改了新 pod 启动时才读到。
- 镜像内置 YAML → 重新 build + push + rollout
- ConfigMap 挂载 → 改 ConfigMap +
kubectl rollout restart - DB 改的 → 重启 proxy(同上)或调依赖 reload 的 endpoint
没有真正的热重载。
1. router_settings(核心)¶
1.1 optional_pre_call_checks ★ 必须¶
- 类型:
List[Literal["prompt_caching", "router_budget_limiting", "responses_api_deployment_check", "session_affinity", "deployment_affinity", "enforce_model_rate_limits"]] - 代码位置:litellm/types/router.py:807-816
- 默认:空列表
- 作用:注册
PromptCachingDeploymentCheck到 router 的 callback 链。没这一行整个机制不存在(router.py:1257-1258)。
可以叠加多个:
按列表顺序串行执行,前者命中后者就拿到单元素列表 no-op。
1.2 enable_pre_call_checks 可选(推荐显式开)¶
- 类型:
bool - 代码位置:router.py:262, router.py:322, router.py:373
- 默认:
False - 官方注释:
"Filter out deployments which are outside context window limits for a given prompt" - 跟 prompt_caching 的关系:
实际跑代码时 PromptCachingDeploymentCheck 的触发只看 _run_pre_call_checks 是否调用了 async_filter_deployments,跟 enable_pre_call_checks 标志没有直接 if/else 关联。但 router 内部还有别的代码路径会用这个开关判断要不要做"上下文窗口超限"过滤。
结论:跟 prompt_caching 不强耦合,但建议显式 true,语义清晰且 future-proof。
1.3 Redis 配置(多 Pod 必须)¶
router_settings:
redis_host: os.environ/REDIS_HOST
redis_port: os.environ/REDIS_PORT
redis_password: os.environ/REDIS_PASSWORD
- 作用:让 router 的
DualCache包含 RedisCache,否则只有 InMemoryCache - 影响 prompt_caching:决定 cache_key → model_id 映射是否跨 Pod / 跨重启共享
- 不配 Redis 的后果:见 01-mechanism.md §6.4 多 Pod 行为
1.4 routing_strategy 不影响命中,只影响未命中时的兜底¶
- 类型:
Literal["simple-shuffle", "least-busy", "usage-based-routing", "latency-based-routing"] - 代码位置:litellm/types/router.py:63-68
- 默认:
"simple-shuffle" - 跟 prompt_caching 的关系:prompt cache 命中时,
async_filter_deployments已经把候选过滤成 1 个,routing_strategy 无事可做;未命中时才走 routing_strategy 选 deployment。
任何 routing_strategy 都能跟 prompt_caching 搭配,没有"必须用 latency-based-routing"的约束。
1.5 跟 deployment_affinity_ttl_seconds 无关¶
router_settings:
deployment_affinity_ttl_seconds: 600 # ← 只影响 deployment_affinity / session_affinity / responses_api_deployment_check
- 作用对象:router.py:1236-1244 构造
DeploymentAffinityCheck时传入 - 不影响 prompt_caching:
PromptCachingCache的 TTL 是 300s 硬编码(prompt_caching_cache.py:190,211)
2. 环境变量¶
2.1 MINIMUM_PROMPT_CACHE_TOKEN_COUNT¶
- 代码位置:litellm/constants.py:252-254
- 默认:
1024 - 作用:
is_prompt_caching_valid_prompt的 token 阈值,低于此值的 prompt 不参与路由黏性 - 影响范围:同时影响
async_filter_deployments(路由前)和async_log_success_event(成功后写入) - 没有 YAML 等价配置
⚠️ 注意:constants.py 被导入即冻结,运行中改环境变量不会热生效,需要重启 proxy。
2.2 Redis 相关环境变量(如果用 os.environ/... 引用)¶
YAML 里写 redis_host: os.environ/REDIS_HOST 时 LiteLLM 会在启动时去环境变量取值。
3. 必备前提:model_list 必须有多个同名 deployment¶
model_list:
- model_name: claude-sonnet-4.5 # ← 同名 ①
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0
aws_profile_name: account-1
model_info:
id: sonnet45-acct1 # ← 可显式给,否则按 _generate_model_id 自动算
- model_name: claude-sonnet-4.5 # ← 同名 ②
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0
aws_profile_name: account-2
model_info:
id: sonnet45-acct2
为什么必须:PromptCachingDeploymentCheck.async_filter_deployments 拿到的 healthy_deployments 是"同一个 model_name 下所有 deployment"。只有 1 个 deployment 时,filter 没事可做。
model_info.id 的语义:
- LiteLLM 自动算(router.py:6142+,对 litellm_params hash)—— 重启后稳定
- 也可显式指定,便于 grep Redis key 时认得出谁是谁
4. 跟 litellm_settings 的关系(澄清混淆)¶
4.1 litellm_settings.cache: true 与本机制完全无关¶
litellm_settings:
cache: true # ← LiteLLM 的"响应缓存",跟 prompt cache 路由没关系
cache_params:
type: redis
host: os.environ/REDIS_HOST
...
- 作用对象:缓存整个
completion响应(输入相同 → 直接返回上次的结果,不打上游) - 代码位置:litellm/caching/caching.py
- 跟 prompt_caching 路由的差异:
| 维度 | litellm_settings.cache |
optional_pre_call_checks: prompt_caching |
|---|---|---|
| 缓存什么 | 整个 completion 响应 | messages 指纹 → deployment_id 映射 |
| 命中后行为 | 不调上游,直接返回缓存结果 | 调上游,但路由到固定 deployment |
| 复用什么 | LiteLLM 本地存的响应 | 上游服务器侧的 prompt cache |
| 失效场景 | 输入变化 | TTL 过期 / 该 deployment 下线 |
→ 两个机制可以同时开,互不干扰。但开 litellm_settings.cache 时 prompt cache 路由几乎用不上 —— 因为响应已经从本地拿了,根本不会去上游。
4.2 enable_caching_on_provider_specific_optional_params 跟本机制无关¶
- 代码位置:litellm/caching/caching.py:295、litellm/init.py:304-305
- 默认:
False - 作用:让
litellm_settings.cache的 cache_key 包含 provider-specific 可选参数(如top_k) - 服务对象:响应缓存的 cache_key 算法
- 跟 prompt_caching 路由毫无关系
5. ⚠️ 幻觉字段清单(代码里根本不存在)¶
下面这些字段在网上配置示例 / AI 生成建议里出现过,但全仓库 grep 0 命中。写进 YAML 不会报错(被解析器丢弃),但完全没效果。
5.1 enable_redis_auth_cache¶
- 来源:常见 AI 建议中出现
- 代码搜索:
grep -r "enable_redis_auth_cache\|redis_auth_cache" litellm/→ 0 命中 - 结论:删掉
5.2 prompt_cache_ttl_seconds / prompt_caching_ttl 等¶
- 代码搜索:
grep -rn "prompt_cache.*ttl\|prompt_caching.*ttl" litellm/→ 0 命中 - 真相:TTL 写死 300s(prompt_caching_cache.py:190,211),无 YAML 配置项
5.3 enable_prompt_caching / prompt_caching_enabled¶
- 真正的启用方式是把
"prompt_caching"字符串加进optional_pre_call_checks列表 - 没有独立的 bool 开关
5.4 单 deployment 级 prompt_caching 标记¶
model_list:
- model_name: claude-sonnet-4.5
model_info:
prompt_caching: true # ❌ 不存在(model_info 没有这个字段定义会被忽略)
supports_prompt_caching: true # ❌ 同上
- LiteLLM 没有"在 deployment 级别声明是否支持 prompt cache"的机制
- 是否参与路由黏性完全由 messages 里有没有
cache_control决定
6. general_settings 里跟本机制相关的项¶
6.1 user_api_key_cache_ttl¶
- 类型:
int(秒) - 代码位置:proxy_server.py:3013-3019
- 默认:60(由 UserAPIKeyCacheTTLEnum 给出)
- 作用:API key 鉴权缓存的内存 TTL —— 跟 prompt_caching 路由毫无关系,纯粹是顺手的优化
代理收到请求时会先查 user_api_key_cache 验证 key 是否合法。这层缓存命中可以避免每次请求都去 DB / master_key 比对,对所有请求都有意义,不只 prompt cache 场景。
加它不错,但不要误以为"为了 prompt cache 才加"。
7. 最小可用配置 vs 完整配置¶
7.1 最小可用(单 Pod 本地测)¶
model_list:
- model_name: claude-sonnet-4.5
litellm_params:
model: openai/claude-sonnet-4-5
api_base: https://relay-a.example.com/v1
api_key: os.environ/RELAY_A_KEY
- model_name: claude-sonnet-4.5 # 同名 ②
litellm_params:
model: openai/claude-sonnet-4-5
api_base: https://relay-b.example.com/v1
api_key: os.environ/RELAY_B_KEY
router_settings:
optional_pre_call_checks: ["prompt_caching"]
不配 Redis 单 Pod 也能跑,本地内存里建映射。重启后映射丢失,所以只适合本地测。
7.2 生产推荐(多 Pod)¶
model_list:
- model_name: claude-sonnet-4.5
litellm_params:
model: openai/claude-sonnet-4-5
api_base: https://relay-a.example.com/v1
api_key: os.environ/RELAY_A_KEY
model_info:
id: sonnet45-relay-a # 显式 ID,便于 grep Redis
- model_name: claude-sonnet-4.5
litellm_params:
model: openai/claude-sonnet-4-5
api_base: https://relay-b.example.com/v1
api_key: os.environ/RELAY_B_KEY
model_info:
id: sonnet45-relay-b
router_settings:
routing_strategy: latency-based-routing
redis_host: os.environ/REDIS_HOST
redis_port: os.environ/REDIS_PORT
redis_password: os.environ/REDIS_PASSWORD
enable_pre_call_checks: true
optional_pre_call_checks: ["prompt_caching"]
general_settings:
master_key: sk-...
store_model_in_db: false
user_api_key_cache_ttl: 300 # 可选优化
7.3 完整版(跟 deployment_affinity 共存)¶
router_settings:
routing_strategy: latency-based-routing
redis_host: os.environ/REDIS_HOST
redis_port: os.environ/REDIS_PORT
redis_password: os.environ/REDIS_PASSWORD
enable_pre_call_checks: true
optional_pre_call_checks:
- prompt_caching # 优先级 1:有 cache_control 的请求绑定到同一 deployment
- deployment_affinity # 优先级 2:同一 api_key 的用户也尽量绑定
deployment_affinity_ttl_seconds: 600 # 注意:这只控制 deployment_affinity, 不影响 prompt_caching
执行顺序:先跑 prompt_caching filter,未命中再跑 deployment_affinity,都未命中走 latency-based-routing。
8. 速查:每个字段从哪里来 / 影响哪里¶
| 字段 | YAML 段 | 默认值 | 代码定义点 | 实际影响 |
|---|---|---|---|---|
optional_pre_call_checks |
router_settings |
[] |
types/router.py:807 | 注册 callback |
enable_pre_call_checks |
router_settings |
False |
router.py:262 | 跟 prompt_caching 弱相关 |
redis_host 等 |
router_settings |
None | router 标准字段 | 决定多 Pod 共享 |
routing_strategy |
router_settings |
simple-shuffle |
types/router.py:63 | 未命中时的兜底 |
MINIMUM_PROMPT_CACHE_TOKEN_COUNT |
环境变量 | 1024 |
constants.py:252 | token 阈值 |
model_list[].model_name |
model_list |
- | router 标准字段 | 多 deployment 同名是黏性前提 |
model_list[].model_info.id |
model_list[].model_info |
自动 hash | router.py:6142 | 黏性 value 存的就是这个 id |
| TTL=300s | - | 硬编码 | prompt_caching_cache.py:190 | 无配置项 |
cache_control: {type: ephemeral} |
request body | - | 03-provider-matrix.md | 客户端侧标记,约束 4 |
9. UI 不可见的事实¶
LiteLLM Dashboard 的 Settings → Router Settings 页面不渲染 optional_pre_call_checks 字段,无论你怎么搜都找不到。证据:
- UI 表单按静态列表 ROUTER_SETTINGS_FIELDS 渲染(RouterSettingsForm.tsx),这个 list 里有 22 个字段(
routing_strategy/num_retries/fallbacks/enable_pre_call_checks/ ...),没有optional_pre_call_checks - 整个 ui/ 目录 grep
optional_pre_call_checks0 命中 /router/settingsGET API 返回的current_values也只覆盖 ROUTER_SETTINGS_FIELDS 白名单字段 + 已挂到 router 实例上的属性(router_settings_endpoints.py:106-120)
这意味着:
- 你在 UI 里改不到这个字段
- 通过
/router/settingsGET 验证optional_pre_call_checks是否生效可能不可靠(字段可能不在返回里,即使后端实际生效了) - 写入路径不受 UI 限制:
/config/updateAPI 接受任意 router_settings JSON(proxy_server.py:11302-11310),UI 白名单只影响读和渲染
10. DB 模式下的 Admin API 配置法¶
适用:
store_model_in_db: true、你不方便改镜像内 YAML 或没权限改 ConfigMap、或者就是想测一下生效再写进 YAML 时。
10.1 一行命令安全合并(最常用)¶
/config/update 对 router_settings 是整体替换(proxy_server.py:11304-11310 的 prisma upsert)—— 直接传 {"router_settings": {"optional_pre_call_checks": ["prompt_caching"]}} 会抹掉所有其他已有字段(routing_strategy / num_retries / redis_password ...)!
正确做法:先 GET 当前值,jq merge 后再 POST:
PROXY=https://your-proxy.example.com
ADMIN_KEY=sk-... # PROXY_ADMIN 级别 key
curl -s "$PROXY/router/settings" \
-H "Authorization: Bearer $ADMIN_KEY" \
| jq '{router_settings: (.current_values + {optional_pre_call_checks: ["prompt_caching"]})}' \
| curl -X POST "$PROXY/config/update" \
-H "Authorization: Bearer $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d @-
强烈建议先 dry run:
# 把 jq 输出打印出来人眼检查,确认所有原有字段都在,且末尾多了 optional_pre_call_checks
curl -s "$PROXY/router/settings" \
-H "Authorization: Bearer $ADMIN_KEY" \
| jq '{router_settings: (.current_values + {optional_pre_call_checks: ["prompt_caching"]})}'
10.2 SQL 等价语义(参考用,不一定能执行)¶
如果你直接读懂 DB 里发生了什么:
UPDATE "LiteLLM_Config"
SET param_value = param_value || '{"optional_pre_call_checks":["prompt_caching"]}'::jsonb
WHERE param_name = 'router_settings';
注意这是 jsonb 合并(|| 运算符 = 浅合并,DB 行已有的同名字段会被覆盖,独有字段保留),跟启动时 Python _update_dictionary 的语义近似但不完全等价(不递归合并嵌套 dict)。
10.3 改完一定要让 proxy 重新加载¶
router_settings 启动时一次性读入(proxy_server.py:3886),改完 DB 不会自动生效:
| 重新加载方式 | 适用情况 |
|---|---|
| 重启 proxy 进程 | 任何情况都生效 |
kubectl rollout restart / Docker docker restart |
K8s / Docker 部署 |
/config/update API 内部是否 reload |
代码里有 update_config_state(self.config) 调用,但是否影响已注册的 callback 列表存疑;保守起见手工重启 |
| Hot-reload endpoint | LiteLLM 没暴露 |
HTTP-only 验证 reload 是否生效:
# 1. 看 callback 列表(LiteLLM 提供的内省端点)
curl -s "$PROXY/get/config/callbacks" \
-H "Authorization: Bearer $ADMIN_KEY" | jq
# 期望: callbacks 列表里出现 "PromptCachingDeploymentCheck"
# 2. 实际跑一个 Claude Code 请求(>1024 tokens + cache_control)
# 然后看 spend logs / S3 logs / proxy access log
# 看同一前缀的连续请求是否落到同一 deployment_id
# 3. 用 GET /router/settings 自检字段(参考 §9 警告)
curl -s "$PROXY/router/settings" \
-H "Authorization: Bearer $ADMIN_KEY" \
| jq '.current_values.optional_pre_call_checks'
# 输出 ["prompt_caching"] 即生效;输出 null 不一定表示未生效(可能是 UI 白名单不返回)
10.4 风险与建议¶
| 风险 | 缓解 |
|---|---|
| 写错把 router_settings 整段 nuke 了 | 必须 dry run + 看 jq 输出再 POST |
| DB 字段会长期覆盖 YAML | 一旦写进 DB,YAML 同名字段就被屏蔽;要清空只能 SQL DELETE 或显式 POST 不含该字段的完整 JSON |
| 跨集群部署一致性 | DB 配置无法 git 追溯;推荐最终方案落回 YAML,DB 改法只作为临时验证手段 |
推荐流程:
- 先用 §10.1 改 DB → 重启 proxy → 跑验证 → 确认生效
- 把字段写进仓库 proxy_server_config.yaml
- 走正常发版流程 → 新镜像生效后 SQL 删除 DB 里的
optional_pre_call_checks字段(如果有的话),让 YAML 接管