Casa · copy check
The validator that catches an LLM echoing your JSON schema back at you
- ok
- report
- claim
- headline
- caption
- post_caption
The first run of this pipeline got rejected in all three languages, and the model was fine. The prompt had shown the JSON schema as the thing to return, so the model returned the schema. The validator's complaint was one line: claim: expected a string, got dict (model echoed the schema). That's a bug that looks exactly like a model problem, caught in milliseconds for zero tokens - which is the argument for having a validator at all rather than regenerating and hoping.
CasaCopyCheck is that validator. It takes the LLM's raw text plus the slot spec, and it's the gate between the language model and your layout.
What it checks, in order
It strips markdown fences (```json and friends) and tries json.loads. Fail there and you get not valid JSON: <the parser error> with everything else empty - the honest outcome, since there's no partial recovery worth doing.
Then per slot:
- Type. A dict or a list where a string was asked for is called out by name as schema echoing.
- Presence. Empty after
.strip()→missing. - Length. Over
max_chars→headline: 44 chars > 38. - Banned phrases, per language. Spanish gets
oportunidad única,sueño,exclusivo,lujo,imperdible,inversión segura; English its own list; Portuguese its own. The brand voice already forbids luxury clichés in the prompt - this is the enforcement that doesn't depend on the model complying. - Exclamation marks, including the inverted Spanish
¡. - Numbers. Every digit sequence in every slot has to match the project facts, or it's flagged as
number '24' is not in the project facts.
That last check is the one that earns the node its place in a real-estate pipeline. It's a hard constraint on invented facts, and it's cheap. It's also merciless in a way you should plan around: the allowlist is the area, rooms, patios, price, the lot dimensions and a few identifiers. A year, a phone digit, a bus route, "24/7" - all rejected. For a listing caption, that's the correct behaviour. If you're reusing the node for something else, loosen config/copy.json first.
Inputs and outputs
llm_output and slots_json are both forceInput, so they have to be links, not typed text - wire the first from your LLM node and the second from CasaCopySpec.slots_json. language must match what CasaCopySpec used, or you'll be checking Spanish copy against the English banned list.
Six outputs. report is COPY OK or COPY REJECTED with a bulleted issue list. ok is the boolean. And then four strings - claim, headline, caption, post_caption - pulled straight out of the parsed JSON, which means you can wire headline directly into CasaLayout.headline without a separate JSON-parsing detour.
One quirk worth knowing: cta is in the default slots and gets fully validated, but it isn't returned as an output. If your layout needs a call to action, either read it off the LLM node's own text output or add it to the slots and parse it yourself.
The repair loop is a pattern, not a node
The README describes a repair call - send the issue list back to the model instead of regenerating from scratch, because it's far cheaper. In the shipped code that's not an automatic retry node: CasaCopyCheck returns ok and report and stops there. What you do with them is graph work. The minimal version is to look at the report and re-run the LLM node with the issues appended to the user prompt; with ok as a boolean you can also drive a switch to route failed copy somewhere harmless rather than into the layout. Just don't go looking for a retry widget that isn't there.
Install
ComfyUI Manager → From 3D to Campaign, or clone into ComfyUI/custom_nodes and restart. There's nothing to pip install for this node and no models involved; it's pure Python string work on top of config/copy.json.
Failure modes worth expecting
Character limits are characters, not words. The defaults are cramped on purpose - a 42-character claim and a 38-character headline are layout constraints from a 1080px grid. Romance languages run long. If your Spanish line is always three characters over, raise the limit in slots and check that the layout still holds; the limit exists because the type block doesn't have room.
Banned phrases are substring matches on lowercase text, so a plural or an inflection slips through. "Oportunidades únicas" passes. It's a coarse filter by design, and the human review step is still the human review step.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| llm_output | STRING | — | |
| slots_json | STRING | — | |
| language | COMBO | 3 options: es, en, pt |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| ok | BOOLEAN | — |
| report | STRING | — |
| claim | STRING | — |
| headline | STRING | — |
| caption | STRING | — |
| post_caption | STRING | — |