Ang Teksto ang Madaling Bahagi: ang Pipeline na Naglalagay ng Larawan sa AI-Written Articles

Solusyonado na ang paggawa ng teksto. Hindi pa solusyonado ang paglalagay ng larawan dito. Ang end-to-end pipeline — router prompt, paghahanap ng litrato, Mermaid diagrams, verified charts, at ang alt text, credit, at ImageObject markup na ginagawang SEO ito.

Isang programmer na nagta-type sa isang desk na may dalawang monitor na puno ng code, na iniilawan ng makukulay na lampara.
Larawan sa pamamagitan ng Unsplash

Isa kang developer na may target sa content: dose-dosenang artikulo bawat buwan, marahil daan-daan. Kinakaya ng writing model ang draft, ang outline, ang meta description, ang internal links. Pagkatapos ay tinatamaan ng pipeline ang isang hakbang na walang sariling modelo — ang mga larawan — at humihinto ito. Hindi dahil mahirap kumuha ng mga larawan, kundi dahil walang bahagi ng stack na alam alin na larawan, mula sa saan, may anong lisensya, na inilalarawan paano.

Ang artikulong ito ang nawawalang hakbang na iyon, nakakonekta mula simula hanggang katapusan: anong uri ng visual ang dapat gawin para sa anong uri ng seksyon, ang prompt na nagpapapalit ng natapos na draft tungo sa mga search brief, ang API call na nagbabalik ng mga larawan, ang ikalawang modelo na gumuhit ng hindi kayang ipakita ng litrato, at ang assembler na naglalabas ng markup na talagang mababasa ng Google. Dalawang kumpletong workflow sa bandang huli, handang i-adopt.

Solusyonado na ang teksto. Dito na-stall ang ilustrasyon.

Isagawa ang tapat na audit ng isang awtomatikong article pipeline. Outline: solusyonado. Draft: solusyonado. Titulo, meta description, schema, internal links, salin: solusyonado, lahat ng iisang modelo, lahat sa teksto. Pagkatapos:

Hakbang sa pipeline Status Ano talaga ang humaharang
Outline at draft Solusyonado Isang tawag sa modelo, isang prompt
Titulo, meta, schema, links Solusyonado Teksto papasok, teksto palabas
Hero image Naharang Kailangan ng tunay na file, isang lisensya, mga dimensyon, at alt text — wala sa mga ito ang kayang gawin ng text model
Mga larawan sa seksyon Naharang Tatlo hanggang lima bawat artikulo, magkaiba ang bawat isa, walang nauulit sa buong site
Mga chart at diagram Naharang Dapat tumpak — ang isang visual na hindi kayang ibalik ng search at hindi dapat imbentuhin ng isang generator

Ang pagkabigo ay hindi estetiko, ito ay istruktural: ang artikulo ay inilalabas na may stock placeholder, o may parehong larawan sa nakaraang labindalawang artikulo, o may generated image na ang kamay na may anim na daliri ang unang makikita ng mambabasa. At ang larawan ay hindi palamuti — malinaw na sinasabi ng dokumentasyon ng Google tungkol sa larawan: ang alt text ay "ang pinakamahalagang attribute pagdating sa pagbibigay ng metadata para sa isang larawan", at ang gabay ay gumamit ng tunay na <img> elements na may deskriptibong alt sa halip na CSS backgrounds, upang mahanap at maintindihan ang larawan nang buo.1

Apat na uri ng visual, apat na uri ng modelo

Ang pinakamalaking pagkakamali sa disenyo ay ituring ang "ang larawan" bilang iisang problema na may iisang provider. Ito ay apat, at ang router sa pagitan nila ay isang linya ng prompt, hindi isang serbisyo:

Kailangan ng seksyon… Gawin ito gamit ang Bakit hindi ang iba
Isang eksena mula sa totoong mundohero, sitwasyon ng tao, lugar, bagay, kilos Semantic photo search (Pexafy) Nag-iimbento ng detalye ang generator; walang maigugraph ang isang chart
Mga numerong totoo nga sa iyobenchmarks, pricing, resulta ng survey, latency Modelong sumusulat ng plotting code, pinapatakbo sa isang sandbox Hindi mapagkakatiwalaan ang image model sa isang value; hindi kayang magdala ng data ang isang litrato
Isang istruktura o daloyarchitecture, sequence, state machine Modelong sumusulat ng Mermaid / Graphviz, na-render nang deterministiko Walang diagram ang mga libreng photo library ng iyong sistema
Ang produkto mo sa screendocs, changelog, tutorials Scripted browser screenshot (Playwright) Walang ibang paraan para ipakita ang UI na umiiral lang sa iyong build

At paano naman ang mga ginawang ilustrasyon? May isa itong tapat na papel: ang eksenang hindi maaaring kunan ng litrato at hindi naman data — isang abstract na mekanismo, isang produktong wala pa, isang house illustration style na pagmamay-ari mo. (Ang buong argumento kung bakit mas mainam ang tunay na larawan kaysa sa generation — bilis sa malaking dami, katumpakan, ang problema sa pagkakapareho — ay ginawa dito.) Isang tanda ng direksyon: mula 2 Agosto 2026, hinihiling ng Article 50 ng EU AI Act na markahan ng mga provider ng generative systems ang mga synthetic na output sa isang machine-readable na format.2 Isa itong obligasyon sa mga AI provider at deployer, hindi isang panuntunan tungkol sa maaaring i-publish ng isang blog — pero ito ang dahilan kung bakit ang pinagmulan ng larawan sa itaas ng iyong artikulo ay unti-unting nagiging bagay na maaaring tsekin ng mambabasa sa halip na paniwalaan lang.

