ADK 2 অর্কেস্ট্রেশন: গ্রাফ, সহযোগিতামূলক এবং গতিশীল ওয়ার্কফ্লো

১. সংক্ষিপ্ত বিবরণ

ADK 2-এর মূল বিষয় হলো তিনটি অর্কেস্ট্রেশন প্যাটার্ন । এই কোডল্যাবটি একটি অ্যাপ—একটি ম্যারাথন রেস ডে কোচ —তৈরি করার মাধ্যমে এই তিনটি প্যাটার্নই শেখায়, যেখানে ধাপে ধাপে একটি করে চালনাযোগ্য স্তর যুক্ত করা হয়। প্রতিটি স্তর একটিমাত্র প্রশ্নের উত্তর দেয়, একটি নতুন ধারণা যোগ করে এবং নিজে নিজেই চলে।

আপনি যা শিখবেন

  • গ্রাফ ওয়ার্কফ্লো (স্তম্ভ ১) — যখন ইনপুট আসার আগেই কার্যপ্রবাহটি এঁকে নেওয়া যায়।
  • সহযোগী এজেন্ট (স্তম্ভ ২) — যখন আপনি দলটিকে চেনেন কিন্তু অনুরোধটি একটি উপদলকে বেছে নেয় — এবং তিনটি সহযোগিতা মোডই ( chat / task / single_turn ), প্রতিটি সরাসরি চলে।
  • গতিশীল কর্মপ্রবাহ (স্তম্ভ ৩) — যখন কাজের কাঠামোটিই প্রদত্ত তথ্যের ওপর নির্ভর করে।
  • কীভাবে নির্বাচন করবেন — একটি এক-প্রশ্নভিত্তিক ডিসিশন ট্রি এবং প্যাটার্নগুলো কীভাবে গঠিত হয়।

মূলভাব

পরিচিত কাঠামো → পরিচিত দল / পরিবর্তনশীল উপসেট → অপরিচিত আকৃতি → সঠিকটি বেছে নিন

আপনার শেখার রোডম্যাপ

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

একটি অ্যাপ— ম্যারাথন রেস ডে কোচ —একবারে একটি করে খেলার যোগ্য লেভেল তৈরি করেছে। প্রতিটি লেভেল হলো একটি সাধারণ পাইথন মডিউল যা আপনি টার্মিনাল থেকে চালান; লেভেল ৫-এ পৌঁছানোর পর নিচের অংশগুলো সবই আপনার হয়ে যাবে।

ছবিটি চলমান কোড থেকে আঁকা হয়েছে: প্রতিটি অবিচ্ছিন্ন রেখা Workflow.graph.edges থেকে পড়া হয়েছে। আর এটাই প্রথম শিক্ষা — যে অংশগুলো আগে থেকে আঁকা যায়, সেগুলোই হলো প্রথম স্তম্ভ, এবং যে অংশগুলো আঁকা যায় না, সে কারণেই দ্বিতীয় ও তৃতীয় স্তম্ভের অস্তিত্ব রয়েছে।

পুরো অ্যাপটি — এবং যা একটি গ্রাফ আপনাকে দেখাতে পারে না

আপনার যা যা লাগবে

  • একটি গুগল অ্যাকাউন্ট (কোলাবের জন্য) — স্থানীয়ভাবে কোনো সেটআপের প্রয়োজন নেই
  • প্রায় ৫০ মিনিট (L4-এর দুটি লেভেল দীর্ঘ হয় — সেগুলোর জন্য বাজেট রাখুন)।
  • জেমিনি মডেলে পৌঁছানোর দুটি উপায়ের মধ্যে একটি। আপনার পথটি বেছে নিন — আপনি একটি সেটআপ ধাপ সম্পন্ন করুন এবং অন্যটি বাদ দিন:

🎓 কর্মশালা

🏠 বাড়িতে নিয়ে যাওয়ার জন্য

WHO

আপনি একটি লাইভ ওয়ার্কশপে আছেন এবং প্রশিক্ষক আপনাকে একটি ক্রেডিট দাবি করার লিঙ্ক দিয়েছেন।

এরপরে কর্মশালার অংশগ্রহণকারীসহ বাকি সবাই

আপনার প্রয়োজন

ক্লেইম লিঙ্ক, এবং একটি গুগল অ্যাকাউন্ট যা দিয়ে ক্লাউড প্রজেক্ট তৈরি করা যায়।

একটি বিনামূল্যের এআই স্টুডিও এপিআই কী

দৌড় চলছে

ভার্টেক্স এআই, আপনার ওয়ার্কশপ ক্রেডিটে বিল করা একটি প্রকল্পে

গুগল এআই স্টুডিও

খরচ

ক্রেডিট দ্বারা আচ্ছাদিত

ফ্রি টিয়ার

সেটআপ ধাপ

ওয়ার্কশপ স্থাপন (পরবর্তী ধাপ)

বাড়িতে নিয়ে যাওয়ার সেটআপ (পরবর্তী ধাপ)

প্রোলগ থেকে পরবর্তী সবকিছু উভয় ক্ষেত্রেই অভিন্ন — লেনটি কেবল নির্ধারণ করে যে নোটবুকটি কোন মডেল এন্ডপয়েন্টের সাথে যোগাযোগ করবে।

অনুসরণ করার দুটি উপায়

নীচের প্রতিটি ধাপ কোলাব নোটবুকের একটি সেল এবং গিটহাব রিপোজিটরির একটি ফোল্ডারের সাথে সম্পর্কিত। যেকোনো একটি বেছে নিন:

  • ▶ কোলাব (প্রস্তাবিত): নোটবুকটি খুলুন → সেলগুলো উপর থেকে নিচে চালান।
  • 💻 স্থানীয়ভাবে: রিপোটি git clone , ./setup_venv.sh চালান, তারপর প্রতিটি লেভেলকে একটি মডিউল হিসেবে চালান ( python -m ... ) অথবা ./run.sh ( adk web ) দিয়ে সবগুলো ব্রাউজ করুন।

২. ওয়ার্কশপ সেটআপ · আপনার ক্রেডিট দাবি করুন এবং ভার্টেক্স এআই-তে সুইচ করুন

কর্মশালায় আপনাকে গুগল ক্লাউড ক্রেডিট দেওয়া হবে। আপনি এটি ক্লেইম করবেন, এর অধীনে একটি প্রজেক্ট তৈরি করবেন এবং নোটবুকটি এআই স্টুডিও-র পরিবর্তে ভার্টেক্স এআই-এর দিকে নির্দেশ করবেন। ক্লেইম করার পরের সমস্ত কাজ একটি সেল দিয়েই সম্পন্ন হয়।

১ · আপনার ক্রেডিট দাবি করুন (~১ মিনিট)

  1. আপনার প্রশিক্ষক যে ক্লেইম লিঙ্কটি শেয়ার করেছেন, সেটি খুলুন। লিঙ্কটি দেখতে https://me.developers.google.com/benefits/claim/your-workshop-name এর মতো।
  2. সাইন ইন করুন এবং ক্রেডিটটি গ্রহণ করতে পৃষ্ঠাটি অনুসরণ করুন।
  3. আপনি কোন গুগল অ্যাকাউন্ট ব্যবহার করেছেন তা মনে রাখুন। নিচের প্রতিটি ধাপ সেই একই অ্যাকাউন্ট দিয়ে চালাতে হবে।

২ · নোটবুকটি খুলুন এবং ADK 2 ইনস্টল করুন (~১ মিনিট)

কোলাবে খুলুন ▶ -এ ক্লিক করুন, তারপর প্রথম কোড সেলটি চালান। এটি এই কোডল্যাবটি যে ADK 2 সংস্করণে যাচাই করা হয়েছিল তা নির্দিষ্ট করে এবং ✓ installed প্রিন্ট করে।

৩ · 'ওয়ার্কশপ সেটআপ' সেলটি চালান (~৩ মিনিট)

এটি হলো 🎓 Path A · Workshop শিরোনামের সেলটি। এটি রান করলে Colab আপনাকে অনুমতি চাইতে বলবে — যে Google অ্যাকাউন্টটি দিয়ে আপনি এইমাত্র ক্রেডিট দাবি করেছেন, সেটিই বেছে নিন এবং অ্যাক্সেসের অনুমতি দিন।

