Nodes/From 3D to Campaign/Casa · copy prompt
ComfyUI Node

Casa · copy prompt

Build the LLM prompt with a fact whitelist, so it can't invent a second bathroom

By meryboth·Created 25 days ago·Updated 25 days ago· 0
Casa · copy prompt
    • system_prompt
    • user_prompt
    • slots_json
    ◄language▾►
    ◄piecestreet_restored►
    ◄scene_noteFacade from across the street, golden hour.►
    ◄slots{ "claim": { "max_chars": 42, "what": "the post's claim, lowercase, no period" }, "headline": { "max_chars": 38, "what": "headline for this image" }, "caption": { "max_chars": 90, "what": "one sentence under the image" }, "post_caption": { "max_chars": 420, "what": "the caption of the post, 2 short paragraphs" }, "cta": { "max_chars": 26, "what": "call to action, imperative" } }►

    An LLM asked to write a listing caption will write "three bedrooms, two bathrooms, minutes from the park." Two of those facts are legal exposure and one of them is invented. The fix isn't a better model, it's a narrower brief - and that's all this node is. It doesn't call an LLM and it doesn't need an API key. It builds the two prompt strings you hand to whatever LLM node you already have.

    What it assembles

    It reads two config files from the pack and formats them into a system prompt:

    • config/copy.json → the fact whitelist. Project name, typology, neighbourhood, lot, square metres, rooms, patios, price, and the status string in your chosen language. The prompt is blunt about it: "Never invent a feature, a room, an amenity, a distance to anything... If a fact is not in this list, it does not exist."
    • config/brand.json → positioning, brand name, the voice do's and don'ts (short plain sentences; talk about light, patios, proportions; no luxury clichés, no exclamation marks, no promises about the neighbourhood's future value).

    Then it adds the language rules, which are more opinionated than most prompt builders would dare: Spanish means rioplatense with vos, not peninsular tú; Portuguese means Brazilian, not European; English is neutral international with metric units. And it spells out the output shape - one JSON object, every value a plain string, never an object, never a list - with each slot's character limit described as a layout constraint rather than a suggestion. That last bit is the useful framing, and it's true: a 42-character claim is 42 characters because the type block has room for 42.

    The inputs

    language (es / en / pt) and piece - a string key naming which asset you're writing for (street_restored in the defaults, matching the pieces in config/copy.json). scene_note is your description of what the image actually shows ("Facade from across the street, golden hour.") so the copy matches the picture rather than the project in the abstract.

    slots is the one worth editing, a JSON dict of {"max_chars": n, "what": "..."}. The defaults are claim 42, headline 38, caption 90, post_caption 420, and a cta at 26 - add, remove or re-limit them and both the prompt and the downstream validator follow, because the same JSON is echoed out of the node untouched.

    The outputs

    system_prompt → your LLM node's system field. user_prompt → the user message; it's just the piece, the scene note and "Write the JSON now, in es". slots_json → CasaCopyCheck, which is the whole point of echoing it.

    That's the wiring: CasaCopySpec.system_prompt and .user_prompt into an LLM node, its text output into CasaCopyCheck.llm_output, and .slots_json into CasaCopyCheck.slots_json. The pack's master graph uses a Claude node for the middle step; any node that returns text works, because the prompt builder and the validator are indifferent to who answers.

    Install

    Same pack - ComfyUI Manager → From 3D to Campaign, or:

    cd ComfyUI/custom_nodes
    git clone https://github.com/meryboth/from-3d-to-campaign
    

    Restart, and it's under from 3d to campaign. No models, no Node.js, no pip installs for this node. The only requirement is that the repo layout survives intact, because the node reads config/copy.json and config/brand.json from the pack root. Registry installs are fine; copying the .py out on its own is not.

    The trap that catches everyone

    If you use a partner API node (Claude, Gemini, GPT - anything billed through Comfy), be aware of two things the README calls out and the journal documents the hard way. ComfyUI attaches the auth token in the frontend, so a prompt queued directly against the HTTP API comes back Unauthorized even when you're signed in and sitting right there. And partner nodes bill against a pay-as-you-go credit balance that is separate from a Cloud plan - the author's own run reported "Payment Required" on the copy node while the same model went through fine in the cloud. Run those nodes from the app window, and check the balance, not the plan.

    Second trap, straight from the general LLM-in-a-graph problem: chat models like to answer with a preamble and a markdown fence. CasaCopySpec asks for bare JSON and gets it often enough to be useful; when it doesn't, the failure surfaces one node downstream as not valid JSON. If you're swapping in a local model, look for hard stopping or a cleaner step rather than hoping.

    One honest note on the defaults

    The shipped config/copy.json describes an invented developer and an invented house, on purpose - the repo states it up front, along with a footnote that 1890s Buenos Aires typologies and metric lot dimensions are the shape of project this is built for. Edit those files for your own listing before you trust a single sentence the LLM writes. The whitelist is only as good as the facts in it.

    Categoryfrom 3d to campaign

    Inputs (4)

    NameTypeDefaultDescription
    languageCOMBO3 options: es, en, pt
    pieceSTRINGstreet_restored—
    scene_noteSTRINGFacade from across the street, golden hour.—
    slotsSTRING{ "claim": { "max_chars": 42, "what": "the post's claim, lowercase, no period" }, "headline": { "max_chars": 38, "what": "headline for this image" }, "caption": { "max_chars": 90, "what": "one sentence under the image" }, "post_caption": { "max_chars": 420, "what": "the caption of the post, 2 short paragraphs" }, "cta": { "max_chars": 26, "what": "call to action, imperative" } }—

    Outputs (3)

    NameTypeDescription
    system_promptSTRING—
    user_promptSTRING—
    slots_jsonSTRING—