环境全套部署 Ollama+ComfyUI+Python
学习目标
- 理解整套系统数据流:文本大模型生成指令 → Python 调度 → ComfyUI 执行绘图 → 结果保存输出
- 完成 Ollama、ComfyUI、Python 依赖全套本地部署
- 理解端口作用:Ollama
11434、ComfyUI8188 - 编写连通检测脚本,验证两个服务是否正常可用
课程内容
1. 课程体系整体架构:Python+Ollama+ComfyUI 工作流逻辑拆解
整套 AI 自动化工作流链路:
- 用户输入简单自然语言描述
- Ollama 本地大模型:负责理解、优化提示词、生成文案、结构化输出;
- Python:作为中间调度层,调用 Ollama API 拿到提示词,再调用 ComfyUI API 下发绘图任务;
- ComfyUI:接收参数,执行文生图 / 图生图、采样、模型推理;
- 图片产出后,Python 获取生成结果,本地保存、归档。
核心特点:全部本地运行,不需要云端 API 密钥,数据不出本机。
2. Ollama 本地安装、环境变量配置、跨版本兼容处理
- 官网下载对应系统安装包完成安装;Windows 安装后会自动注册系统服务,后台常驻。
- 环境变量说明
OLLAMA_HOST:修改监听地址,默认127.0.0.1,如需局域网访问改为0.0.0.0OLLAMA_NUM_PARALLEL:控制并发推理数量,低配机器建议设为 1OLLAMA_MAX_LOADED_MODELS:控制同时驻留内存的模型数量
- 跨版本兼容:新版本 Ollama 部分接口字段变更,Python SDK 版本尽量和 Ollama 主版本匹配;旧模型可以直接继续使用,不需要重新下载。
3. Ollama 常用模型拉取:llama3、qwen、mistral(适配文案、指令优化场景)
终端命令拉取模型:
ollama pull qwen3
ollama pull llama3
ollama pull mistral
- qwen3:中文能力优秀,适合提示词改写、文案生成,本课程主力模型
- llama3:通用能力强,英文提示词质量高
- mistral:速度快,硬件要求低,低配电脑首选
测试对话(终端)
ollama run qwen3
4. ComfyUI 完整部署:源码安装、依赖修复、启动报错解决
打开全世界最大的同性交友网站 github 下载 ComfyUI 最新版 v0.34.0 的ComfyUI_windows_portable_nvidia.7z 解压,路径不要有中文、空格。
https://github.com/Comfy-Org/ComfyUI

- 双击运行
run_nvidia_gpu.bat,如果报错则运行 run_cpu.bat,直到看到如下日志并打开浏览器。黑窗口不要关闭;浏览器自动打开http://127.0.0.1:8188如果浏览器没弹出,手动复制地址打开。
bash
[INFO] Context impl SQLiteImpl.
[INFO] Will assume non-transactional DDL.
[INFO] Using RAM pressure cache.
[INFO] Starting server
[INFO] To see the GUI go to: http://127.0.0.1:8188

官方便携包不带任何 SD 大模型,必须自己下载 Checkpoint 模型,否则下拉框是空,无法生成图。CPU 模式生成很慢,512×512 一张图可能几分钟,内存建议≥12G。
下载 SD1.5 模型:v1‑5‑pruned‑emaonly.safetensors,放到ComfyUI_windows_portable\ComfyUI\models\checkpoints 文件夹下,重启 run_cpu.bat;或者界面按R刷新模型列表ComfyUI。

点击软件图标打开最简单的文生图工作流json文件。

文生图工作流文件内容如下,创建一个记事本名为最简单的文生图工作流,内容如下并将文件后缀改为json:
javascript
{
"3": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "v1-5-pruned-emaonly-fp16.safetensors"
}
},
"4": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "masterpiece, best quality, 1girl, sunset, beautiful face",
"clip": ["3", 1]
}
},
"5": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "lowres, blurry, bad anatomy, deformed, ugly",
"clip": ["3", 1]
}
},
"6": {
"class_type": "EmptyLatentImage",
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
}
},
"7": {
"class_type": "KSampler",
"inputs": {
"model": ["3", 0],
"positive": ["4", 0],
"negative": ["5", 0],
"latent_image": ["6", 0],
"seed": 12345,
"steps": 20,
"cfg": 7,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1.0
}
},
"8": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["7", 0],
"vae": ["3", 2]
}
},
"9": {
"class_type": "SaveImage",
"inputs": {
"images": ["8", 0]
}
}
}
如果模型名不对,切换自己安装的模型名,点击右上角运行即可生成图片。

