05 · 输入与图验证
验证器是外部 JSON 与内部 Python 执行之间的信任边界。它的产物不只是“真/假”,而是:哪些输出可以安全执行、整张请求的顶层错误、以及按节点归组的可解释错误。
为什么不能边执行边检查
一个 Prompt 可能在最后一个节点才暴露类型错。如果前面的 Save、API 或模型节点已经产生副作用,再报告错误就很难回滚。ComfyUI 因此在入队前完成结构和 schema 验证,但不真正加载模型或运行计算。
execution.pyL846-1247递归输入验证与 validate_prompt 汇总。第一层:Prompt 顶层结构
验证首先面对的是字典键和节点记录,而不是张量。典型失败包括:
- Prompt 不是预期映射;
- 某节点缺少
class_type; class_type不在NODE_CLASS_MAPPINGS;inputs缺失或结构不正确;- 没有任何可执行的输出节点;
- partial targets 指向不存在或不合格的节点。
这些属于请求级错误,通常没有必要把任务放入 PromptQueue。
第二层:只验证真正的输出闭包
验证器遍历声明为输出的节点,并递归验证它们的上游。它返回 good_outputs:即便 Prompt 中有多个输出,其中一个分支无效,也能精确记录是哪一组输出失败。
这与“只执行需求闭包”一致:孤立的草稿节点不应阻止另一条完好输出链执行。反过来,一个无效上游会沿依赖使对应输出无效。
第三层:递归与环检测
validate_inputs() 接收两个重要状态:
validated # 已验证节点的 memo,避免重复工作
visiting # 当前 DFS 路径,用于发现回边当节点已在 visiting 中,说明出现依赖环。代码从第一次出现位置截取路径,加回当前节点,构造类似:
12 (KSampler) -> 18 (SomeNode) -> 12 (KSampler)然后给环内每个节点写入 dependency_cycle,而不是只责怪最后访问到的一个节点。
静态环在入队前发现;运行时 expand 新增的环则由 ExecutionList 再做防守性检查。
第四层:输入 schema
required 与 optional
节点声明:
{
"required": {"steps": ("INT", {"min": 1, "max": 10000})},
"optional": {"noise_mask": ("MASK",)}
}缺失 required 生成 required_input_missing;缺失 optional 不报错。隐藏输入不依赖客户端提供,由执行阶段注入。
V3 怎样进入同一验证器
V3 节点先把 Schema.inputs 最终化为兼容的输入描述,并记录嵌套/动态输入元数据;自定义验证方法名为 validate_inputs。V1 则调用 INPUT_TYPES(),可选方法名是 VALIDATE_INPUTS。
两种协议最后汇入同一条值、链接与递归验证链,避免维护两套执行语义。
链接输入验证
当值是列表时,协议优先按链接解释。验证器要求:
- 恰好两个元素;
- 第一个元素指向存在的上游节点;
- 第二个元素是有效输出 slot;
- 上游
RETURN_TYPES[slot]能连接到当前输入类型; - 上游节点本身递归通过验证。
错误会包含 received_type、期待的 input config 与 linked_node,让前端能把问题定位到具体插槽。
为什么普通列表值要包装
列表已被链接协议占用。一个 widget 若本来就需要传数组,可包装成:
{ "__value__": [1, 2, 3] }验证器会解包 __value__,避免把业务数组误判成 [node_id, slot]。V3 typed wrapper 也走类似归一化。
这是协议设计的历史成本:紧凑链接表示很方便,但需要逃生舱表达真正的 list 常量。
常量输入验证
基础类型转换
对于 INT、FLOAT、STRING、BOOLEAN,验证器尝试转换并把归一后的值写回 Prompt。失败产生 invalid_input_type。
这意味着节点执行时通常拿到规范化类型,但要注意 bool("false") 在 Python 中为真一类语言语义;可靠客户端仍应发送正确 JSON 类型,而不是依赖宽松转换。
数值约束
若 input config 声明 min / max,验证器比较范围,分别生成小于最小值或大于最大值的错误。
组合值
当类型声明是可选值列表时,常量必须在列表中。模型文件名下拉框就是典型例子:这既提供 UI 选项,也在提交时防止不存在的名字直接进入 Loader。
自定义验证接管
如果输入名出现在自定义验证函数的参数中,通用范围/组合检查会让位给节点逻辑。节点可以一次看到相关参数,实现跨字段约束。
V1 VALIDATE_INPUTS 与 V3 validate_inputs
自定义验证可返回:
True:通过;- 字符串:失败原因;
- 其他不符合约定的值:按协议错误处理;
- 抛出异常:记录异常类型、消息和 traceback。
验证函数可能是异步的,也参与列表映射。运行时通过函数签名只传它声明需要的输入;若接收 **kwargs,则可接收完整集合。
自定义验证不应做重量级推理或不可逆副作用。
POST /prompt在 aiohttp 事件循环上等待验证结果,慢验证会直接拖慢控制面。
类型系统的强与弱
ComfyUI 类型多是字符串标识,例如 MODEL、CLIP、LATENT、IMAGE。链接验证比较这些声明,也支持协议定义的通配/联合规则。
它能阻止把 IMAGE 直接接到 MODEL,却不能证明:
- 两个
MODEL是否来自兼容架构; - latent 通道数是否符合目标模型;
- 文本编码与某个 checkpoint 是否语义匹配;
- 张量 shape 在运行时分支中一定正确。
因此采样或矩阵乘法仍可能抛 shape mismatch,执行器会添加针对常见 CLIP/Sampler 组合的提示。
错误数据结构
概念上,一个节点错误包含:
{
"type": "return_type_mismatch",
"message": "Return type mismatch between linked nodes",
"details": "model, received_type(IMAGE) mismatch input_type(MODEL)",
"extra_info": {
"input_name": "model",
"received_type": "IMAGE",
"linked_node": ["7", 0]
}
}type 适合机器分支,message 适合人读,details 和 extra_info 用于前端标注与调试。把三者分开比只返回一串异常文本更可演进。
一个坏 Prompt 怎样被报告
假设:
- KSampler 的
steps = 0; model错接了 IMAGE;- 另一个 SaveImage 分支完全正常。
验证器会:
- 对 steps 记录
value_smaller_than_min; - 对 model 记录
return_type_mismatch; - 将 KSampler 所在输出从
good_outputs排除; - 保留另一个可执行输出;
- 返回按输出关联的
node_errors。
是否允许部分输出执行取决于最终 validate_prompt 汇总规则与请求目标;客户端不应只看 HTTP status,要读取响应体中的 output/node 级状态。
验证明确不做什么
| 不做 | 原因 / 后续层 |
|---|---|
| 打开 checkpoint 并检查权重 | 太重;由 Loader / comfy.sd 处理 |
| 分配 GPU 显存 | 入队前不应改变推理资源 |
| 保证输出文件可写到底 | 文件系统状态会变化;执行节点处理 |
| 验证运行时 expand 子图 | 子图此时尚不存在;调度器二次防守 |
| 保证 API key 有效 | 可能需远端 I/O;API node 执行时处理 |
| 隔离恶意 Python 节点 | 节点已在进程内,schema 不是沙箱 |
节点作者检查表
INPUT_TYPES/Schema的 required、optional、hidden 是否准确?- 可枚举输入能否用组合列表,让前端与验证共享约束?
- 范围能否声明为
min/max/step,而不是埋在执行异常里? - 跨字段约束才使用自定义验证,且保持轻量、确定、无副作用。
- 自定义错误返回能否告诉用户“哪一项、收到什么、期待什么”?
- V3 嵌套输入是否通过 Schema 表达,而不是手工解析扁平 key?
可迁移的设计方法
- 在进入 durable / expensive queue 前完成纯结构验证。
- DFS 同时维护 memo 与当前路径:一个提速,一个找环。
- 错误采用稳定 code + 人类消息 + 结构化上下文三层。
- 通用 validator 提供 80% 规则,扩展点只承接跨字段/领域约束。
- 明确验证边界;schema 通过不等于业务执行必然成功。