I built an internal tool that turns one-line product ideas into production-ready SVG illustrations. It uses a three-stage LLM pipeline for art direction, code generation, and self-validation. I run it on Oxlo.ai because the flat per-request pricing keeps costs predictable even when I feed long SVG system prompts and run multiple inference steps per asset.
What you'll need
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai
Step 1: Set up the Oxlo.ai client
First I verify connectivity. Oxlo.ai is fully OpenAI SDK compatible, so this is a drop-in replacement.
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ.get("OXLO_API_KEY")
)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[{"role": "user", "content": "Say hello"}],
max_tokens=10
)
print(response.choices[0].message.content)
Step 2: Build the art direction layer
Raw prompts like "a rocket" produce generic SVGs. I use Llama 3.3 70B to expand the concept into a structured brief with palette, composition, and primitive types. This keeps the downstream model focused on code, not creativity.
ART_DIRECTION_PROMPT = """You are an art director. Convert the user's concept into a structured brief for an SVG illustrator. Output only a JSON object with keys: subject, style, color_palette (list of 4 hex codes), composition, and key_elements (list). Be concise."""
def generate_brief(concept):
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": ART_DIRECTION_PROMPT},
{"role": "user", "content": concept}
],
temperature=0.7
)
return response.choices[0].message.content
Step 3: Generate constrained SVG output
This is where most tutorials fail. I constrain Qwen 3 32B with a tight system prompt that forbids CSS, animations, and external references. Valid SVG only.
Here is the system prompt I use for the SVG agent:
SVG_SYSTEM_PROMPT = """You are an expert SVG illustrator. You write only valid, standalone SVG markup.
Rules:
- Output raw SVG XML starting with <svg xmlns="http://www.w3.org/2000/svg">.
- Use only basic shapes: rect, circle, ellipse, path, polygon, line.
- No CSS classes, no <style> blocks, no animations, no external images.
- Set explicit fill and stroke attributes on every element.
- ViewBox must be 0 0 800 600.
- Keep it under 40 elements for fast rendering."""
And the generation function:
def generate_svg(brief):
response = client.chat.completions.create(
model="qwen-3-32b",
messages=[
{"role": "system", "content": SVG_SYSTEM_PROMPT},
{"role": "user", "content": f"Create an SVG based on this brief:\n{brief}"}
],
temperature=0.4
)
svg_code = response.choices[0].message.content
svg_code = svg_code.replace("
```xml", "").replace("```
", "").strip()
return svg_code
Step 4: Add an LLM-based validation pass
LLMs sometimes hallucinate invalid attributes or unclosed tags. I run a second pass with DeepSeek V3.2 to audit the SVG and return a corrected version if needed.
VALIDATION_PROMPT = """You are a strict SVG validator. Check the provided SVG for these issues: unclosed tags, missing xmlns, CSS dependencies, invalid attributes, or viewBox errors. If valid, return it unchanged. If invalid, return the corrected SVG and nothing else."""
def validate_svg(svg_code):
response = client.chat.completions.create(
model="deepseek-v3.2",
messages=[
{"role": "system", "content": VALIDATION_PROMPT},
{"role": "user", "content": svg_code}
],
temperature=0.1
)
return response.choices[0].message.content.strip()
Step 5: Wire everything into a single pipeline
Now I connect the stages and add a file writer.
def generate_art(concept, output_path="art.svg"):
print(f"Generating brief for: {concept}")
brief = generate_brief(concept)
print("Rendering SVG...")
raw_svg = generate_svg(brief)
print("Validating...")
final_svg = validate_svg(raw_svg)
with open(output_path, "w", encoding="utf-8") as f:
f.write(final_svg)
print(f"Saved to {output_path}")
return final_svg
if __name__ == "__main__":
import sys
concept = sys.argv[1] if len(sys.argv) > 1 else "a retro-futuristic coffee shop"
generate_art(concept)
Run it
I test the pipeline from the terminal.
$ export OXLO_API_KEY="sk-oxlo.ai-..."
$ python art_generator.py "a cyberpunk cat reading a book"
Generating brief for: a cyberpunk cat reading a book
Rendering SVG...
Validating...
Saved to art.svg
The resulting SVG looks like this:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600">
<rect x="0" y="0" width="800" height="600" fill="#0a0a12"/>
<circle cx="400" cy="300" r="150" fill="#1a1a2e" stroke="#00ffcc" stroke-width="2"/>
<polygon points="320,240 380,240 350,180" fill="#ff00aa"/>
<rect x="360" y="280" width="80" height="60" fill="#00ffcc" opacity="0.8"/>
<path d="M 300 400 Q 400 350 500 400" stroke="#ff00aa" stroke-width="3" fill="none"/>
</svg>
Next steps
To push this further, connect the brief generator to Oxlo.ai's images/generations endpoint and swap the SVG stage for a Flux.1 or Stable Diffusion 3.5 call to produce raster art instead of vector code. If you want to keep the vector pipeline, add a Gradio UI so non-technical teammates can generate icons directly.
Top comments (0)