5. Python 环境配置:安装 ollama、requests、json、pillow 等核心依赖库
json 属于 Python 标准库,无需 pip 安装
pip install ollama requests pillow
- ollama:Ollama 官方 Python SDK,调用本地大模型
- requests:http 请求,用来调用 ComfyUI REST API
- pillow:图片读取、保存、预处理
6. 本地服务端口讲解:Ollama 默认 11434、ComfyUI 默认 8188 端口原理
- Ollama:11434 :http 服务端口,所有大模型请求都走这个端口;访问
http://127.0.0.1:11434,服务正常会返回提示。 - ComfyUI:8188 :WebUI 与 API 服务端口;网页界面、Python 提交绘图任务都访问该端口;访问
http://127.0.0.1:8188打开 ComfyUI 网页。
Python 本质就是通过 http 向这两个端口发送请求完成调度。
实操案例
实操 1:终端命令部署 Ollama,成功拉取 qwen3 大模型,本地运行测试对话
- 安装 Ollama;打开终端
bash
# 拉取模型
ollama pull qwen3.5:4b
# 进入交互式对话
ollama run qwen3.5:4b
输入:帮我写一段AI绘画正向与反向提示词,赛博朋克城市雨夜,观察返回结果。输入 /bye 退出对话。
实操 2:启动 ComfyUI,熟悉界面布局、节点面板、工作流保存 / 加载功能
- 启动 ComfyUI,浏览器打开
http://127.0.0.1:8188 - 界面认识:
- 左侧:节点菜单;中间画布;队列生成按钮;
Save:保存工作流 json;Load:加载外部工作流 json
- 随便拖拽几个节点,保存工作流到本地,再重新 Load 导入,验证文件可用。
实操 3:Python 脚本测试连通性,分别检测 Ollama、ComfyUI 服务是否正常启动
完整可运行检测脚本
check_services.py
py
import requests
OLLAMA_URL = "http://127.0.0.1:11434"
COMFYUI_URL = "http://127.0.0.1:8188"
def check_ollama():
try:
resp = requests.get(OLLAMA_URL, timeout=3)
if resp.status_code == 200:
print("✅ Ollama服务正常,端口11434")
return True
except Exception:
print("❌ Ollama服务未启动,请运行Ollama程序")
return False
def check_comfyui():
try:
resp = requests.get(COMFYUI_URL, timeout=3)
if resp.status_code == 200:
print("✅ ComfyUI服务正常,端口8188")
return True
except Exception:
print("❌ ComfyUI服务未启动,请执行启动脚本")
return False
if __name__ == "__main__":
print("===== 服务连通检测 =====")
check_ollama()
check_comfyui()
运行脚本,分别测试两个服务开启 / 关闭状态下输出提示。
课后练习
练习 1:完整重装一遍全套环境,记录所有报错及解决方案(形成个人排错手册)
建议建立文档
day1_error_log.md,记录:
- 报错原文
- 触发条件
- 修复步骤
重点记录:端口占用、pip 依赖失败、显存报错、模型拉取慢等问题。
练习 2:通过 Ollama 终端命令,完成 5 次不同场景的文本生成(文案、提问、总结)
终端执行 ollama run qwen3,完成 5 类任务:
- 短视频带货文案
- 科普问题问答
- 一段文章摘要总结
- AI 绘画正向提示词生成
- 简单故事创作
练习 3:Python 编写检测脚本,自动判断两大服务是否运行,未运行则给出提示
基于上面的check_services.py做扩展:
- 如果两个服务全部正常,打印
系统就绪,可以开始调用 - 如果任意一个服务异常,打印哪些服务缺失;
- 设置超时时间,防止卡死;
- 返回布尔值,可以给后续程序调用判断是否继续执行。
参考扩展逻辑:
py
def all_services_ok():
o_ok = check_ollama()
c_ok = check_comfyui()
return o_ok and c_ok
if __name__ == "__main__":
if all_services_ok():
print("\n🎉 全部服务就绪,可以进行AI任务")
else:
print("\n⚠️ 存在服务未启动,请启动后重试")
Python 基础调用 Ollama
学习目标
- 理解 Ollama Python SDK 本地 API 调用原理,掌握核心参数作用
- 掌握普通生成、流式输出、多轮对话代码编写
- 学会解析返回数据,提取有效文本
- 增加基础异常捕获,处理服务未启动、模型不存在、超时等问题
课程内容
1、Ollama Python SDK 核心原理:本地 API 调用机制、无需外网请求
Ollama 安装完成后会在本机启动 HTTP 服务,监听127.0.0.1:11434。
- ollama python SDK 本质就是对本地 HTTP 接口做封装,所有请求都在本机完成,不需要公网、不需要 API‑key。
- 数据不经过第三方服务器,推理全部消耗本机 CPU/GPU 资源。
- 两种调用方式:①官方 ollama 库;②直接 requests 请求原生 http 接口。本课程优先使用官方 SDK。
前置依赖安装(第 1 天已完成)
pip install ollama requests
2、Ollama 核心参数详解:model、prompt、temperature、max_tokens、stream 流式输出
表格
| 参数 | 作用 |
|---|---|
| model | 指定使用的模型名称,例如qwen3、llama3 |
| prompt | 用户输入提示词,要大模型处理的指令 |
| temperature | 温度,0~1;越低输出越严谨确定;越高创造性越强 |
| max_tokens | 最大输出 token,限制返回文本长度 |
| stream | True开启流式返回,逐块拿到结果;False一次性返回全部结果 |
3、基础文本生成、单次问答、多轮对话代码实现
- 单次文本生成:直接传入 prompt,获取完整返回结果。
- 流式生成:stream=True,循环迭代输出片段,实现打字机效果。
- 多轮对话:使用 messages 数组结构,携带历史上下文,实现记忆对话。
py
import ollama
# 单次问答
resp = ollama.generate(model="qwen3", prompt="写一句朋友圈文案")
print(resp.response)
# 多轮对话格式 messages
messages = [
{"role":"user","content":"介绍一下AI绘画"},
{"role":"assistant","content":"AI绘画是人工智能生成图像技术"},
{"role":"user","content":"有哪些工具可以使用"}
]
resp = ollama.chat(model="qwen3", messages=messages)
print(resp.message.content)
4、返回结果解析:提取纯文本内容、过滤冗余参数信息
ollama 返回是对象,内部包含大量元数据:模型名、耗时、token 数量等。
ollama.generate()→ 有效文本:resp.responseollama.chat()→ 有效文本:resp.message.content
❌不要直接 print 整个返回对象,会打印大量无关调试信息,业务代码只提取需要的文本字段。
5、基础异常处理:服务未启动、模型不存在、超时报错解决
常见异常类型:
ConnectionError:Ollama 服务没有启动,端口连不上ollama.ResponseError:模型不存在、模型拉取失败TimeoutError:推理超时,硬件性能不足导致
使用try‑except捕获对应异常,给出友好提示而不是直接抛出堆栈。
实操案例
实操 1:Python 调用 Ollama 实现单句文案生成(短视频文案、朋友圈文案)
文件:day2_01_simple_gen.py
py
import ollama
def generate_copy(prompt_text:str):
res = ollama.generate(
model="qwen3",
prompt=prompt_text,
temperature=0.7,
max_tokens=200
)
return res.response
if __name__ == "__main__":
text1 = generate_copy("写一条治愈风朋友圈文案")
print("朋友圈文案:\n", text1)
text2 = generate_copy("写一条短视频好物分享开头文案")
print("\n短视频文案:\n", text2)
实操 2:实现流式输出对话,模拟 ChatGPT 实时打字效果
文件:day2_02_stream_chat.py
py
import ollama
def stream_generate(prompt_text):
stream = ollama.generate(
model="qwen3",
prompt=prompt_text,
stream=True,
temperature=0.6
)
print("输出:", end="", flush=True)
for chunk in stream:
piece = chunk.response
print(piece, end="", flush=True)
print()
if __name__ == "__main__":
stream_generate("简单介绍什么是Ollama本地大模型")
实操 3:自定义温度参数,对比创意型、严谨型文本生成效果差异
文件:day2_03_temp_compare.py
py
import ollama
prompt = "给我生成3个奇幻故事的简短小标题"
# low temp:严谨、重复少、创造力低
resp_low = ollama.generate(model="qwen3", prompt=prompt, temperature=0.1)
# high temp:创意强、随机性大
resp_high = ollama.generate(model="qwen3", prompt=prompt, temperature=1.0)
print("=== temperature=0.1(严谨)===")
print(resp_low.response)
print("\n=== temperature=1.0(高创意)===")
print(resp_high.response)
运行观察对比两者输出差异,理解 temperature 参数对生成效果的影响。
课后练习
练习 1:编写通用问答脚本,支持用户输入任意问题,调用 Ollama 返回精准答案
需求:
- 控制台循环接收用户输入问题;输入 quit 退出程序
- 将用户输入作为 prompt 传给 Ollama
- 打印返回答案
练习 2:封装文本生成函数,可自定义模型、创意度、生成字数
函数接口参考:
py
def llm_generate(prompt:str, model:str="qwen3", temp:float=0.7, max_tokens:int=300)->str:
"""
:param prompt: 用户提示词
:param model: 使用模型名称
:param temp: temperature温度
:param max_tokens:最大输出token
:return:返回生成文本字符串
"""
# 在这里实现代码
练习 3:处理异常场景,服务断开时自动提示 "请启动 Ollama 服务"
给上面封装的llm_generate()函数增加异常捕获:
- 捕获连接异常,打印提示:
请启动Ollama服务 - 捕获模型不存在异常,打印提示:
模型不存在,请先执行ollama pull拉取对应模型 - 出现异常时返回空字符串,方便上层调用判断
参考异常捕获模板:
py
import ollama
try:
resp = ollama.generate(model=model, prompt=prompt, temperature=temp, max_tokens=max_tokens)
return resp.response
except Exception as e:
if "connection" in str(e).lower():
print("请启动Ollama服务")
elif "model" in str(e).lower():
print("模型不存在,请先执行ollama pull拉取对应模型")
return ""
测试方法:关闭 Ollama 软件,运行脚本,验证是否输出友好提示,而不是直接抛出报错堆栈。
Ollama 高阶文本能力
学习目标
- 掌握 AI 绘画正向 / 反向提示词编写规范,学会写高质量 Prompt
- 掌握 Ollama 强制 JSON 结构化输出,便于程序解析数据
- 实现文本改写、风格转换、批量生成提示词
- 理解不同模型在提示词优化任务上的选型差异
- 能够输出可直接供给 ComfyUI 使用的标准化提示词
课程内容
1、Prompt 工程基础:AI 绘图提示词优化逻辑、正向 / 反向提示词规范
- 正向提示词:描述希望画面出现的元素:主体、构图、光影、材质、风格、画质词(8k,超细节,胶片质感)
- 反向提示词:描述不希望出现的内容:畸形手、模糊、水印、文字、低分辨率、变形、噪点等瑕疵。
提示词优化逻辑:
用户简单口语描述 → 大模型扩充细节、增加画质修饰词 → 分离正向、反向提示词 → 输出给 ComfyUI。
书写规范:
- 关键词用逗号分隔;
- 优先主体,再风格,再光影,最后画质修饰;
- 反向提示词固定通用瑕疵词,减少画面崩坏。
示例:
口语:一个女孩站在樱花树下
优化正向:少女,站在盛开的樱花树下,春日柔光,胶片摄影,细腻皮肤,8k,高清细节,氛围感
反向:畸形,手指残缺,模糊,水印,文字,低画质,变形
2、Ollama 结构化输出:强制 JSON 格式返回,适配程序自动化读取
Ollama 支持指定输出格式 format="json",强制大模型返回合法 JSON 字符串,避免返回多余自然语言描述,Python 可以直接json.loads()解析。
关键点:必须在 Prompt 中明确告诉模型 JSON 字段结构,配合
format="json"参数,否则容易输出非法 JSON。
示例 prompt 模板:
你是AI绘图提示词生成助手,严格只返回JSON,不要额外解释。
输出字段:
{
"subject":"画面主题",
"positive":"正向提示词",
"negative":"反向提示词",
"style":"绘画风格",
"size":"画面分辨率"
}
输入描述:{用户输入}
3、文本改写、精简、扩写、风格转换(治愈、赛博、国风、简约)
大模型可以按照指令完成文本变换:
- 扩写:把简单描述扩充丰富细节;
- 精简:压缩冗余描述,保留核心关键词;
- 风格迁移:同一主体切换国风、赛博朋克、日系治愈、极简写实等。
核心思路:在 prompt 里显式指定输出要求,例如:
将下面画面描述转换为国风绘画风格提示词,扩充光影、构图、材质细节。
4、批量文本生成逻辑:循环调用模型、参数批量迭代
批量任务基础逻辑:
- 准备输入列表(主题列表、描述列表);
- for 循环遍历每一条输入;
- 循环内调用 Ollama 生成提示词;
- 收集结果,保存内存 / 写入本地文件;
注意:循环之间增加短暂延时,防止短时间大量请求压垮 Ollama 服务。
5、模型选型技巧:不同大模型适配绘图提示词优化的优劣对比
表格
| 模型 | 特点 | 适用场景 |
|---|---|---|
| qwen3 | 中文强,JSON 输出稳定,提示词扩充质量好 | 课程首选,中文提示词生成、结构化输出 |
| llama3 | 英文提示词质量高,中文略弱 | 生成欧美风格、英文提示词 |
| mistral | 速度快,资源占用低;JSON 容易出错 | 低配机器,简单文本生成,不适合复杂 JSON |
提示词 + JSON 结构化任务优先选择 qwen3。
实操案例
实操 1:利用 Ollama 将普通口语化文案,优化为专业 AI 绘图正向提示词
文件:day3_01_prompt_optimize.py
import ollama
def optimize_prompt(raw_desc: str):
prompt = f"""
你是AI绘画提示词专家,把用户口语描述扩写成专业正向提示词,增加风格、光影、画质细节,只输出正向提示词,不要多余文字。
用户描述:{raw_desc}
"""
resp = ollama.generate(
model="qwen3",
prompt=prompt,
temperature=0.6
)
return resp.response
if __name__ == "__main__":
raw = "一个小猫坐在窗台看月亮"
res = optimize_prompt(raw)
print("原始描述:", raw)
print("优化后正向提示词:\n", res)
实操 2:强制模型以 JSON 格式输出(包含主题、正向词、反向词、风格、尺寸)
文件:day3_02_json_output.py
import ollama
import json
def gen_prompt_json(raw_desc: str):
sys_prompt = f"""
你是AI绘图提示词生成助手,严格输出JSON,不要任何额外解释。
输出json字段:
subject:画面主题字符串
positive:正向提示词,逗号分隔
negative:反向提示词,逗号分隔
style:绘画风格
size:推荐分辨率例如1024x1024
用户输入描述:{raw_desc}
"""
resp = ollama.generate(
model="qwen3",
prompt=sys_prompt,
format="json",
temperature=0.5
)
# 解析json字符串为字典
data = json.loads(resp.response)
return data
if __name__ == "__main__":
result = gen_prompt_json("古风江南水乡夜景")
print(type(result))
print("主题:", result["subject"])
print("正向:", result["positive"])
print("反向:", result["negative"])
print("风格:", result["style"])
print("尺寸:", result["size"])
实操 3:批量生成 10 组不同风格的风景绘图提示词
文件:day3_03_batch_scene.py
import ollama
import json
import time
scene_list = ["高山云海", "海边落日", "森林溪流", "沙漠星空", "雪山湖泊",
"古镇清晨", "瀑布峡谷", "海岛沙滩", "枫林山谷", "溶洞秘境"]
style_list = ["国风", "赛博朋克", "日系插画", "写实摄影", "油画"]
batch_result = []
for idx, scene in enumerate(scene_list):
print(f"正在生成第{idx+1}/10组:{scene}")
prompt = f"""
根据风景主题 {scene},随机选用一种风格,生成绘图提示词,返回JSON,字段subject,positive,negative,style,size。不要多余文字。
"""
resp = ollama.generate(model="qwen3", prompt=prompt, format="json", temperature=0.7)
item = json.loads(resp.response)
batch_result.append(item)
time.sleep(0.3) # 请求间隔,防止压服务
# 打印全部结果
for one in batch_result:
print(f"\n【{one['subject']}】 style:{one['style']}")
print(f"positive:{one['positive']}")
# 保存批量结果
with open("batch_scene.txt","w",encoding="utf‑8") as f:
for d in batch_result:
f.write(json.dumps(d,ensure_ascii=False)+"\n")
print("\n批量生成完成,已保存 batch_scene.txt")
课后练习
练习 1:编写脚本,输入任意画面描述,自动输出标准化 ComfyUI 绘图提示词
需求:
- 控制台接收用户输入画面描述;输入 exit 退出程序
- 调用 Ollama 自动扩充为高质量正向提示词,生成通用反向提示词
- 打印输出:正向提示词、反向提示词,可直接复制粘贴到 ComfyUI 使用。
练习 2:实现 JSON 结构化输出,自动拆分正向、反向提示词,单独保存为变量
基于实操 2 代码改造:
- 函数返回字典,
positive、negative作为独立字段; - 增加异常捕获:JSON 解析失败捕获
json.JSONDecodeError,返回空字典; - 上层调用可以直接拿到
data["positive"]、data["negative"],方便后续传给 ComfyUI API。
异常捕获参考片段
try:
data = json.loads(resp.response)
except json.JSONDecodeError:
print("模型返回非法JSON数据")
data = {}
练习 3:批量生成 20 组人物绘图提示词,保存到本地 txt 文件
需求:
- 准备人物主题列表(古风女子、机甲少年、街头少女等 20 个主题);
- 循环批量调用 Ollama 生成 JSON 格式提示词;
- 设置延时,避免请求过快;
- 将 20 组结果写入本地
person_prompt_20.txt;每条一行,json 字符串; - 增加简单异常处理,单条失败不中断整个批量任务,打印哪一条生成失败。
拓展思考:
当 Ollama 返回 JSON 偶尔格式错乱如何处理?(提示:可以增加重试机制,失败重新调用一次)
ComfyUI 核心节点精讲
学习目标
- 理解 ComfyUI 完整文生图执行链路:加载模型→输入提示词→采样生成→保存图片
- 掌握 4 个最核心基础节点作用与连线逻辑
- 理解采样步数、CFG、种子、分辨率、采样器对画面的影响
- 学会正向 / 反向提示词配置,降低画面崩坏概率
- 掌握工作流保存、导出 JSON、加载复用,为后续 Python 远程调用做准备
课程内容
1、ComfyUI 核心工作流逻辑:加载模型→输入提示词→采样生成→保存图片
完整文生图执行链路:
- 加载模型:Checkpoint(大模型)载入,包含扩散模型、VAE、CLIP 文本编码器
- 输入提示词:正向、反向提示词交给 CLIP 编码,转为模型可识别向量
- 采样生成:KSampler 执行扩散采样,在噪声潜空间逐步去噪生成图片潜变量
- VAE 解码:把潜变量 latent 转为真实图像像素
- 保存图片 :SaveImage 输出图片到本地输出目录
ComfyUI/output
所有节点靠连线传递数据;数据流向是从左向右。
2、必备核心节点详解:Checkpoint 加载、CLIP 文本编码、KSampler 采样、SaveImage 保存
- CheckpointLoaderSimple(加载大模型)
- 输入:选择 Stable Diffusion 大模型 ckpt/safetensors
- 输出:MODEL(扩散模型)、CLIP(文本编码器)、VAE(图像解码器)
- 添加节点->模型->加载器->checkpoint 加载器(简易)

- CLIPTextEncode(文本编码)
- 输入:文本提示词、CLIP
- 输出:CONDITION(条件向量);正向、反向提示词各用一个该节点
- 添加节点->模型->条件->CLIP文本编码

- EmptyLatentImage(空潜图)
- 设置生成图片宽高,生成空白噪声潜空间,决定输出图片分辨率
- 添加节点->模型->Latent->空 Latent 图像

- KSampler(采样器)
- 输入:model,positive 正向条件,negative 反向条件,latent_image 噪声图,seed 种子,steps 步数,cfg,sampler 采样器,scheduler 调度器
- 输出:采样完成后的 latent 潜变量
- 添加节点->模型->采样-> k 采样器

- VAEDecode(VAE 解码)
- 将 latent 潜数据解码为图像
- 添加节点->模型->Latent->VAE 编/解码

- SaveImage(保存图片)
- 接收 image,保存到 output 文件夹
- 添加节点->图像->保存图像

标准连线顺序:
CheckpointLoaderSimple → MODEL 给到 KSampler;CLIP 给到两个 CLIPTextEncode;VAE 给到 VAEDecode;EmptyLatentImage 给到 KSampler;KSampler 输出 latent 给到 VAEDecode;VAEDecode 输出 image 给到 SaveImage。

