Der Text ist der leichte Teil: Die Pipeline, die KI-geschriebene Artikel illustriert

Text generieren ist gelöst. Ihn zu illustrieren nicht. Die durchgängige Pipeline — Router-Prompt, Fotosuche, Mermaid-Diagramme, verifizierte Charts und der Alt-Text, die Bildquelle und das ImageObject-Markup, die daraus SEO machen.

Ein Programmierer tippt an einem Schreibtisch mit zwei Monitoren voller Code, beleuchtet von farbigen Lampen.
Foto über Unsplash

Sie sind Entwickler mit einem Content-Ziel: Dutzende Artikel pro Monat, vielleicht Hunderte. Das Schreibmodell übernimmt Entwurf, Gliederung, Meta-Description, interne Links. Dann trifft die Pipeline auf den einen Schritt, für den es kein eigenes Modell gibt — die Bilder — und bleibt stehen. Nicht, weil Bilder schwer zu beschaffen sind, sondern weil nichts im Stack weiß, welches Bild, von wo, mit welcher Lizenz, wie beschrieben.

Dieser Artikel ist genau dieser fehlende Schritt, Ende zu Ende verdrahtet: welche Art Visual für welche Art Abschnitt entstehen soll, der Prompt, der einen fertigen Entwurf in Such-Briefs verwandelt, der API-Call, der die Fotos liefert, das zweite Modell, das zeichnet, was ein Foto nicht kann, und der Assembler, der Markup ausgibt, das Google tatsächlich lesen kann. Am Ende zwei vollständige Workflows, zum direkten Übernehmen.

Text ist gelöst. Illustration ist der Punkt, an dem es stockt.

Machen Sie den ehrlichen Audit einer automatisierten Artikel-Pipeline. Gliederung: gelöst. Entwurf: gelöst. Titel, Meta-Description, Schema, interne Links, Übersetzung: gelöst, alles vom selben Modell, alles in Text. Dann:

Pipeline-Schritt Status Was wirklich blockiert
Gliederung & Entwurf Gelöst Ein Modell-Call, ein Prompt
Titel, Meta, Schema, Links Gelöst Text rein, Text raus
Hero-Bild Blockiert Braucht eine echte Datei, eine Lizenz, Abmessungen und Alt-Text — nichts davon kann ein Textmodell liefern
Abschnittsbilder Blockiert Drei bis fünf pro Artikel, jedes anders, keines wiederholt sich auf der Website
Charts & Diagramme Blockiert Müssen korrekt sein — das eine Visual, das eine Suche nicht liefern kann und ein Generator nicht erfinden darf

Das Scheitern ist nicht ästhetisch, sondern strukturell: Der Artikel geht mit einem Stock- Platzhalter live, oder mit demselben Foto wie die letzten zwölf, oder mit einem generierten Bild, dessen sechsfingrige Hand das Erste ist, was ein Leser sieht. Und das Bild ist keine Dekoration — Googles eigene Bild-Dokumentation stellt es klar: Alt-Text „ist das wichtigste Attribut, wenn es darum geht, Metadaten für ein Bild bereitzustellen“, und die Empfehlung ist, echte <img>-Elemente mit beschreibendem alt statt CSS-Hintergründen zu verwenden, damit das Bild überhaupt gefunden und verstanden werden kann.1

Vier Arten von Visuals, vier Arten von Modell

Der größte Design-Fehler ist es, „das Bild“ als ein Problem mit einem Anbieter zu behandeln. Es sind vier, und der Router zwischen ihnen ist eine Zeile Prompt, kein Service:

Der Abschnitt braucht… Erzeugt mit Warum nicht die anderen
Eine Szene aus der realen WeltHero, menschliche Situationen, Orte, Gegenstände, Gesten Semantische Fotosuche (Pexafy) Ein Generator erfindet die Details; ein Chart hat nichts zu plotten
Zahlen, die man tatsächlich hatBenchmarks, Preise, Umfrageergebnisse, Latenz Ein Modell, das Plotting-Code schreibt, ausgeführt in einer Sandbox Einem Bildmodell kann man keinen Wert anvertrauen; ein Foto kann keine Daten tragen
Eine Struktur oder einen AblaufArchitektur, Sequenz, Zustandsautomat Ein Modell, das Mermaid / Graphviz schreibt, deterministisch gerendert Kostenlose Foto-Bibliotheken haben kein Diagramm von Ihrem System
Ihr Produkt auf dem BildschirmDocs, Changelog, Tutorials Ein skriptgesteuerter Browser-Screenshot (Playwright) Nichts anderes kann eine UI zeigen, die nur in Ihrem Build existiert

