社区版Dify 利用【Python代码模块】实现本地ComfyUI Flux 文生图
一、Dify 安装和专栏的以往文章推荐
- Dify安装时会遇到的网络问题,已成功安装Dify教程
- Dify 部署LLM 可以参考这里,Dify实现Ollama3.2-vision多模态聊天
- 并且欢迎关注我的 社区版 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 开发专栏