টেক্সট সহজ অংশ: এআই-লিখিত আর্টিকেলে ছবি বসানোর পাইপলাইন

টেক্সট জেনারেট করা সমাধান হয়ে গেছে। ছবি বসানো এখনও নয়। এন্ড-টু-এন্ড পাইপলাইন — রাউটার প্রম্পট, ফটো সার্চ, Mermaid ডায়াগ্রাম, যাচাইকৃত চার্ট, এবং alt টেক্সট, ক্রেডিট ও ImageObject মার্কআপ যা একে SEO-তে রূপান্তরিত করে।

শেয়ার করুন
দুটি মনিটরে কোডে ভরা একটি ডেস্কে টাইপ করছেন একজন প্রোগ্রামার, রঙিন ল্যাম্পের আলোয়।
ছবি সৌজন্যে Unsplash

আপনি একজন ডেভেলপার, যার সামনে একটি কনটেন্ট টার্গেট আছে: মাসে কয়েক ডজন আর্টিকেল, হয়তো কয়েকশোও। লেখা-মডেল ড্রাফট, আউটলাইন, মেটা ডেসক্রিপশন, ইন্টারনাল লিংক সামলায়। তারপর পাইপলাইনটি এমন এক ধাপে থমকে যায় যার নিজস্ব কোনো মডেল নেই — ছবি — এবং থেমে যায়। এটা এই কারণে নয় যে ছবি পাওয়া কঠিন, বরং এই কারণে যে স্ট্যাকের কোনো অংশ জানে না কোন ছবি, কোথা থেকে, কোন লাইসেন্সে, কীভাবে বর্ণিত।

এই আর্টিকেলটি সেই হারিয়ে যাওয়া ধাপ, শুরু থেকে শেষ পর্যন্ত সংযুক্ত: কোন ধরনের সেকশনের জন্য কোন ধরনের ভিজ্যুয়াল তৈরি করতে হবে, যে প্রম্পট একটি সম্পূর্ণ ড্রাফটকে সার্চ ব্রিফে রূপান্তরিত করে, যে API কল ছবি ফিরিয়ে দেয়, দ্বিতীয় মডেল যা একটি ফটোগ্রাফ যা বলতে পারে না তা আঁকে, এবং অ্যাসেম্বলার যা এমন মার্কআপ তৈরি করে যা Google আসলে পড়তে পারে। শেষে আছে দুটি সম্পূর্ণ ওয়ার্কফ্লো, চুরি করার জন্য তৈরি।

টেক্সট সমাধান হয়ে গেছে। ইলাস্ট্রেশনেই আটকায়।

একটি স্বয়ংক্রিয় আর্টিকেল পাইপলাইনের সৎ অডিট করুন। আউটলাইন: সমাধান হয়ে গেছে। ড্রাফট: সমাধান হয়ে গেছে। টাইটেল, মেটা ডেসক্রিপশন, স্কিমা, ইন্টারনাল লিংক, অনুবাদ: সবই সমাধান হয়ে গেছে, একই মডেল দিয়ে, সবই টেক্সটে। তারপর:

পাইপলাইন ধাপ স্ট্যাটাস আসলে কী আটকায়
আউটলাইন ও ড্রাফট সমাধান একটি মডেল কল, একটি প্রম্পট
টাইটেল, মেটা, স্কিমা, লিংক সমাধান টেক্সট ইন, টেক্সট আউট
হিরো ইমেজ আটকে আছে একটি বাস্তব ফাইল, একটি লাইসেন্স, ডাইমেনশন এবং alt টেক্সট প্রয়োজন — এর কোনোটিই টেক্সট মডেল তৈরি করতে পারে না
সেকশন ইমেজ আটকে আছে প্রতি আর্টিকেলে তিন থেকে পাঁচটি, প্রতিটি আলাদা, সাইটের অন্য কোথাও পুনরাবৃত্তি হবে না
চার্ট ও ডায়াগ্রাম আটকে আছে নির্ভুল হতে হবে — এমন একটি ভিজ্যুয়াল যা একটি সার্চ ফিরিয়ে দিতে পারে না এবং একটি জেনারেটরের বানানো উচিত নয়

এই ব্যর্থতা নান্দনিক নয়, এটা কাঠামোগত: আর্টিকেলটি একটি স্টক প্লেসহোল্ডার নিয়ে প্রকাশিত হয়, বা গত বারো বারের মতো একই ছবি নিয়ে, বা এমন একটি জেনারেটেড ইমেজ নিয়ে যার ছয়-আঙুলের হাত পাঠকের চোখে প্রথমেই পড়বে। এবং ছবি সাজানোর জিনিস নয় — Google-এর নিজস্ব ইমেজ ডকুমেন্টেশন সরল ভাষায় বলে: alt টেক্সট “একটি ইমেজের জন্য মেটাডেটা প্রদান করার সবচেয়ে গুরুত্বপূর্ণ অ্যাট্রিবিউট”, এবং নির্দেশনা হল CSS ব্যাকগ্রাউন্ডের বদলে বর্ণনামূলক alt-সহ বাস্তব <img> এলিমেন্ট ব্যবহার করা, যাতে ছবিটি খুঁজে পাওয়া এবং বোঝা যায়।1

চার ধরনের ভিজ্যুয়াল, চার ধরনের মডেল

সবচেয়ে বড় ডিজাইন ভুল হল “ছবি”-কে একটি সমস্যা ভাবা, যার একটাই প্রোভাইডার আছে। আসলে এটা চারটি সমস্যা, এবং এদের মধ্যে রাউটার হল প্রম্পটের একটি লাইন, কোনো সার্ভিস নয়:

সেকশনটির প্রয়োজন… এটি তৈরি করুন এভাবে অন্যগুলো কেন না
বাস্তব জগতের একটি দৃশ্যহিরো, মানব-পরিস্থিতি, স্থান, বস্তু, ভঙ্গি সিমানটিক ফটো সার্চ (Pexafy) একটি জেনারেটর বিস্তারিত বিষয় বানিয়ে ফেলে; একটি চার্টে প্লট করার কিছু থাকে না
আপনার হাতে থাকা সংখ্যাবেঞ্চমার্ক, প্রাইসিং, সার্ভে ফলাফল, লেটেন্সি প্লটিং কোড লেখা একটি মডেল, স্যান্ডবক্সে চালিত একটি ইমেজ মডেলকে কোনো ভ্যালুর জন্য বিশ্বাস করা যায় না; একটি ছবি ডেটা বহন করতে পারে না
একটি স্ট্রাকচার বা ফ্লোআর্কিটেকচার, সিকোয়েন্স, স্টেট মেশিন Mermaid / Graphviz লেখা একটি মডেল, ডিটারমিনিস্টিকভাবে রেন্ডার করা ফ্রি ফটো লাইব্রেরিতে আপনার সিস্টেমের ডায়াগ্রাম থাকে না
আপনার প্রোডাক্ট স্ক্রিনেডকস, চেঞ্জলগ, টিউটোরিয়াল একটি স্ক্রিপ্টেড ব্রাউজার স্ক্রিনশট (Playwright) এমন একটি UI অন্য কিছু দেখাতে পারে না যা শুধু আপনার বিল্ডে থাকে