Und generierte Illustration? Sie behält einen ehrlichen Platz: die Szene, die nicht fotografiert werden kann und keine Daten sind — ein abstrakter Mechanismus, ein Produkt, das noch nicht existiert, ein Illustrationsstil im Hausdesign, den Sie besitzen. (Die vollständige Argumentation für echte Fotografie gegenüber Generierung — Geschwindigkeit bei großen Mengen, Genauigkeit, das Problem der Gleichförmigkeit — wird hier ausgeführt.) Der Preis zeigt die Richtung an: Seit dem 2. August 2026 verlangt Artikel 50 des EU AI Act von Anbietern generativer Systeme, synthetische Ausgaben in einem maschinenlesbaren Format zu kennzeichnen.2 Das ist eine Pflicht für KI-Anbieter und -Betreiber, keine Regel dafür, was ein Blog veröffentlichen darf — aber deshalb wird die Herkunft des Bildes am Anfang Ihres Artikels zunehmend zu etwas, das ein Leser prüfen kann, statt es einfach glauben zu müssen.

Die Pipeline, Ende zu Ende

Fünf Stufen. Nur Stufe 3 spricht mit einer Bild-API, und nur Stufe 4 ist optional:

Die Form davon — ein Artikel rein, ein veröffentlichungsfertiger Artikel raus
┌─ 1. WRITE ────────────────────────────────────────────────────┐
   topic ──▶ LLM ──▶ draft.md  (h2 sections, front-matter)
└───────────────────────────────┬───────────────────────────────┘
┌─ 2. BRIEF ────────────────────┴───────────────────────────────┐
   draft.md ──▶ LLM ──▶ { hero: {...}, sections: [ {...} ] }
                     one JSON object: per slot, a kind +
                     either a camera brief or a data/diagram spec
└──────────┬──────────────────────────────────┬─────────────────┘
           │ kind = "photo"                │ kind = "chart" | "diagram"
           ▼                               ▼
┌─ 3. SEARCH ───────────────┐   ┌─ 4. DRAW (optional) ─────────┐
   GET /search/photos          LLM ──▶ mermaid | plotting code
   ← credited photo +           ──▶ sandbox ──▶ .svg / .png
     w/h, blur_hash, alt,       (deterministic render, no
     licence, source URL         invented numbers)
└──────────┬────────────────┘   └──────────────┬───────────────┘
           └───────────────┬──────────────────┘
┌─ 5. ASSEMBLE ─────────────┴───────────────────────────────────┐
   <img> with width/height + fetchpriority | loading
   alt written for a human · visible credit · ImageObject JSON-LD
   photo_id stored so no two pages share a hero
└───────────────────────────────────────────────────────────────┘

Zwei Eigenschaften sind wichtiger als das Diagramm. Stufe 2 ist ein Router: Sie entscheidet pro Slot, welcher Erzeuger läuft, sodass Sie eine Foto-Bibliothek niemals nach einem Balkendiagramm fragen. Und in Stufe 5 steckt die SEO — alles, was Google zu Bildern dokumentiert (beschreibendes Alt, Lizenz-Metadaten, ein LCP-sicherer Hero), wird hier ausgegeben, aus Feldern, die die Such-Antwort bereits mitbrachte.

Stufe 2: den Entwurf in Visual-Briefs verwandeln

Ein Modellaufruf auf den fertigen Entwurf, und was zurückkommt, ist ein Plan statt einer Anfrage. Wie man ein einzelnes Foto-Briefing schreibt — warum der Artikeltitel die schlechteste mögliche Eingabe ist, wie ein Kamera-Briefing mit 12 bis 25 Wörtern aussieht, und auf welche Weisen es scheitert — ist vollständig dargelegt in dem Leitfaden zur Illustration eines Artikels, und wird hier nicht wiederholt. Was eine Pipeline hinzufügt, ist Routing: derselbe Aufruf muss Slot für Slot entscheiden, welcher Erzeuger läuft — und ein Chart-Eintrag trägt Daten, wo ein Foto-Eintrag eine Szene trägt.

Der Router-Prompt — unverändert übernehmen
# system prompt — run once per finished draft
You are the art director of a technical publication. Read the article and
return the visual plan: one entry for the hero, one per H2 section.

For each entry choose exactly one kind:
  "photo"    a real scene: someone doing something somewhere, a place,
              an object, a gesture. The default for heroes.
  "chart"    the section states numbers that are IN the article. Never
              invent values: copy them into data, verbatim.
  "diagram"  the section describes a structure, a flow or a sequence.
  "none"     the section is short, or already carries a code block.

Rules for "photo" entries — the field is query:
   Schreibe ein Kamera-Briefing: eine Szene, die eine Kamera hätte
   aufnehmen können, 12 bis 25 Wörter, auf Englisch, passend zur
   Stimmung des Abschnitts. Benenne, was im Bild zu sehen ist, niemals
   das Thema. Kein Text, keine Logos, Marken oder berühmten Personen;
   keine unsichtbaren Metaphern. (Full rules, with examples and failure cases:
   pexafy.com/blog/illustrate-blog-articles-at-scale/)

Do NOT write the alt text of a photo entry: the picture you get back is the
closest match to the brief, not the scene you described, so its alt has to be
written from the chosen photo. Chart and diagram entries DO carry an alt —
there you control exactly what is rendered.

Return JSON only:
{
  "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": "…" }
  ]
}

