dify案例分享-零代码搞定 DIFY 插件开发:小白也能上手的文生图插件实战

1.前言

"Dify插件开发"是指在Dify平台基础上,通过开发插件来扩展平台功能、增强AI应用能力的过程。Dify是一个开源的大语言模型(LLM)应用开发平台,旨在帮助开发者和企业快速构建、部署和管理AI驱动的解决方案。Dify平台支持多种功能扩展,包括模型插件、工具插件、Agent策略插件、扩展插件和插件包等,这些插件可以增强平台的感知与执行能力,支持集成外部服务、自定义功能及专用工具。

之前我给大伙分享过七十多个 Dify 工作流啦,都传到 GitHub 上了。项目地址:github.com/wwwzhouhui/... 。但 Dify 插件我一直没动手写过。其实早就想搞个自己的开源插件了,结果拖来拖去没做成,今天琢磨着,得把这个小梦想给圆了~

说干就干!结果一打开开发文档就有点头大,按传统方法开发也太麻烦了。之前有小伙伴问我,有没有讲 Dify 插件开发的案例文章,巧了,我之前分享的内容里正好缺这一块。

而且考虑到大伙技术水平不一样,一说到写代码,门槛就上去了,估计好多小白就直接划走了。所以我琢磨着换个思路,用 vibe - coding 的方式能不能搞出个 Dify 插件?有了这个想法就立马动手!不多说啦,下面就给大伙讲讲开发过程~

2.开发过程分享

代码目录

一个dify 插件代码,一个qwen-image代码,一个CLAUDE.md文档

其中dify 插件代码我是从开源项目中下载下来的,项目地址:<github.com/AllenWriter... 选这个项目主要是它有个文生图的功能,我可以让AI> 参考来学习。

qwen-image代码,这里我们从魔搭社区提供的API参考代码拿过来的 modelscope.cn/models/Qwen...

shell 复制代码
import requests
import time
import json
from PIL import Image
from io import BytesIO

base_url = 'https://api-inference.modelscope.cn/'
api_key = "xxxxx" # ModelScope Token

common_headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json",
}

response = requests.post(
    f"{base_url}v1/images/generations",
    headers={**common_headers, "X-ModelScope-Async-Mode": "true"},
    data=json.dumps({
        "model": "Qwen/Qwen-Image",  # ModelScope Model-Id, required
        "prompt": "A golden cat"
    }, ensure_ascii=False).encode('utf-8')
)

response.raise_for_status()
task_id = response.json()["task_id"]

while True:
    result = requests.get(
        f"{base_url}v1/tasks/{task_id}",
        headers={**common_headers, "X-ModelScope-Task-Type": "image_generation"},
    )
    result.raise_for_status()
    data = result.json()

    if data["task_status"] == "SUCCEED":
        image = Image.open(BytesIO(requests.get(data["output_images"][0]).content))
        image.save("result_image.jpg")
        break
    elif data["task_status"] == "FAILED":
        print("Image Generation Failed.")
        break

    time.sleep(5)

CLAUDE.md是基于腾讯的一个CloudBase AI 开发规则指南

yaml 复制代码
cloudbaseAIVersion:1.8.28
description: CloudBase AI 开发规则指南 - 提供场景化的最佳实践,确保开发质量
globs: *

alwaysApply: true
---

# 📋 CloudBase AI 开发规则指南

## 🎯 开发流程规范 - 场景识别与最佳实践

**重要:为确保开发质量,AI 需要在开始工作前完成以下步骤:**

### 1. 场景识别

首先需要识别当前的开发场景类型:

- **Web 项目**:React/Vue/原生 JS 等前端项目
- **微信小程序**:小程序云开发项目  
- **数据库相关**:涉及数据操作的项目
- **UI 设计**:需要界面设计的项目

### 2. 规则文件选择

根据识别的场景,需要参考对应的专业规则文件:

**📋 场景规则映射表(必须遵守):**

- **Web 项目** → 必读:`rules/web-development.mdc` + `rules/cloudbase-platform.mdc`
- **微信小程序** → 必读:`rules/miniprogram-development.mdc` + `rules/cloudbase-platform.mdc`
- **数据库操作** → 额外读:`rules/database.mdc`
- **UI 设计** → 额外读:`rules/ui-design.mdc`

### 3. 开发确认

在开始工作前建议向用户确认:

1. "我识别这是一个 [场景类型] 项目"
2. "我将严格遵循以下规则文件:[具体文件列表]"
3. "请确认我的理解是否正确"

## 核心行为规则

1. **工具优先**:关于腾讯云开发的操作,优先使用 cloudbase 的 MCP 工具
2. **项目理解**:首先阅读当前项目的 README.md,遵照项目说明开发
3. **目录规范**:在当前目录下产出项目代码,先检查当前目录文件
4. **部署顺序**:有后端依赖时,优先部署后端再预览前端
5. **交互确认**:需求不明确时使用 interactiveDialog 澄清,执行高风险操作前必须确认
6. **实时通信**:使用云开发的实时数据库 watch 能力
7. **认证规则**:严格区分平台的认证方式
   - **Web 项目**:必须使用 CloudBase Web SDK 内置认证(如 `auth.toDefaultLoginPage()`)
   - **小程序项目**:天然免登录,云函数中获取 `wxContext.OPENID`

## 版本检测和升级约束

### 自动版本检测

- 当用户使用 CloudBase 相关功能时,AI Agent 应自动检查当前项目的 cloudbaseAIVersion 字段
- 通过 npm registry API 查询 @cloudbase/cloudbase-mcp 的最新版本
- 比较版本差异,如果发现新版本可用,主动向用户提示升级建议
- 首次使用时显示友好的欢迎信息和当前版本

### 升级指导流程

- 检测到版本差异时,使用 interactiveDialog 工具与用户确认升级意愿
- 提供详细的升级指导,包括 MCP 升级和 AI 规则下载两个选项
- 集成官方文档链接:https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/faq#如何更新-cloudbase-ai-toolkit
- 指导用户执行 downloadTemplate 操作,参数为 template:rules