এটি চারটি কাজ করে: আপনার অ্যাকাউন্টে adk-2-tutorial-XXXX নামের একটি প্রজেক্ট তৈরি করে, সেটিতে Vertex AI API সক্রিয় করে, পরবর্তী প্রতিটি সেলের রিড করা চারটি এনভায়রনমেন্ট ভেরিয়েবল সেট করে, এবং তারপর Vertex-এ একটি টেস্ট কল করে তার উত্তরের জন্য অপেক্ষা করে — ফলে সেটআপটি কোনো লেভেলের ভেতরে পরে ব্যর্থ না হয়ে, হয় তার কাজ শেষ করে অথবা আপনাকে ব্যর্থতার কারণ জানিয়ে দেয়।

প্রত্যাশিত আউটপুট — শেষ লাইনটিই গুরুত্বপূর্ণ:

Signed in as: you@example.com
...
Successfully created GCP project 'adk-2-tutorial-4817'.
Successfully linked 'adk-2-tutorial-4817' to billing account '01ABCD-...'.
   waiting for Vertex AI to come up on the new project... (10s)
   waiting for Vertex AI to come up on the new project... (20s)

 Vertex AI on adk-2-tutorial-4817 · us-central1 · gemini-2.5-flash  answered a test call

৪ · 'টেক-হোম সেটআপ' ধাপটি এড়িয়ে যান

AI Studio কী সেলটি চালাবেন না — এটি নোটবুকটিকে AI Studio-তে ফিরিয়ে দেবে এবং আপনি এইমাত্র যা করেছেন তা বাতিল করে দেবে। (সেলটি এটি প্রতিরোধ করে এবং চলতে অস্বীকার করবে, কিন্তু আরও পরিচ্ছন্ন উপায় হলো এটিকে এড়িয়ে যাওয়া।) এখান থেকে সরাসরি Shared building blocks সেলে যান।

৫ · "Shared building blocks" সেলটি চালান

এটি একবার চালান। এটি পাইড্যান্টিক স্কিমা ও পূর্বনির্ধারিত ম্যারাথন সিনারিওগুলো নির্ধারণ করে, যা L2 থেকে শুরু করে পরবর্তী প্রতিটি লেভেলে পুনরায় ব্যবহৃত হয়। আপনি ✓ schemas + scenarios ready দেখতে পাবেন।

কর্মশালার পরে

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

দ্রুত পরিষ্কার করার জন্য: ক্লাউড কনসোল খুলুন, adk-2-tutorial-XXXX নির্বাচন করুন এবং এটি মুছে ফেলুন। এই কোডল্যাবের অন্য কোনো কিছুই বিলযোগ্য রিসোর্স তৈরি করে না।

৩. বাড়িতে ব্যবহারের জন্য সেটআপ · এআই স্টুডিও এপিআই কী

এই পথের সবকিছু একটি বিনামূল্যের গুগল এআই স্টুডিও এপিআই কী (API key) দিয়ে চলে — কোনো গুগল ক্লাউড প্রজেক্ট, বিলিং বা লোকাল ইনস্টলের প্রয়োজন নেই। এই পুরো ধাপটিতে প্রায় ৩ মিনিট সময় লাগে।

১ · নোটবুকটি খুলুন

কোলাবে খুলুন ▶-এ ক্লিক করুন। আপনি নোটবুকটিতে চলে আসবেন — একটি মার্কডাউন ভূমিকা, এবং তারপর প্রতিটি স্তরের জন্য একটি করে রান করার যোগ্য সেল। আপনি সেলগুলো উপর থেকে নিচে রান করবেন; প্রতিটি সেল তার নিজস্ব আউটপুট ঠিক তার নিচে প্রিন্ট করবে।

২ · ADK 2 ইনস্টল করুন (~১ মিনিট)

প্রথম কোড সেলটি চালান। এটি সেই নির্দিষ্ট সংস্করণটি চিহ্নিত করে, যেটিতে এই কোডল্যাবটি যাচাই করা হয়েছিল:

%pip install -q "google-adk==2.3.0" python-dotenv pydantic nest_asyncio

এটি শেষ হওয়া পর্যন্ত অপেক্ষা করুন — আপনি ✓ installed দেখতে পাবেন। (প্রথমবার ইনস্টল হতে প্রায় ৩০-৬০ সেকেন্ড সময় লাগে; এরপর থেকে এটি ক্যাশে সংরক্ষিত থাকে।)

৩ · এআই স্টুডিও থেকে আপনার জেমিনি এপিআই কী সংগ্রহ করুন (~১ মিনিট)

  1. একটি নতুন ব্রাউজার ট্যাবে aistudio.google.com/app/apikey খুলুন।
  2. আপনার গুগল অ্যাকাউন্ট দিয়ে সাইন ইন করুন।
  3. এপিআই কী তৈরি করুন (উপরে-ডানদিকে) বোতামে ক্লিক করুন।
  4. গুগলের বিদ্যমান কোনো প্রজেক্ট বেছে নিন অথবা গুগলকে একটি তৈরি করতে দিন।
  5. কী-টি কপি করুন — এটি AIza... দিয়ে শুরু হয় এবং এতে প্রায় ৪০টি অক্ষর রয়েছে।

৪ · আপনার কী কোলাবে যোগ করুন (~১ মিনিট)

বিকল্প A — কোলাব সিক্রেটস (প্রস্তাবিত; চাবিটি গোপন থাকে):

  1. কোলাবের বাম সাইডবারে থাকা 🔑 চাবির আইকনটিতে ক্লিক করুন।
  2. + নতুন গোপন তথ্য যোগ করুন -এ ক্লিক করুন।
  3. নামটি হুবহু GOOGLE_API_KEY তে সেট করুন।
  4. আপনার কী-টি Value- তে পেস্ট করুন।
  5. নোটবুক অ্যাক্সেস চালু করুন।

বিকল্প B — নির্দেশিত হলে পেস্ট করুন (দ্রুত): সিক্রেটটি দেওয়ার প্রয়োজন নেই; আপনি যখন পরবর্তী সেলটি চালাবেন তখন একটি লুকানো প্রম্পট দেখাবে 🔑 Enter your Google AI Studio API key: — পেস্ট করে এন্টার চাপুন।

৫ · মূল সেলটি চালান

এটি সিক্রেটটি পড়ে (অথবা পেস্ট প্রম্পটে ফিরে যায়), তারপর ADK-কে AI Studio-র দিকে নির্দেশ করে (Vertex AI-এর দিকে নয়):

import os

# 🏠 TAKE-HOME ONLY — if you ran the Workshop setup cell, skip this one.
if os.environ.get("GOOGLE_GENAI_USE_VERTEXAI") == "True":
    raise SystemExit("✋ You're set up on the workshop path (Vertex AI). Skip this cell.")

# Google AI Studio API key — add GOOGLE_API_KEY in the 🔑 Secrets panel (or paste when prompted).
try:
    from google.colab import userdata
    key = userdata.get("GOOGLE_API_KEY")
except Exception:
    import getpass
    key = getpass.getpass("Enter your Google AI Studio API key: ")

os.environ["GOOGLE_API_KEY"] = "".join(key.split())    # drop any stray whitespace/newlines
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False"      # use AI Studio, not Vertex AI
print("✅ API key set — using Google AI Studio.")

প্রত্যাশিত আউটপুট: ✅ API key set — using Google AI Studio.

৬ · "Shared building blocks" সেলটি চালান

'শেয়ার্ড বিল্ডিং ব্লকস' সেলটি একবার চালান। এটি পাইড্যান্টিক স্কিমা ও পূর্বনির্ধারিত ম্যারাথন সিনারিওগুলো সংজ্ঞায়িত করে, যা L2 থেকে পরবর্তী প্রতিটি লেভেল পুনরায় ব্যবহার করে। আপনি ✓ schemas + scenarios ready ' দেখতে পাবেন।

আপনার প্রস্তুতি সম্পন্ন! 🎽 L0- এর আগে একটি ছোট্ট ধাপ — যে সংস্করণটি সবাই প্রথমে তৈরি করে।

৪. ভূমিকা · একটি বড় নির্দেশিকা নয় কেন?

⚡ এটি চালানোর আগে, একটি বিষয় বিশেষভাবে লক্ষ্য করুন: প্রতিটি নির্দিষ্ট সংখ্যা কোথা থেকে আসছে? এটাই হলো মূল বিষয় — বাকি সবকিছুই কেবল অলঙ্করণ।

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