আর জেনারেটেড ইলাস্ট্রেশন? এর জন্য একটাই সৎ জায়গা থাকে: যে দৃশ্য ছবি তোলা সম্ভব নয় এবং যা ডেটাও নয় — একটা বিমূর্ত মেকানিজম, এমন একটা প্রোডাক্ট যা এখনও তৈরি হয়নি, অথবা আপনার নিজস্ব ইলাস্ট্রেশন স্টাইলের বাড়ি। (জেনারেশনের চেয়ে আসল ফটোগ্রাফির পক্ষে পূর্ণ যুক্তি — স্কেলে গতি, নির্ভুলতা, একঘেয়েমির সমস্যা — এখানে দেওয়া আছে।) গতিপথের দিকে তাকিয়ে দাম নির্ধারণ করুন: ২ আগস্ট ২০২৬ থেকে, EU AI Act-এর Article 50 অনুযায়ী জেনারেটিভ সিস্টেমের প্রোভাইডারদের সিন্থেটিক আউটপুট একটি মেশিন-রিডেবল ফরম্যাটে চিহ্নিত করতে হবে।2 এটা AI প্রোভাইডার ও ডিপ্লয়ারদের উপর একটা বাধ্যবাধকতা, ব্লগ কী প্রকাশ করতে পারবে তার নিয়ম নয় — কিন্তু এই কারণেই আপনার আর্টিকেলের শীর্ষে থাকা ছবিটির উৎস ক্রমশ এমন কিছুতে পরিণত হচ্ছে যা পাঠক বিশ্বাসের উপর নির্ভর না করে যাচাই করতে পারে।

পাইপলাইন, শুরু থেকে শেষ

পাঁচটি ধাপ। কেবল ধাপ ৩ কোনো ইমেজ 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
└───────────────────────────────────────────────────────────────┘

