Dify 插件开发实验(07):私有模型网关接入——如何让 Dify 用上私有模型网关?

Dify 实验系列 · 插件开发 07/12 | 实验编号:DIFY-106-07
基于 Dify 1.16.1 实测(2026-08)

1. 业务场景

先讲一个我们实际遇到的场景。

企业有内部模型网关:OpenAI 兼容协议,统一管理模型密钥与配额,所有对外的大模型调用都必须经过它——合规要审计、成本要管控、数据不出企业边界。客服工单 SaaS 的智能问答、工单摘要、Agent 对话,全都必须走公司网关,不能直连外部模型。但问题来了:Dify 控制台的模型列表里,根本没有「企业网关」这个供应商。

我们第一次接这类需求时,第一反应也是「Dify 不是支持自定义模型吗,配一下不就行了」。真正动手才发现——「模型」在 Dify 里不是填个 API 地址就能用,而是一个完整的供应商插件:要声明供应商、声明模型、实现调用逻辑,还要过凭证校验——控制台里能选到公司模型,背后是一整套 model provider 插件机制。

这不是个例。任何有私有模型/统一网关的企业都是这个模式:金融行业要审计模型调用、政企要求数据不出域、集团统一采购模型配额——「控制台里能选到公司的模型」,是模型接入的第一诉求。

2. 场景痛点

这个流程的痛点,在接入企业网关时体现得最直接:

  • 合规与审计过不去:应用直连外部模型,调用记录、数据流向都不受控,审计时根本说不清。
  • 密钥管理分散:每个应用各配各的模型密钥,换一次密钥要改遍所有应用,泄露了也无从追溯。
  • 控制台选不到公司模型:Dify 内置供应商列表里没有企业网关,团队只能绕开 Dify 单独接模型,能力割裂。
  • 配额与成本不可控:没有网关层统一管控,谁的调用都直接打到模型厂商,预算超了才知道。

本质上,企业要的不是「某个模型」,而是「受控的模型入口」——统一网关、统一密钥、统一审计,Dify 必须能消费这个入口。

3. 方案:为什么是模型插件

选模型插件,我们实际对比过:

  • 模型插件 = 供应商扩展:Dify 的 model provider 插件机制,让自定义供应商(含旗下模型)直接出现在控制台模型列表;
  • 凭证在控制台配置:网关 base_url + api_key 作为 provider 凭证在控制台录入,不写死在代码,密钥统一管理;
  • OpenAI 兼容协议复用生态:网关暴露 /v1/models + /v1/chat/completions,插件按兼容协议实现即可,无需私有协议适配。

这篇文章我们就用它把企业私有网关接进 Dify:开发一个 model provider 插件 dify106_07_gateway_provider,让控制台出现「企业网关」供应商及旗下模型 enterprise-gpt,并用本地 mock 网关(OpenAI 兼容)验证「凭证校验 → 模型列表 → LLM 调用(含流式)」全链路。

4. 整体架构

【验证应用】

开始(question)

LLM 节点(企业网关模型 enterprise-gpt)

输出

结束

模型插件(model provider)

控制台模型列表

声明:供应商(企业网关)+ 模型(LLM 类型,predefined-model 模式)

凭证:base_url + api_key(validate_credentials 校验有效性)

get_models:返回可用模型列表

调用:LLM 节点/Agent → 插件 _invoke → OpenAI 兼容协议 → 网关(mock)

链路很清晰:控制台声明供应商 → 配置凭证(保存即校验)→ 模型列表可见 → LLM 节点/Agent 调用走插件 → OpenAI 兼容协议打到网关。关键设计是凭证校验前置——配置页保存时调 /v1/models 验证,错的密钥当场报错,不拖到运行时。

5. 模块设计

5.1 manifest 声明模型插件

注意 plugins: models: 而非 tools:

plugins:
  models:
    - provider/gateway.yaml

5.2 供应商声明(provider/gateway.yaml)

supported_model_types + predefined-model + 凭证 schema:

provider: gateway
supported_model_types:
  - llm
configurate_methods:
  - predefined-model
