社区版Dify 利用【Python代码节点】实现本地 ComfyUI (包括Flux) 文生图

一、Dify 安装和专栏的以往文章推荐

  1. Dify安装时会遇到的网络问题,已成功安装Dify教程
  2. Dify 部署LLM 可以参考这里,Dify实现Ollama3.2-vision多模态聊天
  3. 并且欢迎关注我的 社区版 Dify 开发专栏

二、回顾Dify+LLM+ComfyUI文生图教程与缺点

我在这篇文章这里详细介绍了如何实现在Dify 实现文生图。回顾一下我实现的步骤:
4. 安装Linux ComfyUI
5. 安装Ollama 部署本地LLM
6. 在Dify 接入四个节点:开始、LLM、Dify(0.14版本)自带的Text2IMG工具节点、回复
在这里插入图片描述
为什么不直接在上面这个TEXT2IMG节点上直接输入Flux模型实现文生图?
因为不奏效了!我在这里实测这里只支持SD 模型输入!另外,这个节点还是有很多不好的地方,例如,不能全盘把我整个工作流的进程。
因此,我研究了一下,利用python代码就可是实现另一种基于ComfyUI,包括FLUX模型实现文生图。

三、Dify 利用【Python代码模块】实现本地ComfyUI (Flux)文生图的第一性原理。

我在研究Dify 和ComfyUI 过程中发现了两点重要的可行技术:

1. Dify 的代码模块可以支持自己写 requests 请求API

在这里插入图片描述
我在代码执行模型的测试代码是:

import requests
from typing import Dict
def main() -> Dict[str, int]:
    response = requests.get('https://www.baidu.com')
    return { "status_code": str(response.status_code),}

这就证明了可以通过Dify的代码执行去调用requests API,我觉得这是Dify 一个非常好的地方,因为这给开发者提供了更多自定义的玩法!!!

2. ComfyUI 可以导出为JSON文件,可以利用代码请求整个JSON实现文生图。

如果你还是ComfyUI 的初学者,你需要知道,ComfyUI 的页面工作流是给你调试的,真正地使用它,是可以通过代码来实现调用它!
在你结束你的绘画模型和工作流设计之后,你可以轻松地导出JSON文件,描述你的工作流!让我们回到最初的起点模板,在工作流,导出API,即可得到一个JSON文件:
在这里插入图片描述
第一性原理,既然ComfyUI支持导出JSON ,并且支持利用网络请求ComfyUI 后台来实现文生图,那么一定可以实现这样一个功能:
输入:prompt和尺寸等要求,
输出:一张要画好的图。
(Flux 工作流也只是一个工作流而已,一定也可以实现!)

下面就来详细介绍Dify上直接调用ComfyUI JSON 实现文生图。

四、Dify 利用【Python代码节点】实现本地 ComfyUI Flux 文生图 过程

第一步:Python 调用ComfyUI 的JSON API 实现文生图

这是导出的API
在这里插入图片描述
我找了一个代码,稍微修改了下,具体来说,就是修改JSON文件里面的Prompt等关键因素,让它调用API生成图。返回的是一个可以下载的链接!
为什么我返回链接而不是直接一个图?因为给Dify调用呀,而不是直接上存一个文件给它。

import websocket  # NOTE: 需要安装 websocket-client (https://github.com/websocket-client/websocket-client)
import uuid
import json
import urllib.request
import urllib.parse
from PIL import Image
import io
import random
def queue_prompt(prompt):
    """
    向服务器队列添加生成提示。
    参数:
        prompt (dict): 提示的 JSON 数据。
    返回:
        dict: 包含 prompt_id 的响应数据。
    """
    try:
        payload = {"prompt": prompt, "client_id": CLIENT_ID}
        data = json.dumps(payload).encode('utf-8')
        url = f"http://{SERVER_ADDRESS}/prompt"
        req = urllib.request.Request(url, data=data)
        response = urllib.request.urlopen(req)
        return json.loads(response.read())
    except Exception as e:
        print(f"Error in queue_prompt: {e}")
        return None


def get_image(filename, subfolder, folder_type):  # 获取单张图像
    """
    从服务器下载图像数据。

    参数:
        filename (str): 图像文件名。
        subfolder (str): 子文件夹路径。
        folder_type (str): 文件夹类型。

    返回:
        bytes: 图像的二进制数据。
    """
    try:
        params = urllib.parse.urlencode({"filename": filename, "subfolder": subfolder, "type": folder_type})
        url = f"http://{SERVER_ADDRESS}/view?{params}"
        print(f"Fetching image from: {url}")
        return url
    except Exception as e:
        print(f"Error in get_image: {e}")
        return None


