Arrakis OpenRouter (Vision + Reasoning)
One node that sends your images to Grok, Claude, or any OpenRouter model
- user_image
- raw_response
- json_response
- value_1
- value_2
- value_3
- value_4
- value_5
- value_6
- value_7
- status
Arrakis is one node, and it turns ComfyUI into something it stubbornly refuses to be on its own: a thing that can look at an image and tell you what's in it. Wire a Load Image into it, type a question, and a frontier LLM - by default Grok 4.1, but any model OpenRouter routes - reads your render and answers. No VAE, no CLIP, no local model download. Just your API key and the internet.
Why this instead of ComfyUI's built-in "API nodes"? Those route through Comfy Cloud credits and want you logged into a paid Comfy account. This is bring-your-own-key, exactly what the community keeps asking Comfy for. One OpenRouter key gets you Grok, Claude, GPT, Gemini, and a few hundred Llama variants - swap model and the same graph keeps working.
How it works
It's a plain HTTP client with good manners. It builds a chat request and POSTs it to https://openrouter.ai/api/v1/chat/completions, in the order OpenRouter's multimodal guidance wants: system message first, then your user text, then the image parts appended to the same user message - text before image, always.
The clever bit is the image handling. You feed it a real ComfyUI IMAGE tensor and it does the rest: converts to numpy, encodes as base64 JPEG data URLs, downscales anything over 1 megapixel, and if you feed a batch it samples at most three frames (first, middle, last) rather than torching your OpenRouter balance on every frame. Responses get a genuinely defensive JSON parser: direct decode, then markdown-fence stripping, then a hunt through the text for JSON fragments. Models wrap output in ```json fences constantly; this one doesn't care. It also retries on its own - 429s and other retryable errors get backoff (honoring Retry-After), up to max_retries.
Inputs and outputs that matter
For a beginner, only a few fields earn your attention:
api_key- paste the key, or reference an env var like$OPENROUTER_TOKENand keep the secret out of your workflow file. Do that. Workflows get embedded in PNG metadata, and a literal key travels with every image you share.model- any OpenRouter model id; default isx-ai/grok-4.1-fast.reasoning_level-nonethroughxhigh. The node sends"reasoning": {"effort": ...}for everything exceptnone, which omits the field entirely.user_image- theIMAGEtensor. Leave it disconnected and you've got a plain text LLM node.value_keys- comma-separated JSON paths (name,stats.hp) to map the response into the numbered outputs. Nested keys with dots work.
Everything else is plumbing. custom_parameters is a raw JSON blob merged into the request body for things like temperature or response_format - note max_tokens inside it is ignored, use the dedicated input (0 = let the model decide). max_retries and timeout are what they sound like; enforce_json_output appends a strict "JSON only, no fences" suffix to the system prompt.
Outputs: raw_response (raw text), json_response (pretty-printed, parsed JSON), value_1 through value_7 (auto-extracted fields, or your value_keys picks), and status - a JSON object with status, model, tokens_used, request_id, and the error detail if it failed. Wire json_response or a value_N into anything that wants a STRING and data flows back into the graph.
Installing it
ComfyUI Manager, search the pack title, install, restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/adbrasi/rota_do_imperador-openrouter-single-node-public
Dependencies are just requests and json5 - nothing heavy, no model files, no separate runtime. One thing that trips people: the repo name says "node" and the README warns that this is not a Node.js app - it's a ComfyUI Python node. Running python __init__.py just prints a smoke-test status; it doesn't start a server.
Where people get burned
- Empty
raw_responsebut no error. Check thestatusoutput, not the console - the node logs a full JSON status with the error detail on every failure. Flipdebugon for a payload dump too. - The
seedinput. The README claims it caches responses so unchanged inputs don't re-bill you. In the shipped code it's accepted and logged but not actually wired to a cache - don't count on it yet. - Silent image truncation. A 60-frame batch becomes three images. If you needed all frames analyzed, that's not what this does.
- Key hygiene. The ComfyUI_LLMVISION incident - a vision/LLM node that shipped malware and stole keys - is the reason to be picky about which API nodes you install and never hardcode keys. This one's a thin, readable wrapper; that's the kind you want, but the lesson stands: review what you install, use env vars.
If the model ignores a parameter you passed, that's usually OpenRouter routing to a provider that doesn't support it, not the node eating it. And if you don't have an OpenRouter key yet, that's the actual prerequisite.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| api_key | STRING | — | |
| system_prompt | STRING | You are a structured output assistant. Return exactly one valid JSON object and nothing else. Do not wrap JSON in markdown fences. | — |
| user_prompt | STRING | — | |
| reasoning_level | COMBO | low | 6 options: none, low, minimal, medium, high, xhigh |
| max_tokens | INT | 00–128000 | 0 = sem limite (deixa o modelo decidir) |
| model | STRING | x-ai/grok-4.1-fast | — |
| seed | INT | 00–18446744073709550000 | — |
| user_imageopt | IMAGE | — | |
| custom_parametersopt | STRING | {} | — |
| timeoutopt | INT | 6010–300 | — |
| max_retriesopt | INT | 31–10 | — |
| enforce_json_outputopt | BOOLEAN | true | — |
| debugopt | BOOLEAN | false | — |
| value_keysopt | STRING | Chaves JSON separadas por vírgula para mapear em value_1..value_7. Suporta acesso aninhado com ponto (ex: 'name,stats.hp,stats.mp'). Vazio = extração automática na ordem do JSON. |
Outputs (10)
| Name | Type | Description |
|---|---|---|
| raw_response | STRING | — |
| json_response | STRING | — |
| value_1 | STRING | — |
| value_2 | STRING | — |
| value_3 | STRING | — |
| value_4 | STRING | — |
| value_5 | STRING | — |
| value_6 | STRING | — |
| value_7 | STRING | — |
| status | STRING | — |