在实际使用 ComfyUI 处理文本生成或 AI 绘画工作流时,我们经常会遇到一个需求:当工作流中集成了大型语言模型节点时,如何让 LLM 在生成文本的某个节点处“暂停”,以便我们插入自定义的提示词片段,或者复用之前精心调试好的提示词模板?更进一步,如何将这些可复用的提示词片段管理起来,形成一个高效的“提示词库”,避免每次都要手动复制粘贴或重新编写?这正是 ComfyUI 中“暂停文本”节点和“提示词库”工作流设计要解决的核心问题。
对于使用 ComfyUI 进行 AI 图像生成、视频制作或复杂多模态任务的创作者和开发者来说,掌握这项技能意味着能将工作流从线性的、固定的脚本,升级为模块化、可交互、可复用的智能管道。本文将以一个具体的“暂停 LLM 文本并创建可复用提示词库”工作流为例,带你从零开始理解其原理,搭建环境,实现节点连接,并最终构建一个属于自己的提示词管理模块。无论你是想优化 Stable Diffusion 的提示词输入流程,还是构建复杂的多步骤 AI 创作流水线,这篇文章都将提供清晰的路径和可落地的代码。
1. 理解 ComfyUI 中 LLM 节点的文本暂停机制
在深入操作之前,我们需要先厘清几个核心概念:ComfyUI 的工作流本质、LLM 节点的数据流,以及“暂停”在这里的真实含义。
1.1 ComfyUI 工作流与节点数据流
ComfyUI 是一个基于节点图的可视化编程界面,用于构建和运行 AI 模型(如 Stable Diffusion)的工作流。每个节点代表一个处理单元(如加载模型、编码文本、生成图像),节点之间的连线代表了数据的流动方向。数据(如图像张量、文本字符串、条件信息)从一个节点的输出端口流向另一个节点的输入端口,驱动整个工作流执行。
当我们在工作流中引入 LLM 节点(例如通过ComfyUI-ChatGLM3、ComfyUI-LLM-Vision或其他集成 OpenAI API 的节点),它的核心功能是接收一个文本提示(Prompt),调用背后的语言模型,并输出生成的文本。这个输出文本通常会作为下一个节点的输入,比如直接传递给 KSampler 作为正向提示词,或者经过进一步处理。
1.2 “暂停文本”节点的作用与原理
所谓的“暂停”,并非让 LLM 模型本身停止计算,而是在 ComfyUI 的工作流数据流中制造一个“断点”或“交互点”。标准 LLM 节点的工作模式是“输入 -> 处理 -> 输出”,一气呵成。而“暂停文本”节点的设计目标是在“处理”和“输出”之间插入一个手动干预的环节。
其典型的工作原理如下:
- 数据挂起:LLM 节点生成的原始文本不会直接输出到下游节点,而是被临时“挂起”或存储在一个中间状态。
- 提供编辑接口:这个中间状态通过一个特殊的 UI 组件(如文本输入框、按钮)暴露给用户。
- 用户干预:用户可以在该 UI 组件中查看 LLM 生成的文本,并对其进行编辑、删减、补充,或者从预设库中选择一个模板进行替换或合并。
- 继续执行:用户确认编辑后,触发“继续”操作,被编辑后的文本才会作为该节点的最终输出,传递给工作流中的下一个节点。
这样,我们就实现了对 LLM 生成内容的可控性编辑,而无需中断整个工作流的其他部分(如图像生成管线)。
1.3 构建提示词库的价值
如果每次暂停都只能手动输入,效率提升有限。提示词库的价值在于将“编辑”这一步标准化和模板化。
- 效率提升:将常用的场景描述、风格关键词、构图指令、负面提示词等保存为模板,一键调用。
- 质量稳定:避免手动输入错误,确保特定风格或要求下的提示词一致性。
- 协作与分享:团队可以共享一个提示词库,统一创作标准。
- 动态组合:可以从库中选择多个片段,与 LLM 生成的文本进行智能组合(如前缀、后缀、插入)。
在 ComfyUI 中,实现提示词库通常需要结合自定义节点开发,利用 JSON 文件、数据库或简单的 Python 字典来存储和管理模板。
2. 环境准备与必要插件安装
要实现带有暂停和提示词库功能的工作流,你的 ComfyUI 环境需要包含基础的 LLM 集成能力。以下是一个清晰的准备清单。
2.1 基础 ComfyUI 环境
首先,确保你有一个可运行的 ComfyUI 环境。对于大多数用户,使用整合包是最快捷的方式。
| 环境选项 | 说明 | 获取/安装方式 |
|---|---|---|
| 秋叶一键整合包 | 适合 Windows 用户,内置了常用插件和模型管理工具,开箱即用。 | 从作者发布页(如 B站、GitHub)下载最新版本,解压后运行启动器。 |
| 官方源码部署 | 适合需要深度定制或 Linux/macOS 用户,通过 Git 克隆和 Pip 安装依赖。 | git clone https://github.com/comfyanonymous/ComfyUI,然后根据官方 README 安装依赖。 |
| 云端平台 | 无需本地硬件,直接使用在线服务。功能可能受限,且自定义节点安装复杂。 | 搜索提供 ComfyUI 服务的云平台,注册使用。 |
注意:如果你使用整合包,请确保其版本不是过于陈旧,以兼容较新的插件。
2.2 安装 LLM 相关插件与自定义节点
ComfyUI 本身不包含 LLM 功能,需要安装第三方插件。我们将安装一个支持 API 调用且易于集成的插件。
安装
ComfyUI-LLMVision或类似插件: 这个插件通常提供了连接 OpenAI、Claude 等 API 的节点,也常包含基础的文本处理节点。在 ComfyUI 根目录下的custom_nodes文件夹内,打开终端(或通过启动器进入安装菜单)执行:# 进入 custom_nodes 目录 cd ComfyUI/custom_nodes # 克隆插件仓库 git clone https://github.com/your-repo/ComfyUI-LLMVision.git替换
your-repo为实际的插件仓库地址。安装后,重启 ComfyUI。安装支持“暂停”功能的自定义节点: 标题中提到的“暂停文本节点”可能是一个特定自定义节点的功能。你需要搜索如
ComfyUI-PauseText、ComfyUI-Interactive或ComfyUI-PromptControl这类节点。安装方式同上。cd ComfyUI/custom_nodes git clone https://github.com/another-repo/ComfyUI-PromptControl.git如果找不到专门的暂停节点,我们也可以利用现有节点的组合(如
Primitive节点配合工作流逻辑)或自己编写一个简单的节点来实现,这将在后续实现部分详述。验证安装: 启动 ComfyUI 后,在节点菜单中搜索
LLM、Chat、Prompt、Pause等关键词,检查是否出现了新安装的节点。
2.3 准备 LLM API 密钥或本地模型
根据你安装的 LLM 插件要求,准备相应的后端服务。
- 云端 API(如 OpenAI GPT-4, Claude):你需要拥有相应平台的账号,并创建 API Key。在插件的节点中通常需要填入这个 Key 和 Base URL。
- 本地模型(如 ChatGLM3, Llama.cpp):你需要下载对应的模型文件(
.bin,.safetensors等),并确保有足够的硬件资源(GPU 内存)。插件可能需要配置本地模型的路径和运行参数。
将 API Key 或模型路径信息记录下来,后续在节点配置中会用到。
3. 构建带暂停功能的 LLM 文本生成工作流
现在,我们开始构建核心工作流。假设我们使用一个集成了 OpenAI API 的LLMTextGenerator节点和一个自定义的TextPauseAndEdit节点。
3.1 工作流结构与节点连接
我们的目标工作流逻辑如下:
[文本输入] -> [LLM文本生成节点] -> [暂停与编辑节点] -> [最终文本输出]在 ComfyUI 中具体操作:
添加 LLM 文本生成节点:
- 在节点面板搜索
LLMTextGenerator或ChatGPT,将其拖入画布。 - 配置节点参数:
api_key: 填入你的 OpenAI API Key。model: 选择模型,如gpt-4-turbo-preview。prompt: 连接一个Text节点,输入你的初始提示,例如:“Generate a detailed prompt for a fantasy landscape painting, include style and composition.”max_tokens: 设置生成文本的最大长度,如 300。
- 在节点面板搜索
添加暂停与编辑节点:
- 如果你安装了专门的
TextPauseAndEdit节点,直接搜索添加。 - 如果没有,我们可以用组合方式模拟:
- 添加一个
Primitive节点(类型选STRING),将其输出暂时断开。 - LLM 节点的
text_output连接到这个Primitive节点的输入。此时,运行工作流,LLM 会生成文本并显示在Primitive节点上,但不会继续向下传递。 - 你需要手动复制
Primitive节点上显示的文本,然后粘贴到一个新的Text节点中,再连接后续流程。这本质上是一种“手动暂停”。
- 添加一个
- 如果你安装了专门的
连接后续流程:
- 将“暂停与编辑节点”的输出(或手动编辑后的
Text节点的输出),连接到需要最终文本的节点。例如,连接到一个CLIP Text Encode节点,作为 Stable Diffusion 的正向提示词。
- 将“暂停与编辑节点”的输出(或手动编辑后的
3.2 关键节点参数详解与代码逻辑
理解节点内部的逻辑有助于调试和自定义。以下是一个简化版TextPauseAndEdit自定义节点的核心 Python 代码逻辑:
import comfy.sd from comfy.sd import CLIP from nodes import common_ksampler import torch class TextPauseAndEdit: @classmethod def INPUT_TYPES(s): return { "required": { "input_text": ("STRING", {"multiline": True}), "mode": (["replace", "prepend", "append"], {"default": "replace"}), "library_key": ("STRING", {"default": ""}), }, "hidden": {"unique_id": "UNIQUE_ID"}, } RETURN_TYPES = ("STRING",) FUNCTION = "process_text" CATEGORY = "text processing" def __init__(self): # 模拟一个简单的提示词库 self.prompt_library = { "style_anime": "masterpiece, best quality, anime style, vibrant colors", "style_realistic": "photorealistic, ultra detailed, 8k, realistic lighting", "negative_common": "lowres, bad anatomy, blurry, duplicate", } def process_text(self, input_text, mode, library_key, unique_id): # 1. 检查是否需要从库中提取文本 library_text = self.prompt_library.get(library_key, "") # 2. 根据模式组合文本 if mode == "replace" and library_key: final_text = library_text elif mode == "prepend": final_text = library_text + " " + input_text if library_text else input_text elif mode == "append": final_text = input_text + " " + library_text if library_text else input_text else: final_text = input_text # 3. 在实际的“暂停”节点中,这里会弹出一个UI让用户编辑final_text # 为了演示,我们假设用户直接确认了,返回最终文本。 # 真正的交互需要前端UI配合,这里仅展示逻辑。 print(f"[TextPauseAndEdit Node {unique_id}] Output: {final_text[:50]}...") return (final_text,)关键参数解释:
input_text: 上游 LLM 节点传入的待处理文本。mode: 定义提示词库内容与输入文本的合并方式。replace: 直接用库中的模板替换整个输入文本。prepend: 将库模板添加到输入文本的开头。append: 将库模板添加到输入文本的末尾。
library_key: 用于从内部字典(提示词库)查找对应模板的键名。
这个节点目前只是静态逻辑。一个完整的交互式节点需要定义前端 UI 模板(NODE_CLASS_MAPPINGS和NODE_DISPLAY_NAME_MAPPINGS),并在process_text函数中实现等待用户前端输入的逻辑。这涉及到 ComfyUI 自定义节点更高级的开发知识。
4. 实现可复用的提示词库管理系统
简单的字典存储只适用于演示。一个实用的提示词库需要持久化存储和方便的管理界面。
4.1 设计提示词库的数据结构
我们可以使用 JSON 文件来存储提示词库,结构清晰且易于读写。
// prompts_library.json { "version": "1.0", "categories": { "art_style": { "anime": "masterpiece, best quality, anime style, vibrant colors, detailed background", "oil_painting": "oil painting texture, impasto, classic art, rich colors, canvas feel", "cyberpunk": "neon lights, cyberpunk, futuristic city, rain, synthwave, detailed" }, "composition": { "close_up": "close-up shot, portrait, detailed eyes, facing viewer", "wide_shot": "wide angle, epic scene, vast landscape, cinematic lighting" }, "negative": { "common": "lowres, bad anatomy, blurry, duplicate, error, extra limbs", "nsfw": "nsfw, nude, sexually suggestive" } } }4.2 创建支持文件读取的增强节点
修改之前的节点,使其能够从外部 JSON 文件加载提示词库。
import json import os class TextPauseAndEditWithLibrary: @classmethod def INPUT_TYPES(s): # 尝试从文件加载库键名作为下拉选项 library_options = ["None"] lib_path = os.path.join(os.path.dirname(__file__), "prompts_library.json") try: with open(lib_path, 'r', encoding='utf-8') as f: data = json.load(f) for category, items in data.get("categories", {}).items(): for key in items.keys(): # 使用 category:key 作为唯一标识 library_options.append(f"{category}:{key}") except FileNotFoundError: print(f"Warning: Prompt library not found at {lib_path}") return { "required": { "input_text": ("STRING", {"multiline": True}), "action": (["view_only", "prepend_lib", "append_lib", "replace_with_lib"], {"default": "view_only"}), "library_selection": (library_options, {"default": "None"}), }, } RETURN_TYPES = ("STRING",) FUNCTION = "process_text_with_lib" CATEGORY = "text processing" def load_library(self): lib_path = os.path.join(os.path.dirname(__file__), "prompts_library.json") try: with open(lib_path, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: print(f"Error loading prompt library: {e}") return {"categories": {}} def process_text_with_lib(self, input_text, action, library_selection): final_text = input_text lib_content = "" if library_selection != "None": # 解析 category:key cat, key = library_selection.split(":", 1) library_data = self.load_library() lib_content = library_data.get("categories", {}).get(cat, {}).get(key, "") # 根据用户在前端选择的 action 执行操作 # 注意:这里 action 应来自前端交互,为演示我们直接使用参数 if action == "prepend_lib" and lib_content: final_text = lib_content + " " + input_text elif action == "append_lib" and lib_content: final_text = input_text + " " + lib_content elif action == "replace_with_lib" and lib_content: final_text = lib_content # view_only 模式直接返回 input_text return (final_text,)这个节点通过library_selection下拉菜单,动态加载 JSON 文件中的分类和键名,让用户可以直接选择预定义的提示词片段。
4.3 集成到完整工作流并测试
- 保存节点代码:将上述 Python 代码保存为
text_pause_library.py,放入你的custom_nodes目录下的一个插件文件夹内(例如ComfyUI-PromptLibrary),并确保有正确的__init__.py文件导出该类。 - 创建 JSON 库文件:在同一目录下创建
prompts_library.json,填入你的提示词模板。 - 重启 ComfyUI:使新节点生效。
- 构建测试工作流:
Text节点 ->LLMTextGenerator节点 ->TextPauseAndEditWithLibrary节点 ->CLIP Text Encode节点。- 在
TextPauseAndEditWithLibrary节点上,选择action和library_selection。
- 执行与验证:
- 点击“Queue Prompt”运行工作流。
- LLM 节点会生成文本并传递给我们的自定义节点。
- 观察自定义节点的输出,检查是否按预期(前置、追加、替换)合并了提示词库中的内容。
- 最终编码后的提示词应能正确引导图像生成。
5. 常见问题排查与调试指南
在实现过程中,你可能会遇到以下问题。
5.1 节点加载失败或找不到
| 问题现象 | 可能原因 | 检查与解决步骤 |
|---|---|---|
| 重启 ComfyUI 后,在节点列表找不到新安装的节点。 | 1. 插件未放置在custom_nodes目录下。2. 插件目录缺少 __init__.py或__pycache__缓存问题。3. Python 依赖缺失。 | 1. 确认插件文件夹在ComfyUI/custom_nodes/内。2. 检查插件主目录是否有 __init__.py文件。可尝试删除插件目录下的__pycache__文件夹后重启。3. 查看 ComfyUI 启动日志或终端报错,根据提示安装缺失的包 ( pip install package_name)。 |
| 节点显示红色,提示“Missing node”或“Failed to load”。 | 节点类定义有语法错误,或引用了不存在的模块。 | 1. 检查节点的 Python 文件是否有语法错误。 2. 查看 ComfyUI 启动时的错误日志,定位到具体文件和行号进行修复。 |
5.2 LLM 节点无响应或报错
| 问题现象 | 可能原因 | 检查与解决步骤 |
|---|---|---|
| LLM 节点长时间无输出,最终超时。 | 1. API Key 错误或余额不足。 2. 网络问题无法访问 API。 3. 本地模型路径错误或显存不足。 | 1. 确认 API Key 正确且有效。检查对应平台的控制台,确认是否有可用额度。 2. 测试网络连通性。如果使用需要特殊网络环境,请确保配置正确。 3. 检查本地模型文件路径是否正确,并通过任务管理器查看 GPU 内存占用。 |
报错InvalidRequestError或Model not found。 | 1. 指定的模型名称错误。 2. API 端点 (Base URL) 配置错误。 | 1. 核对插件文档,使用正确的模型标识符。 2. 如果使用第三方代理或本地部署的模型,确保 Base URL 填写正确。 |
5.3 自定义节点逻辑错误
| 问题现象 | 可能原因 | 检查与解决步骤 |
|---|---|---|
| 节点能运行,但输出文本未按预期合并(库内容未生效)。 | 1. JSON 文件路径错误或格式错误。 2. 代码中解析 library_selection的逻辑有误。3. action参数判断逻辑错误。 | 1. 在节点代码中添加print语句,输出lib_path和加载的library_data,检查文件是否被正确读取。2. 打印 library_selection的值,确认其格式与代码中split(‘:’)的逻辑匹配。3. 检查 if-elif逻辑分支,确认当前action的值是否落入正确的分支。 |
| 工作流运行时,自定义节点导致 ComfyUI 卡死或无响应。 | 节点代码可能存在死循环或同步阻塞操作(如弹窗等待)。 | 1. 避免在节点处理函数中使用input()等同步阻塞操作。ComfyUI 是异步服务器。2. 复杂的交互应通过前端 UI 组件和 websocket 通信来实现。对于“暂停”功能,可以考虑使用工作流本身的“队列”机制,先中断,等用户通过其他方式(如修改节点参数)后重新运行。 |
5.4 提示词库管理问题
| 问题现象 | 可能原因 | 检查与解决步骤 |
|---|---|---|
| 新增的提示词模板在节点下拉菜单中不显示。 | JSON 文件已更新,但节点下拉菜单选项在启动时已缓存。 | 重启 ComfyUI 服务,让节点重新执行INPUT_TYPES类方法加载最新的 JSON 数据。对于生产环境,可以考虑实现一个“刷新库”按钮或定时重载机制。 |
| 多人协作时,提示词库无法共享。 | JSON 文件存储在本地。 | 将 JSON 文件放在网络共享存储或版本控制系统(如 Git)中。或者,将提示词库升级为使用轻量级数据库(如 SQLite),并通过一个简单的管理界面进行增删改查。 |
6. 生产环境最佳实践与扩展方向
将实验性的工作流转化为稳定、可协作的生产力工具,需要考虑更多因素。
6.1 安全与配置管理
- 分离敏感信息:绝对不要将 API Key 等硬编码在节点或工作流 JSON 中。应使用环境变量或 ComfyUI 的配置管理功能(如果插件支持)。对于自定义节点,可以设计一个配置节点,从外部文件或环境变量读取密钥。
- 工作流版本化:将最终调试好的、包含节点连接关系的工作流 JSON 文件保存起来,并纳入版本控制(如 Git)。这样可以在不同环境间迁移和回滚。
- 提示词库版本化:
prompts_library.json也应进行版本控制,记录每次的修改,便于团队协作和追溯。
6.2 性能与稳定性
- LLM 调用优化:对于高频使用的工作流,考虑以下策略:
- 缓存:对相同的 LLM 输入提示进行结果缓存,避免重复调用产生费用和延迟。
- 异步处理:如果工作流允许,将 LLM 调用设置为异步,避免阻塞整个图像生成管线。
- 降级方案:当主要 LLM API 不可用时,是否有备用的本地小模型或规则引擎可以生成基础文本。
- 错误处理与日志:在自定义节点中增加完善的异常捕获和日志记录。将关键信息(如用户选择、最终生成的提示词、错误信息)记录到文件,便于排查问题。
import logging logging.basicConfig(filename='comfyui_prompt_library.log', level=logging.INFO) class YourNode: def process(self, ...): try: # ... 业务逻辑 logging.info(f"Processed with selection: {library_selection}") except Exception as e: logging.error(f"Node error: {e}", exc_info=True) # 返回一个安全的默认值,避免工作流完全中断 return ("Error occurred, using default prompt.",)
6.3 扩展功能设想
- 可视化提示词库管理界面:开发一个独立的 ComfyUI 节点或外部网页,提供对
prompts_library.json的增、删、改、查操作,支持分类管理和预览。 - 动态提示词组合:不止是前置/后置/替换,可以实现更复杂的逻辑,如根据关键词自动从库中选取多个片段组合,或实现简单的条件判断(如果输入文本包含“人物”,则自动添加“肖像光照”库片段)。
- 与图像生成参数联动:将提示词库的选择与采样器参数(如 CFG Scale、步骤数)或模型选择(如基础模型、LoRA)联动。选择“动漫风格”提示词时,自动切换到对应的动漫风格模型和较低的 CFG Scale。
- 集成外部知识库:将提示词库升级为连接向量数据库(如 ChromaDB)的知识库。LLM 节点可以先从知识库中检索相关的风格描述和案例,再生成或优化最终提示词,实现更精准的控制。
通过将 LLM 的生成能力与可暂停、可编辑、可复用的提示词库相结合,你能在 ComfyUI 中构建出真正强大且灵活的创意流水线。核心在于理解数据流,合理设计自定义节点的输入输出接口,并将可变的部分(如提示词模板)外置为可配置的资源。从实现一个简单的 JSON 文件库开始,逐步迭代,最终可以形成一套适配你个人或团队独特工作方式的智能创作系统。