Il testo è la parte facile: la pipeline che illustra gli articoli scritti dall'AI

Generare il testo è un problema risolto. Illustrarlo no. La pipeline end-to-end — prompt router, ricerca foto, diagrammi Mermaid, grafici verificati, e il testo alternativo, credito e markup ImageObject che la trasformano in SEO.

Condividi
Un programmatore digita su una scrivania con due monitor pieni di codice, illuminati da lampade colorate.
Foto via Unsplash

Siete sviluppatori con un obiettivo di contenuti: decine di articoli al mese, forse centinaia. Il modello di scrittura gestisce la bozza, la scaletta, la meta description, i link interni. Poi la pipeline si scontra con l'unico passaggio che non ha un modello proprio — le immagini — e si ferma. Non perché le immagini siano difficili da ottenere, ma perché niente nello stack sa quale immagine, da dove, con quale licenza, descritta come.

Questo articolo è quel passaggio mancante, cablato dall'inizio alla fine: che tipo di visual produrre per che tipo di sezione, il prompt che trasforma una bozza finita in brief di ricerca, la chiamata API che restituisce le foto, il secondo modello che disegna ciò che una fotografia non può, e l'assemblatore che emette markup che Google può effettivamente leggere. Due flussi di lavoro completi alla fine, pronti da copiare.

Il testo è risolto. L'illustrazione è dove si blocca.

Fate l'audit onesto di una pipeline articoli automatizzata. Scaletta: risolta. Bozza: risolta. Titolo, meta description, schema, link interni, traduzione: risolti, tutti dallo stesso modello, tutti in forma testuale. Poi:

Fase della pipeline Stato Cosa blocca davvero
Scaletta e bozza Risolto Una chiamata al modello, un prompt
Titoli, meta, schema, link Risolto Testo in ingresso, testo in uscita
Immagine hero Bloccato Serve un file reale, una licenza, dimensioni e testo alt — nessuno dei quali un modello testuale può produrre
Immagini di sezione Bloccato Da tre a cinque per articolo, ciascuna diversa, nessuna che si ripeta nel sito
Grafici e diagrammi Bloccato Devono essere accurati — l'unico visual che una ricerca non può restituire e che un generatore non deve inventare

Il fallimento non è estetico, è strutturale: l'articolo viene pubblicato con un placeholder stock, o con la stessa foto degli ultimi dodici articoli, o con un'immagine generata la cui mano a sei dita è la prima cosa che un lettore nota. E l'immagine non è decorazione — la stessa documentazione di Google sulle immagini lo dice chiaramente: il testo alt “è l'attributo più importante quando si tratta di fornire metadati per un'immagine”, e la linea guida è usare veri elementi <img> con alt descrittivo invece di sfondi CSS, così che l'immagine possa essere trovata e compresa.1

Quattro tipi di visual, quattro tipi di modello

L'errore di progettazione più grande è trattare “l'immagine” come un problema unico con un unico fornitore. Sono quattro, e il router tra di essi è una riga di prompt, non un servizio:

La sezione richiede… Producetela con Perché non gli altri
Una scena dal mondo realehero, situazioni umane, luoghi, oggetti, gesti Ricerca fotografica semantica (Pexafy) Un generatore inventa i dettagli; un grafico non ha nulla da tracciare
Numeri che avete davverobenchmark, prezzi, risultati di sondaggi, latenza Un modello che scrive codice di plotting, eseguito in una sandbox A un modello di immagini non si può affidare un valore; una foto non può contenere dati
Una struttura o un flussoarchitettura, sequenza, macchina a stati Un modello che scrive Mermaid / Graphviz, renderizzato in modo deterministico Le librerie di foto gratuite non hanno un diagramma del vostro sistema
Il vostro prodotto sullo schermodocumentazione, changelog, tutorial Uno screenshot browser scriptato (Playwright) Nient'altro può mostrare una UI che esiste solo nella vostra build

E l'illustrazione generata? Conserva un ruolo onesto: la scena che non può essere fotografata e non è un dato — un meccanismo astratto, un prodotto che non esiste ancora, uno stile illustrativo proprietario della casa editrice. (L'argomentazione completa a favore della fotografia reale rispetto alla generazione — velocità su volumi, accuratezza, il problema della somiglianza — è esposta qui.) Il prezzo va nella direzione di questa tendenza: dal 2 agosto 2026, l'Articolo 50 dell'EU AI Act richiede ai fornitori di sistemi generativi di contrassegnare gli output sintetici in un formato leggibile da macchina.2 Si tratta di un obbligo per i fornitori e i deployer di IA, non di una regola su cosa un blog possa pubblicare — ma è per questo che la provenienza dell'immagine in cima al tuo articolo è sempre più qualcosa che un lettore può verificare invece di dare per scontato.

La pipeline, dall'inizio alla fine