আপনি যা দেখবেন: একটি আত্মবিশ্বাসী, সুনির্দিষ্ট, সুবিন্যস্ত কৌশল... যার সংখ্যাগুলো মনগড়া । একটি লাইভ রানে এটি শুরু হয়েছিল "আমি আজকের আবহাওয়ার মেট্রিকস সংগ্রহ করেছি" বলে এবং রিপোর্ট করেছিল ৫২°F, ৯ মাইল প্রতি ঘণ্টার বাতাস, এবং এমন একটি ট্রেনিং লগের বিশ্লেষণ যা এটি কখনও দেখেনি। এখানে কোনো ওয়েদার এপিআই নেই, কোনো কোর্স ডেটা নেই, কোনো লগ নেই — একটি অস্বচ্ছ মডেল কল হয় এর ইনপুটগুলো মনগড়াভাবে তৈরি করে অথবা সেগুলোকে এমনভাবে বিকৃত করে যে তা অকেজো হয়ে পড়ে।

এটাই সেই রোগ, এবং এর চারটি লক্ষণ রয়েছে যা উল্লেখ করার মতো:

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

সেই অনুভূতিটা ধরে রাখুন। পরবর্তী নয়টি লেভেলে , প্রম্পট থেকে সেই ধাপগুলো এক এক করে বের করে আনা হয়েছে: ফাংশন ফেচ করে (L1–L2a), একটি if স্টেটমেন্ট রাউট করে (L2b), বিশেষজ্ঞরা কাজ ভাগ করে নেয় (L3a–L3b), এবং কোড তার আকৃতিকে সীমাবদ্ধ করে (L4a–L4b)।

অত্যন্ত দ্রুত প্রশিক্ষক — আত্মবিশ্বাসী, যার চার্টের পেছনে কোনো রহস্য নেই।

💻 Local: python -m shared.prologue

৫. L0 · আপনার প্রথম ADK 2 এজেন্ট

রোডম্যাপ — আপনি এখানে আছেন: L0

⚡ সংক্ষেপে: একটি এজেন্ট হলো একটি মডেল, একটি নির্দেশনা এবং তার ব্যবহৃত বিভিন্ন টুল ; একটি Runner সেই নির্দেশনাটি কার্যকর করে। এই স্তরের পরের সবকিছুই হলো আরও কিছু এজেন্ট, যা আরও উন্নত আকারে সাজানো।

প্রশ্নটি হলো: আপনি কি একটি মডেলকে দিয়ে উত্তর আদায় করতে পারেন — এবং গাণিতিক কাজের প্রয়োজনে সেটিকে আসল কোড লিখতে উদ্বুদ্ধ করতে পারেন?

একই ধারণা — তিনটি অংশ:

  • Agent — যা যুক্তি দেয় (মিথুন রাশির একটি মডেল + একটি নির্দেশ)।
  • Runner — এটি এমন একটি অংশ যা একটি সেশনের মধ্যে এজেন্টকে কার্যকর করে এবং ইভেন্ট স্ট্রিম করে।
  • একটি টুল — একটি সাধারণ পাইথন ফাংশন ( pace_splits ) যা মডেলটি কল করার সিদ্ধান্ত নেয় । ADK সিগনেচার ও ডকস্ট্রিং পড়ে এবং মডেলকে একটি ডিক্লারেশন প্রদান করে; কোনো স্কিমা লেখার প্রয়োজন হয় না।

ভূমিকার পর এটাই প্রথম সংশোধন: একটি এলএলএম (LLM) যখন মনে মনে পেস অ্যারিথমেটিক করে, তখন সে সানন্দে ভুল করবে — pace_splits ) হলো ডিটারমিনিস্টিক পাইথন, তাই উত্তরের সংখ্যাগুলো গণনা করা হয়, তাৎক্ষণিকভাবে তৈরি করা হয় না

কোলাব: L0 সেলটি চালান · 📁 গিটহাব: L0_first_agent/ · 💻 লোকাল: python -m L0_first_agent.agent

L0 প্রবাহ

def pace_splits(target_finish: str) -> dict:
    """Convert a goal time like '3:30:00' into exact per-mile / per-km paces."""
    ...                                  # deterministic Python — no LLM

pace_coach = Agent(
    name="pace_coach", model=MODEL,
    tools=[pace_splits],                 # the model may call it; ADK reads the signature
    instruction="You are a friendly, concise marathon coach. ... If the runner "
                "mentions a goal time, call pace_splits — never do arithmetic yourself.",
)
runner = Runner(node=pace_coach, session_service=InMemorySessionService(), auto_create_session=True)
async for event in runner.run_async(user_id="u1", session_id="s1", new_message=msg):
    ...  # events carry the model's text

🔍 মার্কারগুলো হলো: Agent(...) · tools=[pace_splits] · Runner(...) । এবং আউটপুটে, 🔧 লাইনটি — এটি হলো মডেলের সিদ্ধান্ত , যা উত্তর দেওয়ার মাঝপথেই আপনার কোডকে কল করে।

আপনি যা দেখতে পাবেন:

   🔧 model called tool  pace_splits({'target_finish': '3:30:00'})
   🔧 tool returned      {'per_mile': '8:00', 'per_km': '4:58', ...}
🧠 Coach: To finish in 3:30:00, you need an average pace of 8:00 per mile...

🔧 লাইনগুলোই হলো শিক্ষা: উত্তরের মাঝপথে, মডেলটি আপনার ফাংশনটি কল করার সিদ্ধান্ত নেয় , এবং এর উত্তরে আসা সুনির্দিষ্ট 8:00/mile সময়টি আপনার কোড থেকেই এসেছে — টোকেন পরিসংখ্যান থেকে নয়।

আপনার মনে প্রশ্ন জাগতে পারে: মডেলটি কি সবসময় টুলটিকে কল করে? না — এটি প্রতিটি প্রশ্নের জন্য আলাদাভাবে সিদ্ধান্ত নেয়। সংখ্যা ছাড়া কিছু জিজ্ঞাসা করুন এবং 🔧 লাইনগুলো অদৃশ্য হয়ে যাবে (প্লেগ্রাউন্ডে আপনাকে ঠিক এটাই চেষ্টা করতে বলা হয়)।

👀 পড়ুন: pace_splits (একটি সাধারণ ফাংশন) এবং tools=[pace_splits] লাইনটি। · ▶ এটি চালান । · ✏️ পরিবর্তন করুন: সাধারণ প্রশ্নটি জিজ্ঞাসা করুন (কোনো নির্দিষ্ট সময়সীমা ছাড়া) — লক্ষ্য করুন 🔧 লাইনগুলো অদৃশ্য হয়ে যাচ্ছে: মডেলই সিদ্ধান্ত নেয় কখন একটি টুল কল করা উচিত। তারপর instruction পুনরায় লিখুন এবং আবার চালান — নির্দেশনাটিই হলো প্রোগ্রামের বাকি অংশ।

৬. এল১ · আপনার প্রথম কর্মপ্রবাহ

রোডম্যাপ — আপনি এখানে আছেন: L1

⚡ সারসংক্ষেপ: একটি সাধারণ ফাংশন এবং একটি এলএলএম এজেন্ট একই ধরনের নোড । অনুমানযোগ্য কাজ → ফাংশন (০ এলএলএম, ডিটারমিনিস্টিক); যুক্তি → এজেন্ট।

প্রশ্নটি হলো: শুধুমাত্র কোডের অংশগুলোর জন্য মডেল কলের ফি প্রদান না করে, কীভাবে সাধারণ কোড এবং একটি এলএলএম-কে একই প্রক্রিয়ায় একত্রিত করা যায়?

মূল ধারণাটি হলো: একটি Workflow তে, একটি সাধারণ পাইথন ফাংশন এবং একজন এলএলএম এজেন্ট উভয়ই একই edges লিস্টের নোড মাত্র।

START ──► fetch_conditions (function, 0 LLM) ──► advise (agent, 1 LLM)

কোলাব: L1 সেলটি চালান · 📁 গিটহাব: L1_graph_basics/ · 💻 লোকাল: python -m L1_graph_basics.workflow

L1 প্রবাহ

ফাংশন নোডটি তার উৎপাদিত ডেটা প্রিন্ট করে (কোনো মডেল কল ছাড়াই), তারপর এজেন্টটি তার প্রাপ্ত প্রকৃত তাপমাত্রা এবং বাতাসের গতি উল্লেখ করে পরামর্শ দেয়:

def fetch_conditions(node_input):                # function node — 0 LLM
    return Event(output=Conditions(temp_f=78, wind_mph=12, conditions="sunny").model_dump())

advise = Agent(name="advise", model=MODEL, mode="single_turn",
               input_schema=Conditions, instruction="...give pacing + gear advice...")

workflow = Workflow(edges=[(START, fetch_conditions, advise)])

🔍 মার্কারগুলো হলো: একটি এজ টাপল — (START, fetch_conditions, advise) — যার মাঝখানে একটি সাধারণ পাইথন ফাংশন রয়েছে, এবং input_schema= যা হস্তান্তর প্রক্রিয়াটি যাচাই করে।

L0-এর তুলনায় নতুন কী আছে: Workflow(edges=[...]) , START (যেখান থেকে ইনপুট প্রবেশ করে), একটি ফাংশন নোড যা Event(output=...) রিটার্ন করে, এবং input_schema=Conditions , যার ফলে এজেন্ট ফাংশনটির আউটপুট দেখার আগে তা ওই স্কিমার সাপেক্ষে যাচাই করা হয় (JSON টেক্সট হিসেবে — input_schema শুধু বাউন্ডারি যাচাই করে, এটি এজেন্টকে কোনো পাইথন অবজেক্ট দেয় না)।

আপনি হয়তো ভাবছেন: ফাংশন-তারপর-এজেন্ট কি আবশ্যক ক্রম? না — যেকোনো ক্রম, যেকোনো মিশ্রণ, যেকোনো সংখ্যা। advise দ্বিতীয়তে চলে শুধুমাত্র কারণ এটির fetch_conditions এর ডেটা প্রয়োজন। শিক্ষাটি হলো ক্রম নয়, বরং পারস্পরিক সম্পর্ক।

👀 পড়ুন: fetch_conditions কোনো মডেল কল ছাড়াই ডেটা রিটার্ন করে; advise input_schema=Conditions রয়েছে। · ▶ এটি চালান । · ✏️ পরিবর্তন করুন: ফাংশনটিতে temp_f=30 সেট করে আবার চালান — `advice`-টি পাল্টে যাবে, এবং ফাংশনটি চালাতে আগের মতোই ০টি LLM কল লাগবে।

7. L2a · সমান্তরাল ফ্যান-আউট + JoinNode (স্তম্ভ 1a)

রোডম্যাপ — আপনি এখানে আছেন: L2a

⚡ সারসংক্ষেপ: সমান্তরালভাবে ছড়িয়ে দিন (বিনামূল্যে), সবগুলোর জন্য অপেক্ষা করুন, একত্রিত করুন, এবং একজন এজেন্টের হাতে সম্পূর্ণ চিত্রটি তুলে দিন।

প্রশ্নটি হলো: ইনপুট আসার আগেই আপনি কার্যপ্রবাহটি এঁকে নিতে পারেন। একটি কাঠামো দিয়ে শুরু করুন: সমান্তরালভাবে ডেটা সংগ্রহ করুন, সেগুলোকে একত্রিত করুন এবং একজন এজেন্টের হাতে তুলে দিন।

আকৃতি:

START ──► fetch_weather ──┐
START ──► analyze_course ─┼─► JoinNode ─► strategy (1 agent)
START ──► pull_fitness ───┘   (bundles)

কোলাব: L2a সেলটি চালান · 📁 গিটহাব: L2a_parallel_join/ · 💻 লোকাল: python -m L2a_parallel_join.workflow

L2a প্রবাহ

🔍 চিহ্নিতকারীগুলো হলো: তিনটি প্রান্ত যা START থেকে শুরু হয় — এটাই হলো বিস্তার — এবং JoinNode , যা হলো মিলনস্থল।

  • তিনটি ফেচ হলো ফাংশন — এগুলো সমান্তরালভাবে চলে, কোনো LLM কল হয় না।
  • JoinNode তিনটির জন্যই অপেক্ষা করে এবং সেগুলোকে ফাংশনের নাম দ্বারা কী করা একটি টাইপড পেলোডে ( BundledRunData ) একত্রিত করে।
  • একটি strategy এজেন্ট বান্ডেলটি পড়ে একটি RaceStrategy লেখে।

আপনি যা দেখতে পাবেন: প্রতিটি ফেচ একটি started এবং finished টাইমস্ট্যাম্প প্রিন্ট করে। তিনটিই ০.০ সেকেন্ডে শুরু হয় এবং ফ্যান-আউটটি ২.০ সেকেন্ডে শেষ হয় — যা সবচেয়ে ধীরগতির ফেচ, তাদের মোট সময়কালের যোগফল ৪.৫ সেকেন্ড নয়। এই ওভারল্যাপটিই হলো প্যারালেলিজম। (শেষে প্রিন্ট করা মোট ওয়াল টাইম প্রায় ৮ সেকেন্ড, কারণ এতে স্ট্র্যাটেজি এজেন্টের এলএলএম কলটিও অন্তর্ভুক্ত থাকে — প্যারালেল দাবির জন্য মোট সময় নয়, ফেচ টাইমস্ট্যাম্পগুলো পড়ুন।)

💡 প্রোলগ কলব্যাক: মেগা-প্রম্পটটি তার নিজস্ব আবহাওয়া তৈরি করেছে । এখানে তাপমাত্রাটি একটি ফেচ ফাংশন থেকে আসে — আসল কোড, আসল গঠন। পূর্বনির্ধারিত ডিক্টটিকে একটি প্রকৃত আবহাওয়া এপিআই দিয়ে বদলে দিলেও আর কিছুই পরিবর্তন হয় না।

আপনি হয়তো ভাবছেন: কত

JoinNode

আমার কি এটা বোঝার দরকার আছে? এক বাক্যে বলি: এটি প্রতিটি প্যারালাল ব্রাঞ্চ ল্যান্ড করা পর্যন্ত অপেক্ষা করে, আউটপুটগুলোকে আপস্ট্রিম ফাংশনের নাম দিয়ে কী করা একটি ডিক্টে প্যাক করে, এবং নিজে কিছুই গণনা করে না। ঠিক এই ডিক্টটির কারণেই L2b-এর রাউটার node_input["fetch_weather"]["temp_f"] লিখতে পারে।

👀 পড়ুন: তিনটি এজ START থেকে ফ্যানের মতো ছড়িয়ে পড়ে; JoinNode একটি এজেন্টের জন্য সেগুলোকে বান্ডিল করে। · ▶ এটি চালান এবং মোট সময় নয়, টাইমস্ট্যাম্পগুলো পড়ুন। · ✏️ পরিবর্তন: একটি ফেচ-কে sleep 3.0 করুন — প্রথমে নতুন ফ্যান-আউটের শেষ সময় অনুমান করুন, তারপর যাচাই করুন।

৮. L2b · ডিটারমিনিস্টিক রাউটার যোগ করুন (পিলার 1b)

রোডম্যাপ — আপনি এখানেই আছেন: L2b

⚡ সারসংক্ষেপ: L2a অপরিবর্তিত রাখা হয়েছে + একটি সাধারণ if স্টেটমেন্ট দিয়ে ঠিক করা হয় কোন এজেন্টটি চলবে। মডেলকে না জিজ্ঞেস করেই ব্রাঞ্চিং করা হয়।

প্রশ্নটি হলো: গরম ও ঠান্ডা আবহাওয়ার জন্য পরিকল্পনাটি ভিন্ন হওয়া উচিত। মডেলকে সিদ্ধান্ত নিতে না বলে, আপনি কীভাবে এর বিভাজন করবেন?

আকৃতি (L2a + একটি রাউটার):

... JoinNode ─► route_by_weather ─► hot_strategy
               (if-statement)   ─► normal_strategy
                                ─► cold_strategy

কোলাব: L2b সেলটি চালান — try run("NORMAL") / run("COLD") · 📁 গিটহাব: L2b_router/ · 💻 লোকাল: python -m L2b_router.workflow COLD

L2b প্রবাহ

def route_by_weather(node_input):                        # an if-statement, 0 LLM
    temp = node_input["fetch_weather"]["temp_f"]
    route = "HOT" if temp >= 70 else "COLD" if temp <= 40 else "NORMAL"
    return Event(output=node_input, route=route)

(route_by_weather, {"HOT": hot_strategy, "NORMAL": normal_strategy, "COLD": cold_strategy})

