跳转至

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):

  1. CONFIG_FILE_PATH 环境变量 —— 最高优先级,如果设了 proxy 只读这个
  2. WORKER_CONFIG 环境变量 —— 次优先级
  3. --config 命令行参数 —— Dockerfile CMD 里默认是 /app/proxy_server_config.yaml(对应仓库根目录 proxy_server_config.yamlDockerfile: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_settingsproxy_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 ★ 必须

router_settings:
  optional_pre_call_checks: ["prompt_caching"]
  • 类型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)。

可以叠加多个:

router_settings:
  optional_pre_call_checks: ["prompt_caching", "deployment_affinity"]

按列表顺序串行执行,前者命中后者就拿到单元素列表 no-op。

1.2 enable_pre_call_checks 可选(推荐显式开)

router_settings:
  enable_pre_call_checks: true
  • 类型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 不影响命中,只影响未命中时的兜底

router_settings:
  routing_strategy: latency-based-routing
  • 类型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

2. 环境变量

2.1 MINIMUM_PROMPT_CACHE_TOKEN_COUNT

export MINIMUM_PROMPT_CACHE_TOKEN_COUNT=1024   # 默认值
  • 代码位置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/... 引用)

export REDIS_HOST=redis.internal
export REDIS_PORT=6379
export REDIS_PASSWORD=...

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_settings:
  enable_caching_on_provider_specific_optional_params: true
  • 代码位置litellm/caching/caching.py:295litellm/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

litellm_settings:
  enable_redis_auth_cache: true            # ❌ 不存在
  • 来源:常见 AI 建议中出现
  • 代码搜索grep -r "enable_redis_auth_cache\|redis_auth_cache" litellm/ → 0 命中
  • 结论:删掉

5.2 prompt_cache_ttl_seconds / prompt_caching_ttl

router_settings:
  prompt_cache_ttl_seconds: 600            # ❌ 不存在
  prompt_caching_ttl: 600                  # ❌ 不存在
  • 代码搜索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

router_settings:
  enable_prompt_caching: true              # ❌ 不存在
  • 真正的启用方式是把 "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

general_settings:
  user_api_key_cache_ttl: 300

代理收到请求时会先查 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_checks 0 命中
  • /router/settings GET API 返回的 current_values 也只覆盖 ROUTER_SETTINGS_FIELDS 白名单字段 + 已挂到 router 实例上的属性(router_settings_endpoints.py:106-120

这意味着

  • 你在 UI 里改不到这个字段
  • 通过 /router/settings GET 验证 optional_pre_call_checks 是否生效可能不可靠(字段可能不在返回里,即使后端实际生效了)
  • 写入路径不受 UI 限制:/config/update API 接受任意 router_settings JSON(proxy_server.py:11302-11310),UI 白名单只影响读和渲染

10. DB 模式下的 Admin API 配置法

适用:store_model_in_db: true、你不方便改镜像内 YAML 或没权限改 ConfigMap、或者就是想测一下生效再写进 YAML 时。

10.1 一行命令安全合并(最常用)

/config/updaterouter_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 改法只作为临时验证手段

推荐流程

  1. 先用 §10.1 改 DB → 重启 proxy → 跑验证 → 确认生效
  2. 把字段写进仓库 proxy_server_config.yaml
  3. 走正常发版流程 → 新镜像生效后 SQL 删除 DB 里的 optional_pre_call_checks 字段(如果有的话),让 YAML 接管