3、基础参数调节:步数、CFG 值、种子、分辨率、采样器选型
表格
| 参数 | 说明 | 常用参考值 |
|---|---|---|
| seed 种子 | 随机种子;固定种子 + 参数完全一致,产出图片一致;-1 代表随机 | ‑1 随机,固定数字可复现画面 |
| steps 采样步数 | 去噪迭代次数;步数太低细节差;过高不会提升画质还浪费时间 | 20‑30 步 |
| CFG | 提示词强度;数值越大越严格遵守提示词;过高容易过曝、色彩崩坏;太低画面偏离描述 | 6‑8 |
| width/height | 输出分辨率,SD1.5 建议 512‑768;SDXL 建议 1024‑1024 | SDXL:1024×1024 |
| sampler 采样器 | 采样算法;euler、dpmpp‑2m‑karras 综合效果好 | dpmpp‑2m‑karras(通用首选) |
4、正向 / 反向提示词节点使用,画面瑕疵规避技巧
- 两个
CLIPTextEncode节点:- 第一个:正向提示词,画面想要的元素,主体、风格、光影、画质词
- 第二个:反向提示词,传入 KSampler 的 negative 输入口
- 通用反向提示词模板:
lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry
技巧:反向提示词不是越多越好,堆砌过多会污染画面;保留通用瑕疵词即可。
5、工作流保存、导出、重置、复用方法
- Save:保存工作流(UI 格式,用于网页端打开编辑,不能直接给 Python 调用)
- Save(API) :导出 API 格式 JSON!!这是 Python 调用 ComfyUI 必须的工作流文件,会把所有节点参数完整导出;
⚠️普通 Save 保存的 json 不能直接用于 API 调用,一定要选
Save(API)
- Load:加载 API 工作流 json 文件
- Clear:清空画布重新搭建
重要:后续 Python 调用 ComfyUI 读取的就是
Save(API)导出的 JSON 文件。
实操案例
实操 1:手动搭建完整文生图工作流,成功生成第一张 AI 图片
实操步骤:
- 打开 ComfyUI 网页界面 http://127.0.0.1:8188
- 清空画布,依次添加节点:
- CheckpointLoaderSimple
- 2 个 CLIPTextEncode
- EmptyLatentImage
- KSampler
- VAEDecode
- SaveImage
- 按照标准链路完成连线
- Checkpoint 选择 SDXL 基础模型;EmptyLatentImage 设置 1024×1024
- 正向提示词输入:
国风少女,山间竹林,清晨薄雾,唯美,8k,超细节 - 反向提示词填入通用瑕疵词
- KSampler 参数:seed=-1,steps=25,cfg=7,sampler:dpmpp‑2m‑karras
- 点击队列生成按钮,等待出图,在 output 文件夹查看生成图片。
- 使用
Save(API)导出该工作流,保存为txt2img_base.json。
实操 2:调节不同采样步数、CFG 值,对比画面清晰度、细节差异
基于上面搭建好的工作流,固定种子,只修改参数,对比效果:
- 固定 seed 为同一个数字(例如 123456),保证其他条件不变
- 实验组 A:steps=10,cfg=7 → 生成图片,观察细节模糊
- 实验组 B:steps=25,cfg=7 → 标准参数
- 实验组 C:steps=25,cfg=12 → CFG 过高,观察色彩、过曝问题
- 实验组 D:steps=25,cfg=3 → CFG 过低,画面脱离提示词描述
记录现象:步数不足细节缺失;CFG 过高画面炸裂;CFG 过低不听提示词。
实操 3:添加反向提示词,去除画面模糊、畸形、水印等问题
- 断开反向提示词节点,把 negative 输入留空,生成一张图片,观察畸形、模糊瑕疵。
- 重新接入反向提示词节点,填入通用反向提示词模板,再次生成。
- 对比两张图片差异,理解反向提示词作用。
- 导出当前完整工作流
txt2img_with_neg.json。
课后练习
练习 1:独立搭建 3 套不同风格(国风、赛博朋克、日系清新)文生图工作流
要求全部手动搭建,不要导入现成工作流:
- 工作流 1:国风主题,正向提示词偏向古风山水人物
- 工作流 2:赛博朋克主题,城市霓虹、未来机械风格
- 工作流 3:日系清新插画,柔和光影,动漫质感
每套工作流都配置正向、反向提示词,设置合理分辨率、采样参数,均可以正常出图。
练习 2:记录最优参数组合,形成个人绘图参数模板
新建文档 comfyui_param_template.md,记录:
- SDXL 通用文生图最优参数:steps、CFG、采样器、分辨率
- 国风风格推荐参数
- 赛博朋克风格推荐参数
- 日系清新风格推荐参数
- 通用反向提示词完整模板
练习 3:保存所有工作流文件,命名分类便于后续 Python 调用
重点:全部使用 Save(API) 导出 API 格式 JSON(不能普通 Save)
文件命名规范:
txt2img_chinese_style.json国风文生图txt2img_cyberpunk.json赛博朋克文生图txt2img_japanese_fresh.json日系清新文生图
把 3 个 json 文件统一放到项目目录下 comfy_workflow/ 文件夹,为第 6 天 Python 调用 ComfyUI 做准备。
思考题:
- Save 和 Save (API) 两种 json 有什么区别?为什么 Python 远程调用必须用 Save (API) 版本?
- 固定 seed 种子,所有参数不变,两次生成图片是否会完全一致?什么情况下会不一致?
ComfyUI 图生图与高清放大工作流
学习目标
- 掌握图生图核心节点,理解图生图的运行原理
- 学会图片加载、缩放、超分放大,实现小图转高清
- 理解 denoise(图生图重绘强度)对画面的影响
- 掌握分辨率适配、裁剪、画布填充实用技巧
- 能够排查图片格式、尺寸超限、显存不足等常见问题
课程内容
1、图生图核心节点:LoadImage 加载本地图片、ImageResize 尺寸调整
图生图(img2img)原理:不是完全重新生成,而是在原图噪声基础上做重绘修改。
- LoadImage :加载本地磁盘图片文件,输出
IMAGE图像数据与MASK蒙版;支持 jpg、png;不支持 webp 等部分格式。 - ImageResize :调整图片宽高尺寸,支持多种缩放模式:
nearest:最近邻,速度快画质差bilinear:双线性,均衡通用bicubic:双三次缩放,图像更平滑
图生图不再使用 EmptyLatentImage,而是用
VAEEncode把输入图片编码成 latent 潜变量,送入 KSampler。
图生图基础链路:
LoadImage → ImageResize → VAEEncode → KSampler (latent 输入)
同时:CheckpointLoaderSimple、CLIPTextEncode 正向 / 反向提示词、VAEDecode、SaveImage 保持不变。

