本文档是为需要通过编写 Python 代码来为 Dify 添加或增强模型支持的开发者准备的标准指南。当你需要添加的模型涉及新的 API 调用逻辑、特殊参数处理或 Dify 需要显式支持的新功能(如 Vision, Tool Calling)时,就需要遵循本指南的步骤。

阅读本文前,建议你:

  • 具备 Python 编程基础和面向对象编程的基本理解。
  • 熟悉你想要集成的模型供应商提供的 API 文档和认证方式。
  • 已安装并配置好 Dify 插件开发工具包 (参考 初始化开发工具)。
  • (可选)阅读 模型插件介绍 文档,了解模型插件的基本概念和架构。

本指南将引导你完成创建目录结构、编写模型配置 (YAML)、实现模型调用逻辑 (Python) 以及调试和发布插件的全过程。


第 1 步: 创建目录结构

一个组织良好的目录结构是开发可维护插件的基础。你需要为你的模型供应商插件创建特定的目录和文件。

  1. 定位或创建供应商目录: 在你的插件项目(通常是 dify-official-plugins 的本地克隆)的 models/ 目录下,找到或创建以模型供应商命名的文件夹(例如 models/my_new_provider)。
  2. 创建 models 子目录: 在供应商目录下,创建 models 子目录。
  3. 按模型类型创建子目录:models/models/ 目录下,为你需要支持的每种模型类型创建一个子目录。常见的类型包括:
    • llm: 文本生成模型
    • text_embedding: 文本 Embedding 模型
    • rerank: Rerank 模型
    • speech2text: 语音转文字模型
    • tts: 文字转语音模型
    • moderation: 内容审查模型
  4. 准备实现文件:
    • 在每个模型类型目录下(例如 models/models/llm/),你需要创建一个 Python 文件来实现该类型模型的调用逻辑(例如 llm.py)。
    • 同样在该目录下,你需要为该类型下的每个具体模型创建一个 YAML 配置文件(例如 my-model-v1.yaml)。
    • (可选)可以创建一个 _position.yaml 文件来控制该类型下模型在 Dify UI 中的显示顺序。

示例结构 (假设供应商 my_provider 支持 LLM 和 Embedding):

models/my_provider/
├── models                # 模型实现和配置目录
│   ├── llm               # LLM 类型
│   │   ├── _position.yaml  (可选, 控制排序)
│   │   ├── my-llm-model-v1.yaml
│   │   ├── my-llm-model-v2.yaml
│   │   └── llm.py          # LLM 实现逻辑
│   └── text_embedding    # Embedding 类型
│       ├── _position.yaml  (可选, 控制排序)
│       ├── my-embedding-model.yaml
│       └── text_embedding.py # Embedding 实现逻辑
├── provider              # 供应商级别代码目录
│   └── my_provider.py    (用于凭证验证等, 参考“创建模型供应商”文档)
└── manifest.yaml         # 插件清单文件

第 2 步: 定义模型配置 (YAML)

对于每个具体模型,你需要创建一个 YAML 文件来描述其属性、参数和功能,以便 Dify 能够正确地理解和使用它。

  1. 创建 YAML 文件: 在对应的模型类型目录下(例如 models/models/llm/),为你要添加的模型创建一个 YAML 文件,文件名通常与模型 ID 保持一致或具有描述性(例如 my-llm-model-v1.yaml)。
  2. 编写配置内容: 遵循 AIModelEntity Schema Definition 规范编写内容。关键字段包括:
    • model: (必需) 模型的官方 API 标识符。
    • label: (必需) 在 Dify UI 中显示的名称 (支持多语言)。
    • model_type: (必需) 必须与所在目录类型一致 (如 llm)。
    • features: (可选) 声明模型支持的特殊功能 (如 vision, tool-call, stream-tool-call 等)。
    • model_properties: (必需) 定义模型固有属性,如 mode (chatcompletion), context_size
    • parameter_rules: (必需) 定义用户可调参数及其规则 (名称 name, 类型 type, 是否必须 required, 默认值 default, 范围 min/max, 选项 options 等)。可以使用 use_template 引用预定义模板简化常见参数(如 temperature, max_tokens)的配置。
    • pricing: (可选) 定义模型的计费信息。

示例 (claude-3-5-sonnet-20240620.yaml):

model: claude-3-5-sonnet-20240620
label:
  en_US: claude-3-5-sonnet-20240620
model_type: llm
features:
  - agent-thought
  - vision
  - tool-call
  - stream-tool-call
  - document
