Tekst to najprostsza część: pipeline, który ilustruje artykuły pisane przez AI

Generowanie tekstu to problem rozwiązany. Ilustrowanie go — nie. Kompletny pipeline: prompt-router, wyszukiwanie zdjęć, diagramy Mermaid, zweryfikowane wykresy oraz tekst alternatywny, podpisy i znaczniki ImageObject, które zamieniają to w SEO.

Udostępnij
Programista piszący przy biurku z dwoma monitorami pełnymi kodu, oświetlony kolorowymi lampami.
Zdjęcie za pośrednictwem Unsplash

Jesteś deweloperem z celem contentowym: dziesiątki artykułów miesięcznie, może setki. Model piszący obsługuje szkic, konspekt, meta description, linki wewnętrzne. Potem pipeline trafia na jedyny etap, który nie ma własnego modelu — obrazy — i się zatrzymuje. Nie dlatego, że zdjęcia trudno zdobyć, lecz dlatego, że nic w tym stosie technologicznym nie wie, które zdjęcie, skąd, na jakiej licencji i jak opisane.

Ten artykuł jest tym brakującym etapem, połączonym od początku do końca: jaki rodzaj wizualizacji produkować dla jakiego rodzaju sekcji, prompt, który zamienia gotowy szkic w briefy do wyszukiwania, wywołanie API zwracające zdjęcia, drugi model rysujący to, czego fotografia nie potrafi pokazać, oraz assembler generujący znaczniki, które Google faktycznie potrafi odczytać. Na końcu dwa kompletne workflowy, gotowe do skopiowania.

Tekst jest rozwiązany. Ilustracja jest tym, na czym utyka pipeline.

Zróbmy uczciwy audyt zautomatyzowanego pipeline'u artykułów. Konspekt: rozwiązany. Szkic: rozwiązany. Tytuł, meta description, schema, linki wewnętrzne, tłumaczenie: rozwiązane, wszystko przez ten sam model, wszystko w tekście. Potem:

Etap pipeline'u Status Co faktycznie blokuje
Konspekt i szkic Rozwiązane Jedno wywołanie modelu, jeden prompt
Tytuły, meta, schema, linki Rozwiązane Tekst na wejściu, tekst na wyjściu
Obrazek hero Zablokowane Potrzebny jest prawdziwy plik, licencja, wymiary i tekst alt — nic z tego model tekstowy nie wyprodukuje
Obrazy do sekcji Zablokowane Od trzech do pięciu na artykuł, każdy inny, żaden powtórzony w obrębie serwisu
Wykresy i diagramy Zablokowane Muszą być dokładne — to jedyna wizualizacja, której wyszukiwarka nie zwróci, a generator nie może zmyślić

Ta awaria nie jest kwestią estetyki, tylko struktury: artykuł trafia do publikacji z placeholderem stockowym, z tym samym zdjęciem co ostatnie dwanaście, albo z wygenerowanym obrazkiem, w którym sześciopalczasta dłoń jest pierwszą rzeczą, jaką widzi czytelnik. A obrazek to nie dekoracja — własna dokumentacja Google na temat obrazów mówi to wprost: tekst alt „jest najważniejszym atrybutem, jeśli chodzi o dostarczanie metadanych obrazu”, a wytyczne mówią, by używać prawdziwych elementów <img> z opisowym alt, a nie teł CSS, żeby obrazek w ogóle dało się znaleźć i zrozumieć.1

Cztery rodzaje wizualizacji, cztery rodzaje modelu

Największy błąd projektowy to traktowanie „obrazka” jak jednego problemu z jednym dostawcą. To są cztery problemy, a router między nimi to linijka promptu, nie osobna usługa:

Sekcja potrzebuje… Wyprodukuj to za pomocą Dlaczego nie inaczej
Sceny z prawdziwego światahero, sytuacje z udziałem ludzi, miejsca, przedmioty, gesty Semantyczne wyszukiwanie zdjęć (Pexafy) Generator wymyśla szczegóły; wykres nie ma czego przedstawić
Liczb, które faktycznie maszbenchmarki, cennik, wyniki ankiet, opóźnienia Model piszący kod rysujący, uruchamiany w sandboxie Modelowi generującemu obrazy nie można ufać w kwestii wartości liczbowych; zdjęcie nie przenosi danych
Struktury lub przepływuarchitektura, sekwencja, maszyna stanów Model piszący Mermaid / Graphviz, renderowany deterministycznie Darmowe biblioteki zdjęć nie mają diagramu Twojego systemu
Twojego produktu na ekraniedokumentacja, changelog, tutoriale Zeskryptowany zrzut ekranu przeglądarki (Playwright) Nic innego nie pokaże interfejsu, który istnieje tylko w Twoim buildzie