ডায়াগ্রামের তুলনায় দুটি বৈশিষ্ট্য বেশি গুরুত্বপূর্ণ। ধাপ ২ একটি রাউটার: এটি প্রতিটি স্লটের জন্য সিদ্ধান্ত নেয় কোন প্রোডিউসার চলবে, তাই আপনি কখনোই একটি ফটো লাইব্রেরিকে বার চার্ট চাইবেন না। এবং ধাপ ৫ হল সেই জায়গা যেখানে SEO বসবাস করে — ইমেজ সম্পর্কে Google যা কিছু নথিভুক্ত করে (বর্ণনামূলক 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: install ৪৮০ s, compile ১০৮০ s, test ৭২০ s, upload ১২০ s
“আমরা গ্রাফটি কীভাবে ভাগ করেছি” diagram জব ডিপেন্ডেন্সি গ্রাফের flowchart LR
“আমরা কী বদলিয়েছি, এবং আবার কী করব” photo “দুইজন ইঞ্জিনিয়ার একটি হোয়াইটবোর্ডের সামনে দাঁড়িয়ে আছে যা ডায়াগ্রামে ঢাকা, একসঙ্গে একটি সমস্যা সমাধান করছে”

ধাপ ৩: ছবিগুলো ক্রেডিটসহ ফিরে আসে

প্রতিটি kind: "photo" এন্ট্রি একটি রিকোয়েস্ট। উপরের হিরো ব্রিফটি, পাবলিক API-এর বিরুদ্ধে চালিয়ে, ১৪৭ ms-এ এটি ফিরিয়ে দেয়:

GET /search/photos — হিরো ব্রিফ · “একজন ডেভেলপার রাতে একটি ডেস্কে কাজ করছে দুটি মনিটর নিয়ে…” · ১৪৭ ms
ইঞ্জিন অর্থ অনুসারে র‍্যাংক করে, তাই দীর্ঘ বাক্যটি সেটটি খালি না করে সংকুচিত করে। এই ঠিক সার্চটি চালান →

সর্বশেষ সেকশন ব্রিফ, একটি সম্পূর্ণ আলাদা দৃশ্য, ১৪৪ ms-এ:

GET /search/photos — সেকশন ব্রিফ · “দুইজন ইঞ্জিনিয়ার একটি হোয়াইটবোর্ডের সামনে দাঁড়িয়ে ডায়াগ্রামে ঢাকা…” · ১৪৪ ms
একই আর্টিকেল, একই রান, এমন একটি দৃশ্য যা কেউ হিরোর সাথে গুলিয়ে ফেলবে না — কারণ ব্রিফটি প্রতি আর্টিকেলে নয়, প্রতি সেকশনে লেখা হয়েছিল। এটাও চালান →

প্রতিটি ছবির সাথে যা ফিরে আসে তা হল সেই অংশ যা ধাপ ৫-কে সম্ভব করে তোলে — কেবল একটি ফাইল নয়:

একটি ফলাফল, অ্যাসেম্বলার যে ফিল্ডগুলো নেয় সেগুলোতে ছোট করা
{
  "photo_id":  "019e1c7f-0063-759e-b498-33ce1714e6c9",   // store it: no repeats
  "urls": { "small": "…?w=400", "regular": "…?w=1080",
             "large": "…?w=1920" },
  "width": 3000, "height": 1688,          // → no layout shift
  "blur_hash": "LJ8gjv9rVq-6OFxanNNFI7xco$Na",   // → real 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 (…)" }
}

ধাপ ৪: যা একটি ফটোগ্রাফ বলতে পারে না

পরিকল্পনার দুটি স্লট সার্চযোগ্য নয়, এবং এটাই সেই জায়গা যেখানে একটি দ্বিতীয় মডেল তার স্থান অর্জন করে — একটি ছবি আঁকার জন্য নয়, বরং এমন কোড লেখার জন্য যা তা আঁকে। এই পার্থক্যটি গুরুত্বপূর্ণ: কোড রিভিউযোগ্য, ডিটারমিনিস্টিক, এবং একটি বার-এর উচ্চতা নিয়ে হ্যালুসিনেট করতে পারে না।

ডায়াগ্রাম: টেক্সট ইন, SVG আউট

Mermaid একটি প্লেইন-টেক্সট সংজ্ঞা থেকে ডায়াগ্রাম রেন্ডার করে,3 যা এটিকে একটি মডেলের জন্য সবচেয়ে নিরাপদ টার্গেট করে তোলে: আউটপুটটি পরীক্ষাযোগ্য, git-এ ডিফযোগ্য, এবং প্রতিবারই একই ভাবে রেন্ডার হয়। রাউটার ইতিমধ্যে স্পেকটি ফিরিয়েছে।

diagram.sh — মডেলটি স্পেক লিখেছে, CLI সেটি রেন্ডার করে
# the "spec" field of a diagram entry, written to build/graph.mmd
cat build/graph.mmd
flowchart LR
  install["install deps · 480s"] --> compile["compile · 1080s"]
  compile --> test["test suite · 720s"]
  compile --> upload["upload artifacts · 120s"]

npx -y @mermaid-js/mermaid-cli -i build/graph.mmd -o static/img/graph.svg
# → an SVG you can review in the PR, not a picture you have to trust

চার্ট: কেবল সেই সংখ্যাগুলো যা আর্টিকেলে আগে থেকেই আছে

একই নীতি, একটি বাড়তি সতর্কতা। রাউটার ড্রাফট থেকে মানগুলো কপি করেছে; মডেল প্লটিং কোড লেখে; কোড একটি স্যান্ডবক্সে চলে; অ্যাসেম্বলার চার্ট কোনো পেজের কাছে যাওয়ার আগে রেন্ডার করা মানগুলো উৎস সংখ্যার বিরুদ্ধে পুনরায় যাচাই করে।

chart.py — ট্রান্সক্রাইব করা ডেটা প্লট করুন, তারপর যাচাই করুন
import re, matplotlib
matplotlib.use("Agg")                # headless: no display in CI
import matplotlib.pyplot as plt

def assert_in_draft(value, draft: str) -> None:
    """A plotted number must appear in the article AS A NUMBER."""
    # Substring matching is the trap here: "120" is inside "1200", and
    # inside "?w=1200" — a naive `str(v) in draft` passes on anything.
    # Match on word boundaries, and accept 1 234 / 1,234 / 1234.
    body = re.sub(r"(?<=\d)[  ,](?=\d{3}\b)", "", draft)   # strip 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)       # hallucinated value → no 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)                    # deterministic, reviewable artefact
    return out

সতর্কতাটি ছোট, কিন্তু সেটা সাবধানে লিখুন: একটি সাবস্ট্রিং চেক কাজ করে না। "120" in draft সত্য হয়ে যায় এমন একটি আর্টিকেলের জন্য যাতে 1200 বা ?w=1200 আছে, তাই নাইভ ভার্সনটি সবকিছুর ওপর পাস করে যায় এবং কিছুই রক্ষা করে না। ডিজিট বাউন্ডারিতে অ্যাংকর করুন, হাজারের সেপারেটর নরমালাইজ করুন, এবং যে ধরনের ভুল একটি টেকনিক্যাল আর্টিকেলকে কমেন্টে টুকরো টুকরো করে দেয় — একটি চার্ট নিজের প্যারাগ্রাফের বিপরীত কথা বলছে — সেটি প্রোডাকশনে পৌঁছাতে পারে না। স্ক্রিনশটগুলো একই নীতি অনুসরণ করে: আপনার আসল বিল্ডের বিরুদ্ধে একটি স্ক্রিপ্টেড page.screenshot() হল আপনার নিজের UI-এর একমাত্র সত্যতার উৎস, এবং UI পরিবর্তন হলেও এটি সত্য থেকে যায়।

ধাপ ৫: অ্যাসেম্বলি এখানেই SEO বাস করে

এখন পর্যন্ত সবকিছু ফাইল ও ফিল্ড তৈরি করেছে। এই ধাপ সেগুলোকে মার্কআপে রূপান্তরিত করে — এবং এখানে সুনির্দিষ্ট হওয়া উপযোগী, কারণ এই কয়েকটি লাইনে তিনটি নথিভুক্ত আচরণ নির্ধারিত হয়।

হিরো, সার্চ রেসপন্স থেকে ইমিট করা — কিছুই বানানো হয়নি
<!-- The bytes come from another origin: pay the handshake early -->
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>

<!-- LCP element: never lazy, always high priority -->
<figure>
  <img src="{urls.regular}"
       width="{width}" height="{height}"        <!-- kills layout shift -->
       alt="{alt}"                                <!-- written AFTER the pick -->
       fetchpriority="high" decoding="async"
       style="background:{color_hex}">   <!-- dominant colour, 1 field -->
  <figcaption>{attribution.html}</figcaption>
</figure>

<!-- Section images, below the fold: the opposite settings -->
<img src="{urls.regular}" width="{width}" height="{height}"
     alt="{alt}" loading="lazy" decoding="async">

হটলিংক বা রি-হোস্ট? উপরের স্নিপেটটি হটলিংক করে, যা পাঠানোর সবচেয়ে দ্রুত উপায় এবং preconnect-এর কারণ: একটি রিমোট হিরোর জন্য ক্রিটিকাল পাথে একটি DNS লুকআপ এবং একটি TLS হ্যান্ডশেক খরচ হয়, এবং এটা fetchpriority দিয়ে আপনি যে সুবিধা কিনলেন তা খেয়ে ফেলতে পারে। রি-হোস্টিং তৃতীয় পক্ষের উৎসকে সম্পূর্ণ বাদ দেয়, আপনাকে আপনার নিজের ব্রেকপয়েন্টে AVIF/WebP পরিবেশন করতে দেয়, এবং একটি আপস্ট্রিম URL পরিবর্তন হলেও টিকে থাকে — স্টোরেজ, পাইপলাইনে একটি ফেচ ধাপ এবং আপনার নিজের CDN বিলের বিনিময়ে। যাই বেছে নিন, color_hex আপনাকে একটি ফিল্ডের জন্য একটি প্লেসহোল্ডার দেয় (একটি ব্লার হ্যাশ আরও সুন্দর, কিন্তু এটি প্রথমে একটি ডেটা URI-তে ডিকোড করতে হয় — এটা CSS রঙ নয়)। একটি রুক্ষ হিরো সরাসরি background:-এ কপি করাটাই সেই জায়গা যেখানে অধিকাংশ পাইপলাইন নিঃশব্দে একটি খালি ধূসর বক্স পাঠায়।

  1. হিরোকে কখনো লেজি-লোড করবেন না। web.dev স্পষ্ট: “আপনার LCP ইমেজ কখনো লেজি-লোড করবেন না, কারণ তা সর্বদা অপ্রয়োজনীয় রিসোর্স লোড ডিলে সৃষ্টি করবে, এবং LCP-তে নেতিবাচক প্রভাব ফেলবে”, এবং এটি প্রস্তাব করে যে যে এলিমেন্ট সম্ভবত LCP এলিমেন্ট হবে তার ওপর fetchpriority="high" ব্যবহার করুন — সংযতভাবে, একটি ছবিতে।4 এমন একটি পাইপলাইন যা প্রতিটি ছবিতে loading="lazy" স্ট্যাম্প করে, হিরো-সহ, তা স্বয়ংক্রিয় প্রকাশনায় সবচেয়ে সাধারণ স্ব-আরোপিত Core Web Vitals ক্ষত।
  2. সব সময় width ও height ইমিট করুন — এগুলো রেসপন্সে ফিরে আসে, তাই কোনো অজুহাত নেই; সেই একক অ্যাট্রিবিউট জোড়াটিই ব্রাউজারকে জায়গা রিজার্ভ করতে দেয় এবং লেআউট জাম্প করা বন্ধ করে। ফাইল লোড হওয়ার সময় প্লেসহোল্ডার হিসেবে blur_hash ব্যবহার করুন।
  3. নির্বাচনের পরে alt লিখুন, কখনো আগে না। এটা সূক্ষ্ম বিষয়। পরিকল্পনার alt ফিল্ড সেই দৃশ্যটি বর্ণনা করে যা আপনি চেয়েছিলেন; আপনি যে ছবিটি পেয়েছেন সেটি সবচেয়ে কাছের মিল, সেই দৃশ্য নয়। ব্রিফের টেক্সট alt হিসেবে পাঠানোই ঠিক সেই অ্যাক্সেসিবিলিটি ব্যর্থতা যা এই পাইপলাইনটির এড়ানো উচিত — এমন একটি ছবির বর্ণনা যা পেজে নেই। নির্বাচিত ছবির alt_description থেকে alt তৈরি করুন, যে প্যারাগ্রাফে এটি বসেছে তার বিরুদ্ধে পরিমার্জিত করুন। Google-এর নির্দেশনা হল “উপযোগী, তথ্যে পূর্ণ কনটেন্ট তৈরির ওপর ফোকাস করুন যা কিওয়ার্ড উপযুক্তভাবে ব্যবহার করে এবং পেজের কনটেন্টের প্রসঙ্গের সাথে থাকে”, এবং এটি সতর্ক করে দেয় যে alt অ্যাট্রিবিউটে কিওয়ার্ড ঠেসে দেওয়ার ফলে “একটি নেতিবাচক ব্যবহারকারী অভিজ্ঞতা তৈরি হয় এবং আপনার সাইটকে স্প্যাম হিসেবে দেখা হতে পারে”।1

যে অংশ প্রায় কেউ স্বয়ংক্রিয় করে না: লাইসেন্স মেটাডেটা

Google ইমেজ লাইসেন্সিংয়ের জন্য ImageObject স্ট্রাকচার্ড ডেটা সমর্থন করে। এর জন্য contentUrl প্রয়োজন এবং সাথে creator, creditText, copyrightNotice বা license-এর অন্তত একটি, এটি acquireLicensePage প্রস্তাব করে, এবং লাইসেন্স তথ্য থাকা ছবিগুলো Google Images-এ Licensable ব্যাজের জন্য যোগ্য হতে পারে।5 এই ফিল্ডগুলোর প্রতিটিই সার্চ রেসপন্সে ইতিমধ্যে আছে — তাই সেগুলো ইমিট করা একটি টেম্প্লেট, প্রজেক্ট নয়:

ImageObject JSON-LD, API রেসপন্স থেকে পূরণ করা
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "ImageObject",
  "contentUrl": "{urls.large}",                 // required
  "creator": { "@type": "Person",
                "name": "{photographer_full_name}" },
  "creditText": "{photographer_full_name} on {source}",
  "license": "{LICENSE_URL[source]}",           // the library's own page
  "acquireLicensePage": "{source_image_url}"     // the photo's page
}
</script>