model_properties:
  mode: chat
  context_size: 200000
parameter_rules:
  - name: temperature
    use_template: temperature
  - name: top_p
    use_template: top_p
  - name: max_tokens
    use_template: max_tokens
    required: true
    default: 8192
    min: 1
    max: 8192 # 注意 Dify 层面可能有限制
pricing:
  input: '3.00'
  output: '15.00'
  unit: '0.000001' # 每百万 token
  currency: USD

第 3 步: 编写模型调用代码 (Python)

这是实现模型功能的和核心步骤。你需要在对应模型类型的 Python 文件中(例如 llm.py)编写代码来处理 API 调用、参数转换和结果返回。

  1. 创建/编辑 Python 文件: 在模型类型目录下(例如 models/models/llm/)创建或打开相应的 Python 文件(例如 llm.py)。

  2. 定义实现类:

    • 定义一个类,例如 MyProviderLargeLanguageModel
    • 该类必须继承自 Dify 插件 SDK 中对应的模型类型基类。例如,对于 LLM,需要继承 dify_plugin.provider_kits.llm.LargeLanguageModel
    import logging
    from typing import Union, Generator, Optional, List
    from dify_plugin.provider_kits.llm import LargeLanguageModel # 导入基类
    from dify_plugin.provider_kits.llm import LLMResult, LLMResultChunk, LLMUsage # 导入结果和用量类
    from dify_plugin.provider_kits.llm import PromptMessage, PromptMessageTool # 导入消息和工具类
    from dify_plugin.errors.provider_error import InvokeError, InvokeAuthorizationError # 导入错误类
    # 假设你有一个 vendor_sdk 用于调用 API
    # import vendor_sdk
    
    logger = logging.getLogger(__name__)
    
    class MyProviderLargeLanguageModel(LargeLanguageModel):
        # ... 实现方法 ...
    
  3. 实现关键方法: (具体需要实现的方法取决于继承的基类,以下以 LLM 为例)

    • _invoke(...): 核心调用方法

      • 签名: def _invoke(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], model_parameters: dict, tools: Optional[List[PromptMessageTool]] = None, stop: Optional[List[str]] = None, stream: bool = True, user: Optional[str] = None) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:
      • 职责:
        • 使用 credentialsmodel_parameters 准备 API 请求。
        • 将 Dify 的 prompt_messages 格式转换为供应商 API 所需的格式。
        • 处理 tools 参数以支持 Function Calling / Tool Use (如果模型支持)。
        • 根据 stream 参数决定是进行流式调用还是同步调用。
        • 流式返回: 如果 stream=True,此方法必须返回一个生成器 (Generator),通过 yield 逐块返回 LLMResultChunk 对象。每个 chunk 包含部分结果(文本、工具调用块等)和可选的用量信息。
        • 同步返回: 如果 stream=False,此方法必须返回一个完整的 LLMResult 对象,包含最终的文本结果、完整的工具调用列表以及总的用量信息 (LLMUsage)。
      • 实现模式: 强烈建议将同步和流式逻辑拆分到内部帮助方法中。
      def _invoke(self, ..., stream: bool = True, ...) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:
          # 准备 API 请求参数 (认证、模型参数转换、消息格式转换等)
          api_params = self._prepare_api_params(credentials, model_parameters, prompt_messages, tools, stop)
      
          try:
              if stream:
                  return self._invoke_stream(model, api_params, user)
              else:
                  return self._invoke_sync(model, api_params, user)
          except vendor_sdk.APIError as e:
              # 处理 API 错误, 映射到 Dify 错误 (参考 _invoke_error_mapping)
              # ... raise mapped_error ...
              pass # Replace with actual error handling
          except Exception as e:
              logger.exception("Unknown error during model invocation")
              raise e # Or raise a generic InvokeError
      
      def _invoke_stream(self, model: str, api_params: dict, user: Optional[str]) -> Generator[LLMResultChunk, None, None]:
          # 调用 vendor_sdk 的流式接口
          # for api_chunk in vendor_sdk.create_stream(...):
          #     # 将 api_chunk 转换为 LLMResultChunk
          #     dify_chunk = self._convert_api_chunk_to_llm_result_chunk(api_chunk)
          #     yield dify_chunk
          pass # Replace with actual implementation
      
      def _invoke_sync(self, model: str, api_params: dict, user: Optional[str]) -> LLMResult:
          # 调用 vendor_sdk 的同步接口
          # api_response = vendor_sdk.create_sync(...)
          # 将 api_response 转换为 LLMResult (包括 message.content, tools, usage)
          # dify_result = self._convert_api_response_to_llm_result(api_response)
          # return dify_result
          pass # Replace with actual implementation
      
    • validate_credentials(self, model: str, credentials: dict) -> None: (必需) 用于在用户添加或修改凭证时验证其有效性。通常通过调用一个简单的、低成本的 API 端点(如列出可用模型、检查余额等)来实现。如果验证失败,应抛出 CredentialsValidateFailedError 或其子类。

    • get_num_tokens(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], tools: Optional[List[PromptMessageTool]] = None) -> int: (可选但推荐) 用于预估给定输入的 Token 数量。如果无法准确计算或 API 不支持,可以返回 0。

    • @property _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: (必需) 定义一个错误映射字典。键是 Dify 的标准 InvokeError 子类,值是供应商 SDK 可能抛出的需要被映射到该标准错误的异常类型列表。这对于 Dify 统一处理不同供应商的错误至关重要。

      @property
      def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
          # 示例映射
          mapping = {
              InvokeAuthorizationError: [
                  vendor_sdk.AuthenticationError,
                  vendor_sdk.PermissionDeniedError,
              ],
              InvokeRateLimitError: [
                  vendor_sdk.RateLimitError,
              ],
              # ... 其他映射 ...
          }
          # 可以在这里加入基类的默认映射 (如果基类提供的话)
          # base_mapping = super()._invoke_error_mapping
          # mapping.update(base_mapping) # 注意合并策略
          return mapping
      