Ang pipeline, mula simula hanggang katapusan

Limang yugto. Yugto 3 lang ang humihipo sa isang image API, at yugto 4 lang ang opsyonal:

Ang hugis nito — isang artikulo papasok, isang artikulong maaaring ilathala palabas
┌─ 1. WRITE ────────────────────────────────────────────────────┐
   topic ──▶ LLM ──▶ draft.md  (h2 sections, front-matter)
└───────────────────────────────┬───────────────────────────────┘
┌─ 2. BRIEF ────────────────────┴───────────────────────────────┐
   draft.md ──▶ LLM ──▶ { hero: {...}, sections: [ {...} ] }
                     isang JSON object: bawat slot, isang kind +
                     alinman sa isang camera brief o isang data/diagram spec
└──────────┬──────────────────────────────────┬─────────────────┘
           │ kind = "photo"                │ kind = "chart" | "diagram"
           ▼                               ▼
┌─ 3. SEARCH ───────────────┐   ┌─ 4. DRAW (optional) ─────────┐
   GET /search/photos          LLM ──▶ mermaid | plotting code
   ← larawang may kredito +      ──▶ sandbox ──▶ .svg / .png
     w/h, blur_hash, alt,       (deterministic render, walang
     lisensya, source URL         imbentadong numero)
└──────────┬────────────────┘   └──────────────┬───────────────┘
           └───────────────┬──────────────────┘
┌─ 5. ASSEMBLE ─────────────┴───────────────────────────────────┐
   <img> na may width/height + fetchpriority | loading
   alt na isinulat para sa tao · nakikitang kredito · ImageObject JSON-LD
   photo_id na naka-imbak upang walang dalawang pahina ang magbahagi ng hero
└───────────────────────────────────────────────────────────────┘

Dalawang katangian ang mas mahalaga kaysa sa diagram. Ang yugto 2 ay isang router: pinagdedesisyunan nito bawat slot kung anong producer ang tatakbo, kaya hinding-hindi mo hihilingin sa isang photo library ang isang bar chart. At nasa yugto 5 kung saan nakasalalay ang SEO — lahat ng dinodokumento ng Google tungkol sa mga larawan (deskriptibong alt, licence metadata, isang LCP-safe hero) ay inilalabas dito, mula sa mga field na dala na ng search response.

Yugto 2: gawing visual briefs ang draft

Isang tawag sa modelo sa tapos nang draft, at ang ibinabalik nito ay isang plano sa halip na isang query. Kung paano magsulat ng iisang photo brief — kung bakit ang pamagat ng artikulo ang pinakamasamang input, kung ano ang hitsura ng isang 12-hanggang-25-salitang camera brief, at ang mga paraan kung paano ito nabibigo — ay inilahad nang buo sa gabay sa pag-iilustrasyon ng isang artikulo, at hindi na ito inuulit dito. Ang idinaragdag ng isang pipeline ay routing: ang parehong tawag ang dapat magpasya, slot by slot, kung aling producer ang tatakbo — at ang isang entry ng chart ay nagdadala ng data samantalang ang entry ng photo ay nagdadala ng eksena.

Ang router prompt — kopyahin nang buo
# system prompt — patakbuhin isang beses bawat natapos na draft
Ikaw ang art director ng isang teknikal na publikasyon. Basahin ang artikulo at
ibalik ang visual plan: isang entry para sa hero, isa bawat H2 section.

Para sa bawat entry pumili ng eksaktong isang kind:
  "photo"    isang tunay na eksena: may gumagawa ng isang bagay kung saan man,
              isang lugar, isang bagay, isang kilos. Ang default para sa heroes.
  "chart"    binabanggit ng seksyon ang mga numerong NASA artikulo na. Huwag
              kailanman mag-imbento ng value: kopyahin ang mga ito sa data, salita-sa-salita.
  "diagram"  inilalarawan ng seksyon ang isang istruktura, isang daloy, o isang sequence.
  "none"     maikli ang seksyon, o may dala nang code block.

Mga panuntunan para sa "photo" entries — ang field ay query:
Sumulat ng camera brief: isang eksenang maaaring kuhanan ng camera, 12 hanggang 25 salita,
   sa Ingles, na tugma sa mood ng seksyon. Ipangalan ang nasa loob ng frame,
   hindi ang paksa. Walang teksto, logo, brand o sikat na tao; walang
   di-nakikitang metapora. (Kumpletong mga tuntunin, may mga halimbawa at failure cases:
   pexafy.com/blog/illustrate-blog-articles-at-scale/)

HUWAG isulat ang alt text ng isang photo entry: ang larawang ibabalik ay ang
pinakamalapit na match sa brief, hindi ang eksenang inilarawan mo, kaya ang alt nito ay dapat
isulat batay sa napiling larawan. Ang chart at diagram entries AY may dalang alt —
doon ay ganap mong kontrolado kung ano ang ire-render.