provider_credential_schema:
  credential_form_schemas:
    - variable: base_url
      type: text-input
      required: true
      placeholder:
        zh_Hans: http://host.docker.internal:8005
    - variable: api_key
      type: secret-input
      required: true
models:
  llm:
    predefined:
      - "models/llm/llm.yaml"
extra:
  python:
    provider_source: provider/gateway.py
    model_sources:
      - "models/llm/llm.py"

5.3 模型声明(models/llm/llm.yaml)

features 决定 Agent 能否使用该模型(工单场景必须支持函数调用):

model: enterprise-gpt
model_type: llm
features:
  - agent-thought
  - tool-call
model_properties:
  mode: chat
  context_size: 8192
parameter_rules:
  - name: temperature
    use_template: temperature
  - name: max_tokens
    use_template: max_tokens

5.4 LLM 实现(models/llm/llm.py)

LargeLanguageModel 子类实现四抽象 _invoke / validate_credentials / get_num_tokens / _invoke_error_mapping_invoke 把 PromptMessage 转成 OpenAI messages 调 /chat/completions;流式逐行解析 SSE(data: 前缀、[DONE] 终止、delta.content 非空才 yield);validate_credentials/v1/models 验证 base_url/api_key——配置页「保存」即时报错。

6. 运行验证

输入 预期 结果
mock 网关 /models + /chat/completions curl 通过(非流式+流式 SSE) ✅ 一致
正确凭证保存 201 保存成功 ✅ 一致
错误 api_key / 网关不可达 保存报错明确(网关返回 401 / 不可达) ✅ 错误信息可读
控制台模型列表 出现「企业网关」供应商及 enterprise-gpt ✅ status active,features agent-thought+tool-call
LLM 节点调用 企业网关回答 ✅ run succeeded(0.8s)
流式输出 增量完整可拼接 ✅ 本地单测 6 chunks 拼接完整
mock 网关停止 LLM 调用报错明确 ✅ ConnectionError 语义与校验层一致

环境:Dify 1.16.1(Docker Compose),mock 网关 tmp/mock_gateway.py(127.0.0.1:8005,Bearer mock-secret-106)。Agent 形态经等价验证(106-03 已挂工具,替换模型场景等价;未单独跑 agent+企业网关组合)。

7. 实战坑

现象 修复
model_properties 枚举 model_properties 写 max_tokens → 上传报「Failed to parse response from plugin daemon to PluginDecodeResponse」(daemon 正常,api 解析失败) max_tokens 放 parameter_rules(use_template: max_tokens);model_properties 只允许 mode/context_size 等 10 个枚举
模型凭证 schema provider yaml 带空 model_credential_schema 数组反而解析失败 predefined-model 模式可省(对照 deepseek 官方结构确认)
provider 声明格式错 按工具插件写法声明模型,上传失败 manifest 用 plugins.models(非 tools);供应商 id = author/plugin/provider 三段
接口签名不符 抽象方法未实现启动失败 LargeLanguageModel 四抽象全实现;本地单测用 object.new 绕过 init(model_schemas)
流式实现缺陷 SSE 解析不全丢字/不终止 逐行解析 data: 前缀 + [DONE] 终止;delta.content 非空才 yield(含 finish_reason 终止 chunk)
凭证泄漏 错误信息带出 base_url/api_key 错误信息只报状态码+原因,不出凭证
模型凭证 API POST 创建同名凭证 400 already exists 用 PUT 更新(credential_id 从 provider_credentials 表拿);凭证校验失败不保存(保护行为)
LLM 节点 DSL model.provider 缺完整 id / data 缺字段 → 校验错 provider 用完整 id(dify106/dify106_07_gateway_provider/gateway);data 必须含 context/memory: null/memory_config/variables/vision

8. 实验文档及源码获取

文章聚焦核心配置与采坑点,完整分步操作与模型接入全链路验证记录见实验文档原文。

下一篇:Dify 插件开发实验(08):外部知识库插件——如何把外部检索能力做成插件?

💬 你在这个实验的场景里踩过什么坑?欢迎评论区分享你的实战经验。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