## 工作流

### Workflow 命令控制

**可用命令:**

- **默认** - AI 根据任务复杂度智能判断
- **/spec** - 强制使用完整 spec 流程
- **/no_spec** - 跳过 spec 流程,直接执行
- **/help** - 显示命令帮助

**智能判断标准:**

- **使用 spec**:新功能开发、复杂架构设计、多模块集成、涉及数据库/UI设计
- **跳过 spec**:简单修复、文档更新、配置修改、代码重构

<spec_workflow>

0. 请注意!必须遵守以下的规则,每个环节完成后都需要由我进行确认后才可进行下一个环节;
1. 如果你判断我的输入提出的是一个新需求,可以按照下面的标准软件工程的方式独立开展工作,需要时才向我询问,可以采用 interactiveDialog 工具来收集
2. 每当我输入新的需求的时候,为了规范需求质量和验收标准,你首先会搞清楚问题和需求,然后再进入下一阶段
3. 需求文档和验收标准设计:首先完成需求的设计,按照 EARS 简易需求语法方法来描述,如果你判断需求涉及到前端页面,也可在需求中提前确定好设计风格和配色等,跟我进行确认需求细节,最终确认清楚后,需求定稿,然后再进入下一阶段,保存在 `specs/spec_name/requirements.md` 中,参考格式如下

```markdown
# 需求文档

## 介绍

需求描述

## 需求

### 需求 1 - 需求名称

**用户故事:** 用户故事内容

#### 验收标准

1. 采用 ERAS 描述的子句 While <可选前置条件>, when <可选触发器>, the <系统名称> shall <系统响应>,例如 When 选择"静音"时,笔记本电脑应当抑制所有音频输出。
2. ...
...
```

4. 技术方案设计: 在完成需求的设计之后,你会根据当前的技术架构和前面确认好的需求,进行需求的技术方案设计,精简但是能够准确的描述技术的架构(例如架构、技术栈、技术选型、数据库/接口设计、测试策略、安全性),必要时可以用 mermaid 来绘图,跟我确认清楚后,保存在  `specs/spec_name/design.md`  中,然后再进入下一阶段
5. 任务拆分:在完成技术方案设计后,你会根据需求文档和技术方案,细化具体要做的事情,跟我确认清楚后,,保存在`specs/spec_name/tasks.md` 中, 然后再进入下一阶段,开始正式执行任务,同时需要及时更新任务的状态,执行的时候尽可能独立自主运行,保证效率和质量

任务参考格式如下

``` markdown
# 实施计划

- [ ] 1. 任务信息
  - 具体要做的事情
  - ...
  - _需求: 相关的需求点的编号

```

</workflow>

## 🔄 开发工作流程

### 部署流程

1. **部署云函数流程**:可以通过 getFunctionList MCP 工具来查询是否有云函数,然后直接调用 createFunction 或者 updateFunctionCode 更新云函数代码,只需要将functionRootPath 指向云函数目录的父目录(例如 cloudfuncitons 这个目录的绝对路径),不需要压缩代码等操作,上述工具会自动读取云函数父目录下的云函数同名目录的文件,并自动进行部署

2. **部署静态托管流程**:通过使用 uploadFiles 工具部署,部署完毕后提醒用户 CDN 有几分钟缓存,可以生成一个带有随机 queryString 的markdown 格式 访问链接

3. **下载远程素材链接**:使用 downloadRemoteFile 工具下载文件到本地,如果需要远程链接,可以继续调用 uploadFile 上传后获得临时访问链接和云存储的 cloudId

4. **从知识库查询专业知识**:可以使用 searchKnowledgeBase 工具智能检索云开发知识库(支持云开发与云函数、小程序前端知识等),通过向量搜索快速获取专业文档与答案

5. **下载云开发 AI 规则或者其他模板**:可以使用downloadTemplate 来下载,如果无法下载到当前目录,可以使用脚本来进行复制,注意隐藏文件也需要复制

### 文档生成规则

1. 你会在生成项目后生成一个 README.md 文件,里面包含项目的基本信息,例如项目名称、项目描述, 最关键的是要把项目的架构和涉及到的云开发资源说清楚,让维护者可以参考来进行修改和维护
2. 部署完毕后,如果是 web 可以把正式部署的访问地址也写到文档中

### 配置文件规则

1. 为了方便其他不使用 AI 的人了解有哪些资源,可以在生成之后,同时生成一个 cloudbaserc.json

### MCP 接口调用规则

你调用mcp服务的时候,需要充分理解所有要调用接口的数据类型,以及返回值的类型,如果你不确定需要调用什么接口,请先查看文档和tools的描述,然后根据文档和tools的描述,确定你需要调用什么接口和参数,不要出现调用的方法参数,或者参数类型错误的情况。

例如,很多接口都需要传confirm参数,这个参数是boolean类型,如果你不提供这个参数,或者提供错误的数据类型错误,那么接口会返回错误。

## 🔍 专业规则文件详细说明

### 📱 rules/miniprogram-development.mdc  

**强制适用**:微信小程序项目

- 小程序项目结构和配置
- 微信开发者工具 CLI 集成  
- 云开发能力和 API 使用
- **特别注意**:严禁使用 Web SDK 认证方式

### 🌐 rules/web-development.mdc

**强制适用**:Web 前端项目  

- 现代前端工程化(Vite/Webpack)
- 静态托管部署和预览
- CloudBase Web SDK 集成和认证
- **特别注意**:必须使用 SDK 内置认证功能

### ☁️ rules/cloudbase-platform.mdc

**通用必读**:所有 CloudBase 项目

- 云开发环境配置和认证机制
- 云函数、数据库、存储服务
- 数据模型和权限策略
- 控制台管理链接



### 🗄️ rules/database.mdc

**条件必读**:涉及数据库操作时

- CloudBase 数据库操作规范
- 权限管理和安全策略
- 错误处理和数据更新

### 🎨 rules/ui-design.mdc

**条件必读**:需要界面设计时  

- 高保真原型设计
- UI/UX 规范和样式处理