Die kind-Zeile erledigt den Großteil der Arbeit, und die data-Anweisung den Rest: Ein Chart-Eintrag darf nur Zahlen tragen, die bereits im Entwurf vorkommen, sodass das Modell überträgt statt erfindet. Hier ist der Router an drei realen Abschnitten eines Entwickler-Artikels:

Abschnitt kind Was der Router zurückgab
Hero — „Warum unser nächtlicher Build 40 Minuten dauert“ photo „ein Entwickler, der abends spät an einem Schreibtisch mit zwei Monitoren und einer mechanischen Tastatur in einem dunklen, vom Bildschirmlicht beleuchteten Raum arbeitet“
„Wohin die Zeit tatsächlich fließt“ chart data aus dem Absatz kopiert: Installation 480 s, Kompilieren 1080 s, Tests 720 s, Upload 120 s
„Wie wir den Graphen aufteilen“ diagram flowchart LR des Job-Abhängigkeitsgraphen
„Was wir geändert haben, und was wir wieder so machen würden“ photo „zwei Ingenieure an einem Whiteboard voller Diagramme, die gemeinsam ein Problem durchdenken“

Stufe 3: die Fotos kommen kreditiert zurück

Jeder kind: "photo"-Eintrag ist eine Anfrage. Der obige Hero-Brief, gegen die öffentliche API ausgeführt, liefert dies in 147 ms:

GET /search/photos — Hero-Brief · „ein Entwickler, der abends spät an einem Schreibtisch mit zwei Monitoren arbeitet…“ · 147 ms
Die Engine ordnet nach Bedeutung, sodass der lange Satz die Auswahl eingrenzt statt sie zu leeren. Genau diese Suche ausführen →

Der letzte Abschnitts-Brief, eine völlig andere Szene, in 144 ms:

GET /search/photos — Abschnitts-Brief · „zwei Ingenieure an einem Whiteboard voller Diagramme…“ · 144 ms
Derselbe Artikel, derselbe Lauf, eine Szene, die niemand mit dem Hero verwechseln würde — weil der Brief pro Abschnitt geschrieben wurde, nicht pro Artikel. Auch diese ausführen →

Was pro Foto zurückkommt, ist der Teil, der Stufe 5 überhaupt möglich macht — nicht nur eine Datei:

Ein Ergebnis, gekürzt auf die Felder, die der Assembler nutzt
{
  "photo_id":  "019e1c7f-0063-759e-b498-33ce1714e6c9",   // speichern: keine Wiederholungen
  "urls": { "small": "…?w=400", "regular": "…?w=1080",
             "large": "…?w=1920" },
  "width": 3000, "height": 1688,          // → kein Layout-Shift
  "blur_hash": "LJ8gjv9rVq-6OFxanNNFI7xco$Na",   // → echter Platzhalter
  "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 (…)" }
}

Stufe 4: was ein Foto nicht sagen kann

Zwei Slots im Plan sind nicht durchsuchbar, und genau hier verdient sich ein zweites Modell seinen Platz — nicht um ein Bild zu zeichnen, sondern um Code zu schreiben, der es zeichnet. Der Unterschied ist wichtig: Code ist überprüfbar, deterministisch und kann keine Balkenhöhe halluzinieren.

Diagramme: Text rein, SVG raus

Mermaid rendert Diagramme aus einer reinen Textdefinition,3 was es zum sichersten Ziel für ein Modell macht: Die Ausgabe ist inspizierbar, in git diffbar und rendert jedes Mal gleich. Der Router hat die Spezifikation bereits zurückgegeben.

diagram.sh — das Modell schrieb die Spec, die CLI rendert sie
# the "spec" field of a diagram entry, written to 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
# → ein SVG, das man im PR prüfen kann, kein Bild, dem man vertrauen muss

Charts: nur Zahlen, die der Artikel bereits enthält

Gleiches Prinzip, ein zusätzlicher Schutz. Der Router hat die Werte aus dem Entwurf kopiert; das Modell schreibt den Plotting-Code; der Code läuft in einer Sandbox; der Assembler überprüft die gerenderten Werte erneut gegen die Quellzahlen, bevor das Chart in die Nähe einer Seite darf.

chart.py — die abgeschriebenen Daten plotten, dann verifizieren
import re, matplotlib
matplotlib.use("Agg")                # headless: kein Display in CI
import matplotlib.pyplot as plt

def assert_in_draft(value, draft: str) -> None:
    """Eine geplottete Zahl muss ALS ZAHL im Artikel vorkommen."""
    # Substring-Matching ist hier die Falle: "120" steckt in "1200" und
    # in "?w=1200" — ein naives `str(v) in draft` schlägt bei allem an.
    # An Wortgrenzen matchen, 1 234 / 1,234 / 1234 akzeptieren.
    body = re.sub(r"(?<=\d)[  ,](?=\d{3}\b)", "", draft)   # Trennzeichen entfernen
    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)       # halluzinierter Wert → kein Chart

    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)                    # deterministisches, überprüfbares Artefakt
    return out