A wygenerowana ilustracja? Zachowuje jedną uczciwą rolę: scenę, której nie da się sfotografować i która nie jest danymi — abstrakcyjny mechanizm, produkt, który jeszcze nie istnieje, autorski styl ilustracji firmowych. (Pełną argumentację za prawdziwą fotografią zamiast generowania — szybkość przy dużej skali, dokładność, problem powtarzalności — przedstawiono tutaj.) Wycena idzie w tym samym kierunku: od 2 sierpnia 2026 roku Artykuł 50 unijnego AI Act wymaga od dostawców systemów generatywnych oznaczania syntetycznych wyników w formacie odczytywalnym maszynowo.2 To obowiązek dostawców i podmiotów wdrażających AI, a nie zasada dotycząca tego, co może opublikować blog — ale właśnie dlatego pochodzenie obrazu na górze artykułu coraz częściej jest czymś, co czytelnik może zweryfikować, a nie po prostu przyjąć na wiarę.

Pipeline od początku do końca

Pięć etapów. Tylko etap 3 dotyka API obrazów, a tylko etap 4 jest opcjonalny:

Kształt całości — jeden artykuł na wejściu, jeden gotowy do publikacji na wyjściu
┌─ 1. PISANIE ──────────────────────────────────────────────────┐
   temat ──▶ LLM ──▶ draft.md  (sekcje h2, front-matter)
└───────────────────────────────┬───────────────────────────────┘
┌─ 2. BRIEF ────────────────────┴───────────────────────────────┐
   draft.md ──▶ LLM ──▶ { hero: {...}, sections: [ {...} ] }
                     jeden obiekt JSON: dla każdego slotu kind +
                     brief zdjęciowy albo specyfikacja danych/diagramu
└──────────┬──────────────────────────────────┬─────────────────┘
           │ kind = "photo"                │ kind = "chart" | "diagram"
           ▼                               ▼
┌─ 3. WYSZUKIWANIE ─────────┐   ┌─ 4. RYSOWANIE (opcjonalnie) ─┐
   GET /search/photos          LLM ──▶ mermaid | kod rysujący
   ← zdjęcie z autorstwem +      ──▶ sandbox ──▶ .svg / .png
     w/h, blur_hash, alt,       (deterministyczny render, bez
     licencja, URL źródłowy      zmyślonych liczb)
└──────────┬────────────────┘   └──────────────┬───────────────┘
           └───────────────┬──────────────────┘
┌─ 5. SKŁADANIE ────────────┴───────────────────────────────────┐
   <img> z width/height + fetchpriority | loading
   alt napisany dla człowieka · widoczny credit · ImageObject JSON-LD
   photo_id zapisany, żeby żadne dwie strony nie dzieliły hero
└───────────────────────────────────────────────────────────────┘

Dwie własności mają większe znaczenie niż sam diagram. Etap 2 to router: decyduje dla każdego slotu, który producent zostanie uruchomiony, więc nigdy nie prosisz biblioteki zdjęć o wykres słupkowy. A etap 5 to miejsce, gdzie rodzi się SEO — wszystko, co Google dokumentuje na temat obrazów (opisowy alt, metadane licencyjne, bezpieczny dla LCP hero) jest tu generowane z pól, które odpowiedź wyszukiwania już zawierała.

Etap 2: zamień szkic w briefy wizualne

Jedno wywołanie modelu na gotowym szkicu — i to, co zwraca, jest planem, a nie zapytaniem. Jak napisać pojedynczy brief fotograficzny — dlaczego tytuł artykułu jest najgorszym możliwym wejściem, jak wygląda brief kamery liczący 12–25 słów i na czym zawodzi — opisano w całości w przewodniku o ilustrowaniu jednego artykułu i nie powtarzamy tego tutaj. To, co dodaje pipeline, to routing: to samo wywołanie musi zdecydować, slot po slocie, który producent zostaje uruchomiony — a wpis wykresu niesie dane tam, gdzie wpis zdjęcia niesie scenę.

Prompt routera — skopiuj go bez zmian
# prompt systemowy — uruchamiany raz na gotowy szkic
Jesteś dyrektorem artystycznym publikacji technicznej. Przeczytaj artykuł
i zwróć plan wizualny: jeden wpis dla hero, jeden na każdą sekcję H2.

Dla każdego wpisu wybierz dokładnie jeden kind:
  "photo"    prawdziwa scena: ktoś coś robi gdzieś, miejsce,
              przedmiot, gest. Domyślny wybór dla hero.
  "chart"    sekcja podaje liczby, które SĄ w artykule. Nigdy
              nie wymyślaj wartości: skopiuj je do data dosłownie.
  "diagram"  sekcja opisuje strukturę, przepływ lub sekwencję.
  "none"     sekcja jest krótka albo już zawiera blok kodu.

Zasady dla wpisów "photo" — pole to query:
   Napisz opis ujęcia: scenę, którą mogłaby uchwycić kamera, 12 do 25 słów,
   w języku angielskim, dopasowaną nastrojem do sekcji. Nazwij to, co jest
   w kadrze, nigdy temat. Bez tekstu, logo, marek ani sławnych osób; bez
   niewidzialnych metafor. (Pełne zasady, z przykładami i błędnymi przypadkami:
   pexafy.com/blog/illustrate-blog-articles-at-scale/)

NIE pisz tekstu alt dla wpisu photo: zdjęcie, które dostaniesz, jest
najbliższym dopasowaniem do briefu, a nie sceną, którą opisałeś, więc jego alt
trzeba napisać dopiero na podstawie wybranego zdjęcia. Wpisy chart i diagram
MAJĄ alt — tam kontrolujesz dokładnie to, co jest renderowane.