## ⚡ 开发质量检查清单

为确保开发质量,建议在开始任务前完成以下检查:

### ✅ 推荐完成的步骤

1. **[ ] 场景识别**:明确当前是什么类型的项目(Web/小程序/数据库/UI)
2. **[ ] 规则声明**:明确列出将要遵循的规则文件清单  
3. **[ ] 用户确认**:向用户确认场景识别和规则选择是否正确
4. **[ ] 规则执行**:严格按照选定的规则文件进行开发

### ⚠️ 常见问题避免

- 避免跳过场景识别直接开始开发
- 避免混用不同平台的 API 和认证方式  
- 避免忽略专业规则文件的指导
- 重要技术方案建议与用户确认

### 🔄 质量保障

如发现开发不符合规范,可以:

- 指出具体问题点
- 要求重新执行规则检查流程
- 明确指定需要遵循的规则文件

和AI对话的问题

接下来我的问题。先不着急让它写代码,写把开发规范文档写出来,也就是让它先输出CLAUDE2.md

shell 复制代码
doubao-image-and-video-generator-main 文件里面是基于dify的一个插件,这里有关于dify开发相关规范。请认真阅读这个项目。

qwen-image.py是一个文生图的推理代码,CLAUDE.md是基于腾讯的一个CloudBase AI 开发规则指南。

我希望你根据qwen-image.py 文生图的功能基于doubao-image-and-video-generator-main 里面的代码逻辑生成一个类似CLAUDE2.md

开发规则指南,基于这个开发指南我后面就可以根据这个指南开发其他的dify插件了。请基于以上功能先生成一个dify开发相关规范的CLAUDE2.md文档。

和AI对话的问题

arduino 复制代码
CLAUDE2.md 是基于doubao-image-and-video-generator-main 来实现的基于dify 插件的开发,我希望你在认真阅读qwen-image.py功能代码逻辑把上面的文档CLAUDE2.md  内容修改一下,只要实现文生图功能就可以了,请修改。生成代码过程中使用中文

修改后的CLAUDE2.md 文档

文档内容如下:

yaml 复制代码
---
difyPluginVersion: 0.1.0
description: Dify 文生图插件开发规则指南 - 基于 ModelScope Qwen-Image 的最佳实践
globs: *
alwaysApply: true
---

# 📋 Dify 文生图插件开发规则指南

## 🎯 开发流程规范 - 文生图插件最佳实践

**重要:为确保开发质量,AI 需要在开始工作前完成以下步骤:**

### 1. 文生图插件特点识别
文生图插件的核心特征:
- **异步任务处理**:大多数 AI 图像生成服务采用异步模式
- **任务状态轮询**:需要定期检查生成任务的完成状态
- **图像结果处理**:处理返回的图像 URL 或 base64 数据
- **用户体验优化**:提供实时的生成进度反馈

### 2. 技术架构选择
基于 qwen-image.py 的实现模式,文生图插件应采用:

**📋 文生图架构标准(必须遵守):**
- **异步任务模式** → 提交生成任务,获取 task_id
- **轮询状态机制** → 定期查询任务状态直到完成
- **图像下载处理** → 从 URL 下载图像并转换为 Dify 支持的格式
- **错误重试机制** → 处理网络异常和 API 限流

### 3. 开发确认
在开始工作前建议向用户确认:
1. "我识别这是一个文生图插件项目"
2. "我将使用异步任务轮询架构模式"
3. "请确认我的理解是否正确"

## 🏗️ Dify 文生图插件核心架构规范

### 项目结构标准
```
text2image_plugin/
├── manifest.yaml          # 插件清单文件
├── main.py                # 插件入口文件
├── requirements.txt       # Python 依赖
├── .env.example          # 环境变量示例
├── README.md             # 项目文档
├── icon.svg              # 插件图标
├── provider/             # 服务提供者配置
│   ├── __init__.py
│   ├── modelscope.yaml   # ModelScope 提供者配置
│   └── modelscope_provider.py
└── tools/                # 工具实现
    ├── __init__.py
    ├── text2image.yaml   # 文生图工具配置
    └── text2image.py     # 文生图工具实现
```

### 核心文件规范

#### 1. manifest.yaml 配置规范
```yaml
# 基础信息
author: your_name
created_at: '2025-XX-XXTXX:XX:XX.XXXXXXXXX'
description:
  en_US: AI text-to-image generation plugin powered by ModelScope Qwen-Image
  zh_CN: 基于 ModelScope Qwen-Image 的 AI 文生图插件
icon: icon.svg
label:
  en_US: Qwen Text2Image
  zh_CN: Qwen 文生图

# 元数据配置
meta:
  arch: [amd64, arm64]
  runner:
    entrypoint: main
    language: python
    version: '3.12'
  version: 0.0.1

# 插件配置
name: qwen_text2image
plugins:
  tools:
  - provider/modelscope.yaml

# 资源配置
resource:
  memory: 1048576
  permission:
    model:
      enabled: true
      llm: true
    tool:
      enabled: true

# 标签和类型
tags: [image, text2image, ai, modelscope]
type: plugin
version: 0.0.1
```

#### 2. main.py 入口文件规范
```python
from dify_plugin import Plugin, DifyPluginEnv

# 配置插件环境,设置超时时间为 300 秒(5分钟)以适应图像生成的时间需求
plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=300))

if __name__ == '__main__':
    plugin.run()
```

#### 3. requirements.txt 依赖规范
```txt
# Dify 插件核心依赖
dify_plugin>=0.1.0,<0.2.0

# HTTP 请求库
requests>=2.31.0

# 图像处理库
Pillow>=10.0.0

# JSON 处理(Python 内置,无需额外安装)
# time 模块(Python 内置,无需额外安装)
```

## 🔧 Provider 开发规范