Cinque fasi. Solo la fase 3 tocca un'API di immagini, e solo la fase 4 è opzionale:

La struttura — un articolo in ingresso, un articolo pubblicabile in uscita
┌─ 1. SCRIVI ───────────────────────────────────────────────────┐
   argomento ──▶ LLM ──▶ draft.md  (sezioni h2, front-matter)
└───────────────────────────────┬───────────────────────────────┘
┌─ 2. BRIEF ────────────────────┴───────────────────────────────┐
   draft.md ──▶ LLM ──▶ { hero: {...}, sections: [ {...} ] }
                     un oggetto JSON: per slot, un kind +
                     un brief camera o una spec dati/diagramma
└──────────┬──────────────────────────────────┬─────────────────┘
           │ kind = "photo"                │ kind = "chart" | "diagram"
           ▼                               ▼
┌─ 3. CERCA ────────────────┐   ┌─ 4. DISEGNA (opzionale) ─────┐
   GET /search/photos          LLM ──▶ mermaid | codice di plotting
   ← foto con credito +         ──▶ sandbox ──▶ .svg / .png
     w/h, blur_hash, alt,       (rendering deterministico, nessun
     licenza, URL sorgente       numero inventato)
└──────────┬────────────────┘   └──────────────┬───────────────┘
           └───────────────┬──────────────────┘
┌─ 5. ASSEMBLA ─────────────┴───────────────────────────────────┐
   <img> con width/height + fetchpriority | loading
   alt scritto per un umano · credito visibile · ImageObject JSON-LD
   photo_id memorizzato così nessuna pagina condivide un hero
└───────────────────────────────────────────────────────────────┘