Zwróć wyłącznie 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": "…" }
  ]
}

Większość pracy wykonuje linia kind, a resztę instrukcja data: wpis wykresu może zawierać wyłącznie liczby, które już pojawiają się w szkicu, więc model przepisuje, a nie wymyśla. Oto router działający na trzech prawdziwych sekcjach artykułu dla programistów:

Sekcja kind Co zwrócił router
Hero — „Dlaczego nasz nocny build trwa 40 minut” photo „programista pracujący do późna przy biurku z dwoma monitorami i mechaniczną klawiaturą w ciemnym pokoju oświetlonym przez ekrany”
„Na co faktycznie idzie czas” chart data skopiowane z akapitu: instalacja 480 s, kompilacja 1080 s, testy 720 s, upload 120 s
„Jak podzieliliśmy graf” diagram flowchart LR grafu zależności zadań
„Co zmieniliśmy i co zrobilibyśmy ponownie” photo „dwoje inżynierów stojących przy tablicy pełnej diagramów, wspólnie rozwiązujących problem”

Etap 3: zdjęcia wracają z podanym autorstwem

Każdy wpis kind: "photo" to jedno zapytanie. Powyższy brief dla hero, uruchomiony na publicznym API, zwraca to w 147 ms:

GET /search/photos — brief hero · „programista pracujący do późna przy biurku z dwoma monitorami…” · 147 ms
Silnik rankinguje po znaczeniu, więc długie zdanie zawęża zbiór, zamiast go opróżniać. Uruchom dokładnie to wyszukiwanie →

Brief ostatniej sekcji, zupełnie inna scena, w 144 ms:

GET /search/photos — brief sekcji · „dwoje inżynierów stojących przy tablicy pełnej diagramów…” · 144 ms
Ten sam artykuł, ten sam przebieg, scena, której nikt nie pomyliłby z hero — bo brief został napisany na poziomie sekcji, a nie artykułu. Uruchom też i to →

To, co wraca dla każdego zdjęcia, to część, która czyni etap 5 możliwym — nie tylko plik:

Jeden wynik, przycięty do pól, które konsumuje assembler
{
  "photo_id":  "019e1c7f-0063-759e-b498-33ce1714e6c9",   // zapisz to: bez powtórek
  "urls": { "small": "…?w=400", "regular": "…?w=1080",
             "large": "…?w=1920" },
  "width": 3000, "height": 1688,          // → brak przesunięcia layoutu
  "blur_hash": "LJ8gjv9rVq-6OFxanNNFI7xco$Na",   // → prawdziwy placeholder
  "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 (…)" }
}

Etap 4: to, czego fotografia nie potrafi powiedzieć

Dwa sloty w planie nie są przeszukiwalne i to jest dokładnie miejsce, w którym drugi model udowadnia swoją wartość — nie po to, żeby narysować obrazek, ale żeby napisać kod, który go narysuje. To rozróżnienie ma znaczenie: kod da się zrecenzować, jest deterministyczny i nie potrafi zmyślić wysokości słupka.

Diagramy: tekst na wejściu, SVG na wyjściu

Mermaid renderuje diagramy z definicji w czystym tekście,3 co czyni go najbezpieczniejszym celem dla modelu: wynik jest możliwy do inspekcji, do porównania w gicie i renderuje się tak samo za każdym razem. Router już zwrócił specyfikację.

diagram.sh — model napisał specyfikację, CLI ją renderuje
# pole "spec" wpisu diagram, zapisane do build/graph.mmd
cat build/graph.mmd
flowchart LR
  install["instalacja zależności · 480s"] --> compile["kompilacja · 1080s"]
  compile --> test["pakiet testów · 720s"]
  compile --> upload["upload artefaktów · 120s"]

npx -y @mermaid-js/mermaid-cli -i build/graph.mmd -o static/img/graph.svg
# → SVG, który możesz zrecenzować w PR, a nie obrazek, któremu musisz zaufać

Wykresy: tylko liczby, które artykuł już zawiera

Ta sama zasada, jedno dodatkowe zabezpieczenie. Router skopiował wartości ze szkicu; model pisze kod rysujący; kod uruchamia się w sandboxie; assembler ponownie sprawdza wyrenderowane wartości względem liczb źródłowych, zanim wykres trafi w pobliże strony.

chart.py — narysuj przepisane dane, a potem je zweryfikuj
import re, matplotlib
matplotlib.use("Agg")                # bez wyświetlacza: brak GUI w CI
import matplotlib.pyplot as plt

def assert_in_draft(value, draft: str) -> None:
    """Narysowana liczba musi wystąpić w artykule JAKO LICZBA."""
    # Pułapka polega na dopasowywaniu podciągów: "120" jest wewnątrz "1200"
    # i wewnątrz "?w=1200" — naiwne `str(v) in draft` przejdzie na wszystkim.
    # Dopasuj po granicach wyrazów i zaakceptuj 1 234 / 1,234 / 1234.
    body = re.sub(r"(?<=\d)[  ,](?=\d{3}\b)", "", draft)   # usuń separatory
    if not re.search(rf"(?<![\d.]){re.escape(str(value))}(?![\d.])", body):
        raise ValueError(f"{value} nie jest podane w artykule — odmowa rysowania")

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)       # zmyślona wartość → brak wykresu

    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)                    # deterministyczny, weryfikowalny artefakt
    return out

