接入一个大模型并不难:保存 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
model: example-chat
model_type: llm
features:
- tool-call
- vision
- structured-output
model_properties:
context_size: 128000
parameter_rules:
- name: temperature
min: 0
max: 2
- name: max_tokens
min: 1
max: 8192

界面可以据此生成表单,运行时也能在请求发出前拒绝不支持的组合。

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"]

主要职责如下:

  1. ModelManager 根据租户、Provider、模型名和类型获得模型实例;
  2. ProviderManager 组装该租户可见的供应商配置;
  3. ProviderConfiguration 选择凭证、启用状态和负载均衡配置;
  4. Model Runtime 通过插件协议发起调用;
  5. 模型插件转换供应商请求和响应;
  6. 统一结果返回业务层。

业务代码最终只面对 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 供应商级与模型级配置

常见供应商有两种模式:

  1. 预定义模型:配置一次 Provider API Key,即可使用其模型目录;
  2. 可自定义模型:用户填写模型名、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
2
3
4
5
6
# 非流式:一次得到完整结果
result = model.invoke(messages, stream=False)

# 流式:逐个消费标准化 Chunk
for chunk in model.invoke(messages, stream=True):
publish(chunk)

无论供应商返回 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 == ...,而是一组稳定边界:

  1. Provider 插件封装供应商差异;
  2. Model Type 定义不同能力的标准接口;
  3. 模型 Schema 显式声明参数、功能和价格;
  4. ProviderManager 按租户组装配置;
  5. ModelManager 为业务提供统一模型实例;
  6. 凭证、缓存和默认模型都遵守租户隔离;
  7. 调用结果、流式 Chunk、用量和异常统一表达;
  8. 多凭证负载均衡配合熔断、轮换和审计。

模型网关的价值不是隐藏供应商名称,而是让模型变化停留在适配层,让工作流、Agent 和 RAG 可以长期依赖稳定协议。