03 — UI 价格展示与修改¶
概述¶
LiteLLM 管理 UI 展示的价格信息来自两个来源的合并:DB 中存储的自定义价格(优先)和 JSON 文件中的标准价格(补充)。理解这两个来源的优先级对于正确配置价格至关重要。
/model/info 端点数据来源¶
端点:GET /model/info 和 GET /v1/model/info
定义:litellm/proxy/proxy_server.py:9914-10055(model_info_v1 函数)
数据组装流程¶
flowchart TD
A[GET /model/info] --> B[llm_router.get_model_list\n从 router.model_list 内存取]
B --> C[_get_proxy_model_info model\nproxy_server.py:9871]
C --> D[model_info = model.get 'model_info' \n来自 DB/config 原始值]
C --> E[litellm_model_info = get_litellm_model_info model \n调用 litellm.get_model_info\n即查 litellm.model_cost]
E --> F{litellm_model_info 为空?}
F -- 是 --> G[fallback: 用 litellm_params.model 再查]
G --> H{仍为空?}
H -- 是 --> I[fallback: 取 model 最后一个 / 段查]
H -- 否 --> J
F -- 否 --> J[for k,v in litellm_model_info:\n if k not in model_info:\n model_info k = v]
I --> J
J --> K[model 'model_info' = model_info\n注入到响应]
K --> L[model 'litellm_params' 保持原样\n不注入 JSON 价格]
关键代码(proxy_server.py:9902-9905):
for k, v in litellm_model_info.items():
if k not in model_info:
model_info[k] = v # JSON 价格只补充缺失字段,不覆盖 DB 中已有的值
model["model_info"] = model_info
# model["litellm_params"] 不做任何修改
UI 各字段来源对照表¶
| UI 显示区域 | 字段来源 | 覆盖规则 |
|---|---|---|
| Model Info → Input/Output 价格 | model_info.input_cost_per_token |
DB 值优先,JSON 补充缺失字段 |
| Model Info → cache 价格 | model_info.cache_creation_input_token_cost |
同上 |
| LiteLLM Params → input_cost_per_token | litellm_params.input_cost_per_token |
纯 DB 原始值,无 JSON 注入 |
| LiteLLM Params → cache_creation_input_token_cost | litellm_params.cache_creation_input_token_cost |
纯 DB 原始值 |
结论: - Model Info 区域:可能混合了 DB 值和 JSON 值(DB 优先) - LiteLLM Params 区域:完全来自 DB,是计费的实际数据源
前端字段优先级逻辑¶
编辑表单初始值设置(model_info_view.tsx:595-600):
// 价格字段展示:litellm_params 中有值则用,否则 fallback 到 model_info
input_cost: localModelData.litellm_params.input_cost_per_token
? localModelData.litellm_params.input_cost_per_token * 1_000_000
: localModelData.model_info?.input_cost_per_token * 1_000_000 || null,
output_cost: localModelData.litellm_params?.output_cost_per_token
? localModelData.litellm_params.output_cost_per_token * 1_000_000
: localModelData.model_info?.output_cost_per_token * 1_000_000 || null,
前端显示单位转换:UI 以 "\(/1M tokens" 显示,内部存储为 "\)/token"(相差 1,000,000 倍)。
编辑保存的完整流程¶
API 端点¶
前端调用(networking.tsx:3849-3882):
const url = `${proxyBaseUrl}/model/${modelId}/update`;
const response = await fetch(url, {
method: "PATCH",
body: JSON.stringify(formValues),
});
后端端点:PATCH /model/{model_id}/update
定义:litellm/proxy/management_endpoints/model_management_endpoints.py:145-272(update_db_model)
保存流程图¶
sequenceDiagram
participant UI as 前端 UI
participant API as PATCH /model/id/update
participant DB as PostgreSQL
participant Router as llm_router
participant MC as litellm.model_cost
UI->>UI: input_cost / 1_000_000 → input_cost_per_token
UI->>API: PATCH { litellm_params: { input_cost_per_token, cache_creation_... } }
API->>DB: 读取旧 litellm_params(已加密)
API->>API: 解密旧值 → merged = old_params
API->>API: 加密新值
API->>API: merged.update(new_encrypted_params)
API->>DB: 写回 merged litellm_params
API->>API: clear_cache()
Note over API: 立即触发重载,不等30s
API->>Router: 删除所有 db_model=True 的 deployments
API->>Router: proxy_config.add_deployment()
Router->>Router: _create_deployment()
Router->>MC: register_model UUID → model_info 含新价格
API-->>UI: 200 OK
clear_cache() 的行为¶
定义:model_management_endpoints.py:1315-1368
async def clear_cache():
# 1. 删除 router 中所有 db_model=True 的 deployment
for m in db_model_list:
llm_router.delete_deployment(model_id=m.model_info.id)
# 2. 立即重载
await proxy_config.add_deployment(prisma_client, proxy_logging_obj)
效果:UI 保存后立即生效,litellm.model_cost[UUID] 立刻更新,无需等待 30 秒轮询。
前端表单字段清单与 cache 字段的输入通道¶
原生表单字段¶
ui/litellm-dashboard/src/components/model_info_view.tsx 中 Form.Item name= 的全部字段(编辑模型对话框):
| Form.Item name | 行号 | 对应 litellm_params 字段 |
|---|---|---|
model_name |
621 | — |
litellm_model_name |
632 | model |
input_cost |
643 | input_cost_per_token(×1/1_000_000) |
output_cost |
660 | output_cost_per_token(×1/1_000_000) |
api_base |
677 | api_base |
custom_llm_provider |
690 | custom_llm_provider |
organization |
703 | organization |
tpm / rpm |
716 / 729 | tpm / rpm |
max_retries / timeout / stream_timeout |
742 / 755 / 768 | 同名 |
model_access_group |
781 | → model_info.access_groups |
guardrails |
840 | guardrails |
tags |
889 | tags |
health_check_model |
938 | → model_info.health_check_model |
model_info |
1004 | 整个 model_info JSON 文本框 |
litellm_extra_params |
1034 | 整个 litellm_params 补充 JSON 文本框 |
价格字段:仅 input_cost / output_cost 两个原生输入框,对应 input_cost_per_token / output_cost_per_token。
cache 价格字段的输入路径¶
所有 cache 相关字段(cache_creation_input_token_cost、cache_read_input_token_cost、cache_creation_input_token_cost_above_1hr 等)通过 LiteLLM Params JSON 文本框(litellm_extra_params,行 1034)以 JSON 形式输入:
{
"cache_creation_input_token_cost": 0.00000625,
"cache_read_input_token_cost": 0.0000005,
"cache_creation_input_token_cost_above_1hr": 0.00001
}
前端合并逻辑¶
handleModelUpdate(model_info_view.tsx:229-244):
let updatedLitellmParams = {
...values.litellm_params,
...parsedExtraParams, // ← JSON 文本框解析结果
model: values.litellm_model_name,
api_base: values.api_base,
custom_llm_provider: values.custom_llm_provider,
organization: values.organization,
tpm: values.tpm,
rpm: values.rpm,
max_retries: values.max_retries,
timeout: values.timeout,
stream_timeout: values.stream_timeout,
input_cost_per_token: values.input_cost / 1_000_000,
output_cost_per_token: values.output_cost / 1_000_000,
tags: values.tags,
};
parsedExtraParams 中的 cache 字段位于展开链第二位,后续显式 key 集合中不含任何 cache_* 字段,所以 JSON 文本框输入的 cache 字段会保留到 PATCH 请求体中。
后端字段类型保障¶
PATCH 端点通过 updateLiteLLMParams Pydantic 模型接收请求(model_management_endpoints.py:105-112):
encrypted_params = {
k: encrypt_value_helper(v)
for k, v in updated_patch.litellm_params.model_dump(
exclude_none=True
).items()
}
merged_deployment_dict["litellm_params"].update(encrypted_params)
类型链:
updateLiteLLMParams (types/router.py:371)
→ GenericLiteLLMParams (types/router.py:165)
├─ CredentialLiteLLMParams
└─ CustomPricingLiteLLMParams ← 声明全部 49 个价格字段(含 cache 字段)
model_config = ConfigDict(extra="allow", ...) (types/router.py:194)
两项关键保障:
1. cache 字段是 CustomPricingLiteLLMParams 声明字段,非 None 值经 model_dump(exclude_none=True) 后会正常序列化
2. extra="allow" 确保未声明字段(如自定义扩展)也会保留
因此 JSON 文本框中输入的 cache 字段能完整经过 Pydantic 反序列化与 model_dump 写入 DB。
encrypt_value_helper 对非字符串的处理¶
加密工具(encrypt_decrypt_utils.py:19-36):
def encrypt_value_helper(value: str, new_encryption_key: Optional[str] = None):
signing_key = new_encryption_key or _get_salt_key()
if isinstance(value, str):
encrypted_value = encrypt_value(value=value, signing_key=signing_key)
return base64.urlsafe_b64encode(encrypted_value).decode("utf-8")
return value # float / int 原样返回
价格字段(float)不会被加密,直接以原值写入 DB 的 litellm_params JSON。因此 SQL 直查结果里 cache_creation_input_token_cost: 0.00000625 是可读的明文数字。
清空字段的陷阱¶
问题¶
用户在 UI 将某个价格字段清空并保存:
前端发送 PATCH:
{ "litellm_params": { "input_cost_per_token": null } }
Pydantic exclude_none=True 序列化后:
{ "litellm_params": {} } ← 字段消失
后端 merge 逻辑:
merged = old_params ← 包含旧 input_cost_per_token
merged.update({}) ← 空 dict,不覆盖任何字段
DB 结果:旧值保留!
后果¶
- DB 中
input_cost_per_token仍然存在 use_custom_pricing_for_model()仍返回 Truelitellm.model_cost[UUID]的 cache 价格字段(如果之前就缺失)仍然缺失
解决方法¶
将字段值显式设为 0(而非留空),或直接通过 DB 操作删除 JSON 中的字段:
UPDATE "LiteLLM_ProxyModelTable"
SET litellm_params = litellm_params - 'input_cost_per_token'
WHERE model_name = 'claude-opus-4-6';
完整字段写入验证¶
可通过以下 SQL 验证 DB 中实际存储的字段(加密版本):
SELECT model_name, litellm_params
FROM "LiteLLM_ProxyModelTable"
WHERE model_name = 'claude-opus-4-6';
若 litellm_params 中有价格字段(无论是否加密),即说明走 UUID 计费路径。字段值是 base64-like 密文是正常的(由 encrypt_value_helper 加密)。