# LICENSE_URL maps the `source` field to the licence that actually governs
# the photo — unsplash.com/license, pexels.com/license, pixabay.com/…

একটি বিস্তারিত বিষয় যা সঠিকভাবে করা জরুরি: license-এর সেই লাইসেন্সের দিকেই নির্দেশ করা উচিত যা সেই ছবিকে নিয়ন্ত্রণ করে — উৎস লাইব্রেরির নিজস্ব লাইসেন্স পেজ — আপনার ডোমেনের কোনো সারাংশ পেজ নয়। Google এটি পড়ে ব্যাজ যোগ্যতা নির্ধারণ করে, এবং একটি সেল্ফ-রেফারেন্সিয়াল URL একটি সংকেত হিসেবে দুর্বল, এবং নিজের কাছে ফিরে আসা লিংক ছাড়া অন্য কিছু হিসেবে রক্ষা করা কঠিন। পাঠকদের জন্য আপনার নিজের লাইসেন্স সারাংশ একটি ইন্টারনাল পেজ হিসেবে রাখুন; মার্কআপে ক্যানোনিকালটি রাখুন।

আর যখন আপনি অ্যাসেম্বলারে আছেন: ফাইলটিকে IMG_0042.jpg-এর চেয়ে একটি ছোট বর্ণনামূলক নাম দিন, এবং ছবিটি একটি সাইটম্যাপে যোগ করুন — Google-এর ইমেজ সাইটম্যাপ ফরম্যাট প্রতি পেজ URL-এ ১,০০০টি পর্যন্ত ছবি গ্রহণ করে।6 দুটোই একটি পাইপলাইনে এক লাইন এবং কোনোটাই হাতে করা হয় না।

চুরি করার জন্য দুটি পাইপলাইন

একই পাঁচটি ধাপ, দুটি সম্পূর্ণ আলাদা গঠন — একটি আপনি লেখা আর্টিকেলের জন্য, একটি একটি চলমান প্রোডাক্টের সাথে মিলতে হবে এমন ডকুমেন্টেশনের জন্য। যেটার ব্যর্থতার ধরন আপনি চিনতে পারেন সেটা বেছে নিন।

১ · CI-তে ডেভ ব্লগ — রেপোতে Markdown

আর্টিকেলগুলো Markdown হিসেবে থাকে, ছবিগুলো তাদের পাশে কমিট করা থাকে, এবং পুরো ব্যাপারটি পুশে চলে। ডিটারমিনিস্টিক, PR-এ রিভিউযোগ্য, কোনো 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   # images land in the PR
        with: { commit-message: "chore(content): illustrate" }