🔍 মার্কারগুলো হলো: Event(output=..., route=...) — একটি ফাংশন নোড যা পাথটির নাম নির্দেশ করে — এবং ডিক্ট-এজ {"HOT": ..., "NORMAL": ..., "COLD": ...} যা নামগুলোকে নোডের সাথে সংযুক্ত করে।

সারকথা হলো— তিন ধরনের কাজ, তিন ধরনের বাসস্থান:

  • পূর্বাভাসযোগ্য কাজ → ফাংশন (৩টি সমান্তরাল ফেচ)
  • একটি সুস্পষ্ট নিয়ম → সুস্পষ্ট রাউটিং ( route_by_weather একটি if স্টেটমেন্ট, কোনো মডেল ডিসিশন নয়)
  • যুক্তি → মডেল (এজেন্ট ঠিক একটি কৌশলই প্রয়োগ করে)

আপনি যা দেখতে পাবেন: temp=78F -> route=HOT , তারপর একটি স্ট্রাকচার্ড RaceStrategyমোট খরচ: ১টি LLM কল।

⚠️ আপনি যদি চতুর্থ কোনো ব্রাঞ্চ যোগ করেন, তাহলে রাউট-ডিক্টটিতেও একটি DEFAULT_ROUTE এন্ট্রি দিন। ডিক্টটির সাথে মেলে না এমন কোনো রাউট কোনো ত্রুটি নয় — ব্রাঞ্চটি কেবল শেষ হয়ে যায় এবং প্রোগ্রামটি কোনো আউটপুট ছাড়াই 0 কোড দিয়ে এক্সিট করে, যা ডিবাগ করার জন্য একটি বিভ্রান্তিকর ডেড এন্ড।

আপনি হয়তো ভাবছেন: তাহলে L2b কি আক্ষরিক অর্থেই L2a-এর সাথে একটি রাউটার যুক্ত করা? হ্যাঁ — ফেচ এবং জয়েন অপরিবর্তিত থাকে, এবং এটি এখনও ঠিক ১টি LLM কল । যা পরিবর্তিত হয়েছে: "সবসময় একই এজেন্ট" থেকে তা হয়ে গেছে "ডেটা দ্বারা নির্বাচিত তিনটির মধ্যে একটি"।

👀 পড়ুন: route_by_weather — রাউটারটি একটি if -statement, কোনো এজেন্ট নয়। · ▶ run run("COLD") কমান্ডটিও চালান । · ✏️ পরিবর্তন করুন: চতুর্থ এজেন্টসহ একটি WINDY ব্রাঞ্চ যোগ করুন — এবং এটি করার আগে উপরের DEFAULT_ROUTE সতর্কবার্তাটি পড়ুন।

৯. এল৩এ · সহযোগী এজেন্ট: একটি পতাকা, দুটি বিশ্ব — স্তম্ভ ২

রোডম্যাপ — আপনি এখানে আছেন: L3a

⚡ সংক্ষেপে: একই দল, একটি পতাকা। chat পুরো কথোপকথনটি একজন বিশেষজ্ঞের হাতে তুলে দেয় এবং আর ফিরে আসে না; single_turn প্রতিটি বিশেষজ্ঞকে একটি টুলে পরিণত করে — সমান্তরাল উপসেট, স্বয়ংক্রিয় প্রত্যাবর্তন, একটি সংশ্লেষণ।

প্রশ্নটি হলো: আপনি দলটিকে চেনেন, কিন্তু অনুরোধটিই ঠিক করে দেয় কোন সদস্যরা উত্তর দেবে। আপনি কীভাবে একজন এলএলএম-কে (LLM) দলের একটি উপসেট বেছে নিতে দেবেন — এবং তাদের একই সাথে পরিচালনা করবেন?

কাঠামোটি হলো: ছয়জন বিশেষজ্ঞের (চিকিৎসা, আবহাওয়া, গতি, সরঞ্জাম, পুষ্টি, মানসিক) অধীনে একজন সমন্বয়কারী। এই স্তরে একই দল দুইবার কাজ করে — একই সমন্বয়কারীর নির্দেশ, একই ছয়জন বিশেষজ্ঞ। একমাত্র পার্থক্য হলো উপ-এজেন্টদের উপর একটি ফ্ল্যাগ। এই বৈসাদৃশ্যই হলো শিক্ষা।

কোলাব: L3a সেলটি চালান · 📁 গিটহাব: L3a_collaborative/ · 💻 লোকাল: python -m L3a_collaborative.concierge --mode chat "What about fueling?"

L3a প্রবাহ

🔍 চিহ্নিতকারীগুলো হলো: ফ্যাক্টরিতে mode="single_turn" — এবং আউটপুটে , TRANSFER → (বিট ১) বনাম একই টাইমস্ট্যাম্প শেয়ার করা DISPATCH → লাইনগুলোর একটি গুচ্ছ (বিট ২)।

ধাপ ১ · প্রথমে ডিফল্টটি চালান — এবং দেখুন এটি কাজটি করতে ব্যর্থ হয়।

কোনো mode= লিখিত → সাবএজেন্টরা ডিফল্টভাবে chat ব্যবহার করে। আপনি যা দেখতে পাবেন:

TRANSFER  nutrition_specialist   (transfer_to_agent  the only tool chat subagents provide)
Final speaker: nutrition_specialist

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

এটা কোনো বাগ নয় — চ্যাট মোড তার কাজই করছে। যতক্ষণ না কেউ স্পষ্টভাবে কথোপকথনটি হস্তান্তর করছে, ততক্ষণ এটি যার কাছে থাকে। একটি উন্মুক্ত অ্যাসিস্ট্যান্টের জন্য এটি ঠিক; কিন্তু একটি পাইপলাইন স্টেপের জন্য ভুল।

বিট ২ · এক পতাকা, দুই বিশ্ব

একমাত্র পার্থক্য: প্রতিটি বিশেষজ্ঞের জন্য mode="single_turn" । একই প্রশ্ন, আবার চালান:

[t= 7.8s] DISPATCH  medical_specialist       same timestamp =
[t= 7.8s] DISPATCH  weather_specialist         one turn, many calls
[t=14.5s]    medical_specialist replied      replies land inside
[t=14.5s]    weather_specialist replied        one short window
🧠 Concierge (synthesized): <one answer>

এখন ADK প্রতিটি স্পেশালিস্টের জন্য একটি করে ডেলিগেশন টুল ইনজেক্ট করে — যার নামকরণ করা হয় সাবএজেন্টের নামে এবং এর বর্ণনা দেওয়া হয় description= দিয়ে (এই লেখাটিই কোঅর্ডিনেটর সাবসেট বেছে নেওয়ার সময় পড়ে; এটি বাদ দিলে আপনি শুধু নামের ওপর ভিত্তি করে রাউটিং করবেন)। কোঅর্ডিনেটর এক টার্নে বেশ কয়েকটি কল নির্গত করে, ADK সেগুলোকে সমান্তরালভাবে চালায়, প্রতিটি স্বয়ংক্রিয়ভাবে তার ফলাফল ফেরত দেয় এবং কোঅর্ডিনেটর সিন্থেসাইজ করে।

প্রশ্ন

বিশেষজ্ঞরা যারা গুলি চালায়

জ্বালানি ভরার ব্যাপারে কী হবে?

শুধুমাত্র পুষ্টি

১৮ মাইলে আমার হাঁটুতে ব্যথা করছে।

শুধুমাত্র চিকিৎসার জন্য

আজ কি আমার দৌড় প্রতিযোগিতায় অংশ নেওয়া উচিত?

চিকিৎসা + আবহাওয়া + গতি

আমার কি কোনো কিছু নিয়ে চিন্তিত হওয়া উচিত?

সব ৬টি

কেন প্রত্যেক স্পেশালিস্টকে সম্পূর্ণ ব্রিফিংটি দেওয়া হয়: প্রতিটি single_turn সাবএজেন্ট তার নিজস্ব বিচ্ছিন্ন সেশন ব্রাঞ্চে চলে — এটি কথোপকথন বা তার সমকক্ষদের দেখতে পায় না। কোনো কিছুই অ্যাম্বিয়েন্ট নয়: কোঅর্ডিনেটরকে অবশ্যই সম্পূর্ণ SpecialistInput (প্রশ্ন + কৌশল + রানার ডেটা) প্রতিটি প্যারালাল কলে আলাদাভাবে ফরোয়ার্ড করতে হয়।