### modelscope.yaml 配置模板
```yaml
# 认证配置
credentials_for_provider:
  api_key:
    help:
      en_US: Get your ModelScope API key from https://modelscope.cn
      zh_CN: 从 ModelScope 平台获取您的 API Key
    label:
      en_US: ModelScope API Key
      zh_CN: ModelScope API Key
    placeholder:
      en_US: Please input your ModelScope API Key (ms-xxxxxx)
      zh_CN: 请输入您的 ModelScope API Key (ms-xxxxxx)
    required: true
    type: secret-input
    url: https://modelscope.cn/my/myaccesstoken

# 扩展配置
extra:
  python:
    source: provider/modelscope_provider.py

# 身份信息
identity:
  author: your_name
  description:
    en_US: ModelScope AI image generation service integration
    zh_CN: ModelScope AI 图像生成服务集成
  icon: icon.svg
  label:
    en_US: ModelScope
    zh_CN: ModelScope
  name: modelscope
  tags: [image, text2image, ai]

# 工具列表
tools:
- tools/text2image.yaml
```

### modelscope_provider.py 实现规范
```python
from typing import Any
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
from tools.text2image import Text2ImageTool
from dify_plugin import ToolProvider

class ModelScopeProvider(ToolProvider):
    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
        """
        验证 ModelScope API 凭据有效性
        
        Args:
            credentials: 包含 ModelScope API key 的字典
            
        Raises:
            ToolProviderCredentialValidationError: 当凭据验证失败时
        """
        try:
            # 检查 API key 格式
            api_key = credentials.get("api_key")
            if not api_key or not api_key.startswith("ms-"):
                raise ToolProviderCredentialValidationError(
                    "Invalid ModelScope API key format. Should start with 'ms-'"
                )
            
            # 创建测试参数
            test_params = {
                "prompt": "测试图像生成",
                "model": "Qwen/Qwen-Image"
            }
            
            # 尝试调用文生图工具进行验证
            # 注意:这里只验证 API key 的有效性,不会真正生成图像
            tool = Text2ImageTool.from_credentials(credentials)
            # 可以通过发送一个简单的请求来验证 API key
            # 具体验证逻辑根据 ModelScope API 的特点来实现
                
        except Exception as e:
            raise ToolProviderCredentialValidationError(
                f"ModelScope API 凭据验证失败: {str(e)}"
            )
```

## 🛠️ Tool 开发规范

### text2image.yaml 配置模板
```yaml
# 工具描述
description:
  human:
    en_US: Generate images from text prompts using ModelScope Qwen-Image AI model
    zh_CN: 使用 ModelScope Qwen-Image AI 模型从文本提示生成图像
  llm: This tool generates high-quality images from text descriptions using ModelScope Qwen-Image model

# 扩展配置
extra:
  python:
    source: tools/text2image.py

# 身份信息
identity:
  author: your_name
  icon: icon.svg
  label:
    en_US: Text to Image
    zh_CN: 文生图
  name: text2image

# 参数配置
parameters:
- form: llm
  human_description:
    en_US: The text prompt to generate image from. Describe what you want to see in the image.
    zh_CN: 用于生成图像的文本提示。描述您希望在图像中看到的内容。
  label:
    en_US: Prompt
    zh_CN: 提示词
  llm_description: Text prompt that describes the desired image content
  name: prompt
  required: true
  type: string
  
- form: form
  human_description:
    en_US: The AI model to use for image generation
    zh_CN: 用于图像生成的 AI 模型
  label:
    en_US: Model
    zh_CN: 模型
  name: model
  type: select
  options:
  - label:
      en_US: Qwen-Image (Default)
      zh_CN: Qwen-Image (默认)
    value: "Qwen/Qwen-Image"
  default: "Qwen/Qwen-Image"
  required: false
```

### text2image.py 实现规范(基于 qwen-image.py 逻辑)

