DEV Community

shashank ms
shashank ms

Posted on

Engineering LLMs for Art Generation: Challenges and Opportunities

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

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)