VibeStudio — ADK-সহ মাল্টিমোডাল এজেন্টিক ওয়ার্কফ্লো, হন্টেড এডিশন

১. ভূমিকা

VibeStudio, Haunted Edition

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

জেনারেটিভ মডেল প্রতিটি কাজ একা করতে পারে। Gemini রিসার্চ করে, শোনে, দেখে ও লেখে; ইমেজ মডেল আঁকে; Veo রেন্ডার করে। তারা একা যা করতে পারে না তা হল রাত অর্ডার অনুযায়ী চালানো, আপনার একটি সিদ্ধান্তের জন্য থেমে থাকা, টাকা খরচ হওয়ার আগে একটি খারাপ দিকনির্দেশ প্রত্যাখ্যান করা, কিছু খোলা না রেখে রেন্ডারের জন্য মিনিট অপেক্ষা করা এবং একটি রেকর্ড রেখে যাওয়া।

এই কোডল্যাব সেই সন্ধ্যাকে ADK Workflow-এ পরিণত করে: নোড এবং প্রান্তের একটি আঁকা গ্রাফ যা নিজেই চলে, শুধুমাত্র আপনার বিচার যেখানে প্রয়োজন সেখানে আপনাকে জিজ্ঞাসা করে এবং VibeTube-এ একটি ক্লিপ এবং এর পোস্টার দিয়ে প্রতিটি রান শেষ করে।

প্রম্পট কোনও অর্ডার, স্টপ বা চেকের প্রতিশ্রুতি দিতে পারে না। গ্রাফ পারে।

আপনি কী শিখবেন

  • একটি এজেন্ট এবং টুল হিসেবে ইমেজ মডেল। Agent হল একটি মডেল, একটি নির্দেশাবলী ও টুল। তৃতীয় টুলটি ইমেজ মডেলকে কল করে এবং প্রথম রানটি একটি পোস্টারের মাধ্যমে শেষ হয়। তারপর, ল্যাবের বাকি অংশকে চালিত করে এমন বাক্য: কোনও প্রম্পট অর্ডার, স্টপ বা চেকের প্রতিশ্রুতি দিতে পারে না।
  • গ্রাফের গঠন। Workflow হল একটি এজ লিস্ট। আপনি ফ্যান-আউট এবং জয়েন (JoinNode-এ মিলিত হওয়া সমান্তরাল রিডার) এবং একটি রাউটার তৈরি করেন যা তার নিজস্ব এক্সিটের নাম দেয়, প্রত্যাখ্যান করা এক্সিটের পিছনে একটি টাস্ক-মোড এজেন্ট থাকে যা টুল কলের লুপে একটি দিক পরিষ্কার করে এবং মূল লাইনে আবার যোগ দেয়।
  • একটি মাল্টিমোডাল ইনজেশন পাইপলাইন। দর্শক চার ধরনের জিনিস পাঠিয়েছেন, প্রতি ধরনের জন্য একজন করে রিডার: Gemini ভয়েস নোট ট্রান্সক্রাইব করে, ফটোতে ক্যাপশন দেয়, ক্লিপ দেখে এবং সেগুলির সাউন্ডট্র্যাক শোনে এবং প্রতিটি রিডার একই লাইন শেপ একটি RAG Engine কর্পাসে লেখে। ফ্যান-আউটে থাকা তৃতীয় রিডার অর্থ অনুযায়ী তথ্য খুঁজে বের করে এবং অনুচ্ছেদ সহ মিডিয়া ফিরিয়ে দেয়।
  • দুই ধরনের সাসপেনশন, একটি মেকানিজম। কোনও ব্যক্তির জন্য রান থেমে যায় (RequestInput: চারটি কার্ড, আপনি উত্তর দেন) এবং স্লো টুলের জন্য (Veo-এর পিছনে LongRunningFunctionTool: একটি রসিদ)। দুটিই একই জিনিস: সেশন স্টোরে একটি ওপেন কল, ফর্ম বা অন্য প্রসেস থেকে এর আইডি দ্বারা আবার শুরু করা হয়েছে। মিডিয়া ডেস্কের দুটি শাখা, রাজ্যে নাম অনুসারে পোস্টার শেয়ার করে।
  • রাতের পর রাত ধরে মেমরি। মেমরি ব্যাঙ্কে দুটি লাইফসাইকেল কলব্যাক: before_model_callback প্রোপোজার পড়ার আগে ক্রিয়েটরের পছন্দ মনে করিয়ে দেয়, after_agent_callback স্ক্রিপ্টারের পরে আজকের রাতের পছন্দ মনে রাখে।
  • রিলিজ। ADK Runner সহ অ্যাপের মধ্যে গ্রাফ, পৃষ্ঠায় সার্ভার-প্রেরিত ইভেন্ট, সেশন অ্যাফিনিটি সহ Cloud Run এবং একটি মাল্টিপার্ট POST যা থাম্বনেল হিসেবে পোস্টার সহ VibeTube-এ ক্লিপ রাখে।

আপনি কী তৈরি করবেন

আপনার ব্রাউজারে একটি অ্যাপ চলছে: VibeStudio Workbench, পেজ-মধ্যস্থ কোড এডিটর সহ একটি কম্প্যানিয়ন পৃষ্ঠা, ডিস্কের ফাইল এবং আপনার রান করা সেশন রিড করে এমন ভেরিফায়ার এবং একটি এম্বেড করা adk web। এর সাতটি সেশন হল এই কোডল্যাবের সাতটি পৃষ্ঠা, একই নম্বর এবং একই পার্ট লেটার সহ, যাতে দুটি ধাপে থাকে। প্রতিটি পৃষ্ঠা একটি ব্যান্ডে খোলে যা এক বাক্যে সেশনটি বলে এবং আপনি সেখানে তিনটি জিনিস করেন: এডিট, রান, যাচাই। এখানে একটি বিভাগ পড়ুন, তারপর ওয়ার্কবেঞ্চে এডিট করুন; প্রতিটি এডিট একটি যাচাইকরণ প্যানেলের মাধ্যমে শেষ হয় যা আসলে কী ঘটেছে তা পড়ে।

কোথায় কী চলে: VibeStudio Workbench, আপনার ব্যাকএন্ড এবং Google Cloud পরিষেবা

তিনটি জিনিস রান করে: Workbench (পোর্ট ৪৬০০-এ থাকা পৃষ্ঠা, এডিটর, যাচাইকারী), আপনার ব্যাকএন্ড (agent/-এ Workflow এবং stage0_prompt/-এ সেশন প্রতি একটি স্যান্ডবক্স অ্যাপ, stage6_video/-এর মাধ্যমে, যা adk web পরিবেশন করে) এবং Google Cloud (Gemini, ইমেজ মডেল, RAG Engine, Veo, Memory Bank — আপনার নিজস্ব ক্রেডেনশিয়াল সহ Vertex AI-এর মাধ্যমে সবকটিতে পৌঁছানো যায়)।

সাতটি সেশন

সেশন

আপনি যা তৈরি করেন

আপনার করা এডিট

১ · স্টোরি

এখনও কিছু হয়নি — রাত এবং গ্রাফ, এটি হয়ে যায়

—

২ · ইমেজ জেনারেশন

একজন এজেন্ট প্রথম পোস্টার আঁকেন

POSTER_TOOL

৩ · ওয়ার্কফ্লো

গ্রাফের মতো রাত: ফ্যান-আউট ও জয়েন, রাউটার ও তার লুপ

FANOUT, ROUTER

৪ · মাল্টিমোডাল ইনজেশন

দর্শক যা পাঠিয়েছেন, তা একটি কর্পাসে পড়ুন; তৃতীয় পাঠক

INGEST_READERS, RAG_MEDIA

৫ · ভিডিও জেনারেশন

দুই ধরনের সাসপেনশন; রাজ্যে নাম অনুযায়ী শেয়ার করা পোস্টার

GATE, VIDEO_TOOL, DELIVER_RESPONSE, STATE_SHARE

৬ · মেমরি

চ্যানেলটি রাতের মধ্যে কী মনে রাখে

MEMORY_RECALL, MEMORY_REMEMBER

৭ · ডিপ্লয় ও প্রকাশ করা

Cloud Run-এ VibeStudio; VibeTube-এ নাইট

রুমের ইভেন্ট কোড এবং প্রকাশ করুন

এই কোডল্যাব কীভাবে ব্যবহার করবেন। এটি কনসেপ্ট-ফার্স্ট: প্রতিটি সেশন আইডিয়া ব্যাখ্যা করে, তারপর কোড দেখায় যেমন এটি পরিবর্তন করার পরে একবার পড়ে এবং এটি দিয়ে যায়। এটি আপনাকে এখানে কোনও ফাইল এডিট করতে বলে না — এডিটিং ওয়ার্কবেঞ্চে হয়, যা আপনাকে একবারে একটি এডিট, তার পাশে একটি ইঙ্গিত এবং তার নিচে একটি যাচাইকরণ প্যানেল দেয়। সেশনটি পড়ুন, তারপর পৃষ্ঠাটি আপনাকে এর মাধ্যমে নিয়ে যেতে দিন।

এটি কোথায় থাকে

VibeTube-এ গ্রাফ, থিম ও রুম Lin-এর Haunted Edition of VibeStudio থেকে নেওয়া হয়েছে; এটি মাল্টিমোডাল কাট, যা দর্শক যা পাঠিয়েছেন এবং দুই ধরনের সাসপেনশনের উপর আবার ফোকাস করে, Agent Valley ল্যাবের এক-পৃষ্ঠার ইন্ট্যার‍্যাক্টিভ ফর্ম্যাটে। স্টুডিও হল দৃশ্য; এর অধীনে প্রতিটি মেকানিজম হল সাধারণ একটি মেকানিজম যা আপনি একটি সাপোর্ট পাইপলাইন, একটি কন্টেন্ট রিভিউ ফ্লো বা অন্য কিছুর জন্য ব্যবহার করবেন যা একজন ব্যক্তির জন্য পজ করতে হবে এবং একটি ধীরগতির কাজের জন্য অপেক্ষা করতে হবে।