💡 ADK 2 যেখানে এটিকে সরাসরি একটি জায়গা দিয়েছে: একটি LLM প্রতিটি অনুরোধের জন্য একটি উপসেট বেছে নেয় এবং সেটিকে সমান্তরালভাবে চালায় — যা sub_agents + mode="single_turn" এর মাধ্যমে ঘোষিত হয় । আপনি 1.x সংস্করণেও প্রতিটি স্পেশালিস্টকে AgentTool দিয়ে মুড়ে দিয়ে একই কাঠামো তৈরি করতে পারতেন; পরিবর্তনটা হলো, এটি এখন একটি ঘোষণা, কোনো অভ্যন্তরীণ সংযোগ নয়। ( ParallelAgent সর্বদা-সব-কিছু-চালু রাখে এবং transfer_to_agent ক্রমিক।)

⚠️ দুটি স্পষ্ট সতর্কবার্তা: (1) মডেলটি সাবসেটটি বেছে নেয়, তাই এটি L2-এর হার্ড-কোডেড রাউটারের চেয়ে কম সুনির্দিষ্ট — সঠিক সাবসেটটি এক রান থেকে অন্য রানে পরিবর্তিত হতে পারে। (2) মাঝে মাঝে আপনি একজন স্পেশালিস্টের জন্য " Error validating input: ... লাইনটি দেখতে পাবেন। এটি প্রায় কখনোই স্পেশালিস্টের আউটপুট নয় — output_schema জেমিনিকে সার্ভার-সাইডে এটি প্রয়োগ করতে বাধ্য করে। এটি ইনপুটের সমস্যা : কোঅর্ডিনেটরকে প্রতিটি প্যারালাল কলের জন্য সম্পূর্ণ নেস্টেড SpecialistInput হুবহু পুনরুৎপাদন করতে হয়, এবং কখনও কখনও এটি একটিতে ভুল করে ফেলে। ADK সেই টুলের ফলাফল হিসাবে ত্রুটিটি ফেরত দেয়, কোঅর্ডিনেটর তা সামলে নেয়, এবং সিন্থেসিসটি ঠিকই সম্পন্ন হয়।

আপনি হয়তো ভাবছেন :

chat

ঠিক 1.x-স্টাইলের ডেলিগেশন — একবারে একজন এজেন্ট? মূলত হ্যাঁ: এটি 1.x-এর ডিফল্ট আচরণ, এখন শুধু একটি নাম দেওয়া হয়েছে। single_turn এর সাথে এর পার্থক্যটি ত্রি-মাত্রিক: কোঅর্ডিনেটর কী ধারণ করে ( প্রতি স্পেশালিস্টের জন্য একটি transfer_to_agent বনাম একটি tool) · কতজন কাজ করতে পারে (একজন, কথোপকথনের মালিক হিসেবে বনাম সমান্তরালভাবে N জন) · নিয়ন্ত্রণ ফিরে আসে কিনা (কখনোই না বনাম স্বয়ংক্রিয়ভাবে, ফলাফল সহ)। আর কোডের কথা বলতে গেলে: ফ্যাক্টরির if mode == branch শুধুমাত্র এই পার্থক্যের জন্য একটি টিমকে উভয় উপায়ে তৈরি করার জন্যই বিদ্যমান — একটি আসল অ্যাপ একটি মোড হার্ডকোড করে দেয় এবং if টি অদৃশ্য হয়ে যায়।

👀 পড়ুন: _specialist ফ্যাক্টরি — mode প্যারামিটারটি হলো সম্পূর্ণ লেভেল। · ▶ উভয় বিট চালান । · ✏️ পরিবর্তন করুন: জিজ্ঞাসা করুন "১৮ মাইলে আমার হাঁটুতে ব্যথা করছে"প্রথমে সাবসেটটি প্রেডিক্ট করুন , তারপর DISPATCH লাইনগুলো চেক করুন।

# The factory's mode parameter is THE variable this level teaches:
def _specialist(name, domain, focus, mode):
    kwargs = {}
    if mode == "single_turn":   # the structured contract only makes sense for a TOOL
        kwargs = dict(mode="single_turn",
                      input_schema=SpecialistInput, output_schema=SpecialistResponse)
    return Agent(name=name, model=MODEL,
                 description=f"Marathon {domain} specialist. Consult for: {focus}.",
                 instruction=..., **kwargs)

race_concierge = Agent(name="race_concierge", model=MODEL,
                       sub_agents=[...six specialists...],   # NOTE: no `mode` on the coordinator
                       instruction="...DECIDE which specialists are relevant... call them IN PARALLEL... SYNTHESIZE...")

১০. এল৩বি · টাস্ক মোড: একটি সমাপ্তি রেখা সহ কথোপকথন — স্তম্ভ ২

রোডম্যাপ — আপনি এখানে আছেন: L3b

⚡ সংক্ষেপে: মধ্যবর্তী পদ্ধতি — ফিল্ডগুলো সংগ্রহ না হওয়া পর্যন্ত ব্যবহারকারীর সাথে কথা বলুন, তারপর একটি যাচাইকৃত অবজেক্ট স্বয়ংক্রিয়ভাবে ফেরত পাঠান।

প্রশ্নটি হলো: L3a একটি ফাঁক রেখে গেছে। chat পুরো কথোপকথনটির মালিক; single_turn ব্যবহারকারীর সাথে একেবারেই কথা বলে না। কিন্তু আসল তথ্য সংগ্রহের কাজটি এর মাঝেই থাকে: "ব্যবহারকারীর সাথে ততক্ষণ কথা বলো যতক্ষণ না তুমি X পরিমাণ তথ্য সংগ্রহ করছো — তারপর একটি যাচাইকৃত অবজেক্ট নিয়ে ফিরে এসো।" এটি কোন মোড?

আকৃতি:

race_desk (coordinator)
  └─ gear_fitter (mode="task", output_schema=GearOrder)

কোলাব: L3b সেলটি চালান · 📁 গিটহাব: L3b_task_desk/ · 💻 লোকাল: python -m L3b_task_desk.desk

L3b প্রবাহ

gear_fitter টাস্কটিকে খোলা রাখছে — এটি একটি বিরতি দেওয়া টাস্ক, হ্যাং নয়।

🔍 একই এজেন্টে mode="task" + output_schema= মার্কারগুলো — এবং আউটপুটে, ⏸ pause ও finish_task কলটি।

আপনি যা দেখতে পাবেন:

━━ TURN 1 ━━  user: 'I need shoes for the marathon.'
  race_desk  delegate: gear_fitter
  gear_fitter: What is your shoe size?
    The run ENDED  but nothing failed. This is a PAUSED task.

━━ TURN 2 ━━  user: 'Size 9, wide.'   (same session  resumes the task)
  gear_fitter  finish_task   (payload validates as GearOrder)
  race_desk: Your order ... in size 9 Wide has been confirmed.

তিনটি ঘটনা ঘটল যা L3a মোডের কোনোটিই করতে পারে না:

  1. রানটি সত্যিই মাঝপথে থেমে গেছে — এটি একটি পজ করা টাস্ক, হ্যাং বা ব্যর্থতা নয়। এজেন্ট তার স্পষ্টীকরণমূলক প্রশ্নটি জিজ্ঞাসা করেছে এবং টাস্কটি খোলা রেখেছে। ( adk web আপনি শুধু উত্তরটি টাইপ করতেন; কিন্তু হারনেস স্ক্রিপ্টটি একই সেশনে এটিকে দ্বিতীয় বার্তা হিসেবে লিখে রাখে।)
  2. পরবর্তী বার্তাটি একই টাস্ক এজেন্টকে পুনরায় চালু করে — কোনো রিরাউটিং বা রি-ডেলিগেশন ছাড়াই। সেশনটি জানে কে অপেক্ষা করছিল।
  3. finish_task এটি শেষ করেছে — এটি একটি টুল যা ADK, mode="task" এর কারণে যুক্ত করেছে। শেষ করার জন্য এজেন্টকে অবশ্যই এটি কল করতে হবে, এবং এর পেলোড অবশ্যই output_schema সাথে ভ্যালিডেট হতে হবে। একটি টাইপ করা ফিনিশ লাইন সহ কথোপকথনটি সম্পন্ন হলে, ফলাফল সংযুক্ত অবস্থায় নিয়ন্ত্রণ স্বয়ংক্রিয়ভাবে কোঅর্ডিনেটরের কাছে ফিরে আসে।

মোড বাছাই করার জন্য এক-প্রশ্ন নিয়ম

