接入一个大模型并不难:保存 API Key,然后调用供应商 SDK 即可。困难的是同时接入几十家供应商、几百个模型,还要让工作流、Agent、RAG 和语音功能使用统一接口。
Dify 在业务代码与模型插件之间建立了一层模型运行时。它既像适配器,也承担模型发现、凭证选择、能力判断、错误归一和用量统计等网关职责。
本文把这种架构统称为“模型网关”,重点讨论它如何消除多模型接入中的复杂度。
一、多模型接入为什么会失控
不同供应商看似都提供“模型调用”,实际差异很大:
- 消息、工具、图片和文件的请求格式不同;
- 流式响应的事件结构不同;
- 参数名称和有效范围不同;
- Token 统计、价格和限额不同;
- 错误码、重试语义和限流方式不同;
- 有的支持 Function Calling,有的只支持文本;
- Embedding、Rerank、语音模型又是完全不同的接口。
如果每个业务模块直接调用供应商 SDK,会形成这样的依赖:
flowchart LR
Workflow["工作流"] --> OpenAI["OpenAI SDK"]
Workflow --> Bedrock["Bedrock SDK"]
Agent["Agent"] --> OpenAI
Agent --> Local["本地模型 API"]
RAG["RAG"] --> EmbedA["Embedding A"]
RAG --> EmbedB["Embedding B"]
同样的鉴权、重试和统计逻辑会散落在各处。新增模型时,所有调用方都可能需要修改。
模型网关把关系改为:
1 | 业务模块 → 统一模型接口 → Provider 适配器 → 供应商 API |
业务只表达“我要调用一个 LLM”,不负责理解某家供应商的细节。
二、Dify 的模型抽象
2.1 Provider、Model 与 Model Type
Dify 将模型接入拆成三层概念:
| 概念 | 含义 | 示例 |
|---|---|---|
| Provider | 模型供应商或兼容服务 | OpenAI、Bedrock、本地兼容接口 |
| Model | 具体模型 | 某个聊天模型或向量模型 |
| Model Type | 标准能力接口 | LLM、Embedding、Rerank、STT、TTS |
Provider 描述认证方式和模型目录;Model 描述上下文长度、参数、价格和功能;Model Type 规定调用方法和返回结构。
这样可以避免把所有能力塞进一个巨大接口。例如,Embedding 只需要文本向量化,不应该被迫实现聊天消息和工具调用。
2.2 模型能力必须显式声明
“这是一个 LLM”仍不足以判断能否用于某个场景,还需要声明:
- 是否支持工具调用;
- 是否支持图片、文档等多模态输入;
- 是否支持结构化输出;
- 是否支持流式响应;
- 上下文窗口和最大输出 Token;
- 可调参数及范围;
- 输入、输出计费规则。
模型声明示意:
1 | model: example-chat |
界面可以据此生成表单,运行时也能在请求发出前拒绝不支持的组合。
2.3 统一调用链
Dify 的典型模型调用链可以概括为:
flowchart LR
Biz["LLM节点 / Agent / RAG"] --> Manager["ModelManager"]
Manager --> ProviderManager["ProviderManager
租户配置"]
ProviderManager --> Config["ProviderConfiguration
凭证与模型设置"]
Config --> Runtime["Model Runtime"]
Runtime --> Plugin["Model Plugin"]
Plugin --> Vendor["供应商 API"]
主要职责如下:
ModelManager根据租户、Provider、模型名和类型获得模型实例;ProviderManager组装该租户可见的供应商配置;ProviderConfiguration选择凭证、启用状态和负载均衡配置;- Model Runtime 通过插件协议发起调用;
- 模型插件转换供应商请求和响应;
- 统一结果返回业务层。
业务代码最终只面对 invoke_llm()、Embedding 或 Rerank 等稳定接口。
三、配置与凭证管理
3.1 为什么配置必须按租户隔离
在多租户平台中,同一个 OpenAI 插件可以被多个工作空间使用,但它们的 API Key、Base URL 和配额不能共享。
因此应区分:
- Provider Schema:插件提供的公共定义;
- Provider Credential:租户配置的供应商级凭证;
- Model Credential:某个自定义模型的独立凭证;
- Model Setting:启用状态、负载均衡等运行配置;
- Default Model:租户为某种 Model Type 选择的默认模型。
任何查询都必须带上 tenant_id。缓存键也必须包含租户,否则数据库虽然隔离,缓存仍可能串号。
3.2 供应商级与模型级配置
常见供应商有两种模式:
- 预定义模型:配置一次 Provider API Key,即可使用其模型目录;
- 可自定义模型:用户填写模型名、Base URL 和模型级凭证。
OpenAI API Compatible 插件属于第二类典型:同一套协议可以连接本地推理服务、企业网关或第三方兼容平台。
这种灵活性也扩大了风险:
- Base URL 必须经过 SSRF 防护;
- API Key 只能发往预期目标;
- 自定义模型名和能力声明需要校验;
- 测试凭证时不能把密钥写入日志;
- 导出应用 DSL 时不能包含明文凭证。
3.3 凭证生命周期
一份模型凭证至少经历:
1 | 录入 → 校验 → 加密存储 → 调用时解密 → 轮换 → 禁用/删除 |
推荐规则:
- 使用专用密钥加密数据库中的凭证;
- 前端再次编辑时返回占位符,而不是明文;
- 修改凭证后清理相关缓存;
- 记录谁在何时修改,但不记录密钥内容;
- 支持多凭证并行,以便无停机轮换;
- 删除凭证前检查是否仍被应用或负载均衡配置引用。
四、统一输入、输出和错误
4.1 消息适配
业务层使用统一消息对象:System、User、Assistant、Tool,以及文本、图片、文件等内容块。模型插件负责转换为供应商格式。
例如,同样的工具定义可能被转成 OpenAI tools、其他供应商的函数声明,或注入 Prompt 的文本协议。适配逻辑应该留在插件中,不能泄漏到工作流节点。
4.2 流式与非流式结果
统一接口通常需要同时支持:
1 | # 非流式:一次得到完整结果 |
无论供应商返回 SSE、分块 JSON 还是其他协议,业务层都应得到统一的文本增量、工具调用增量、结束原因和用量信息。
4.3 错误归一
供应商错误应映射成有限的业务类型:
| 统一错误 | 业务含义 | 是否适合重试 |
|---|---|---|
| AuthorizationError | 凭证错误或无权限 | 否 |
| RateLimitError | 请求频率或配额受限 | 延迟后可重试 |
| QuotaExceededError | 余额或总配额耗尽 | 通常否 |
| BadRequestError | 参数、Prompt 或模型不支持 | 否 |
| ConnectionError | 网络连接失败 | 可有限重试 |
| ServerUnavailableError | 供应商临时不可用 | 可退避重试 |
如果业务层依赖供应商原始错误码,就无法实现统一告警、重试和降级。
五、多凭证负载均衡
5.1 为什么一个模型需要多份凭证
生产系统可能为同一模型配置多个账号或 Endpoint:
- 分散单账号的速率限制;
- 区分不同地域和网络线路;
- 轮换密钥时保持服务不中断;
- 某个 Endpoint 故障时切换;
- 控制不同业务的成本中心。
Dify 的 Provider 配置中包含模型负载均衡设置,运行时可以从候选配置中选择可用凭证。
5.2 负载均衡不只是轮询
一个可用的模型负载均衡器还需要状态:
1 | 选择候选 → 调用 → 记录成功/失败 → 临时熔断 → 探测恢复 |
设计时应考虑:
- 认证失败应立即摘除对应凭证;
- 限流只应短暂冷却,不一定永久禁用;
- 超时和 5xx 使用指数退避;
- 重试前确认请求是否具有幂等性;
- 流式响应已经输出后,不能静默切换模型重放;
- 记录最终使用的 Provider、模型和凭证标识,但不记录密钥。
负载均衡解决可用性,但跨模型自动降级可能改变回答质量、上下文长度和价格,必须由业务显式授权。
六、工程实践与常见误区
6.1 生产检查清单
- 工作流只保存 Provider 和模型标识,不保存明文密钥;
- 每次模型查询和缓存访问都携带租户边界;
- 调用前校验模型类型和能力;
- 输入、输出和工具调用使用统一数据结构;
- 供应商异常映射为稳定错误类型;
- 设置连接、读取、总时长和最大重试次数;
- 记录 Token、费用、首 Token 延迟和总延迟;
- 自定义 Base URL 经过 SSRF 与出口网络策略;
- 凭证支持轮换、禁用和引用检查;
- 负载均衡节点具有熔断和恢复机制。
6.2 常见误区
| 误区 | 真实情况 |
|---|---|
| “统一成 OpenAI 格式就够了” | 不同模型能力、错误和计费语义仍然不同 |
| “模型名相同就是同一个模型” | Provider、版本、地域和能力配置也属于模型身份 |
| “API Key 放环境变量最安全” | 多租户动态凭证需要独立加密存储和访问控制 |
| “失败就换下一个模型” | 流式响应、非幂等工具和质量差异使切换并不透明 |
| “模型网关只负责转发” | 它还承担能力发现、凭证、用量、错误和治理 |
七、总结
Dify 的模型体系说明,多模型平台真正需要的不是更多 if provider == ...,而是一组稳定边界:
- Provider 插件封装供应商差异;
- Model Type 定义不同能力的标准接口;
- 模型 Schema 显式声明参数、功能和价格;
- ProviderManager 按租户组装配置;
- ModelManager 为业务提供统一模型实例;
- 凭证、缓存和默认模型都遵守租户隔离;
- 调用结果、流式 Chunk、用量和异常统一表达;
- 多凭证负载均衡配合熔断、轮换和审计。
模型网关的价值不是隐藏供应商名称,而是让模型变化停留在适配层,让工作流、Agent 和 RAG 可以长期依赖稳定协议。