图生图json文件
javascript
{
"3": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "v1-5-pruned-emaonly-fp16.safetensors"
}
},
"4": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "masterpiece, best quality",
"clip": ["3", 1]
}
},
"5": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "lowres, blurry, deformed, ugly",
"clip": ["3", 1]
}
},
"10": {
"class_type": "LoadImage",
"inputs": {
"image": ""
}
},
"11": {
"class_type": "VAEEncode",
"inputs": {
"pixels": ["10", 0],
"vae": ["3", 2]
}
},
"7": {
"class_type": "KSampler",
"inputs": {
"model": ["3", 0],
"positive": ["4", 0],
"negative": ["5", 0],
"latent_image": ["11", 0],
"seed": 5678,
"steps": 22,
"cfg": 7,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 0.6
}
},
"8": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["7", 0],
"vae": ["3", 2]
}
},
"9": {
"class_type": "SaveImage",
"inputs": {
"images": ["8", 0]
}
}
}
2、高清放大节点:Upscale、超分修复,模糊图片变高清原理
两种主流放大方案:
- LatentUpscale(潜空间放大):在 latent 潜空间放大,速度快,显存占用低;放大后再解码;细节提升有限。
- ImageUpscaleWithModel(AI 超分模型放大):使用 Real‑ESRGAN 等超分模型,对已经解码完成的图像做像素级修复放大;画质最好,显存消耗更大。
原理:
- 普通放大只是拉伸像素;
- AI 超分模型会预测补充丢失细节、纹理、边缘,把低分辨率模糊图重建为高清大图。
常用超分模型:RealESRGAN‑x4‑plus,放到
models/upscale_models目录。
3、图片分辨率适配、比例裁剪、画布填充技巧
实际业务中原图尺寸五花八门,直接送入模型容易变形、出黑边:
- 按比例缩放:先缩放到目标长边,短边不足不强行拉伸;
- 裁剪(Crop):超出部分裁掉,保证宽高严格匹配模型输入尺寸;
- 画布填充(Pad):不足部分填充黑色 / 模糊边缘,不裁剪主体;
SDXL 推荐输入尺寸:1024×1024;不要传入奇数字分辨率,容易出现 bug。
节点:ImagePadForOutpaint画布填充;ImageCrop图片裁剪。
4、图生图强度参数详解:保留原图比例与创意修改平衡
核心参数 denoise strength(重绘强度),范围 0‑1
- denoise=0:完全不修改,输出原图;
- denoise=0.2‑0.4:低强度,微调原图,色彩、细节小改动,构图主体基本不变;
- denoise=0.5‑0.6:中等强度,图生图常用;保留大致构图,大幅度更换风格;
- denoise=0.7‑0.9:高强度;构图改动很大,接近文生图效果;
- denoise=1.0:完全丢弃原图信息,等价文生图。
风格转换建议 denoise:0.5‑0.6;图片局部微调 denoise:0.2‑0.3。
5、常见报错:图片格式不兼容、尺寸超限、显存不足解决
- 图片加载失败
- 不支持 webp、heic;转成 jpg/png;路径不要中文、空格;
- 尺寸超限
- 分辨率设置过大,latent 体积超出显存;SDXL 尽量控制单边不超过 1536;大图先缩放再送入模型。
- 显存不足 OOM
- 降低输出分辨率;
- 开启模型 CPU 卸载;
- 优先使用 latent 放大,少用像素级超分;
- 关闭其他占用显卡程序;
- 使用小模型。
实操案例
实操 1:搭建图生图工作流,将普通照片转化为动漫风格图片
文件:手动搭建工作流,导出 API 格式 img2img_anime.json
节点清单:
- CheckpointLoaderSimple(SDXL 大模型)
- LoadImage:加载一张真人照片
- ImageResize:缩放到 1024×1024
- VAEEncode:把图片编码为 latent
- 2×CLIPTextEncode:正向提示词:日系动漫风格,细腻上色,明亮光影;反向填入通用瑕疵词
- KSampler:latent 来自 VAEEncode;denoise=0.55;steps=25,cfg=7,采样器 dpmpp‑2m‑karras
- VAEDecode
- SaveImage
操作步骤:
- 加载一张真人实拍照片;
- 设置 denoise=0.55;正向提示词写动漫风格;
- 队列生成,观察照片转为动漫画风;
- Save (API) 导出工作流
img2img_anime.json。
实操 2:使用高清放大节点,将 512 尺寸图片放大至 2K 高清画质
工作流:upscale_2k.json
流程:
文生图 / 图生图输出 512×512 小图 → ImageUpscaleWithModel(Real‑ESRGAN‑x4‑plus)放大 → SaveImage
512 ×4 =2048,得到 2K 图像。
操作要点:
- 确认超分模型文件放置在
models/upscale_models; - 先生成一张 512 尺寸的图;
- 接入 AI 超分放大节点,执行生成;对比原图和放大后细节差异。
对比实验:LatentUpscale 潜空间放大 VS Real‑ESRGAN 像素超分放大,观察细节差距。
实操 3:调节图生图强度,实现原图微调、大幅改写两种效果
固定所有参数,只修改 denoise 重绘强度,使用同一张输入图片。
- denoise=0.3:低强度;只做调色、轻微美化,人物构图几乎不变;
- denoise=0.7:高强度;画面构图、形象发生很大改变;接近重新创作。
对比两张输出图片,记录 denoise 强度带来的画面变化。导出工作流 img2img_denoise_demo.json。
课后练习
练习 1:针对 3 张不同原图,完成风格转换 + 高清修复全套操作
准备 3 张本地图片:人像照片、风景照片、静物照片。
对每张图片完整流程:
- LoadImage 加载 → 缩放适配 SDXL 尺寸;
- 图生图做风格改写(人像→国风;风景→油画;静物→赛博朋克);denoise 设置 0.5‑0.6;
- AI 超分放大到 2K;
- 保存输出图片;
观察不同原图在相同 denoise 下效果差异。
练习 2:搭建通用高清修图工作流,适配所有普通图片一键高清
要求:
- 输入任意图片,自动缩放;
- Real‑ESRGAN x4 超分放大;
- 不需要图生图重绘,只做高清修复;
- Save (API) 导出工作流
pure_upscale_workflow.json;
后续 Python 脚本可以直接加载这个 API 工作流,传入图片路径一键高清。
练习 3:整理显存不足、图片加载失败的全套解决方案
新建文档 comfyui_img_error_solve.md,整理故障清单:
- 图片加载失败:报错现象、产生原因、处理步骤
- webp/heic 格式无法读取:处理办法
- OOM 显存耗尽:分级解决方案(分辨率、放大方式、模型卸载)
- 图片尺寸奇数值导致画面错乱
- denoise 参数设置不合理导致完全丢失原图内容
思考题:
- denoise=1 的时候图生图和文生图有什么区别?
- LatentUpscale 和 ImageUpscaleWithModel 分别适合什么场景?优缺点分别是什么?
Python 调用 ComfyUI API 提交绘图任务
学习目标
- 理解 ComfyUI API 工作原理,掌握 8188 端口远程调用机制
- 掌握使用 requests 向 ComfyUI 提交队列任务、轮询任务状态、获取生成图片
- 看懂 API 工作流 JSON 结构,学会动态修改节点参数
- 实现图片获取、本地保存、按时间命名
- 增加服务检测、超时异常处理
课程内容
1、ComfyUI API 接口原理:8188 端口远程调用机制、工作流 JSON 调度逻辑
ComfyUI 网页界面本身就是基于 API 实现。
- 服务地址:
http://127.0.0.1:8188 - 核心接口:
/prompt:POST 提交工作流 JSON,返回任务 ID (prompt_id),任务进入队列排队执行/history/{prompt_id}:查询任务执行历史,获取生成结果文件信息/view:根据文件名下载生成的图片
调度流程:
- ComfyUI 网页使用 Save(API) 导出完整工作流 JSON;JSON 内部是所有节点 ID、节点类型、节点参数。
- Python 读取该 JSON,修改需要动态变化的字段(提示词、分辨率、步数等)。
- POST 发送 JSON 到
/prompt,拿到prompt_id任务编号。 - 循环轮询
/history/{prompt_id},判断任务是否完成。 - 任务完成后从 history 拿到输出图片文件名,调用
/view下载图片字节流,保存到本地。
⚠️ 必须使用
Save(API)导出的 JSON;普通 Save 的 UI 工作流 JSON 不能用于 API 调用。
2、Python requests 库调用 ComfyUI API 核心代码
整体流程伪代码:
#1.读取API工作流json
#2.动态修改节点参数
#3.post提交 /prompt 获取prompt_id
#4.循环轮询history接口等待任务完成
#5.获取图片文件名,requests.get("/view")下载图片
#6.写入本地文件
依赖:第 1 天已安装 requests
3、工作流 JSON 文件解析:节点参数、输入输出映射关系
API‑JSON 结构示例片段:
{
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 12345,
"steps":25,
"cfg":7.0,
...
}
},
"6": {
"class_type":"CLIPTextEncode",
"inputs":{"text":"正向提示词","clip":["4",0]}
}
}
- 外层 key:节点 ID 字符串 (数字字符串,如
"3"、"6") class_type:节点类型inputs:节点输入参数;普通参数直接赋值;连线是数组格式["节点ID",输出端口索引]
动态修改数据方式:
workflow_json["6"]["inputs"]["text"] = "新的正向提示词"关键点:需要先在 ComfyUI 确认你要修改的功能对应的节点 ID。
4、本地图片生成、结果回调、文件自动保存原理
- ComfyUI 推理完成后图片会先保存在自身
output目录;同时在 history 记录输出文件名。 - Python 不直接读取 ComfyUI 本地 output 文件夹,而是调用
/view?filename=xxx.png接口下载图片二进制数据。 - 拿到二进制 bytes,使用
open(目标路径,"wb")写入磁盘,实现自定义路径保存。 - 没有回调接口,只能轮询查询 history判断任务是否完成。
5、服务状态检测、调用超时基础处理
- 先访问根地址
http://127.0.0.1:8188判断 ComfyUI 服务是否启动; - 提交任务设置 post 超时;轮询设置最大等待次数,防止脚本无限死循环;
- 捕获连接异常、超时异常,输出友好提示。
实操案例
实操 1:导出 ComfyUI 基础文生图工作流 JSON 文件
- 打开第 4 天搭建好的基础文生图工作流;
- 点击菜单
Save(API),导出文件,命名:txt2img_api.json; - 将该 json 放到项目目录;
- 用 VS Code 打开 json,观察节点 ID,记录:
- 正向提示词节点 ID
- 反向提示词节点 ID
- KSampler 节点 ID
- EmptyLatentImage 分辨率节点 ID
示例假设:
正向 CLIPTextEncode 节点 ID:
"6"反向 CLIPTextEncode 节点 ID:
"7"KSampler 节点 ID:
"3"EmptyLatentImage 节点 ID:
"5"
实操 2:编写 Python 脚本,远程调用 ComfyUI 自动生成图片
文件:day6_01_comfy_api_base.py
import requests
import json
import time
COMFYUI_BASE = "http://127.0.0.1:8188"
def check_comfyui_service():
try:
resp = requests.get(COMFYUI_BASE, timeout=3)
return resp.status_code == 200
except Exception:
print("ComfyUI服务未启动,请先启动ComfyUI")
return False
def submit_workflow(workflow:dict):
payload = {"prompt": workflow}
resp = requests.post(f"{COMFYUI_BASE}/prompt", json=payload, timeout=10)
res = resp.json()
prompt_id = res["prompt_id"]
return prompt_id
def wait_for_done(prompt_id:str, max_wait=120):
waited = 0
while waited < max_wait:
resp = requests.get(f"{COMFYUI_BASE}/history/{prompt_id}", timeout=5)
hist = resp.json()
if prompt_id in hist:
return hist[prompt_id]
time.sleep(1)
waited +=1
raise TimeoutError("任务执行超时")
def download_image(filename:str, save_path:str):
url = f"{COMFYUI_BASE}/view"
params = {"filename":filename}
resp = requests.get(url, params=params, timeout=15)
with open(save_path,"wb") as f:
f.write(resp.content)
if __name__ == "__main__":
if not check_comfyui_service():
exit(1)
# 读取API工作流
with open("txt2img_api.json","r",encoding="utf-8") as f:
workflow = json.load(f)
prompt_id = submit_workflow(workflow)
print(f"任务已提交 prompt_id:{prompt_id},等待生成...")
history_result = wait_for_done(prompt_id)
# 获取输出图片文件名(SaveImage输出)
outputs = history_result["outputs"]
for node_out in outputs.values():
if "images" in node_out:
img_info = node_out["images"][0]
fn = img_info["filename"]
download_image(fn, "output_01.png")
print(f"图片已保存至 output_01.png")
break
运行前确认 ComfyUI 已经启动;执行脚本,脚本自动提交任务,下载图片到本地。
实操 3:修改脚本参数,通过 Python 修改提示词、分辨率、采样步数
文件:day6_02_modify_params.py
根据你实际导出 json 里的节点 ID 修改下面 ID 字符串
import requests
import json
import time
COMFYUI_BASE = "http://127.0.0.1:8188"
# 复用上面函数 check_comfyui_service submit_workflow wait_for_done download_image
if __name__ == "__main__":
if not check_comfyui_service():
exit(1)
with open("txt2img_api.json","r",encoding="utf‑8") as f:
workflow = json.load(f)
# =========动态修改节点参数(根据你实际节点ID修改)=========
workflow["6"]["inputs"]["text"] = "赛博朋克雨夜都市,霓虹灯光,电影质感,8k,超细节" #正向提示词
workflow["3"]["inputs"]["steps"] = 28 #采样步数
workflow["3"]["inputs"]["cfg"] = 7.5 #CFG强度
workflow["5"]["inputs"]["width"] = 1024 #宽度
workflow["5"]["inputs"]["height"] = 1024 #高度
# =====================================================
prompt_id = submit_workflow(workflow)
print(f"提交任务 {prompt_id}")
hist_result = wait_for_done(prompt_id)
outputs = hist_result["outputs"]
for node_out in outputs.values():
if "images" in node_out:
fn = node_out["images"][0]["filename"]
download_image(fn, "output_modify.png")
print(f"已保存 output_modify.png")
break
运行脚本:Python 修改工作流内部参数,提交任务生成新图。
重要提醒:如果你的 JSON 节点 ID 不是
"3""5""6",代码不会生效,需要打开你的 txt2img_api.json 核对真实节点 ID。
课后练习
练习 1:实现 Python 动态修改工作流提示词,一键生成不同风格图片
需求:
- 定义风格列表:国风山水、赛博朋克城市、日系插画海边;
- for 循环遍历风格列表;每次循环复制一份原始 workflow 字典,修改正向提示词;
- 逐个提交任务;每张图片输出不同文件名;
- 增加 sleep 间隔,不要并发大量提交压垮 ComfyUI。
提示:复制字典使用
workflow_copy = json.loads(json.dumps(workflow))做深度拷贝,避免修改同一个字典对象。
练习 2:编写脚本自动检测 ComfyUI 服务状态,异常自动告警
改造封装函数:
- 脚本启动首先调用检测函数;ComfyUI 未启动打印告警文字,直接退出程序。
- 提交任务捕获连接异常、超时异常;捕获后打印:
ComfyUI调用异常,请检查服务。 - 任务最大等待时间设置为 150 秒,超时抛出提示。
练习 3:将生成的图片自动保存到指定文件夹,按时间命名
需求点:
- 创建输出目录
./comfy_output/,不存在则自动创建; - 使用
datetime获取当前时间戳,文件名格式gen_20260829_221030.png; - 下载图片保存到该目录下;
- 打印完整保存路径。
参考时间命名片段:
import os
from datetime import datetime
out_dir = "./comfy_output"
os.makedirs(out_dir, exist_ok=True)
now_str = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = os.path.join(out_dir,f"gen_{now_str}.png")
Ollama + ComfyUI 端到端完整闭环
学习目标
- 打通完整 AI 生成链路:用户简单自然语言描述 → Ollama 生成结构化提示词 → Python 把提示词注入 ComfyUI 工作流 → 自动生成图片
- 掌握多模块之间数据传递,把 Ollama 输出的正向、反向提示词动态替换 ComfyUI‑API 工作流节点参数
- 增加全链路异常拦截:大模型返回空、JSON 解析失败、绘图超时、参数为空防护
- 将代码模块化封装,拆分工具函数,便于后续复用、扩展批量任务
课程内容
1、完整工作流闭环:用户输入简单描述→Ollama 优化专业提示词→Python 传递参数→ComfyUI 生成图片
整体业务链路:
- 用户输入口语化简单描述,例如:
古风山水,山间明月 - Ollama (qwen3) 接收原始描述,输出结构化 JSON:subject、positive 正向提示词、negative 反向提示词、style 风格
- Python 解析 JSON,拿到正向、反向提示词字符串
- 读取 ComfyUI API 工作流 json,动态修改 CLIPTextEncode 节点的 text 参数
- 通过 ComfyUI API 提交任务,等待推理完成
- 下载图片,按时间命名保存到本地输出文件夹
整套系统全部本地运行,不需要外网 API 密钥。
2、多模块数据对接:Ollama 文本输出变量传入 ComfyUI API 参数
数据流转关键点:
-
Ollama 输出是内存中的字典变量
prompt_data["positive"]、prompt_data["negative"]; -
不能写入中间文件,直接内存传递给 ComfyUI workflow 字典;
-
⚠️提交任务前必须做深度拷贝工作流 json 对象,避免多次循环生成时共用同一个对象导致参数错乱。
深度拷贝工作流,防止修改原始模板
workflow_template = json.load(open("txt2img_api.json","r",encoding="utf‑8"))
workflow = json.loads(json.dumps(workflow_template))把ollama输出赋值给工作流节点
workflow["6"]["inputs"]["text"] = prompt_data["positive"]
workflow["7"]["inputs"]["text"] = prompt_data["negative"]
3、正向 / 反向提示词动态替换逻辑
- 在 ComfyUI 导出的 API 工作流确认节点 ID:
- 正向提示词节点 ID:如
"6"(CLIPTextEncode) - 反向提示词节点 ID:如
"7"(CLIPTextEncode)
- 正向提示词节点 ID:如
- Python 读取 Ollama 返回字典,覆盖两个节点的
text输入字段; - 如果 Ollama 返回的 negative 为空字符串,程序自动填充一套通用反向瑕疵词兜底,防止反向提示词为空引发画面崩坏。
通用兜底反向提示词:
lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature
4、链路异常处理:大模型生成失败、绘图超时、参数为空拦截
链路每一步都有可能失败,需要分层拦截:
- Ollama 服务不可用:直接返回错误,不往下执行绘图;
- Ollama 返回 JSON 解析失败:重试 1 次,仍然失败返回;
- 解析后 positive 正向提示词为空字符串:拦截,不提交 ComfyUI 任务;
- ComfyUI 服务未启动:直接终止;
- ComfyUI 任务超时:捕获 TimeoutError;
- 生成结果无图片输出:捕获,打印日志。
流程控制原则:上游失败,不要执行下游昂贵的 GPU 绘图任务。
5、基础自动化流程封装,代码模块化拆分
把功能拆分为独立函数:
check_ollama_service():检测 Ollama 服务generate_ai_prompt(raw_desc:str):调用 Ollama,返回结构化提示词字典check_comfyui_service():检测 ComfyUI 服务comfy_submit_and_generate(workflow_dict):提交工作流,下载返回图片路径end_to_end_generate(raw_user_desc:str):对外统一入口函数,串联全部流程
模块化好处:函数可以单独调试;后续做批量生成、GUI 调用都可以直接复用
end_to_end_generate()。
实操案例
前置准备:
- Ollama 服务已启动;ComfyUI 服务已启动;
- 使用第 6 天导出 API 工作流
txt2img_api.json;核对自己 json 的正向、反向节点 ID;- 依赖:
pip install ollama requests
实操 1:搭建极简全链路流程,输入 "古风山水",自动生成优化提示词 + AI 图片
文件 day7_01_end2end_simple.py
import ollama
import requests
import json
import time
import os
from datetime import datetime
OLLAMA_URL = "http://127.0.0.1:11434"
COMFYUI_URL = "http://127.0.0.1:8188"
WORKFLOW_PATH = "txt2img_api.json"
OUTPUT_DIR = "./e2e_output"
os.makedirs(OUTPUT_DIR, exist_ok=True)
# --------服务检测--------
def check_ollama_service():
try:
r = requests.get(OLLAMA_URL, timeout=3)
return r.status_code ==200
except Exception:
print("[错误] Ollama服务未启动")
return False
def check_comfyui_service():
try:
r = requests.get(COMFYUI_URL, timeout=3)
return r.status_code ==200
except Exception:
print("[错误] ComfyUI服务未启动")
return False
# --------Ollama生成结构化提示词--------
def generate_ai_prompt(raw_desc:str):
sys_prompt = f"""
你是AI绘画提示词助手,严格输出JSON,不要额外解释。
字段:subject,positive,negative,style。
用户描述:{raw_desc}
"""
try:
resp = ollama.generate(
model="qwen3",
prompt=sys_prompt,
format="json",
temperature=0.6
)
data = json.loads(resp.response)
return data
except json.JSONDecodeError:
print("[警告] Ollama返回非法JSON")
return None
except Exception as e:
print(f"[警告] LLM调用异常 {e}")
return None
# --------ComfyUI API工具函数--------
def submit_workflow(workflow:dict):
payload = {"prompt":workflow}
r = requests.post(f"{COMFYUI_URL}/prompt", json=payload, timeout=10)
return r.json()["prompt_id"]
def wait_for_done(prompt_id, max_wait=120):
waited = 0
while waited < max_wait:
r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}", timeout=5)
hist = r.json()
if prompt_id in hist:
return hist[prompt_id]
time.sleep(1)
waited +=1
raise TimeoutError("绘图任务超时")
def download_image(filename, save_path):
r = requests.get(f"{COMFYUI_URL}/view", params={"filename":filename}, timeout=15)
with open(save_path,"wb") as f:
f.write(r.content)
# --------端到端主流程--------
def end_to_end_run(user_input:str):
if not check_ollama_service() or not check_comfyui_service():
return None
print(f"\n用户输入:{user_input}")
prompt_data = generate_ai_prompt(user_input)
if prompt_data is None or not prompt_data.get("positive"):
print("[错误] 获取提示词失败,终止绘图")
return None
print(f"优化正向提示词:{prompt_data['positive']}")
print(f"优化反向提示词:{prompt_data['negative']}")
# 读取模板,深度拷贝
with open(WORKFLOW_PATH,"r",encoding="utf‑8") as f:
template = json.load(f)
workflow = json.loads(json.dumps(template))
# =========修改为你自己工作流真实节点ID=========
workflow["6"]["inputs"]["text"] = prompt_data["positive"]
workflow["7"]["inputs"]["text"] = prompt_data["negative"]
# ============================================
prompt_id = submit_workflow(workflow)
print(f"绘图任务提交 prompt_id:{prompt_id}")
hist_result = wait_for_done(prompt_id)
# 获取输出图片
for node_out in hist_result["outputs"].values():
if "images" in node_out:
img_file = node_out["images"][0]["filename"]
time_str = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = os.path.join(OUTPUT_DIR,f"gen_{time_str}.png")
download_image(img_file, save_path)
print(f"✅图片已保存 {save_path}")
return save_path
print("[错误]未获取生成图片")
return None
if __name__ == "__main__":
end_to_end_run("古风山水,山间明月,薄雾缭绕")
运行脚本,观察完整链路:用户简单描述 → Ollama 扩充提示词 → ComfyUI 出图保存。
实操 2:实现风格自定义,支持用户指定国风、赛博、日系等风格生成图片
修改end_to_end_run调用处,在传给 Ollama 的 prompt 中增加风格指令。
def generate_ai_prompt_with_style(raw_desc:str, style:str):
sys_prompt = f"""
你是AI绘画提示词助手,绘画风格强制使用:{style}。严格输出JSON,不要额外解释。
字段:subject,positive,negative,style。
用户描述:{raw_desc}
"""
resp = ollama.generate(model="qwen3", prompt=sys_prompt, format="json", temperature=0.6)
return json.loads(resp.response)
if __name__ == "__main__":
# 测试不同风格
end_to_end_run("城市夜景,高楼")
# 在调用内部把风格参数传入generate_ai_prompt_with_style,例如 style="赛博朋克"
测试三组:国风、赛博朋克、日系插画,观察输出画面风格变化。
实操 3:完善异常拦截,避免空参数、无效文本导致的绘图失败
在主流程增加校验逻辑:
- 如果
prompt_data["positive"]为空字符串,直接 return,不提交 ComfyUI; - 如果
prompt_data["negative"]为空,自动填充通用反向提示词兜底; - 捕获绘图 TimeoutError;
- 用户输入为空字符串直接拦截。
关键片段:
# 兜底反向提示词
default_neg = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature"
prompt_data = generate_ai_prompt(user_input)
if prompt_data is None or not prompt_data.get("positive"):
print("[错误]正向提示词为空,放弃绘图")
return None
if not prompt_data.get("negative"):
prompt_data["negative"] = default_neg
课后练习
练习 1:封装完整单图生成函数,一行代码即可完成「输入描述→出图」全流程
对外封装函数接口:
def generate_image(user_desc: str, style: str = None) -> str|None:
"""
:param user_desc: 用户原始画面描述
:param style: 指定绘画风格,None由大模型自选
:return: 成功返回图片本地路径;失败返回None
"""
# 实现完整逻辑
调用示例:
path = generate_image("海边落日沙滩", style="油画")
print(path)
练习 2:实现反向提示词自动通用填充,规避画面瑕疵
需求:
- Ollama 返回 negative 为空的时候,自动填充内置通用反向词;
- 如果 Ollama 返回有内容,则把 Ollama 输出的反向词和内置通用反向词做拼接合并;
- 最终传给 ComfyUI。
示例逻辑:
base_neg = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature"
llm_neg = prompt_data.get("negative","").strip()
if llm_neg:
final_neg = llm_neg + "," + base_neg
else:
final_neg = base_neg
workflow["7"]["inputs"]["text"] = final_neg
练习 3:测试 10 组不同场景描述,确保全链路稳定运行
准备 10 条测试描述(人像、风景、静物、幻想场景)示例:
"樱花树下少女"
"雪山湖泊日出"
"未来机甲战士"
"江南古镇雨夜"
"猫咪趴在窗台看月亮"
"浩瀚宇宙星云"
"林间小木屋,秋天枫叶"
"深海发光水母"
"敦煌飞天壁画"
"赛博朋克雨中街头"
循环调用generate_image();每轮之间加 time.sleep (1);记录:
- 是否全部正常出图;
- 记录失败案例;
- 观察提示词生成质量。
出现 JSON 解析失败属于偶发,增加单次重试逻辑提升稳定性。
思考题
- 为什么每次提交 ComfyUI 之前要对 workflow 做深度拷贝?直接修改模板字典会发生什么问题?
- 如果 Ollama 和 ComfyUI 其中一个服务挂掉,程序应该怎么处理,是否继续往下执行?
AI 批量生成任务开发
学习目标
- 掌握批量任务的执行逻辑:循环迭代、任务队列、限速控制,避免服务过载与显存溢出
- 实现读取 txt /excel 关键词素材,批量调用 Ollama 生成提示词
- 批量调用 ComfyUI API 完成出图,实现自动分类、命名保存
- 学会批量任务调优:请求间隔、任务限速、显存保护策略
- 添加进度输出、日志文件记录,方便排查批量任务失败案例
课程内容
1、批量任务逻辑:循环调度、参数列表迭代、批量任务队列
批量两种实现思路:
- 串行循环(本课程采用,稳定):一条任务完整跑完(Ollama 生成提示词→ComfyUI 出图保存完成),再执行下一条。
优点:不会并发抢占 GPU 显存,不容易 OOM;调试简单;适合本地单机。
缺点:速度慢。
- 并发队列(进阶):多任务入队列,限制并发数量;ComfyUI 本身自带任务队列,但并发提交过多极易爆显存,不建议新手直接使用。
串行批量流程:
读取关键词列表 → for 循环遍历每一条素材
├─调用 Ollama 生成提示词
├─校验参数合法性,失败跳过当前条目,记录日志
├─深度拷贝 ComfyUI 工作流模板,替换正向 / 反向提示词
├─提交 ComfyUI 绘图任务,等待完成
├─下载图片,分类保存
├─间隔 sleep,保护 Ollama、ComfyUI 服务
└─打印进度,写入日志
循环结束。
重要:每次循环必须深度拷贝工作流模板,不能复用同一个字典对象,否则参数会互相污染。
2、Python 读取本地 txt/excel 批量关键词,批量调用 Ollama 优化提示词
- TXT 读取:每行一条主题关键词;
readlines()读取,strip 去除换行、空格;过滤空行。 - Excel 读取:使用
pandas读取,读取指定列作为主题列表。
安装依赖:
pip install pandas openpyxl
示例 txt 素材 batch_keyword.txt
樱花树下少女
雪山湖泊日出
未来机甲战士
江南古镇雨夜
猫咪趴在窗台看月亮
浩瀚宇宙星云
林间小木屋,秋天枫叶
深海发光水母
敦煌飞天壁画
赛博朋克雨中街头
......
读取 txt 简单示例:
with open("batch_keyword.txt","r",encoding="utf‑8") as f:
lines = f.readlines()
keyword_list = [line.strip() for line in lines if line.strip()]
3、批量调用 ComfyUI 生成图片,自动分类保存、命名
保存策略:
- 根输出目录
./batch_output/; - 可以按风格建立子文件夹,例如
./batch_output/国风/、./batch_output/赛博朋克/; - 文件命名规则:
{序号}_{主题缩写}_{时间戳}.png; - 每条任务对应的提示词 json 同步保存到日志目录,方便回溯;
- 单条失败不中断整体批量,跳过当前,记录失败日志,继续下一条。
4、批量任务限速、防卡顿、显存溢出优化
本地 GPU 显存有限,批量极易 OOM,优化手段:
- 串行执行,禁止并发提交多个 ComfyUI 任务;
- Ollama 调用后加短延时
time.sleep(0.2~0.5);ComfyUI 完成后延时time.sleep(0.5~1),释放显存; - 控制生成分辨率不要过大;SDXL 优先 1024×1024;
- 批量任务不要一次性几百条跑,可以分批次;
- 一旦出现 OOM,脚本记录日志,继续下一条;有条件可以增加简单显存检测。
5、批量任务进度打印、日志记录功能实现
日志包含信息:序号、原始关键词、生成的正向提示词、输出图片路径、任务状态(成功 / 失败、失败原因)。
两种日志输出:
- 控制台实时打印进度:
[3/20] 正在处理:xxx; - 写入本地日志文件
batch_log.txt,追加模式a;任务结束后可以回看哪些条目失败。
日志写入示例:
def write_log(msg:str):
with open("batch_log.txt","a",encoding="utf‑8") as f:
f.write(f"{msg}\n")
print(msg)
实操案例
前置:复用第 7 天的工具函数:服务检测、generate_ai_prompt、submit_workflow、wait_for_done、download_image。
工作流文件:
txt2img_api.json,核对节点 ID。
实操 1:读取本地关键词列表,批量生成 20 组优化绘图提示词
文件 day8_01_batch_prompt.py
import json
import time
import os
from day7_01_end2end_simple import check_ollama_service, generate_ai_prompt
KEYWORD_FILE = "batch_keyword.txt"
PROMPT_SAVE = "batch_prompt_result.json"
def read_keywords(file_path):
with open(file_path,"r",encoding="utf‑8") as f:
lines = f.readlines()
kw_list = [x.strip() for x in lines if x.strip()]
return kw_list
if __name__ == "__main__":
if not check_ollama_service():
exit()
keywords = read_keywords(KEYWORD_FILE)
print(f"读取到 {len(keywords)} 条关键词")
result_list = []
for idx, kw in enumerate(keywords[:20]):
print(f"\n[{idx+1}/20] 处理关键词:{kw}")
prompt_info = generate_ai_prompt(kw)
if prompt_info:
result_list.append(prompt_info)
else:
print(f"[{idx+1}/20] 提示词生成失败 {kw}")
time.sleep(0.3)
with open(PROMPT_SAVE,"w",encoding="utf‑8") as f:
json.dump(result_list,f,ensure_ascii=False,indent=2)
print(f"\n完成,提示词结果保存 {PROMPT_SAVE}")
运行效果:读取 txt 前 20 条关键词,调用 Ollama 批量生成结构化提示词,保存 json 文件。
实操 2:基于批量提示词,自动批量生成 20 张风格统一的 AI 图片
文件 day8_02_batch_img.py
import json
import time
import os
from datetime import datetime
from day7_01_end2end_simple import (
check_ollama_service, check_comfyui_service,
submit_workflow, wait_for_done, download_image
)
WORKFLOW_PATH = "txt2img_api.json"
BATCH_OUTPUT_DIR = "./batch_output"
os.makedirs(BATCH_OUTPUT_DIR, exist_ok=True)
PROMPT_SAVE = "batch_prompt_result.json"
DEFAULT_NEG = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature"
def write_log(msg):
with open("batch_log.txt","a",encoding="utf‑8") as f:
f.write(f"{msg}\n")
print(msg)
if __name__ == "__main__":
if not check_ollama_service() or not check_comfyui_service():
write_log("服务检测失败,退出批量任务")
exit(1)
# 读取已经批量生成好的提示词
with open(PROMPT_SAVE,"r",encoding="utf‑8") as f:
prompt_batch = json.load(f)
# 读取工作流模板
with open(WORKFLOW_PATH,"r",encoding="utf‑8") as f:
workflow_template = json.load(f)
total = len(prompt_batch)
success_count = 0
for idx, item in enumerate(prompt_batch):
index_show = idx + 1
subject = item.get("subject","unknown")
positive = item.get("positive","").strip()
llm_neg = item.get("negative","").strip()
write_log(f"\n====[{index_show}/{total}] subject:{subject}====")
if not positive:
write_log(f"[{index_show}/{total}] 正向提示词为空,跳过")
continue
# 合并反向提示词
final_neg = f"{llm_neg},{DEFAULT_NEG}" if llm_neg else DEFAULT_NEG
# 深度拷贝模板,非常关键
workflow = json.loads(json.dumps(workflow_template))
# 修改节点ID,替换为你自己工作流真实ID
workflow["6"]["inputs"]["text"] = positive
workflow["7"]["inputs"]["text"] = final_neg
try:
prompt_id = submit_workflow(workflow)
write_log(f"提交任务 prompt_id:{prompt_id}")
hist = wait_for_done(prompt_id, max_wait=150)
for node_out in hist["outputs"].values():
if "images" in node_out:
img_filename = node_out["images"][0]["filename"]
time_str = datetime.now().strftime("%Y%m%d_%H%M%S")
save_name = f"{index_show}_{subject}_{time_str}.png"
save_path = os.path.join(BATCH_OUTPUT_DIR, save_name)
download_image(img_filename, save_path)
write_log(f"✅成功:{save_path}")
success_count +=1
break
except Exception as e:
write_log(f"❌任务失败:{str(e)}")
time.sleep(0.8) # 任务间隔,释放显存
write_log(f"\n====批量任务结束,成功:{success_count}/{total}====")
实操 3:添加进度条日志,实时显示当前批量任务进度
简易控制台进度打印,不需要第三方库,直接打印百分比。
如果需要美观进度条,可以安装
pip install tqdm。
简易原生进度打印片段:
total = len(prompt_batch)
for idx, item in enumerate(prompt_batch):
index_show = idx +1
progress = (index_show / total)*100
print(f"\r进度:{index_show}/{total} ({progress:.1f}%)",end="")
# ...业务逻辑
tqdm 版本(实操替换循环):
from tqdm import tqdm
for idx, item in tqdm(enumerate(prompt_batch), total=len(prompt_batch), desc="批量生成图片"):
# 内部业务代码
pass
运行脚本,控制台实时输出当前进度百分比;全部任务完成查看batch_log.txt日志。
课后练习
练习 1:读取 Excel 关键词批量生成(pandas)
准备 excel 文件 batch_topic.xlsx,A 列存放画面主题关键词。
需求:
- pandas 读取 excel A 列,过滤空值得到关键词列表;
- 复用批量逻辑,完成 Ollama 提示词生成 + ComfyUI 批量出图;
- 图片输出目录按 excel 行号命名。
参考读取代码片段:
import pandas as pd
df = pd.read_excel("batch_topic.xlsx")
kw_list = df.iloc[:,0].dropna().tolist()
kw_list = [str(x).strip() for x in kw_list]
练习 2:增加按风格分类输出文件夹
需求:
- Ollama 返回
style字段; - 根据 style 自动创建子文件夹,例如
./batch_output/国风/; - 生成图片保存到对应风格子目录;
- 风格名称做文件名过滤,去除
/ \ : * ? " < > |非法字符。
练习 3:批量异常健壮性完善
- 单条 Ollama JSON 解析失败增加一次重试机会;重试失败记录日志跳过;
- ComfyUI 任务超时捕获,记录失败日志,不中断整体批量;
- 统计:总任务数、成功数、失败数;批量结束打印汇总统计;
- 将每一条成功任务的
subject、positive、negative、图片路径写入 csv 日志文件方便后期查阅。
思考题
- 批量任务为什么强烈建议串行而不是并发提交多个 ComfyUI 任务?
- 如果去掉深度拷贝
json.loads(json.dumps(template)),直接修改 template 字典循环批量,会出现什么现象?
ComfyUI LoRA + ControlNet 精准控图与 API 动态调参
学习目标
- 理解 LoRA 模型作用,掌握风格、人物、场景 LoRA 加载、权重调节、适配技巧
- 掌握 ControlNet 常用控制类型:姿态、线稿、边缘构图,解决人物动作跑偏、构图不可控问题
- 熟悉 LoRA 权重、ControlNet 控制强度对画面的影响,掌握参数调优区间
- 导出包含 LoRA、ControlNet 的 API 格式工作流 JSON,理清节点输入输出关系
- 通过 Python API 动态修改 LoRA 权重、ControlNet 强度,实现代码层面控图
课程内容
1、LoRA 模型加载与调用:人物、风格、场景 LoRA 适配技巧
LoRA 是轻量微调模型,依附主 Checkpoint 大模型使用,不单独运行。
- 存放路径:
ComfyUI/models/loras/*.safetensors - 核心节点:
LoraLoader
输入: model:主模型clip:CLIP 文本编码器lora_name:选择 LoRA 文件strength_model:模型权重,影响画面内容、人物特征strength_clip:文本权重,影响提示词语义
输出:更新后的 model、clip,再向下游节点传递。
参数经验区间:
- 人物 LoRA :
strength_model=0.6‑0.8,过高会面部崩坏 - 风格 LoRA :
strength_model=0.4‑0.7,不要超过 0.8,避免画风污染 - 场景 LoRA :
strength_model=0.5‑0.7
注意:LoRA 对主模型有版本匹配要求;SDXL LoRA 只能搭配 SDXL 主模型;SD1.5 LoRA 只能搭配 SD1.5 主模型。
工作流链路:
CheckpointLoaderSimple → 输出 model、clip 送入LoraLoader → LoraLoader 输出更新后的 model、clip 再给到 CLIPTextEncode、KSampler。
2、ControlNet 核心功能:姿态控制、线条控制、构图固定,解决 AI 画面跑偏问题
ControlNet 可以把外部参考信息(姿态、线稿、边缘图)嵌入扩散采样过程,强制约束生成画面构图、人物动作。
常用类型:
- OpenPose(姿态):输入姿态图,固定人物肢体动作,人物姿势不会乱飘
- Canny(边缘):输入线稿 / 草图,AI 沿着轮廓生成画面
- Depth(深度):固定空间景深、物体相对位置
核心节点:
ControlNetLoader:加载 ControlNet 权重模型,路径models/controlnet/ControlNetApply:把 controlnet 模型、参考图片、控制强度传入,输出带约束的 condition 给到 KSampler 的 positive 输入。
基础链路:
- LoadImage 读取姿态 / 线稿参考图 → 送入 ControlNetApply
- ControlNetLoader 加载对应 ControlNet 模型
- ControlNetApply 输出 condition,与 CLIPTextEncode 正向条件合并,给到 KSampler positive 输入。
ComfyUI 中多条件合并使用
ConditioningCombine节点。
3、LoRA 权重、ControlNet 强度参数优化
- LoRA strength_model
- 0:不生效;数值越大 LoRA 特征越强;>0.8 容易出现畸形、崩坏。
- strength_clip 一般和 strength_model 保持一致,部分风格 LoRA 可略低。
- ControlNet strength(控制强度)
0.3‑0.4:弱约束,大致参考构图,允许 AI 自由创作0.5‑0.7:中等约束,最常用;严格遵循姿态 / 轮廓,保留生成自由度>0.8:强约束,画面高度复刻参考图线条姿态,但容易僵硬、细节死板。
实操建议:姿态控制优先 0.5‑0.7;线稿转图 0.6‑0.8。
4、带 LoRA/ControlNet 的工作流 JSON 导出与参数适配
- 完整搭建包含:Checkpoint → LoraLoader → ControlNet 整套节点;调试出图正常。
- 使用 Save(API) 导出工作流 JSON,不能用普通 Save。
- 在 JSON 中记录关键节点 ID:
- LoraLoader 节点 ID:修改
lora_name、strength_model、strength_clip - ControlNetApply 节点 ID:修改
strength控制强度 - ControlNetLoader 节点 ID:修改 controlnet 模型文件名
- LoraLoader 节点 ID:修改
注意:JSON 里
lora_name是字符串文件名,例如"my_style_lora.safetensors",文件名必须和磁盘上完全一致。
5、Python 动态控制 LoRA 权重、ControlNet 参数
读取 API 工作流模板,深度拷贝后直接修改对应节点inputs字段:
# 修改LoRA权重
workflow["12"]["inputs"]["strength_model"] = 0.65
workflow["12"]["inputs"]["strength_clip"] = 0.65
# 切换LoRA文件
workflow["12"]["inputs"]["lora_name"] = "character_lora.safetensors"
# 修改ControlNet控制强度
workflow["18"]["inputs"]["strength"] = 0.6
"12"、"18"是示例节点 ID,以你导出 JSON 真实 ID 为准。
如果需要动态切换 ControlNet 模型:修改ControlNetLoader节点control_net_name字段。
实操案例
前置准备:
- 准备 LoRA 文件放入
models/loras;ControlNet 模型放入models/controlnet- 准备一张 openpose 姿态参考图片;
- Ollama、ComfyUI 服务正常运行;复用第 7 天 API 工具函数。
实操 1:搭建 LoRA 专属绘图工作流,生成指定人物 / 风格图片
- ComfyUI 新建画布
- 节点:
CheckpointLoaderSimple(SDXL) →LoraLoader - LoraLoader 选择一个风格 / 人物 LoRA;设置
strength_model=0.65,strength_clip=0.65 - LoraLoader 输出 model 给到 KSampler;输出 clip 给到两个 CLIPTextEncode
- CLIPTextEncode 填写正向、反向提示词;EmptyLatentImage、KSampler、VAEDecode、SaveImage 完成基础文生图链路。
- 队列生成图片,观察 LoRA 带来人物 / 风格效果。
- 调整权重分别为 0.4 /0.75 对比画面差异。
- Save (API) 导出工作流:
workflow_lora_api.json。
实操 2:使用 ControlNet 姿态控制,固定人物动作生成精准画面
搭建 LoRA + OpenPose 姿态控制完整工作流:
- 在 LoRA 工作流基础上增加:
LoadImage:加载 openpose 姿态参考图ControlNetLoader:选择 SDXL openpose controlnet 模型ControlNetApply:输入 controlnet 模型、姿态图片、strength=0.6ConditioningCombine:把 CLIPTextEncode 正向条件 和 ControlNetApply 输出条件合并,输出给到 KSampler positive。
- 正向提示词写人像描述;保持 LoRA 启用。
- 生成图片:观察人物姿态严格跟随参考姿态图。
- 修改 strength=0.3 和 0.8,对比约束强弱带来画面变化。
- Save (API) 导出完整工作流:
workflow_lora_cn_api.json。
实操 3:Python 动态修改 LoRA 权重,实现画面风格强弱微调
文件 day9_01_lora_cn_dynamic.py
节点 ID 替换为你导出 json 中的真实 ID
import json
import time
import os
from datetime import datetime
# 复用第7天工具函数:服务检测、submit_workflow、wait_for_done、download_image
COMFYUI_URL = "http://127.0.0.1:8188"
WORKFLOW_TPL = "workflow_lora_cn_api.json"
OUT_DIR = "./lora_cn_output"
os.makedirs(OUT_DIR, exist_ok=True)
if __name__ == "__main__":
# 读取模板
with open(WORKFLOW_TPL,"r",encoding="utf‑8") as f:
template = json.load(f)
# 测试多组LoRA权重
weight_list = [0.4,0.6,0.75]
for w in weight_list:
workflow = json.loads(json.dumps(template))
# =========替换为你JSON真实节点ID=========
workflow["12"]["inputs"]["strength_model"] = w
workflow["12"]["inputs"]["strength_clip"] = w
workflow["18"]["inputs"]["strength"] = 0.6
# =======================================
prompt_id = submit_workflow(workflow)
print(f"提交任务 LoRA权重={w}, prompt_id={prompt_id}")
hist = wait_for_done(prompt_id)
for node_out in hist["outputs"].values():
if "images" in node_out:
fn = node_out["images"][0]["filename"]
save_name = f"lora_w_{w}_{datetime.now().strftime('%H%M%S')}.png"
save_path = os.path.join(OUT_DIR, save_name)
download_image(fn, save_path)
print(f"保存 {save_path}")
time.sleep(1)
运行脚本,自动生成不同 LoRA 权重图片,观察画风强弱变化。
课后练习
练习 1:搭建一套「LoRA+ControlNet」精准控图通用工作流
要求:
- 集成:Checkpoint、LoraLoader、OpenPose ControlNet 整套节点;
- 正向、反向 CLIPTextEncode 完整配置;
- 可正常出图,姿态约束生效,LoRA 人物 / 风格生效;
- Save (API) 导出
workflow_lora_cn_general.json;记录全部关键节点 ID,备注到文档。
练习 2:编写 Python 脚本,动态切换 LoRA 模型、调整控制强度
需求:
- 定义 LoRA 列表,例如:
["style_a.safetensors","style_b.safetensors"] - 循环切换 lora_name;同时动态修改 LoRA 权重、ControlNet strength;
- 每次循环深度拷贝工作流模板;
- 生成图片文件名带上 LoRA 名称、权重参数;保存到对应输出文件夹。
练习 3:批量生成 10 张固定构图、不同风格的精准 AI 图片
完整链路:
- 固定 ControlNet 姿态参考图,保持构图、人物动作完全不变;
- 调用 Ollama 生成 10 组不同风格主题提示词;
- Python 注入提示词到工作流;动态切换不同风格 LoRA;
- 批量串行生成 10 张图片;每张人物姿态动作不变,但是画风、场景不同;
- 输出日志记录 LoRA 名称、权重、ControlNet 强度、提示词、图片路径。
提示:ControlNet 参考图是写死在工作流 LoadImage 节点中;如需动态替换参考图片,需要使用 ComfyUI API 上传图片接口,把图片上传到 ComfyUI 服务端,再修改节点
image字段。
思考题
- LoRA strength_model 设置到 1.0 以上,大概率会出现什么现象?
- ControlNet strength=1 的时候,画面会出现什么效果?实际业务为什么一般不建议开到 1?
多模态视觉模型识图 + AI 自动重绘完整链路
学习目标
- 掌握 Ollama 部署视觉多模态模型,实现本地图片解析、画面描述
- 使用 Python 调用多模态模型读取本地图片,提取画面主体、构图、缺陷
- 根据图片分析结果自动生成图生图重绘提示词
- 打通完整链路:识图分析 → 生成优化提示词 → ComfyUI 图生图重绘
- 调优多模态参数,提升图片识别准确率,规避描述偏差
课程内容
1、Ollama 多模态模型部署(llama3‑vision),图片识别、画面描述能力
多模态模型可以同时接收图片 + 文本 Prompt,对图片做理解、描述、打分、找缺陷。
本地运行,不需要联网 API。
部署命令:
ollama pull llama3‑vision
也可以使用 qwen‑vl 等国产视觉模型。
能力:
- 描述画面主体、人物、场景、光影、构图
- 识别画面缺陷:手部畸形、五官崩坏、构图失衡、画质模糊
- 根据原图内容生成适配图生图的正向、反向提示词
限制:不能识别极小细节;对艺术抽象图描述会有偏差。
2、Python 调用多模态模型,读取本地图片、解析画面内容
ollama 库多模态调用格式:传入images=[图片路径]参数。
resp = ollama.generate(
model="llama3‑vision",
prompt="请描述这张图片,输出结构化JSON",
images=["./test.jpg"],
format="json"
)
关键点:
- 图片路径为本地磁盘路径,Ollama 会读取图片编码;
- 强制输出 JSON 格式,方便 Python 解析;
- 图片格式优先 jpg/png;过大图片模型会自动缩放。
返回 JSON 设计字段:
{
"subject":"画面主体内容",
"scene":"场景环境",
"style":"当前画面风格",
"defects":"画面存在缺陷",
"positive":"图生图正向重绘提示词",
"negative":"图生图反向提示词"
}
3、基于原图内容,自动生成优化重绘提示词
Prompt 设计思路(给视觉模型):
任务:分析输入图片,为图生图生成优化提示词。
- 识别画面主体、场景、构图;
- 找出图片缺陷:手部畸形、面部崩坏、模糊、畸变;
- 保留原图主体与构图,优化细节、画质;
- 输出适合图生图的 positive、negative 提示词;
- 严格输出 JSON,不要多余文字。
得到positive、negative后,直接交给 ComfyUI 图生图工作流使用。
4、图理解 + 图重绘全链路逻辑:识图→分析→优化提示词→重绘图片
完整端到端链路:
- 传入一张本地原图路径
- Ollama‑vision 加载图片,AI 识图分析,输出结构化重绘提示词
- Python 解析 JSON,拿到正向、反向提示词
- 读取 ComfyUI 图生图 API 工作流模板(img2img_api.json)
- 把原图送入 ComfyUI 图生图节点;动态替换正向 / 反向提示词,设置 denoise 重绘强度
- 提交 API 任务,等待生成重绘图片
- 保存输出图片,完成智能重绘
denoise 建议:0.4‑0.6,保留原图主体构图,优化细节画质。
5、多模态调用参数优化,提升画面识别精准度
核心参数:
temperature:0.2‑0.4。识图任务调低,输出稳定,减少脑洞编造;- 提示词尽量约束输出格式,强制 JSON;
- 如果图片过大,先做缩放,避免模型解析性能下降;
- 增加兜底逻辑:多模态 JSON 解析失败,使用默认提示词,不中断流程。
实操案例
前置条件
- Ollama 已拉取
llama3‑vision模型;ComfyUI 正常运行; - 第 5 天搭建好图生图工作流,Save (API) 导出
img2img_api.json; - 核对工作流节点 ID:正向 CLIP、反向 CLIP、denoise 所在 KSampler 节点、LoadImage 节点。
- 依赖:
pip install ollama requests
⚠️ComfyUI API 图生图:LoadImage 节点默认读取服务端磁盘文件;如果要动态传入客户端本地图片,需要调用 ComfyUI
/upload/image接口上传图片,拿到服务端文件名,赋值给 LoadImage 节点image字段。
实操 1:部署视觉大模型,Python 调用实现本地图片内容识别
文件 day10_01_vision_analyse.py
import ollama
import json
VISION_MODEL = "llama3‑vision"
def analyse_image(image_path:str):
prompt = """
你是图片分析助手。分析这张图片,识别主体、场景、画面缺陷。
输出用于图生图重绘的正向、反向提示词。
严格输出JSON,字段:subject,scene,style,defects,positive,negative。不要额外说明。
"""
try:
res = ollama.generate(
model=VISION_MODEL,
prompt=prompt,
images=[image_path],
format="json",
temperature=0.3
)
data = json.loads(res.response)
return data
except json.JSONDecodeError:
print("多模态返回JSON解析失败")
return None
except Exception as e:
print(f"多模态调用异常:{e}")
return None
if __name__ == "__main__":
img = "./test_input.jpg"
result = analyse_image(img)
if result:
print(json.dumps(result,ensure_ascii=False,indent=2))
运行:传入一张本地图片,控制台输出图片分析结果与重绘提示词。
实操 2:AI 自动分析原图缺陷,生成优化重绘提示词
挑选一张有瑕疵图片(手部畸形、模糊老照片)运行脚本。
观察输出:
defects字段识别出缺陷;positive保留原图主体,增加高清、细腻细节;negative自动带上缺陷描述,规避原有问题。
示例输出片段:
{
"subject":"一个站在草地上的少女",
"scene":"户外草地,黄昏",
"style":"写实照片",
"defects":"手部畸形,画面模糊,面部细节丢失",
"positive":"少女站在草地上,黄昏光影,高清写实,细腻皮肤,精致五官,优美手部,8k",
"negative":"畸形手,模糊,五官崩坏,低画质"
}
实操 3:完成「原图识图→智能优化→ComfyUI 重绘」全自动化流程
需要 ComfyUI 上传图片接口,把客户端图片上传到 ComfyUI 服务端。
文件
day10_02_vision_img2img.py
import ollama
import requests
import json
import os
from datetime import datetime
COMFYUI_URL = "http://127.0.0.1:8188"
WORKFLOW = "img2img_api.json"
OUT_DIR = "./vision_rewrite_output"
os.makedirs(OUT_DIR, exist_ok=True)
VISION_MODEL = "llama3‑vision"
DEFAULT_NEG = "lowres, bad anatomy, bad hands, blurry, watermark"
# --------复用工具函数--------
def check_comfyui_service():
try:
return requests.get(COMFYUI_URL,timeout=3).status_code ==200
except Exception:
print("ComfyUI未启动")
return False
def upload_to_comfy(image_path:str):
"""上传本地图片到ComfyUI服务端,返回服务端图片文件名"""
url = f"{COMFYUI_URL}/upload/image"
with open(image_path,"rb") as f:
files = {"image":f}
resp = requests.post(url,files=files,timeout=20)
return resp.json()["name"]
def submit_workflow(workflow:dict):
return requests.post(f"{COMFYUI_URL}/prompt",json={"prompt":workflow},timeout=10).json()["prompt_id"]
def wait_for_done(prompt_id,max_wait=150):
waited=0
while waited<max_wait:
r=requests.get(f"{COMFYUI_URL}/history/{prompt_id}",timeout=5)
hist=r.json()
if prompt_id in hist:
return hist[prompt_id]
import time
time.sleep(1)
waited+=1
raise TimeoutError("任务超时")
def download_image(filename,save_path):
r=requests.get(f"{COMFYUI_URL}/view",params={"filename":filename},timeout=15)
with open(save_path,"wb") as f:
f.write(r.content)
def analyse_image(image_path:str):
prompt = """
分析图片,输出图生图重绘JSON:subject,scene,style,defects,positive,negative。
保留原图主体构图,修复缺陷。严格JSON输出。
"""
res = ollama.generate(model=VISION_MODEL,prompt=prompt,images=[image_path],format="json",temperature=0.3)
return json.loads(res.response)
# --------完整识图重绘主流程--------
def vision_rewrite(input_img_path:str):
if not check_comfyui_service():
return None
print(f"[1]多模态分析图片 {input_img_path}")
vision_result = analyse_image(input_img_path)
if not vision_result or not vision_result.get("positive"):
print("图片分析失败")
return None
print(f"解析得到正向提示词:{vision_result['positive']}")
llm_neg = vision_result.get("negative","").strip()
final_neg = f"{llm_neg},{DEFAULT_NEG}" if llm_neg else DEFAULT_NEG
# 上传图片到ComfyUI服务端
comfy_img_name = upload_to_comfy(input_img_path)
print(f"[2]图片上传完成:{comfy_img_name}")
# 读取图生图模板,深度拷贝
with open(WORKFLOW,"r",encoding="utf‑8") as f:
tpl = json.load(f)
wf = json.loads(json.dumps(tpl))
# =========修改为你自己工作流真实节点ID=========
wf["10"]["inputs"]["image"] = comfy_img_name # LoadImage节点
wf["6"]["inputs"]["text"] = vision_result["positive"]
wf["7"]["inputs"]["text"] = final_neg
wf["3"]["inputs"]["denoise"] = 0.52 #图生图重绘强度
# ============================================
prompt_id = submit_workflow(wf)
print(f"[3]提交绘图任务 {prompt_id}")
hist = wait_for_done(prompt_id)
for node_out in hist["outputs"].values():
if "images" in node_out:
fn = node_out["images"][0]["filename"]
time_str = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = os.path.join(OUT_DIR,f"rewrite_{time_str}.png")
download_image(fn,save_path)
print(f"✅重绘完成,保存:{save_path}")
return save_path
return None
if __name__ == "__main__":
vision_rewrite("./test_input.jpg")
运行流程:本地图片上传 ComfyUI → llama3‑vision 识图分析 → 自动生成重绘提示词 → ComfyUI 图生图重绘优化图片输出。
课后练习
练习 1:实现任意图片自动智能重绘,保留主体、优化画质和风格
封装函数接口:
def auto_rewrite_image(input_path:str, denoise:float=0.5) -> str|None:
"""
:param input_path:本地原图路径
:param denoise:重绘强度0‑1
:return:输出重绘图片路径
"""
需求:
- 支持任意 jpg/png 输入;
- 多模态识图自动生成提示词;denoise 参数可外部传入;
- 失败返回 None,打印错误信息;
- 重绘图片按时间命名保存输出目录。
练习 2:封装多模态识图函数,支持批量图片解析优化
批量处理文件夹下多张图片:
- 遍历输入文件夹
./input_batch/,读取所有 jpg/png; - 循环调用
auto_rewrite_image(),串行执行;每张图片完成 sleep (1); - 生成日志记录:原图路径、分析得到的 subject、positive、输出图片路径;
- 单张失败记录日志,不中断批量流程。
练习 3:对比普通文生图和识图重绘的效果差异,优化提示词逻辑
实验对比:
- 方案 A:普通文生图,人工写提示词生成一张图;
- 方案 B:把 A 生成的图作为输入,交给多模态识图重绘;
观察:
- 是否修复原图缺陷;主体构图是否保留;
优化多模态 Prompt:让模型更多保留原图构图,减少过度改动。
调优提示词增加约束:
严格保留原图物体位置、构图布局,只优化细节画质,不要大幅度改变画面内容。
思考题
- denoise 设置为 0.85 做识图重绘,会发生什么现象?什么场景适合高 denoise?
- 如果不使用 ComfyUI
/upload/image上传接口,直接本地路径填进 LoadImage 节点为什么不能跨客户端脚本使用?
AI 本地生成项目工程化与模块化重构
学习目标
- 学习工程化思想:把原来单文件大脚本拆分为配置、核心业务、日志、文件工具模块,便于维护、复用、二次开发
- 使用 JSON 配置文件统一管理端口、模型、默认参数、目录路径,修改参数不用改动业务代码
- 将提示词生成、ComfyUI 调用、多模态识图、图片 IO、异常捕获封装成类 / 独立函数
- 搭建完整日志系统:记录时间、入参、返回结果、成功失败状态,日志按日期归档
- 清理冗余代码,统一异常处理,提升代码健壮性,做成可复用项目模板
课程内容
1、项目工程化拆分:配置文件、核心调用、日志模块、文件管理分离
原来所有代码写在一个 py 文件,缺点:参数散落在代码各处;修改要改源码;代码臃肿;不方便复用。
模块化拆分原则:配置和业务分离;工具函数和主逻辑分离;日志和业务分离。
模块划分:
config.json:全局配置,所有可调参数全部放这里core/ai_generator.py:Ollama 文本 / 多模态提示词生成封装core/comfy_client.py:ComfyUI API 客户端封装(提交任务、等待、上传下载图片)utils/file_helper.py:文件夹创建、文件名过滤、遍历图片文件utils/logger_helper.py:日志模块,控制台 + 文件双输出,按日期保存日志main.py:程序入口,对外简单调用接口batch_run.py:批量任务入口脚本workflows/:存放所有 ComfyUI API 工作流 json 模板input/:输入图片 / 关键词 txtoutput/:输出生成图片logs/:日志文件目录
2、JSON 配置文件管理:统一管理模型路径、端口、默认参数、保存路径
config.json存放全部可变参数:Ollama 地址、ComfyUI 地址、模型名称、默认 LoRA、ControlNet 强度、denoise、各种目录、兜底反向提示词。
修改参数只改配置文件,不碰业务 py 源码。
示例配置字段:
{
"ollama":{
"base_url":"http://127.0.0.1:11434",
"llm_model":"qwen3",
"vision_model":"llama3‑vision",
"temperature":0.6,
"vision_temperature":0.3
},
"comfyui":{
"base_url":"http://127.0.0.1:8188",
"default_denoise":0.5,
"lora_strength":0.6,
"controlnet_strength":0.6
},
"path":{
"workflow_dir":"./workflows",
"input_dir":"./input",
"output_dir":"./output",
"log_dir":"./logs"
},
"default_negative":"lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature",
"batch_sleep":0.8
}
Python 读取配置:启动时加载一次 config.json,全局使用。
3、函数封装:提示词生成、绘图调用、图片保存、异常处理独立封装
面向对象封装思路:
OllamaClient:封装文本生成提示词、多模态图片分析;服务检测;统一异常捕获ComfyUIClient:封装服务检测、上传图片、提交工作流、等待任务、下载图片- 上层业务只调用类的方法,不关心底层 http 细节。
好处:
- 底层接口变更,只修改类内部,上层调用代码不用动
- 可以单独单元测试每个模块
- 批量任务、单图任务、识图重绘全部复用底层类
4、日志系统搭建:记录每一次调用时间、参数、成功 / 失败状态
日志需要记录内容:
- 时间戳
- 调用类型:文生图 / 图生图 / 多模态识图 / 批量任务
- 入参:用户描述、图片路径、关键参数
- 输出:生成提示词、输出图片路径
- 状态:SUCCESS / FAIL,附带失败异常信息
日志策略:
- 同时输出控制台 + 本地日志文件;
- 日志文件按日期命名,例如
2026‑08‑29.log,避免单日志文件过大; - 日志目录不存在自动创建;
- 使用 python 标准库
logging,不要自己简单 open 文件追加。
5、代码精简优化,去除冗余逻辑,提升运行效率
工程化优化要点:
- 删除重复的服务检测、sleep、路径创建代码,统一工具函数;
- 统一异常捕获,分层处理:网络异常、JSON 解析异常、IO 异常;
- 工作流模板只在初始化读取一次,运行时做深度拷贝,不重复读磁盘;
- 常量全部迁移到 config.json,代码中不写硬编码字符串;
- 增加参数校验,空字符串、空路径提前拦截,不向下传递无效参数。
实操案例
实操 1:搭建标准化项目文件夹结构,拆分各个功能模块
完整目录树:
ai_local_project/
├── config.json # 全局配置文件
├── main.py # 主程序入口
├── batch_run.py # 批量任务入口
├── core/
│ ├── __init__.py
│ ├── ai_generator.py # Ollama封装:文本、多模态
│ └── comfy_client.py # ComfyUI API客户端
├── utils/
│ ├── __init__.py
│ ├── file_helper.py # 文件、目录工具
│ └── logger_helper.py # 日志工具
├── workflows/ # ComfyUI工作流模板
│ ├── txt2img_api.json
│ ├── img2img_api.json
│ └── lora_cn_api.json
├── input/ # 输入素材:图片、关键词txt
├── output/ # 生成输出图片
└── logs/ # 运行日志
创建各个空文件和文件夹。
实操 2:编写全局配置文件,统一管理所有可调参数
config.json
{
"ollama": {
"base_url": "http://127.0.0.1:11434",
"llm_model": "qwen3",
"vision_model": "llama3‑vision",
"temperature": 0.6,
"vision_temperature": 0.3
},
"comfyui": {
"base_url": "http://127.0.0.1:8188",
"default_denoise": 0.5,
"lora_strength": 0.6,
"controlnet_strength": 0.6,
"task_max_wait": 150
},
"path": {
"workflow_dir": "./workflows",
"input_dir": "./input",
"output_dir": "./output",
"log_dir": "./logs"
},
"default_negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature",
"batch_sleep": 0.8
}
读取配置工具,utils/file_helper.py
import json
import os
def load_config(config_path="config.json")->dict:
with open(config_path,"r",encoding="utf‑8") as f:
cfg = json.load(f)
# 自动创建目录
for key in ["workflow_dir","input_dir","output_dir","log_dir"]:
p = cfg["path"][key]
os.makedirs(p, exist_ok=True)
return cfg
实操 3:封装全套调用类,实现代码极简调用
utils/logger_helper.py
import logging
import os
from datetime import datetime
def setup_logger(log_dir:str):
os.makedirs(log_dir,exist_ok=True)
log_file = os.path.join(log_dir,f"{datetime.now().strftime('%Y‑%m‑%d')}.log")
logger = logging.getLogger("ai_project")
logger.setLevel(logging.INFO)
# 去重handler
if logger.handlers:
logger.handlers.clear()
fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
# 文件输出
fh = logging.FileHandler(log_file,encoding="utf‑8")
fh.setFormatter(fmt)
# 控制台输出
ch = logging.StreamHandler()
ch.setFormatter(fmt)
logger.addHandler(fh)
logger.addHandler(ch)
return logger
core/ai_generator.py(Ollama 封装)
import ollama
import json
import requests
class OllamaClient:
def __init__(self, cfg:dict):
self.cfg = cfg
self.base_url = cfg["ollama"]["base_url"]
self.llm_model = cfg["ollama"]["llm_model"]
self.vision_model = cfg["ollama"]["vision_model"]
self.temp = cfg["ollama"]["temperature"]
self.vision_temp = cfg["ollama"]["vision_temperature"]
def health_check(self)->bool:
try:
return requests.get(self.base_url,timeout=3).status_code==200
except Exception:
return False
def generate_prompt(self, user_desc:str, style:str=None)->dict|None:
sys_prompt = "你是AI绘画提示词助手,输出JSON字段:subject,positive,negative,style。不要多余文字。"
if style:
sys_prompt += f"强制绘画风格:{style}"
try:
resp = ollama.generate(
model=self.llm_model,
prompt=f"{sys_prompt}\n用户描述:{user_desc}",
format="json",
temperature=self.temp
)
return json.loads(resp.response)
except Exception:
return None
def vision_analyze(self, img_path:str)->dict|None:
prompt = "分析图片,输出图生图重绘JSON:subject,scene,style,defects,positive,negative。保留原图主体构图。严格JSON。"
try:
resp = ollama.generate(
model=self.vision_model,
prompt=prompt,
images=[img_path],
format="json",
temperature=self.vision_temp
)
return json.loads(resp.response)
except Exception:
return None
core/comfy_client.py ComfyUI 客户端封装
import requests
import json
import time
import os
class ComfyUIClient:
def __init__(self, cfg:dict):
self.cfg = cfg
self.base_url = cfg["comfyui"]["base_url"]
self.max_wait = cfg["comfyui"]["task_max_wait"]
self.workflow_dir = cfg["path"]["workflow_dir"]
def health_check(self)->bool:
try:
return requests.get(self.base_url,timeout=3).status_code==200
except Exception:
return False
def load_workflow(self, name:str)->dict:
fp = os.path.join(self.workflow_dir,name)
with open(fp,"r",encoding="utf‑8") as f:
return json.load(f)
def upload_image(self, local_img_path:str)->str:
url = f"{self.base_url}/upload/image"
with open(local_img_path,"rb") as f:
r = requests.post(url,files={"image":f},timeout=20)
return r.json()["name"]
def submit(self, workflow:dict)->str:
r = requests.post(f"{self.base_url}/prompt",json={"prompt":workflow},timeout=10)
return r.json()["prompt_id"]
def wait_finish(self, prompt_id:str):
waited = 0
while waited < self.max_wait:
r = requests.get(f"{self.base_url}/history/{prompt_id}",timeout=5)
hist = r.json()
if prompt_id in hist:
return hist[prompt_id]
time.sleep(1)
waited +=1
raise TimeoutError("ComfyUI任务超时")
def download(self, filename:str, save_path:str):
r = requests.get(f"{self.base_url}/view",params={"filename":filename},timeout=15)
with open(save_path,"wb") as f:
f.write(r.content)
main.py 入口,极简调用示例
from utils.file_helper import load_config
from utils.logger_helper import setup_logger
from core.ai_generator import OllamaClient
from core.comfy_client import ComfyUIClient
import json
import os
from datetime import datetime
cfg = load_config("config.json")
logger = setup_logger(cfg["path"]["log_dir"])
ollama_client = OllamaClient(cfg)
comfy_client = ComfyUIClient(cfg)
def txt2img_generate(user_input:str, style=None):
logger.info(f"开始文生图,用户输入:{user_input}, style:{style}")
if not ollama_client.health_check() or not comfy_client.health_check():
logger.error("服务未就绪")
return None
prompt_data = ollama_client.generate_prompt(user_input,style)
if not prompt_data or not prompt_data.get("positive"):
logger.error("提示词生成失败")
return None
llm_neg = prompt_data.get("negative","").strip()
final_neg = f"{llm_neg},{cfg['default_negative']}" if llm_neg else cfg["default_negative"]
tpl = comfy_client.load_workflow("txt2img_api.json")
wf = json.loads(json.dumps(tpl))
# 替换为你实际工作流节点ID
wf["6"]["inputs"]["text"] = prompt_data["positive"]
wf["7"]["inputs"]["text"] = final_neg
pid = comfy_client.submit(wf)
logger.info(f"提交绘图任务 pid:{pid}")
hist = comfy_client.wait_finish(pid)
out_dir = cfg["path"]["output_dir"]
for node_out in hist["outputs"].values():
if "images" in node_out:
fn = node_out["images"][0]["filename"]
save_name = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
save_path = os.path.join(out_dir,save_name)
comfy_client.download(fn,save_path)
logger.info(f"生成成功 {save_path}")
return save_path
logger.error("未获取输出图片")
return None
if __name__ == "__main__":
path = txt2img_generate("古风山水,山间明月薄雾")
print(path)
运行main.py,只需要一行函数调用完成文生图,所有参数全部由 config.json 控制。
课后练习
练习 1:完成个人标准化 AI 自动化项目工程模板,可复用所有项目
完成完整项目模板要求:
- 完整目录结构,所有模块文件齐全;
- 文生图、图生图、识图重绘三个对外业务函数全部实现;
- 所有硬编码参数全部迁移至
config.json;代码内无魔写死的端口、路径、权重; - 工作流模板统一放在
workflows文件夹; - 可以直接复制整个项目文件夹用于新业务开发。
练习 2:完善日志记录功能,自动保存每日运行日志
需求:
- 日志按日期生成日志文件,例如
logs/2026‑08‑29.log; - 每次调用记录:调用类型、入参、提示词、输出图片路径、成功失败;异常堆栈写入日志;
- 批量任务运行时统计总任务、成功、失败数量写入日志末尾;
- 日志同时输出控制台,方便调试。
练习 3:实现参数一键修改,无需改动核心代码
验证工程化效果:
- 修改
config.json里面:comfyui.lora_strength、ollama.temperature、batch_sleep; - 不修改任何 py 源码;运行程序,确认参数生效;
- 新增一个配置项,例如:
max_batch_count最大批量条数,在批量脚本读取使用。
思考题
- 为什么工作流模板加载后运行前,必须做
json.loads(json.dumps(tpl))深度拷贝? - 工程化把配置抽离 JSON,带来哪些好处?
为什么 AI 任务优先跑 GPU,不用 CPU
一、一句话核心结论
CPU 擅长复杂逻辑串行思考 ;GPU 擅长成千上万简单数学同时并行计算 。
深度学习本质就是海量矩阵乘法,非常适合 GPU。
二、拆开对比,给学生讲明白
1)内部硬件结构完全不一样
- CPU(中央处理器)
核心很少:普通家用 CPU 只有 6‑16 个物理核心。
擅长:判断、分支、if‑else、复杂逻辑、操作系统、软件调度。
比喻:一个高级专家,做事精细,但是同时干的活不多。
- GPU(图形显卡)
几千个小计算核心(CUDA 流处理器)。RTX3060‑8G 有 3584 个 CUDA 核心 。
每一个小核心能力不强,但是可以几千个同时一起干活。
比喻:几千个普通工人,同时做一模一样简单重复的运算。
2)AI 神经网络到底在干什么?
不管是 ComfyUI 画图、Ollama 大模型、YOLO 目标检测:
90% 以上时间都在做矩阵乘法,大量浮点数运算。
矩阵运算特点:计算简单、重复、可以并行。
- CPU:只能一批一批慢慢算,排队执行 → 速度极慢
- GPU:几千个核心同时开工,一次性批量算完 → 速度几十~上百倍提升
举真实例子给学生直观感受:
同样一张图 FLUX 生成:
- CPU:几十分钟
- RTX3060 8G 显卡:几十秒
三、还有第二个关键:显存 VRAM(非常重要,学生最容易忽略)
- CPU 用的是系统内存 RAM。内存总线速度慢。
- GPU 自带高速显存 VRAM ,带宽巨大。
几十亿参数的 AI 模型,需要一次性把全部权重放进高速内存里。
大模型体积很大,如果没有显存,CPU 内存搬运数据会严重堵车。
补充:Ollama 可以 CPU 跑,是因为用了 gguf 量化 + llama‑cpp 高度优化 C++ 代码,做了很多妥协,代价就是速度很慢。
四、表格总结(直接放进 PPT)
表格
| 项目 | CPU | GPU |
|---|---|---|
| 核心数量 | 少(6‑16 核) | 几千个小计算核心 |
| 擅长任务 | 复杂逻辑、分支判断 | 大规模并行浮点计算、矩阵运算 |
| AI 推理速度 | 很慢 | 快几十~上百倍 |
| 高速存储器 | 内存 RAM,带宽低 | 显卡显存 VRAM,带宽极高 |
| 适合场景 | 轻量测试、小模型演示 | 绘图、视频、YOLO、大模型生产任务 |
五、三个常见误区(课堂考点)
- ❌ 误区:CPU 性能弱,所以跑不动 AI
✅ 真相:CPU 理论可以算,但是架构天生不适合大规模并行,效率极低。就算顶级 i9,跑 AI 依然慢。 - ❌ 误区:只要显卡越好,AI 一定越快
✅ 真相:第一看 CUDA 核心数量,第二看显存大小。显存不够直接爆 OOM,根本加载不起大模型。
8G 显存就跑不动完整版 Wan2.1 视频模型,不是显卡算力不够,是装不下模型。 - ❌ 误区:所有 AI 都必须 GPU
✅ 真相:
- 小量化大模型:Ollama 可以 CPU 勉强运行,适合学习演示;
- 图像、视频生成任务,计算量爆炸,几乎必须 GPU。
六、拓展补充(前沿拓展)
- NPU:手机端 AI 专用芯片,专门做 AI 加速,介于 CPU‑GPU 之间;
- TPU:谷歌专门为神经网络设计的处理器,云端大规模训练;
- AMD 显卡:有显卡但是 CUDA 生态差,PyTorch、ComfyUI 兼容不好,所以 AI 圈大多用 N 卡。
七、课堂记忆口诀
CPU 动脑子,GPU 出力气;
矩阵并行算 AI,显卡速度快千里。