```python
import requests
import time
import json
from collections.abc import Generator
from PIL import Image
from io import BytesIO
from dify_plugin.entities.tool import ToolInvokeMessage
from dify_plugin import Tool

class Text2ImageTool(Tool):
    def _invoke(
        self, tool_parameters: dict
    ) -> Generator[ToolInvokeMessage, None, None]:
        """
        基于 ModelScope API 的异步文生图工具
        实现逻辑参考 qwen-image.py
        
        Args:
            tool_parameters: 工具参数字典
            
        Yields:
            ToolInvokeMessage: 工具调用消息
        """
        # 1. 获取 API 配置
        api_key = self.runtime.credentials.get("api_key")
        base_url = 'https://api-inference.modelscope.cn/'
        
        # 2. 获取和验证参数
        prompt = tool_parameters.get("prompt", "")
        if not prompt:
            yield self.create_text_message("请输入提示词")
            return
            
        model = tool_parameters.get("model", "Qwen/Qwen-Image")
        
        # 3. 设置请求头
        common_headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        }
        
        try:
            yield self.create_text_message("🚀 正在提交图像生成任务...")
            
            # 添加调试信息
            yield self.create_text_message(f"🔧 使用模型: {model}")
            yield self.create_text_message(f"🔧 提示词长度: {len(prompt)} 字符")
            
            # 4. 提交异步生成任务(优化的请求格式)
            request_data = {
                "model": model,
                "prompt": prompt,
                "n": 1,  # 添加生成图片数量参数
                "size": "1024x1024"  # 添加图片尺寸参数
            }
            
            response = requests.post(
                f"{base_url}v1/images/generations",
                headers={**common_headers, "X-ModelScope-Async-Mode": "true"},
                json=request_data,  # 使用 json 参数而不是手动编码
                timeout=30  # 添加超时设置
            )
            
            # 检查响应状态
            if response.status_code != 200:
                yield self.create_text_message(f"🔧 API 响应状态码: {response.status_code}")
                yield self.create_text_message(f"🔧 响应内容: {response.text[:500]}")
            
            response.raise_for_status()
            
            # 获取任务 ID
            response_data = response.json()
            task_id = response_data.get("task_id")
            
            if not task_id:
                yield self.create_text_message("❌ 创建任务失败,未获取到任务ID")
                return
            
            yield self.create_text_message(f"✅ 任务已创建,ID: {task_id}")
            yield self.create_text_message("⏳ 正在生成图像,请稍候...")
            
            # 5. 轮询任务状态
            max_retries = 60  # 最大重试次数,防止无限等待
            retry_count = 0
            
            while retry_count < max_retries:
                # 等待 5 秒再查询
                time.sleep(5)
                
                # 查询任务状态
                result = requests.get(
                    f"{base_url}v1/tasks/{task_id}",
                    headers={**common_headers, "X-ModelScope-Task-Type": "image_generation"},
                )
                result.raise_for_status()
                data = result.json()
                
                task_status = data["task_status"]
                
                if task_status == "SUCCEED":
                    # 任务成功,下载图像(按照 qwen-image.py 的处理方式)
                    output_images = data.get("output_images", [])
                    if not output_images:
                        yield self.create_text_message("❌ 生成成功但未找到图像数据")
                        return
                    
                    image_url = output_images[0]
                    yield self.create_text_message("🎨 图像生成成功,正在下载...")
                    
                    # 下载图像(完全按照 qwen-image.py 的方式)
                    image_response = requests.get(image_url)
                    image_response.raise_for_status()
                    
                    # 处理图像数据(使用 PIL,与 qwen-image.py 一致)
                    image = Image.open(BytesIO(image_response.content))
                    
                    # 将图像转换为字节流
                    img_byte_arr = BytesIO()
                    image.save(img_byte_arr, format='PNG')
                    img_byte_arr = img_byte_arr.getvalue()
                    
                    # 返回图像
                    yield self.create_blob_message(
                        blob=img_byte_arr,
                        meta={"mime_type": "image/png"}
                    )
                    yield self.create_text_message("🎉 图像生成完成!")
                    return
                    
                elif task_status == "FAILED":
                    error_info = data.get("error", {})
                    error_message = error_info.get("message", "未知错误")
                    yield self.create_text_message(f"❌ 图像生成失败: {error_message}")
                    return
                
                # 继续等待,提供进度反馈
                wait_time = (retry_count + 1) * 5
                yield self.create_text_message(
                    f"⏳ 图像正在生成中,已等待 {wait_time} 秒..."
                )
                retry_count += 1
            
            # 超时处理
            yield self.create_text_message("⏰ 图像生成超时(5分钟),请稍后再试")
        
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 401:
                yield self.create_text_message("❌ API Key 无效,请检查您的 ModelScope API Key")
            elif e.response.status_code == 429:
                yield self.create_text_message("❌ API 调用频率过高,请稍后再试")
            elif e.response.status_code == 500:
                yield self.create_text_message("❌ ModelScope 服务器内部错误")
                yield self.create_text_message("💡 可能的解决方案:")
                yield self.create_text_message("1. 检查提示词是否包含敏感内容")
                yield self.create_text_message("2. 尝试简化提示词描述")
                yield self.create_text_message("3. 稍后重试,可能是服务器临时故障")
                yield self.create_text_message(f"🔧 错误详情: {e.response.text[:200] if hasattr(e.response, 'text') else 'N/A'}")
            else:
                yield self.create_text_message(f"❌ HTTP 错误: {e.response.status_code} - {str(e)}")
                if hasattr(e.response, 'text'):
                    yield self.create_text_message(f"🔧 响应内容: {e.response.text[:200]}")
        except requests.exceptions.RequestException as e:
            yield self.create_text_message(f"❌ 网络请求错误: {str(e)}")
        except KeyError as e:
            yield self.create_text_message(f"❌ API 响应格式错误,缺少字段: {str(e)}")
        except json.JSONDecodeError as e:
            yield self.create_text_message(f"❌ API 响应解析错误: {str(e)}")
        except Exception as e:
            yield self.create_text_message(f"❌ 生成图像时出现未知错误: {str(e)}")
```

## 📝 核心开发模式总结

### ModelScope 异步 API 调用模式
基于 `qwen-image.py` 的实现,文生图插件采用以下标准流程:

1. **任务提交阶段**
   - 发送 POST 请求到 `/v1/images/generations`
   - 设置 `X-ModelScope-Async-Mode: true` 启用异步模式
   - 获取 `task_id` 用于后续查询

2. **状态轮询阶段**
   - 使用 `task_id` 查询任务状态
   - 设置 `X-ModelScope-Task-Type: image_generation`
   - 每 5 秒查询一次,最多查询 60 次(5 分钟)

3. **结果处理阶段**
   - 任务成功时下载图像 URL
   - 使用 PIL 处理图像数据
   - 转换为 Dify 支持的 blob 格式返回

## 📝 文生图插件消息类型规范

### 1. 进度反馈消息
```python
yield self.create_text_message("🚀 正在提交图像生成任务...")
yield self.create_text_message(f"🔧 使用模型: {model}")
yield self.create_text_message(f"🔧 提示词长度: {len(prompt)} 字符")
yield self.create_text_message(f"✅ 任务已创建,ID: {task_id}")
yield self.create_text_message("⏳ 正在生成图像,请稍候...")
yield self.create_text_message(f"⏳ 图像正在生成中,已等待 {wait_time} 秒...")
```

### 2. 图像结果消息
```python
yield self.create_blob_message(
    blob=img_byte_arr,
    meta={"mime_type": "image/png"}
)
```

### 3. 状态消息
```python
yield self.create_text_message("🎉 图像生成完成!")
yield self.create_text_message("❌ 图像生成失败: {error_message}")
yield self.create_text_message("⏰ 图像生成超时(5分钟),请稍后再试")
yield self.create_text_message("❌ ModelScope 服务器内部错误")
yield self.create_text_message("💡 可能的解决方案:")
```

## 🔄 文生图插件开发工作流程

### 1. 项目初始化
1. **创建项目结构**:按照文生图插件标准目录结构创建文件
2. **配置 manifest.yaml**:设置文生图插件基本信息和权限
3. **编写 main.py**:配置插件入口,设置超时时间为 120 秒
4. **配置依赖**:添加 requests、Pillow 等必要依赖

### 2. ModelScope Provider 开发
1. **配置认证**:在 modelscope.yaml 中设置 ModelScope API key 认证
2. **实现验证**:验证 API key 格式(必须以 ms- 开头)
3. **注册工具**:注册 text2image.yaml 工具