Due proprietà contano più del diagramma. La fase 2 è un router: decide per ogni slot quale produttore eseguire, così non chiedete mai a una libreria di foto un grafico a barre. E la fase 5 è dove sta la SEO — tutto ciò che Google documenta sulle immagini (alt descrittivo, metadati di licenza, un hero sicuro per l'LCP) viene emesso qui, da campi che la risposta di ricerca già conteneva.

Fase 2: trasformare la bozza in brief visivi

Una singola chiamata al modello sulla bozza finita, e ciò che restituisce è un piano piuttosto che una query. Come scrivere un singolo brief fotografico — perché il titolo dell'articolo è il peggior input possibile, come si presenta un brief da 12 a 25 parole per la fotocamera, e i modi in cui può fallire — è spiegato per intero in la guida all'illustrazione di un singolo articolo, e non viene ripetuto qui. Ciò che una pipeline aggiunge è il routing: la stessa chiamata deve decidere, slot per slot, quale produttore eseguire — e una voce di tipo grafico porta dati dove una voce fotografica porta una scena.

Il prompt router — copiatelo così com'è
# system prompt — eseguito una volta per bozza finita
Sei il direttore artistico di una pubblicazione tecnica. Leggi l'articolo e
restituisci il piano visivo: una voce per l'hero, una per ogni sezione H2.

Per ogni voce scegli esattamente un kind:
  "photo"    una scena reale: qualcuno che fa qualcosa da qualche parte, un luogo,
              un oggetto, un gesto. Il default per gli hero.
  "chart"    la sezione riporta numeri che sono NELL'articolo. Non
              inventare mai valori: copiali in data, testualmente.
  "diagram"  la sezione descrive una struttura, un flusso o una sequenza.
  "none"     la sezione è breve, o contiene già un blocco di codice.

Regole per le voci "photo" — il campo è query:
Scrivi una descrizione per la fotocamera: una scena che una fotocamera avrebbe
   potuto catturare, da 12 a 25 parole, in inglese, coerente con il tono della
   sezione. Indica cosa si vede nell'inquadratura, mai l'argomento. Niente testo,
   loghi, marchi o persone famose; niente metafore invisibili. (Regole complete, con esempi e casi di errore:
   pexafy.com/blog/illustrate-blog-articles-at-scale/)

NON scrivere il testo alt di una voce photo: l'immagine che ottenete è la
corrispondenza più vicina al brief, non la scena che avete descritto, quindi il suo alt va
scritto a partire dalla foto scelta. Le voci chart e diagram PORTANO invece un alt —
lì controllate esattamente cosa viene renderizzato.

Restituisci solo JSON:
{
  "hero": { "kind": "photo", "query": "…", "orientation": "landscape" },
  "sections": [
    { "h2": "…", "kind": "photo",   "query": "…" },
    { "h2": "…", "kind": "chart",   "title": "…", "unit": "ms",
      "data": [ {"label": "…", "value": 0} ], "alt": "…" },
    { "h2": "…", "kind": "diagram", "spec": "flowchart LR; …", "alt": "…" }
  ]
}

La riga kind fa gran parte del lavoro, e l'istruzione data fa il resto: una voce di tipo grafico può contenere solo numeri che compaiono già nella bozza, così il modello trascrive invece di inventare. Ecco il router applicato a tre sezioni reali di un articolo per sviluppatori:

Sezione kind Cosa ha restituito il router
Hero — “Perché la nostra build notturna richiede 40 minuti” photo “uno sviluppatore che lavora fino a tardi a una scrivania con due monitor e una tastiera meccanica in una stanza buia illuminata dagli schermi”
“Dove va davvero il tempo” chart data copiati dal paragrafo: installazione 480 s, compilazione 1080 s, test 720 s, upload 120 s
“Come dividiamo il grafo” diagram flowchart LR del grafo delle dipendenze dei job
“Cosa abbiamo cambiato, e cosa rifaremmo” photo “due ingegneri in piedi davanti a una lavagna coperta di diagrammi che risolvono un problema insieme”

Fase 3: le foto tornano con credito

Ogni voce kind: "photo" è una richiesta. Il brief hero qui sopra, eseguito contro l'API pubblica, restituisce questo in 147 ms:

GET /search/photos — brief hero · “uno sviluppatore che lavora fino a tardi a una scrivania con due monitor…” · 147 ms
Il motore classifica per significato, quindi la frase lunga restringe l'insieme invece di svuotarlo. Esegui questa esatta ricerca →

L'ultimo brief di sezione, una scena completamente diversa, in 144 ms:

GET /search/photos — brief di sezione · “due ingegneri in piedi davanti a una lavagna coperta di diagrammi…” · 144 ms
Stesso articolo, stessa esecuzione, una scena che nessuno confonderebbe con l'hero — perché il brief è stato scritto per sezione, non per articolo. Esegui anche questa →

Ciò che torna per ogni foto è la parte che rende possibile la fase 5 — non solo un file:

Un risultato, ridotto ai campi che l'assemblatore consuma
{
  "photo_id":  "019e1c7f-0063-759e-b498-33ce1714e6c9",   // memorizzalo: niente ripetizioni
  "urls": { "small": "…?w=400", "regular": "…?w=1080",
             "large": "…?w=1920" },
  "width": 3000, "height": 1688,          // → niente layout shift
  "blur_hash": "LJ8gjv9rVq-6OFxanNNFI7xco$Na",   // → placeholder reale
  "alt_description": "Person types on keyboard in front of dual monitors…",
  "photographer_full_name": "Jakub Żerdzicki",   // → ImageObject.creator
  "source": "Unsplash", "license_type": "free",
  "source_image_url": "https://unsplash.com/photos/…",
  "attribution": { "html": "<span…>Photo by …</span>",
                    "plain": "Photo by Jakub Żerdzicki on Unsplash (…)" }
}

Fase 4: ciò che una fotografia non può dire

Due slot del piano non sono ricercabili, ed è esattamente qui che un secondo modello si guadagna il suo posto — non per disegnare un'immagine, ma per scrivere codice che la disegna. La distinzione conta: il codice è verificabile, deterministico e non può allucinare l'altezza di una barra.

Diagrammi: testo in ingresso, SVG in uscita

Mermaid renderizza diagrammi da una definizione in testo semplice,3 il che lo rende il bersaglio più sicuro per un modello: l'output è ispezionabile, confrontabile con diff in git, e viene renderizzato allo stesso modo ogni volta. Il router ha già restituito la spec.

diagram.sh — il modello ha scritto la spec, la CLI la renderizza
# il campo "spec" di una voce diagram, scritto in build/graph.mmd
cat build/graph.mmd
flowchart LR
  install["install deps · 480s"] --> compile["compile · 1080s"]
  compile --> test["test suite · 720s"]
  compile --> upload["upload artifacts · 120s"]

npx -y @mermaid-js/mermaid-cli -i build/graph.mmd -o static/img/graph.svg
# → un SVG che potete rivedere nella PR, non un'immagine di cui fidarvi

Grafici: solo numeri che l'articolo già contiene

Stesso principio, una protezione in più. Il router ha copiato i valori dalla bozza; il modello scrive il codice di plotting; il codice gira in una sandbox; l'assemblatore ricontrolla i valori renderizzati contro i numeri sorgente prima che il grafico possa avvicinarsi a una pagina.

chart.py — traccia i dati trascritti, poi verificali
import re, matplotlib
matplotlib.use("Agg")                # headless: nessun display in CI
import matplotlib.pyplot as plt

def assert_in_draft(value, draft: str) -> None:
    """Un numero tracciato deve comparire nell'articolo COME NUMERO."""
    # Il matching per sottostringa è la trappola qui: "120" è dentro "1200", e
    # dentro "?w=1200" — un ingenuo `str(v) in draft` passa su qualsiasi cosa.
    # Fai matching sui confini di parola, e accetta 1 234 / 1.234 / 1234.
    body = re.sub(r"(?<=\d)[  ,](?=\d{3}\b)", "", draft)   # rimuovi separatori
    if not re.search(rf"(?<![\d.]){re.escape(str(value))}(?![\d.])", body):
        raise ValueError(f"{value} is not stated in the article — refusing to plot")

def render_chart(entry: dict, draft: str, out: str) -> str:
    labels = [d["label"] for d in entry["data"]]
    values = [d["value"] for d in entry["data"]]
    for v in values:
        assert_in_draft(v, draft)       # valore allucinato → nessun grafico

    fig, ax = plt.subplots(figsize=(8, 4.5), dpi=160)
    ax.barh(labels, values)
    ax.set_xlabel(entry["unit"])
    ax.set_title(entry["title"])
    fig.tight_layout()
    fig.savefig(out)                    # artefatto deterministico, verificabile
    return out

La protezione è breve, ma scrivetela con cura: un controllo per sottostringa non funziona. "120" in draft è vero per un articolo che contiene 1200 o ?w=1200, quindi la versione ingenua passa su tutto e non protegge nulla. Ancoratevi ai confini delle cifre, normalizzate i separatori delle migliaia, e la classe di errore che fa smontare un articolo tecnico nei commenti — un grafico che contraddice il proprio paragrafo — non può raggiungere la produzione. Gli screenshot seguono lo stesso principio: un page.screenshot() scriptato contro la vostra build reale è l'unica fonte di verità per la vostra UI, e resta vero mentre la UI cambia.

Fase 5: l'assemblaggio è dove vive la SEO

Tutto ciò che è avvenuto finora ha prodotto file e campi. Questa fase li trasforma in markup — ed è utile essere precisi qui, perché tre comportamenti documentati si decidono in queste poche righe.

L'hero, emesso dalla risposta di ricerca — niente di inventato
<!-- I byte provengono da un'altra origine: paga l'handshake in anticipo -->
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>

<!-- Elemento LCP: mai lazy, sempre priorità alta -->
<figure>
  <img src="{urls.regular}"
       width="{width}" height="{height}"        <!-- elimina il layout shift -->
       alt="{alt}"                                <!-- scritto DOPO la scelta -->
       fetchpriority="high" decoding="async"
       style="background:{color_hex}">   <!-- colore dominante, 1 campo -->
  <figcaption>{attribution.html}</figcaption>
</figure>

<!-- Immagini di sezione, sotto la piega: le impostazioni opposte -->
<img src="{urls.regular}" width="{width}" height="{height}"
     alt="{alt}" loading="lazy" decoding="async">

Hotlink o re-hosting? Lo snippet sopra fa hotlink, che è la cosa più rapida da pubblicare e il motivo del preconnect: un hero remoto costa una risoluzione DNS e un handshake TLS sul percorso critico, e questo può erodere il guadagno appena ottenuto con fetchpriority. Il re-hosting elimina del tutto l'origine di terze parti, vi permette di servire AVIF/WebP ai vostri breakpoint, e sopravvive a un cambio di URL a monte — al costo di storage, un passaggio di fetch nella pipeline e la vostra bolletta CDN. Qualunque scegliate, color_hex vi dà un placeholder per un campo (un blur hash è più elegante, ma va decodificato prima in un data URI — non è un colore CSS). Copiare un hero approssimativo direttamente in background: è dove la maggior parte delle pipeline pubblica silenziosamente un riquadro grigio vuoto.

  1. Non caricare mai l'hero in lazy loading. web.dev è inequivocabile: “Non caricare mai in lazy loading la tua immagine LCP, poiché questo porterà sempre a un ritardo inutile nel caricamento delle risorse, e avrà un impatto negativo sull'LCP”, e raccomanda fetchpriority="high" sull'elemento probabilmente LCP — usato con parsimonia, su una sola immagine.4 Una pipeline che applica loading="lazy" a ogni immagine, hero incluso, è la ferita autoinflitta più comune ai Core Web Vitals nella pubblicazione automatizzata.
  2. Emettete sempre width e height — tornano nella risposta, quindi non c'è scusa; quella singola coppia di attributi è ciò che permette al browser di riservare lo spazio e impedisce al layout di saltare. Usate blur_hash come placeholder mentre il file si carica.
  3. Scrivete l'alt dopo la scelta, mai prima. Questo è il punto sottile. Il campo alt del piano descrive la scena che avete chiesto; la foto che avete ottenuto è la corrispondenza più vicina, non quella scena. Pubblicare il testo del brief come alt è esattamente il fallimento di accessibilità che questa pipeline dovrebbe evitare — una descrizione di un'immagine che non è quella in pagina. Costruite l'alt a partire dall'alt_description della foto scelta, raffinato contro il paragrafo in cui si trova. La linea guida di Google è di “concentrarsi sulla creazione di contenuti utili e informativi che usano le parole chiave in modo appropriato e nel contesto del contenuto della pagina”, e avverte che riempire gli attributi alt di parole chiave “si traduce in un'esperienza utente negativa e può far sì che il vostro sito venga considerato spam”.1

La parte che quasi nessuno automatizza: i metadati di licenza

Google supporta i dati strutturati ImageObject per la licenza delle immagini. Richiede contentUrl più almeno uno tra creator, creditText, copyrightNotice o license, raccomanda acquireLicensePage, e le immagini con informazioni di licenza diventano idonee al badge Licensable in Google Images.5 Ognuno di questi campi è già nella risposta di ricerca — quindi emetterlo è un template, non un progetto:

ImageObject JSON-LD, compilato dalla risposta API
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "ImageObject",
  "contentUrl": "{urls.large}",                 // obbligatorio
  "creator": { "@type": "Person",
                "name": "{photographer_full_name}" },
  "creditText": "{photographer_full_name} on {source}",
  "license": "{LICENSE_URL[source]}",           // la pagina propria della libreria
  "acquireLicensePage": "{source_image_url}"     // la pagina della foto
}
</script>