To zabezpieczenie jest krótkie, ale napisz je uważnie: sprawdzenie podciągu nie działa. "120" in draft jest prawdziwe dla artykułu, który zawiera 1200 albo ?w=1200, więc naiwna wersja przepuszcza wszystko i niczego nie chroni. Zakotwicz na granicach cyfr, znormalizuj separatory tysięcy, a klasa błędu, przez który artykuł techniczny zostanie rozłożony na czynniki pierwsze w komentarzach — wykres zaprzeczający własnemu akapitowi — nie ma szans trafić na produkcję. Zrzuty ekranu podlegają tej samej zasadzie: zeskryptowany page.screenshot() na Twoim prawdziwym buildzie jest jedynym źródłem prawdy o Twoim własnym interfejsie i pozostaje prawdziwy, gdy UI się zmienia.

Etap 5: składanie to miejsce, gdzie rodzi się SEO

Wszystko dotychczas wyprodukowało pliki i pola. Ten etap zamienia je w znaczniki — i warto tu być precyzyjnym, bo w tych kilku liniach decydują się trzy udokumentowane zachowania.

Hero, wygenerowany z odpowiedzi wyszukiwania — nic nie wymyślone
<!-- Bajty pochodzą z innego źródła: zapłać za handshake wcześnie -->
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>

<!-- Element LCP: nigdy lazy, zawsze wysoki priorytet -->
<figure>
  <img src="{urls.regular}"
       width="{width}" height="{height}"        <!-- eliminuje przesunięcie layoutu -->
       alt="{alt}"                                <!-- napisany PO wyborze -->
       fetchpriority="high" decoding="async"
       style="background:{color_hex}">   <!-- kolor dominujący, 1 pole -->
  <figcaption>{attribution.html}</figcaption>
</figure>

<!-- Obrazy w sekcjach, poniżej fold: odwrotne ustawienia -->
<img src="{urls.regular}" width="{width}" height="{height}"
     alt="{alt}" loading="lazy" decoding="async">

Hotlink czy re-hosting? Powyższy fragment robi hotlinking, co jest najszybszym rozwiązaniem do wdrożenia i powodem, dla którego jest tam preconnect: zdalne hero kosztuje lookup DNS i handshake TLS na ścieżce krytycznej, co może zjeść zysk, który właśnie kupiłeś dzięki fetchpriority. Re-hosting całkowicie usuwa zewnętrzne źródło, pozwala serwować AVIF/WebP przy własnych breakpointach i przetrwa zmianę adresu URL po stronie dostawcy — kosztem miejsca na dysku, dodatkowego kroku pobierania w pipeline i własnego rachunku za CDN. Cokolwiek wybierzesz, color_hex daje placeholder za jedno pole (blur hash wygląda ładniej, ale najpierw trzeba go zdekodować do data URI — to nie jest kolor CSS). Wklejenie przybliżonego koloru hero bezpośrednio do background: to miejsce, w którym większość pipeline'ów po cichu wysyła pustą szarą skrzynkę.

  1. Nigdy nie ładuj hero leniwie. web.dev jest jednoznaczne: „Nigdy nie ładuj leniwie swojego obrazu LCP, ponieważ zawsze prowadzi to do niepotrzebnego opóźnienia ładowania zasobu i będzie miało negatywny wpływ na LCP”, i zaleca fetchpriority="high" na elemencie, który prawdopodobnie będzie LCP — stosowane oszczędnie, na jednym obrazku.4 Pipeline, który wstawia loading="lazy" do każdego obrazka, hero włącznie, to najczęstsza samodzielnie zadana rana Core Web Vitals w automatycznej publikacji.
  2. Zawsze generuj width i height — wracają one w odpowiedzi, więc nie ma wymówki; ta jedna para atrybutów pozwala przeglądarce zarezerwować miejsce i zatrzymuje skakanie layoutu. Użyj blur_hash jako placeholdera podczas ładowania pliku.
  3. Napisz alt po wyborze zdjęcia, nigdy wcześniej. To subtelny szczegół. Pole alt w planie opisuje scenę, o którą poprosiłeś; zdjęcie, które dostałeś, jest najbliższym dopasowaniem, nie tą sceną. Wysłanie tekstu z briefu jako alt to dokładnie ta awaria dostępności, której ten pipeline ma unikać — opis obrazka, którego nie ma na stronie. Zbuduj alt na podstawie alt_description wybranego zdjęcia, dopracowanego względem akapitu, w którym się znajduje. Wytyczne Google mówią, by „skupić się na tworzeniu użytecznych, bogatych w informacje treści, które używają słów kluczowych odpowiednio i w kontekście treści strony”, i ostrzegają, że upychanie atrybutów alt słowami kluczowymi „skutkuje negatywnym doświadczeniem użytkownika i może sprawić, że Twoja strona zostanie uznana za spam”.1