### 3. Text2Image Tool 开发
1. **定义参数**:配置 prompt(必需)和 model(可选)参数
2. **实现异步逻辑**:按照 qwen-image.py 的模式实现异步任务处理
3. **错误处理**:处理网络异常、API 错误、超时等情况
4. **图像处理**:使用 PIL 处理下载的图像数据

### 4. 部署和测试
1. **本地测试**:测试 ModelScope API 连接和图像生成功能
2. **异步测试**:验证任务轮询和超时处理机制
3. **集成测试**:在 Dify 平台中测试完整的文生图工作流

## ⚡ 文生图插件开发最佳实践

### 1. 异步任务处理
- **必须**:实现任务状态轮询机制,每 5 秒查询一次
- **必须**:设置最大重试次数(建议 60 次,即 5 分钟)
- **必须**:处理 SUCCEED、FAILED 等不同任务状态
- **建议**:提供实时的等待时间反馈

### 2. 图像数据处理
- **必须**:使用 PIL 库处理图像数据
- **必须**:将图像转换为 PNG 格式的字节流
- **必须**:设置正确的 mime_type 为 "image/png"
- **建议**:处理不同格式的图像输入

### 3. API 调用规范
- **必须**:设置 X-ModelScope-Async-Mode: true 启用异步模式
- **必须**:在查询时设置 X-ModelScope-Task-Type: image_generation
- **必须**:使用 Bearer token 进行身份验证
- **必须**:处理 HTTP 状态码和 API 错误响应

### 4. 用户体验优化
- **必须**:提供任务创建、进行中、完成的状态反馈
- **必须**:显示任务 ID 和等待时间
- **必须**:处理超时和失败情况
- **必须**:使用表情符号增强用户体验(🚀🔧✅⏳❌💡🎉)
- **必须**:提供调试信息(模型名称、提示词长度、响应状态码)
- **建议**:提供中文用户友好的错误消息和解决方案

### 5. 安全和配置
- **必须**:验证 ModelScope API key 格式(ms- 开头)
- **必须**:使用 secret-input 类型存储 API key
- **禁止**:在代码中硬编码 API key
- **建议**:提供 API key 获取链接和帮助信息

## 🔍 文生图插件常见问题和解决方案

### 1. ModelScope API 调用失败
- **检查**:API key 格式是否正确(必须以 ms- 开头)
- **检查**:网络连接是否正常,能否访问 api-inference.modelscope.cn
- **检查**:请求头是否包含正确的 Authorization 和异步模式设置
- **解决**:验证 API key 有效性,检查网络防火墙设置

### 2. 异步任务轮询超时
- **原因**:图像生成时间过长或网络延迟
- **调整**:增加 MAX_REQUEST_TIMEOUT 到 300 秒或更长
- **优化**:调整轮询间隔(建议保持 5 秒)
- **改进**:增加最大重试次数到 120 次(10 分钟)

### 3. 图像下载和处理失败
- **检查**:返回的图像 URL 是否有效
- **检查**:PIL 库是否正确安装
- **处理**:添加图像下载重试机制
- **优化**:处理不同图像格式的兼容性

### 4. 任务状态查询异常
- **检查**:task_id 是否正确获取
- **检查**:查询请求头是否包含 X-ModelScope-Task-Type
- **处理**:添加任务状态验证逻辑
- **改进**:处理 API 返回的异常状态

### 5. ModelScope 500 服务器内部错误
- **原因**:提示词包含敏感内容、服务器临时故障、请求格式问题
- **解决方案**:
  - 检查提示词是否包含敏感词汇
  - 尝试简化提示词描述
  - 使用标准的 JSON 请求格式
  - 添加适当的请求参数(n=1, size="1024x1024")
  - 稍后重试,可能是服务器临时故障
- **调试**:查看详细的错误响应内容
- **预防**:添加调试信息输出模型和提示词长度

## 📋 文生图插件开发检查清单

### ✅ 开发前准备
- [ ] **ModelScope 账号**:注册 ModelScope 账号并获取 API key
- [ ] **API 文档研读**:理解 ModelScope 异步图像生成 API
- [ ] **环境准备**:安装 Python 3.12 和必要依赖
- [ ] **Dify 环境**:准备 Dify 插件开发环境

### ✅ 开发过程检查
- [ ] **项目结构**:按照文生图插件标准结构组织文件
- [ ] **manifest.yaml**:配置正确的插件信息和资源限制
- [ ] **modelscope.yaml**:配置 ModelScope API key 认证
- [ ] **text2image.yaml**:配置提示词和模型参数
- [ ] **异步逻辑**:实现任务提交和状态轮询机制
- [ ] **图像处理**:使用 PIL 正确处理图像数据
- [ ] **错误处理**:处理网络异常、API 错误、超时等情况
- [ ] **用户反馈**:提供实时的进度和状态消息

### ✅ 测试和部署
- [ ] **API 连接测试**:验证 ModelScope API 连接正常
- [ ] **异步流程测试**:测试完整的任务提交到完成流程
- [ ] **错误场景测试**:测试网络异常、API 错误等情况
- [ ] **超时处理测试**:验证超时机制正常工作
- [ ] **图像质量测试**:验证生成的图像质量和格式
- [ ] **Dify 集成测试**:在 Dify 平台中测试插件功能

### ⚠️ 文生图插件特有问题避免
- 避免忘记设置异步模式头部信息
- 避免轮询间隔过短导致 API 限流
- 避免缺少任务状态的完整处理
- 避免图像数据格式转换错误
- 避免超时时间设置过短
- 避免缺少网络异常处理

## 🎯 质量保障

### 代码质量标准
- **异步处理**:严格按照 qwen-image.py 的模式实现
- **错误处理**:完整处理所有可能的异常情况
- **用户体验**:提供清晰的中文进度反馈
- **性能优化**:合理设置轮询间隔和超时时间