একজন মানুষ এখনও PR অনুমোদন করে, যেটাই এখানকার মূল বিষয়: পাইপলাইন প্রস্তাব করে, রিভিউয়ার সিদ্ধান্ত নেয়, এবং এটি যে ফ্রন্ট-ম্যাটার লিখেছে তা ডিফযোগ্য।

সার্চের অর্ধেকটা একটাই GET — রিকোয়েস্ট, এর score_threshold এবং এটি যেসব ফিল্ড ফেরত দেয় তা লাইন ধরে ধরে একক-আর্টিকেল গাইডে লেখা আছে, তাই এখানে তা পুনর্মুদ্রণের বদলে ইমপোর্ট করা হয়েছে। এই ফাইলটি যা যোগ করে তা হলো একটা প্ল্যান-এর জন্য প্রয়োজনীয় সবকিছু, যা একটা মাত্র ফটোর জন্য দরকার হয় না: এমন একটা ব্রিফের জন্য রিট্রাই যা কিছুই ফেরত দেয়নি, একটা photo_id-এর উপর দাবি যাতে দুটো পেজ একই ছবি শেয়ার না করে, এবং ফিরে আসা ফটো থেকে লেখা alt।

plan_to_pr.py — গ্লু: ভিজ্যুয়াল প্ল্যান ইনপুট, ফ্রন্ট-ম্যাটার আউটপুট
import frontmatter
from photo_search import search   # একটাই GET /search/photos; `used`-এ থাকা id বাদ দেয়

def find_photo(entry: dict, used: set) -> dict | None:
    """Search; if the brief was too specific, widen it once, then give up."""
    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"])   # claim it: no repeats site-wide
            return photo
    return None                        # caller decides: skip the slot, or fail