# LICENSE_URL mappa il campo `source` alla licenza che governa davvero
# la foto — unsplash.com/license, pexels.com/license, pixabay.com/…

Un dettaglio che vale la pena curare: license dovrebbe puntare alla licenza che governa quella foto — la pagina di licenza propria della libreria sorgente — non a una pagina riassuntiva sul vostro dominio. Google la legge per decidere l'idoneità al badge, e un URL autoreferenziale è sia più debole come segnale sia difficile da difendere come qualcosa di diverso da un link verso voi stessi. Tenete il vostro riassunto della licenza come pagina interna per i lettori; mettete quello canonico nel markup.

E già che siete nell'assemblatore: date al file un nome breve e descrittivo invece di IMG_0042.jpg, e aggiungete l'immagine a una sitemap — il formato di sitemap immagini di Google accetta fino a 1.000 immagini per URL di pagina.6 Entrambe sono una riga ciascuna in una pipeline e nessuna delle due viene mai fatta a mano.

Due pipeline da copiare

Stesse cinque fasi, due forme molto diverse — una per gli articoli che scrivete, una per la documentazione che deve corrispondere a un prodotto funzionante. Scegliete quella il cui modo di fallire riconoscete.

1 · Il blog per sviluppatori in CI — Markdown nel repository

Gli articoli vivono come Markdown, le immagini vengono committate accanto ad essi, e il tutto gira a ogni push. Deterministico, verificabile nella PR, nessuna dipendenza a runtime da alcuna API:

.github/workflows/illustrate.yml
on: { pull_request: { paths: ["content/**.md"] } }

jobs:
  illustrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt
      - run: python plan_to_pr.py $(git diff --name-only origin/main -- 'content/*.md')
        env:
          PEXAFY_API_KEY: ${{ secrets.PEXAFY_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      - uses: peter-evans/create-pull-request@v6   # le immagini arrivano nella PR
        with: { commit-message: "chore(content): illustrate" }

Un umano approva comunque la PR, ed è il punto: la pipeline propone, il revisore dispone, e il front-matter che ha scritto è confrontabile con diff.

La metà relativa alla ricerca è una singola GET — la richiesta, il suo score_threshold e i campi che restituisce sono descritti riga per riga in la guida al singolo articolo, quindi qui viene importata anziché ristampata. Ciò che questo file aggiunge è tutto ciò di cui un piano ha bisogno e una singola foto no: il ritentativo su un brief che non ha restituito nulla, la rivendicazione su un photo_id in modo che nessuna pagina condivida un'immagine con un'altra, e l'alt scritto a partire dalla foto ottenuta.

plan_to_pr.py — il collante: piano visivo in ingresso, front-matter in uscita
import frontmatter
from photo_search import search   # una GET /search/photos; salta gli id in `used`

def find_photo(entry: dict, used: set) -> dict | None:
    """Cerca; se il brief era troppo specifico, allargalo una volta, poi rinuncia."""
    orientation = entry.get("orientation", "landscape")
    for query in (entry["query"], widen(entry["query"])):
        photo = search(query, orientation, used)
        if photo:
            used.add(photo["photo_id"])   # riservala: niente ripetizioni nel sito
            return photo
    return None                        # decide il chiamante: salta lo slot, o fallisci

def widen(query: str) -> str:
    """Elimina l'ultima clausola — di solito quella troppo specifica."""
    return query.rsplit(" in ", 1)[0] if " in " in query else query

def illustrate(path: str, plan: dict, used: set) -> None:
    post = frontmatter.load(path)
    hero = find_photo(plan["hero"], used)
    if hero is None:                    # nessun hero è meglio di uno cattivo
        raise SystemExit(f"{path}: no photo above threshold — rewrite the brief")

    post["hero"] = {                    # tutto ciò di cui il template ha bisogno
        "src": hero["urls"]["regular"], "w": hero["width"], "h": hero["height"],
        # L'alt descrive la foto che ABBIAMO OTTENUTO, mai la scena che avevamo chiesto.
        "alt": alt_for(hero, section=post.get("title", "")),
        "bg": hero["color_hex"], "credit": hero["attribution"]["html"],
        "creator": hero["photographer_full_name"], "id": hero["photo_id"],
        "source": hero["source"],
        "source_url": hero["source_image_url"],   # → acquireLicensePage
    }
    open(path, "w").write(frontmatter.dumps(post))

def alt_for(photo: dict, section: str) -> str:
    """alt_description come base, ridotto a ~125 caratteri per gli screen reader.
    Rimandalo al modello insieme al paragrafo se vuoi un risultato migliore."""
    base = photo.get("alt_description") or photo.get("description", "")
    return base[:125].rstrip(" ,;")

2 · Documentazione e changelog — prima gli screenshot, poi le foto

Invertite le impostazioni predefinite del router. Nella documentazione di prodotto il visual onesto è quasi sempre la vostra UI: uno script Playwright che apre la build reale, imposta un viewport fisso e cattura lo stato esatto che il paragrafo descrive. I diagrammi coprono le pagine di architettura, e le fotografie compaiono solo nelle pagine concettuali e landing — dove uno screenshot non direbbe nulla.

shots.py — lo screenshot è generato, mai descritto
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1440, "height": 900},
                            device_scale_factor=2)   # nitidezza retina
    page.goto("http://localhost:3000/dashboard")
    page.get_by_role("button", name="New API key").click()
    page.screenshot(path="static/img/docs/new-api-key.png")
    browser.close()