### 验收标准
1. 能够成功连接 ModelScope API
2. 正确实现异步任务提交和轮询
3. 能够处理各种错误和异常情况
4. 生成的图像质量符合预期
5. 在 Dify 平台中正常工作

---

**注意**:本指南专门针对基于 ModelScope Qwen-Image 的文生图插件开发,基于 qwen-image.py 的实现逻辑和 Dify 插件框架 v0.1.0。

看起来这个文档规范做的还不错。 接下来我让它按照这个规范来开发代码。

生成代码

请基于CLAUDE2.md 规范结合qwen-image.py功能模型来实现一个文生图的dify插件代码,生成代码过程中请使用中文来回答。

打开代码后我们仔细查看一下代码的结构

这个代码目录结构是符合dify插件开发的。

测试

接下来我们需要对生成的代码进行验证测试。

请帮我使用test_plugin.py 对该插件代码进行测试

我们它虽然自己测试了,我们这里手工测试一下

打开这个生成的图片链接 muse-ai.oss-cn-hangzhou.aliyuncs.com/img/2dfa992...

我们看到的确是生成了这个小猫的图片了。那么这个插件的测试也是OK了。

通过上面我们就实现一行代码都没有写就做出来一个基于qwen-image的一个dify插件了。呵呵是不是挺爽的。

3.打包插件

dify-plugin安装

我们之前是基于AI 开发的没有使用Dify 插件 CLI 工具,打包是要用到的,所以我先安装一下这个插件工具

Dify 插件 CLI 工具可以通过 Homebrew(在 Linux 和 macOS 上)或独立的二进制可执行文件(在 Windows、Linux 和 macOS 上)进行安装。

通过二进制可执行文件安装,下载github.com/langgenius/...

