Tenosai LLM Image Describe
Point a vision LLM at your reference image and get sampler-ready conditioning back
- clip
- image
- conditioning
The hard part of img2img isn't the generation - it's translating what you see in a reference image into a prompt that actually drives the model. Tenosai LLM Image Describe skips that step: you feed it a reference image, it ships that image to a cloud vision model (OpenAI, Gemini, or Groq), and the model writes a prompt describing it. Then the node encodes that prompt through your CLIP and hands you CONDITIONING, ready to wire straight into a sampler.
That last part is the whole point, and it's easy to miss. This is not a caption generator that dumps text on your screen - the output is conditioning, so it slots into a generation workflow: reference-style transfer, img2img seeding, image-to-video prompt assists. It's the "LLM in the graph" pattern, and this node takes the API path: frontier-model quality per call, but metered, and nothing runs on your GPU.
How it works
Straightforward under the hood. The image gets converted to JPEG, base64-encoded, and sent to whichever provider you picked along with one of three system prompts baked into the source:
- Style - the "Flux Style Condenser" prompt. Extracts only lighting, palette, texture, and rendering technique, and is explicitly forbidden from mentioning the subject. This is the one for style-LO / reference-style workflows where you want the mood and nothing else.
- Subject - the "Flux Content Analyzer". Pure content and composition, no style words. Wire both modes into separate branches and you've got clean style/content separation - the classic trick for mixing the look of one image with the subject of another.
- Describe - the balanced mix of both.
Notable detail: the Gemini path sets its safety settings to BLOCK_NONE in the code, making it the least likely of the three to refuse a description. OpenAI and Groq still filter on their side.
Two things make it worth using over a one-off script. First, hash-based caching - the node MD5-hashes the image bytes and remembers provider/model/mode, so it won't hit the API again unless something actually changed. Requeue the same workflow and you spend zero extra tokens. Second, the CLIP encoding is agnostic: it tries encode_from_tokens+tokenize (with pooled output for SDXL/Flux), then encode_conditioning, encode, encode_text, and finally a bare callable, formatting whatever comes back into [[embeddings, {"pooled_output": ...}]]. It genuinely doesn't care which text encoder you hooked up.
The inputs that matter
clip- your text encoder from a CLIP Loader. No CLIP upstream, no output.image- the reference tensor, any IMAGE output.llm_provider-gemini(default),groq, oropenai. Backed by your API keys.llm_model- dropdown of six"provider: model"pairs, defaultgemini: gemini-1.5-flash. The list comes fromllm_models.json; you can still type any model string.description_mode-Style,Subject, orDescribe.auto_describe_on_image_change- default true. Flip it false and the node won't re-describe on a new image unless you nudge a field.override_api_key- optional string input withforceInput, so it must be wired (the pack's TenosaiAPIKeyInput node exists exactly for this).
Output: a single conditioning connection. Merge it with your text conditioning or feed the sampler directly.
Install and keys
The reliable route is ComfyUI Manager (search Tenos-LLM-Image-Describe) or:
cd ComfyUI/custom_nodes && git clone https://github.com/Tenos-ai/Tenos-LLM-Image-Describe
pip install -r requirements.txt # requests, openai, google-generativeai, groq
then restart. No model downloads, no VRAM cost - everything is an API call, so your GPU only sees the diffusion side.
Keys go in custom_nodes/Tenos-LLM-Image-Describe/api_keys.json - and note the shipped file has literal "key" placeholders, so replacing them with real values (or wiring override_api_key instead) is the first thing you must do. Ignore the README's manual-copy instructions that suggest dumping three files into a tenos_nodes/ folder; the repo is a proper package with the __init__.py and config JSONs it needs, so just clone it whole.
Troubleshooting
Error: No API key providedin the prompt output → empty or placeholderapi_keys.json, or a bad override. Paste a real key, restart.Unknown LLM provider→ provider name typo'd; matchopenai,gemini,groqexactly.- Blank conditioning, no error →
auto_describe_on_image_changeis off and nothing changed, so the cache kicks in. Toggle a field or feed a new image. - CLIP error about encoding methods → you forgot the CLIP Loader upstream. Some custom CLIPs lack pooled output; Flux still runs, just with less global conditioning.
- Groq model ignoring the image → you pointed it at a text-only model. The shipped Groq option is
llava-v1.5-7b(vision-capable); a non-vision model will happily "describe" without looking at the picture.
The honest tradeoff
For bulk-captioning a LoRA training set, this is the wrong tool - reach for a local captioner like JoyCaption or Florence-2 instead (free, uncensored, offline). This node is for when you want GPT-4o/Gemini-grade analysis feeding a generation graph and you accept paying per call and living with provider filtering. Keep batches small - it loops images internally and fires one API call per frame. And since it's an API-wrapper node carrying your key, the usual caution applies: it's small and open source, but read before you run, the way you should with anything in this category.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| clip | CLIP | — | |
| image | IMAGE | — | |
| llm_provider | COMBO | gemini | 3 options: gemini, groq, openai |
| llm_model | COMBO | gemini: gemini-1.5-flash | 6 options: gemini: gemini-1.5-flash, gemini: gemini-1.5-pro, groq: llava-v1.5-7b, openai: gpt-4o, openai: gpt-4o-mini, openai: gpt-4-turbo |
| description_mode | COMBO | Style | 3 options: Style, Subject, Describe |
| auto_describe_on_image_change | BOOLEAN | true | — |
| override_api_keyopt | STRING | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| conditioning | CONDITIONING | — |