Ibalik ang JSON lamang:
{
  "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": "…" }
  ]
}

Ang linyang kind ang gumagawa ng karamihan sa trabaho, at ang instruksyong data ang gumagawa ng natitira: maaari lamang magdala ang isang chart entry ng mga numerong nasa draft na, kaya ang modelo ay nagsasalin sa halip na mag-imbento. Narito ang router sa tatlong tunay na seksyon ng isang artikulo para sa developer:

Seksyon kind Ano ang ibinalik ng router
Hero — "Bakit 40 minuto ang nightly build namin" photo "isang developer na nagtatrabaho pagkagabi sa isang desk na may dalawang monitor at isang mechanical keyboard sa isang madilim na silid na naiilawan ng mga screen"
"Saan talaga napupunta ang oras" chart data na kinopya mula sa talata: install 480 s, compile 1080 s, test 720 s, upload 120 s
"Paano namin hinati ang graph" diagram flowchart LR ng job dependency graph
"Ano ang binago namin, at ano ang uulitin namin" photo "dalawang engineer na nakatayo sa harap ng isang whiteboard na puno ng diagram, magkasamang lumulutas ng isang problema"

Yugto 3: nagbabalik ang mga larawan na may kredito

Bawat kind: "photo" entry ay isang request. Ang hero brief sa itaas, na pinatakbo laban sa pampublikong API, ay ibinabalik ito sa 147 ms:

GET /search/photos — hero brief · "isang developer na nagtatrabaho pagkagabi sa isang desk na may dalawang monitor…" · 147 ms
Nagra-rank ang engine batay sa kahulugan, kaya ang mahabang pangungusap ay nagpapaliit sa set imbes na ubusin ito. Patakbuhin ang eksaktong search na ito →

Ang huling section brief, isang ganap na magkaibang eksena, sa loob ng 144 ms:

GET /search/photos — section brief · "dalawang engineer na nakatayo sa harap ng isang whiteboard na puno ng diagram…" · 144 ms
Parehong artikulo, parehong run, isang eksenang walang magkakamali sa hero — dahil isinulat ang brief bawat seksyon, hindi bawat artikulo. Patakbuhin din ito →

Ang ibinabalik bawat larawan ang siyang bahaging nagpapagana sa yugto 5 — hindi lang isang file:

Isang resulta, pinaikli sa mga field na ginagamit ng assembler
{
  "photo_id":  "019e1c7f-0063-759e-b498-33ce1714e6c9",   // i-imbak ito: walang paulit-ulit
  "urls": { "small": "…?w=400", "regular": "…?w=1080",
             "large": "…?w=1920" },
  "width": 3000, "height": 1688,          // → walang layout shift
  "blur_hash": "LJ8gjv9rVq-6OFxanNNFI7xco$Na",   // → tunay na 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 (…)" }
}

Yugto 4: ang hindi kayang sabihin ng litrato

Dalawang slot sa plano ang hindi maaaring hanapin sa search, at dito talaga kumikita ang ikalawang modelo ng papel nito — hindi para gumuhit ng larawan, kundi para magsulat ng code na gumuguhit nito. Mahalaga ang pagkakaiba: ang code ay maaaring suriin, deterministiko, at hindi maaaring mag-hallucinate ng taas ng isang bar.

Mga diagram: teksto papasok, SVG palabas

Nire-render ng Mermaid ang mga diagram mula sa isang plain-text na kahulugan,3 na siyang gumagawa nito bilang ang pinakaligtas na target para sa isang modelo: ang output ay maaaring siyasatin, mai-diff sa git, at nagre-render nang pareho sa bawat pagkakataon. Naibalik na ng router ang spec.

diagram.sh — isinulat ng modelo ang spec, ni-render ito ng CLI
# ang "spec" field ng isang diagram entry, isinulat sa 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
# → isang SVG na masusuri mo sa PR, hindi isang larawang dapat mong pagkatiwalaan

Mga chart: mga numero lang na nasa artikulo na

Parehong prinsipyo, isang dagdag na tanggulan. Kinopya ng router ang mga value mula sa draft; sumusulat ang modelo ng plotting code; tumatakbo ang code sa isang sandbox; sinusuri muli ng assembler ang mga value na na-render laban sa mga source number bago pahintulutan ang chart na malapit sa isang pahina.

chart.py — i-plot ang na-transcribe na data, pagkatapos ay i-verify ito
import re, matplotlib
matplotlib.use("Agg")                # headless: walang display sa CI
import matplotlib.pyplot as plt

def assert_in_draft(value, draft: str) -> None:
    """Ang isang na-plot na numero ay dapat lumitaw sa artikulo BILANG ISANG NUMERO."""
    # Ang substring matching ang bitag dito: "120" ay nasa loob ng "1200", at
    # nasa loob ng "?w=1200" — pumapasa ang naive na `str(v) in draft` sa kahit ano.
    # Mag-match sa word boundaries, at tanggapin ang 1 234 / 1,234 / 1234.
    body = re.sub(r"(?<=\d)[  ,](?=\d{3}\b)", "", draft)   # tanggalin ang separators
    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)       # na-hallucinate na value → walang 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)                    # deterministiko, masusuring artefact
    return out

