Initial commit: VLM-as-judge prompt calibration loop

Qwen3-VL image-similarity judge node, external-prompt receptor node,
agent_bridge CLI, example SDXL workflow, and methodology/agent-loop/
calibration-policy docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 22:15:56 +02:00
commit 95198a15b5
13 changed files with 1294 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
"""
Calibrator Prompt Receptor node.
The injection point for the external CLI-agent controller. The agent overrides
this node's widget values per queue via the ComfyUI HTTP API (`POST /prompt`,
override by node id), or — as a fallback — points `source_file` at a JSON file
the agent writes. Its outputs feed the T2I sampler in place of a static prompt.
This is the "receptor in ComfyUI" in the loop:
agent -> (sets prompt here) -> T2I -> Qwen3-VL Judge -> analysis -> agent
"""
from __future__ import annotations
import json
import os
class CalibratorPromptReceptor:
CATEGORY = "prompt_calibrator"
FUNCTION = "emit"
RETURN_TYPES = ("STRING", "STRING", "INT")
RETURN_NAMES = ("prompt", "negative", "seed")
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"negative": ("STRING", {"default": "", "multiline": True}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0x7FFFFFFFFFFFFFFF}),
},
"optional": {
# If set and present, a JSON file {prompt, negative, seed} overrides
# the widgets above. Lets the agent drive the loop file-first if it
# prefers that to the HTTP API.
"source_file": ("STRING", {"default": ""}),
},
}
@classmethod
def IS_CHANGED(cls, prompt, negative, seed, source_file=""):
# Re-run whenever the effective inputs change: widget values (API override)
# OR the source file's mtime (file-driven mode).
mtime = ""
if source_file and os.path.isfile(source_file):
mtime = str(os.path.getmtime(source_file))
return f"{prompt}|{negative}|{seed}|{source_file}|{mtime}"
def emit(self, prompt, negative, seed, source_file=""):
if source_file and os.path.isfile(source_file):
try:
with open(source_file, "r", encoding="utf-8") as f:
data = json.load(f)
prompt = data.get("prompt", prompt)
negative = data.get("negative", negative)
seed = int(data.get("seed", seed))
except (OSError, ValueError, json.JSONDecodeError) as e:
print(f"[CalibratorPromptReceptor] could not read {source_file}: {e}")
return (prompt, negative, int(seed))
NODE_CLASS_MAPPINGS = {"CalibratorPromptReceptor": CalibratorPromptReceptor}
NODE_DISPLAY_NAME_MAPPINGS = {
"CalibratorPromptReceptor": "SxCP External Prompt (Receptor)"
}