← Back to blog

🎨 From Raw Photos to Editable Ads: A Multi-Agent Engine in n8n

How I built Agent1 for Dataverse Solutions: vision agents, deterministic geometry, and a zero-dependency DrawingML compiler that outputs editable PPTX posters.

Generative design for advertising usually forces a bad trade. Diffusion models give you a beautiful image that nobody can edit, so a typo in the headline means generating everything again. Template tools give you editable text, but they paste rigid boxes on top of the photo without knowing what is in it.

In September 2026 I built Agent1 for Dataverse Solutions and Hoteles Lavid. It takes raw photos of luxury hotels and large concerts and returns advertising posters as PowerPoint files (ECMA-376) that a marketing team can open in Canva, Google Slides, or Keynote and edit layer by layer. Each poster also ships with a pixel-accurate HD preview.

🤖 Full Case Study & Architecture Spec:
Explore the complete system breakdown, data flow, and benchmarks on the Agent1 Project Page.

💥 1. Where the first version broke

The first iterations followed the common pattern of asking an LLM to pick a quadrant or a template. Four failures kept showing up:

  1. Covered subjects. Text cards landed on top of the bed, the furniture, or the artist on stage.
  2. Unreadable text. Light type over bright skies, failing WCAG 2.1 contrast.
  3. Broken line wrapping. Headlines written without any idea of the physical size of the box they had to fit in.
  4. Rejected files. .pptx packages that Canva or Google Slides flagged as corrupt because of non-standard XML ordering or invalid ZIP timestamps.

All four share a root cause. The model was being asked to do math.

📐 2. One rule: if it can be computed, the LLM does not touch it

I organized the system around a single invariant. Spatial geometry, luminance, box collisions, and binary packaging are computed by deterministic code. The models handle what they are good at, which is seeing the photo and writing the copy.

Every hand-off between steps is a JSON Schema contract validated with Ajv:

Contract Produced by What it carries
VisionGroundingOutput Vision agent Exclusion boxes normalized to 0–1000, primary vs. secondary subjects, lighting profile
ArtDirectorBlueprint Art-director step Coordinates in EMUs, color system, scrim opacity stops, character budgets
CreativeCopyPack Copy agent Headlines and body text tied to the brief, within per-format length limits
QualityAuditResult Deterministic critic Audit score and structured corrective directives

The vision step runs on GPT-4o or Qwen 2.5 VL 72B, and the copy step on GPT-4o or Claude 3.7 Sonnet. If any output fails its schema, the pipeline stops at that step instead of passing bad data downstream.

📦 3. Finding free space with a Maximal Empty Rectangle solver

Picking from prebuilt templates was the source of the covered subjects, so I replaced it with a pure Node.js node that runs a Maximal Empty Rectangle (MER) algorithm. It makes no model calls and adds no measurable latency.

  1. It takes the primary_focus boxes from the vision contract and projects orthogonal cuts across a normalized 1000×1000 canvas.
  2. It evaluates every collision-free cell and extracts the largest empty rectangles for the header and footer zones.
  3. The layout step stacks typography in a cumulative vertical flow (body, then CTA, then footer). When the stack would cross the safety margin, the whole block shifts up, which removed the collisions between the CTA button and the footer.

⚙️ 4. Writing a PPTX compiler from scratch

n8n runs code in a sandbox, and heavy document libraries were not an option there. So I wrote an OpenXML/DrawingML compiler on top of native Node.js Buffer.

  • ZIP packaging in memory. I build the local file headers, the central directory, and the end-of-central-directory record by hand, with an in-memory CRC32 table. Writing valid DOS timestamps fixed the date exceptions that Google Slides and Canva threw on import.
  • Strict ECMA-376 ordering. Canva’s parser cares about node order inside run properties, Dublin Core metadata, a theme part, and explicit transform matrices on group shapes. Getting those right is what makes every layer unlock after import.
  • Six vector layers. The compiler reads real image dimensions from the JPEG/PNG header bytes, computes proportional crops, and adds a 70,000 EMU bleed over a dark master background so no white subpixel edges appear. Multi-stop gradient scrims and a 360° soft shadow keep the text readable on bright photos.

🛡️ 5. Two quality gates before anything ships

  1. Deterministic critic. It computes CIE sRGB relative luminance to require a WCAG 2.1 contrast ratio of at least 4.5:1, runs 2D AABB intersection tests so no text box touches a primary subject, enforces a 24-point type floor, and blocks prices or dates that were not in the brief.
  2. Dual output with a visual QA loop. The flow forks in parallel. One branch compiles the editable .pptx. The other renders an HD HTML5/CSS3 preview in headless Chromium through Puppeteer, with a local PHP GD fallback. A visual critic inspects the render and can request a bounded geometric adjustment. A hard iteration ceiling with triple redundancy makes infinite loops impossible.

Assets are stored in Cloudflare R2 with SigV4 signing, state lives in PostgreSQL, and a human-in-the-loop editor lets the team approve or tweak each poster.

💻 6. Treating workflows as code

Visual workflow tools are easy to start and hard to maintain. I added a local CLI (bundle, extract, validate, test, deploy) that decompiles each n8n flow into atomic JavaScript modules versioned in Git, validates schemas and contracts offline, and deploys through the n8n REST API.

💡 What I took from it

The most reliable part of an AI pipeline is the part that does not use AI. Contracts at every boundary and deterministic code for anything measurable turned a demo that worked on good photos into a system that holds up on bad ones.

Popular:

Type any technology, keyword, or company to see live results.

Open dedicated search page →

Direct Message Channel

Encrypted Direct Channel · Immediate Delivery

(Optional)
Minimum 20 characters
Initializing security check...
Average response < 24h
Prefer direct email? arturo@arturonavax.dev