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:
@@ -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)"
|
||||
}
|
||||
Reference in New Issue
Block a user