Maikli ang tanggulan, ngunit isulat itong may ingat: hindi gumagana ang substring check. Totoo ang "120" in draft para sa isang artikulong may laman na 1200 o ?w=1200, kaya ang naive na bersyon ay pumapasa sa lahat at walang pinoprotektahan. Mag-anchor sa digit boundaries, i-normalize ang thousand separators, at ang uri ng pagkakamaling ikakalas ng isang teknikal na artikulo sa mga komento — isang chart na sumasalungat sa sarili nitong talata — ay hindi maaaring makarating sa production. Sumusunod ang mga screenshot sa parehong prinsipyo: ang isang scripted na page.screenshot() laban sa iyong tunay na build ang tanging pinagmumulan ng katotohanan para sa iyong sariling UI, at nananatiling totoo ito habang nagbabago ang UI.

Yugto 5: dito nakasalalay ang SEO sa pagsasama-sama

Lahat ng nauna ay gumawa ng mga file at field. Ang yugtong ito ay ginagawang markup ang mga ito — at sulit na maging eksakto dito, dahil tatlong dokumentadong pag-uugali ang napagpasyahan sa ilang linyang ito.

Ang hero, na inilabas mula sa search response — walang imbentado
<!-- Nagmumula ang mga byte sa ibang origin: bayaran ang handshake nang maaga -->
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>

<!-- LCP element: huwag kailanman lazy, laging high priority -->
<figure>
  <img src="{urls.regular}"
       width="{width}" height="{height}"        <!-- pumapatay ng layout shift -->
       alt="{alt}"                                <!-- isinulat PAGKATAPOS piliin -->
       fetchpriority="high" decoding="async"
       style="background:{color_hex}">   <!-- dominanteng kulay, 1 field -->
  <figcaption>{attribution.html}</figcaption>
</figure>

<!-- Mga larawan sa seksyon, sa ibaba ng fold: ang kabaligtarang settings -->
<img src="{urls.regular}" width="{width}" height="{height}"
     alt="{alt}" loading="lazy" decoding="async">

Hotlink o i-re-host? Nagha-hotlink ang snippet sa itaas, na siyang pinakamabilis na ipalabas at ang dahilan ng preconnect: ang isang remote na hero ay nagkakahalaga ng isang DNS lookup at isang TLS handshake sa critical path, at maaari nitong ubusin ang tinubo mo lang gamit ang fetchpriority. Ganap na tinatanggal ng pag-re-host ang third-party origin, hinahayaan kang mag-serve ng AVIF/WebP sa iyong sariling mga breakpoint, at nabubuhay kahit magbago ang upstream URL — sa kapalit ng storage, isang fetch step sa pipeline, at ang iyong sariling CDN bill. Anuman ang piliin mo, ang color_hex ay nagbibigay sa iyo ng placeholder para sa isang field (mas maganda ang blur hash, ngunit kailangan itong i-decode muna sa isang data URI — hindi ito isang CSS color). Ang direktang pagkopya ng magaspang na hero sa background: ang siyang dahilan kung bakit tahimik na nagpapalabas ang karamihan ng pipeline ng isang walang lamang kulay abo na kahon.

  1. Huwag kailanman i-lazy-load ang hero. Malinaw ang sinasabi ng web.dev: "Huwag kailanman i-lazy-load ang iyong LCP image, dahil laging magreresulta iyon sa hindi kinakailangang pagkaantala ng pag-load ng resource, at magkakaroon ng negatibong epekto sa LCP", at inirerekomenda nito ang fetchpriority="high" sa elementong malamang na maging ang LCP — na ginagamit nang tipid, sa isang larawan.4 Ang isang pipeline na naglalagay ng loading="lazy" sa bawat larawan, kasama ang hero, ang pinakakaraniwang self-inflicted na sugat sa Core Web Vitals sa automated publishing.
  2. Laging ilabas ang width at height — bumabalik ang mga ito sa response, kaya walang dahilan; ang tanging pares ng attribute na iyon ang nagbibigay-daan sa browser na mareserba ang espasyo at pinipigilan ang paglukso ng layout. Gamitin ang blur_hash bilang placeholder habang nilo-load ang file.
  3. Isulat ang alt pagkatapos ng pagpili, hindi bago. Ito ang mas maselan. Inilalarawan ng alt field ng plano ang eksenang hiniling mo; ang larawang nakuha mo ay ang pinakamalapit na match, hindi ang eksenang iyon. Ang pag-ilabas ng teksto ng brief bilang alt ay eksaktong kabiguan sa accessibility na dapat iwasan ng pipeline na ito — isang paglalarawan ng larawan na wala nga sa pahina. Buuin ang alt mula sa alt_description ng napiling larawan, na pinong-pino laban sa talatang kinaroroonan nito. Ang gabay ng Google ay "mag-focus sa paglikha ng kapaki-pakinabang, mayaman-sa-impormasyon na nilalaman na gumagamit ng mga keyword nang wasto at nasa konteksto ng nilalaman ng pahina", at nagbababala ito na ang pagpuno ng alt attributes ng mga keyword ay "nagreresulta sa isang negatibong karanasan ng user at maaaring maging dahilan upang ituring ang iyong site bilang spam".1

