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.
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:
┌─ 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 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:
Brief ostatniej sekcji, zupełnie inna scena, w 144 ms:
To, co wraca dla każdego zdjęcia, to część, która czyni etap 5 możliwym — nie tylko plik:
{
"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ę.
# 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.
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.
<!-- 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ę.
-
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 wstawialoading="lazy"do każdego obrazka, hero włącznie, to najczęstsza samodzielnie zadana rana Core Web Vitals w automatycznej publikacji. -
Zawsze generuj
widthiheight— 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żyjblur_hashjako placeholdera podczas ładowania pliku. -
Napisz alt po wyborze zdjęcia, nigdy wcześniej. To subtelny szczegół.
Pole
altw 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 podstawiealt_descriptionwybranego 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:
<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:
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.
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ł.
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ą.
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ąć
- 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ę.
- Podłącz tylko sloty zdjęciowe. Jedno
GET /search/photosna brief, i zapisujphoto_idod pierwszego dnia — deduplikacja wprowadzana wstecznie na 3000 działających stron to migracja, nie kolumna. - Generuj width, height i alt w tym samym commicie. To najtańsza praca nad Core Web Vitals, jaką kiedykolwiek wykonasz.
- Potem dodaj etap 4, diagramy przed wykresami — Mermaid to tekst, więc jest jedynym elementem bez trybu awarii poza błędem składni.
- 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.
Źródła sprawdzone 17 sierpnia 2026: Najlepsze praktyki SEO dla obrazów · Metadane obrazu · Sitemapy obrazów · Optymalizacja LCP · Zasady dotyczące spamu w wyszukiwarce · AI Act, artykuł 50 · Mermaid · Dokumentacja API i MCP Pexafy. Czasy wyszukiwania (147 ms, 144 ms) i każde pokazane zdjęcie to prawdziwe odpowiedzi API zarejestrowane tego samego dnia.
Najczęściej zadawane pytania
Jak automatycznie ilustrować artykuły napisane przez LLM?
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?
Jak sprawić, by pipeline AI nie umieszczał błędnych liczb na wykresie?
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?
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ć?
Jak uruchomić etap ilustrowania w CI, nie tracąc kontroli redakcyjnej?
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.