Qwen-Image-2512-ComfyUI API集成:Flask调用封装代码实例
1. 为什么需要把ComfyUI变成API服务
你有没有遇到过这样的情况:在ComfyUI界面里点点选选,生成一张图很顺手,但想把它嵌进自己的网页、小程序或者自动化流程里,就卡住了?ComfyUI默认是图形界面,不直接对外提供HTTP接口。而真实项目中,我们往往需要让图片生成功能“被调用”——比如电商后台批量生成商品图、内容平台自动配图、甚至接进企业微信机器人发图。
这时候,Qwen-Image-2512-ComfyUI这个镜像就很有价值:它已经预装了阿里最新版的Qwen-Image模型(2512版本),还集成了ComfyUI环境,4090D单卡就能跑起来。但光有镜像还不够,得让它“听懂”程序发来的请求。本文不讲怎么从零搭环境,也不重复部署步骤,而是聚焦一个工程师真正要落地的事:用Flask写一套轻量、稳定、可复用的API封装层,把ComfyUI变成你随时能POST调用的服务。
整套方案完全本地运行,不依赖云服务,代码简洁(不到150行),支持传入提示词、尺寸、采样步数等常用参数,返回生成图的URL或base64。你不需要改ComfyUI源码,也不用碰Node.js,只要会写Python,就能快速接入。
2. 环境准备与服务结构设计
2.1 镜像基础确认
你已按说明完成镜像部署:
- 在4090D单卡机器上拉起镜像;
- 运行
/root/1键启动.sh; - 通过“我的算力”进入 ComfyUI 网页端,确认工作流可正常出图。
这说明后端服务(ComfyUI的/prompt接口)已在本地http://127.0.0.1:8188运行。这是整个API封装的前提——我们不是重写推理,而是做一层“翻译+调度”。
2.2 Flask服务定位与职责划分
我们不替代ComfyUI,而是站在它肩膀上构建能力:
| 模块 | 职责 | 你不用管的 | 你需要控制的 |
|---|---|---|---|
| ComfyUI原生服务 | 执行节点计算、加载模型、渲染图像 | 模型权重路径、显存分配、节点逻辑 | 仅需确保其/prompt接口可访问 |
| Flask API层 | 接收HTTP请求 → 校验参数 → 构造ComfyUI Prompt JSON → 发送请求 → 轮询结果 → 返回图片 | 请求超时重试策略、日志格式、错误码定义 | 请求字段映射、默认值设定、响应结构 |
简单说:Flask是“前台接待员”,ComfyUI是“后台画师”。接待员负责听懂客户说什么(解析JSON)、填好工单(构造Prompt)、催进度(轮询)、最后把画作交到客户手上(返回图片)。
2.3 依赖安装与目录结构
在镜像内执行(建议在/root下新建qwen-api目录):
pip install flask requests pillow推荐目录结构如下(清晰、易维护):
qwen-api/ ├── app.py # 主服务入口 ├── workflow_api.json # 从ComfyUI导出的API工作流(关键!) ├── utils.py # 图片处理、路径管理等工具函数 └── static/ └── outputs/ # 自动生成的图片存放目录(ComfyUI默认输出路径)注意:
workflow_api.json不是手写的,而是从ComfyUI网页端导出的。打开内置工作流 → 点右上角「Queue Prompt」旁的「Save as API」→ 下载JSON文件,放入项目根目录。这是保证API调用和界面操作效果一致的核心。
3. 核心代码实现详解
3.1 工作流JSON的关键字段解析
打开你导出的workflow_api.json,找到类似这样的节点:
"6": { "inputs": { "text": "A cat wearing sunglasses, summer vibe", "clip": ["12", 1] }, "class_type": "CLIPTextEncode" }, "8": { "inputs": { "width": 1024, "height": 1024, "batch_size": 1 }, "class_type": "KSampler" }我们要动态替换的,就是这些inputs里的值。Flask收到请求后,会提取prompt、width、height等参数,精准注入到对应节点ID(如6、8)的inputs中。不是全文本替换,而是基于节点ID的字典级更新——这才是稳定可靠的做法。
3.2 Flask主服务(app.py)
# app.py from flask import Flask, request, jsonify, send_file import requests import json import time import os from pathlib import Path from utils import get_image_path, save_workflow_with_params app = Flask(__name__) # ComfyUI服务地址(镜像内默认) COMFYUI_URL = "http://127.0.0.1:8188" # 加载原始工作流模板 with open("workflow_api.json", "r", encoding="utf-8") as f: WORKFLOW_TEMPLATE = json.load(f) @app.route("/generate", methods=["POST"]) def generate_image(): try: data = request.get_json() prompt = data.get("prompt", "").strip() if not prompt: return jsonify({"error": "prompt is required"}), 400 # 提取参数,设置默认值 width = int(data.get("width", 1024)) height = int(data.get("height", 1024)) steps = int(data.get("steps", 30)) seed = int(data.get("seed", -1)) # 动态注入参数到工作流(核心逻辑) workflow = save_workflow_with_params( WORKFLOW_TEMPLATE, prompt=prompt, width=width, height=height, steps=steps, seed=seed ) # 发送Prompt请求 resp = requests.post( f"{COMFYUI_URL}/prompt", json={"prompt": workflow}, timeout=5 ) resp.raise_for_status() queue_resp = resp.json() prompt_id = queue_resp["prompt_id"] # 轮询获取结果(最多等待90秒) for _ in range(90): history = requests.get(f"{COMFYUI_URL}/history/{prompt_id}").json() if prompt_id in history and "outputs" in history[prompt_id]: output = list(history[prompt_id]["outputs"].values())[0] if "images" in output: img_info = output["images"][0] img_path = get_image_path(img_info) if os.path.exists(img_path): # 返回图片URL(假设Nginx已配置static代理) return jsonify({ "status": "success", "image_url": f"/static/outputs/{img_info['subfolder']}/{img_info['filename']}", "prompt_id": prompt_id }) time.sleep(1) return jsonify({"error": "timeout waiting for image"}), 504 except requests.exceptions.RequestException as e: return jsonify({"error": f"comfyui request failed: {str(e)}"}), 502 except Exception as e: return jsonify({"error": f"server error: {str(e)}"}), 500 if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)这段代码做了四件关键事:
- 安全校验:检查
prompt非空,参数类型转换防崩; - 精准注入:调用
save_workflow_with_params,只改目标节点,不动其他逻辑; - 异步等待:用
/history/{id}轮询,避免长连接阻塞; - 错误分层:网络失败返回502,超时返回504,代码异常返回500——便于前端区分处理。
3.3 参数注入工具(utils.py)
# utils.py import json import os from pathlib import Path def save_workflow_with_params(workflow, **kwargs): """根据节点ID,将参数注入到指定位置""" # CLIPTextEncode节点(通常ID为6或类似,按你导出的JSON调整) if "prompt" in kwargs and "6" in workflow: workflow["6"]["inputs"]["text"] = kwargs["prompt"] # KSampler节点(控制尺寸、步数、随机种子) if "8" in workflow: # 假设KSampler节点ID是8 if "width" in kwargs: workflow["8"]["inputs"]["width"] = kwargs["width"] if "height" in kwargs: workflow["8"]["inputs"]["height"] = kwargs["height"] if "steps" in kwargs: workflow["8"]["inputs"]["steps"] = kwargs["steps"] if "seed" in kwargs: workflow["8"]["inputs"]["seed"] = kwargs["seed"] # 其他节点可依此类推(如VAEDecode、SaveImage等) return workflow def get_image_path(img_info): """根据ComfyUI返回的image info,拼出本地绝对路径""" base_dir = "/root/comfyui/output" # ComfyUI默认输出目录 subfolder = img_info.get("subfolder", "") filename = img_info["filename"] return os.path.join(base_dir, subfolder, filename)关键提醒:节点ID(如
"6"、"8")必须和你导出的workflow_api.json中实际ID严格一致。打开JSON文件,搜索"class_type": "CLIPTextEncode"和"class_type": "KSampler",看它们前面的数字ID是多少,然后在utils.py里对应修改。这是唯一需要你手动核对的地方。
4. 启动服务与调用验证
4.1 启动Flask服务
在qwen-api/目录下执行:
nohup python app.py > api.log 2>&1 &服务将在http://你的服务器IP:5000/generate监听POST请求。
4.2 用curl测试(最简验证)
curl -X POST http://localhost:5000/generate \ -H "Content-Type: application/json" \ -d '{ "prompt": "a cyberpunk cityscape at night, neon lights, rain, cinematic", "width": 1024, "height": 576, "steps": 25 }'成功响应示例:
{ "status": "success", "image_url": "/static/outputs/ComfyUI_2024-06-15/IMG_00001.png", "prompt_id": "abc123def456" }4.3 前端调用示例(JavaScript)
async function callQwenAPI(prompt) { const res = await fetch("http://your-server-ip:5000/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: prompt, width: 1024, height: 1024 }) }); const data = await res.json(); if (data.status === "success") { document.getElementById("result-img").src = data.image_url; } }5. 实用技巧与避坑指南
5.1 如何让生成图直接返回base64(免静态服务)
如果你不想配Nginx代理静态文件,想让API直接返回图片数据,只需修改app.py中成功分支:
# 替换原来的 jsonify(...) 部分 with open(img_path, "rb") as f: img_data = f.read() import base64 encoded = base64.b64encode(img_data).decode("utf-8") return jsonify({ "status": "success", "image_base64": f"data:image/png;base64,{encoded}", "prompt_id": prompt_id })这样前端拿到base64字符串,直接赋给<img src="...">即可显示,零配置。
5.2 多模型切换支持(扩展思路)
Qwen-Image-2512只是起点。ComfyUI支持加载多个模型。你可以在workflow_api.json中,把模型加载节点(如CheckpointLoaderSimple)的ckpt_name字段,改为一个变量占位符,然后在save_workflow_with_params里根据请求参数动态替换:
# 在workflow中预留 "4": { "inputs": { "ckpt_name": "{{model_name}}" }, "class_type": "CheckpointLoaderSimple" } # 注入时 if "model_name" in kwargs: workflow["4"]["inputs"]["ckpt_name"] = kwargs["model_name"]再配合一个/models接口返回可用模型列表,就能做成真正的多模型API网关。
5.3 常见问题速查
Q:调用返回502,但ComfyUI网页能出图?
A:检查COMFYUI_URL是否正确(必须是127.0.0.1:8188,不能写localhost或外网IP);确认/root/comfyui目录下output子目录有写入权限。Q:轮询一直超时,history里没outputs?
A:打开ComfyUI网页,看右下角WebSocket状态是否绿色;检查workflow_api.json中SaveImage节点的filename_prefix是否为ComfyUI(默认值),否则路径拼接会失败。Q:中文提示词乱码或不生效?
A:确保workflow_api.json中CLIPTextEncode节点的text字段是UTF-8编码;Flask接收JSON时加request.get_json(force=True)强制解码。
6. 总结
本文带你走通了一条从“能用”到“好用”的关键路径:Qwen-Image-2512-ComfyUI镜像本身解决了模型和环境问题,而Flask API封装则解决了工程集成问题。你学到的不是一段孤立代码,而是一种可复用的方法论——
- 不侵入原系统:所有改动都在API层,ComfyUI保持原样;
- 强可维护性:参数注入逻辑集中,增删字段只需改
utils.py; - 真生产就绪:包含超时控制、错误分类、路径安全校验;
- 平滑可扩展:base64返回、多模型支持、鉴权接入,都只需几行代码。
下一步,你可以把这套API注册进你的内部服务发现系统,加上Prometheus监控,再用Swagger生成文档——Qwen-Image就真正成为你技术栈里一个标准的AI能力模块了。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。