Der Schutz ist kurz, aber schreiben Sie ihn sorgfältig: eine Substring-Prüfung funktioniert nicht. "120" in draft ist wahr für einen Artikel, der 1200 oder ?w=1200 enthält — die naive Version schlägt also bei allem an und schützt vor nichts. Ankern Sie an Ziffer-Grenzen, normalisieren Sie Tausendertrennzeichen, und die Fehlerklasse, die einen technischen Artikel in den Kommentaren auseinandernimmt — ein Chart, das seinem eigenen Absatz widerspricht — kann so nicht in die Produktion gelangen. Screenshots folgen demselben Prinzip: ein skriptgesteuertes page.screenshot() gegen Ihren echten Build ist die einzige Quelle der Wahrheit für Ihre eigene UI, und es bleibt wahr, wenn sich die UI ändert.

Stufe 5: In der Assembly steckt die SEO

Alles bisher hat Dateien und Felder erzeugt. Diese Stufe verwandelt sie in Markup — und es lohnt sich, hier präzise zu sein, denn in diesen wenigen Zeilen werden drei dokumentierte Verhalten entschieden.

Der Hero, ausgegeben aus der Such-Antwort — nichts erfunden
<!-- The bytes come from another origin: pay the handshake early -->
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>

<!-- LCP element: never lazy, always high priority -->
<figure>
  <img src="{urls.regular}"
       width="{width}" height="{height}"        <!-- kills layout shift -->
       alt="{alt}"                                <!-- written AFTER the pick -->
       fetchpriority="high" decoding="async"
       style="background:{color_hex}">   <!-- dominant colour, 1 field -->
  <figcaption>{attribution.html}</figcaption>
</figure>

<!-- Section images, below the fold: the opposite settings -->
<img src="{urls.regular}" width="{width}" height="{height}"
     alt="{alt}" loading="lazy" decoding="async">

Hotlink oder selbst hosten? Der Snippet oben verlinkt direkt (Hotlink), was am schnellsten umzusetzen ist und der Grund für das preconnect: Ein entfernter Hero kostet einen DNS-Lookup und einen TLS-Handshake auf dem kritischen Pfad, und das kann den Gewinn aufzehren, den Sie sich gerade mit fetchpriority erkauft haben. Selbst Hosten entfernt die Drittanbieter-Origin vollständig, erlaubt AVIF/WebP an Ihren eigenen Breakpoints und überlebt eine sich ändernde Upstream-URL — zum Preis von Speicherplatz, einem Fetch-Schritt in der Pipeline und Ihrer eigenen CDN-Rechnung. Was auch immer Sie wählen: color_hex liefert Ihnen einen Platzhalter für ein Feld (ein Blur-Hash ist hübscher, muss aber erst in eine Data-URI decodiert werden — es ist keine CSS-Farbe). Einen ungefilterten Hero direkt in background: zu kopieren, ist der Punkt, an dem die meisten Pipelines stillschweigend eine leere graue Box ausliefern.

  1. Den Hero niemals lazy-loaden. web.dev ist unmissverständlich: „Never lazy-load your LCP image, as that will always lead to unnecessary resource load delay, and will have a negative impact on LCP“, und empfiehlt fetchpriority="high" auf dem Element, das vermutlich das LCP ist — sparsam eingesetzt, auf einem Bild.4 Eine Pipeline, die jedem Bild loading="lazy" aufdrückt, Hero eingeschlossen, ist die häufigste selbstverschuldete Core-Web-Vitals-Wunde im automatisierten Publizieren.
  2. Immer width und height ausgeben — sie kommen in der Antwort zurück, es gibt also keine Ausrede; genau dieses Attributpaar erlaubt es dem Browser, den Platz zu reservieren, und stoppt das Springen des Layouts. Nutzen Sie blur_hash als Platzhalter, während die Datei lädt.
  3. Den Alt-Text nach der Auswahl schreiben, nie vorher. Das ist der subtile Punkt. Das alt-Feld des Plans beschreibt die Szene, um die Sie gebeten haben; das Foto, das Sie bekommen haben, ist die nächstgelegene Übereinstimmung, nicht diese Szene. Den Text des Briefs als Alt auszuliefern ist genau der Accessibility-Fehler, den diese Pipeline vermeiden soll — eine Beschreibung eines Bildes, das nicht auf der Seite ist. Bauen Sie das Alt aus der alt_description des gewählten Fotos, verfeinert am Absatz, in dem es sitzt. Googles Leitlinie ist, sich auf die Erstellung „nützlicher, informationsreicher Inhalte zu konzentrieren, die Keywords angemessen verwenden und im Kontext des Seiteninhalts stehen“, und warnt, dass das Vollstopfen von Alt-Attributen mit Keywords „zu einer negativen Nutzererfahrung führt und dazu führen kann, dass Ihre Website als Spam eingestuft wird“.1

Der Teil, den fast niemand automatisiert: Lizenz-Metadaten

Google unterstützt ImageObject-Structured-Data für Bildlizenzierung. Es verlangt contentUrl plus mindestens eines von creator, creditText, copyrightNotice oder license, empfiehlt acquireLicensePage, und Bilder mit Lizenzinformationen werden für das Licensable-Badge in Google Images qualifizierbar.5 Jedes dieser Felder ist bereits in der Such-Antwort enthalten — es auszugeben ist also ein Template, kein Projekt:

ImageObject JSON-LD, gefüllt aus der API-Antwort
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "ImageObject",
  "contentUrl": "{urls.large}",                 // erforderlich
  "creator": { "@type": "Person",
                "name": "{photographer_full_name}" },
  "creditText": "{photographer_full_name} on {source}",
  "license": "{LICENSE_URL[source]}",           // die eigene Seite der Bibliothek
  "acquireLicensePage": "{source_image_url}"     // die Seite des Fotos
}
</script>

# LICENSE_URL bildet das `source`-Feld auf die Lizenz ab, die für das
# Foto tatsächlich gilt — unsplash.com/license, pexels.com/license, pixabay.com/…

Ein Detail, das man richtig machen sollte: license sollte auf die Lizenz zeigen, die genau dieses Foto regelt — die eigene Lizenzseite der Quell-Bibliothek — nicht auf eine Zusammenfassungsseite Ihrer Domain. Google liest das, um über die Badge-Qualifizierung zu entscheiden, und eine selbstreferenzielle URL ist als Signal sowohl schwächer als auch schwer als etwas anderes zu verteidigen als ein Link zu sich selbst. Behalten Sie Ihre eigene Lizenz-Zusammenfassung als interne Seite für Leser; setzen Sie die kanonische in das Markup.

Und solange Sie im Assembler sind: Geben Sie der Datei einen kurzen, beschreibenden Namen statt IMG_0042.jpg, und fügen Sie das Bild einer Sitemap hinzu — Googles Bild-Sitemap-Format akzeptiert bis zu 1.000 Bilder pro Seiten-URL.6 Beides ist jeweils eine Zeile in einer Pipeline und wird von Hand nie erledigt.

Zwei Pipelines zum Übernehmen

Dieselben fünf Stufen, zwei sehr unterschiedliche Formen — eine für Artikel, die Sie schreiben, eine für Dokumentation, die zu einem laufenden Produkt passen muss. Wählen Sie die, deren Fehlermodus Sie wiedererkennen.

1 · Der Dev-Blog in CI — Markdown im Repo

Artikel liegen als Markdown vor, Bilder werden daneben committet, und das Ganze läuft bei jedem Push. Deterministisch, im PR überprüfbar, keine Laufzeit-Abhängigkeit von irgendeiner 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   # Bilder landen im PR
        with: { commit-message: "chore(content): illustrate" }

Ein Mensch genehmigt den PR trotzdem — das ist der Punkt: Die Pipeline schlägt vor, der Reviewer entscheidet, und die geschriebenen Front-Matter-Daten sind diffbar.

Die Suchhälfte ist ein einziges GET — die Anfrage, ihr score_threshold und die Felder, die sie zurückgibt, sind Zeile für Zeile ausgeführt in dem Leitfaden zum einzelnen Artikel, wird hier also importiert statt erneut abgedruckt. Was diese Datei hinzufügt, ist alles, was ein Plan braucht und ein einzelnes Foto nicht: der erneute Versuch bei einem Briefing, das nichts zurückgab, die Reservierung einer photo_id, damit sich keine zwei Seiten ein Bild teilen, und der Alt-Text, der aus dem zurückgekommenen Foto geschrieben wird.

plan_to_pr.py — der Kitt: visueller Plan hinein, Front-Matter hinaus
import frontmatter
from photo_search import search   # ein GET /search/photos; überspringt IDs in `used`

def find_photo(entry: dict, used: set) -> dict | None:
    """Suchen; war der Brief zu spezifisch, einmal erweitern, dann aufgeben."""
    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"])   # beanspruchen: keine Wiederholungen site-weit
            return photo
    return None                        # Aufrufer entscheidet: Slot überspringen oder fehlschlagen

