متن، بخش آسان کار است: پایپلاینی که مقالههای نوشتهشده با هوش مصنوعی را تصویرسازی میکند
تولید متن مسئلهای حلشده است. تصویرسازی آن نیست. پایپلاین سرتاسری — پرامپت مسیریاب، جستوجوی عکس، دیاگرامهای Mermaid، نمودارهای تأییدشده، و متن جایگزین (alt)، اعتبار منبع و مارکآپ ImageObject که آن را به سئو تبدیل میکند.
شما یک توسعهدهنده با یک هدف محتوایی هستید: دهها مقاله در ماه، شاید صدها مقاله. مدل نویسنده پیشنویس، طرح کلی، توضیحات متا و لینکهای داخلی را انجام میدهد. سپس خط تولید به مرحلهای میرسد که هیچ مدل مخصوص خودش را ندارد — تصاویر — و متوقف میشود. نه چون بهدستآوردن عکس سخت است، بلکه چون هیچجای این پشته نمیداند کدام عکس، از کجا، با چه مجوزی، و چگونه توصیفشده باید باشد.
این مقاله همان حلقهٔ گمشده است، از ابتدا تا انتها سیمکشیشده: چه نوع تصویری برای چه نوع بخشی باید تولید شود، پرامپتی که یک پیشنویس تمامشده را به بریفهای جستوجو تبدیل میکند، فراخوانی API که عکسها را برمیگرداند، مدل دومی که چیزی را میکشد که یک عکس نمیتواند بگوید، و مونتاژگری که مارکآپی میسازد که گوگل واقعاً میتواند بخواند. در پایان، دو خط تولید کامل که میتوانید عیناً استفاده کنید.
متن حلشده است. تصویرسازی جایی است که متوقف میشود.
حسابرسی صادقانهٔ یک خط تولید مقاله خودکار را اجرا کنید. طرح کلی: حلشده. پیشنویس: حلشده. عنوان، توضیح متا، اسکیما، لینکهای داخلی، ترجمه: همه حلشده، همه توسط یک مدل، همه بهصورت متنی. سپس:
| مرحلهٔ خط تولید | وضعیت | مانع واقعی چیست |
|---|---|---|
| طرح کلی و پیشنویس | حلشده | یک فراخوانی مدل، یک پرامپت |
| عنوانها، متا، اسکیما، لینکها | حلشده | متن ورودی، متن خروجی |
| تصویر اصلی (hero) | مسدود | به یک فایل واقعی، مجوز، ابعاد و متن alt نیاز دارد — هیچکدام را یک مدل متنی نمیتواند تولید کند |
| تصاویر بخشها | مسدود | سه تا پنج تصویر در هر مقاله، هر یک متفاوت، بدون تکرار در سطح سایت |
| نمودارها و دیاگرامها | مسدود | باید دقیق باشند — تنها نوع تصویری که جستوجو نمیتواند برگرداند و یک مدل مولد نباید ابداع کند |
این نقص، زیباییشناختی نیست، ساختاری است: مقاله با یک نگهدارندهٔ جای خالی استوک منتشر میشود، یا با همان عکسی که دوازده مقالهٔ قبلی داشتند، یا با یک تصویر مولد که دستِ ششانگشتیاش اولین چیزی است که خواننده میبیند. و تصویر تزئین نیست — مستندات تصویر خودِ گوگل صریح میگوید: متن alt «مهمترین ویژگی در ارائهٔ فراداده برای یک تصویر است»، و راهنمایی این است که بهجای پسزمینههای CSS از عناصر واقعی <img> با alt توصیفی استفاده شود، تا تصویر اصلاً قابلیافتن و قابلفهم باشد.1
چهار نوع تصویر، چهار نوع مدل
بزرگترین اشتباه طراحی، رفتار با «تصویر» بهعنوان یک مسئله با یک تأمینکننده است. در واقع چهار مسئله است، و مسیریاب بین آنها یک خط پرامپت است، نه یک سرویس:
| این بخش نیاز دارد به… | تولیدش کنید با | چرا نه سایرین |
|---|---|---|
| صحنهای از دنیای واقعیتصویر اصلی، موقعیتهای انسانی، مکانها، اشیا، حرکات | جستوجوی معنایی عکس (Pexafy) | یک مدل مولد جزئیات را ابداع میکند؛ یک نمودار چیزی برای رسمکردن ندارد |
| اعدادی که واقعاً داریدبنچمارکها، قیمتگذاری، نتایج نظرسنجی، تأخیر | مدلی که کد رسمکردن نمودار مینویسد، اجراشده در یک محیط sandbox | نمیتوان به یک مدل تصویر برای مقادیر اعتماد کرد؛ یک عکس نمیتواند داده حمل کند |
| یک ساختار یا یک جریانمعماری، توالی، ماشین حالت | مدلی که Mermaid / Graphviz مینویسد و بهصورت قطعی رندر میشود | کتابخانههای عکس رایگان هیچ دیاگرامی از سیستم شما ندارند |
| محصول شما روی صفحهاسناد، لاگ تغییرات، آموزشها | اسکرینشات مرورگر با اسکریپت (Playwright) | هیچچیز دیگری نمیتواند رابط کاربریای را نشان دهد که فقط در بیلد شما وجود دارد |
و تصویرسازی تولیدشده؟ یک جایگاه صادقانه برای خودش نگه میدارد: صحنهای که نمیتوان از آن عکس گرفت و داده هم نیست — یک سازوکار انتزاعی، محصولی که هنوز وجود ندارد، یک سبک تصویرسازی خانگی که متعلق به شماست. (استدلال کامل برای برتری عکاسی واقعی بر تولید تصویر — سرعت در حجم بالا، دقت، مسئلهٔ یکسانبودن — اینجا مطرح شده.) قیمتگذاری در جهتی که مسیر پیش میرود: از تاریخ ۲ اوت ۲۰۲۶، ماده ۵۰ قانون هوش مصنوعی اتحادیه اروپا ارائهدهندگان سیستمهای مولد را ملزم میکند خروجیهای مصنوعی را در قالبی قابلخواندن توسط ماشین علامتگذاری کنند.2 این یک الزام برای ارائهدهندگان و بهکارگیرندگان هوش مصنوعی است، نه قاعدهای دربارهٔ آنچه یک وبلاگ میتواند منتشر کند — اما به همین دلیل است که منشأ تصویری که در بالای مقالهتان قرار دارد، بهطور فزایندهای چیزی است که خواننده میتواند بررسی کند، نه اینکه صرفاً به آن اعتماد کند.
خط تولید، از ابتدا تا انتها
پنج مرحله. فقط مرحلهٔ ۳ با یک API تصویر تماس میگیرد، و فقط مرحلهٔ ۴ اختیاری است:
┌─ 1. WRITE ────────────────────────────────────────────────────┐
topic ──▶ LLM ──▶ draft.md (h2 sections, front-matter)
└───────────────────────────────┬───────────────────────────────┘
┌─ 2. BRIEF ────────────────────┴───────────────────────────────┐
draft.md ──▶ LLM ──▶ { hero: {...}, sections: [ {...} ] }
one JSON object: per slot, a kind +
either a camera brief or a data/diagram spec
└──────────┬──────────────────────────────────┬─────────────────┘
│ kind = "photo" │ kind = "chart" | "diagram"
▼ ▼
┌─ 3. SEARCH ───────────────┐ ┌─ 4. DRAW (optional) ─────────┐
GET /search/photos LLM ──▶ mermaid | plotting code
← credited photo + ──▶ sandbox ──▶ .svg / .png
w/h, blur_hash, alt, (deterministic render, no
licence, source URL invented numbers)
└──────────┬────────────────┘ └──────────────┬───────────────┘
└───────────────┬──────────────────┘
┌─ 5. ASSEMBLE ─────────────┴───────────────────────────────────┐
<img> with width/height + fetchpriority | loading
alt written for a human · visible credit · ImageObject JSON-LD
photo_id stored so no two pages share a hero
└───────────────────────────────────────────────────────────────┘
دو ویژگی بیش از خودِ دیاگرام اهمیت دارند. مرحلهٔ ۲ یک مسیریاب است: برای هر جایگاه تعیین میکند کدام تولیدکننده اجرا شود، پس هرگز از یک کتابخانهٔ عکس نمودار میلهای نمیخواهید. و مرحلهٔ ۵ جایی است که سئو شکل میگیرد — هرچه گوگل درباره تصاویر مستند کرده (alt توصیفی، فراداده مجوز، یک تصویر اصلی امن از نظر LCP) در همینجا از فیلدهایی که پاسخ جستوجو از قبل حمل میکرد، صادر میشود.
مرحلهٔ ۲: پیشنویس را به بریفهای تصویری تبدیل کنید
یک فراخوانی مدل روی پیشنویس نهایی، و آنچه برمیگرداند یک برنامه است نه یک کوئری. اینکه چگونه باید یک بریف عکس واحد نوشت — چرا عنوان مقاله بدترین ورودی ممکن است، یک بریف دوربینی ۱۲ تا ۲۵ کلمهای چه شکلی دارد، و به چه شیوههایی شکست میخورد — بهطور کامل در راهنمای تصویرسازی یک مقاله آمده است، و اینجا تکرار نمیشود. آنچه یک پایپلاین اضافه میکند مسیریابی است: همان فراخوانی باید، جایگاه به جایگاه، تصمیم بگیرد کدام تولیدکننده اجرا شود — و یک مدخل نمودار داده حمل میکند در حالی که یک مدخل عکس صحنه حمل میکند.
# system prompt — run once per finished draft
You are the art director of a technical publication. Read the article and
return the visual plan: one entry for the hero, one per H2 section.
For each entry choose exactly one kind:
"photo" a real scene: someone doing something somewhere, a place,
an object, a gesture. The default for heroes.
"chart" the section states numbers that are IN the article. Never
invent values: copy them into data, verbatim.
"diagram" the section describes a structure, a flow or a sequence.
"none" the section is short, or already carries a code block.
Rules for "photo" entries — the field is query:
یک بریف دوربین بنویس: صحنهای که یک دوربین میتوانست ثبت کند، بین ۱۲ تا ۲۵ کلمه،
به زبان انگلیسی، متناسب با حالوهوای بخش. آنچه را در قاب است نام ببر،
نه موضوع را. بدون متن، لوگو، برند یا افراد مشهور؛ بدون
استعارههای نامرئی. (قوانین کامل، همراه با مثالها و موارد ناموفق:
pexafy.com/blog/illustrate-blog-articles-at-scale/)
Do NOT write the alt text of a photo entry: the picture you get back is the
closest match to the brief, not the scene you described, so its alt has to be
written from the chosen photo. Chart and diagram entries DO carry an alt —
there you control exactly what is rendered.
Return JSON only:
{
"hero": { "kind": "photo", "query": "…", "orientation": "landscape" },
"sections": [
{ "h2": "…", "kind": "photo", "query": "…" },
{ "h2": "…", "kind": "chart", "title": "…", "unit": "ms",
"data": [ {"label": "…", "value": 0} ], "alt": "…" },
{ "h2": "…", "kind": "diagram", "spec": "flowchart LR; …", "alt": "…" }
]
}
خط kind بیشتر کار را انجام میدهد، و دستور data بقیه را:
یک مدخل نمودار فقط میتواند اعدادی را حمل کند که از قبل در پیشنویس ظاهر شدهاند، بنابراین مدل
رونویسی میکند نه اختراع. در اینجا مسیریاب را روی سه بخش واقعی از یک مقالهٔ توسعهدهنده میبینید:
| بخش | kind | خروجی مسیریاب |
|---|---|---|
| تصویر اصلی — «چرا بیلد شبانهمان ۴۰ دقیقه طول میکشد» | photo |
«توسعهدهندهای که دیرهنگام پشت میزی با دو مانیتور و صفحهکلید مکانیکی، در اتاقی تاریک که با روشنایی صفحهنمایشها نور میگیرد، کار میکند» |
| «زمان واقعاً کجا میرود» | chart |
data از پاراگراف رونویسی شده: نصب ۴۸۰ ثانیه، کامپایل ۱۰۸۰ ثانیه، تست ۷۲۰ ثانیه، آپلود ۱۲۰ ثانیه |
| «چگونه گراف را تقسیم میکنیم» | diagram |
flowchart LR از گراف وابستگی وظایف |
| «چه چیزی را تغییر دادیم و باز هم چه کاری را انجام میدهیم» | photo |
«دو مهندس ایستاده در برابر تختهسفیدی پوشیده از دیاگرامها که با هم مشکلی را حل میکنند» |
مرحلهٔ ۳: عکسها با اعتبار برمیگردند
هر ورودی از نوع kind: "photo" یک درخواست است. بریف تصویر اصلی بالا، اجراشده بر API عمومی، این را در ۱۴۷ میلیثانیه برمیگرداند:
بریف بخش آخر، صحنهای کاملاً متفاوت، در ۱۴۴ میلیثانیه:
چیزی که بهازای هر عکس برمیگردد، همان بخشی است که مرحلهٔ ۵ را ممکن میکند — نه صرفاً یک فایل:
{
"photo_id": "019e1c7f-0063-759e-b498-33ce1714e6c9", // ذخیرهاش کنید: بدون تکرار
"urls": { "small": "…?w=400", "regular": "…?w=1080",
"large": "…?w=1920" },
"width": 3000, "height": 1688, // → بدون تغییر چیدمان
"blur_hash": "LJ8gjv9rVq-6OFxanNNFI7xco$Na", // → جاینگهدار واقعی
"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 (…)" }
}
مرحلهٔ ۴: چیزی که یک عکس نمیتواند بگوید
دو جایگاه از طرح، قابلجستوجو نیستند، و این دقیقاً جایی است که یک مدل دوم جای خود را ثابت میکند — نه برای کشیدن یک تصویر، بلکه برای نوشتن کدی که آن را میکشد. این تفاوت اهمیت دارد: کد قابلبازبینی، قطعی است و نمیتواند ارتفاع یک میله را توهم بزند.
دیاگرامها: متن ورودی، SVG خروجی
Mermaid دیاگرامها را از یک تعریف متن ساده رندر میکند،3 که این آن را به هدف امنترین برای یک مدل تبدیل میکند: خروجی قابلبازرسی است، در گیت قابلdیف است، و هر بار به یک شکل رندر میشود. مسیریاب از قبل مشخصات را برگردانده است.
# فیلد "spec" یک ورودی diagram، نوشتهشده در 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
# → یک SVG که میتوانید در PR بازبینی کنید، نه تصویری که باید فقط باورش کنید
نمودارها: فقط اعدادی که مقاله از قبل دارد
همان اصل، یک محافظ اضافه. مسیریاب مقادیر را از پیشنویس رونویسی کرد؛ مدل کد رسمکردن را مینویسد؛ کد در یک sandbox اجرا میشود؛ مونتاژگر مقادیر رندرشده را دوباره در مقابل اعداد منبع بررسی میکند پیش از آنکه اجازه یابد نمودار به صفحه نزدیک شود.
import re, matplotlib
matplotlib.use("Agg") # headless: بدون نمایشگر در CI
import matplotlib.pyplot as plt
def assert_in_draft(value, draft: str) -> None:
"""عدد رسمشده باید در مقاله بهعنوان یک عدد ظاهر شود."""
# تطبیق زیررشتهای همان تله است: "120" داخل "1200" است، و
# داخل "?w=1200" — یک `str(v) in draft` ساده روی هرچیزی قبول میشود.
# روی مرزهای کلمه تطبیق دهید، و 1 234 / 1,234 / 1234 را بپذیرید.
body = re.sub(r"(?<=\d)[ ,](?=\d{3}\b)", "", draft) # حذف جداکنندهها
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) # مقدار توهمزدهشده → بدون نمودار
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) # مصنوع قطعی و قابلبازبینی
return out
محافظ کوتاه است، اما آن را با دقت بنویسید: بررسی زیررشتهای کار نمیکند.
"120" in draft برای مقالهای که 1200 یا
?w=1200 دارد هم درست است، پس نسخهٔ سادهلوحانه روی هرچیزی قبول میشود و هیچچیز را محافظت نمیکند. تطبیق را به مرزهای رقمی متصل کنید، جداکنندههای هزارگان را یکسانسازی کنید، و آن دسته از خطاهایی که یک مقالهٔ فنی را در نظرات از هم میدرد — نموداری که با پاراگراف خودش تناقض دارد — نمیتواند به تولید برسد. اسکرینشاتها همان اصل را دنبال میکنند: یک page.screenshot() اسکریپتشده در برابر بیلد واقعی شما، تنها منبع حقیقت برای رابط کاربری خودتان است و با تغییر رابط کاربری همچنان درست باقی میماند.
مرحلهٔ ۵: مونتاژ جایی است که سئو شکل میگیرد
همهٔ آنچه تا اینجا انجام شد، فایل و فیلد تولید کرد. این مرحله آنها را به مارکآپ تبدیل میکند — و اینجا ارزش دقیقبودن دارد، چون سه رفتار مستندشده در همین چند خط تصمیمگیری میشوند.
<!-- بایتها از یک مبدأ دیگر میآیند: هزینهٔ handshake را زودتر بپردازید -->
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>
<!-- عنصر LCP: هرگز lazy نه، همیشه اولویت بالا -->
<figure>
<img src="{urls.regular}"
width="{width}" height="{height}" <!-- تغییر چیدمان را از بین میبرد -->
alt="{alt}" <!-- بعد از انتخاب نوشتهشده -->
fetchpriority="high" decoding="async"
style="background:{color_hex}"> <!-- رنگ غالب، ۱ فیلد -->
<figcaption>{attribution.html}</figcaption>
</figure>
<!-- تصاویر بخشها، زیر تای اول: تنظیمات معکوس -->
<img src="{urls.regular}" width="{width}" height="{height}"
alt="{alt}" loading="lazy" decoding="async">
هاتلینک یا میزبانی مجدد؟ قطعهٔ بالا هاتلینک میکند، که سریعترین راه برای انتشار است و دلیل وجود preconnect: یک تصویر اصلی راهدور یک jلوکاپ DNS و یک handshake TLS در مسیر بحرانی هزینه دارد، و این میتواند سودی را که با fetchpriority خریدید بخورد. میزبانی مجدد، کلاً مبدأ ثالث را حذف میکند، اجازه میدهد AVIF/WebP را در breakpointهای خودتان سرو کنید، و در برابر تغییر URL بالادستی زنده میماند — به قیمت فضای ذخیرهسازی، یک مرحلهٔ fetch در خط تولید و صورتحساب CDN خودتان. هرکدام را انتخاب کنید،
color_hex یک جاینگهدار برای یک فیلد به شما میدهد (blur hash زیباتر است، اما باید ابتدا به یک data URI تبدیل شود — یک رنگ CSS نیست). کپیکردن یک تصویر اصلی خام مستقیم در background: جایی است که بیشتر خط تولیدها بیسروصدا یک کادر خاکستری خالی منتشر میکنند.
-
هرگز تصویر اصلی را lazy-load نکنید. web.dev بیابهام است: «هرگز تصویر LCP خود را lazy-load نکنید، چرا که این همیشه به تأخیر بارگذاری منبعی غیرضروری میانجامد و اثری منفی روی LCP خواهد داشت»، و توصیه میکند
fetchpriority="high"روی عنصری قرار گیرد که احتمالاً LCP خواهد بود — با استفادهٔ محدود، روی یک تصویر.4 خط تولیدی کهloading="lazy"را روی هر تصویری میزند، شامل تصویر اصلی، شایعترین زخم خودزنی هستهٔ حیاتی وب در انتشار خودکار است. -
همیشه
widthوheightرا صادر کنید — آنها در پاسخ برمیگردند، پس بهانهای وجود ندارد؛ همان جفت ویژگی چیزی است که به مرورگر امکان میدهد فضا را رزرو کند و از پرش چیدمان جلوگیری کند. ازblur_hashبهعنوان جاینگهدار در حین بارگذاری فایل استفاده کنید. -
alt را بعد از انتخاب بنویسید، هرگز پیش از آن. این نکتهٔ ظریف است. فیلد
altطرح، صحنهای را توصیف میکند که خواستهاید؛ عکسی که بهدست آوردهاید نزدیکترین تطبیق است، نه آن صحنه. انتشار متن بریف بهعنوان alt دقیقاً همان شکست دسترسیپذیریای است که این خط تولید قرار است از آن جلوگیری کند — توصیف یک تصویری که در صفحه نیست. alt را ازalt_descriptionعکس انتخابشده بسازید، و آن را در تناسب با پاراگرافی که در آن قرار میگیرد اصلاح کنید. راهنمایی گوگل این است که «روی ساختن محتوای مفید و اطلاعاتمحوری تمرکز کنید که کلیدواژهها را بهدرستی استفاده میکند و متناسب با محتوای صفحه است»، و هشدار میدهد که پُرکردن ویژگیهای alt با کلیدواژه «به تجربهٔ کاربری منفی میانجامد و ممکن است باعث شود سایت شما هرزنامه تشخیص داده شود».1
بخشی که تقریباً هیچکس خودکارسازیاش نمیکند: فراداده مجوز
گوگل دادهساختاریافتهٔ ImageObject را برای مجوزدهی تصویر پشتیبانی میکند. این نیازمند
contentUrl بههمراه حداقل یکی از creator، creditText،
copyrightNotice یا license است، acquireLicensePage را توصیه میکند، و تصاویری با اطلاعات مجوز واجد شرایط نشان Licensable در Google Images میشوند.5 هر یک از این فیلدها از قبل در پاسخ جستوجو موجود است — پس صدورش یک قالب است، نه یک پروژه:
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "ImageObject",
"contentUrl": "{urls.large}", // اجباری
"creator": { "@type": "Person",
"name": "{photographer_full_name}" },
"creditText": "{photographer_full_name} on {source}",
"license": "{LICENSE_URL[source]}", // صفحهٔ مجوز خودِ کتابخانه
"acquireLicensePage": "{source_image_url}" // صفحهٔ عکس
}
</script>
# LICENSE_URL فیلد `source` را به مجوزی که واقعاً
# عکس را حاکم است نگاشت میکند — unsplash.com/license, pexels.com/license, pixabay.com/…
یک جزئیات ارزش دقیقبودن دارد: license باید به مجوزی اشاره کند که آن عکس را حاکم است — صفحهٔ مجوز خودِ کتابخانهٔ منبع — نه به یک صفحهٔ خلاصه در دامنهٔ شما. گوگل آن را میخواند تا واجدشرایطبودن نشان را تعیین کند، و یک URL خودارجاع هم بهعنوان یک سیگنال ضعیفتر است و هم دشوار قابلدفاع بهعنوان چیزی جز لینکی به خودتان. خلاصهٔ مجوز خودتان را بهعنوان یک صفحهٔ داخلی برای خوانندگان نگه دارید؛ نسخهٔ کانونی را در مارکآپ قرار دهید.
و درحالیکه در مونتاژگر هستید: به فایل یک نام کوتاه و توصیفی بدهید بهجای
IMG_0042.jpg، و تصویر را به یک sitemap اضافه کنید — قالب image sitemap گوگل تا ۱٬۰۰۰ تصویر بهازای هر URL صفحه میپذیرد.6 هر دو کار، هرکدام یک خط در یک خط تولید هستند و هیچکدام هرگز بهصورت دستی انجام نمیشوند.
دو خط تولید که میتوانید بردارید
همان پنج مرحله، دو شکل بسیار متفاوت — یکی برای مقالههایی که مینویسید، یکی برای اسنادی که باید با محصولی در حال اجرا همخوانی داشته باشد. آنی را انتخاب کنید که حالت شکستاش را میشناسید.
۱ · بلاگ توسعه در CI — Markdown در مخزن
مقالهها بهصورت Markdown زندگی میکنند، تصاویر کنارشان کامیت میشوند، و همهچیز روی push اجرا میشود. قطعی، قابلبازبینی در PR، بدون وابستگی زمان اجرا به هیچ 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 # تصاویر در PR فرود میآیند
with: { commit-message: "chore(content): illustrate" }
یک انسان همچنان PR را تصویب میکند، که نکتهٔ اصلی است: خط تولید پیشنهاد میدهد، بازبین تصمیم میگیرد، و front-matterای که نوشته شده قابلdیف است.
نیمهٔ جستوجو یک GET است — درخواست، score_threshold آن و فیلدهایی که برمیگرداند
خطبهخط در
راهنمای تکمقالهای نوشته شدهاند، پس اینجا صرفاً وارد میشود نه اینکه دوباره چاپ شود. آنچه این فایل اضافه میکند هر چیزی است که یک
برنامه به آن نیاز دارد و یک عکس ندارد: تلاش مجدد روی بریفی که چیزی برنگردانده،
ادعای مالکیت روی یک photo_id تا هیچ دو صفحه یک تصویر را به اشتراک نگذارند، و متن جایگزینی که از عکس بازگشتی نوشته میشود.
import frontmatter
from photo_search import search # یک GET /search/photos؛ شناسههای موجود در `used` را نادیده میگیرد
def find_photo(entry: dict, used: set) -> dict | None:
"""جستوجو؛ اگر بریف بیشازحد خاص بود، یک بار وسیعترش کنید، سپس تسلیم شوید."""
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"]) # مالکش شوید: بدون تکرار در کل سایت
return photo
return None # فراخوان تصمیم میگیرد: ردکردن جایگاه، یا شکست
def widen(query: str) -> str:
"""آخرین بند را حذف کنید — معمولاً همان که بیشازحد خاص است."""
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: # بدون تصویر اصلی بهتر از یک تصویر بد است
raise SystemExit(f"{path}: no photo above threshold — rewrite the brief")
post["hero"] = { # هرچه قالب نیاز دارد
"src": hero["urls"]["regular"], "w": hero["width"], "h": hero["height"],
# alt عکسی را توصیف میکند که بهدست آوردیم، نه صحنهای که خواستیم.
"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 بهعنوان پایه، به حدود ۱۲۵ کاراکتر برای صفحهخوانها بریدهشده.
اگر بهتری میخواهید، آن را با پاراگراف از طریق مدل دوباره بفرستید."""
base = photo.get("alt_description") or photo.get("description", "")
return base[:125].rstrip(" ,;")
۲ · اسناد و لاگ تغییرات — ابتدا اسکرینشات، در آخر عکس
حالت پیشفرض مسیریاب را معکوس کنید. در اسناد محصول، تصویر صادقانه تقریباً همیشه رابط کاربری خودتان است: یک اسکریپت Playwright که بیلد واقعی را باز میکند، viewport ثابتی تنظیم میکند و همان حالتی را که پاراگراف توصیف میکند، ثبت میکند. دیاگرامها صفحات معماری را میپوشانند، و عکسها فقط روی صفحات مفهومی و لندینگ ظاهر میشوند — جایی که یک اسکرینشات چیزی نمیگفت.
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
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()
# در همان job CI که ساخت اسناد اجرا میشود اجرا میشود → اسکرینشات هرگز
# نمیتواند نسخهای از رابط کاربری را توصیف کند که دیگر وجود ندارد.
دو واریانت ارزش نامبردن دارند اما ارزش نسخهٔ خودشان را ندارند. برنامهنویسیِ سئوی مقیاسپذیر (Programmatic SEO)
حلقه را معکوس میکند: با صدها صفحهٔ تولیدشده از یک پایگاهداده، بهازای هر صفحه جستوجو نمیکنید —
یک درخواست تا ۱۰۰ عکس برمیگرداند، پس بهازای هر خوشهٔ موضوعی جستوجو میکنید و از یک استخر تخصیص میدهید، با یک محدودیت یکتایی که تکرارزدایی را انجام میدهد
(آن معماری، بهطور کامل).
و خبرنامهها و کارتهای شبکهٔ اجتماعی به همان عکس در چهار برش نیاز دارند: photo_id را نگه دارید، اندازهای که نیاز دارید را از urls بخواهید، و فقط زمانی دوباره جستوجو کنید که یک برش واقعاً شکست بخورد.
همان خط تولید، بهعنوان یک عامل (agent)
اگر مدلی از قبل در حال نوشتن پیشنویس است، کوتاهترین مسیر این است که ابزار جستوجو را مستقیماً در اختیارش بگذاریم، بهجای رد و بدل کردن JSON بین فرایندهای جداگانه. Pexafy یک سرور میزبانیشده Model Context
Protocol در آدرس mcp.pexafy.com/mcp اجرا میکند. راهاندازی کانکتور و ابزارهایی که ارائه میدهد در جای دیگری پوشش داده شدهاند — راهاندازی دسکتاپ و ویرایشگر در
راهنمای تکمقالهای،
نسخهٔ بدون رابط گرافیکی برای یک runner در CI در
مقالهٔ محتوا در مقیاس،
و اینکه بازار گستردهتر کانکتورها چه شکلی دارد — چه کسی سرور تصویر MCP ارائه میدهد و با چه شرایطی —
در مطالعهٔ
زیرساخت جستوجوی تصویر برای عاملهای هوش مصنوعی آمده است.
چیزی که ارزش نشان دادن دارد این است که با در اختیار داشتن این ابزارها توسط عامل، بر سر این خط لوله چه میآید: دیگر پنج مرحله نیست، بلکه یک دستور واحد میشود.
You این پیشنویس است. طرح تصویری را بساز، تصویرسازیاش کن، و یک PR باز کن.
نمودارها فقط از اعدادی که از قبل در متن هستند.
Agent → plan: hero=photo · §2=chart · §3=diagram · §4=photo
→ search_photos(q="a developer working late at a desk with two
monitors and a mechanical keyboard in a dark room…")
← 16 photos · 147 ms · picked #1, 3000×1688, credited
→ mermaid-cli build/graph.mmd → static/img/graph.svg
→ chart.py §2 → 4 values checked against the draft ✓
→ search_photos(q="two engineers standing at a whiteboard…")
← 16 photos · 144 ms · picked #1
✓ 4 جایگاه پُرشد · 2 فراخوانی API · alt + credit + ImageObject نوشته شد
⚠ §5 هیچچیز بالای 0.5 برنگرداند — بریف بیشازحد انتزاعی بود، بازنویسی شد به
"a person at a kitchen table checking figures on a laptop"
آن خط آخر، همان دلیل ارزش داشتن یک عامل در این حلقه است، نه یک اسکریپت خالص: حالت شکست تصویرسازی خودکار یک بریف بد است، و بازنویسی یک بریف بد دقیقاً همان کاری است که یک مدل زبانی برای آن ساخته شده. بخشهای قطعی — تخصیص، تکرارزدایی، محافظ عددی — را در کد نگه دارید.
هزینهٔ یک مقالهٔ تصویرسازیشده چقدر است
سه جایگاه عکس بهازای هر مقاله به معنای سه درخواست جستوجو است، پس پلن رایگان (5,000 درخواست در ماه) 1,666 مقاله در ماه را پیش از اینکه هرگونه پرسشی درباره پرداخت مطرح شود، پوشش میدهد — و اگر جستوجوها را بهازای هر خوشهٔ موضوعی بهجای هر مقاله جمع کنید، آن سقف یک مرتبهٔ بزرگی دیگر بالاتر میرود. تفکیک پلنبهپلن، و معماری تجمیعی که آن را بیاهمیت میکند، در مقالهٔ همراه در مورد تصویرسازی محتوا در مقیاس آمده است.
دو فراخوانی مدل بهازای هر مقاله — یکی برای پیشنویس، یکی برای طرح تصویری — چند هزار توکن است و ارزانترین سطر خط تولید خواهد بود؛ رندرهای Mermaid و matplotlib چیزی جز چند ثانیهٔ CI هزینه ندارند. سطری که مردم را غافلگیر میکند این است: تولید چهار تصویر بهازای هر مقاله، چهارصد تصویر در ماه، بههمراه تلاشهایی که به سرانجام نرسیدند — و خروجی همچنان بدون هیچ عکاسی، تاریخ یا URL منبعی میماند.
چیزی که این خط تولید حل نمیکند
یک بخش صادقانه، چون شکستی که از آن جلوگیری میکند هزینهبر است. یک مقالهٔ خوبتصویرسازیشده هنوز یک مقاله است: عکاسی واقعی، نمودارهای دقیق و مارکآپ صحیح، صفحهای را بهتر میکنند که سزاوار وجود است. آنها محتوای بیکیفیت و انبوهتولیدشده را رتبهبند نمیکنند. سیاستهای هرزنامهٔ گوگل مورد سوءاستفادهٔ محتوای مقیاسپذیر را نام میبرند — تولید صفحات زیاد اساساً برای دستکاری رتبهبندی و ارائهٔ ارزش کم به کاربران، خواه اتوماسیون دخیل باشد یا نه — و کیفیت تصویرسازیها عاملی در این قضاوت نیست.7
پس چارچوبی که دوام میآورد: این خط تولید یک کف کیفیتِ تحت کنترل شما است، اعمالشده روی صفحاتی که از قبل دلیلی برای انتشار دارند. جایی که بهطور مشخص میارزد:
- منشأیی که خواننده میتواند تأیید کند. یک خط اعتبار با یک عکاس واقعی و یک URL منبع، ادعایی است که میتوان بررسی کرد — و همان فیلدها مارکآپ
ImageObjectرا میسازند که گوگل میخواند. - دسترسیپذیری و هستهٔ حیاتی وب. متن alt واقعی، ابعاد روی هر تصویر، تصویر اصلیای که هرگز lazy-load نمیشود. این را در هر مقالهای که منتشر میکنید ضرب کنید و این میشود داستان کیفیت تصویر سایت.
- دقت جایی که قابلبررسی است. نموداری که اعدادش در مقابل مقاله تأیید شدهاند، اسکرینشاتی تولیدشده از بیلد در حال اجرا، دیاگرامی قابلبازبینی بهعنوان متن در PR — سه تصویری که نمیتوانند بدون شکست یک تست از حقیقت منحرف شوند.
در مورد اسناد منبع، نکتهٔ خاص پایپلاین محدود است:
استدلال برای همیشه نمایشدادن اعتبار در جای دیگری مطرح شده، و آنچه یک اسمبلر خودکار اضافه میکند این است که همان
رشتهٔ attribution که چاپ میکند همان creditText است که دادههای ساختیافته
به آن نیاز دارند. یک فیلد، دو جا، در یک اجرای واحد از قالب صادر میشود — به همین دلیل است که یک پایپلاین حتی بهانهٔ کمتری نسبت به یک انسان دارد که آن را حذف کند.
از کجا شروع کنید
- پرامپت مسیریاب را اضافه کنید به هرچه که از قبل پیشنویسهایتان را مینویسد، و JSON را چاپ کنید بدون اینکه روی آن عمل کنید. ده طرح را بخوانید. اگر بریفها موضوعات را بهجای صحنهها نام میبرند، پرامپت را پیش از نوشتن هرگونه یکپارچهسازی اصلاح کنید.
- فقط جایگاههای عکس را سیمکشی کنید. یک
GET /search/photosبهازای هر بریف، وphoto_idرا از روز اول ذخیره کنید — تکرارزدایی که بعداً روی ۳٬۰۰۰ صفحهٔ زنده اعمال شود، یک مهاجرت است، نه یک ستون. - width، height و alt را در همان کامیت صادر کنید. ارزانترین کار هستهٔ حیاتی وبی است که تا حالا انجام میدهید.
- سپس مرحلهٔ ۴ را اضافه کنید، دیاگرامها پیش از نمودارها — Mermaid متن است، پس تنها حالت شکستش خطای نحوی است.
- محافظ عددی را اضافه کنید پیش از اینکه اولین نمودار به خواننده برسد، نه پس از آن.
منابع و پانویسها
1 Google Search Central، بهترین شیوههای سئوی تصویر: متن alt
«مهمترین ویژگی در ارائهٔ فراداده برای یک تصویر است»؛ راهنمایی این است که «محتوای مفید و اطلاعاتمحوری بسازید که کلیدواژهها را بهدرستی استفاده میکند و متناسب با محتوای صفحه است»، از پُرکردن ویژگیهای alt با کلیدواژه پرهیز کنید، از عناصر HTML
<img> بهجای تصاویر CSS استفاده کنید، و به فایلها نامهای کوتاه اما توصیفی بدهید.
2 قانون هوش مصنوعی اتحادیهٔ اروپا، مادهٔ ۵۰ — تکالیف شفافیت قابلاجرا از ۲ آگوست ۲۰۲۶: ارائهدهندگان سیستمهایی که تصویر، صدا، ویدیو یا متن ساختگی تولید میکنند باید خروجیها را در قالبی ماشینخوانا برچسبگذاری کنند و آنها را بهعنوان مولدِ هوش مصنوعی قابلشناسایی سازند. این ارائهدهندگان و بهکارگیرندگان هوش مصنوعی را ملزم میکند؛ قاعدهای در مورد اینکه یک وبسایت چه تصاویری میتواند منتشر کند، نیست.
3 Mermaid دیاگرامها و نمودارها را از تعریفهای متنی الهامگرفته از Markdown رندر میکند، که همین خروجی را قابلبازبینی و قطعی میکند.
4 web.dev، بهینهسازی Largest Contentful Paint: «ایدهٔ خوبی است که
fetchpriority="high" را روی یک عنصر <img> تنظیم کنید اگر فکر میکنید احتمالاً همان عنصر LCP صفحهتان است»، با استفادهٔ محدود؛ و «هرگز تصویر LCP خود را lazy-load نکنید، چرا که این همیشه به تأخیر بارگذاری منبعی غیرضروری میانجامد و اثری منفی روی LCP خواهد داشت.»
5 Google Search Central، فراداده تصویر (دادهساختاریافته):
ImageObject نیازمند contentUrl بههمراه حداقل یکی از
creator، creditText، copyrightNotice یا
license است؛ acquireLicensePage توصیه میشود، و تصاویری با اطلاعات مجوز میتوانند واجدشرایط نشان Licensable در Google Images شوند.
6 Google Search Central، Sitemap تصویر: sitemapهای تصویر گوگل را از تصاویر یک سایت مطلع میکنند، از جمله آنهایی که از طریق جاوااسکریپت یافت میشوند، و تا ۱٬۰۰۰ تصویر بهازای هر URL صفحه میپذیرند.
7 سیاستهای هرزنامهٔ Google Search — سوءاستفادهٔ محتوای مقیاسپذیر: تولید صفحات زیاد اساساً برای دستکاری رتبهبندی و ارائهٔ ارزش کم به کاربران، خواه از طریق اتوماسیون، تلاش انسانی یا ترکیبی از آنها ساخته شده باشد.
منابع بررسیشده در ۱۷ آگوست ۲۰۲۶: بهترین شیوههای سئوی تصویر · فراداده تصویر · Sitemapهای تصویر · بهینهسازی LCP · سیاستهای هرزنامهٔ جستوجو · مادهٔ ۵۰ قانون هوش مصنوعی · Mermaid · اسناد API و MCP پکسفی. زمانبندیهای جستوجو (۱۴۷ میلیثانیه، ۱۴۴ میلیثانیه) و هر عکس نمایشدادهشده، پاسخهای واقعی API هستند که در همان روز ثبت شدهاند.
پرسشهای پرتکرار
چگونه مقالههای نوشتهشده توسط یک مدل زبانی را بهطور خودکار تصویرسازی کنم؟
GET /api/v1/search/photos، تقریباً ۱۵۰ میلیثانیه). ورودیهای نمودار و دیاگرام به مدلی فرستاده میشوند که کد رسم یا Mermaid مینویسد و بهصورت قطعی رندر میشود. هرگز عنوان مقاله را به جستوجوی تصویر ندهید: عنوانها انتزاعی هستند و هیچ عکسی آنها را به تصویر نمیکشد.آیا یک سایت مستندات باید از اسکرینشات یا عکسهای استوک استفاده کند؟
چگونه از قرار گرفتن اعداد اشتباه در یک نمودار توسط پایپلاین هوش مصنوعی جلوگیری کنم؟
data ورودی نمودار کپی کند. سپس پیش از رندر، آن را در کد اعتبارسنجی کنید — برای هر مقدار، بررسی کنید که رشتهی آن در مقاله وجود دارد و در غیر این صورت خطا صادر کنید. تنها نه خط کد، و نموداری که با پاراگراف خودش تناقض دارد هرگز نمیتواند به دست خواننده برسد.پایپلاین خودکار باید چه مارکآپ تصویری برای سئو تولید کند؟
alt توصیفی که در بافت پاراگراف نوشته شده — گوگل متن جایگزین را مهمترین متادیتای تصویر میداند و در برابر استفادهی افراطی از کلمات کلیدی هشدار میدهد. width و height روی هر تصویر، با fetchpriority="high" روی تصویر اصلی و هرگز loading="lazy" روی آن، چون تصویر LCP نباید تنبل بارگذاری شود. و دادهساختاریافتهی ImageObject با contentUrl، creator، creditText و license، که همان چیزی است که یک تصویر را واجد شرایط نشان Licensable در گوگل ایمیجز میکند.آیا تصویرسازی مقالههای نوشتهشده با هوش مصنوعی به رتبهی آنها کمک میکند؟
چطور مرحلهٔ تصویرسازی را در سیآیای اجرا کنم بدون اینکه کنترل ویرایشی را از دست بدهم؟
photo_id، تضمین اینکه هر عددی که رسم شده در مقاله هم ظاهر میشود، و شکست قطعی زمانی که هیچ عکسی از آستانهٔ امتیاز عبور نمیکند. هیچ تصویر اصلی بهتر از یک تصویر نادرست است.