← Back to Lab
// Backend2026.07.133 min read
多模型 Key 管理与路由:统一 resolve 与 Fernet 加密
多 LLM Key 与多模型路由的统一 resolve + ModelFactory 设计,以及用 Fernet 加密密钥、掩码返回的密钥安全实践。
如何优雅管理多个 LLM Key 与多模型路由
适用场景:需要支持多 provider、用户自带 Key、项目级/用户级模型覆盖的 AI 应用。
一、背景
项目要支持多个 provider(Anthropic / OpenAI 兼容 / 自托管 MaaS),还要让用户自带 Key、项目级覆盖用户默认。早期散落 4 处的 ChatOpenAI(...) 构造极难维护、也难审计"到底用了哪个模型"。
二、统一为 resolve + ModelFactory
所有 LLM 调用点先 resolve(user_id, project_id, scenario) 拿到具体配置,再 ModelFactory.build() 构造:
# 优先级:项目精确 → 项目全局(*) → 用户精确 → 用户全局(*) → yaml 兜底
def resolve(db, user_id, project_id, scenario):
if (o := project_override精确命中(scenario)): return o, "project_override"
if (o := project_global.get("*")): return o, "project_global_override"
if (o := user_default精确命中(scenario)): return o, "user_default"
if (o := user_global.get("*")): return o, "user_global_default"
return yaml_fallback, "yaml_fallback"
class ModelFactory:
def build(self, cfg, temperature=None, max_tokens=None):
if cfg.provider == "anthropic":
return ChatAnthropic(model=cfg.model, ...)
return ChatOpenAI(base_url=cfg.base_url, model=cfg.model, ...)
source 字段让每一步命中来源可观测——出问题时一眼知道模型从哪来。
三、踩坑:非章节任务静默落兜底
最初只有"精确匹配",导致 suggestion / outline_validate 等非章节场景无法命中用户的全局默认,悄悄用了系统兜底模型。修复就是加两级 * 通配(见上方优先级链)。
结论:所有 LLM 调用点统一纳管、零硬编码,这本身比"能不能调通"更重要。问题往往出在 fallback 策略层,而不是漏纳管。
四、密钥安全
用户 Key 用 Fernet 对称加密存 DB,明文只在内存瞬时解密;GET 接口只返回 sk-****xxxx 掩码,绝不返回明文 / 密文:
from cryptography.fernet import Fernet
key = Fernet(MASTER_KEY).encrypt(api_key.encode()) # 存储密文
# 返回时
return "sk-****" + api_key[-4:]
MASTER_ENCRYPTION_KEY 只在服务端环境变量里,日志永不打印。
五、复盘
- 所有 LLM 调用点统一纳管,零硬编码模型名;
- fallback 策略比"能不能调通"更该被重视,记得给"全局默认"留通配;
- 密钥永远只进不出(掩码),加密存储、瞬时解密。
这套架构让"换模型 / 加 provider / 用户自带 Key"都变成配置级操作,而不是改代码。