def get_history(prompt_id):
    """
    获取指定提示 ID 的生成历史记录。
    参数:
        prompt_id (str): 提示的唯一 ID。
    返回:
        dict: 包含历史记录的 JSON 数据。
    """
    try:
        url = f"http://{SERVER_ADDRESS}/history/{prompt_id}"
        with urllib.request.urlopen(url) as response:
            return json.loads(response.read())
    except Exception as e:
        print(f"Error in get_history: {e}")
        return None


def get_images(ws, prompt):
    """
    通过 WebSocket 接收消息并下载生成的图像。
    参数:
        ws (websocket.WebSocket): WebSocket 连接对象。
        prompt (dict): 提示的 JSON 数据。
    返回:
        dict: 包含生成的图像数据,按节点 ID 分类。
    """
    try:
        prompt_response = queue_prompt(prompt)
        if not prompt_response:
            return {}
        prompt_id = prompt_response['prompt_id']

        # 等待生成过程完成
        while True:
            out = ws.recv()
            if isinstance(out, str):
                message = json.loads(out)
                if message.get('type') == 'executing':
                    data = message['data']
                    if data.get('node') is None and data.get('prompt_id') == prompt_id:
                        break  # 执行完成

        # 获取生成历史记录
        history = get_history(prompt_id)
        print("history", history)

        #  获取图像的url 给dify 下载
        for node_id, node_output in history[prompt_id]['outputs'].items():
            if 'images' in node_output:
                for image in node_output['images']:
                    image_data = get_image(image['filename'], image['subfolder'],
                                           image['type'])  # 这一步是获取图像下载的URL image['filename'] 是文件名称,支持多张生成
                    if image_data:
                        return image_data

    except Exception as e:
        print(f"Error in get_images: {e}")
        return {}
# ComfyUI JSON 可以修改的参数
def update_prompt_from_file(filepath, text_prompt, noise_seed, width, heigth):
    """
    从文件加载 JSON 并更新提示信息。

    参数:
        filepath (str): JSON 文件路径。
        text_prompt (str): 新的文本提示。
        noise_seed (int): 随机种子值。

    返回:
        dict: 更新后的 JSON 数据。
    """
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            prompt = json.load(f)

        prompt["3"]["inputs"]["seed"] = noise_seed
        prompt["5"]["inputs"]["width"] = width
        prompt["5"]["inputs"]["heigth"] = heigth
        prompt["6"]["inputs"]["text"] = text_prompt
        return prompt
    except Exception as e:
        print(f"Error in update_prompt_from_file: {e}")
        return None
def generate_random_15_digit_number():
    """
    生成一个 15 位的随机数。

    返回:
        int: 15 位随机数。
    """
    return random.randint(10 ** 14, 10 ** 15 - 1)

# 主程序
if __name__ == "__main__":

	# 设置服务器地址和客户端 ID
	SERVER_ADDRESS = "your_ComfyUI_Server_addr:8188"
	CLIENT_ID = str(uuid.uuid4())  # 防止不同客户端的请求混淆。
    # JSON 文件路径和提示信息
    json_filepath = "Image_Generation.json"
    text_prompt = "a dog"
    noise_seed = generate_random_15_digit_number()

    # 加载并更新提示
    prompt_json = update_prompt_from_file(json_filepath, text_prompt, noise_seed, width=512, heigth=512)

    if not prompt_json:
        print("Failed to load or update the prompt.")
        exit()

    # 创建 WebSocket 连接
    try:
        ws = websocket.WebSocket()
        ws.connect(f"ws://{SERVER_ADDRESS}/ws?clientId={CLIENT_ID}")

        # 获取图像
        url = get_images(ws, prompt_json)
        print(url)


    except Exception as e:
        print(f"Error in WebSocket connection: {e}")

    finally:
        ws.close()

主要是以下这部分修改就好,因为每个工作流编号不一样:

# 可以修改json的参数,就实现了文生图
def update_prompt_from_file(filepath, text_prompt, noise_seed, width, heigth):
    """
    从文件加载 JSON 并更新提示信息。

    参数:
        filepath (str): JSON 文件路径。
        text_prompt (str): 新的文本提示。
        noise_seed (int): 随机种子值。

    返回:
        dict: 更新后的 JSON 数据。
    """
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            prompt = json.load(f)

        prompt["3"]["inputs"]["seed"] = noise_seed
        prompt["5"]["inputs"]["width"] = width
        prompt["5"]["inputs"]["heigth"] = heigth
        prompt["6"]["inputs"]["text"] = text_prompt
        return prompt
    except Exception as e:
        print(f"Error in update_prompt_from_file: {e}")
        return None

结果返回一个链接地址,点进去即可打开这张生成的图像:

http://127.0.0.1:8188/view?filename=ComfyUI_00373_.png&subfolder=&type=output

在这里插入图片描述
你可以去看ComfyUI 后台 (别惊讶,A800 一秒多就出图了):
在这里插入图片描述

第二步 封装第一步的代码,为另一个requests API

给代码你参考,把第一步的def 函数复制过来:

from flask import Flask, request, jsonify
import websocket
import uuid
import json
import urllib.request
import urllib.parse
import random

app = Flask(__name__)

# 设置服务器地址
SERVER_ADDRESS = "你的地址:8188"
CLIENT_ID = str(uuid.uuid4())

--------
请复制第一步的def...函数到这里
--------
# 以下是封装代码!
@app.route('/generate_image', methods=['POST'])
def generate_image():
    data = request.json
    text_prompt = data.get('text_prompt')
    width = data.get('width', 512)

    if not text_prompt:
        return jsonify({"error": "text_prompt is required"}), 400

    json_filepath = "flux_dev_aodebiao.json"
    noise_seed = generate_random_15_digit_number()

    # 更新提示
    prompt_json = update_prompt_from_file(json_filepath, text_prompt, noise_seed, width)
    if not prompt_json:
        return jsonify({"error": "Failed to update prompt"}), 500

    try:
        ws = websocket.WebSocket()
        ws.connect(f"ws://{SERVER_ADDRESS}/ws?clientId={CLIENT_ID}")
        url = get_images(ws, prompt_json)
        if url:
            return jsonify({"image_url": url})
        else:
            return jsonify({"error": "Failed to generate image"}), 500
    except Exception as e:
        print(f"Error in WebSocket connection: {e}")
        return jsonify({"error": "WebSocket connection failed"}), 500
    finally:
        ws.close()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

简单起见,我的代码只封装了prompt,和width,自动随机数。这部分其实很灵活,每个人导出的JSON都不一样,需求也不一样,你们封装的时候可以交给GPT。这部分代码只供参考。

调用我封装的API:

import requests
import json

# 服务器地址
url = "http://localhost:5000/generate_image"

# 请求数据
data = {
    "text_prompt": "a dog and a cat",
    "width": 512
}

# 发送 POST 请求并传递 JSON 数据
response = requests.post(url, json=data)
print(eval(response.text))
url = eval(response.text)["image_url"]
print(url)

可以直接输出我的图像URL!

第三步 Dify 运行请求第二部封装好API 调用本地的ComfyUI JSON 工作流生成图像链接

在这里插入图片描述
这里Dify代码块代码为:

import requests
import json
from typing import Dict

def main(prompt) -> Dict[str, str]:
    # 服务器地址
    url = "http://地址:22311/generate_image"

    # 请求数据
    data = {
        "text_prompt": prompt,
        "width": 1024
    }

    # 发送 POST 请求并传递 JSON 数据
    response = requests.post(url, json=data)
    
    if response.status_code == 200:
        result = eval(response.text)["image_url"]
        return {'result': result}
    else:
        return {'error': 'Request failed with status code {}'.format(response.status_code)}

想中文输入可以看我之前的教程。
在这里插入图片描述

第四步,直接输出图像到聊天框,非常简单!

在第三步基础上加上一个HTTP请求节点即可!,对图像的URL就可以得到file 输出了。
在这里插入图片描述

最后,等等,标题说的Flux去哪了?不会骗人的吧?

咳咳,以上的模型就是Flux,我的Flux 长这样:
在这里插入图片描述
Flux怎么安装?下次再更新了,我在上面说了,这个方法可以实现任意ComfyUI的工作流,Flux 也只是个工作流而已!
以下内容属于个性输出,无需参考,本人每次体验文生图模型,第一句prompt都是“月黑风高的夜晚,一只孤狼在仰天长啸” 以下是Flux的杰作:
在这里插入图片描述

欢迎关注我的 社区版 Dify 开发专栏

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值