# Gira nello stesso job CI della build della documentazione → lo screenshot non può mai
# descrivere una versione della UI che non esiste più.

Due varianti che vale la pena nominare ma non meritano una ricetta propria. La SEO programmatica inverte il ciclo: con centinaia di pagine generate da un database non si cerca per pagina — una richiesta restituisce fino a 100 foto, quindi si cerca per cluster di argomento e si assegna da un pool, con un vincolo di unicità che fa la deduplicazione (quell'architettura, per intero). E le newsletter e le card social hanno bisogno della stessa foto in quattro ritagli: mantenete il photo_id, richiedete la dimensione che vi serve da urls, e cercate di nuovo solo quando un ritaglio fallisce davvero.

La stessa pipeline, come un unico agente

<<>>

Se un modello sta già scrivendo la bozza, la strada più breve è dargli direttamente lo strumento di ricerca invece di far transitare JSON tra processi diversi. Pexafy gestisce un server Model Context Protocol ospitato all'indirizzo mcp.pexafy.com/mcp. La configurazione del connettore e gli strumenti che espone sono trattati altrove — la configurazione desktop ed editor in la guida per il singolo articolo, la variante headless per un runner CI in l'articolo sui contenuti su larga scala, e come si presenta il più ampio mercato dei connettori — chi offre un server MCP per immagini, a quali condizioni — in lo studio sull'infrastruttura di ricerca immagini per agenti AI. Ciò che vale la pena mostrare qui è cosa succede a questa pipeline una volta che l'agente dispone degli strumenti: smette di essere cinque fasi e diventa un'unica istruzione.

Un'istruzione, l'intero piano eseguito
Tu  Ecco la bozza. Costruisci il piano visivo, illustralo e apri una PR.
     Grafici solo con numeri già presenti nel testo.

Agente  → piano: hero=photo · §2=chart · §3=diagram · §4=photo
      → search_photos(q="a developer working late at a desk with two
         monitors and a mechanical keyboard in a dark room…")
      ← 16 foto · 147 ms · scelta la #1, 3000×1688, con credito
      → mermaid-cli build/graph.mmd → static/img/graph.svg
      → chart.py §2 → 4 valori verificati contro la bozza ✓
      → search_photos(q="two engineers standing at a whiteboard…")
      ← 16 foto · 144 ms · scelta la #1

      ✓ 4 slot riempiti · 2 chiamate API · alt + credito + ImageObject scritti
      ⚠ §5 non ha restituito nulla sopra 0.5 — brief troppo astratto, riscritto come
        "a person at a kitchen table checking figures on a laptop"

Quest'ultima riga è il motivo per cui vale la pena avere un agente in questo ciclo invece di uno script puro: la modalità di fallimento dell'illustrazione automatizzata è un brief scadente, e riscrivere un brief scadente è esattamente a cosa serve un modello linguistico. Mantenete le parti deterministiche — assegnazione, deduplicazione, la protezione numerica — nel codice.

Quanto costa un articolo illustrato

Tre slot foto per articolo significano tre richieste di ricerca, quindi il piano free (5,000 richieste/mese) copre 1,666 articoli al mese prima che si ponga qualsiasi questione di pagamento — e se raggruppate le ricerche per cluster di argomento invece che per articolo, quel tetto si sposta di un altro ordine di grandezza. La ripartizione piano per piano, e l'architettura di raggruppamento che la rende superflua, sono in l'articolo compagno sull'illustrare contenuti su larga scala.

Le due chiamate al modello per articolo — una per la bozza, una per il piano visivo — sono qualche migliaio di token e saranno la voce più economica della pipeline; i rendering Mermaid e matplotlib non costano nulla se non secondi di CI. Il numero che sorprende è quale voce non è economica: generare quattro immagini per articolo, quattrocento immagini al mese, più i tentativi che non hanno superato la selezione — e l'output continua a non portare né fotografo, né data, né URL sorgente.

Cosa questa pipeline non risolve

Una sezione onesta, perché il fallimento che previene è costoso. Un articolo ben illustrato è comunque un articolo: fotografia reale, grafici accurati e markup corretto migliorano una pagina che merita di esistere. Non fanno posizionare contenuti mediocri prodotti in massa. Le politiche antispam di Google nominano l'abuso di contenuti su larga scala — generare molte pagine principalmente per manipolare il ranking e offrire poco valore agli utenti, che l'automazione sia coinvolta o meno — e la qualità delle illustrazioni non è un fattore in quel giudizio.7

Quindi l'inquadramento che regge è questo: questa pipeline è un pavimento di qualità che controllate, applicato a pagine che hanno già un motivo per essere pubblicate. Dove paga in modo dimostrabile:

  • Provenienza che un lettore può verificare. Una didascalia con un fotografo reale e un URL sorgente è un'affermazione verificabile — e gli stessi campi alimentano il markup ImageObject che Google legge.
  • Accessibilità e Core Web Vitals. Alt testuale reale, dimensioni su ogni immagine, un hero mai caricato in lazy loading. Moltiplicate per ogni articolo che pubblicate e questa è la storia della qualità delle immagini del sito.
  • Accuratezza dove è verificabile. Un grafico i cui numeri sono verificati contro l'articolo, uno screenshot generato dalla build in esecuzione, un diagramma verificabile come testo nella PR — tre visual che non possono divergere dalla verità senza che un test fallisca.

Sull'attribuzione, il punto specifico della pipeline è ristretto: l'argomentazione a favore del mostrare sempre il credito è esposta altrove, e ciò che un assemblatore automatizzato aggiunge è che la stessa stringa attribution che stampa è il creditText di cui i dati strutturati hanno bisogno. Un campo, due luoghi, emessi nello stesso passaggio di template — motivo per cui una pipeline ha ancora meno scuse di un essere umano per ometterlo.

Da dove iniziare

  1. Aggiungete il prompt router a qualunque cosa scriva già le vostre bozze, e stampate il JSON senza agire su di esso. Leggete dieci piani. Se i brief nominano argomenti invece di scene, correggete il prompt prima di scrivere qualsiasi integrazione.
  2. Collegate solo gli slot foto. Un GET /search/photos per brief, e memorizzate photo_id fin dal primo giorno — la deduplicazione applicata retroattivamente su 3.000 pagine in produzione è una migrazione, non una colonna.
  3. Emettete width, height e alt nello stesso commit. È il lavoro sui Core Web Vitals più economico che farete mai.
  4. Poi aggiungete la fase 4, diagrammi prima dei grafici — Mermaid è testo, quindi è quello con nessuna modalità di fallimento oltre un errore di sintassi.
  5. Aggiungete la protezione numerica prima che il primo grafico raggiunga un lettore, non dopo.

Riferimenti e note

1 Google Search Central, Image SEO best practices: il testo alt è “l'attributo più importante quando si tratta di fornire metadati per un'immagine”; la linea guida è creare “contenuti utili e informativi che usano le parole chiave in modo appropriato e nel contesto del contenuto della pagina”, evitare attributi alt riempiti di parole chiave, usare elementi HTML <img> invece di immagini CSS, e dare ai file nomi brevi ma descrittivi.

2 AI Act dell'UE, Articolo 50 — obblighi di trasparenza applicabili dal 2 agosto 2026: i fornitori di sistemi che generano immagini, audio, video o testo sintetici devono contrassegnare gli output in un formato leggibile da macchina e renderli rilevabili come artificialmente generati. Vincola i fornitori e i deployer di IA; non è una regola su quali immagini un sito web possa pubblicare.

3 Mermaid renderizza diagrammi e grafici da definizioni testuali ispirate al Markdown, il che rende l'output verificabile e deterministico.

4 web.dev, Optimize Largest Contentful Paint: “È una buona idea impostare fetchpriority="high" su un elemento <img> se pensate che sia probabile che sia l'elemento LCP della vostra pagina”, usato con parsimonia; e “Non caricate mai in lazy loading la vostra immagine LCP, poiché ciò porterà sempre a un ritardo inutile nel caricamento delle risorse, e avrà un impatto negativo sull'LCP.”

5 Google Search Central, Image metadata (structured data): ImageObject richiede contentUrl più almeno uno tra creator, creditText, copyrightNotice o license; acquireLicensePage è raccomandato, e le immagini con informazioni di licenza possono diventare idonee al badge Licensable in Google Images.

6 Google Search Central, Image sitemaps: le sitemap immagini informano Google sulle immagini di un sito, incluse quelle trovate tramite JavaScript, e accettano fino a 1.000 immagini per URL di pagina.

7 Politiche antispam di Google Search — abuso di contenuti su larga scala: generare molte pagine principalmente per manipolare il ranking e offrire poco valore agli utenti, che siano create tramite automazione, sforzo umano o una combinazione dei due.

Domande frequenti

Come illustro automaticamente gli articoli scritti da un LLM?
Aggiungi una chiamata a un modello tra la scrittura e la pubblicazione: chiedigli di restituire un piano visivo — una voce per ogni slot immagine, ciascuna etichettata come foto, grafico, diagramma o nessuno. Le voci foto portano una descrizione di 12-25 parole di una scena che una fotocamera avrebbe potuto catturare, che invii a un'API di ricerca semantica delle immagini (GET /api/v1/search/photos, circa 150 ms). Le voci grafico e diagramma vanno a un modello che scrive codice di plotting o Mermaid, renderizzato in modo deterministico. Non inserire mai il titolo dell'articolo in una ricerca di immagini: i titoli sono astratti e nessuna fotografia li rappresenta.
Un sito di documentazione dovrebbe usare screenshot o foto stock?
Inverti le impostazioni predefinite del router: nella documentazione di prodotto il visual onesto è quasi sempre la tua stessa UI. Uno script Playwright che apre la build reale, fissa il viewport e cattura esattamente lo stato descritto dal paragrafo viene eseguito nello stesso job CI della build della documentazione, così uno screenshot non può mai mostrare una versione dell'interfaccia che non esiste più. I diagrammi sostengono le pagine di architettura, e le fotografie compaiono solo nelle pagine concettuali e di landing, dove uno screenshot non direbbe nulla.
Come impedisco a una pipeline AI di inserire numeri sbagliati in un grafico?
Fai in modo che il piano trascriva anziché inventare: il router può solo copiare valori già presenti nella bozza nel campo data della voce grafico. Poi verificalo nel codice prima del rendering — per ogni valore, controlla che la sua stringa compaia nell'articolo e solleva un errore altrimenti. Nove righe, e un grafico che contraddice il proprio paragrafo non potrà mai raggiungere un lettore.
Quale markup delle immagini dovrebbe emettere una pipeline automatizzata per la SEO?
Tre cose, tutte provenienti da campi già contenuti nella risposta di ricerca. Un alt descrittivo scritto nel contesto del paragrafo — Google definisce il testo alternativo il metadato più importante dell'immagine e mette in guardia contro il keyword stuffing. width e height su ogni immagine, con fetchpriority="high" sulla hero image e mai loading="lazy" su di essa, perché l'immagine LCP non deve essere caricata in modo lazy. E dati strutturati ImageObject con contentUrl, creator, creditText e license, che è ciò che rende un'immagine idonea al badge Licensable in Google Immagini.
Illustrare articoli scritti dall'AI aiuta il posizionamento?
Non da solo, ed è bene essere precisi. Le politiche anti-spam di Google definiscono lo scaled content abuse come la generazione di molte pagine principalmente per manipolare il posizionamento con scarso valore per gli utenti, indipendentemente dal coinvolgimento dell'automazione; le illustrazioni non cambiano questa valutazione. Ciò che una buona pipeline garantisce è una soglia di qualità su pagine che già meritano di esistere: provenienza verificabile, testo alternativo accessibile, Core Web Vitals che sopravvivono all'automazione, e grafici e screenshot che non possono discostarsi dalla verità.
Come eseguo lo step di illustrazione in CI senza perdere il controllo editoriale?
Attiva il job su una pull request che tocca i tuoi file di contenuto, lascia che scriva le immagini e il front-matter, e fai in modo che apra una pull request invece di fare commit direttamente sul branch — la pipeline propone, un umano approva, e ogni campo che ha scritto è confrontabile con un diff. Mantieni tre cose deterministiche nel codice piuttosto che nel modello: la deduplicazione tramite photo_id, la verifica che ogni numero rappresentato nel grafico compaia nell'articolo, e un fallimento netto quando nessuna foto supera la soglia di punteggio. Nessuna immagine di copertina è meglio di una sbagliata.

Basta cercare parole chiave. Descrivi ciò che intendi.

Cerca 9M+ immagini libere per significato — in qualsiasi lingua, in meno di 100 ms.