Część, której prawie nikt nie automatyzuje: metadane licencyjne

Google obsługuje dane strukturalne ImageObject dla licencjonowania obrazów. Wymaga contentUrl plus co najmniej jednego z: creator, creditText, copyrightNotice lub license, zaleca acquireLicensePage, a obrazy z informacją o licencji stają się uprawnione do odznaki Licensable w Grafice Google.5 Każde z tych pól jest już w odpowiedzi wyszukiwania — więc wygenerowanie ich to szablon, nie projekt:

ImageObject JSON-LD, wypełniony z odpowiedzi API
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "ImageObject",
  "contentUrl": "{urls.large}",                 // wymagane
  "creator": { "@type": "Person",
                "name": "{photographer_full_name}" },
  "creditText": "{photographer_full_name} on {source}",
  "license": "{LICENSE_URL[source]}",           // własna strona biblioteki
  "acquireLicensePage": "{source_image_url}"     // strona zdjęcia
}
</script>

# LICENSE_URL mapuje pole `source` na licencję, która faktycznie rządzi
# zdjęciem — unsplash.com/license, pexels.com/license, pixabay.com/…

Jeden szczegół, który warto zrobić poprawnie: license powinno wskazywać na licencję, która rządzi tym konkretnym zdjęciem — własną stronę licencyjną biblioteki źródłowej — a nie na stronę podsumowującą w Twojej domenie. Google odczytuje to pole, by zdecydować o uprawnieniu do odznaki, a adres wskazujący sam na siebie jest zarówno słabszym sygnałem, jak i trudnym do obrony jako coś innego niż link zwrotny do samego siebie. Zachowaj własne podsumowanie licencji jako stronę wewnętrzną dla czytelników; kanoniczną wersję umieść w znacznikach.

I skoro już jesteś w assemblerze: nadaj plikowi krótką, opisową nazwę zamiast IMG_0042.jpg, i dodaj obrazek do sitemapy — format sitemapy obrazów Google przyjmuje do 1000 obrazów na jeden adres URL strony.6 Obie rzeczy to po jednej linijce w pipeline, a żadna z nich nigdy nie jest robiona ręcznie.

Dwa pipeline'y do skopiowania

Te same pięć etapów, dwa bardzo różne kształty — jeden dla artykułów, które piszesz, drugi dla dokumentacji, która musi odpowiadać działającemu produktowi. Wybierz ten, którego tryb awarii rozpoznajesz.

1 · Blog deweloperski w CI — Markdown w repozytorium

Artykuły żyją jako Markdown, obrazy są commitowane obok nich, a całość działa przy każdym push. Deterministyczne, weryfikowalne w PR, bez zależności runtime od jakiegokolwiek 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   # obrazy trafiają do PR
        with: { commit-message: "chore(content): illustrate" }

Człowiek nadal zatwierdza PR, i to jest cały sens: pipeline proponuje, recenzent decyduje, a front-matter, który napisał, jest możliwy do porównania (diff).

Połowa dotycząca wyszukiwania to jedno GET — żądanie, jego score_threshold i zwracane pola opisano linia po linii w przewodniku dla pojedynczego artykułu, więc tutaj jest tylko importowane, a nie przedrukowywane. To, co dodaje ten plik, to wszystko, czego potrzebuje plan, a czego nie potrzebuje jedno zdjęcie: ponowna próba przy briefie, który nic nie zwrócił, rezerwacja photo_id, dzięki której żadne dwie strony nie dzielą tego samego obrazu, oraz tekst alternatywny napisany na podstawie zwróconego zdjęcia.

plan_to_pr.py — spoiwo: plan wizualny na wejściu, front matter na wyjściu
import frontmatter
from photo_search import search   # jedno GET /search/photos; pomija id z `used`

def find_photo(entry: dict, used: set) -> dict | None:
    """Szukaj; jeśli brief był zbyt szczegółowy, poszerz go raz, a potem zrezygnuj."""
    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"])   # zastrzeż je: bez powtórek w serwisie
            return photo
    return None                        # wywołujący decyduje: pominąć slot albo zawieść