২. 🧰 সেট-আপ করুন

  • কী — একটি অ্যাকাউন্ট যা পেমেন্ট করতে পারে, একটি প্রোজেক্ট, ইনস্টল করা ল্যাব, উত্তর দেওয়ার জন্য প্রমাণিত মডেল এবং একটি পোর্টে ব্যাকগ্রাউন্ডে চলা ওয়ার্কবেঞ্চ।
  • কেন — একটি পোর্টে একটি প্রসেস: পৃষ্ঠা, এডিটর, যাচাইকারী, /api/* এবং adk web একটি অরিজিন শেয়ার করে, তাই এই ল্যাবের কোনও কিছুর জন্য দ্বিতীয় টার্মিনাল বা দ্বিতীয় পোর্টের প্রয়োজন নেই।
  • কীভাবে — ক্লোন, দুটি স্ক্রিপ্ট। দ্বিতীয়টি আসল মডেল কল করে, ওয়ার্কবেঞ্চ চালু করে এবং আপনি পড়ার সময় RAG কর্পাস ও মেমরি ব্যাঙ্ক তৈরি করা শুরু করে, যাতে সেশন ৪ ও ৬-এ সেগুলি রেডি থাকে।

এই ল্যাবের সবকিছু Cloud Shell-এ রান করে — gcloud, Python, Node, ffmpeg এবং git সেখানে আগে থেকেই ইনস্টল করা আছে এবং Gemini, ইমেজ মডেল, Veo, RAG Engine এবং Memory Bank-এর সবকিছু আপনার ক্রেডেনশিয়াল ব্যবহার করে Vertex AI-এর মাধ্যমে অ্যাক্সেস করা হয়। এই ল্যাবের কোথাও কোনও API key নেই এবং আপনার ল্যাপটপে ইনস্টল করার মতো কিছু নেই। Google Cloud SDK, Python 3.12 এবং Node 20+ সহ একটি ল্যাপটপ একইভাবে কাজ করে।

এই ল্যাব আপনাকে একটি জিনিস দিতে পারবে না: বিলিং সহ Google Cloud প্রোজেক্ট। ধাপ ১-এ আপনি এমন একটি অ্যাকাউন্ট পাবেন যা এর জন্য পেমেন্ট করতে পারবে; ধাপ ৪-এ সেই অ্যাকাউন্টটি একটি প্রোজেক্টে পরিণত হবে। সেই ক্রমেই সেগুলি করুন — ৪ নম্বর ধাপের পরে প্রতিটি ধাপ ৪ নম্বর ধাপের প্রোজেক্ট পড়ে।

১ · বিলিং সহ অ্যাকাউন্ট পান (~১ মিনিট)

বিলিং চালু আছে এমন Google অ্যাকাউন্ট দিয়ে সাইন-ইন করুন। এই ধরনের যেকোনও অ্যাকাউন্ট কাজ করে। আপনার যদি তা না থাকে, তাহলে ফ্রি ট্রায়াল শুরু করুন; এটি আপনাকে একটি বিলিং অ্যাকাউন্ট দেয়, যা ধাপ ৪-এর জন্য প্রয়োজন। আপনি যদি কোনও ইনস্ট্রাক্টর-পরিচালিত ল্যাবে থাকেন, তাহলে প্রথমে এই সেশনের জন্য ক্রেডিট রিডিম করুন — এটি আপনার জন্য বিলিং অ্যাকাউন্ট তৈরি করে।

২ · Cloud Shell খুলুন (~২ মিনিট)

👉🖥 Google Cloud কনসোলের সবচেয়ে উপরে Cloud Shell অ্যাক্টিভেট করুন বিকল্পে ক্লিক করুন — এটি হেডারে টার্মিনাল-আকৃতির আইকন।

Google Cloud Console-এর হেডার থেকে Cloud Shell চালু করুন

👉🖥 এডিটর খুলুন (পেন্সিল সহ ফোল্ডার খোলার আইকন) বিকল্পে ক্লিক করুন। এর ফলে বাঁদিকে ফাইল এক্সপ্লোরার সহ Cloud Shell Editor খুলে যায় — আপনি যখনই কোনও ফাইল সম্পূর্ণ পড়তে চান, আপনাকে এখানে যেতে হবে।

Cloud Shell Editor খুলুন

👉🖥 আজ যেকোনও সময় আপনাকে অনুমোদন করার জন্য প্রম্পট করা হলে, চালিয়ে যেতে অনুমোদন করুন বিকল্পে ক্লিক করুন।

👉🖥 এডিটরের মধ্যে একটি টার্মিনাল খুলুন — অর্থাৎ ট্যাব ১ এবং প্রতিটি 👉💻 ধাপ সেখানে রান করে। ট্যাব ২ হল ওয়ার্কবেঞ্চ এবং আপনি এটি ৭ নম্বর ধাপে খুলবেন। স্ক্রিনের নিচের দিকে টার্মিনাল আগে থেকে না থাকলে, View → Terminal বিকল্পের সাহায্যে এটি খুলুন:

Cloud Shell এডিটরে নতুন টার্মিনাল খুলুন

👉💻 আপনি কোন অ্যাকাউন্টে সাইন-ইন করে আছেন তা চেক করুন — এটি ধাপ ১-এ উল্লেখ করা অ্যাকাউন্ট হতে হবে:

gcloud auth list

৩ · ল্যাব পান (~১ মিনিট)

👉💻 রেপো ক্লোন করুন:

cd ~
git clone https://github.com/cuppibla/vibe-studio-multimodal
cd vibe-studio-multimodal

৪ · আপনার প্রোজেক্ট তৈরি করুন (~২ মিনিট)

👉💻 একটি স্ক্রিপ্ট আপনার বিলিং অ্যাকাউন্ট খুঁজে বের করে, এতে একটি প্রোজেক্ট তৈরি করে, বিলিং লিঙ্ক করে এবং সেই প্রোজেক্টে Cloud Shell পয়েন্ট করে:

cd ~/vibe-studio-multimodal
./setup_project.sh

এটি যে বিলিং অ্যাকাউন্ট বেছে নিয়েছে তার জন্য একটি ✓, এটি যে প্রোজেক্ট তৈরি করেছে তার জন্য একটি ✓ এবং এই ধরনের একটি সমাপ্তি আশা করুন:

Project ready: vibe-studio-4271
  Next:  ./setup_codelab.sh

vibe-studio-4271 হল আপনার প্রোজেক্ট আইডি — আপনার প্রোজেক্ট আইডিতে অন্য সংখ্যা থাকবে। সেখানে চারটি জিনিস ঘটে:

বিলিং

আপনার কাছে থাকলে, সবচেয়ে নতুন [YYYY-MM-DD] GDP Credit: অ্যাকাউন্ট নেয়, অন্যথায় অন্য যেকোনও খোলা বিলিং অ্যাকাউন্ট নেয়

প্রোজেক্ট

vibe-studio-XXXX তৈরি করে এবং সেই বিলিং অ্যাকাউন্টকে এটির সাথে লিঙ্ক করে

selects

~/project_id.txt-এ আইডি লেখে এবং gcloud config set project রান করে, তাই এর পরে আর কিছু আপনাকে এটি টাইপ করতে বলে না

অপেক্ষা করে

প্রোজেক্টটি আসলে পরিবেশন করার অনুমতি না দেওয়া পর্যন্ত পোল করে, সর্বাধিক দুই মিনিটের জন্য

👉🖥 একই প্রোজেক্ট এখন কনসোলে আছে — এর আইডি ড্যাশবোর্ডের প্রোজেক্টের তথ্য কার্ডে আছে:

কনসোল ড্যাশবোর্ডে আপনার প্রোজেক্ট আইডি

৫ · ল্যাব ইনস্টল ও সেট-আপ করুন (~৪ মিনিট)

💻

cd ~/vibe-studio-multimodal
./setup_codelab.sh

একটি স্ক্রিপ্ট, আবার রান করা নিরাপদ — দ্বিতীয়বার রান করলে প্রথমবার আপনার দেওয়া প্রতিটি উত্তর রেখে দেয়। এটি শুরুতেই দুটি প্রশ্ন জিজ্ঞাসা করে এবং Enter প্রেস করলে দুটির জন্যই ডিফল্ট উত্তর বেছে নেওয়া হয়:

ইভেন্ট কোড

সেশন ৭-এ আপনার ভিডিও যে VibeTube রুমে প্রকাশ করা হয়েছে। আপনার ইনস্ট্রাক্টরের থেকে পাওয়া রুম কোড টাইপ করুন অথবা sandbox-এর জন্য Enter প্রেস করুন, যা যেকোনও সময় যেকোনও ব্যক্তির জন্য খোলা থাকে। কোড ছোট ও বড় হাতের অক্ষরের মধ্যে পার্থক্য করে এবং রুম শুধুমাত্র তার উইন্ডোর মধ্যে আপলোড গ্রহণ করে; স্ক্রিপ্ট আপনাকে বলে যে আপনার নাম দেওয়া রুমটি খোলা আছে কিনা।

যে নাম দেখানো হবে

রুমের নাম আপনাকে ক্রেডিট দেয়, অথবা আপনার অ্যাকাউন্ট থেকে তৈরি করা রুমের জন্য Enter প্রেস করুন।

দুটিই .env-এ ল্যান্ড করে এবং পরে .env-এ বা প্রকাশনা পৃষ্ঠায় পরিবর্তন করা যেতে পারে। তারপর এটি বাকি কাজ নিজে থেকে করে, এই ক্রমে:

uv env

uv না থাকলে ইনস্টল করে, তারপর uv.lock যা পিন করে — google-adk, google-genai, vertexai, fastapi, uvicorn — uv venv এবং uv sync ঠিক তাই .venv-এ ইনস্টল করে

apis

প্রজেক্টের ৪ নম্বর ধাপে বেছে নেওয়া ছয়টি API চালু করে এবং পরবর্তী ধাপে যাওয়ার আগে প্রতিটি API-এর উত্তর কনফার্ম করে: aiplatform (Gemini, ইমেজ মডেল, Veo, মেমরি ব্যাঙ্ক, RAG ইঞ্জিন), vectorsearch (RAG কর্পাসের পিছনে থাকা স্টোর), run, cloudbuild, artifactregistry (সেশন ৭), cloudtrace (অ্যাপের ট্রেস)

অ্যাকাউন্ট

প্রোজেক্টের ডিফল্ট কম্পিউট পরিষেবা অ্যাকাউন্ট আছে কিনা এবং সেশন ৭-এর বিল্ড ও পরিষেবার জন্য প্রয়োজনীয় ভূমিকা আছে কিনা তা নিশ্চিত করে

.env

আপনার প্রোজেক্টে Vertex AI-তে প্রতিটি কল পয়েন্ট করে .env লেখে — global এন্ডপয়েন্টে Gemini, us-central1-এ Veo, মেমরি ব্যাঙ্ক এবং RAG ইঞ্জিন — এবং কোথাও কোনও API কী নেই; আগের .env, .env.bak হিসেবে রাখা হয় এবং আপনি ম্যানুয়ালি যা যোগ করেছেন তা থেকে যায়

veo · room

veo-3.1-fast-generate-001, us-central1-এ পরিবেশন করা হয়েছে কিনা তা কনফার্ম করে (মেটাডেটা রিড, কোনও রেন্ডার নয়) এবং VibeTube-কে জিজ্ঞাসা করে যে আপনার রুম খোলা আছে কিনা

মডেল

Gemini-তে একটি আসল কল করে, যাতে কোনও খারাপ প্রোজেক্ট সেট-আপের সময় ব্যর্থ হয় এবং সেশনের মধ্যে নয়

স্টার্টার

starter/ থেকে আপনি যে নয়টি ফাইল এডিট করবেন সেগুলি যথাস্থানে রাখে, তাদের TODO লাইন ধরে রাখে — শুধুমাত্র যেগুলি অনুপস্থিত; আপনার এডিটগুলি কখনই ওভাররাইট করা হয় না

পৃষ্ঠা

web/, ওয়ার্কবেঞ্চ পৃষ্ঠা ইনস্টল ও তৈরি করে, তারপর পোর্ট ৪৬০০-এ ব্যাকগ্রাউন্ডে সার্ভার চালু করে

মেঘ

ব্যাকগ্রাউন্ডে RAG ইঞ্জিন কর্পাস এবং মেমরি ব্যাঙ্ক তৈরি করা শুরু করে, যাতে সেশন ৪ এবং ৬ সেগুলি প্রস্তুত অবস্থায় খুঁজে পায়

প্রিফ্লাইট

scripts/preflight.py রান করে এবং খোলার জন্য লিঙ্ক প্রিন্ট করে

৬ · প্রিফ্লাইট পড়ুন (~১ মিনিট)

৫ নম্বর ধাপে শেষ যে জিনিসটি প্রিন্ট করা হয়েছে তা হল প্রিফ্লাইট — প্রতিটি লাইন একটি বাস্তব প্রোব, কোনও অনুমান নয়:

  ✓ python 3.12
  ✓ auth path A: Vertex via ADC (STUDIO_VERTEX=1)
  ✓ Google Cloud ADC (project vibe-studio-4271)
  ✓ Memory Bank SDK
  ✓ stage0_prompt loads
  ✓ stage1_fanout loads (0 edges)
  ✓ stage3_router loads (... edges)
  ✓ stage4_memory loads (... edges)
  ✓ stage5_rag loads (... edges)
  ✓ stage6_video loads (... edges)
  ✓ node and npm (build the learning center's page)
  ✓ learning center page built (web/dist)
  ✓ aiplatform.googleapis.com enabled (Gemini, Veo, Memory Bank, RAG Engine)
  ...
  - Memory Bank: still being created in the background
  - RAG corpus: still being created in the background
  ✓ session 7 runtime account 123456789-compute@developer.gserviceaccount.com has its roles
  ✓ learning center running on port 4600

PREFLIGHT GREEN

Open session 1 here:  https://4600-cs-...cloudshell.dev/step/story

stage1_fanout loads (0 edges) ঠিকই বলেছে: গ্রাফে কিছু নেই, সেশন ৩-এ আপনি এটি পূরণ করবেন। দুটি - লাইনও ঠিক — কর্পাস আসতে এক বা দুই মিনিট এবং ব্যাঙ্ক প্রায় কুড়ি সেকেন্ড সময় নেয় এবং সেশন ৪-এর আগে আপনার কোনওটিরই প্রয়োজন হবে না।

👉💻 আপনি কোথায় আছেন তা দেখতে যেকোনও সময় এটি আবার রান করুন — এটি আপনার ফাইল, আপনার প্রোজেক্ট এবং রান করা সার্ভার পড়ে, চেকলিস্ট নয়:

cd ~/vibe-studio-multimodal
uv run python scripts/preflight.py

৭ · ওয়ার্কবেঞ্চ খুলুন (~১ মিনিট)

ওয়ার্কবেঞ্চ আগে থেকেই চলছে। ৫ নম্বর ধাপ ব্যাকগ্রাউন্ডে এটি শুরু করেছে, পোর্ট ৪৬০০-এ একটি uvicorn প্রসেস যা পৃষ্ঠা, প্রতিটি /api/* রুট এবং /workbench-এ মাউন্ট করা adk web পরিবেশন করে। শুরু করার মতো কিছু নেই এবং এই ল্যাবের কোনও কিছুই আপনাকে এটি রিস্টার্ট করতে বলে না: আপনার পরবর্তী রান থেকে এডিট শুরু হয়।

👉🖥 প্রিফ্লাইটের প্রিন্ট করা লিঙ্কে ক্লিক করুন অথবা Cloud Shell-এ: ওয়েব প্রিভিউ → পোর্ট পরিবর্তন করুন → ৪৬০০। সেটিকে ট্যাব ২ বলা হয়। এটি সেশন ১ · গল্প দিয়ে শুরু হয়।

তিনটি কমান্ড যা আপনি পরে চাইতে পারেন, সব ~/vibe-studio-multimodal থেকে:

scripts/stop.sh

ওয়ার্কবেঞ্চ বন্ধ করে দেয়

scripts/start.sh

এটি ফোরগ্রাউন্ডে শুরু করে; Ctrl+C এটি এবং এর শুরু করা প্রতিটি প্রসেস বন্ধ করে দেয়

scripts/restart.sh --pull

git pull, তারপর ব্যাকগ্রাউন্ডে রিস্টার্ট করুন, venv আবার সিঙ্ক করুন এবং শুধুমাত্র পরিবর্তন হলে পৃষ্ঠাটি আবার তৈরি করুন

রিপোজিটরি লেআউট

vibe-studio-multimodal/
├── agent/                  # the workflow and the platform clients
│   ├── graph.py            # the graph: three readers, the proposer, the gate, the router, the script, the desk
│   ├── desk.py             # render_desk and render_submit, the LongRunningFunctionTool
│   ├── deliver.py          # the delivery console: find the pending call, wait for Veo, answer by id
│   ├── schemas.py          # Direction, Directions, CleanedDirection, Script, each with a poster idea
│   ├── cleanup_tools.py    # find_policy_hits, suggest_replacement: the task agent's tools
│   ├── policy_words.txt    # the policy, as data; policy_replacements.txt, the approved stand-ins
│   ├── backlog.txt         # the creator's Halloween ideas; comments.md, the audience's comments
│   ├── audience/           # what else the audience sent: voice/*.mp3, photo/*.jpg, clip/*.mp4
│   ├── media/past/         # six past posters and captions.md, what Gemini sees in each
│   └── platform/           # config, audience (the readers), rag (RAG Engine), images, videogen (Veo), memory (Memory Bank)
├── stage0_prompt/ ... stage6_video/   # one sandbox app per session, runnable in adk web
├── server/ & web/          # the VibeStudio Workbench (FastAPI + React, port 4600)
├── vibestudio/             # the app deployed in session 7, with its own copy of agent/
├── checks/ & scripts/      # the hole registry, the verifiers, preflight, start/stop/restart
└── starter/                # the nine files you edit, as students receive them

৩. ১ · গল্প — রাত এবং এটি যে গ্রাফে পরিণত হয়

👉🖥 সেশন ১ · গল্প-এ ট্যাব ২ খোলে। এটি পড়ুন: একজন ব্যক্তির চারপাশে একটি বৃত্তে সাজানো ছয়টি কাজ — ট্রেন্ড রিসার্চ করা, ব্যাকলগ চেক করা, একটি দিক বেছে নেওয়া, নীতি চেক করা, স্ক্রিপ্ট লেখা, ক্লিপ রেন্ডার করা — এবং বৃত্তটি প্রতি রাতে পুনরাবৃত্তি হয়। Gemini একাই রিসার্চ, ব্যাকলগ ও স্ক্রিপ্ট তৈরি করতে পারে; Veo একাই রেন্ডার করতে পারে; বেছে নেওয়া ও চেক করার কাজটা মানুষকেই করতে হয়।

রিংয়ের নিচে রয়েছে গ্রাফ যা ল্যাবের বাকি অংশ তৈরি করে, আইডিয়া থেকে ক্লিপ ও তার পোস্টার পর্যন্ত তেরোটি নোড। কোনও নোড কী করে এবং কোন সেশন এটি তৈরি করে তা জানতে সেটির উপরে মাউস নিয়ে যান। এখানে এডিট করার কিছু নেই; পৃষ্ঠার প্রথম পোস্টার জেনারেট করুন বোতামটি সেশন ২-এ আছে।

শব্দভাণ্ডার, তাই পরের ছয়টি পৃষ্ঠা না থেমে পড়া:

শব্দ

এখানে এর অর্থ কী

এজেন্ট

একটি ADK Agent: একটি মডেল, একটি নির্দেশ, টুল। সেশন ২

নোড

গ্রাফের একটি ধাপ — একটি Python ফাংশন, একটি এজেন্ট, একটি জয়েন, একটি রাউটার

প্রান্ত

নোডের একটি টিউপল যা ক্রমানুসারে চলে; গ্রাফ হল তাদের একটি তালিকা। সেশন ৩

সেশন

runs/sessions.db-এ একটি রান রেকর্ড: এর ইভেন্ট, এর স্ট্যাটাস এবং যেকোনও কল খোলা আছে। সেশন ৫

রাজ্য

কী-এর নাম অনুযায়ী নোড শেয়ার করে এমন একটি ডিক্ট; একটি নোড Event(state=...) লেখে, অন্যটি একই নামের একটি প্যারামিটার ঘোষণা করে। সেশন ৫

স্মৃতি

সেশনের পরেও কী থেকে যায় — মেমরি ব্যাঙ্ক, কলব্যাকের মাধ্যমে। সেশন ৬

৪. ২ · ইমেজ জেনারেশন — একটি এজেন্ট এবং টুল হিসেবে ইমেজ মডেল

সেশন ২ · ইমেজ জেনারেশন

👉🖥 ওয়ার্কবেঞ্চে, সেশন ২ · ইমেজ জেনারেশন খুলুন। গ্রাফের আগে, সবচেয়ে সহজ যে জিনিসটি কাজ করতে পারে: একটি Agent যার নির্দেশ হল গদ্য হিসেবে পুরো রাত, এর হাত হিসেবে তিনটি Python ফাংশন এবং একটি রান যা ছবির মাধ্যমে শেষ হয়।

এজেন্ট হল একটি মডেল, একটি নির্দেশ এবং টুল

stage0_prompt/agent.py, ADK এজেন্টের তিনটি ফিল্ডের সাথে root_agent নির্ধারণ করে। model হল agent/platform/config.py-এর Gemini মডেল। instruction হল সিস্টেম প্রম্পট: এক নিঃশ্বাসে ছ'টি কাজ, যার শেষটি হল একটি ছবি। tools-এ তিনটি ফাংশন আছে।

root_agent = Agent(
    name="solo_channel", model=config.MODEL,
    tools=[check_trends, read_backlog, make_poster],
    instruction=(
        "You run the creator's cozy-spooky short-video channel, alone, every "
        "night until Halloween.\n"
        "When the creator gives you an idea (or nothing), do ALL of this:\n"
        "check what is trending. look at your backlog of ideas. propose a "
        "direction and agree on it with the creator. refuse blacklisted "
        "subjects (competitor, hateful, gore). then describe the video you "
        "would make: a title (<=60 chars) and 3 shots, one visual sentence "
        "each. then draw its poster: call make_poster once with one line that "
        "names the single image that sells the video, and tell the creator "
        "where the poster is.\n"
        "Ask the creator to confirm the direction before describing the "
        "video."))

মডেল হল একটি বদ্ধ জগৎ: এটি তার ওয়েট এবং প্রসঙ্গের টোকেন নিয়ে যুক্তি দেয়। টুল হল একটি Python ফাংশন যা মডেল চাইতে পারে। ADK প্রতিটি ফাংশনের সিগনেচার ও ডকস্ট্রিং পড়ে এবং মডেলে তা ঘোষণা করে; মডেলের কোনও একটির প্রয়োজন হলে, এটি একটি function_call ইভেন্ট এমিট করে, রানটাইম Python রান করে এবং কনটেক্সটে একটি function_response ইভেন্ট লিখে দেয় এবং মডেলটি তার সামনে থাকা ফলাফল নিয়ে কাজ চালিয়ে যায়।

তিনটি ফাংশন, এবং তাদের মধ্যে একটি আঁকে

তিনজনের মধ্যে দুজন রিসার্চটি পড়েছেন। তৃতীয় প্রম্পটটি ইমেজ মডেলকে একটি পোস্টার তৈরি করতে বলে:

def check_trends() -> dict:
    """Ten formats trending on the platform right now, with a heat score each."""
    from agent.trends import sample_trends
    return {"trends": sample_trends()}


def read_backlog() -> dict:
    """The creator's backlog: ideas they noted down to make someday."""
    from agent.graph import backlog_notes
    return {"backlog": backlog_notes()}


def make_poster(subject: str) -> dict:
    """Draw tonight's poster: one image of `subject` in the channel's cozy
    low-poly style. Returns where the PNG landed (path, url) in a few seconds."""
    ...

make_poster agent/platform/images.py-কে কল করে, ইমেজ মডেলটি একটি সাধারণ ফাংশন হিসেবে র‍্যাপ করা হয়েছে যেভাবে videogen.py Veo-কে র‍্যাপ করে: একটি হাউস-স্টাইল প্রিফিক্স (চ্যানেলের আরামদায়ক লো-পলি হ্যালোইন লুক, কুমড়োর কমলা এবং মধ্যরাতের প্লামের বিপরীতে মোমবাতির আলো ক্রিম), সাবজেক্ট, একটি উল্লম্ব ৯:১৬ ফ্রেম, runs/media/posters-এর অধীনে একটি PNG, /static/posters-এ পরিবেশিত। STUDIO_REAL_IMAGES=0-এর মাধ্যমে এটি একটি স্ট্যান্ড-ইন পোস্টার কপি করে এবং ফলাফল prebaked ফ্ল্যাগ করে, যাতে ল্যাব যেকোনও জায়গায় রান করে।

MODEL = os.environ.get("STUDIO_IMAGE_MODEL", "gemini-2.5-flash-image")
ASPECT = "9:16"

def generate(subject: str, visual_style: str = "", name: str | None = None) -> dict:
    """One poster for `subject`. Returns where it landed: status, path, url,
    prompt, and prebaked=True when the stand-in was used instead of the model."""

একই কল সেশন ৫-এ গ্রাফের একটি নোড।

হ্যান্ডস-অন: সেই এডিট

As shipped, make_poster returns a dict that says the poster was skipped. 👉🖥 ওয়ার্কবেঞ্চ এডিটরে, TODO লাইনের পরিবর্তে, ইমেজ মডেল কল করার জন্য এটি তৈরি করুন:

    from agent.platform import images
    return images.generate(subject, name="solo")

সেভ করুন। যাচাইকরণ সারি ফাইলটি পড়ে: এজেন্টের তিনটি টুল, ইমেজ মডেল কল ইন প্লেস। 👉🖥 ওয়ার্কফ্লো লোড চেক করুন বিকল্পে ক্লিক করুন, তারপর adk ওয়েব খুলুন এবং পাঠান:

tonight's idea: a tiny ghost learning to haunt a sock drawer

কী আশা করা যায় এবং কেন

  • উত্তরের আগে দুটি টুল ইভেন্ট, তারপর একটি প্রশ্ন। check_trends এবং read_backlog রান করে কারণ নির্দেশে চেক করতে এবং দেখতে বলা হয়েছে এবং মডেলের ওয়েটে দুটির কোনওটিই ছিল না। উত্তরে একটি দিকনির্দেশ প্রস্তাব করা হয়েছে এবং আপনাকে কনফার্ম করতে বলা হয়েছে, কারণ নির্দেশে প্রথমে সম্মতি চাওয়া হয়েছে।
  • দ্বিতীয় মেসেজে সব তথ্য থাকে। skip the questions, describe the video and draw the poster পাঠান। মডেলটি ভিডিওর বর্ণনা দেয়, নিজে থেকে লেখা সাবজেক্ট লাইন সহ একবার make_poster-কে কল করে এবং PNG কোথায় সেভ হয়েছে তা আপনাকে জানায়। যাচাইকরণ প্যানেলে সারির পাশে পোস্টারটি দেখানো হয়: চ্যানেলের লুকে হ্যালোইনের দৃশ্য, যা প্রায় আট সেকেন্ডে আঁকা হয়েছে।
  • এটি কেন চ্যানেল নয়। রিসার্চটি গদ্যে ভাঁজ করা হয়েছে, কিছুই উদ্ধৃত করা যাবে না। যে মডেলটি নির্দেশ লিখেছে সেটি নীতিটি সার্টিফাই করেছে এবং কেউ চেক করার আগেই পোস্টারটি আঁকা হয়েছে। একটি মেসেজে চুক্তি সম্পর্কে আলোচনা করা হয়েছে। প্রম্পট টেক্সট কোনও গেট নয়। ল্যাবের বাকি অংশে এই ছয়টি কাজকে একটি আঁকা গ্রাফে আলাদা করা হয়েছে।

পৃষ্ঠাটি ADK এজেন্ট কী দিয়ে তৈরি দিয়ে শেষ হয়, এটি এমন একটি রেফারেন্স যা আপনি পরে আবার দেখতে পারেন: মডেল, যে প্রসঙ্গের ভিত্তিতে এটি যুক্তি দেয়, যে টুল ও কোলাবরেটরের মাধ্যমে এটি কাজ করে, প্রতিটি কলকে র‍্যাপ করে এমন কলব্যাক এবং এর বাইরে থাকা সেশন ও মেমরি।

৫. ৩ · ওয়ার্কফ্লো — ফ্যান-আউট ও জয়েন, রাউটার ও লুপ

সেশন ৩ · ওয়ার্কফ্লো

👉🖥 ওয়ার্কবেঞ্চে, সেশন ৩ · ওয়ার্কফ্লো, ৩ক এবং ৩খ অংশ খুলুন। প্রতিটি অংশ একটি স্যান্ডবক্স অ্যাপে তৈরি করা হয়েছে যাতে আপনি এটি adk web-এ নিজে থেকে চালাতে পারেন: stage1_fanout-এ ফ্যান-আউট, রাউটার এবং এর লুপ stage3_router-এ।

ফ্যান-আউট ও যোগ দিন (3a)

ADK Workflow হল একটি এজ লিস্ট। প্রতিটি এন্ট্রি হল একটি চেন, Node-এর একটি টাপল যা ক্রমানুসারে চলে। START থেকে শুরু হওয়া দুটি চেন সমান্তরালভাবে চলে। JoinNode-এ শেষ হওয়া চেন সেটিতে রিপোর্ট করে এবং জয়েন সেখানে শেষ হওয়া প্রতিটি চেনের জন্য অপেক্ষা করে, তারপর নোডের নাম অনুযায়ী একটি ডিক্ট হ্যান্ড-অন করে। ডিক্ট হিসেবে লেখা টার্গেট, রাউটারের রুটের নাম নোডে ম্যাপ করে। ওয়ার্কবেঞ্চের 3a পৃষ্ঠায় আকার আঁকা আছে এবং এতে প্রান্ত-লেখার অনুশীলন আছে।

Node-এর ধরন

এইভাবে লেখা হয়েছে

এর কাজ কী

ফাংশন নোড

def scan_trends(node_input): ... return Event(output=...)

Plain Python. এছাড়াও, Event(state=...)-এর সাথে শেয়ার করা স্ট্যাটাস লিখতে পারে।

নোডে যোগ দিন

JoinNode(name="join_research")

প্রতিটি ইনকামিং চেইন রিপোর্ট না করা পর্যন্ত অপেক্ষা করে, তারপর তাদের সমস্ত আউটপুট একটি ডিক্ট হিসাবে পাস করে।

এজেন্ট নোড

Agent(...)

single_turn মোডে একটি মডেল কল, একটি স্ট্রাকচার্ড উত্তর, কোনও কথোপকথন নেই।

রাউটার

return Event(output=..., route="OK")

একটি ফাংশন নোড যার রিটার্ন আউটগোয়িং এজকে নাম দেয়।

হিউম্যান ইনপুট নোড

yield RequestInput(...)

কোনও ব্যক্তি ফর্মে উত্তর না দেওয়া পর্যন্ত গ্রাফ সাসপেন্ড করে। সেশন ৫.

রিসার্চ ফ্যান-আউট: START থেকে দুটি রিডার একটি জয়েনে

প্রথম গ্রাফটি হল সেশন ২-এর রিসার্চ, যা নোড হিসেবে দেখানো হয়েছে। agent/graph.py-এ scan_trends এবং read_backlog হল দুটি রিডার; read_backlog আজকের রাতের আইডিয়া শেয়ার করা স্টেটে লেখে। stage1_fanout/agent.py জয়েনকে সংজ্ঞায়িত করে এবং EDGES নামের তালিকা থেকে এর Workflow তৈরি করে, যা খালি থাকে (প্রিফ্লাইটের 0 edges)। 👉🖥 এটি দুটি চেইন দিয়ে পূরণ করুন:

EDGES = [(START, scan_trends, join_research),
         (START, read_backlog, join_research)]

👉🖥 এটি adk web-এ রান করুন। দুটি নোড একসাথে আলো দেয়, তারপর যোগ হয়; join_research-এর ইভেন্ট খোলে এবং scan_trends কী ও read_backlog কী সহ একটি ডিক্ট পড়ে। কোনও মডেল কল হয়নি। Nothing reformats the join's dict: the node after it, in 3b, is an agent, and an agent node receives its node_input as its message, so a dict arrives as JSON the model reads directly.

রাউটার ও লুপ (3b)

Workflow-এর মধ্যে থাকা Agent, single_turn মোডে রান করে: এটি আগের নোডের আউটপুটকে মেসেজ হিসেবে গ্রহণ করে, একটি মডেল কল করে এবং একবার উত্তর দেয়। output_schema-এর মাধ্যমে, পরবর্তী নোড দেখার আগে উত্তরটি একটি পাইড্যান্টিক মডেলের মাধ্যমে যাচাই করা হয়, তাই গ্রাফটি টাইপ করা অবজেক্ট পাস করে, গদ্য নয়। propose_directions জয়েনের রিসার্চ ডিক্ট পড়ে এবং চারজন প্রার্থীকে পিচ করে, প্রত্যেকেই হ্যালোউইনের দৃশ্য। চতুর্থ প্রার্থী হল টোপ: এর টাইটেলে এমন একটি শব্দ আছে যা চ্যানেল প্রত্যাখ্যান করে, তাই রাউটারের কাছে ধরার মতো কিছু আছে। প্রত্যেক প্রার্থীকে একটি poster_idea নিয়ে আসতে হবে, এক লাইনে সেই ছবির নাম লিখতে হবে যা ভিডিওটি বিক্রি করে; স্ক্রিপ্টার এটি রেখে দেন এবং মিডিয়া ডেস্ক সেশন ৫-এ এটি আঁকেন।

রাউটার হল একটি ফাংশন নোড যার রিটার্ন রান যে এজ নেয় তার নাম দেয়। কোনও মডেল কল নেই: সিদ্ধান্ত নেওয়ার সময় policy_check agent/policy_words.txt পড়ে, ডিরেকশনের শীর্ষক ও অ্যাঙ্গেলের সাথে সম্পূর্ণ শব্দ ম্যাচ করে এবং কোনও স্ক্রিপ্ট লেখা বা টাকা খরচ করার আগে এটি রুট করে। নীতিটি হল ডেটা: ফাইলটি এডিট করুন এবং পরবর্তী রান এটি পড়ে।

def policy_check(node_input):
    """The policy gate: a router. Refused words in the direction send it to
    quarantine; a clean one goes to the scripter. Code, not a model."""
    chosen = _as_dict(node_input)
    bad = policy_hits(f"{chosen.get('title', '')} {chosen.get('angle', '')}")
    return Event(output=node_input, route="BLOCK" if bad else "OK")

নীতি গেট: দুটি লেবেলযুক্ত এক্সিট সহ একটি রাউটার

stage3_router/agent.py হল ফ্যান-আউট প্লাস propose_directions এবং choose_direction, সেশন ৫-এ আপনার তৈরি করা হিউম্যান ডোরের স্ট্যান্ড-ইন: এটি সেই প্রার্থীকে বেছে নেয় যার নম্বর আজকের রাতের আইডিয়া (..., pick 4) শেষ করে, অন্যথায় প্রথমটি এবং বেছে নেওয়া প্রার্থীকে স্টেটে লেখে। scripter হল একটি single_turn এজেন্ট যা অনুমোদিত নির্দেশকে Script-এ পরিণত করে: একটি শিরোনাম, একটি বিবরণ, ট্যাগ, একটি ওপেনিং লাইন, তিনটি শট এবং একটি পোস্টার আইডিয়া, প্রতিটি শট চ্যানেলের লুকে একটি হ্যালোইন ছবি।

প্রত্যাখ্যাত এক্সিটের পিছনে লুপ। quarantine টাস্ক-মোড এজেন্ট হিসেবে কাজ করে। Agent-এর mode আছে: chat হল কথোপকথনের লুপ যা কোনও ব্যক্তির সাথে রুট এজেন্ট চালায়, single_turn হল স্ট্রাকচার্ড উত্তর সহ একটি কল এবং task হল টুল কলের লুপ যা এজেন্ট নিজে থেকে চালায় যতক্ষণ না এটি বিল্ট-ইন finish_task কল করে, যার আর্গুমেন্ট ADK, output_schema থেকে নেয়। কোয়ারেন্টাইন, প্রত্যাখ্যাত ডিরেকশনে find_policy_hits কল করে, প্রতিটি শব্দের জন্য চ্যানেলের অনুমোদিত স্ট্যান্ড-ইন সম্পর্কে suggest_replacement-কে জিজ্ঞাসা করে (agent/policy_replacements.txt, আবার ডেটা), আবার লেখে, আবার চেক করে এবং টাইপ করা CleanedDirection দিয়ে শেষ করে। পৃষ্ঠা থেকে এটি পড়ুন; আপনাকে এটি টাইপ করতে হবে না।

quarantine = Agent(
    name="quarantine",
    model=config.MODEL,
    mode="task",
    instruction=QUARANTINE_INSTRUCTION,
    tools=[find_policy_hits, suggest_replacement],
    output_schema=CleanedDirection)

শিপ করা হলে, চেনটি পিক থেকে সরাসরি স্ক্রিপ্টারে চলে। 👉🖥 রাউটারকে তাদের মাঝে রাখুন, এর দুটি এক্সিট এবং কোয়ারেন্টাইন থেকে ফিরে আসার পথ সহ; তিনটি লাইন একটিকে প্রতিস্থাপন করে:

           (join_research, propose_directions, choose_direction, policy_check),
           (policy_check, {"OK": scripter, "BLOCK": quarantine}),
           (quarantine, scripter),

👉🖥 দুটি রুটই রান করুন। আইডিয়াটি যেমন আছে তেমনই পাঠান: choose_direction প্রার্থী ১-কে নেয়, policy_check OK নির্গত করে এবং স্ক্রিপ্টার একটি স্ট্রাকচার্ড স্ক্রিপ্ট লেখে। নতুন সেশন বিকল্পে ক্লিক করুন এবং শেষে , pick 4 সহ আইডিয়াটি আবার পাঠান: প্রার্থী ৪ হল টোপ, policy_check BLOCK নির্গত করে, কোয়ারেন্টাইন তার টুলকে কল করে, প্রত্যাখ্যাত শব্দগুলি আবার লেখে, finish_task-কে কল করে এবং পরিষ্কার করা নির্দেশ স্ক্রিপ্টারের কাছে ফিরে যায়। যাচাইকরণ রো সেশন স্টোর থেকে দুটি রানই পড়ে।

সেশন ২-এর প্রম্পটের প্রতিটি বাক্যে এখন একটি নোড রয়েছে: রিসার্চ হল দুটি রিডার এবং একটি জয়েন, রিফিউজাল হল একটি রাউটার এবং একটি টাস্ক এজেন্ট, স্ক্রিপ্ট হল একটি এজেন্ট নোড। পুরো গ্রাফে স্ট্যান্ড-ইন পিক হল দুটি ফাংশন নোড, direction_gate এবং persist_direction, যা আপনি সেশন ৫ দরজা বন্ধ না করা পর্যন্ত সরাসরি চলতে দেখবেন। ক্রিয়েটরের সাথে চুক্তি এখনও স্ট্যান্ড-ইন; রিসার্চের ক্ষেত্রেও তাই, যা এখনও দর্শকদের কথা শোনেনি।

৬. ৪ · মাল্টিমোডাল ইনজেশন — দর্শক যা পাঠিয়েছেন, একটি কর্পাসে পড়ুন

সেশন ৪ · মাল্টিমোডাল ইনজেশন

👉🖥 ওয়ার্কবেঞ্চে, সেশন ৪ · মাল্টিমোডাল ইনজেশন, ৪ক এবং ৪খ অংশ খুলুন। দর্শকরা শুধু লেখেননি। প্রতিটি ধরনের জন্য একজন রিডার তাদের পাঠানো সবকিছুকে একই আকারের টেক্সটে পরিণত করে, একটি RAG ইঞ্জিন কর্পাস এটি সব ধরে রাখে এবং ফ্যান-আউটে একজন তৃতীয় রিডার অর্থ অনুসারে এটি সার্চ করে এবং মিডিয়া ফেরত দেয়।

দর্শকরা কী পাঠিয়েছেন (4a)

agent/comments.md-এর কাছে চ্যানেলের হ্যালোউইন সংক্রান্ত ছয়টি ভিডিওর নিচে লেখা বারোটি কমেন্ট আছে। agent/audience/-এর কাছে বাকিগুলি আছে: আটটি ভয়েস নোট (voice/*.mp3), দর্শকদের নিজেদের রুমের চোদ্দটি ফটো (photo/*.jpg) এবং ছটি ক্লিপ (clip/*.mp4), একটি ফটোর উপর ধীরে ধীরে প্যান করা এবং তার সাথে দর্শকের ভয়েস ওভার। 4a পৃষ্ঠায় সব কিছু খোলে: প্রতিটি ভয়েস নোট চলে, প্রতিটি ফটো দেখা যায়, প্রতিটি ক্লিপ চলে।

ইনজেস্টের পরে অডিয়েন্স গ্রিড: Gemini যা শুনেছে বা দেখেছে তা প্রতিটি কার্ডে দেখানো হয়

RAG ইঞ্জিন টেক্সট ফিরিয়ে আনে। ভয়েস নোট, ফটো বা ক্লিপ শুধুমাত্র সেইভাবেই কর্পাসে যোগ করা যায় যেভাবে rag.caption_posters-এ আগের পোস্টারগুলি করা হয়েছিল: একটি মডেল এটিকে টেক্সটের লাইনে পরিণত করে যা এর ফাইলের নাম দেয়, লাইনটি একটি অনুচ্ছেদ হয়ে যায় এবং অনুচ্ছেদটি ফিরে এলে ফাইলটি তার সাথে ফিরে আসে। agent/platform/audience.py-এ প্রতি ধরনের জন্য একজন করে পাঠক আছেন। প্রত্যেকে Gemini-তে একটি ফাইল ও একটি প্রম্পট পাঠায় এবং প্রতিটি ফাইলের জন্য একটি লাইন লেখে:

VOICE_PROMPT = ("Transcribe this voice note word for word inside double quotes. Then add: mood: and one word. "
                "Plain text on one line, nothing else.")
PHOTO_PROMPT = ("Two sentences: what is in the picture, the palette, the mood. Then: tags: and three short tags. "
                "Plain text on one line, no headings, no markdown.")
CLIP_PROMPT = ("Describe the shots in two sentences, then quote what you HEAR on the soundtrack as: heard: \"...\". "
               "Plain text on one line, no headings, no markdown.")


def _ask(path: pathlib.Path, mime: str, prompt: str) -> str:
    """One media file in, one line of text out."""
    from google.genai import types as gt
    client = images._client()
    resp = client.models.generate_content(
        model=config.MODEL,
        contents=[gt.Part.from_bytes(data=path.read_bytes(), mime_type=mime), prompt],
        config=gt.GenerateContentConfig(automatic_function_calling=gt.AutomaticFunctionCallingConfig(disable=True)))
    return " ".join((resp.text or "").split())


def transcribe_voice() -> pathlib.Path:
    """voice note -> text: Gemini listens."""
    files = sorted((AUDIENCE / "voice").glob("*.mp3"))
    lines = []
    for p in files:
        said = _ask(p, "audio/mpeg", VOICE_PROMPT)
        lines.append(f"- voice: {p.name} — {said}")
    return _write(AUDIENCE / "voice" / "transcripts.md", "voice", "What Gemini heard in the audience's voice notes", lines)

caption_photos, image/jpeg ও PHOTO_PROMPT-এর সাথে একই কাজ করে; watch_clips, video/mp4 ও CLIP_PROMPT-এর সাথে এবং Gemini একটি কলে ছবি দেখে ও সাউন্ডট্র্যাক শোনে। প্রতিটি রিডার একই লাইন শেপ লেখে, তাই চাঙ্কিং, এম্বেডিং ও রিট্রিভাল কখনই জানতে পারে না যে কোন ধরনের প্যাসেজ থেকে এটি এসেছে:

- voice: note-03.mp3 — "I want a whole series of the cat judging costumes ..." mood: delighted
- photo: photo-01-ghost-drawer.jpg — A cream wooden drawer, striped socks, a paper-cup ghost ... tags: drawer, socks, cozy
- clip:  clip-05.mp4 — A hallway floor, a snail in a sheet and a round vacuum ... heard: "The snail won."
- poster: sock-ghost.png — A sheet ghost one inch above a striped sock ...

প্রতিটি ধরনের জন্য একজন রিডার, একটি লাইন শেপ, একটি কর্পাস

কর্পাস। Vertex AI RAG Engine হল ডকুমেন্ট থেকে তথ্য খুঁজে বের করার টুল: একটি কর্পাসকে প্রায় ১২০টি টোকেনের প্যাসেজে ভাগ করা হয়, প্রতিটি প্যাসেজ text-embedding-005-এর সাথে এম্বেড করা হয় এবং একটি কোয়েরি একই পদ্ধতিতে এম্বেড করা হয় এবং শেয়ার করা শব্দের মাধ্যমে নয়, অর্থের দিক থেকে সবচেয়ে কাছাকাছি প্যাসেজের মাধ্যমে উত্তর দেওয়া হয়। "বাড়িতে ছোটখাটো ভুতুড়ে কাণ্ড" প্রম্পটটি সেই কমেন্ট খুঁজে পায় যেখানে ছোট ঘরে ছোটখাটো ম্যাজিক দেখানোর কথা বলা হয়েছে, সেই ভয়েস নোট খুঁজে পায় যেখানে বাদুড় বাল্বকে জড়িয়ে ধরে আছে এবং সেই ক্লিপ খুঁজে পায় যার সাউন্ডট্র্যাকে বলা হয়েছে যে ভূতটি আসলে একটি কাগজের কাপ।

corpus = rag.create_corpus(
    display_name="vibestudio-feedback",
    backend_config=rag.RagVectorDbConfig(
        rag_embedding_model_config=rag.RagEmbeddingModelConfig(
            vertex_prediction_endpoint=rag.VertexPredictionEndpoint(
                publisher_model="publishers/google/models/text-embedding-005"))))

rag.upload_file(
    corpus_name=corpus.name, path="agent/comments.md", display_name="comments.md",
    transformation_config=rag.TransformationConfig(
        chunking_config=rag.ChunkingConfig(chunk_size=120, chunk_overlap=20)))

সেট-আপ ইতিমধ্যেই কর্পাস তৈরি করা শুরু করেছে; 4a পৃষ্ঠার কানেক্ট রোতে বলা আছে যে এটি আপ আছে কিনা এবং সেট-আপ না পারলে এটি তৈরি করে। পৃষ্ঠার বোতাম বা টার্মিনাল থেকে: python -m agent.platform.rag কর্পাস তৈরি করে এবং runs/ragcorpus.json-এ রেকর্ড করে; python -m agent.platform.rag load --audience audience.READERS-এ প্রতিটি রিডার রান করে, তারপর কমেন্ট এবং পোস্টারের ক্যাপশনের পাশে তারা যা লিখেছে তা আপলোড করে, প্রতিটি ফাইল তার নিজস্ব নামে এবং ইন্ডেক্সের উত্তরের জন্য অপেক্ষা করে; python -m agent.platform.rag query "cozy little hauntings at home" রিট্রিভাল ফাইন্ডিং প্যাসেজ দেখায় যা কোয়েরির সাথে কোনও শব্দ শেয়ার করে না।

এডিট। শিপ করা অবস্থায়, READERS-এ শুধু read_comments থাকে; তিনটি মিডিয়া রিডার একটি #-এর পিছনে থাকে। 👉🖥 এটি শিপ করার সাথে সাথে একবার ইনজেস্ট রান করুন: আউটপুটে not read (no reader wired in audience.READERS): voice, photo, clip দেখায় এবং শুধুমাত্র কমেন্ট ও ক্যাপশন ইনজেস্ট করা হয়। তারপর # মুছে দিন :

    transcribe_voice, caption_photos, watch_clips,

👉🖥 সেভ করে আবার ইনজেস্ট রান করুন। ৪০টি আইটেম পড়তে রিডারের প্রায় তিন মিনিট সময় লাগে; কনসোল একবারে একটি লাইন করে heard, saw ও watched বলে এবং গ্রিডের প্রতিটি কার্ড তার লাইন দিয়ে পূরণ করে। যাচাইকরণ সারি ফাইল, রিডারের আউটপুট এবং কর্পাস পড়ে: চারটি রিডার ওয়্যার্ড, আটটি ভয়েস নোট শোনা, চৌদ্দটি ফটো দেখা, ছয়টি ক্লিপ দেখা, কর্পাসে পাঁচটি ফাইল।

তৃতীয় রিডার (4b)

দর্শকের মতামত হল রিসার্চ, যা প্রতিটি রান শেয়ার করে, তাই এটি গ্রাফে প্রবেশ করে যেখানে রিসার্চ করে: যোগদানের মধ্যে তৃতীয় পাঠক হিসাবে। agent/graph.py-এ read_feedback আজকের রাতের আইডিয়া নেয়, কর্পাসকে সবচেয়ে কাছের প্যাসেজ সম্পর্কে জিজ্ঞাসা করে এবং join_research-কে রিপোর্ট করে, যেটি তিনটির জন্য অপেক্ষা করে। (START, read_feedback, join_research)-এর প্রান্ত stage5_rag/agent.py-এ শিপ করে, শেপ সেশন ৩ শেখানো হয়েছে; আপনার এডিট নোডের ভিতরে আছে। রিট্রিভাল, মডেল কল নয় এবং নির্ধারক: একই আইডিয়া একই প্যাসেজ খুঁজে পায়, যদিও প্রপোজার আলাদা হয়।

def read_feedback(node_input):
    """The third reader: what the audience sent - comments, and what Gemini
    heard and saw in their voice notes, photos and clips - the passages
    nearest to tonight's idea, and the media those passages came with.
    Retrieval, not a model call."""
    from .platform import rag
    idea = idea_text(node_input)
    query = idea or "what viewers liked and what they complained about"
    try:
        hits = rag.retrieve(query)
    except Exception as e:
        print(f"  [rag] feedback unavailable ({str(e)[:80]})")
        return Event(output={"query": query, "feedback": [], "media": [],
                             "note": "no corpus connected - run: python -m agent.platform.rag"})
    return Event(output={"query": query, "feedback": [h["text"] for h in hits]})  # TODO: RAG_MEDIA

কর্পাসের প্রতিটি লাইন যা কোনও ভয়েস নোট, ফটো, ক্লিপ বা পোস্টার থেকে এসেছে, তার ফাইলের নাম উল্লেখ করে। rag.media_in(hits) প্যাসেজ থেকে সেইসব নাম পড়ে এবং সেগুলিকে URL-এ পরিণত করে যা পৃষ্ঠাটি দেখাতে এবং চালাতে পারে। 👉🖥 প্যাসেজের সাথে মিডিয়া ফেরত দিন, যাতে দর্শক যা পাঠিয়েছেন তা তাদের বক্তব্যের সাথে যায়:

    return Event(output={"query": query, "feedback": [h["text"] for h in hits],
                         "media": rag.media_in(hits)})

👉🖥 কোনও আইডিয়া নিয়ে stage5_rag রান করুন। read_feedback খুলুন: কোয়েরি হল আপনার আইডিয়া, আউটপুটে পাঁচটি সবচেয়ে কাছাকাছি প্যাসেজ এবং প্রতিটি ফাইলের URL সহ একটি media তালিকা রয়েছে। join_research খুলুন: তিনটি কী। propose_directions খুলুন: ১ থেকে ৩ নম্বর ক্যান্ডিডেট তাদের প্রমাণে feedback উল্লেখ করেছেন। যাচাইকরণ প্যানেলে দেখা যাচ্ছে যে পাঠক সারির নিচে কী কী দেখেছেন: একটি ক্লিপ, তিনটি ভয়েস নোট এবং ফ্রিজের আলোতে ভূতের একটি পোস্টার।

তৃতীয় পাঠক কী দেখেছেন: যে মিডিয়া থেকে উদ্ধার করা অনুচ্ছেদ এসেছে

৭. ৫ · ভিডিও জেনারেশন — দুই ধরনের সাসপেনশন, একটি মেকানিজম

সেশন ৫ · ভিডিও জেনারেশন

👉🖥 ওয়ার্কবেঞ্চে, stage6_video-এ তৈরি করা সেশন ৫ · ভিডিও জেনারেশন, ৫ক এবং ৫খ অংশ, সম্পূর্ণ গ্রাফ খুলুন। ক্লিপটি কয়েক মিনিট এবং পোস্টারটি কয়েক সেকেন্ড সময় নেয় এবং রানটি পথে দুবার থামে: একবার আপনার জন্য, একবার Veo-এর জন্য। দুটি স্টপ হল একটি মেকানিজম এবং ডেস্কের দুটি শাখা তাদের তৈরি করা জিনিস নাম অনুসারে শেয়ার করে।

দুই ধরনের সাসপেনশন (5a)

কিছুই অপেক্ষা করে না। যে রানটি বন্ধ করতে হবে সেটি তার সেশনে একটি ওপেন কল লেখে এবং শেষ হয়; যে কেউ সেই কলের উত্তর দেয়, একজন ব্যক্তি বা একটি প্রসেস, এটি চালিয়ে যায়। পৃষ্ঠাটি একটি টেবিলে খোলে:

একজন ব্যক্তির জন্য · direction_gate

ধীরে কাজ করা টুলের জন্য · render_desk

কে রান বন্ধ করে

direction_gate থেকে RequestInput পাওয়া যায়

render_submit {status: pending} ফেরত দেয়; LongRunningFunctionTool এটিকে একটি স্টপ করে তোলে

এটি কিসের জন্য অপেক্ষা করে

একজন ব্যক্তি: চারটি কার্ডের মধ্যে একটি

একটি ধীরগতির কাজ: Veo-এর অপারেশন, মিনিট

সেশনে কী কী থাকে

একটি খোলা adk_request_input কল: মেসেজ, ফর্ম, পেলোড

একটি খোলা render_submit কল: অপারেশনের নাম সহ রসিদ

এটি কীভাবে আবার শুরু হয়

আপনার পছন্দ সহ সেই কলের আইডি সহ একটি function_response

সেই কলের আইডি সহ একটি function_response, যাতে ক্লিপের URL থাকে

কে পাঠায়

adk web, আপনি ফর্ম জমা দিলে

python -m agent.deliver, অন্য প্রসেস থেকে, পরে

where it lives meanwhile

runs/sessions.db · একটি সেশন আইডি · কিছুই রান করে না

একই সারি, একই আইডি · কিছুই রান করে না

দুই ধরনের সাসপেনশন, একটি টেবিল

একজন ব্যক্তির জন্য। একটি প্রম্পটে, "ক্রিয়েটরকে কনফার্ম করতে বলুন" হল পরামর্শমূলক এবং সেশন ২ একটি মেসেজে এটি সম্পর্কে কথা বলেছে। ওয়ার্কফ্লোতে পজ হল ইঞ্জিনের। RequestInput প্রদানকারী নোড রান সাসপেন্ড করে: ADK আপনার message, response_schema ও payload সহ সেশনে adk_request_input নামের একটি ওপেন কল লেখে, এটিকে একটি interrupt_id দেয় এবং কলারের কাছে ফিরে যায়। কোনও থ্রেড অপেক্ষা করে না। এটি আবার শুরু করার একমাত্র উপায় হল আপনার স্কিমার সাথে যাচাই করা সেই আইডি সহ function_response; ওয়ার্কফ্লোতে চ্যাট মেসেজ হল নতুন টার্ন, উত্তর নয়। স্কিমাও হল ফর্ম: adk web এটি রেন্ডার করে এবং VibeStudio অ্যাপ একই স্কিমাকে চারটি কার্ড হিসেবে রেন্ডার করে। direction_gate আগে থেকেই প্রার্থীদের নাম স্টেটে লিখে রাখে। 👉🖥 এটি বন্ধ করুন:

    yield RequestInput(
        message="Pick tonight's direction: 1, 2, 3 or 4.",
        response_schema={
            "type": "object",
            "properties": {
                "pick": {"type": "string", "enum": ["1", "2", "3", "4"]}}},
        payload={"candidates": cands})

আপনার উত্তর persist_direction-এ node_input হিসেবে আসে, একটি ডিক্ট যার একটি কী, pick। চারটি ক্যান্ডিডেট অন্যভাবে আসে: গেট তাদের candidates-এর অধীনে শেয়ার করা স্টেটে লিখেছে এবং ADK একই নামের প্যারামিটারে স্টেট কী বাইন্ড করে। নোডটি একটি প্রার্থীকে বেছে নেয় এবং direction, angle, hook এবং user:prefs-কে Event(state=...)-এর সাথে আবার লেখে। user: প্রিফিক্স সহ 'কী' সেই ব্যবহারকারীর সেশন জুড়ে থাকে।

ধীরে কাজ করা টুলের জন্য। Veo রেন্ডার করতে কয়েক মিনিট সময় লাগে এবং গ্রাফের জন্য এতক্ষণ অপেক্ষা করা উচিত নয়। agent/desk.py-এ render_submit জব শুরু করে এবং সাথে সাথে একটি রসিদ ফেরত দেয় যার স্ট্যাটাস হল pending:

def render_submit(prompt: str) -> dict:
    """Submit one Veo render of `prompt`. Returns at once with a pending
    receipt; the clip is delivered later, to this call, by id."""
    receipt = videogen.start(f"{prompt} {videogen.NO_TEXT}")
    return {"status": "pending", "operation": receipt["operation"], "prompt": receipt["prompt"]}

LongRunningFunctionTool-এ মোড়ানো, সেই রসিদ ADK-এর কাছে কিছু অর্থ বহন করে: এজেন্টের পালা শেষ হয়, ওয়ার্কফ্লো render_desk-এ সাসপেন্ড হয় এবং এর আইডি ও রসিদ সহ ওপেন কল হল runs/sessions.db-এর একটি সারি। 👉🖥 stage6_video/agent.py-এ, টুলটি র‍্যাপ করুন:

    tools=[LongRunningFunctionTool(render_submit)])

অন্য প্রসেস থেকে আইডি দিয়ে আবার শুরু করুন। agent/deliver.py সেশন স্টোরে বাকি থাকা কল খুঁজে পায়, ক্লিপটি না পাওয়া পর্যন্ত Veo-কে পোল করে, ফলাফল runs/state.json-এ লেখে এবং একই সেশনে সেই কলের আইডি সহ একটি function_response পাঠায়। 👉🖥 উত্তরের অংশ তৈরি করুন:

    part = Part(function_response=FunctionResponse(
        id=row["call_id"], name=row["name"], response=response))

👉🖥 stage6_video দুবার বন্ধ না হওয়া পর্যন্ত চালান। একটি আইডিয়া পাঠান: আপনার স্কিমা থেকে তৈরি করা ফর্মে রান বন্ধ হয়ে যায়, এর পেলোডে চারটি প্রার্থী। pick-এ 2 টাইপ করে জমা দিন; গ্রাফ persist_direction-এ আবার শুরু হবে। এখানে কিল টেস্ট করে দেখুন: ফর্মে সাসপেন্ড করুন, সার্ভার বন্ধ করুন (ট্যাব ১-এ scripts/stop.sh), এটি আবার চালু করুন (scripts/restart.sh), সেশন আবার খুলুন। ফর্মটি এখনও আছে, কারণ পজ হল একটি সারি, থ্রেড নয়। script_ready-এর পরে, render_desk render_submit-কে কল করে, রসিদ পায়, WAITING-কে উত্তর দেয় এবং রান শেষ হয়; সেশন কল আইডি রেখে দেয়। পৃষ্ঠার কনসোল বা ট্যাব ১ থেকে ডেলিভারি চালান:

cd ~/vibe-studio-multimodal
uv run python -m agent.deliver status   # the pending renders, by session, call id and operation
uv run python -m agent.deliver          # wait for Veo, answer the newest pending call by id

👉🖥 adk ওয়েব রিফ্রেশ করুন বিকল্পে ক্লিক করুন: function_response বাকি থাকা কল ফলো করে, join_media রিলিজ করে এবং store_release রান করে। আটটি যাচাইকরণ সারি সেশন থেকে দুটি স্টপ পড়ে: গেটের কল এবং আপনার উত্তর, রেন্ডারের রসিদ এবং এর ডেলিভারি।

স্টেট এবং ডেস্ক (5b)

মিডিয়া ডেস্ক হল 3a-এর মতো স্ট্রাকচার, একটি ফ্যান-আউট ও একটি জয়েন, সাথে একটি স্লো ব্রাঞ্চ। script_ready স্ক্রিপ্টটি দুটি উপায়ে পাঠায়। render_desk ক্লিপ জমা দেয় এবং রান সাসপেন্ড হয়ে যায়। make_poster হল নোড হিসেবে সেশন ২ টুল: স্ক্রিপ্টের poster_idea, চ্যানেলের স্টাইল এবং মেমরি ব্যাঙ্ক ক্রিয়েটরের পোস্টার সংক্রান্ত পছন্দ সম্পর্কে যা জানে (memory_facts, সেশন ৬ হিসেবে স্টেট থেকে বাউন্ড) তা ইমেজ মডেলে যায় এবং কয়েক সেকেন্ডের মধ্যে ৯:১৬ PNG ফিরে আসে। join_media ধীরগতির জন্য অপেক্ষা করে এবং store_release যা খুঁজে পায় তা স্টেটমেন্টে লিখে দেয়।

নাম দিয়ে শেয়ার করুন। store_release কখনও পোস্টার পায় না। এটি poster_url নামের একটি প্যারামিটার ঘোষণা করে এবং ADK এটিকে শেয়ার করা স্টেট থেকে বাইন্ড করে, কারণ অন্য শাখার একটি নোড সেই 'কী' লিখেছে। শাখাগুলির মধ্যে কোনও পেলোড ক্রস করে না:

def store_release(node_input, poster_url: str = "", poster_path: str = ""):
    """`poster_url` and `poster_path` are not passed by anyone: ADK binds them
    from shared STATE because the parameter names match the keys make_poster
    wrote (the STATE_SHARE edit). The two branches of the desk never hand each
    other payloads; they share what they made by name."""
    render = state.load().get("render") or {}          # the delivered clip, written by deliver
    url, status = render.get("url") or "", render.get("status", "")
    ...
    if not poster_url:
        said += " · poster: not in state (did make_poster write poster_url to state? the STATE_SHARE edit)"
    yield Event(output={"status": status, "url": url, "poster_url": poster_url},
                state={"render_url": url, "render_status": status, "poster_url": poster_url},
                message=f"video: {said} · poster: {poster_url or 'none'}")

শিপ করা হলে, make_poster আউটপুট হিসেবে পোস্টার ও মেসেজ দেয় এবং store_release বলে poster: not in state। 👉🖥 চূড়ান্ত ফলনে স্টেট রাইট যোগ করুন:

                state={"poster_url": rec.get("url", ""), "poster_path": rec.get("path", "")},
                message=f"poster: {said}")

👉🖥 আরও একবার রান করে ডেলিভার করুন। make_poster-এর ইভেন্ট খুলুন: এর আউটপুটে পোস্টারের URL এবং poster_url-এর সাথে একটি স্টেট ডেল্টা। store_release খুলুন: এর মেসেজে পোস্টারের নাম উল্লেখ করা আছে। পৃষ্ঠার এই রান সেশন ব্লক একই সারি adk web-এর স্টেট ট্যাব দেখায়: সেশন আইডি যা আবার শুরু করার জন্য প্রয়োজন, কল যা খোলা আছে এবং কী যা নোড লিখেছে, poster_url তাদের মধ্যে। যাচাইকরণ প্যানেলে ক্লিপ ও পোস্টার পাশাপাশি দেখানো হয়।

ডেলিভারির পরে ক্লিপ ও তার পোস্টার পাশাপাশি

৮. ৬ · মেমরি — চ্যানেলটি রাতের মধ্যে কী মনে রাখে

সেশন ৬ · মেমরি

👉🖥 ওয়ার্কবেঞ্চে, stage4_memory-এ তৈরি করা সেশন ৬ · মেমরি, ৬ক এবং ৬খ অংশ খুলুন। সেশন ৫-এ একটি রান সম্পর্কে আলোচনা করা হয়েছে। এই সেশনটি রান করার মধ্যে চ্যানেল কী মনে রাখে সেই সম্পর্কে: দুটি কলব্যাকের মাধ্যমে গ্রাফ বাইরে থেকে কী শোনে।

মেমরি ব্যাঙ্ক (6a)

Vertex AI Memory Bank হল কোনও ব্যক্তির সম্পর্কে ম্যানেজ করা দীর্ঘমেয়াদী মেমরি। ফ্যাক্টগুলি একটি স্কোপ, অ্যাপের নাম এবং ব্যবহারকারীর আইডি এবং আপনার সংজ্ঞায়িত বিষয়ের অধীনে থাকে। আপনি যখন memories.generate-এর সাথে কোনও এক্সচেঞ্জ হ্যান্ড-ওভার করেন, তখন এটি কোনও বিষয়ের সাথে মানানসই তথ্য এক্সট্র্যাক্ট করে, এটি এম্বেড করে এবং এটি আগে থেকে যা ধারণ করে তার সাথে একত্রিত করে: একটি নতুন ফ্যাক্ট তৈরি করা হয়, একটি পরিবর্তিত ফ্যাক্ট আপডেট করা হয়, একটি পুনরাবৃত্তি মার্জ করা হয়। memories.retrieve কোনও স্কোপের জন্য ফ্যাক্ট রিটার্ন করে। দুটি কলই agent/platform/memory.py-এ লাইভ আছে।

SCOPE = {"app_name": config.APP, "user_id": config.USER}
TOPICS = {
    "CREATOR_TASTE": "Which video directions this creator picks and passes on, "
                     "and how that preference changes over time.",
    "CHANNEL_RULES": "Standing instructions the creator states for every video "
                     "(style, subjects to avoid, format rules).",
    "VISUAL_STYLE": "What this creator approves and rejects in a poster: palettes, "
                    "framing, props and mood of the posters they shipped.",
}

তৃতীয় বিষয়টি হল চ্যানেলের ছবির রুচি। রিলিজটি সেশন ৫-এ এটি লিখেছে: একটি শিপ করা পোস্টার একটি বাক্যে বর্ণনা করা হয়েছে এবং মনে রাখা হয়েছে এবং পরবর্তী পোস্টারটি তার প্রম্পটে সেই বাক্যটি দিয়ে আঁকা হয়েছে।

সেট-আপ ইতিমধ্যেই ব্যাঙ্ক তৈরি করা শুরু করেছে; 6a পৃষ্ঠার কানেক্ট রোতে এটি চালু আছে কিনা তা বলা আছে। পৃষ্ঠার বোতাম বা টার্মিনাল থেকে: python -m agent.platform.bank ব্যাঙ্ক ও তার বিষয় তৈরি করে এবং runs/memorybank.json-এ রিসোর্সের নাম ক্যাশে করে; python -m agent.platform.bank load চারটি পুরনো সেশন সিড করে (স্টাইল রুল সহ দুটি অ্যানিমেল এরা, একটি গ্যাজেট এরা, একটি সাম্প্রতিক ফ্যান্টাসি এরা); python -m agent.platform.bank list দেখায় যে ট্রান্সক্রিপ্ট কী হয়েছে: ফ্যাক্টের কনসোলিডেট করা স্টেটমেন্ট, ট্রান্সক্রিপ্ট নয়।

কলব্যাক (6b)

মেমরি কোনও নোড নয়। কলব্যাক হল এমন একটি ফাংশন যা আপনি Agent-এ পাস করেন; ADK লাইভ কন্টেক্সট সহ একটি নির্দিষ্ট মুহূর্তে এটিকে কল করে এবং None রিটার্ন করলে স্বাভাবিক জিনিসটি ঘটতে দেয়। তিনটি পেয়ার: এজেন্টের টার্নের আশেপাশে (before_agent_callback, after_agent_callback, সাথে CallbackContext), প্রতিটি মডেল কলের আশেপাশে (LlmRequest-এর সাথে before_model_callback, LlmResponse-এর সাথে after_model_callback), প্রতিটি টুল কলের আশেপাশে। গার্ডরেল, টেলিমেট্রি, ক্যাশে এবং কনটেক্সট ইনজেকশন এখানে যায়, গ্রাফে কোনও নোড ছাড়াই।

stage4_memory/agent.py-এ, propose_directions তার মডেল কলের আগে ব্যাঙ্ক পড়ে: recall_taste ফ্যাক্টগুলি নিয়ে আসে, সেগুলিকে অনুরোধে একটি মেমরি ব্লক হিসাবে যোগ করে, সবচেয়ে পুরনোটি প্রথমে, এবং মডেলকে বলে যে ক্যান্ডিডেট ১ থেকে ৩-কে বর্তমান স্বাদের দিকে ঝুঁকতে এবং নিয়মগুলিকে সীমাবদ্ধতা হিসাবে বিবেচনা করতে। এটি memory_facts-এ স্টেট করার জন্য তথ্যও লেখে, যেখানে make_poster সেগুলি খুঁজে পায়। 👉🖥 ওয়্যার ট্রান্সফার করুন:

    output_schema=Directions,
    before_model_callback=recall_taste)

ক্রিয়েটর তার টার্নে যা বেছে নিয়েছেন স্ক্রিপ্টার তা লেখেন: remember_pick রাজ্য থেকে direction পড়েন, পছন্দ সম্পর্কে একটি বাক্য লেখেন এবং memories.generate-কে তা দেন। 👉🖥 ওয়্যার ট্রান্সফার:

    output_schema=Script,
    after_agent_callback=remember_pick)

👉🖥 কোনও মেসেজ না লিখে রান করুন এবং একটি কার্ড বেছে নিন। propose_directions ইভেন্টটি খুলুন: অনুরোধে তিনটি যুগ রয়েছে এবং ১ থেকে ৩ নম্বর প্রার্থী বর্তমান স্বাদের দিকে ঝুঁকেছেন, যদিও ট্রেন্ড অন্য কিছু সাজেস্ট করে। স্ক্রিপ্টারের পরে, ব্যাঙ্ক দেখান-এ ক্লিক করুন: আজকের রাতের পছন্দের জন্য একটি মেমরি পরিবর্তন করা হয়েছে বা যোগ করা হয়েছে। দ্বিতীয় রাতে চালান: এটি প্রথম রাতের কথা মনে রাখে।

৯. ৭ · ডেপ্লয় ও প্রকাশ করা — অ্যাপের মধ্যে গ্রাফ, VibeTube-এ নাইট

সেশন ৭ · ডিপ্লয় ও প্রকাশ করা

👉🖥 ওয়ার্কবেঞ্চে, সেশন ৭ · ডিপ্লয় ও প্রকাশ করুন, ৭ক এবং ৭খ অংশ খুলুন।

VibeStudio (7a) ডিপ্লয় করুন

স্যান্ডবক্সে, adk web গ্রাফটি চালিয়েছে। VibeStudio এটি ADK-এর Runner-এর মাধ্যমে চালায়, নিজস্ব ইভেন্ট লুপ সহ একটি ওয়ার্কার থ্রেডে এবং প্রতিটি ইভেন্টকে একটি রান স্টেটে ফোল্ড করে, পৃষ্ঠাটি সার্ভার-প্রেরিত ইভেন্টগুলি পড়ে: রিডার, আপনার বেছে নেওয়া চারটি কার্ড, পোস্টারটি যেমন ল্যান্ড করে, রেন্ডারটি যেমন ডেলিভার করা হয়। গেটের উত্তর এবং রেন্ডারের ডেলিভারি একই কল, একটি function_response অংশ এবং অ্যাপটি কনসোলের জন্য অপেক্ষা করার পরিবর্তে Veo-কে নিজেই পোল করে।

self._svc = DatabaseSessionService(db_url=config.DB_URL)
self._runner = Runner(app_name=config.APP, agent=wf, session_service=self._svc)

async for ev in self._runner.run_async(user_id=config.USER, session_id=run_id, new_message=message):
    self._absorb(ev)    # fold the ADK event into the run state, publish one app event

part = Part(function_response=FunctionResponse(id=call_id, name=name, response=response))

vibestudio/server/agent/ হল ল্যাবের agent/-এর বাইট-ফর-বাইট কপি, যা checks/verify_app.py চেক করেছে। agent/graph.py-এ সম্পূর্ণ এজ লিস্ট হল এই কোডল্যাবের প্রতিটি স্ট্রাকচার এক জায়গায়, তেরোটি নোড; এটি পূরণ করা অবস্থায় পাঠানো হয়, যাতে আপনি পড়তে পারেন, টাইপ করতে না হয়:

        (START, scan_trends, join_research),
        (START, read_backlog, join_research),
        (START, read_feedback, join_research),
        (join_research, propose_directions, direction_gate,
         persist_direction, policy_check),
        (policy_check, {"OK": scripter, "BLOCK": quarantine}),
        (quarantine, scripter),
        (scripter, script_ready),
        (script_ready, render_desk, join_media),
        (script_ready, make_poster, join_media),
        (join_media, store_release),

👉🖥 ওয়ার্কবেঞ্চের ডিপ্লয় বোতামের সাহায্যে ডিপ্লয় করুন অথবা 👉💻 ট্যাব ১ থেকে:

cd ~/vibe-studio-multimodal
uv run python vibestudio/deploy.py

যা আপনার .env থেকে পাওয়া ভ্যালু দিয়ে পূরণ করা হয়:

gcloud run deploy vibestudio --source vibestudio \
  --project $GOOGLE_CLOUD_PROJECT --region us-central1 \
  --labels dev-tutorial-codelab=vibetube --allow-unauthenticated \
  --memory 2Gi --cpu 2 --timeout 3600 --concurrency 40 \
  --max-instances 1 --min-instances 1 --session-affinity \
  --set-env-vars GOOGLE_CLOUD_PROJECT=...,STUDIO_VERTEX=1,STUDIO_MEMORY_BANK=...,STUDIO_RAG_CORPUS=...,VIBETUBE_URL=...,VIBETUBE_EVENT=...,VIBETUBE_NAME=...,VIBETUBE_PROJECT=...

--source Cloud Build-এর মাধ্যমে কন্টেনার তৈরি করে এবং এক ধাপে এটি ডিপ্লয় করে; প্রথমবার তৈরি করতে চার বা পাঁচ মিনিট সময় লাগে। --session-affinity with one warm instance keeps a suspended run on the instance that holds it: the session store of session 5, once more. আরও বড় ডিপ্লয়মেন্টে সেশন স্টোর VertexAiSessionService-এ চলে যায় এবং অ্যাফিনিটি চলে যায়। বিল্ড ও পরিষেবা প্রোজেক্টের ডিফল্ট কম্পিউট পরিষেবা অ্যাকাউন্ট হিসেবে রান করে, যে সেট-আপ প্রয়োজনীয় ভূমিকা প্রদান করে।

👉🖥 পরিষেবার URL খুলুন এবং অ্যাপে একটি নাইট রান করুন: একটি আইডিয়া, একটি বাছাই, ক্লিপ ও পোস্টারের জন্য অপেক্ষা করুন। ইতিহাস ট্যাবে দুটিই থাকে।

প্রকাশ করুন (7b)

vibestudio/server/platform/publish.py-এ, VibeTube-এ রুমের এন্ডপয়েন্টে অ্যাপ থেকে একটি মাল্টিপার্ট POST হল প্রকাশ করা। ক্লিপ হল ভিডিও, পোস্টার হল থাম্বনেল এবং প্রোফাইল হল রুমের নাম ও আপনি:

url = f"{prof['platform_url']}/api/events/{prof['event_code']}/videos"
data = {"title": state.script["title"], "description": state.script["description"],
        "duration": "0:08", "displayName": prof["display_name"],
        "projectId": prof["project_id"]}        # one video per project and room: publishing again replaces it
files = {"videoFile": (clip.name, clip.open("rb"), "video/mp4"),
         "thumbnailFile": (poster.name, poster.open("rb"), "image/png")}   # the poster, not a frame
r = httpx.post(url, data=data, files=files, timeout=120)
video_id = r.json()["id"]                       # watch it at {platform}/e/{event}?v={video_id}

VibeTube প্রতিটি projectId এবং রুমের জন্য একটি ভিডিও রাখে এবং অ্যাপটি যে প্রোজেক্ট পাঠায় সেটি হল আপনার Google Cloud প্রোজেক্ট, তাই আবার প্রকাশ করলে আপনার ভিডিও যোগ করার পরিবর্তে সেটি পরিবর্তন করা হয়। স্ট্যান্ড-ইন পোস্টার কখনও আপলোড করা হয় না; আসল পোস্টারটিই থাম্বনেল হিসেবে ব্যবহার করা হয়, বাইট ফর বাইট।

শেষ এডিটটি হল একটি কনফিগারেশন, কোডের লাইন নয়। 👉🖥 7b পৃষ্ঠায়: VibeStudio URL (7a-এর ডেপ্লয় থেকে পূরণ করা), রুমের ইভেন্ট কোড, ঠিক যেভাবে VibeTube-এ বানান লেখা আছে (আপনি সেট-আপে যেটি দিয়েছেন, যদি না আপনার ইনস্ট্রাক্টর অন্য কোনওটি খুলে থাকেন) এবং আপনার নাম। প্রকাশ করুন বিকল্পে প্রেস করুন। ওয়ার্কবেঞ্চ অ্যাপে ক্লিক রিলে করে, অ্যাপ নাইট পোস্ট করে এবং নিচের সারি অ্যাপ ও VibeTube থেকে ফলাফল পড়ে: অ্যাপ উত্তর দেয়, আসল পোস্টারের মাধ্যমে নাইট শেষ হয়, কোড নেম একটি ওপেন রুম, VibeTube একটি ভিডিও আইডি ফিরিয়ে দেয়, ভিডিওটি ওয়ালে আছে এবং এর থাম্বনেল হল আপনার পোস্টার। ফলাফল কার্ডে VibeTube-এ থাম্বনেল দেখায়, অ্যাপে পোস্টারের পাশে।

প্রকাশিত: VibeTube-এ থাম্বনেল হল ইমেজ মডেলের আঁকা পোস্টার

১০. আপনি যা তৈরি করেছেন

👉🖥 7b পৃষ্ঠার শেষে পুরো গ্রাফটি আঁকা হয়েছে, একটি আইডিয়া থেকে একটি ক্লিপ এবং তার পোস্টার পর্যন্ত তেরোটি নোড; কোনও নোড কী করে এবং কোন সেশন এটি তৈরি করেছে তা জানতে তার উপর মাউস নিয়ে যান।

সেশন

এটি কী শিখিয়েছে

এইভাবে লেখা হয়েছে

২ · ইমেজ জেনারেশন

An agent: model, instruction, tools; the image model as a tool; what a prompt cannot promise

Agent(tools=[...]), function_call / function_response, images.generate

৩ · ওয়ার্কফ্লো

এজ লিস্ট, ফ্যান-আউট ও জয়েন, এর পিছনে টাস্ক-মোড লুপ সহ রাউটার

Workflow, START, JoinNode, Event(route=...), mode="task", finish_task

৪ · মাল্টিমোডাল ইনজেশন

প্রতিটি প্রকারের জন্য একটি করে রিডার, একটি লাইন শেপ, একটি কর্পাস; মিডিয়া সহ অর্থ অনুযায়ী তৃতীয় রিডার

অডিও, ছবি ও ভিডিও সহ generate_content, rag.upload_file, rag.retrieve, media_in

৫ · ভিডিও জেনারেশন

Two kinds of suspension, one mechanism; the session store; the poster shared by name

RequestInput, LongRunningFunctionTool, FunctionResponse(id=...), Event(state=...), প্যারামিটার বাইন্ডিং

৬ · মেমরি

Memory across nights, two callbacks

memories.generate / retrieve, before_model_callback, after_agent_callback

৭ · ডিপ্লয় ও প্রকাশ করা

Cloud Run-এ, অ্যাপের ভিতরে গ্রাফ; VibeTube-এ রাত

Runner(agent=wf), run_async, সার্ভার-প্রেরিত ইভেন্ট, একটি মাল্টিপার্ট POST

গ্রাফটি পাঁচটি নিয়ম মেনে চলে:

  1. অপেক্ষা করার পরিবর্তে সাসপেন্ড করুন। পজ হল সেশন স্টোরের একটি সারি, কোনও থ্রেড নয়।
  2. সবকিছু একই উপায়ে আবার শুরু করুন — ওপেন কলের আইডি অনুযায়ী, কোনও ব্যক্তি বা প্রসেস উত্তর দেয় কিনা।
  3. স্টেটে নাম অনুযায়ী ডেটা শেয়ার করুন, প্রতিটি পেলোডের মাধ্যমে থ্রেড করে নয়।
  4. মডেলের জন্য পেমেন্ট করার আগে কোড ব্যবহার করে সিদ্ধান্ত নিন। নীতিটি হল একটি টেক্সট ফাইল এবং একটি রাউটার এবং এটি রেন্ডার করার আগে রান করে।
  5. কর্পাসে পৌঁছানোর আগে টেক্সট নয় এমন সবকিছু টেক্সটে পড়ুন, লাইনের মধ্যে ফাইলের নাম সহ যাতে ফাইলটি ফিরে আসতে পারে।

প্রম্পট কোনও অর্ডার, স্টপ বা চেকের প্রতিশ্রুতি দিতে পারে না। গ্রাফ পারে।

এর পরে কোথায় নিয়ে যেতে হবে

  • VertexAiSessionService সেশনের জন্য যা একটি ইনস্ট্যান্সের চেয়ে বেশি সময় ধরে চলে এবং --session-affinity ড্রপ করে।
  • একটি ওয়েবহুক যা পোলারের পরিবর্তে রেন্ডারের কলে উত্তর দেয়: একই FunctionResponse(id=...), আলাদা প্রেরক।
  • প্রকাশ করার আগে আরও একটি RequestInput — স্কিমা হল ফর্ম, তাই এটি একটি নোড এবং কোনও পৃষ্ঠা কোড নেই।
  • প্রতিটি রিলিজের পরে, দর্শকদের নতুন কমেন্ট, ভয়েস নোট এবং ক্লিপগুলি কর্পাসে যোগ করা হয়, সাথে পোস্টারটি যার অধীনে পাঠানো হয়েছিল।

অপ্রয়োজনীয়গুলি মুছুন

এই ল্যাবে তৈরি করা সবকিছু প্রোজেক্টের ৪ নম্বর ধাপে থাকে, তাই সবচেয়ে সস্তায় ক্লিন-আপ করার উপায় হল প্রোজেক্টটি মুছে ফেলা। Cloud Shell-এ:

gcloud projects delete $(cat ~/project_id.txt)

প্রোজেক্ট রেখে দিয়ে শুধু পেড অংশগুলি সরাতে: Cloud Run পরিষেবা (gcloud run services delete vibestudio --region us-central1), RAG কর্পাস ও মেমরি ব্যাঙ্ক (এগুলির রিসোর্সের নাম runs/ragcorpus.json ও runs/memorybank.json-এ আছে এবং কনসোলের Agent Engine ও RAG Engine পৃষ্ঠা থেকে এগুলি মুছে দেওয়া হয়) এবং Artifact Registry-তে কন্টেনার ইমেজ।