Ang bahaging halos walang nag-a-automate: licence metadata

Sinusuportahan ng Google ang ImageObject structured data para sa image licensing. Kailangan nito ang contentUrl kasama ang kahit isa sa creator, creditText, copyrightNotice, o license, inirerekomenda ang acquireLicensePage, at nagiging kwalipikado ang mga larawang may impormasyon ng lisensya para sa Licensable badge sa Google Images.5 Bawat isa sa mga field na iyon ay nasa search response na — kaya ang paglalabas nito ay isang template, hindi isang proyekto:

ImageObject JSON-LD, napunan mula sa API response
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "ImageObject",
  "contentUrl": "{urls.large}",                 // kailangan
  "creator": { "@type": "Person",
                "name": "{photographer_full_name}" },
  "creditText": "{photographer_full_name} on {source}",
  "license": "{LICENSE_URL[source]}",           // ang sariling pahina ng library
  "acquireLicensePage": "{source_image_url}"     // ang pahina ng larawan
}
</script>

# Ini-map ng LICENSE_URL ang `source` field sa lisensyang talagang namamahala sa
# larawan — unsplash.com/license, pexels.com/license, pixabay.com/…

Isang detalye na dapat itama: dapat itinuturo ng license ang lisensyang namamahala sa larawang iyon — ang sariling licence page ng source library — hindi ang isang summary page sa iyong domain. Binabasa ito ng Google upang magpasya sa badge eligibility, at ang isang self-referential URL ay parehong mas mahina bilang isang signal at mahirap ipagtanggol bilang anumang bagay maliban sa isang link pabalik sa sarili mo. Panatilihin ang sarili mong buod ng lisensya bilang isang internal na pahina para sa mga mambabasa; ilagay ang canonical isa sa markup.

At habang nasa assembler ka: bigyan ang file ng isang maikli, deskriptibong pangalan sa halip na IMG_0042.jpg, at idagdag ang larawan sa isang sitemap — tinatanggap ng image sitemap format ng Google ang hanggang 1,000 na larawan bawat page URL.6 Isang linya lang ang bawat isa sa isang pipeline at wala man sa dalawang ito ang natatapos kapag manwal ang ginawa.

Dalawang pipeline na maaaring i-adopt

Parehong limang yugto, dalawang napakaibang hugis — isa para sa mga artikulong sinusulat mo, isa para sa dokumentasyong dapat tumugma sa isang tumatakbong produkto. Piliin ang isa na kilala mo ang failure mode.

1 · Ang dev blog sa CI — Markdown sa repo

Nakatira ang mga artikulo bilang Markdown, kino-commit ang mga larawan sa tabi nila, at tumatakbo ang lahat sa push. Deterministiko, masusuring sa PR, walang runtime dependency sa anumang 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   # napupunta ang mga larawan sa PR
        with: { commit-message: "chore(content): illustrate" }

Isang tao pa rin ang nag-a-approve ng PR, at iyon ang punto: iminumungkahi ng pipeline, nagdedesisyon ang reviewer, at ang front-matter na isinulat nito ay maaaring i-diff.

Ang kalahati ng search ay isang GET lang — ang request, ang score_threshold nito at ang mga fields na ibinabalik nito ay nakasulat linya por linya sa gabay para sa iisang artikulo, kaya ini-import na lamang ito dito sa halip na i-print muli. Ang idinaragdag ng file na ito ay lahat ng kailangan ng isang plano na wala sa isang litrato: ang muling pagsubok sa isang brief na walang naibalik, ang pag-angkin sa isang photo_id para walang dalawang pahina ang magbahagi ng iisang larawan, at ang alt text na sinulat mula sa larawang naibalik.

plan_to_pr.py — ang tagapagdugtong: plano ng visual papasok, front-matter palabas
import frontmatter
from photo_search import search   # isang GET /search/photos; nililiban ang mga id na nasa `used`

def find_photo(entry: dict, used: set) -> dict | None:
    """Maghanap; kung sobrang specific ang brief, palawakin nang isang beses, tapos sumuko."""
    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"])   # i-claim ito: walang paulit-ulit sa buong site
            return photo
    return None                        # magdedesisyon ang caller: laktawan ang slot, o mag-fail