def widen(query: str) -> str:
    """Usuń ostatnią klauzulę — zwykle tę zbyt szczegółową."""
    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:                    # brak hero jest lepszy niż złe hero
        raise SystemExit(f"{path}: brak zdjęcia powyżej progu — przepisz brief")

    post["hero"] = {                    # wszystko, czego potrzebuje szablon
        "src": hero["urls"]["regular"], "w": hero["width"], "h": hero["height"],
        # Alt opisuje zdjęcie, które DOSTALIŚMY, nigdy scenę, o którą prosiliśmy.
        "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 jako baza, przycięte do ~125 znaków dla czytników ekranu.
    Wyślij to ponownie przez model razem z akapitem, jeśli chcesz lepszy wynik."""
    base = photo.get("alt_description") or photo.get("description", "")
    return base[:125].rstrip(" ,;")

2 · Dokumentacja i changelog — najpierw zrzuty ekranu, na końcu zdjęcia

Odwróć domyślne ustawienia routera. W dokumentacji produktu uczciwą wizualizacją jest niemal zawsze Twój własny interfejs: skrypt Playwright, który otwiera prawdziwy build, ustawia stały viewport i przechwytuje dokładnie ten stan, który opisuje akapit. Diagramy obsługują strony architektury, a fotografie pojawiają się wyłącznie na stronach koncepcyjnych i lądowania — gdzie zrzut ekranu nic by nie powiedział.

shots.py — zrzut ekranu jest generowany, nigdy opisywany
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)   # ostre jak 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()
# Działa w tym samym zadaniu CI co build dokumentacji → zrzut ekranu nigdy
# nie może opisywać wersji UI, która już nie istnieje.

Dwa warianty warte wspomnienia, choć niewarte osobnego przepisu. SEO programistyczne odwraca pętlę: przy setkach stron generowanych z bazy danych nie wyszukujesz na poziomie strony — jedno zapytanie zwraca do 100 zdjęć, więc wyszukujesz na poziomie klastra tematycznego i przydzielasz z puli, a deduplikację zapewnia ograniczenie unikalności (ta architektura w pełni). A newslettery i karty społecznościowe potrzebują tego samego zdjęcia w czterech kadrach: zachowaj photo_id, poproś o rozmiar, którego potrzebujesz, z urls, i wyszukuj ponownie tylko wtedy, gdy kadr faktycznie zawiedzie.

Ten sam pipeline jako jeden agent

Jeśli model już pisze szkic, najkrótsza droga to dać mu narzędzie wyszukiwania bezpośrednio, zamiast przesyłać JSON między procesami. Pexafy udostępnia hostowany serwer Model Context Protocol pod adresem mcp.pexafy.com/mcp. Konfiguracja konektora i narzędzia, które udostępnia, są opisane w innych miejscach — konfiguracja desktopowa i edytorska w przewodniku po pojedynczym artykule, wariant bezinterfejsowy dla uruchomienia CI w artykule o treści na dużą skalę, a to, jak wygląda szerszy rynek konektorów — kto udostępnia serwer obrazów MCP i na jakich warunkach — w analizie infrastruktury wyszukiwania obrazów dla agentów AI. Warto pokazać tutaj, co dzieje się z tym pipeline'em, gdy agent dysponuje już narzędziami: przestaje to być pięć etapów, a staje się jedną instrukcją.

Jedna instrukcja, wykonany cały plan
Ty  Oto szkic. Zbuduj plan wizualny, zilustruj go i otwórz PR.
     Wykresy tylko z liczb już zawartych w tekście.

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 zdjęć · 147 ms · wybrano #1, 3000×1688, z podanym autorstwem
      → mermaid-cli build/graph.mmd → static/img/graph.svg
      → chart.py §2 → 4 wartości zweryfikowane względem szkicu ✓
      → search_photos(q="two engineers standing at a whiteboard…")
      ← 16 zdjęć · 144 ms · wybrano #1

      ✓ 4 sloty wypełnione · 2 wywołania API · alt + credit + ImageObject zapisane
      ⚠ §5 nie zwróciło niczego powyżej 0,5 — brief zbyt abstrakcyjny, przepisany jako
        "osoba przy kuchennym stole sprawdzająca liczby na laptopie"

Ta ostatnia linijka pokazuje, dlaczego warto mieć agenta w tej pętli, a nie czysty skrypt: trybem awarii automatycznej ilustracji jest zły brief, a przepisanie złego briefu jest dokładnie tym, do czego służy model językowy. Deterministyczne części — przydział, deduplikację, zabezpieczenie liczbowe — trzymaj w kodzie.

Ile kosztuje jeden zilustrowany artykuł

Trzy sloty zdjęciowe na artykuł oznaczają trzy żądania wyszukiwania, więc plan bezpłatny (5,000 żądań/miesiąc) pokrywa 1,666 artykułów miesięcznie, zanim w ogóle pojawi się kwestia płatności — a jeśli grupujesz wyszukiwania na poziomie klastra tematycznego zamiast artykułu, ten pułap przesuwa się o kolejny rząd wielkości. Szczegółowe zestawienie plan po planie, oraz architektura grupowania, która czyni tę kwestię nieistotną, znajdują się w artykule towarzyszącym o ilustrowaniu treści na dużą skalę.

Dwa wywołania modelu na artykuł — jedno na szkic, jedno na plan wizualny — to kilka tysięcy tokenów i będą najtańszą pozycją w pipeline; rendery Mermaid i matplotlib nie kosztują nic poza sekundami CI. Liczba, która zaskakuje ludzi, to ta, która nie jest tania: generowanie czterech obrazów na artykuł, czterystu obrazów miesięcznie, plus próby, które się nie udały — a wynik i tak nie niesie żadnego fotografa, żadnej daty i żadnego adresu źródłowego.

Czego ten pipeline nie naprawia

Uczciwa sekcja, bo awaria, której zapobiega, jest kosztowna. Dobrze zilustrowany artykuł to nadal artykuł: prawdziwa fotografia, dokładne wykresy i poprawne znaczniki poprawiają stronę, która zasługuje na istnienie. Nie sprawiają, że słaba, masowo produkowana treść zaczyna się rankingować. Zasady dotyczące spamu Google nazywają wprost nadużycie treści na skalę — generowanie wielu stron przede wszystkim w celu manipulowania rankingami i oferowanie niewielkiej wartości użytkownikom, niezależnie od tego, czy zaangażowana jest automatyzacja — a jakość ilustracji nie jest czynnikiem w tej ocenie.7

Więc podejście, które się broni: ten pipeline to kontrolowany przez Ciebie próg jakości, stosowany na stronach, które już mają powód, by zostać opublikowane. Gdzie wyraźnie się opłaca:

  • Pochodzenie, które czytelnik może zweryfikować. Linia z podanym autorstwem, z prawdziwym fotografem i adresem źródłowym, to twierdzenie, które można sprawdzić — a te same pola zasilają znaczniki ImageObject, które odczytuje Google.
  • Dostępność i Core Web Vitals. Prawdziwy tekst alt, wymiary na każdym obrazku, hero, które nigdy nie jest ładowane leniwie. Pomnóż przez każdy publikowany artykuł, a to jest cała historia jakości obrazów w serwisie.
  • Dokładność tam, gdzie da się ją sprawdzić. Wykres, którego liczby są weryfikowane względem artykułu, zrzut ekranu wygenerowany z działającego buildu, diagram możliwy do zrecenzowania jako tekst w PR — trzy wizualizacje, które nie mogą oddalić się od prawdy bez niepowodzenia testu.

Jeśli chodzi o atrybucję, kwestia specyficzna dla pipeline'u jest wąska: argumentację za tym, by zawsze wyświetlać podpis autorski przedstawiono gdzie indziej, a to, co dodaje zautomatyzowany asembler, to fakt, że ten sam ciąg attribution, który drukuje, jest jednocześnie creditText potrzebnym danym strukturalnym. Jedno pole, dwa miejsca, emitowane w tym samym przebiegu szablonu — dlatego pipeline ma jeszcze mniej usprawiedliwienia niż człowiek, by je pominąć.

Od czego zacząć

  1. Dodaj prompt routera do czegokolwiek, co już pisze Twoje szkice, i wypisz JSON bez działania na nim. Przeczytaj dziesięć planów. Jeśli briefy nazywają tematy zamiast scen, popraw prompt, zanim napiszesz jakąkolwiek integrację.
  2. Podłącz tylko sloty zdjęciowe. Jedno GET /search/photos na brief, i zapisuj photo_id od pierwszego dnia — deduplikacja wprowadzana wstecznie na 3000 działających stron to migracja, nie kolumna.
  3. Generuj width, height i alt w tym samym commicie. To najtańsza praca nad Core Web Vitals, jaką kiedykolwiek wykonasz.
  4. Potem dodaj etap 4, diagramy przed wykresami — Mermaid to tekst, więc jest jedynym elementem bez trybu awarii poza błędem składni.
  5. Dodaj zabezpieczenie liczbowe zanim pierwszy wykres trafi do czytelnika, nie potem.

Źródła i przypisy

1 Google Search Central, Najlepsze praktyki SEO dla obrazów: tekst alt jest „najważniejszym atrybutem, jeśli chodzi o dostarczanie metadanych obrazu”; wytyczne mówią o tworzeniu „użytecznych, bogatych w informacje treści, które używają słów kluczowych odpowiednio i w kontekście treści strony”, o unikaniu upychania atrybutów alt słowami kluczowymi, o używaniu elementów HTML <img> zamiast obrazów CSS oraz o nadawaniu plikom krótkich, ale opisowych nazw.

2 Unijny AI Act, artykuł 50 — obowiązki przejrzystości obowiązujące od 2 sierpnia 2026: dostawcy systemów generujących syntetyczny obraz, dźwięk, wideo lub tekst muszą oznaczać wyniki w formacie odczytywalnym maszynowo i uczynić je wykrywalnymi jako wygenerowane sztucznie. Wiąże dostawców i podmioty wdrażające AI; nie jest zasadą dotyczącą tego, jakie obrazy może opublikować strona internetowa.

3 Mermaid renderuje diagramy i wykresy z definicji tekstowych inspirowanych Markdownem, co czyni wynik możliwym do zrecenzowania i deterministycznym.

4 web.dev, Optymalizacja Largest Contentful Paint: „Dobrym pomysłem jest ustawienie fetchpriority="high" na elemencie <img>, jeśli sądzisz, że prawdopodobnie będzie to element LCP Twojej strony”, stosowane oszczędnie; oraz „Nigdy nie ładuj leniwie swojego obrazu LCP, ponieważ zawsze prowadzi to do niepotrzebnego opóźnienia ładowania zasobu i będzie miało negatywny wpływ na LCP”.

5 Google Search Central, Metadane obrazu (dane strukturalne): ImageObject wymaga contentUrl plus co najmniej jednego z: creator, creditText, copyrightNotice lub license; acquireLicensePage jest zalecane, a obrazy niosące informację o licencji mogą stać się uprawnione do odznaki Licensable w Grafice Google.

6 Google Search Central, Sitemapy obrazów: sitemapy obrazów informują Google o obrazach na stronie, w tym tych znalezionych przez JavaScript, i przyjmują do 1000 obrazów na jeden adres URL strony.

7 Zasady dotyczące spamu w Google Search — nadużycie treści na skalę: generowanie wielu stron przede wszystkim w celu manipulowania rankingami i oferowanie niewielkiej wartości użytkownikom, niezależnie od tego, czy zostały utworzone dzięki automatyzacji, pracy człowieka czy ich połączeniu.

Najczęściej zadawane pytania

Jak automatycznie ilustrować artykuły napisane przez LLM?
Dodaj jedno wywołanie modelu pomiędzy pisaniem a publikacją: poproś go o zwrócenie planu wizualnego — jeden wpis na każde miejsce na obraz, oznaczony jako zdjęcie, wykres, diagram albo brak grafiki. Wpisy typu zdjęcie zawierają opis sceny liczący od 12 do 25 słów, którą mogłaby uchwycić kamera; ten opis wysyłasz do API semantycznego wyszukiwania obrazów (GET /api/v1/search/photos, około 150 ms). Wpisy typu wykres i diagram trafiają do modelu, który pisze kod rysujący wykres lub kod Mermaid, renderowany deterministycznie. Nigdy nie podawaj tytułu artykułu do wyszukiwarki obrazów: tytuły są abstrakcyjne i żadne zdjęcie ich nie przedstawia.
Czy witryna z dokumentacją powinna używać zrzutów ekranu czy zdjęć stockowych?
Odwróć domyślne ustawienia routera: w dokumentacji produktu uczciwym obrazem jest niemal zawsze twój własny interfejs. Skrypt Playwright, który otwiera rzeczywistą wersję buildu, ustala viewport i przechwytuje dokładnie ten stan, który opisuje akapit, działa w tym samym zadaniu CI, co budowanie dokumentacji, dzięki czemu zrzut ekranu nigdy nie może pokazywać wersji interfejsu, która już nie istnieje. Diagramy dominują na stronach architektury, a fotografie pojawiają się tylko na stronach koncepcyjnych i landing page'ach, gdzie zrzut ekranu niczego by nie powiedział.
Jak sprawić, by pipeline AI nie umieszczał błędnych liczb na wykresie?
Spraw, by plan przepisywał, a nie wymyślał: router może kopiować do pola data wpisu z wykresem wyłącznie wartości, które już pojawiają się w szkicu. Następnie sprawdź to w kodzie przed renderowaniem — dla każdej wartości zweryfikuj, czy jej zapis w postaci tekstu występuje w artykule, a jeśli nie, zgłoś błąd. Dziewięć linijek kodu i wykres sprzeczny z własnym akapitem nigdy nie dotrze do czytelnika.
Jakie znaczniki obrazów powinien generować zautomatyzowany pipeline pod kątem SEO?
Trzy rzeczy, wszystkie z pól, które odpowiedź wyszukiwarki już zawiera. Opisowy alt napisany w kontekście akapitu — Google nazywa tekst alternatywny najważniejszym metadanymi obrazu i przestrzega przed nadmiernym upychaniem słów kluczowych. width i height na każdym obrazie, z fetchpriority="high" na obrazie hero i nigdy z loading="lazy" na nim, ponieważ obraz LCP nie może być ładowany leniwie. Oraz dane strukturalne ImageObject z polami contentUrl, creator, creditText i license, które kwalifikują obraz do odznaki Licensable w Google Images.
Czy ilustrowanie artykułów pisanych przez AI pomaga im się pozycjonować?
Nie samo w sobie, i warto to precyzyjnie ująć. Zasady Google dotyczące spamu definiują scaled content abuse jako generowanie wielu stron przede wszystkim po to, by manipulować rankingami, przy niewielkiej wartości dla użytkowników, niezależnie od tego, czy w grę wchodzi automatyzacja; ilustracje nie zmieniają tej oceny. To, co dobry pipeline daje w zamian, to minimalny standard jakości dla stron, które i tak zasługują na istnienie: weryfikowalne pochodzenie treści, dostępny tekst alternatywny, Core Web Vitals odporne na automatyzację oraz wykresy i zrzuty ekranu, które nie mogą odbiegać od prawdy.
Jak uruchomić etap ilustrowania w CI, nie tracąc kontroli redakcyjnej?
Uruchom zadanie przy pull requeście dotykającym plików treści, pozwól mu wygenerować obrazy oraz front-matter, i niech otworzy pull request zamiast commitować bezpośrednio do gałęzi — pipeline proponuje, człowiek zatwierdza, a każde wpisane przez niego pole można porównać w diffie. Trzy rzeczy zachowaj jako deterministyczne w kodzie, a nie w modelu: deduplikację po photo_id, asercję, że każda wykreślona liczba pojawia się w artykule, oraz twarde niepowodzenie, gdy żadne zdjęcie nie przekroczy progu punktowego. Lepszy brak obrazu głównego niż zły.

Przestań szukać słów kluczowych. Opisz, o co Ci chodzi.

Przeszukuj 9M+ darmowych obrazów według znaczenia — w dowolnym języku, w mniej niż 100 ms.