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.
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:
┌─ 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.
# 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:
Der letzte Abschnitts-Brief, eine völlig andere Szene, in 144 ms:
Was pro Foto zurückkommt, ist der Teil, der Stufe 5 überhaupt möglich macht — nicht nur eine Datei:
{
"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.
# 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.
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.
<!-- 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.
-
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 Bildloading="lazy"aufdrückt, Hero eingeschlossen, ist die häufigste selbstverschuldete Core-Web-Vitals-Wunde im automatisierten Publizieren. -
Immer
widthundheightausgeben — 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 Sieblur_hashals Platzhalter, während die Datei lädt. -
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 deralt_descriptiondes 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:
<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:
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.
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.
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.
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
- 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.
- Nur die Foto-Slots verdrahten. Ein
GET /search/photospro Brief, undphoto_idvom ersten Tag an speichern — Deduplizierung, die man über 3.000 Live-Seiten nachträglich einbaut, ist eine Migration, keine Spalte. - Width, Height und Alt im selben Commit ausgeben. Es ist die günstigste Core-Web-Vitals-Arbeit, die Sie je machen werden.
- Dann Stufe 4 hinzufügen, Diagramme vor Charts — Mermaid ist Text, hat also keinen anderen Fehlermodus als einen Syntaxfehler.
- 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.
Quellen geprüft am 17. August 2026: Image SEO best practices · Image metadata · Image sitemaps · Optimize LCP · Search spam policies · AI Act Article 50 · Mermaid · Pexafy API & MCP docs. Such-Zeiten (147 ms, 144 ms) und jedes gezeigte Foto sind echte API-Antworten, aufgezeichnet am selben Tag.
Häufig gestellte Fragen
Wie illustriere ich automatisch Artikel, die von einem LLM geschrieben wurden?
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?
Wie verhindere ich, dass eine KI-Pipeline falsche Zahlen in ein Chart einbaut?
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?
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?
Wie führe ich den Bebilderungsschritt in CI aus, ohne die redaktionelle Kontrolle zu verlieren?
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.