💡 "ব্যবহারকারীর কি এটির সাথে কথা বলার প্রয়োজন আছে — এবং কতক্ষণ পর্যন্ত?" চ্যাট = অনির্দিষ্টকালের জন্য · টাস্ক = ফিল্ডগুলো সংগ্রহ না হওয়া পর্যন্ত · একক পালা = কখনও না।

মোড

মানুষের সম্পৃক্ততা

সমান্তরাল?

মূল ঠিকানায় ফেরত

chat (ডিফল্ট সাবএজেন্ট) — সাপোর্ট অ্যাসিস্ট্যান্ট, উন্মুক্ত কো-পাইলট

সম্পূর্ণ কথোপকথন

না

ম্যানুয়াল (স্থানান্তরের মাধ্যমে)

task — গ্রহণ, বুকিং, সমস্যা সমাধান

শুধুমাত্র স্পষ্টীকরণমূলক প্রশ্ন

না

স্বয়ংক্রিয় ( finish_task এর মাধ্যমে, একটি যাচাইকৃত অবজেক্ট সহ)

single_turn — শ্রেণীবদ্ধ করা · নিষ্কাশন করা · বিচার করা · তৈরি করা

কোনোটিই না

হ্যাঁ

স্বয়ংক্রিয় (এর ফলাফল সহ)

mode শুধুমাত্র সাবএজেন্টদের উপর কাজ করে — কোঅর্ডিনেটরের উপর কখনোই নয়। এবং ওয়ার্কফ্লো নোডগুলো ডিফল্টভাবে single_turn ব্যবহার করে (এ কারণেই L1–L2b এটি কখনো লেখেনি), অপরদিকে সাবএজেন্টগুলো ডিফল্টভাবে chat ব্যবহার করে (এ কারণেই L3a-কে তা করতে হয়েছিল)।

⚠️ এটির উপর ভিত্তি করে কিছু তৈরি করার আগে দুটি সংস্করণ নোট: (1) task

যেহেতু একটি স্ট্যাটিক গ্রাফ নোড সংস্করণ-নির্ভর — 2.0.0b1–2.3.0 (এই কোডল্যাবের পিন)-এ, Workflow(...) কনস্ট্রাকশনের সময় একটি এরর দেখায়; এই লেভেল যা করে ঠিক তাই ব্যবহার করুন (টাস্ক সাব-এজেন্ট সহ একটি চ্যাট কোঅর্ডিনেটর) অথবা ctx.run_node এর মাধ্যমে ডিসপ্যাচ করুন। 2.5.0 সংস্করণে এটি তুলে নেওয়া হয়েছে। (2) "টাস্ক এজেন্টদের অবশ্যই লিফ এজেন্ট হতে হবে" (তাদের নিজস্ব কোনো সাব-এজেন্ট থাকবে না) এটি একটি নথিভুক্ত ADK সীমাবদ্ধতা — কিন্তু এটি একটি চুক্তি , কোনো রানটাইম গার্ড নয়: 2.3.0 বা 2.5.0 কোনোটিই আপনাকে বাধা দেবে না। এররের অনুপস্থিতিকে অনুমতি হিসেবে পড়বেন না।

💡 আরও গভীরে যান: একটি গ্রাফ ওয়ার্কফ্লো (২.৫.০+ সংস্করণ)-এর মধ্যে এমবেড করা একটি task এজেন্ট, যার রাউটিং কথোপকথনটিকে পুনরায় চেষ্টার জন্য ফিরিয়ে আনতে পারে: সহযোগী রিপো 22_agent_in_workflow · সম্পূর্ণ মোড গাইড: docs/agent-modes.md

আপনি হয়তো ভাবছেন: এর মানে কী?

task

এমন তিনটি জিনিস যা অন্য দুটি পারে না : অটো-রিটার্ন (যেখানে চ্যাট কথোপকথনটি এগিয়ে নিয়ে যায়) · টাইপ করা একটি সমাপ্তি রেখা ( finish_task এর পেলোডকে অবশ্যই স্কিমার সাথে যাচাই করতে হবে — আপনি ডেটা ফেরত পাবেন, কোনো ট্রান্সক্রিপ্ট নয়) · পজ/রিজিউম (⏸ চিহ্নটি কোনো হ্যাং নয়, বরং এটি একজন মানুষের জন্য অপেক্ষারত একটি স্থগিত টাস্ক)।

👀 পড়ুন: gear_fittermode="task" + output_schema হলো সম্পূর্ণ কন্ট্রাক্টটি। · ▶ এটি চালান । · ✏️ পরিবর্তন করুন: run_desk("I need a hydration vest", "2 liters, medium") — স্পষ্টকারী প্রশ্নটি পরিবর্তিত হয়, শেষ লাইনটি টাইপ করাই থাকে।

১১. এল৪এ · রানটাইম-আকারের সমান্তরাল ফ্যান-আউট (পিলার ৩এ)

রোডম্যাপ — আপনি এখানে আছেন: L4a

⚡ সংক্ষেপে: কাঠামোটি এখনও তিনটি স্থির ধাপের উপর ভিত্তি করে তৈরি — ডাইনামিক অংশটি মাঝের ধাপটির ভেতরে লুকিয়ে থাকে, যার প্রস্থ রানটাইমে ডেটার মাধ্যমে নির্ধারিত হয়।

⚠️ সতর্কবার্তা: এটি এই ধাপের সবচেয়ে কঠিন অংশ। আগের লেভেলটি ছিল ৪৪ লাইনের; এটি প্রায় ১২০ লাইনের — এর মধ্যে রয়েছে তিনজন এজেন্ট এবং দুটি ওয়ার্কফ্লো নোড, এবং এর কোনো অংশই অপ্রয়োজনীয় নয়। এর জন্য প্রায় ১৫ মিনিট সময় বরাদ্দ রাখুন, এবং শেষের Read/Run/Change লাইনটির ওপর বেশি মনোযোগ দিন: প্রথমবারেই আপনার প্রতিটি লাইন বুঝে ফেলার প্রয়োজন নেই।

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

আকৃতি (এক স্তর গভীর):

START ─► decompose ─► research_topic (parallel_worker) ─► synthesize
                                 
                             └──┴──┴─ (flat: no children yet)

একটি মুক্ত প্রশ্নকে N সংখ্যক উপ-প্রশ্নে বিভক্ত করা হয় — N-এর মান LLM দ্বারা রানটাইমে (৩-৭) নির্বাচন করা হয় — প্রতিটি সমান্তরালভাবে গবেষণা করা হয় , তারপর সেগুলোকে সংশ্লেষণ করে একটি সংক্ষিপ্তসার তৈরি করা হয়।

কোলাব: L4a সেলটি চালান · 📁 গিটহাব: L4a_flat_research/ · 💻 লোকাল: python -m L4a_flat_research.deep_research

L4a প্রবাহ

🔍 চিহ্নিতকারীগুলো — নেই

dynamic=True

সুইচ। ডাইনামিক হলো লেখার একটি পদ্ধতি, কোনো কনফিগারেশন নয়। এর দুটি নির্দেশক আছে: @node(parallel_worker=True) (যা রানটাইম-আকারের একটি তালিকা নেয় এবং প্রতিটি আইটেমের জন্য একটি করে ওয়ার্কার চালায়) এবং ctx.run_node(...) (যা সরাসরি নোড শিডিউল করার কোড চালায়)। এই দুটির যেকোনো একটি দেখলেই বুঝবেন আপনি ডাইনামিক মোডে আছেন।

What you'll see: the decomposer prints eg 5 sub-questions, they research in parallel, then a synthesized briefing. The number differs on every run — the fixed graph couldn't do that.

Two flags on the worker worth understanding:

  • rerun_on_resume=True is mandatory on any node that calls ctx.run_node — ADK raises a ValueError without it. On resume it must re-execute the dispatching node to rebuild the children it spawned, since those aren't in the static graph.
  • retry_config= bounds how this FAILS. A parallel worker cancels every sibling and re-raises the instant one child fails — so without a retry, a single transient 429 discards the whole run, including every call already paid for. The retry lands on the inner per-item node, so each branch retries independently.

You might be wondering: where does ADK "know" this is dynamic? It doesn't need to — nothing is declared anywhere. The decomposer produces a list at runtime; the parallel worker sizes itself to whatever arrives. The dynamism is a property of the data flow you wrote, not a mode you switched on.

