前阵子有个朋友跑来问我:能不能让AI Agent自己把图画了?就是在对话框里说一句"给我画一只戴着耳机的猫,背景是霓虹小巷",Agent不光回你一段文字,还能真的调用本地ComfyUI,“咣当”一下给你跑出一张图来。我第一反应是这玩意要折腾很久,但真正上手把OpenClaw和ComfyUI通过本地桥接服务串起来之后发现,不仅可行,而且比想象中实用太多。这篇就把我完整的部署过程、踩过的坑、以及常见问题的排查方法都写出来,给有同样想法的人做个参考。
这套方案解决的核心问题很直接:让OpenClaw这个智能体平台拥有调用本地图像生成引擎的能力,同时把ComfyUI从“手工拖工作流节点”的图形界面工具,变成Agent可以随时调用的“视觉生产力后端”。整条链路完全跑在本地,不需要把提示词和图片传到任何云端服务,数据安全性和响应速度都有保障。文章适合三类人看:已经在用ComfyUI但想接Agent的人、在玩OpenClaw想扩展技能的人、以及单纯好奇“本地AI编排”到底怎么落地的人。下面就从设计思路开始,一步步把整个桥接方案讲透。
1. 桥接方案的整体设计与思路拆解
1.1 OpenClaw和ComfyUI各自扮演什么角色
先捋一下这两个东西在我这套方案里的定位。OpenClaw是一个智能体运行平台,负责接收指令、理解用户意图、调用各种能力。它有一套“技能”机制,类似给Agent装插件,每个技能对应一个可执行的功能模块,Agent会根据对话内容决定调用哪个技能。它在Windows上可以用官方安装脚本部署,也支持安卓端通过Termux跑,还见过有人把它接到ROS2的机器人环境里做试验,生态挺开放的。
ComfyUI这边就不用多介绍了,节点式图像生成工作流工具,基于Stable Diffusion生态,靠自定义工作流完成文生图、图生图、局部重绘这些任务。它真正让我觉得适合做桥接后端的原因,是它内置了一套HTTP API:你可以不打开那个画布的图形界面,纯靠接口提交工作流、查队列状态、拉生成结果。这就意味着它天然可以被别的程序调度,而不是只能靠人手去点“Run”。
把两个东西“桥接”起来,本质就是让OpenClaw发出的高层的自然语言指令,落到ComfyUI的低层技术参数上,再通过一套中间服务把两端粘起来。这套中间服务,就是标题里说的“本地桥接部署服务”的实体。
1.2 为什么选择“本地”而不是云端API
很多人第一反应是:OpenClaw直接接入云端的绘图API不就行了?确实可以,但有几个现实问题。一是每个Agent智能体调用绘图能力其实很频繁,特别是做批量生成或者多轮迭代的时候,云端按量计费的成本会很快涨上去。二是有不少部署场景是内网环境,根本访问不了外部的API服务,热词里搜“内网服务器”的人不少,说明这个需求不是少数。
本地桥接的好处有三点。第一,延迟低,图像生成的瓶颈本来就在显卡推理上,如果再从外部API绕一圈,来回网络开销会进一步拖慢体验。第二,省钱,显卡已经买了,电费已经出了,本地跑满它的算力是天经地义的。第三,可控性强,工作流长什么样、模型用哪个、插什么自定义节点,都是自己说了算,云端API给不了这种自由度。
1.3 三种桥接思路的对比
动手之前我把桥接方案在脑子里列了几种,实测下来差异很大,整理成表格方便对比:
| 方案 | 实现方式 | 优点 | 缺点 | 我的评价 |
|---|---|---|---|---|
| 方案A:直接调ComfyUI原生API | OpenClaw技能脚本里直接POST /prompt | 简单直接,代码量小 | 任务管理和状态同步要自己处理,工作流切换逻辑会很乱 | 只适合玩一玩 |
| 方案B:中间网关服务 | OpenClaw技能 → FastAPI网关 → ComfyUI API | 统一管理任务、排队、参数映射,容易扩展 | 多一个服务要维护 | 长期使用推荐 |
| 方案C:WebSocket实时通道 | OpenClaw与ComfyUI建立长连接 | 生成过程可以实时推状态 | 实现复杂度高,断线重连麻烦 | 需求强烈再考虑 |
我最终选了方案B,用FastAPI写一个轻量的桥接网关放在本机。原因是OpenClaw本身要处理对话逻辑、技能调度,如果在技能脚本里直接拼ComfyUI请求,参数映射稍微复杂一点代码就乱成一团。中间加一层网关,把“自然语言参数”到“ComfyUI工作流参数”的转换收敛到一个地方,后面要加新的绘图模式、换模型、调默认参数,都只改网关不改Agent端,维护体验舒服很多。
2. 环境准备与核心组件安装
2.1 OpenClaw部署与WSL2环境修复
OpenClaw在Windows上的部署,官方推荐走PowerShell脚本,底层依赖WSL2作为Linux运行环境。这里有个高频坑,就是热词里很多人搜的“OpenClaw无法安全验证WSL2环境”。我第一次装的时候也卡在这。现象是在PowerShell里跑安装脚本,报错提示类似“无法验证WSL 2环境,请运行wsl --status检查”,然后进程直接退出。
排查方式按官方提示来就行:打开PowerShell,先执行:
wsl --status如果显示“默认版本:2”和“内核版本”等信息,说明WSL2本体没问题,很可能是安装脚本检测机制跟当前的Windows版本不兼容。这个时候去Windows设置里把“适用于Linux的Windows子系统”和“虚拟机平台”两个可选功能确认开启,然后执行一次:
wsl --update把内核更新到最新版再重新跑安装脚本就过了。这一步我当时折腾了小半天,最后发现是WSL内核版本太旧导致的检测失败,升级内核之后一次通过。如果你装了多个Linux发行版,最好再执行wsl --set-default <发行版名>把默认发行版指定清楚,OpenClaw的安装脚本会依赖默认发行版路径。
2.2 Node.js与OpenClaw本体安装
OpenClaw的另一个安装依赖是Node.js,建议直接从官网下载LTS版本,安装时记得勾选“Add to PATH”,不然后面命令找不到。Node.js装好之后,在PowerShell里验证一下:
node -v npm -v都能输出版本号,再继续装OpenClaw。安装完成后先别急着配技能,先把服务拉起来确认基础运行正常。以Windows环境为例,在OpenClaw的安装目录下执行启动命令,等服务输出监听到本地端口,再用官方配套的Companion或Web界面连一下,确认对话功能正常,再接大模型。
接大模型这里要顺便回应一个很多人的疑问:OpenClaw是不是只能通过接入外部API的方式使用算力?不是的。完全可以接本地跑在Ollama上的模型,把Ollama的地址配置到OpenClaw里就行。推理速度当然跟机器配置有关,但胜在完全离线。我本地用的就是Ollama加载了一个7B级别的模型,配合下面的ComfyUI画图,整条链路除了下载模型之外没有任何外部流量。
2.3 ComfyUI部署的两种路径
ComfyUI的安装我自己走过两条路,一条是秋叶整合包,一条是官方手动部署。秋叶整合包的优势是真的省心,解压即用,自带Python环境、常用插件、模型管理工具,新手照着教程点几下就能跑起来,网上搜“秋叶ComfyUI整合包下载”的人特别多,说明大家对这个包的依赖度很高。它的缺点是版本更新不稳定,整合包里的节点和插件有时候版本偏旧,跟最新的API行为有出入。
官方手动部署其实也不难,逻辑就是:把ComfyUI仓库clone到本地,创建Python虚拟环境,安装requirements.txt里的依赖,然后启动。唯一麻烦的是显卡驱动和PyTorch版本要对上,N卡用户建议直接用官方预编译的torch版本,省去编译折腾。我个人的建议是:如果你主要是想用ComfyUI跑图且以后要频繁更新节点,用官方手动部署更灵活;如果你只想赶紧把整条链路跑通看效果,用秋叶整合包最快。
2.4 本地大模型与ComfyUI的显存分配
本地部署有个绕不开的话题:显存分配。OpenClaw对话用的本地大模型和ComfyUI图像生成都要吃显存,两个一起跑很容易爆。我用的策略是给Ollama设置环境变量OLLAMA_MAX_LOADED_MODELS=1,然后为模型配置较低的上下文长度,把显存占用控制在4GB左右,剩下的给ComfyUI。如果你的显卡是16GB显存,跑一个7B对话模型加一个SD 1.5绘图的ComfyUI工作流是够的,如果是SDXL模型,就得按分辨率调整批量大小,不然照样OOM。
3. 桥接服务的核心实现:从FastAPI到ComfyUI API
3.1 桥接网关的目录结构与职责划分
我建的桥接服务目录结构大概长这样:
bridge/ ├── main.py # FastAPI入口 ├── config.py # 端口、模型、工作流路径等配置 ├── comfy_client.py # ComfyUI API客户端封装 ├── workflows/ # 存放导出的工作流JSON文件 │ ├── txt2img.json │ └── img2img.json └── outputs/ # 生成图片的落盘目录(与ComfyUI共享)职责划分清楚很重要:main.py只管接收OpenClaw技能转过来的请求、校验参数、调用comfy_client发任务;comfy_client只管跟ComfyUI的API交互;config.py集中管理所有可调参数。这样拆完,后面换模型、改端口、加工作流,都不用动逻辑代码。
3.2 ComfyUI接口交互要点
ComfyUI的API交互,核心就三个接口:提交任务用的是向/prompt发送POST请求,body是一个包含工作流JSON的prompt字段。查询队列用/queue,看历史结果用/history。提交之后整个任务在ComfyUI内部异步执行,所以要轮询/history拿结果,图片落地之后会有个文件名,这个文件名就是回传给Agent的成果。
有个细节容易忽略:工作流JSON里的节点ID是字符串,每个节点有class_type,比如CLIPTextEncode是提示词编码节点,KSampler是采样器,VAEDecode是解码节点,SaveImage是保存图片节点。你要动态改的是某些节点的inputs值。我调试时最常犯的错是改了节点ID的字符串格式,或者漏了节点之间的连线关系,导致ComfyUI报“invalid prompt”。所以强烈建议:先在ComfyUI图形界面把工作流跑通、导出JSON,再拿来改参数,别从零手写。
3.3 桥接网关核心代码
下面是我精简过的comfy_client.py核心片段,删掉了各种注释和异常分支,保留主线逻辑:
import json import time import urllib.request class ComfyClient: def __init__(self, base_url="http://127.0.0.1:8188"): self.base_url = base_url def submit_prompt(self, workflow: dict) -> str: payload = json.dumps({"prompt": workflow}).encode("utf-8") req = urllib.request.Request( f"{self.base_url}/prompt", data=payload, headers={"Content-Type": "application/json"}, ) with urllib.request.urlopen(req) as resp: result = json.loads(resp.read().decode("utf-8")) return result.get("prompt_id") def wait_for_result(self, prompt_id: str, timeout=180, interval=2): deadline = time.time() + timeout while time.time() < deadline: with urllib.request.urlopen(f"{self.base_url}/history/{prompt_id}") as resp: history = json.loads(resp.read().decode("utf-8")) if prompt_id in history: outputs = history[prompt_id]["outputs"] images = [] for node_id, node_output in outputs.items(): if "images" in node_output: for img in node_output["images"]: images.append(img["filename"]) if images: return images time.sleep(interval) raise TimeoutError(f"任务执行超时: {prompt_id}")这段代码做的事情很朴素:提交工作流,拿个prompt_id,然后每隔2秒查一次history,直到生成结果出现或者超时。不要小看这个简单的轮询,它解决了Agent调用绘图能力时最容易出问题的一个环节——异步任务的同步等待。没有这一步,你的技能脚本发起请求之后不知道该等多久,很容易直接把任务丢掉。
3.4 动态修改工作流参数的关键:CLIP“询问机”节点
很多人搜“comfyui clip 询问机”,其实就是想让工作流接收外部动态输入的提示词。在ComfyUI里,CLIPTextEncode节点负责把文本编码成条件向量,它的默认inputs里是写死的文本。要想把OpenClaw传来的提示词塞进去,必须在提交前修改这个节点的inputs.text字段。我在桥接服务里写了一个工具函数,遍历工作流里的所有节点,找到class_type为CLIPTextEncode的节点,区分正负提示词位置做替换。代码如下:
def inject_prompt(workflow: dict, positive: str, negative: str) -> dict: for node_id, node in workflow.items(): if node["class_type"] == "CLIPTextEncode": current_text = node["inputs"].get("text", "") if "negative" in current_text.lower() or node_id == "7": node["inputs"]["text"] = negative else: node["inputs"]["text"] = positive return workflow这里的node_id == "7"是默认工作流里负提示词节点的常见ID,但不同工作流可能不一样,实际用的时候建议手动指定正负节点的ID映射,写成配置项,不要硬编码。这个环节处理好了,“OpenClaw说什么 → 画什么”的顺手感就出来了;处理不好,你会发现自己提的词全被塞进了负面提示词里,生成结果永远跟你对着干。
4. OpenClaw技能开发与联动调试
4.1 Skill目录结构与注册机制
OpenClaw这边要做的就是开发一个技能。技能的本质是一个可执行的脚本加上一份声明文件,声明文件告诉OpenClaw这个技能叫什么、什么时候该被调用、需要哪些参数。以我用的txt2img技能为例,目录结构长这样:
skills/ └── txt2img/ ├── SKILL.md └── run.pySKILL.md里要写清楚技能的描述和参数格式,OpenClaw会把描述交给大模型理解,大模型根据用户对话判断要不要调用这个技能。描述的写法直接影响Agent的调用准确率,写得太泛,Agent会在画猫的时候跑去调用画狗的技能;写得太死,Agent又识别不出用户意图的变体。我的经验是在描述里多列几个同义表达,比如“生成图片、画、绘图、创作图像”都写进去。
4.2 技能脚本与桥接服务的联动
run.py的职责是接收OpenClaw解析后的参数,然后请求本地桥接网关。下面是一段示意代码:
import json import sys import urllib.request def main(): # OpenClaw会以JSON形式传入解析好的参数 params = json.loads(sys.argv[1]) prompt = params.get("prompt", "") width = params.get("width", 512) height = params.get("height", 512) req = urllib.request.Request( "http://127.0.0.1:8000/txt2img", data=json.dumps({ "prompt": prompt, "width": width, "height": height }).encode("utf-8"), headers={"Content-Type": "application/json"}, ) with urllib.request.urlopen(req) as resp: result = json.loads(resp.read().decode("utf-8")) print(json.dumps(result)) if __name__ == "__main__": main()有人会问:为什么技能脚本不直接连ComfyUI,非要绕一道FastAPI网关?如果只是简单跑通,确实可以不绕。但当你同时有文生图、图生图、局部重绘、批量生成多个能力时,每个技能脚本都自己处理ComfyUI的工作流逻辑,代码爆炸是迟早的事。网关的存在让技能脚本保持轻薄,只做“发请求、收结果”这一件事。
4.3 联调时的完整数据流
整条链路我实测最多的路径是:用户对OpenClaw说“画一只戴耳机的猫,霓虹色背景”,OpenClaw判断意图匹配txt2img技能,解析出prompt和尺寸,调用run.py脚本,脚本带着参数请求桥接网关的/txt2img接口,网关加载工作流JSON、注入提示词、调用ComfyUI的/prompt提交任务,轮询/history拿到图片文件名,再把这个文件名或本地路径返回给run.py,最终由OpenClaw组织语言告诉用户“图已经生成了,保存在某某目录”。这个流程走通的那一刻,说实话挺有成就感的。
参数映射这件事也值得展开说。用户说“画得精细一点”,OpenClaw传给技能的可能只是个模糊表述,技能脚本要负责把它翻译成明确的steps值。我在网关里做了一层简单的规则映射:用户提到“精细、高质量”就把steps调高到30-40,CFG调到7-8;提到“越快越好”就把steps降到20以下,采样器换成速度快的那款。这些映射规则写在config.py里,方便随时调整。
5. 常见问题与排查技巧实录
5.1 WSL、端口与连接类问题速查
我把部署和联调过程中真实遇到的高频问题整理成了下面的速查表,标题里涉及的热词大多能在这里找到对应答案:
| 问题现象 | 可能原因 | 处理方法 |
|---|---|---|
| OpenClaw安装脚本报“无法安全验证WSL2环境” | WSL内核过旧或可选功能未开启 | 执行wsl --status检查,然后wsl --update更新内核 |
| OpenClaw连不上Ollama | Ollama默认只监听127.0.0.1 | 设置OLLAMA_HOST=0.0.0.0并重启服务,检查模型名是否写对 |
| ComfyUI提交工作流返回400 | 工作流JSON节点结构错误 | 回到ComfyUI界面跑通同等工作流后重新导出JSON |
| 图片生成成功但Agent拿不到结果 | ComfyUI的output目录与桥接服务预期路径不一致 | 统一两边的输出目录,或在网关里映射实际落盘路径 |
| 显存不足,生成过程中OOM | 对话模型与图像模型同时占显存 | 降低Ollama上下文长度,减小ComfyUI批量大小或分辨率 |
5.2 安卓端部署与算力边界
热词里有人搜“安卓Termux安装OpenClaw”,说明很多人想用手机跑Agent。安卓端部署OpenClaw是可以实现的,Termux里装好Node.js和依赖,对准对应版本配置就能跑起来。但要说清楚一个现实:手机上的算力支撑对话模型和图像生成都很勉强,尤其是ComfyUI这种吃显存的应用,手机基本跑不了。我实测的合理用法是:手机上跑OpenClaw做Agent入口,桥接网关和ComfyUI放在局域网内的PC上,手机通过局域网IP访问PC的服务。这样既保住了移动端随时对话的体验,又不用牺牲图像生成质量。
5.3 我踩过最深的三个坑
第一个坑是ComfyUI的端口写错。ComfyUI默认端口是8188,FastAPI网关我用了8000。有次改配置时手滑把网关的ComfyUI地址写成了127.0.0.1:8000,结果自连自己,提交任务直接连拒绝。这个错误排查了半天才反应过来,后来我把所有端口配置集中在config.py里并加了注释,才没有再犯。
第二个坑是OpenClaw技能的参数解析方式。不同版本的OpenClaw传参数给技能脚本的方式不太一样,有的用命令行参数传JSON,有的从标准输入读。我第一次按命令行参数写的run.py,在新版本下直接收到空值,还得去翻对应版本的文档查协议。建议初学者先在技能脚本里把接收到的原始参数打印出来,确认格式后再写业务逻辑。
第三个坑是图片结果的回传路径。ComfyUI默认把生成图片存到它自己的output目录,文件名是带随机后缀的。如果桥接网关跑在另外一台机器或者跟OpenClaw不在同一目录,路径映射不统一,Agent就会给用户返回一个打不开的文件路径。我的解决方案是在网关里复制图片到共享输出目录,并把绝对路径组装好返回,确保用户拿到手就能直接打开。
5.4 性能调优的几个实操建议
整条链路跑稳定之后,我又花了一些时间做性能调优。第一个优化是把ComfyUI的模型预加载打开,避免每次生成都重新加载模型到显存,明显缩短了首图等待时间。第二个优化是在网关里加了一个简单的请求队列,OpenClaw并发传多个生成任务时,按顺序提交给ComfyUI,避免同时压多个任务导致显存爆炸。第三个优化是给ComfyUI配置了--cache-lru参数,把经常用的CLIP模型和VAE模型缓存起来,多次生成时减少重复加载。
我实测下来,16GB显存的机器,跑SD 1.5的文生图工作流,512x512分辨率,单张图从提交到出图大概10-20秒,步数越低越快。如果要跑SDXL,建议分辨率控制在1024以下,批量大小保持为1。对话模型方面,7B量化模型配合短上下文,首响应速度体感很快,整套链路日常使用完全扛得住。
写在最后的个人体会
整套OpenClaw x ComfyUI本地桥接部署下来,我最大的感受是:本地AI应用组合拳的想象空间比单玩任何一个工具都大。以前用ComfyUI,灵感来了还得自己动手拖节点、调参数;现在对着Agent说一句话就能出图,而且整个链路离线可控,不依赖任何外部服务。部署过程中踩坑最多的不是功能逻辑,反而是环境准备和路径映射这些不起眼的环节,所以我这篇花了不少篇幅写问题排查,希望后来的人能少走这些弯路。
如果你也想试试,建议先从最小闭环开始:OpenClaw能对话、ComfyUI能出图,两者先别连,各自跑通了再上桥接网关。一次只引入一个变量,出问题才好定位。后续如果你想让Agent具备更多视觉能力,往图生图、局部重绘、甚至是配合ROS2机器人做环境感知生成的方向扩展,这套网关架构都能平滑承接。我个人的下一步计划是给桥接层加上更多工作流模板,再试着把Agent的对话历史接到生成参数里,做更有上下文感的图像创作。这条路走起来挺有意思的,希望你也能跑出自己的效果。