跳转至

02 — PostgreSQL 价格数据生命周期

概述

通过 UI 或 API 创建的模型(store_model_in_db=True)将 litellm_params 加密后存入 LiteLLM_ProxyModelTable。LiteLLM 在启动和 30 秒轮询时从 DB 读取并注册到 litellm.model_cost[UUID],供计费使用。


DB 存储结构

LiteLLM_ProxyModelTable(Prisma schema:proxy/schema.prisma

字段 说明
model_name 对外暴露的公开模型名,如 claude-opus-4-6
litellm_params 加密的 JSON 对象,含 model、api_key、api_base、价格字段等
model_info 附加 metadata,如 id、created_by 等

litellm_params 的内容示例(解密后):

{
  "model": "Vendor2/Claude-4.6-Opus",
  "api_key": "sk-xxx",
  "api_base": "https://api.gpugeek.com",
  "custom_llm_provider": "anthropic",
  "input_cost_per_token": 0.000005,
  "output_cost_per_token": 0.000025,
  "cache_creation_input_token_cost": 0.00000625,
  "cache_read_input_token_cost": 5e-7,
  "cache_creation_input_token_cost_above_1hr": 0.00001,
  "cache_creation_input_token_cost_above_200k_tokens": 0.0000125,
  "cache_read_input_token_cost_above_200k_tokens": 0.000001,
  "input_cost_per_token_above_200k_tokens": 0.00001,
  "output_cost_per_token_above_200k_tokens": 0.0000375
}

加密model_management_endpoints.py:288-295,使用 encrypt_value_helper() 对每个字段值加密(配置了加密密钥时),字符串值变为 base64-like 密文。


启动时加载流程

sequenceDiagram
    participant Startup as proxy_startup_event\nproxy_server.py:742
    participant BG as initialize_scheduled_background_jobs\nproxy_server.py:5574
    participant PC as ProxyConfig.add_deployment\nproxy_server.py:4313
    participant DB as PostgreSQL
    participant Router as llm_router
    participant MC as litellm.model_cost

    Startup->>BG: 启动后台任务
    BG->>PC: 立即调用 add_deployment\nproxy_server.py:5714
    PC->>DB: prisma.litellm_proxymodeltable.find_many()
    DB-->>PC: 返回加密的 model 列表
    PC->>PC: decrypt_value_helper() 解密每个字段
    PC->>PC: LiteLLM_Params(**decrypted_params)

    alt llm_router 不存在(首次启动)
        PC->>Router: 创建 Router(model_list=models)
        Router->>Router: set_model_list()
        Router->>Router: _create_deployment() [router.py:6173]
        Router->>MC: register_model({UUID: model_info_with_cache_prices})
        Router->>MC: register_model({provider/model: model_info_without_custom})
    else llm_router 已存在(重新加载)
        PC->>Router: ProxyConfig._add_deployment(db_models)\nproxy_server.py:3536
        Router->>Router: upsert_deployment(Deployment)\nrouter.py:6744
        Router->>Router: _create_deployment() [router.py:6173]
        Router->>MC: register_model({UUID: model_info_with_cache_prices})
    end

关键调用链(完整)

proxy_startup_event()                      [proxy_server.py:742]
  └─ initialize_scheduled_background_jobs() [proxy_server.py:5574]
       └─ proxy_config.add_deployment()      [proxy_server.py:5714 立即调用]
            └─ ProxyConfig.add_deployment()  [proxy_server.py:4313]
                 ├─ _get_models_from_db()    [proxy_server.py:4300]
                 │    └─ prisma.find_many()
                 └─ _update_llm_router()     [proxy_server.py:3616]
                      ├─ _delete_deployment() 删除已从DB移除的模型
                      └─ ProxyConfig._add_deployment(db_models) [proxy_server.py:3536]
                           ├─ decrypt_value_helper() 解密
                           ├─ LiteLLM_Params(**_litellm_params)
                           └─ llm_router.upsert_deployment(Deployment)
                                └─ Router.upsert_deployment()  [router.py:6744]
                                     └─ Router.add_deployment() [router.py:6652]
                                          └─ Router._add_deployment() [router.py:6465]
                                          └─ Router._create_deployment() [router.py:6173]
                                               ├─ 合并 litellm_params 价格字段到 _model_info
                                               └─ litellm.register_model({UUID: _model_info})

30 秒轮询更新

注册位置proxy_server.py:5699-5711

if store_model_in_db is True:
    scheduler.add_job(
        proxy_config.add_deployment,
        "interval",
        seconds=30,           # 每 30 秒轮询一次
        args=[prisma_client, proxy_logging_obj],
        id="add_deployment_job",
    )

轮询走的路径与启动时相同,均从 ProxyConfig.add_deployment() 开始。

轮询是否会更新 litellm.model_cost[UUID]

会,但需要 litellm_params 发生变化

upsert_deployment() 的对比逻辑(router.py:6760-6764):

if deployment.litellm_params == _deployment_on_router.litellm_params:
    return None  # 参数未变化,跳过,不更新 model_cost
# 参数有变化:删除旧 deployment,重新 add_deployment → _create_deployment → register_model
场景 结果
UI 保存后(clear_cache 已立即触发注册) 轮询时参数相同,跳过
DB 被外部工具直接修改 轮询检测到差异,重新注册 UUID
字段未变化 永远跳过,model_cost 不变

CustomPricingLiteLLMParams 字段完整列表

定义litellm/types/utils.py:2851-2904

这些字段构成 _CUSTOM_PRICING_KEYS frozenset,任意一个字段非 None 即触发 custom_pricing=True(UUID 计费路径)。

字段名 说明
input_cost_per_token 基础输入 token 价格
output_cost_per_token 基础输出 token 价格
cache_creation_input_token_cost 缓存写入(5min,标准)
cache_creation_input_token_cost_above_1hr 缓存写入(1hr)
cache_creation_input_token_cost_above_200k_tokens 200k+ token 缓存写入阈值
cache_creation_input_audio_token_cost 音频 token 缓存写入
cache_read_input_token_cost 缓存读取
cache_read_input_token_cost_flex Flex tier 缓存读取
cache_read_input_token_cost_priority Priority tier 缓存读取
cache_read_input_token_cost_above_200k_tokens 200k+ token 缓存读取
cache_read_input_audio_token_cost 音频 token 缓存读取
input_cost_per_token_above_200k_tokens 200k+ token 输入阈值
output_cost_per_token_above_200k_tokens 200k+ token 输出阈值
input_cost_per_second 按秒计费输入
output_cost_per_second 按秒计费输出
input_cost_per_pixel 图像输入(像素)
output_cost_per_pixel 图像输出(像素)
input_cost_per_token_flex Flex tier 输入
input_cost_per_token_priority Priority tier 输入
output_cost_per_token_flex Flex tier 输出
output_cost_per_token_priority Priority tier 输出
input_cost_per_character 字符计费输入
output_cost_per_character 字符计费输出
input_cost_per_audio_token 音频 token 输入
output_cost_per_audio_token 音频 token 输出
input_cost_per_token_batches Batch 输入
output_cost_per_token_batches Batch 输出
output_cost_per_reasoning_token 推理 token
input_cost_per_query 按查询计费
input_cost_per_image 图像数量输入
output_cost_per_image 图像数量输出
input_cost_per_audio_per_second 音频秒数输入
input_cost_per_video_per_second 视频秒数输入
search_context_cost_per_query 搜索上下文(dict)
citation_cost_per_token 引用 token
tiered_pricing 分层定价(list)
…(共 49 个字段)

重要陷阱

陷阱 1:清空 UI 字段不会从 DB 删除

用户在 UI 编辑时将某个价格字段清空(置为 null),前端发送 PATCH 请求时该字段被 Pydantic exclude_none=True 序列化时排除,后端合并逻辑:

# model_management_endpoints.py(约 180-200 行)
merged = db_model.litellm_params.model_dump(exclude_none=True)  # 旧值
merged.update(new_encrypted_params)                               # 新值覆盖
# 若新值中没有该字段 → 旧值保留

结果:清空 ≠ 删除,DB 中旧值依然保留,use_custom_pricing_for_model() 仍返回 True。

解决方法:显式将字段值设为 0(而非留空),或通过直接操作 DB 删除字段。

陷阱 2:store_model_in_db=False 时无轮询

若未开启 store_model_in_dbadd_deployment 定时任务不会注册,DB 模型无法被加载。