Prompt Expansion — the Creative Director Tool¶
creative-director is the first built-in tool. It rewrites a terse prompt into a
single, vivid, self-contained prompt using Google's officially validated five-component
formula, optionally injecting domain-specific (cinema / portrait / product / …) vocabulary, and
deterministically strips low-signal "quality-booster" keywords. It calls any OpenAI-compatible
endpoint server-side and is never fatal — any failure degrades to your original prompt.
This is the tool. For the framework that hosts it (
gflow tools,--tool, the TOML schema, MCP exposure), see TOOLS.md.
1. Quick start¶
# 1. Point at any OpenAI-compatible endpoint. A Google key works out of the
# box against the default endpoint: https://aistudio.google.com/apikey
export GFLOW_CLI_LLM_API_KEY="..." # any OpenAI-compatible endpoint; see CONFIGURATION.md
# 2. Preview an expansion (free of Flow credits)
gflow tools run creative-director "a cat on a couch" --style cinema
# 3. Use it on a real generation
gflow image t2i "a fox in the snow" --tool creative-director:style=cinema
gflow video t2v "a city at night" -t creative-director:style=cinematic
Without GFLOW_CLI_LLM_API_KEY (or GFLOW_CLI_LLM_BASE_URL) the tool leaves your prompt unchanged (see §6).
2. Domain model¶
| Concept | Where | Meaning |
|---|---|---|
| Tool spec | tools/builtin/creative-director.toml |
Single source of truth — name, version, the formula instruction, banned keywords, and the style libraries. |
| System instruction | built at runtime | system_template + the selected style's vocabulary + a single User prompt: marker. |
| Style / domain mode | [[config.domains]] |
A named vocabulary library (camera/lens/lighting refs) injected into the instruction. 9 image + 6 video. |
| Expansion | ExpansionResult(original, expanded, was_expanded) |
The result; expanded == original when nothing changed. |
| Provenance | AppliedTool(name, version, model, config_hash, params) |
Recorded in history when a tool rewrote a prompt. |
The tool itself lives in src/gflow_cli/tools/: spec.py (schema),
runtime.py (apply_tool), expander.py (the provider-agnostic PromptExpander), banned.py
(keyword stripping), invocation.py (provenance + MCP adapter).
3. The five-component formula¶
Based on Google's validated prompt structure for Gemini image/video models. The tool instructs the model to write natural narrative paragraphs — never comma-separated keyword lists — keeping the user's original intent and named subjects intact:
- Subject — who/what is the focus; specific physical characteristics, material, species, age, expression. (Never just "a person" or "a product".)
- Action — what the subject is doing or its primary visual state; strong present-tense verbs, or a described pose/arrangement.
- Location / Context — environment, time of day, atmospheric conditions.
- Composition — camera perspective, framing, spatial relationship (e.g. "intimate close-up from slightly below eye level, shallow depth of field").
- Style (lighting included) — visual register, medium, and lighting combined; references real cameras, film stock, photographers, publications, or art movements. Lighting is a sub-element of Style, not a sixth component.
The instruction also carries "Critical Rules": name real cameras/brands, include micro-details,
use prestigious context anchors ("Vanity Fair editorial", "National Geographic cover"), use ALL
CAPS for hard constraints, "prominently displayed" for products, and never emit banned
keywords. The full text is the system_template in
creative-director.toml.
4. Domain modes (styles)¶
Select with --style <name> (tools run) or :style=<name> (--tool). Each style injects a
vocabulary library of concrete references into the instruction. Styles are category-gated —
image styles apply to image commands, video styles to video commands (see
TOOLS.md §4). product and abstract exist in both categories
with different vocabularies.
Image styles (9)¶
| Style | Flavor of vocabulary |
|---|---|
cinema |
Cine cameras (RED, ARRI Alexa 65), anamorphic lenses, Kodak Vision3 stocks, lighting setups, color grades. |
product |
Surfaces, softbox/tent lighting, hero angles, Apple/Aesop/B&O style refs. |
portrait |
Focal lengths (85/105/135mm), apertures, pose language, skin micro-texture. |
editorial |
Vogue/Harper's/Kinfolk refs, styling notes, locations, editorial poses. |
ui |
Flat vector / isometric / glassmorphism, exact hex palettes, retina sizing. |
logo |
Geometric construction, golden ratio, 2–3 colors, monochrome-safe. |
landscape |
Depth layers, atmospherics, blue/golden/magic hour, weather. |
infographic |
Modular layout, data-viz types, exact-text quoting, accessible palette. |
abstract |
Fractals/voronoi, fluid/ink textures, color harmonies, generative styles. |
Video styles (6)¶
| Style | Flavor of vocabulary |
|---|---|
cinematic |
Camera motion, slow-motion/speed-ramp timing, transitions, film grades. |
documentary |
Observational/handheld, B-roll coverage, vérité aesthetic. |
animation |
Cel/3D/stop-motion, easing curves, flat/isometric visual language. |
social |
9:16/1:1/16:9 formats, hook-in-2s pacing, UGC vs branded aesthetic. |
product |
360 orbit/macro reveal, studio lighting, infinity cove, end-card pacing. |
abstract |
Particle/fluid sims, smoke/ink textures, gradient-shift color, VJ aesthetic. |
An unknown style is ignored with a tool_unknown_style warning — never an error. Full
vocabularies live in creative-director.toml;
gflow tools show creative-director lists the current set.
5. Banned-keyword policy¶
The tool defends against low-signal "quality boosters" in two layers:
- Prevention — the instruction explicitly forbids them.
- Cleanup —
strip_banned_keywordsdeterministically removes any that slip through, on a whole-word, case-insensitive basis, and logstool_banned_keywords_stripped.
The 13 banned terms:
4k, 8k, ultra HD, high resolution, masterpiece, highly detailed, ultra detailed,
trending on artstation, hyperrealistic, ultra realistic, photorealistic, best quality,
award winning
Matching is longest-first (so ultra detailed is stripped before ultra), word-boundaried, and
the resulting separators are tidied (no dangling , , or double spaces). Resolution intent
belongs in generation parameters, not the prompt. Stripping runs only on a rewritten prompt — a
prompt the tool left unchanged is never altered.
6. Never-fatal contract¶
The PromptExpander (expander.py) is
stdlib-only (urllib) and bounded. expand() returns the original prompt with
was_expanded=False — never raising — in every degraded case:
- Neither
GFLOW_CLI_LLM_API_KEYnorGFLOW_CLI_LLM_BASE_URLis set. GFLOW_CLI_LLM_BASE_URLis wrong (bad host, missing/v1).GFLOW_CLI_LLM_MODELnames a model the endpoint does not serve.- Any HTTP error, network failure, timeout, or malformed JSON.
- An empty, whitespace-only, or quote-only model response.
Retryable statuses (429, 500, 502, 503, 504) are retried with exponential backoff (default 3
attempts, base delay 1s, doubling); every other 4xx fails fast (and still degrades to the
original). Providers disagree on the code for a bad key — most gateways answer 401, Google's
compat endpoint answers 400 — but neither is retryable, so both fall back immediately rather
than burning the retry budget. Each attempt has a 20s socket timeout, and the whole call is
bounded by an overall
~60s wall-clock budget (max_total_seconds): retries stop once the budget is spent and each
attempt's timeout is clamped to the remaining budget, so sustained rate limiting can never stall a
batch for the full timeout×retries schedule. (Both bounds are fixed defaults, not env-tunable.)
Input is truncated to max_input_chars (4000) and output to max_output_chars (3500); a single
layer of wrapping quotes is stripped from the response. The net guarantee: a tool can never
abort a generation — the worst case is "your prompt went through unchanged."
7. LLM wiring (I/O)¶
The transport speaks the OpenAI Chat Completions API — the format OpenAI, gateways/proxies, local runtimes and Google's compatibility endpoint all accept. See CONFIGURATION.md for the full env-var reference and worked examples (Google, OpenRouter, a local Docker proxy, keyless Ollama).
| Aspect | Value |
|---|---|
| Endpoint | {GFLOW_CLI_LLM_BASE_URL}/chat/completions — default base https://generativelanguage.googleapis.com/v1beta/openai |
| Auth | Authorization: Bearer <GFLOW_CLI_LLM_API_KEY>, in the header, never the query string. Omitted entirely when no key is set, for keyless local gateways. |
| Redirects | Never followed — urllib would re-send the Authorization header to whatever host a redirect names. |
| Model | TOML config.model pin > GFLOW_CLI_LLM_MODEL > default-endpoint default > omitted so the gateway chooses. The builtins pin nothing. |
| Request | messages = [{role: system, content: instruction}, {role: user, content: prompt}]; temperature=0.7 (0.4 multimodal); max_tokens derived from max_output_chars. |
| Multimodal | Images ride the user message as {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,…"}} parts. |
| Response | choices[0].message.content, cleaned (markdown fence and wrapping quotes stripped, truncated). null content — a refusal — degrades to the original. |
The system instruction is a separate
systemmessage, not concatenated onto the user's prompt as it was under the Gemini-native shape.
8. Provenance — what history records¶
When a tool rewrites a prompt, the generation is recorded with full provenance
(data/recorder.py):
promptcolumn = the user's original prompt.expanded_promptcolumn = the submitted (rewritten) prompt.operations.metadata_json.tool= the applied tool descriptor.
This is redaction-aware, gated on GFLOW_CLI_HISTORY_PROMPTS
(see CONFIGURATION.md and DATA_LAYER.md):
| Mode | expanded_prompt |
metadata_json.tool |
|---|---|---|
store (default) |
stored | {name, version, model, params, config_hash} |
redacted |
withheld | {name, version, params_hash, config_hash} |
In redacted mode the recorder builds the minimal descriptor itself — it does not dump the
full tool dict and lean on key-name redaction, because a free-text option (e.g.
params.style) would otherwise leak verbatim. params_hash is a SHA-256 of the sorted params;
config_hash is a SHA-256 of the resolved ToolConfig, pairing with the hand-bumped version for
tamper-evidence. The metadata_json.tool write rides the post-success recorder safety wrapper, so
a recording failure warns and returns exit 0 — never a non-zero exit after a paid generation.
9. Relationship to the expand_prompt MCP prompt¶
The MCP server also exposes an older, now-deprecated prompt template named expand_prompt
(mcp/prompts.py). It predates the tools framework and overlaps
functionally with creative-director, but they are different surfaces:
expand_prompt (MCP prompt) |
creative-director (tool) |
|
|---|---|---|
| Kind | MCP prompt template — returns text for the client's model to expand. | MCP tool / CLI --tool — runs server-side. |
| API call | None (no Gemini call; the client model does the work). | Calls Gemini via PromptExpander. |
| Input | 5 structured slots (subject, action, setting, camera, lighting). |
A single free-text prompt + optional style. |
| Banned-keyword stripping | No. | Yes (13 terms). |
| Domain styles | No. | Yes (15). |
| Provenance | No. | Yes (expanded_prompt + metadata_json.tool). |
| Formula | Treats lighting as a 5th separate component. | Folds lighting into the Style component. |
Guidance: prefer the creative-director tool (--tool / the MCP tools array) for actual
generation — it is the maintained, provenance-recording, server-side path. expand_prompt is
deprecated as of v0.22.0: its client-visible description carries a [DEPRECATED]
marker pointing to the tool, and it is slated for removal in a future major release. It remains
functional for now because some MCP clients surface prompts as user-pickable templates (a distinct
UX from agent-invoked tools), but no new work should depend on it. See
TOOLS.md §8.
10. Configuration¶
| Env var | Default | Purpose |
|---|---|---|
GFLOW_CLI_LLM_BASE_URL |
Google compat endpoint | OpenAI-compatible endpoint. The on/off switch. |
GFLOW_CLI_LLM_API_KEY |
— | Credential for that endpoint; optional for a keyless local gateway. Google key. |
GFLOW_CLI_LLM_MODEL |
— | Model, and therefore the provider selector. |
GFLOW_CLI_HISTORY_PROMPTS |
store |
redacted withholds the expanded prompt and minimizes the recorded tool descriptor (§8). |
See CONFIGURATION.md for the full precedence chain.
11. Credit¶
The Creative Director patterns — the five-component formula, the banned-keyword policy, and the domain-vocabulary libraries — are adapted from the banana-claude evaluation (PR #202) of Google's published Gemini prompt-engineering guidance.
12. See also¶
- TOOLS.md — the tools framework.
- CONFIGURATION.md — env vars and precedence.
- DATA_LAYER.md —
expanded_prompt,metadata_json, redaction. - USAGE.md — command reference for
image/video.