--- dimensions: type: primary: implementation detail: standard level: intermediate standard_title: Creating New Model Provider Extra language: zh title: 实现标准的模型集成 description: 本文档面向需要编写Python代码以添加或增强Dify模型支持的开发者,详细指导如何创建目录结构、编写模型配置、实现模型调用逻辑、调试和发布插件的完整流程,包含了核心方法实现和错误处理的细节。 --- 本文档是为需要通过编写 Python 代码来为 Dify 添加或增强模型支持的开发者准备的标准指南。当你需要添加的模型涉及新的 API 调用逻辑、特殊参数处理或 Dify 需要显式支持的新功能(如 Vision, Tool Calling)时,就需要遵循本指南的步骤。 **阅读本文前,建议你:** * 具备 Python 编程基础和面向对象编程的基本理解。 * 熟悉你想要集成的模型供应商提供的 API 文档和认证方式。 * 已安装并配置好 Dify 插件开发工具包 (参考 [初始化开发工具](../initialize-development-tools.md))。 * (可选)阅读 [模型插件介绍](link-to-conceptual-intro) 文档,了解模型插件的基本概念和架构。 本指南将引导你完成创建目录结构、编写模型配置 (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):** ```bash 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](../../../schema-definition/model/model-designing-rules.md#aimodelentity) 规范编写内容。关键字段包括: * `model`: (必需) 模型的官方 API 标识符。 * `label`: (必需) 在 Dify UI 中显示的名称 (支持多语言)。 * `model_type`: (必需) 必须与所在目录类型一致 (如 `llm`)。 * `features`: (可选) 声明模型支持的特殊功能 (如 `vision`, `tool-call`, `stream-tool-call` 等)。 * `model_properties`: (必需) 定义模型固有属性,如 `mode` (`chat` 或 `completion`), `context_size`。 * `parameter_rules`: (必需) 定义用户可调参数及其规则 (名称 `name`, 类型 `type`, 是否必须 `required`, 默认值 `default`, 范围 `min`/`max`, 选项 `options` 等)。可以使用 `use_template` 引用预定义模板简化常见参数(如 `temperature`, `max_tokens`)的配置。 * `pricing`: (可选) 定义模型的计费信息。 **示例 (`claude-3-5-sonnet-20240620.yaml`):** ```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`。 ```python 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]]:` * **职责:** * 使用 `credentials` 和 `model_parameters` 准备 API 请求。 * 将 Dify 的 `prompt_messages` 格式转换为供应商 API 所需的格式。 * 处理 `tools` 参数以支持 Function Calling / Tool Use (如果模型支持)。 * 根据 `stream` 参数决定是进行流式调用还是同步调用。 * **流式返回:** 如果 `stream=True`,此方法必须返回一个生成器 (`Generator`),通过 `yield` 逐块返回 `LLMResultChunk` 对象。每个 chunk 包含部分结果(文本、工具调用块等)和可选的用量信息。 * **同步返回:** 如果 `stream=False`,此方法必须返回一个完整的 `LLMResult` 对象,包含最终的文本结果、完整的工具调用列表以及总的用量信息 (`LLMUsage`)。 * **实现模式:** 强烈建议将同步和流式逻辑拆分到内部帮助方法中。 ```python 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 统一处理不同供应商的错误至关重要。 ```python @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://:5003`)。 2. **配置本地环境:** * 在你的本地插件项目**根目录**下,找到或创建 `.env` 文件 (可从 `.env.example` 复制)。 * 编辑 `.env` 文件,填入调试信息: ```dotenv INSTALL_METHOD=remote REMOTE_INSTALL_HOST= # Dify 服务器地址 REMOTE_INSTALL_PORT=5003 # 调试端口 REMOTE_INSTALL_KEY=****-****-****-****-**** # 你的调试 Key ``` 3. **启动本地插件服务:** * 在插件项目根目录下,确保你的 Python 环境已激活(如果使用虚拟环境)。 * 运行主程序: ```bash python -m main ``` * 观察终端输出,如果连接成功,通常会有相应的日志提示。 4. **在 Dify 中测试:** * 刷新 Dify 的 "插件" 或 "模型供应商" 页面,你应该能看到你的本地插件实例,可能带有 "调试中" 标识。 * 前往 "设置" -> "模型供应商",找到你的插件,配置有效的 API 凭证。 * 在 Dify 应用中选择并使用你的模型进行测试。你在本地对 Python 代码的修改(保存后通常会自动重新加载服务)会直接影响 Dify 中的调用行为。使用 Dify 的调试预览功能可以帮助你检查输入输出和错误信息。 --- ## 第 5 步: 打包与发布 当你完成了开发和调试,并对插件的功能满意后,就可以将其打包并贡献给 Dify 社区了。 1. **打包插件:** * 停止本地调试服务 (`Ctrl+C`)。 * 在插件项目**根目录**下运行打包命令: ```bash # 将 替换为你的供应商目录名 dify plugin package models/ ``` * 这将在项目根目录下生成一个 `.difypkg` 文件。 2. **提交 Pull Request:** * 确保你的代码风格良好,并遵循 Dify 的[插件发布规范](https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace)。 * 将你的本地 Git 提交推送到你 Fork 的 `dify-official-plugins` 仓库。 * 在 GitHub 上向 `langgenius/dify-official-plugins` 主仓库发起 Pull Request。在 PR 描述中清晰说明你所做的更改、添加的模型或功能,以及任何必要的测试说明。 * 等待 Dify 团队审核。审核通过并合并后,你的贡献将包含在官方插件中,并在 [Dify Marketplace](https://marketplace.dify.ai/) 上可用。 --- ## 探索更多 * [模型 Schema 定义](/plugin-dev-zh/0412-model-schema) (模型 YAML 规范) * [插件 Manifest 结构](/plugin-dev-zh/0411-general-specifications) (`manifest.yaml` 规范) * [Dify Plugin SDK 参考](https://github.com/langgenius/dify-plugin-sdks) (查找基类、数据结构和错误类型) * [Dify 官方插件仓库](https://github.com/langgenius/dify-official-plugins) (查看现有插件的实现) {/* Contributing Section DO NOT edit this section! It will be automatically generated by the script. */} --- [编辑此页面](https://github.com/langgenius/dify-docs/edit/main/plugin-dev-zh/0222-creating-new-model-provider-extra.mdx) | [提交问题](https://github.com/langgenius/dify-docs/issues/new?template=docs.yml)