第 4 步: 调试插件

在将插件贡献给社区之前,充分的测试和调试是必不可少的。Dify 提供了远程调试功能,让你可以在本地修改代码并实时在 Dify 实例中测试效果。

  1. 获取调试信息:

    • 在你的 Dify 实例中,进入 “插件管理” 页面 (可能需要管理员权限)。
    • 点击页面右上角的 “调试插件”,获取你的 调试 Key远程服务器地址 (例如 http://<your-dify-domain>:5003)。
  2. 配置本地环境:

    • 在你的本地插件项目根目录下,找到或创建 .env 文件 (可从 .env.example 复制)。

    • 编辑 .env 文件,填入调试信息:

      INSTALL_METHOD=remote
      REMOTE_INSTALL_HOST=<your-dify-domain-or-ip> # Dify 服务器地址
      REMOTE_INSTALL_PORT=5003                     # 调试端口
      REMOTE_INSTALL_KEY=****-****-****-****-**** # 你的调试 Key
      
  3. 启动本地插件服务:

    • 在插件项目根目录下,确保你的 Python 环境已激活(如果使用虚拟环境)。

    • 运行主程序:

      python -m main
      
    • 观察终端输出,如果连接成功,通常会有相应的日志提示。

  4. 在 Dify 中测试:

    • 刷新 Dify 的 “插件” 或 “模型供应商” 页面,你应该能看到你的本地插件实例,可能带有 “调试中” 标识。
    • 前往 “设置” -> “模型供应商”,找到你的插件,配置有效的 API 凭证。
    • 在 Dify 应用中选择并使用你的模型进行测试。你在本地对 Python 代码的修改(保存后通常会自动重新加载服务)会直接影响 Dify 中的调用行为。使用 Dify 的调试预览功能可以帮助你检查输入输出和错误信息。

第 5 步: 打包与发布

当你完成了开发和调试,并对插件的功能满意后,就可以将其打包并贡献给 Dify 社区了。

  1. 打包插件:
    • 停止本地调试服务 (Ctrl+C)。

    • 在插件项目根目录下运行打包命令:

      # 将 <provider_name> 替换为你的供应商目录名
      dify plugin package models/<provider_name>
      
    • 这将在项目根目录下生成一个 <provider_name>.difypkg 文件。

  2. 提交 Pull Request:
    • 确保你的代码风格良好,并遵循 Dify 的插件发布规范
    • 将你的本地 Git 提交推送到你 Fork 的 dify-official-plugins 仓库。
    • 在 GitHub 上向 langgenius/dify-official-plugins 主仓库发起 Pull Request。在 PR 描述中清晰说明你所做的更改、添加的模型或功能,以及任何必要的测试说明。
    • 等待 Dify 团队审核。审核通过并合并后,你的贡献将包含在官方插件中,并在 Dify Marketplace 上可用。

探索更多