👀 Read: the two flags on research_topicparallel_worker and rerun_on_resume . · ▶ Run it. · ✏️ Change: swap in your own open question — N changes because the input decided the width.

12. L4b · Add recursive spawning (Pillar 3b)

Roadmap — you are here: L4b

⚡ TL;DR: recursion is written, not given — the worker calls itself through ctx.run_node , ordinary Python — so the brake must be written too. That's MAX_DEPTH .

The question: one research finding sometimes surfaces a narrow sub-topic worth its own investigation. How do you let a branch spawn more parallel work — and keep it bounded?

The shape (now recursive):

START ─► decompose ─► research_topic (parallel_worker, recursive) ─► synthesize
                                 
                                 └─ research(q3) ─► maybe spawn children
                               └─── research(q2) ─► maybe spawn children
                             └────── research(q1) ─► maybe spawn children

Colab: run the L4b cell · 📁 GitHub: L4b_recursion/ · 💻 Local: python -m L4b_recursion.deep_research

L4b flow

@node(parallel_worker=True, rerun_on_resume=True)
async def research_topic(ctx, node_input):
    finding = coerce(await ctx.run_node(research_agent, node_input=...), ResearchFinding)
    if finding.needs_deeper and finding.deeper_questions and depth < MAX_DEPTH:   # boundary in CODE
        children = await ctx.run_node(research_topic, node_input=deeper)          # recursive fan-out
    yield Event(output={..., "children": children})

🔍 The markers: ctx.run_node(research_topic, ...) inside research_topic itself — self-reference is the recursion — and the guard depth < MAX_DEPTH one line above it.

What you'll see: research nodes printing spawning N deeper — recursion happening live — then a runtime tree shape (eg 5 top-level + 10 recursive children ). The tree differs on every run.

⚠️ Before you raise the knob: the ceiling grows fast — MAX_DEPTH=3 takes the worst case from ~30 calls to ~93. And at the very end of a run you may see a cancelling N leftover tasks log line: that's ADK tearing down its parallel task group after the result is already complete. Harmless — and depending on your logging config you may never see it.

You might be wondering: isn't dynamic recursive by default? No — L4a is fully dynamic with zero recursion. Dynamic only hands you ordinary Python control flow; L4b chooses to write recursion with it. And because you wrote the recursion, you must write its boundary — this is where "let the LLM shape the work, keep the boundaries in code" stops being a slogan.

👀 Read: the guard: if finding.needs_deeper and depth < MAX_DEPTH . · ▶ Run it. · ✏️ Change: set MAX_DEPTH = 1 and re-run — the tree flattens (and the run gets cheaper). The boundary is YOURS, in code.

13. L5 · Which pattern should you use?

Roadmap — you are here: L5

⚡ TL;DR: one axis decides everything — who picks the next step: the graph you drew, the LLM, or your code.

You've built all three. This is the model that makes them useful: match the pattern to the shape of your problem.

The axis: who decides what runs next?

স্তম্ভ

Who decides what runs next

Built in

1 · Graph

the graph you drew

L2a / L2b

2 · Collaborative

the LLM

L3a / L3b

3 · Dynamic

your Python code, at runtime

L4a / L4b

Step 0: do you even need a graph?

ADK ships prebuilt workflow agentsSequentialAgent , ParallelAgent , LoopAgent . For a plain chain of agents, those are the cheapest correct answer and there's no graph to assemble. Reach past them when you need explicit routing (L2b's router), a join (L2a's JoinNode ), or nodes that aren't agents (a plain function, zero LLM calls) — that last one is usually the reason.

Would a prebuilt SequentialAgent / ParallelAgent / LoopAgent do?

├─ YES ──────────────────────────────► use it; stop here

└─ NO  I need routing, a join, or non-agent nodes
   
   Can you draw the workflow before the input arrives?
   
   ├─ YES ───────────────────────────► Pillar 1 · Graph workflow    (L2a/L2b)
   
   └─ NO
      ├─ Known team, request picks the subset? ─► Pillar 2 · Collaborative  (L3a/L3b)
      └─ Does the shape depend on the input?  ──► Pillar 3 · Dynamic        (L4a/L4b)

L5 · which pattern

The honest 1.x-vs-2 framing

This is not "2.0 can do things 1.x couldn't" — 1.x could build all of it. The shift is that 2.0 gives each shape a more direct home , so known control flow leaves the prompt and becomes structure you can see and test.

প্যাটার্ন

The 1.x cost

The ADK 2 home

গ্রাফ

4 LLM calls in the common build; routing hidden in a prompt

function + agent nodes as peers → 1 call, if -statement router

Collaborative

buildable via AgentTool plumbing; ParallelAgent always-all, transfer_to_agent serial

a declared team: sub_agents + mode="single_turn"

গতিশীল

recursion drops you out of the framework

parallel_worker + recursive ctx.run_node inside the framework

The whole app, and what a graph can't show you

You have now built every piece below. Workflow exposes its structure at graph.edges , so this picture is generated from the code rather than drawn by hand — and what the introspection finds is the summary of this lab:

স্তম্ভ

What graph.edges contains

কেন

1 · Graph (L2b)

10 edges , routes and all

you drew it before any input arrived

2 · Collaborative (L3a)

0 edges — only sub_agents + mode

the LLM picks the subset per request

3 · Dynamic (L4a/L4b)

3 edges — identical in both

the recursion is written in Python, not wired in the graph

That last row is the proof for the question L4b answers: L4a and L4b have the same graph, and only one of them recurses.

What you can build now

Each pattern you just ran is a real product shape:

You practiced

In the wild, that's

শুরু করুন

Graph + router (L2a/L2b)

document pipelines, ETL-with-LLM-steps, review/approval chains, eval harnesses

this repo's L2b

Coordinator + single_turn team (L3a)

a support copilot with specialist teams, triage desks, multi-lens review

marathon demo mode 2

task agents (L3b)

intake forms, booking flows, onboarding, KYC — any "collect then act"

22_agent_in_workflow

Dynamic width/depth (L4a/L4b)

research agents, report generators, audit sweeps over unknown-sized inputs

marathon demo mode 3

They compose

The three patterns are not mutually exclusive . A graph node can call a collaborative coordinator; a specialist can launch a dynamic workflow. Choose the right pattern per part of the problem — that's how you avoid turning every agent system into one giant prompt.

The whole app — and what a graph can’t show you

💡 Try it on your own workflow: the script that drew this is scripts/graph_dump.py . Point it at any Workflow and it will print the real edges — a free structural diagram of anything you build.

14. Congratulations

Nine agents, one baton, an orderly finish

You built a Marathon Race Day Coach and, along the way, all three of ADK 2's orchestration patterns.

আপনি যা শিখেছেন

  • Prologue — the mega-prompt that invented its own weather : why structure exists at all.
  • L0–L1Agent , Runner , a real tool the model chooses to call, and your first Workflow (function nodes + agent nodes as peers).
  • L2a / L2b — graph workflows: parallel fan-out + JoinNode , then deterministic routing — one LLM call.
  • L3a — collaborative agents: the same team run in chat (stranded) then single_turn (parallel subset + synthesis) — one flag, two worlds.
  • L3btask mode: a paused clarifying question, a scripted resume, finish_task returning a validated object.
  • L4a / L4b — dynamic workflows: runtime width (fan-out), then runtime depth (recursion) with boundaries in code.
  • L5 — the decision tree, and how the patterns compose.

Lines worth keeping

Functions prepare the context. Edges define the workflow. The router chooses the path. The model writes the answer.

Let the LLM shape the work, but keep the boundaries in code.

Match the pattern to the shape of your problem.

পরবর্তী পদক্ষেপ

  • Run the full app these levels were distilled from — the Marathon Race Day Coach , a FastAPI + SSE build with a browser UI showing all three modes live: github.com/cuppibla/adk-2-marathon-demo .
  • Go wider: adk-workflows-compared — all 23 official ADK 2 workflow samples, each with a 1.x port and when-to-use guidance. Start with docs/three-pillars.md , then the things this codelab skipped: 07_loop , 17_request_input , 22_agent_in_workflow .
  • Port your own problem: which parts are known-structure (L2), known-team (L3a/L3b), unknown-shape (L4)?
  • Explore the code: github.com/cuppibla/adk2-tutorial .
  • Came through the workshop? Your credit — and the project it created — won't last forever. To keep re-running these levels for free, do the Take-home setup step instead: a free AI Studio key, no Cloud project, no billing. Swapping that one cell is the only change.