def widen(query: str) -> str:
    """Den letzten Nebensatz entfernen — meist der zu spezifische."""
    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:                    # kein Hero ist besser als ein schlechter
        raise SystemExit(f"{path}: no photo above threshold — rewrite the brief")

    post["hero"] = {                    # alles, was das Template braucht
        "src": hero["urls"]["regular"], "w": hero["width"], "h": hero["height"],
        # Das Alt beschreibt das Foto, das wir BEKOMMEN haben, nie die erbetene Szene.
        "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 als Basis, auf ~125 Zeichen für Screenreader gekürzt.
    Für bessere Ergebnisse zusammen mit dem Absatz erneut ans Modell schicken."""
    base = photo.get("alt_description") or photo.get("description", "")
    return base[:125].rstrip(" ,;")

2 · Docs & Changelog — zuerst Screenshots, zuletzt Fotos

Kehren Sie die Standardeinstellungen des Routers um. In Produktdokumentation ist das ehrliche Visual fast immer Ihre eigene UI: ein Playwright-Skript, das den echten Build öffnet, ein festes Viewport setzt und genau den Zustand einfängt, den der Absatz beschreibt. Diagramme decken die Architekturseiten ab, und Fotografien erscheinen nur auf den konzeptionellen und Landingpages — wo ein Screenshot nichts aussagen würde.

shots.py — der Screenshot wird generiert, nie beschrieben
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)   # retina-scharf
    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()
# Läuft im selben CI-Job wie der Docs-Build → der Screenshot kann niemals
# eine Version der UI beschreiben, die nicht mehr existiert.

Zwei Varianten, die man erwähnen sollte, aber die kein eigenes Rezept brauchen. Programmatic SEO kehrt die Schleife um: Bei Hunderten von Seiten, die aus einer Datenbank generiert werden, sucht man nicht pro Seite — eine Anfrage liefert bis zu 100 Fotos, sodass man pro Themen-Cluster sucht und aus einem Pool zuweist, wobei eine Eindeutigkeits-Bedingung die Deduplizierung übernimmt (diese Architektur, im Detail). Und Newsletter und Social Cards brauchen dasselbe Foto in vier Zuschnitten: photo_id behalten, die benötigte Größe aus urls anfordern, und nur dann erneut suchen, wenn ein Zuschnitt tatsächlich scheitert.

Dieselbe Pipeline, als ein Agent

Wenn ohnehin schon ein Modell den Entwurf schreibt, ist der kürzeste Weg, ihm das Suchwerkzeug direkt zu geben, statt JSON zwischen Prozessen hin- und herzuschieben. Pexafy betreibt einen gehosteten Model Context Protocol-Server unter mcp.pexafy.com/mcp. Das Connector-Setup und die Werkzeuge, die er bereitstellt, werden an anderer Stelle behandelt — das Desktop- und Editor-Setup in dem Leitfaden für einzelne Artikel, die Headless-Variante für einen CI-Runner in dem Artikel über Content im großen Maßstab, und wie der breitere Connector-Markt aussieht — wer einen MCP-Bildserver anbietet, zu welchen Konditionen — in der Studie zur Bildsuch-Infrastruktur für KI-Agenten. Was sich hier zu zeigen lohnt, ist, was mit dieser Pipeline passiert, sobald der Agent die Werkzeuge selbst in der Hand hält: Aus fünf Stufen wird eine einzige Anweisung.

Eine Anweisung, der ganze Plan ausgeführt
You  Here is the draft. Build the visual plan, illustrate it, and open a PR.
     Charts only from numbers already in the text.

Agent  → plan: 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 photos · 147 ms · picked #1, 3000×1688, credited
      → mermaid-cli build/graph.mmd → static/img/graph.svg
      → chart.py §2 → 4 values checked against the draft ✓
      → search_photos(q="two engineers standing at a whiteboard…")
      ← 16 photos · 144 ms · picked #1

      ✓ 4 slots filled · 2 API calls · alt + credit + ImageObject written
      ⚠ §5 returned nothing above 0.5 — brief too abstract, rewritten as
        "a person at a kitchen table checking figures on a laptop"

Die letzte Zeile ist der Grund, warum sich ein Agent in dieser Schleife lohnt statt eines reinen Skripts: Der Fehlermodus automatisierter Illustration ist ein schlechter Brief, und einen schlechten Brief umzuschreiben ist genau das, wofür ein Sprachmodell da ist. Die deterministischen Teile — Zuweisung, Deduplizierung, die numerische Prüfung — bleiben im Code.

Was ein illustrierter Artikel kostet

Drei Foto-Slots pro Artikel bedeuten drei Such-Anfragen, sodass der Free-Plan (5,000 Anfragen/Monat) 1,666 Artikel im Monat abdeckt, bevor überhaupt die Frage nach Bezahlung aufkommt — und wenn Sie Suchen pro Themen-Cluster statt pro Artikel poolen, verschiebt sich diese Grenze um eine weitere Größenordnung. Die Aufschlüsselung Plan für Plan, und die Pooling-Architektur, die das ohnehin irrelevant macht, stehen in dem Begleitartikel über Illustration von Content in großem Maßstab.

Die zwei Modell-Aufrufe pro Artikel — einer für den Entwurf, einer für den Visual-Plan — sind ein paar Tausend Tokens und werden die günstigste Zeile in der Pipeline sein; Mermaid- und matplotlib-Renderings kosten nichts außer CI-Sekunden. Die Zahl, die überrascht, ist, welche Zeile nicht günstig ist: vier generierte Bilder pro Artikel, vierhundert Bilder im Monat, plus die Versuche, die es nicht geschafft haben — und die Ausgabe trägt am Ende immer noch keinen Fotografen, kein Datum und keine Quell-URL.

Was diese Pipeline nicht behebt

Ein ehrlicher Abschnitt, denn der Fehler, den er verhindert, ist teuer. Ein gut illustrierter Artikel ist immer noch ein Artikel: echte Fotografie, korrekte Charts und richtiges Markup verbessern eine Seite, die es verdient zu existieren. Sie lassen dünne, massenproduzierte Inhalte nicht ranken. Googles Spam-Richtlinien benennen Scaled Content Abuse — das Generieren vieler Seiten primär, um Rankings zu manipulieren, und die Bereitstellung wenig Nutzens für Nutzer, unabhängig davon, ob Automatisierung im Spiel ist — und die Qualität der Illustrationen ist in dieser Bewertung kein Faktor.7

Die Einordnung, die trägt: Diese Pipeline ist eine Qualitätsuntergrenze, die Sie kontrollieren, angewendet auf Seiten, die bereits einen Grund haben, veröffentlicht zu werden. Wo sie sich nachweislich auszahlt:

  • Herkunft, die ein Leser überprüfen kann. Eine Credit-Zeile mit einem echten Fotografen und einer Quell-URL ist eine Aussage, die geprüft werden kann — und dieselben Felder füllen das ImageObject-Markup, das Google liest.
  • Accessibility und Core Web Vitals. Echter Alt-Text, Abmessungen auf jedem Bild, ein Hero, der nie lazy-geladen wird. Multipliziert mit jedem Artikel, den Sie veröffentlichen, ist das die Bildqualitäts-Story der Website.
  • Genauigkeit, wo sie überprüfbar ist. Ein Chart, dessen Zahlen gegen den Artikel abgesichert sind, ein Screenshot, generiert aus dem laufenden Build, ein Diagramm, als Text im PR überprüfbar — drei Visuals, die nicht von der Wahrheit abweichen können, ohne dass ein Test fehlschlägt.

Bei der Namensnennung ist der pipeline-spezifische Punkt eng gefasst: das Argument, den Credit immer anzuzeigen wird anderswo vorgebracht, und was ein automatisierter Assembler hinzufügt, ist, dass derselbe attribution-String, den er ausgibt, der creditText ist, den die strukturierten Daten brauchen. Ein Feld, zwei Stellen, im selben Template-Durchlauf erzeugt — weshalb eine Pipeline noch weniger Entschuldigung hat als ein Mensch, es wegzulassen.

Wo Sie anfangen

  1. Den Router-Prompt hinzufügen zu allem, was bereits Ihre Entwürfe schreibt, und das JSON ausgeben, ohne darauf zu reagieren. Zehn Pläne lesen. Wenn die Briefs Themen statt Szenen benennen, den Prompt fixen, bevor Sie irgendeine Integration schreiben.
  2. Nur die Foto-Slots verdrahten. Ein GET /search/photos pro Brief, und photo_id vom ersten Tag an speichern — Deduplizierung, die man über 3.000 Live-Seiten nachträglich einbaut, ist eine Migration, keine Spalte.
  3. Width, Height und Alt im selben Commit ausgeben. Es ist die günstigste Core-Web-Vitals-Arbeit, die Sie je machen werden.
  4. Dann Stufe 4 hinzufügen, Diagramme vor Charts — Mermaid ist Text, hat also keinen anderen Fehlermodus als einen Syntaxfehler.
  5. Die numerische Prüfung hinzufügen, bevor das erste Chart einen Leser erreicht, nicht danach.

Quellen & Fußnoten

1 Google Search Central, Image SEO best practices: Alt-Text ist „das wichtigste Attribut, wenn es darum geht, Metadaten für ein Bild bereitzustellen“; die Empfehlung lautet, „nützliche, informationsreiche Inhalte zu erstellen, die Keywords angemessen verwenden und im Kontext des Seiteninhalts stehen“, keyword-vollgestopfte Alt-Attribute zu vermeiden, HTML-<img>-Elemente statt CSS-Bilder zu nutzen und Dateien kurze, aber beschreibende Namen zu geben.

2 EU AI Act, Artikel 50 — Transparenzpflichten, anwendbar ab 2. August 2026: Anbieter von Systemen, die synthetische Bild-, Audio-, Video- oder Textinhalte erzeugen, müssen Ausgaben in einem maschinenlesbaren Format kennzeichnen und als künstlich erzeugt erkennbar machen. Es bindet KI-Anbieter und -Betreiber; es ist keine Regel dazu, welche Bilder eine Website veröffentlichen darf.

3 Mermaid rendert Diagramme und Charts aus Markdown-inspirierten Textdefinitionen, was die Ausgabe überprüfbar und deterministisch macht.

4 web.dev, Optimize Largest Contentful Paint: „It's a good idea to set fetchpriority="high" on an <img> element if you think it's likely to be your page's LCP element“, sparsam eingesetzt; und „Never lazy-load your LCP image, as that will always lead to unnecessary resource load delay, and will have a negative impact on LCP.“

5 Google Search Central, Image metadata (structured data): ImageObject erfordert contentUrl plus mindestens eines von creator, creditText, copyrightNotice oder license; acquireLicensePage wird empfohlen, und Bilder mit Lizenzinformationen können für das Licensable-Badge in Google Images qualifizierbar werden.

6 Google Search Central, Image sitemaps: Bild-Sitemaps informieren Google über Bilder auf einer Website, einschließlich solcher, die über JavaScript gefunden werden, und akzeptieren bis zu 1.000 Bilder pro Seiten-URL.

7 Google Search Spam Policies — Scaled Content Abuse: das Generieren vieler Seiten primär, um Rankings zu manipulieren, und die Bereitstellung wenig Nutzens für Nutzer, ob durch Automatisierung, menschlichen Aufwand oder eine Kombination erzeugt.

Häufig gestellte Fragen

Wie illustriere ich automatisch Artikel, die von einem LLM geschrieben wurden?
Fügen Sie zwischen Schreiben und Veröffentlichen einen zusätzlichen Modellaufruf ein: Lassen Sie es einen visuellen Plan zurückgeben — einen Eintrag pro Bildplatz, jeweils markiert als Foto, Chart, Diagramm oder nichts. Foto-Einträge enthalten eine 12- bis 25-Wörter-Beschreibung einer Szene, die eine Kamera hätte aufnehmen können, die Sie an eine semantische Bildsuch-API senden (GET /api/v1/search/photos, etwa 150 ms). Chart- und Diagramm-Einträge gehen an ein Modell, das Plot-Code oder Mermaid schreibt, deterministisch gerendert. Geben Sie niemals den Artikeltitel in eine Bildsuche ein: Titel sind abstrakt, und kein Foto bildet sie ab.
Sollte eine Dokumentationsseite Screenshots oder Stockfotos verwenden?
Kehren Sie die Standardeinstellungen des Routers um: In der Produktdokumentation ist das ehrliche visuelle Element fast immer die eigene Benutzeroberfläche. Ein Playwright-Skript, das den echten Build öffnet, den Viewport fixiert und genau den Zustand erfasst, den der Absatz beschreibt, läuft im selben CI-Job wie der Doku-Build, sodass ein Screenshot niemals eine Version der Oberfläche zeigen kann, die es nicht mehr gibt. Diagramme tragen die Architekturseiten, und Fotografien erscheinen nur auf konzeptionellen Seiten und Landingpages, wo ein Screenshot nichts aussagen würde.
Wie verhindere ich, dass eine KI-Pipeline falsche Zahlen in ein Chart einbaut?
Sorgen Sie dafür, dass der Plan transkribiert statt erfindet: Der Router darf nur Werte, die bereits im Entwurf stehen, in das data-Feld des Chart-Eintrags kopieren. Prüfen Sie dies dann per Code vor dem Rendern — für jeden Wert wird kontrolliert, ob seine Zeichenkette im Artikel vorkommt, andernfalls wird ein Fehler ausgelöst. Neun Zeilen, und ein Chart, das seinem eigenen Absatz widerspricht, kann niemals einen Leser erreichen.
Welches Bild-Markup sollte eine automatisierte Pipeline für SEO ausgeben?
Drei Dinge, alle aus Feldern, die die Suchantwort bereits enthält. Ein beschreibender alt-Text, verfasst im Kontext des Absatzes — Google bezeichnet Alt-Text als die wichtigsten Bildmetadaten und warnt vor Keyword-Stuffing. width und height für jedes Bild, mit fetchpriority="high" beim Hero-Bild und niemals loading="lazy" darauf, denn das LCP-Bild darf nicht lazy geladen werden. Und ImageObject-strukturierte Daten mit contentUrl, creator, creditText und license, was ein Bild erst für das Licensable-Badge in Google Bilder qualifiziert.
Hilft das Illustrieren KI-geschriebener Artikel beim Ranking?
Nicht von allein, und hier lohnt sich Präzision. Googles Spam-Richtlinien definieren Scaled Content Abuse als das Erzeugen vieler Seiten, primär um Rankings zu manipulieren, mit geringem Nutzen für Nutzer — unabhängig davon, ob Automatisierung beteiligt ist; die Illustrationen ändern diese Einschätzung nicht. Was eine gute Pipeline bringt, ist eine Qualitätsuntergrenze für Seiten, die ohnehin existieren sollten: nachvollziehbare Herkunft, barrierefreier Alt-Text, Core Web Vitals, die Automatisierung überstehen, sowie Charts und Screenshots, die nicht von der Wahrheit abweichen können.
Wie führe ich den Bebilderungsschritt in CI aus, ohne die redaktionelle Kontrolle zu verlieren?
Lösen Sie den Job bei einem Pull-Request aus, der Ihre Content-Dateien betrifft, lassen Sie ihn die Bilder und das Front-Matter schreiben, und lassen Sie ihn einen Pull-Request öffnen, statt direkt auf den Branch zu committen — die Pipeline schlägt vor, ein Mensch genehmigt, und jedes von ihr geschriebene Feld ist per Diff überprüfbar. Halten Sie drei Dinge im Code deterministisch statt im Modell: die Deduplizierung nach photo_id, die Prüfung, dass jede dargestellte Zahl auch im Artikel vorkommt, und einen harten Fehlschlag, wenn kein Foto den Score-Schwellenwert erreicht. Kein Hero-Bild ist besser als ein falsches.

Schluss mit der Jagd nach Schlüsselwörtern. Beschreiben Sie, was Sie meinen.

Durchsuchen Sie 9M+ kostenlos nutzbare Bilder nach Bedeutung — in jeder Sprache, in unter 100 ms.