def widen(query: str) -> str:
    """Tanggalin ang huling clause — kadalasan ang sobrang specific."""
    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:                    # mas mabuti ang walang hero kaysa masamang hero
        raise SystemExit(f"{path}: no photo above threshold — rewrite the brief")

    post["hero"] = {                    # lahat ng kailangan ng template
        "src": hero["urls"]["regular"], "w": hero["width"], "h": hero["height"],
        # Inilalarawan ng alt ang larawang NAKUHA namin, hindi kailanman ang eksenang hiniling namin.
        "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 bilang batayan, pinaikli sa ~125 karakter para sa screen readers.
    Ibalik ito sa modelo kasama ang talata kung gusto mo ng mas mahusay."""
    base = photo.get("alt_description") or photo.get("description", "")
    return base[:125].rstrip(" ,;")

2 · Docs at changelog — screenshot muna, larawan sa huli

Baliktarin ang mga default ng router. Sa dokumentasyon ng produkto ang tapat na visual ay halos laging ang sarili mong UI: isang Playwright script na nagbubukas ng tunay na build, nagtatakda ng nakapirming viewport, at kumukuha ng eksaktong estado na inilalarawan ng talata. Sinasaklaw ng mga diagram ang mga architecture page, at ang mga litrato ay lumilitaw lamang sa mga conceptual at landing page — kung saan wala talagang masasabi ang isang screenshot.

shots.py — ang screenshot ay generated, hindi kailanman inilarawan
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-crisp
    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()
# Tumatakbo sa parehong CI job kasama ang docs build → hindi kailanman kayang
# ilarawan ng screenshot ang isang bersyon ng UI na wala na.

Dalawang variant na sulit banggitin ngunit hindi sulat ng sariling recipe. Binabaligtad ng Programmatic SEO ang loop: sa daan-daang pahina na na-generate mula sa isang database ay hindi ka naghahanap bawat pahina — ang isang request ay nagbabalik ng hanggang 100 larawan, kaya naghahanap ka bawat topic cluster at nagtatalaga mula sa isang pool, na may uniqueness constraint na siyang gumagawa ng deduplication (ang arkitekturang iyon, nang buo). At kailangan ng newsletters at social cards ang parehong larawan sa apat na crop: itago ang photo_id, hilingin ang laki na kailangan mo mula sa urls, at maghanap lang muli kapag talagang nabigo ang isang crop.

Ang parehong pipeline, bilang iisang agent

Kung isang modelo na ang sumusulat ng draft, ang pinakamaikling landas ay bigyan ito nang direkta ng search tool sa halip na maglipat-lipat ng JSON sa pagitan ng mga proseso. Nagpapatakbo ang Pexafy ng isang hosted Model Context Protocol server sa mcp.pexafy.com/mcp. Nasakop na sa ibang lugar ang setup ng connector at ang mga tool na inilalantad nito — ang desktop at editor setup sa gabay para sa iisang artikulo, ang headless na variant para sa isang CI runner sa artikulo tungkol sa content sa malaking sukat, at kung ano ang itsura ng mas malawak na merkado ng connector — sino ang naglalabas ng MCP image server, sa anong mga termino — sa pag-aaral ng image search infrastructure para sa mga AI agent. Ang mahalagang ipakita rito ay ang mangyayari sa pipeline na ito kapag hawak na ng agent ang mga tool: hindi na ito magiging limang yugto kundi isang instruksyon na lamang.

Isang instruction, isinasagawa ang buong plano
Ikaw  Narito ang draft. Buuin ang visual plan, ilustrahan ito, at magbukas ng PR.
     Mga chart lamang mula sa mga numerong nasa teksto na.

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 · napili #1, 3000×1688, may kredito
      → mermaid-cli build/graph.mmd → static/img/graph.svg
      → chart.py §2 → 4 value na na-check laban sa draft ✓
      → search_photos(q="two engineers standing at a whiteboard…")
      ← 16 photos · 144 ms · napili #1

      ✓ 4 slot napunuan · 2 API call · alt + kredito + ImageObject naisulat
      ⚠ walang ibinalik ang §5 na higit sa 0.5 — sobrang abstract ang brief, muling isinulat bilang
        "isang tao sa kusina na sinusuri ang mga numero sa isang laptop"

Ang huling linyang iyon ang dahilan kung bakit sulit magkaroon ng agent sa loop na ito kaysa sa isang purong script: ang failure mode ng automated illustration ay ang isang masamang brief, at ang muling pagsulat ng masamang brief ang eksaktong ginagamit sa isang language model. Panatilihin ang mga deterministikong bahagi — pagtatalaga, deduplication, ang numeric guard — sa code.

Ano ang gastos ng isang ilustradong artikulo

Ang tatlong photo slot bawat artikulo ay nangangahulugang tatlong search request, kaya ang free plan (5,000 requests/month) ay sumasaklaw sa 1,666 artikulo bawat buwan bago pa man lumitaw ang tanong ng pagbabayad — at kung pinagsasama-sama mo ang mga search bawat topic cluster sa halip na bawat artikulo, gumagalaw ang langit na iyon nang isa pang antas ng magnitude. Ang breakdown bawat plano, at ang pooling architecture na nagpapawalang-saysay dito, ay nasa kasamang artikulo tungkol sa pag-ilustra ng content sa malawakang saklaw.

Ang dalawang tawag sa modelo bawat artikulo — isa para sa draft, isa para sa visual plan — ay ilang libong token lamang at magiging pinakamurang linya sa pipeline; walang gastos ang Mermaid at matplotlib render maliban sa mga segundo ng CI. Ang bilang na nakakagulat sa mga tao ay kung aling linya ang hindi mura: ang paggawa ng apat na larawan bawat artikulo, apat na daang larawan bawat buwan, kasama ang mga pagsubok na hindi nakarating — at ang output ay wala pa ring photographer, petsa, o source URL.

Ang hindi naaayos ng pipeline na ito

Isang tapat na seksyon, dahil mahal ang pagkabigong pinipigilan nito. Isang mahusay na ini-ilustrang artikulo ay isang artikulo pa rin: ang tunay na photography, tumpak na chart, at wastong markup ay nagpapabuti sa isang pahinang karapat-dapat umiral. Hindi nila napapa-rank ang manipis, malawakang-produced na content. Binabanggit ng patakaran sa spam ng Google ang scaled content abuse — ang paggawa ng maraming pahina na pangunahing layunin ay manipulahin ang mga ranking at nag-aalok ng kaunting halaga sa mga user, gawin man ito gamit ang automation o hindi — at hindi salik sa paghatol na iyon ang kalidad ng mga ilustrasyon.7

Kaya ang framing na kumakapit: ang pipeline na ito ay isang quality floor na iyong kinokontrol, na inilalapat sa mga pahinang mayroon nang dahilan upang ilathala. Kung saan malinaw itong nagbabayad:

  • Pinanggalingang mapapatunayan ng mambabasa. Ang isang credit line na may tunay na photographer at isang source URL ay isang claim na maaaring i-check — at ang parehong mga field ang nagpapakain sa ImageObject markup na binabasa ng Google.
  • Accessibility at Core Web Vitals. Tunay na alt text, mga dimensyon sa bawat larawan, isang hero na hindi kailanman lazy-loaded. I-multiply sa bawat artikulong inilathala mo at ito ang kwento ng kalidad ng larawan ng site.
  • Katumpakan kung saan ito masusuri. Isang chart na ang mga numero ay tsina-tsek laban sa artikulo, isang screenshot na na-generate mula sa tumatakbong build, isang diagram na masusuri bilang teksto sa PR — tatlong visual na hindi maaaring lumihis sa katotohanan nang hindi bumagsak ang isang test.

Sa usapin ng attribution, makitid ang punto na partikular sa pipeline: ang argumento para sa laging pag-render ng credit ay ginawa na sa ibang lugar, at ang idinaragdag ng isang automated assembler ay ang parehong string ng attribution na ini-print nito ay siya ring creditText na kailangan ng structured data. Isang field, dalawang lugar, ibinubuga sa parehong pagdaan ng template — kaya't mas kaunti pa ang dahilan ng isang pipeline kaysa sa isang tao para itong laktawan.

Saan magsisimula

  1. Idagdag ang router prompt sa anumang sumusulat na ng iyong mga draft, at i-print ang JSON nang hindi kumikilos dito. Basahin ang sampung plano. Kung pinapangalanan ng mga brief ang mga paksa sa halip na mga eksena, ayusin ang prompt bago magsulat ng anumang integration.
  2. Ikonekta lamang ang mga photo slot. Isang GET /search/photos bawat brief, at itago ang photo_id mula sa unang araw — ang deduplication na iretrofit sa 3,000 live na pahina ay isang migration, hindi isang column.
  3. Ilabas ang width, height, at alt sa parehong commit. Ito ang pinakamurang Core Web Vitals na trabahong gagawin mo kailanman.
  4. Pagkatapos idagdag ang yugto 4, mga diagram bago mga chart — teksto ang Mermaid, kaya ito ang walang failure mode maliban sa isang syntax error.
  5. Idagdag ang numeric guard bago makarating ang unang chart sa isang mambabasa, hindi pagkatapos.

Mga sanggunian at talababa

1 Google Search Central, Image SEO best practices: ang alt text ay "ang pinakamahalagang attribute pagdating sa pagbibigay ng metadata para sa isang larawan"; ang gabay ay lumikha ng "kapaki-pakinabang, mayaman-sa-impormasyon na nilalaman na gumagamit ng mga keyword nang wasto at nasa konteksto ng nilalaman ng pahina", na iwasan ang mga alt attribute na puno ng keyword, gumamit ng HTML <img> elements sa halip na CSS images, at magbigay sa mga file ng maiikli ngunit deskriptibong pangalan.

2 EU AI Act, Article 50 — obligasyong pang-transparency na naaangkop simula 2 Agosto 2026: dapat markahan ng mga provider ng sistemang gumagawa ng synthetic na larawan, audio, video, o teksto ang output sa isang machine-readable na format at gawin itong madetektang artipisyal na ginawa. Sinasaklaw nito ang mga AI provider at deployer; hindi ito isang patakaran tungkol sa aling mga larawan ang maaaring ilathala ng isang website.

3 Nire-render ng Mermaid ang mga diagram at chart mula sa mga kahulugang inspirado ng Markdown na teksto, na siyang gumagawa sa output na masusuri at deterministiko.

4 web.dev, Optimize Largest Contentful Paint: "Magandang ideya na itakda ang fetchpriority="high" sa isang <img> element kung sa palagay mo ay malamang na maging ito ang LCP element ng iyong pahina", na ginagamit nang tipid; at "Huwag kailanman i-lazy-load ang iyong LCP image, dahil laging magreresulta iyon sa hindi kinakailangang pagkaantala ng pag-load ng resource, at magkakaroon ng negatibong epekto sa LCP."

5 Google Search Central, Image metadata (structured data): kailangan ng ImageObject ang contentUrl kasama ang kahit isa sa creator, creditText, copyrightNotice, o license; inirerekomenda ang acquireLicensePage, at maaaring maging kwalipikado ang mga larawang may impormasyon ng lisensya para sa Licensable badge sa Google Images.

6 Google Search Central, Image sitemaps: ang mga image sitemap ay nagbibigay-alam sa Google tungkol sa mga larawan sa isang site, kabilang ang mga natagpuan sa pamamagitan ng JavaScript, at tumatanggap ng hanggang 1,000 na larawan bawat page URL.

7 Google Search spam policies — scaled content abuse: ang paggawa ng maraming pahina na pangunahing layunin ay manipulahin ang mga ranking at nag-aalok ng kaunting halaga sa mga user, ginawa man sa pamamagitan ng automation, pagsisikap ng tao, o isang kombinasyon.

Mga madalas na tanong

Paano ko awtomatikong lalagyan ng larawan ang mga artikulong isinulat ng LLM?
Idagdag ang isang model call sa pagitan ng pagsulat at paglalathala: hilingin dito na ibalik ang isang visual plan — isang entry para sa bawat image slot, na tinatakda bilang litrato, chart, diagram, o wala. Ang mga entry na litrato ay may 12-hanggang-25-word na deskripsyon ng isang eksenang maaaring kunan ng kamera, na ipapadala mo sa isang semantic image search API (GET /api/v1/search/photos, humigit-kumulang 150 ms). Ang mga entry na chart at diagram ay ipinapadala sa isang model na sumusulat ng plotting code o Mermaid, na dine-render nang deterministiko. Huwag kailanman ipapasok ang titulo ng artikulo sa isang image search: abstract ang mga titulo at walang litratong naglalarawan nito.
Dapat bang gumamit ng screenshots o stock photos ang isang documentation site?
Baliktarin ang mga default ng router: sa product documentation, ang tapat na visual ay halos palaging ang sarili mong UI. Ang isang Playwright script na nagbubukas ng aktwal na build, nag-aayos ng viewport, at kinukuha ang eksaktong estado na inilalarawan ng talata ay tumatakbo sa parehong CI job kasama ang docs build, kaya't hindi kailanman maipapakita ng isang screenshot ang isang bersyon ng interface na hindi na umiiral. Ang mga diagram ang bumubuhay sa mga architecture page, at ang mga larawan (photographs) lumalabas lamang sa mga conceptual at landing page, kung saan walang sasabihin ang isang screenshot.
Paano ko pipigilan ang isang AI pipeline sa paglalagay ng maling numero sa isang chart?
Gawin ang plano na kopyahin sa halip na mag-imbento: ang router ay maaari lamang kopyahin ang mga value na nagpapakita na sa draft patungo sa field na data ng entry ng chart. Pagkatapos, i-assert ito sa code bago i-render — para sa bawat value, tingnan kung ang string nito ay lumitaw sa artikulo, kung hindi ay mag-raise ng error. Siyam na linya lang, at hindi na maaaring makarating sa mambabasa ang isang chart na sumasalungat sa sariling talata nito.
Anong image markup ang dapat ilabas ng isang awtomatikong pipeline para sa SEO?
Tatlong bagay, lahat mula sa mga field na nasa search response na. Isang deskriptibong alt na isinulat batay sa konteksto ng talata — tinatawag ng Google ang alt text bilang pinakamahalagang image metadata at binabalaan laban sa keyword stuffing. width at height sa bawat larawan, na may fetchpriority="high" sa hero at hindi kailanman loading="lazy" dito, dahil hindi dapat i-lazy load ang LCP image. At ImageObject structured data na may contentUrl, creator, creditText, at license, na kung saan ito ang nagpapasilbi sa isang larawan na kwalipikado para sa Licensable badge sa Google Images.
Nakakatulong ba ang paglalagay ng larawan sa AI-written articles para mag-rank ito?
Hindi ito sapat mag-isa, at mahalagang maging tumpak dito. Ang spam policies ng Google ay nagbibigay-kahulugan sa scaled content abuse bilang paggawa ng maraming pahina na pangunahing naglalayong manipulahin ang ranking na may kaunting halaga sa mga user, may automation man o wala; hindi nababago ng mga ilustrasyon ang pagsusuring ito. Ang binibigay ng isang mahusay na pipeline ay isang minimum na antas ng kalidad sa mga pahinang karapat-dapat nang umiral: verifiable na provenance, accessible na alt text, Core Web Vitals na kayang lumagpas sa automation, at mga chart at screenshot na hindi maaaring lumihis sa katotohanan.
Paano ko patakbuhin ang illustration step sa CI nang hindi nawawala ang editorial control?
I-trigger ang job sa isang pull request na humihipo sa iyong mga content file, hayaan itong isulat ang mga larawan at ang front-matter, at ipagawa ito bilang isang pull request sa halip na direktang mag-commit sa branch — ang pipeline ay nagmumungkahi, ang tao ang nag-aapruba, at ang bawat field na isinulat nito ay diffable. Panatilihing deterministic sa code, hindi sa model, ang tatlong bagay: ang deduplication ayon sa photo_id, ang pagpapatunay na ang bawat naka-plot na numero ay lumalabas sa artikulo, at ang hard failure kapag walang photo na nakapasa sa score threshold. Mas mabuti ang walang hero kaysa sa maling hero.

Itigil ang paghahanap ng mga keyword. Ilarawan kung ano ang ibig mong sabihin.

Maghanap ng 9M+ libreng gamitin na larawan ayon sa kahulugan — sa anumang wika, sa loob ng wala pang 100 ms.