我这里就拿linux为案例,下载 `dify-plugin-linux-amd64 文件后,赋予其执行权限。

bash 复制代码
chmod +x ./dify-plugin-linux-amd64
mv ./dify-plugin-linux-amd64 ./dify

要检查安装是否成功,请运行 ./dify version,应该会显示版本代码。

bash 复制代码
./dify version

打包插件

当插件开发完成并通过本地测试后,您可以将其打包成一个 .difypkg 文件,用于分发或安装。

在打包之前我们需要修改一下manifest.yaml 文件和 /provider 路径下的 .yaml 文件中的 author 字段改成我的github名字wwwzhouhui

执行打包命令

bash 复制代码
./dify plugin package ./qwen_text2image

4.验证测试

插件包上传并安装

接下来我们把刚才打包好的插件包通过本地离线包的方式上传到dify平台对这个插件包进行验证性测试

打开我们本地或者私有化部署的dify平台,插件管理

在下拉选项中选择本地插件

选择这个刚才打包好的dify插件包上传到dify平台

点击安装后 插件在dify平台上实现安装了,我们稍等片刻。

​ svg的图片有点丑,大家可以自己找一个替换一下。

​ 因为这个文生图 需要调用魔搭社区Qwen-Image模型,所以我们需要授权一下。

工作流验证

接下来我们配置一个最简单工作流并使用这个插件来验证测试一下文生图的效果。(工作流制作这里就不做展开了。)

工作流dsl

yaml 复制代码
app:
  description: ''
  icon: 🤖
  icon_background: '#FFEAD5'
  mode: advanced-chat
  name: 自定义文生图插件验证测试-chatflow
  use_icon_as_answer_icon: false
dependencies:
- current_identifier: null
  type: package
  value:
    plugin_unique_identifier: wwwzhouhui/qwen_text2image:0.0.1@18eb2a22be7173a6bd806402b1748b3d7e9967acd87e1b4c5a6b794fa08fca0c
kind: app
version: 0.3.0
workflow:
  conversation_variables: []
  environment_variables: []
  features:
    file_upload:
      allowed_file_extensions:
      - .JPG
      - .JPEG
      - .PNG
      - .GIF
      - .WEBP
      - .SVG
      allowed_file_types:
      - image
      allowed_file_upload_methods:
      - local_file
      - remote_url
      enabled: false
      fileUploadConfig:
        audio_file_size_limit: 500
        batch_count_limit: 10
        file_size_limit: 100
        image_file_size_limit: 100
        video_file_size_limit: 500
        workflow_file_upload_limit: 10
      image:
        enabled: false
        number_limits: 3
        transfer_methods:
        - local_file
        - remote_url
      number_limits: 3
    opening_statement: ''
    retriever_resource:
      enabled: true
    sensitive_word_avoidance:
      enabled: false
    speech_to_text:
      enabled: false
    suggested_questions: []
    suggested_questions_after_answer:
      enabled: false
    text_to_speech:
      enabled: false
      language: ''
      voice: ''
  graph:
    edges:
    - data:
        isInIteration: false
        isInLoop: false
        sourceType: start
        targetType: tool
      id: 1755656337314-source-1755657278812-target
      source: '1755656337314'
      sourceHandle: source
      target: '1755657278812'
      targetHandle: target
      type: custom
      zIndex: 0
    - data:
        isInLoop: false
        sourceType: tool
        targetType: answer
      id: 1755657278812-source-answer-target
      source: '1755657278812'
      sourceHandle: source
      target: answer
      targetHandle: target
      type: custom
      zIndex: 0
    nodes:
    - data:
        desc: ''
        selected: false
        title: 开始
        type: start
        variables: []
      height: 53
      id: '1755656337314'
      position:
        x: 80
        y: 282
      positionAbsolute:
        x: 80
        y: 282
      selected: false
      sourcePosition: right
      targetPosition: left
      type: custom
      width: 244
    - data:
        answer: '{{#1755657278812.text#}}

          {{#1755657278812.files#}}

          '
        desc: ''
        selected: false
        title: 直接回复
        type: answer
        variables: []
      height: 123
      id: answer
      position:
        x: 740
        y: 282
      positionAbsolute:
        x: 740
        y: 282
      selected: true
      sourcePosition: right
      targetPosition: left
      type: custom
      width: 244
    - data:
        desc: ''
        is_team_authorization: true
        output_schema: null
        paramSchemas:
        - auto_generate: null
          default: null
          form: llm
          human_description:
            en_US: The text prompt to generate image from. Describe what you want
              to see in the image in detail. For example "A golden cat sitting on
              a red sofa in a cozy living room".
            ja_JP: The text prompt to generate image from. Describe what you want
              to see in the image in detail. For example "A golden cat sitting on
              a red sofa in a cozy living room".
            pt_BR: The text prompt to generate image from. Describe what you want
              to see in the image in detail. For example "A golden cat sitting on
              a red sofa in a cozy living room".
            zh_Hans: The text prompt to generate image from. Describe what you want
              to see in the image in detail. For example "A golden cat sitting on
              a red sofa in a cozy living room".
          label:
            en_US: Prompt
            ja_JP: Prompt
            pt_BR: Prompt
            zh_Hans: Prompt
          llm_description: Text prompt that describes the desired image content in
            detail. The more specific and descriptive, the better the generated image
            quality.
          max: null
          min: null
          name: prompt
          options: []
          placeholder: null
          precision: null
          required: true
          scope: null
          template: null
          type: string
        - auto_generate: null
          default: Qwen/Qwen-Image
          form: form
          human_description:
            en_US: The AI model to use for image generation. Qwen-Image is the default
              and recommended model.
            ja_JP: The AI model to use for image generation. Qwen-Image is the default
              and recommended model.
            pt_BR: The AI model to use for image generation. Qwen-Image is the default
              and recommended model.
            zh_Hans: The AI model to use for image generation. Qwen-Image is the default
              and recommended model.
          label:
            en_US: Model
            ja_JP: Model
            pt_BR: Model
            zh_Hans: Model
          llm_description: ''
          max: null
          min: null
          name: model
          options:
          - icon: ''
            label:
              en_US: Qwen-Image (Recommended)
              ja_JP: Qwen-Image (Recommended)
              pt_BR: Qwen-Image (Recommended)
              zh_Hans: Qwen-Image (Recommended)
            value: Qwen/Qwen-Image
          placeholder: null
          precision: null
          required: false
          scope: null
          template: null
          type: select
        params:
          model: ''
          prompt: ''
        provider_id: wwwzhouhui/qwen_text2image/modelscope
        provider_name: wwwzhouhui/qwen_text2image/modelscope
        provider_type: builtin
        selected: false
        title: Text to Image
        tool_configurations:
          model:
            type: constant
            value: Qwen/Qwen-Image
        tool_description: Generate high-quality images from text prompts using ModelScope
          Qwen-Image AI model. Support various image styles and detailed descriptions.
        tool_label: Text to Image
        tool_name: text2image
        tool_parameters:
          prompt:
            type: mixed
            value: '{{#sys.query#}}'
        type: tool
        version: '2'
      height: 121
      id: '1755657278812'
      position:
        x: 384
        y: 282
      positionAbsolute:
        x: 384
        y: 282
      selected: false
      sourcePosition: right
      targetPosition: left
      type: custom
      width: 244
    viewport:
      x: 34
      y: 87.5
      zoom: 1

一个简单的基于dify的文生图的插件就这么做完了。

说明:在实际AI生成代码中也不是一次性就能搞定的,中间也有一些错误,大家耐心和AI聊天就行了。有错误不担心就发给AI ,反正就和它聊,聊一聊这个插件就聊出来了。

dify工作流体验地址

工作流地址:dify.duckcloud.fun/chat/p9oMff...

5.后续

考虑到这个插件是本地打包上传开发的,我们后面希望分享到插件市场上给更多人使用,这样我们也可以参考docs.dify.ai/plugin-dev-... 把插件发布到Dify Marketplace,详细步骤这里就不做展开,大家看官方文档来操作就可以了。

6.总结

今天主要带大家了解并制定了基于 Dify 开发文生图插件的规范指南,该指南以 ModelScope 的 Qwen-Image 模型为基础,完整覆盖了插件开发的全流程。这份指南的搭建涉及多个关键环节:项目结构设计(明确 manifest、provider、tool 等核心文件的组织方式)、异步任务处理(实现任务提交、状态轮询、结果获取的标准化逻辑)、图像处理流程(基于 PIL 库的图像下载与格式转换)、错误处理机制(覆盖网络异常、API 错误、超时等各类场景)以及用户体验优化(提供实时进度反馈与友好提示)。

通过这套规范指南,开发者能够快速搭建符合 Dify 平台标准的文生图插件,同时具备良好的扩展性 ------ 小伙伴们可以基于此框架扩展更多 AI 生成类插件,如视频生成、图像风格转换等,进一步丰富 Dify 的功能场景。在实际验证中,遵循该指南开发的插件能够稳定对接 ModelScope API,实现从文本提示到图像生成的完整流程,有效解决了插件开发中异步任务处理、跨平台集成等常见问题。

感兴趣的小伙伴可以按照这份指南尝试开发自己的 Dify 文生图插件,甚至拓展更多创意功能。今天的分享就到这里结束了,我们下一篇文章见。

项目我已经放到github上面了,地址github.com/wwwzhouhui/... 觉得项目不错,麻烦点个赞。

相关推荐
一语长情13 分钟前
Netty流量整形:保障微服务通信稳定性的关键策略
java·后端·架构
冲鸭ONE13 分钟前
java数据类型与语句结构
后端
柯南二号27 分钟前
【后端】SpringBoot中HttpServletRequest参数为啥不需要前端透传
前端·spring boot·后端
MonKingWD1 小时前
MySQL事务篇-事务概念、并发事务问题、隔离级别
数据库·后端·mysql
华仔啊1 小时前
别学23种了!Java项目中最常用的6个设计模式,附案例
java·后端·设计模式
bobz9651 小时前
openstack nova ironic 架构图以及流程
后端
咕白m6251 小时前
C# 实现 PDF 转图片 - 分辨率设置、图片格式选择
后端·c#
Java水解1 小时前
深入理解 SQL 中的 COALESCE、NULLIF 和 IFNULL 函数
后端·sql
PetterHillWater1 小时前
开源知识库项目WeKnora技术拆解
后端·aigc