def widen(query: str) -> str:
    """Drop the last clause — usually the over-specific one."""
    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:                    # no hero is better than a bad one
        raise SystemExit(f"{path}: no photo above threshold — rewrite the brief")

    post["hero"] = {                    # everything the template needs
        "src": hero["urls"]["regular"], "w": hero["width"], "h": hero["height"],
        # The alt describes the photo we GOT, never the scene we asked for.
        "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 as the base, trimmed to ~125 chars for screen readers.
    Send it back through the model with the paragraph if you want better."""
    base = photo.get("alt_description") or photo.get("description", "")
    return base[:125].rstrip(" ,;")

২ · ডকস ও চেঞ্জলগ — প্রথমে স্ক্রিনশট, শেষে ছবি

রাউটারের ডিফল্টগুলো উল্টে দিন। প্রোডাক্ট ডকুমেন্টেশনে সৎ ভিজ্যুয়াল প্রায় সবসময়ই আপনার নিজের UI: একটি Playwright স্ক্রিপ্ট যা আসল বিল্ড খোলে, একটি স্থির ভিউপোর্ট সেট করে এবং প্যারাগ্রাফটি ঠিক যে অবস্থা বর্ণনা করে সেটাই ক্যাপচার করে। ডায়াগ্রামগুলো আর্কিটেকচার পেজগুলো কভার করে, এবং ফটোগ্রাফ কেবল কনসেপচুয়াল ও ল্যান্ডিং পেজগুলোতে দেখা যায় — যেখানে একটি স্ক্রিনশট কিছুই বলবে না।

shots.py — স্ক্রিনশট জেনারেট করা হয়, কখনো বর্ণনা করা হয় না
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()
# Runs in the same CI job as the docs build → the screenshot can never
# describe a version of the UI that no longer exists.

দুটি ভ্যারিয়েন্ট উল্লেখ করা উপযোগী, যদিও তাদের নিজস্ব রেসিপি লাগে না। প্রোগ্রামেটিক SEO লুপটি উল্টে দেয়: একটি ডেটাবেস থেকে তৈরি শত শত পেজের সাথে আপনি প্রতি পেজে সার্চ করেন না — একটি রিকোয়েস্ট পর্যন্ত ১০০টি ছবি ফিরিয়ে দেয়, তাই আপনি প্রতি টপিক ক্লাস্টারে সার্চ করেন এবং একটি পুল থেকে বরাদ্দ করেন, যেখানে একটি ইউনিকনেস কনস্ট্রেইন্ট ডিডুপ্লিকেশন করে (সম্পূর্ণ আর্কিটেকচারটি)। এবং নিউজলেটার ও সোশ্যাল কার্ডে একই ছবি চারটি ক্রপে প্রয়োজন হয়: photo_id রেখে দিন, urls থেকে আপনার প্রয়োজনীয় সাইজ চান, এবং যখন একটি ক্রপ আসলেই ব্যর্থ হয় তখনই আবার সার্চ করুন।

একই পাইপলাইন, একটি এজেন্ট হিসেবে

যদি একটা মডেল ইতিমধ্যে খসড়া লিখছে, তাহলে সবচেয়ে ছোট পথ হলো প্রসেসের মধ্যে JSON আদান-প্রদান না করে সরাসরি এটিকে সার্চ টুল দিয়ে দেওয়া। Pexafy mcp.pexafy.com/mcp-এ একটি হোস্টেড Model Context Protocol সার্ভার চালায়। কানেক্টর সেটআপ এবং এটি যেসব টুল দেয়, তা অন্যত্র আলোচনা করা হয়েছে — ডেস্কটপ ও এডিটর সেটআপ একক-আর্টিকেল গাইডে, CI রানারের জন্য হেডলেস ভ্যারিয়েন্ট স্কেলে কনটেন্ট নিয়ে আর্টিকেলে, এবং বৃহত্তর কানেক্টর মার্কেট দেখতে কেমন — কে কী শর্তে MCP ইমেজ সার্ভার সরবরাহ করে — তা AI এজেন্টদের জন্য ইমেজ সার্চ ইনফ্রাস্ট্রাকচার নিয়ে করা স্টাডিতে আছে। এখানে দেখানোর মতো বিষয় হলো: এজেন্টের হাতে টুলগুলো এসে গেলে এই পাইপলাইনের কী হয় — এটি আর পাঁচটা ধাপ থাকে না, একটামাত্র নির্দেশনা হয়ে যায়।

একটি নির্দেশ, পুরো পরিকল্পনা কার্যকর
You  Here is the draft. Build the visual plan, illustrate it, and open a PR.
     Charts only from numbers already in the text.

Agent  → plan: hero=photo · §2=chart · §3=diagram · §4=photo
      → search_photos(q="a developer working late at a desk with two
         monitors and a mechanical keyboard in a dark room…")
      ← 16 photos · 147 ms · picked #1, 3000×1688, credited
      → mermaid-cli build/graph.mmd → static/img/graph.svg
      → chart.py §2 → 4 values checked against the draft ✓
      → search_photos(q="two engineers standing at a whiteboard…")
      ← 16 photos · 144 ms · picked #1

      ✓ 4 slots filled · 2 API calls · alt + credit + ImageObject written
      ⚠ §5 returned nothing above 0.5 — brief too abstract, rewritten as
        "a person at a kitchen table checking figures on a laptop"

শেষ লাইনটিই সেই কারণ যার জন্য এই লুপে একটি খাঁটি স্ক্রিপ্টের বদলে একটি এজেন্ট রাখার মূল্য আছে: স্বয়ংক্রিয় ইলাস্ট্রেশনের ব্যর্থতার ধরন হল একটি খারাপ ব্রিফ, এবং একটি খারাপ ব্রিফ পুনর্লিখন করাটাই ঠিক একটি ভাষা মডেলের কাজ। ডিটারমিনিস্টিক অংশগুলো — বরাদ্দকরণ, ডিডুপ্লিকেশন, নিউমেরিক গার্ড — কোডে রাখুন।

একটি ইলাস্ট্রেটেড আর্টিকেলের খরচ কত

প্রতি আর্টিকেলে তিনটি ফটো স্লট মানে তিনটি সার্চ রিকোয়েস্ট, তাই ফ্রি প্ল্যান (5,000 requests/month) কভার করে প্রতি মাসে 1,666 আর্টিকেল, তাও পেমেন্টের কোনো প্রশ্ন ওঠার আগেই — এবং যদি আপনি প্রতি আর্টিকেলে না করে প্রতি টপিক ক্লাস্টারে সার্চ পুল করেন, তাহলে এই সীমা আরও এক অর্ডার অফ ম্যাগনিটিউড এগিয়ে যায়। প্ল্যান-অনুযায়ী বিস্তারিত হিসাব, এবং যে পুলিং আর্কিটেকচার একে অপ্রয়োজনীয় করে তোলে, তা আছে স্কেলে কনটেন্ট ইলাস্ট্রেট করা নিয়ে সহচর আর্টিকেলে।

প্রতি আর্টিকেলে দুটি মডেল কল — একটি ড্রাফটের জন্য, একটি ভিজ্যুয়াল পরিকল্পনার জন্য — কয়েক হাজার টোকেন হবে এবং পাইপলাইনে সবচেয়ে সস্তা লাইন হবে; Mermaid ও matplotlib রেন্ডারের খরচ শুধু CI সেকেন্ড। যে সংখ্যাটি মানুষকে চমকে দেয় তা হল কোন লাইনটি সস্তা নয়: প্রতি আর্টিকেলে চারটি ছবি জেনারেট করা, মাসে চারশো ছবি, সাথে যে প্রচেষ্টাগুলো টেকেনি — এবং আউটপুটটি তখনও কোনো ফটোগ্রাফার, কোনো তারিখ, কোনো উৎস URL বহন করে না।

এই পাইপলাইন যা ঠিক করে না

একটি সৎ সেকশন, কারণ যে ব্যর্থতা এটি রোধ করে তা খরচবহুল। একটি ভালোভাবে ইলাস্ট্রেটেড আর্টিকেল তবুও একটি আর্টিকেল: বাস্তব ফটোগ্রাফি, নির্ভুল চার্ট এবং সঠিক মার্কআপ এমন একটি পেজকে উন্নত করে যার থাকার যোগ্যতা আছে। এগুলো পাতলা, ব্যাপক-উৎপাদিত কনটেন্টকে র‍্যাংক করাতে পারে না। Google-এর স্প্যাম পলিসি স্কেল্ড কনটেন্ট অ্যাবিউজ-এর নাম দেয় — অনেক পেজ মূলত র‍্যাংকিং ম্যানিপুলেট করতে জেনারেট করা এবং ব্যবহারকারীদের কম মূল্য দেওয়া, অটোমেশন জড়িত থাকুক বা না থাকুক — এবং ইলাস্ট্রেশনের গুণমান সেই বিচারে একটি ফ্যাক্টর নয়।7

তাই যে ফ্রেমিং টিকে থাকে সেটা হল: এই পাইপলাইনটি একটি গুণমানের ফ্লোর যা আপনি নিয়ন্ত্রণ করেন, যা এমন পেজগুলোতে প্রয়োগ করা হয় যাদের প্রকাশিত হওয়ার একটি কারণ ইতিমধ্যেই আছে। যেখানে এটি প্রমাণযোগ্যভাবে ফল দেয়:

  • একটি পাঠক যাচাই করতে পারে এমন প্রোভিনেন্স। একটি আসল ফটোগ্রাফার ও একটি উৎস URL-সহ একটি ক্রেডিট লাইন এমন একটি দাবি যা যাচাই করা যায় — এবং একই ফিল্ডগুলো Google পড়া ImageObject মার্কআপে ফিড করে।
  • অ্যাক্সেসিবিলিটি ও Core Web Vitals। আসল alt টেক্সট, প্রতিটি ছবিতে ডাইমেনশন, একটি হিরো যা কখনো লেজি-লোড হয় না। প্রতিটি প্রকাশিত আর্টিকেল দিয়ে গুণ করুন এবং এটাই সাইটের ইমেজ-কোয়ালিটি স্টোরি।
  • যাচাইযোগ্য জায়গায় নির্ভুলতা। এমন একটি চার্ট যার সংখ্যাগুলো আর্টিকেলের বিরুদ্ধে যাচাই করা, চলমান বিল্ড থেকে জেনারেট করা একটি স্ক্রিনশট, PR-এ টেক্সট হিসেবে রিভিউযোগ্য একটি ডায়াগ্রাম — তিনটি ভিজ্যুয়াল যা সত্য থেকে বিচ্যুত হতে পারে না একটি টেস্ট ব্যর্থ না হয়ে।

অ্যাট্রিবিউশনের ক্ষেত্রে, পাইপলাইন-নির্দিষ্ট পয়েন্টটি সংকীর্ণ: সবসময় ক্রেডিট রেন্ডার করার পক্ষে যুক্তি অন্যত্র দেওয়া আছে, এবং একটা স্বয়ংক্রিয় অ্যাসেম্বলার যা যোগ করে তা হলো — যে attribution স্ট্রিং এটি প্রিন্ট করে সেটাই স্ট্রাকচার্ড ডেটার প্রয়োজনীয় creditText। একটা ফিল্ড, দুটো জায়গা, একই টেমপ্লেট পাসে নিঃসৃত — এই কারণেই একটা পাইপলাইনের এটি বাদ দেওয়ার অজুহাত একজন মানুষের চেয়েও কম থাকে।

কোথা থেকে শুরু করবেন

  1. রাউটার প্রম্পট যোগ করুন যা কিছু ইতিমধ্যেই আপনার ড্রাফট লিখছে তার সাথে, এবং JSON প্রিন্ট করুন কোনো ব্যবস্থা না নিয়ে। দশটি পরিকল্পনা পড়ুন। যদি ব্রিফগুলো দৃশ্যের বদলে টপিকের নাম দেয়, তবে কোনো ইন্টিগ্রেশন লেখার আগে প্রম্পটটি ঠিক করুন।
  2. কেবল ফটো স্লটগুলো সংযুক্ত করুন। প্রতি ব্রিফে একটি GET /search/photos, এবং প্রথম দিন থেকে photo_id সংরক্ষণ করুন — ৩,০০০ লাইভ পেজে পিছিয়ে গিয়ে ডিডুপ্লিকেশন বসানো একটি মাইগ্রেশন, কোনো কলাম নয়।
  3. একই কমিটে width, height ও alt ইমিট করুন। এটাই সবচেয়ে সস্তা Core Web Vitals কাজ যা আপনি কখনো করবেন।
  4. তারপর ধাপ ৪ যোগ করুন, চার্টের আগে ডায়াগ্রাম — Mermaid টেক্সট, তাই এটাই একমাত্র ব্যর্থতার ধরন যা সিনট্যাক্স ত্রুটির বাইরে যায় না।
  5. প্রথম চার্ট একজন পাঠকের কাছে পৌঁছানোর আগে নিউমেরিক গার্ড যোগ করুন, পরে নয়।

তথ্যসূত্র ও ফুটনোট

1 Google Search Central, Image SEO best practices: alt টেক্সট হল “একটি ইমেজের জন্য মেটাডেটা প্রদান করার সবচেয়ে গুরুত্বপূর্ণ অ্যাট্রিবিউট”; নির্দেশনা হল “উপযোগী, তথ্যে পূর্ণ কনটেন্ট” তৈরি করা যা কিওয়ার্ড উপযুক্তভাবে ব্যবহার করে এবং পেজের কনটেন্টের প্রসঙ্গের সাথে থাকে, কিওয়ার্ড-ঠাসা alt অ্যাট্রিবিউট এড়ানো, CSS ইমেজের বদলে HTML <img> এলিমেন্ট ব্যবহার করা, এবং ফাইলগুলোকে ছোট কিন্তু বর্ণনামূলক নাম দেওয়া।

2 EU AI Act, ধারা ৫০ — ২ আগস্ট ২০২৬ থেকে প্রযোজ্য স্বচ্ছতা বাধ্যবাধকতা: সিন্থেটিক ইমেজ, অডিও, ভিডিও বা টেক্সট জেনারেট করা সিস্টেমের প্রোভাইডারদের আউটপুট একটি মেশিন-রিডেবল ফরম্যাটে চিহ্নিত করতে হবে এবং সেগুলো কৃত্রিমভাবে জেনারেট করা হিসেবে সনাক্তযোগ্য করতে হবে। এটি AI প্রোভাইডার ও ডিপ্লয়ারদের আবদ্ধ করে; এটি কোন ইমেজ একটি ওয়েবসাইট প্রকাশ করতে পারবে সে বিষয়ে নিয়ম নয়।

3 Mermaid Markdown-অনুপ্রাণিত টেক্সট সংজ্ঞা থেকে ডায়াগ্রাম ও চার্ট রেন্ডার করে, যা আউটপুটটিকে রিভিউযোগ্য ও ডিটারমিনিস্টিক করে তোলে।

4 web.dev, Optimize Largest Contentful Paint: “যদি আপনি মনে করেন কোনো <img> এলিমেন্ট আপনার পেজের LCP এলিমেন্ট হতে পারে, তবে তাতে fetchpriority="high" সেট করা ভালো ধারণা”, সংযতভাবে ব্যবহৃত; এবং “আপনার LCP ইমেজ কখনো লেজি-লোড করবেন না, কারণ তা সর্বদা অপ্রয়োজনীয় রিসোর্স লোড ডিলে সৃষ্টি করবে, এবং LCP-তে নেতিবাচক প্রভাব ফেলবে।”

5 Google Search Central, Image metadata (structured data): ImageObject-এর জন্য contentUrl প্রয়োজন এবং সাথে creator, creditText, copyrightNotice বা license-এর অন্তত একটি; acquireLicensePage প্রস্তাবিত, এবং লাইসেন্স তথ্য বহনকারী ছবিগুলো Google Images-এ Licensable ব্যাজের জন্য যোগ্য হতে পারে।

6 Google Search Central, Image sitemaps: ইমেজ সাইটম্যাপ Google-কে একটি সাইটের ছবি সম্পর্কে জানায়, JavaScript-এর মাধ্যমে পাওয়া ছবিগুলোও ধরে, এবং প্রতি পেজ URL-এ ১,০০০টি পর্যন্ত ছবি গ্রহণ করে।

7 Google Search স্প্যাম পলিসি — scaled content abuse: অনেক পেজ মূলত র‍্যাংকিং ম্যানিপুলেট করতে জেনারেট করা এবং ব্যবহারকারীদের কম মূল্য দেওয়া, তা অটোমেশন, মানুষের প্রচেষ্টা বা দুটোর সংমিশ্রণে তৈরি হোক না কেন।

প্রায়শই জিজ্ঞাসিত প্রশ্ন

LLM-এর লেখা আর্টিকেলে কীভাবে স্বয়ংক্রিয়ভাবে ছবি যুক্ত করব?
লেখা এবং প্রকাশের মাঝে একটি মডেল কল যোগ করুন: এটিকে একটি ভিজ্যুয়াল প্ল্যান ফেরত দিতে বলুন — প্রতিটি ইমেজ স্লটের জন্য একটি এন্ট্রি, যা ফটো, চার্ট, ডায়াগ্রাম বা কিছুই না হিসেবে ট্যাগ করা। ফটো এন্ট্রিতে থাকে ১২-থেকে-২৫-শব্দের একটি বর্ণনা যা কোনো ক্যামেরা ধারণ করতে পারত এমন একটি দৃশ্যের, যা আপনি একটি সিমান্টিক ইমেজ সার্চ API-তে পাঠান (GET /api/v1/search/photos, প্রায় ১৫০ ms)। চার্ট এবং ডায়াগ্রাম এন্ট্রিগুলো এমন একটি মডেলে যায় যা প্লটিং কোড বা Mermaid লেখে, যা ডিটারমিনিস্টিকভাবে রেন্ডার করা হয়। আর্টিকেলের শিরোনাম কখনোই ইমেজ সার্চে দেবেন না: শিরোনাম বিমূর্ত, এবং কোনো ফটোগ্রাফ তা চিত্রিত করে না।
একটি ডকুমেন্টেশন সাইটে স্ক্রিনশট নাকি স্টক ফটো ব্যবহার করা উচিত?
রাউটারের ডিফল্ট সেটিংস উল্টে দিন: প্রোডাক্ট ডকুমেন্টেশনে সৎ ভিজ্যুয়াল প্রায় সবসময় আপনার নিজের UI-ই। একটি Playwright স্ক্রিপ্ট যা প্রকৃত বিল্ড খোলে, ভিউপোর্ট নির্দিষ্ট করে এবং প্যারাগ্রাফে বর্ণিত ঠিক সেই অবস্থা ক্যাপচার করে, তা docs বিল্ডের একই CI জবে চলে, ফলে স্ক্রিনশট কখনোই ইন্টারফেসের এমন কোনো সংস্করণ দেখাতে পারে না যা আর অস্তিত্বে নেই। ডায়াগ্রাম আর্কিটেকচার পেজগুলো বহন করে, আর ফটোগ্রাফ শুধু কনসেপচুয়াল ও ল্যান্ডিং পেজে দেখা যায়, যেখানে স্ক্রিনশট কিছুই বলবে না।
কীভাবে একটি AI পাইপলাইনকে চার্টে ভুল সংখ্যা বসানো থেকে আটকাবেন?
প্ল্যানকে উদ্ভাবন নয়, প্রতিলিপি করতে বাধ্য করুন: রাউটার শুধুমাত্র খসড়ায় ইতিমধ্যে বিদ্যমান মানগুলো চার্ট এন্ট্রির data ফিল্ডে কপি করতে পারবে। এরপর রেন্ডারিংয়ের আগে কোডে এটি যাচাই করুন — প্রতিটি মানের জন্য চেক করুন যে তার স্ট্রিং আর্টিকেলে উপস্থিত আছে কি না, না থাকলে এরর তুলুন। মাত্র নয় লাইন কোড, আর যে চার্ট নিজের প্যারাগ্রাফের সাথে সাংঘর্ষিক তা কখনো পাঠকের কাছে পৌঁছাতে পারবে না।
একটি স্বয়ংক্রিয় পাইপলাইনের SEO-র জন্য কোন ইমেজ মার্কআপ থাকা উচিত?
তিনটি জিনিস, সবগুলোই সার্চ রেসপন্সে ইতিমধ্যে থাকা ফিল্ড থেকে। প্যারাগ্রাফের প্রসঙ্গ অনুযায়ী লেখা একটি বর্ণনামূলক alt — Google alt টেক্সটকে সবচেয়ে গুরুত্বপূর্ণ ইমেজ মেটাডেটা বলে অভিহিত করে এবং কীওয়ার্ড স্টাফিং সম্পর্কে সতর্ক করে। প্রতিটি ছবিতে width ও height, হিরো ইমেজে fetchpriority="high" এবং কখনোই তাতে loading="lazy" নয়, কারণ LCP ইমেজ লেজি লোড করা উচিত নয়। এবং ImageObject স্ট্রাকচার্ড ডেটা যাতে থাকে contentUrl, creator, creditText ও license, যা একটি ছবিকে Google Images-এ Licensable ব্যাজের জন্য যোগ্য করে তোলে।
AI-লিখিত আর্টিকেলে ছবি যুক্ত করা কি র‍্যাংকিংয়ে সাহায্য করে?
একা এটি করবে না, এবং এখানে সুনির্দিষ্ট হওয়া দরকার। Google-এর স্প্যাম পলিসি স্কেলড কনটেন্ট অ্যাবিউজ-কে সংজ্ঞায়িত করে ব্যবহারকারীদের জন্য সামান্য মূল্য দিয়ে, র‍্যাংকিং ম্যানিপুলেট করার মূল উদ্দেশ্যে অনেক পেজ তৈরি করা হিসেবে, তাতে অটোমেশন জড়িত থাক বা না থাক; ছবি যোগ করলে সেই মূল্যায়ন পাল্টায় না। একটি ভালো পাইপলাইন যা কিনে দেয় তা হলো ইতিমধ্যে টিকে থাকার যোগ্য পেজগুলোর জন্য একটি মান নির্ণায়ক: যাচাইযোগ্য উৎস, অ্যাক্সেসযোগ্য alt টেক্সট, অটোমেশন সহ্য করতে পারা Core Web Vitals, এবং চার্ট ও স্ক্রিনশট যা সত্য থেকে বিচ্যুত হতে পারে না।
সম্পাদকীয় নিয়ন্ত্রণ না হারিয়ে কীভাবে CI-তে চিত্রায়ণ ধাপ চালাব?
আপনার কনটেন্ট ফাইল স্পর্শ করে এমন একটি পুল রিকোয়েস্টে জবটি ট্রিগার করুন, এটিকে ছবি এবং ফ্রন্ট-ম্যাটার লিখতে দিন, এবং ব্রাঞ্চে সরাসরি কমিট করার পরিবর্তে একটি পুল রিকোয়েস্ট খুলতে দিন — পাইপলাইন প্রস্তাব করে, একজন মানুষ অনুমোদন করেন, এবং এটি যা লিখেছে তার প্রতিটি ফিল্ড ডিফযোগ্য থাকে। তিনটি বিষয় মডেলে না রেখে কোডে ডিটারমিনিস্টিক রাখুন: photo_id অনুযায়ী ডুপ্লিকেশন বাদ দেওয়া, আর্টিকেলে প্রতিটি প্লট করা সংখ্যা উপস্থিত থাকার নিশ্চয়তা, এবং কোনো ফটো স্কোর থ্রেশহোল্ড অতিক্রম না করলে কঠোর ব্যর্থতা। ভুল হিরোর চেয়ে হিরো না থাকাই ভালো।

কীওয়ার্ড খোঁজা বন্ধ করুন। আপনি যা বোঝাতে চান তা বর্ণনা করুন।

যেকোনো ভাষায়, ১০০ ms-এর কম সময়ে 9M+ মুক্ত-ব্যবহারযোগ্য ছবি অর্থ দিয়ে অনুসন্ধান করুন।