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

সংক্ষিপ্ত বিবরণ
ডিসক্রিমিনেটিভ মডেল ( jev-1.13 , ওরফে jev-latest ) হলো TypeSafe AI-এর একটি হোস্টেড মডেল, যা ১৯ সেপ্টেম্বর, ২০২৬-এ প্রকাশিত হয়েছে। এটি কোনো টেক্সট তৈরি করে না। আপনি এটিকে স্টেট (টেক্সট, JSON, বা একটি লিস্ট) এবং টাইপ করা প্রশ্ন ( Choice , Score , Noul ) পাঠান, এবং এটি প্রায় ৭০ থেকে ৫০০ মিলিসেকেন্ডের মধ্যে, প্রতি মিলিয়ন ইনপুট টোকেনের জন্য $০.০৪২ খরচে, ক্যালিব্রেটেড সম্ভাবনাসহ টাইপ করা উত্তর ফেরত দেয় এবং কোনো আউটপুট দেয় না। এর কাজ হলো ল্যাঙ্গুয়েজ মডেলগুলোর সামনে, মধ্যে এবং পেছনে সিদ্ধান্ত গ্রহণ করা: যেমন রাউটিং, ক্লাসিফিকেশন, গেটিং, এবং এক্ষেত্রে, একজন যোদ্ধার মতো ক্ষিপ্রতা।
একটি খেলা উদাহরণ হিসেবে

আপনি কি কখনো কোনো যুদ্ধের খেলা খেলেছেন? সেখানে আপনাকে একজন প্রতিপক্ষের মুখোমুখি হতে হয় এবং তার চালের জবাবে তাৎক্ষণিকভাবে প্রতিক্রিয়া দেখাতে হয়। একটি ভুল অনুমানেই আপনার HP কমে যায়। গেমগুলোতে প্রায়শই স্পেল বা মন্ত্র প্রয়োগ করা কঠিন করে তোলা হয়। আমাদের গেমে, স্পেলটি প্রয়োগ করার আগে আপনাকে স্পেল কার্ডের রঙ এবং আকৃতি ক্রমানুসারে বেছে নিতে হয়। এই কর্মশালায় দেখানো হবে কীভাবে উভয় ধরনের মডেলকে একত্রিত করে আপনার চরিত্রকে জেতানো যায়।
গেমটির প্রতিটি উপাদান একটি বাস্তব ব্যবস্থার অনুরূপ:
- প্রতিপক্ষের চাল হলো একটি আগত ঘটনা, যেমন কোনো অনুরোধ বা লেনদেন।
- প্রতিক্রিয়াটি একটি সীমাবদ্ধ সিদ্ধান্ত, যা বৈষম্যমূলক মডেল দ্বারা গৃহীত এবং কোড দ্বারা যাচাইকৃত।
- স্পেল কার্ড হলো একটি অসংগঠিত ইনপুট, যা পড়ার জন্য একটি ল্যাঙ্গুয়েজ মডেলের প্রয়োজন হয়।
- ম্যাচটি হলো কর্মপ্রবাহ, যেখানে দ্রুত ও ধীরগতির কাজগুলো নিজ নিজ গতিতে চলে।
মূল লক্ষ্য হলো চারটি উপাদানকে একত্রিত করে একটি দ্রুত ও স্মার্ট সিস্টেম তৈরি করা।
আপনি যা শিখবেন
- বৈষম্যমূলক (সিস্টেম এক) এবং উৎপাদক (সিস্টেম দুই) মডেলের মধ্যে পার্থক্য ব্যাখ্যা করুন এবং কখন কোনটি ব্যবহার করতে হবে তা বলুন।
- Jev এবং DiffusionGemma কীভাবে পরিবেশন করা হয় তা বর্ণনা করুন, এবং কর্মশালার জন্য একটি সেটআপ তৈরি করুন, যার মধ্যে Compute Engine GPU VM-এ DiffusionGemma অন্তর্ভুক্ত থাকবে।
- পছন্দ, স্কোর এবং নাউল প্রশ্ন লিখুন এবং সম্ভাবনা ও আত্মবিশ্বাস ব্যাখ্যা করুন।
- ডিটারমিনিস্টিক কোডে সম্ভাবনাগুলোকে কার্যকলাপে পরিণত করতে থ্রেশহোল্ড ব্যবহার করুন।
- TypeSafe SDK ব্যবহার করে একটি রিকোয়েস্ট তৈরি করুন, তারপর মডেলটিকে খেলার প্রতিটি চাল বেছে নিতে দিন।
- একটি ধীরগতির শাখা তৈরি করুন, যেখানে জেমিনি একটি ছবি পড়ে, এবং একটি দ্রুতগতির শাখা তৈরি করুন, যেখানে বৈষম্যমূলক মডেলটি একটি লুপের মধ্যে সিদ্ধান্ত নেয়, এবং প্রত্যেকটিকে আলাদাভাবে চালান।
- একটি ADK গ্রাফ ওয়ার্কফ্লোতে উভয় শাখাকে যুক্ত করুন যা একটি ইভেন্ট লুপে স্টেট শেয়ার করে, ফলে ধীরগতির কাজ কখনোই দ্রুত সিদ্ধান্তকে আটকে রাখতে পারে না।
স্থাপত্য
ওয়ার্কবেঞ্চটি ক্লাউডশেলে (বা আপনার মেশিনে) থাকে; এটি লোকাল ফাইল সিস্টেমে ডেটা লেখে এবং অ্যারেনা, জেমিনি ও ডিসিশন মডেলের সাথে ইন্টারঅ্যাক্ট করে। ② "ডিসক্রিমিনেটিভ মডেল ফাইটস"-এ ডিসিশন মডেলকে কল করে; ③ "ওয়ার্কফ্লো ফাইটস"-এ উভয়কে কল করে।

কে কাকে কল করে। ব্রাউজারটি সবসময় শুধু ①-এর সাথেই কথা বলে। উভয় মডেলকেই মেশিনে পাইথন থেকে কল করা হয়:
কলার | বৈষম্যমূলক মডেল | মিথুন |
② ক্ষেত্র, "বৈষম্যমূলক মডেলের লড়াই" | প্রতি টিক, | না |
③ ওয়ার্কফ্লো | প্রতি টিক, | না |
③ কর্মপ্রবাহের | না | মন্ত্র কার্ডের ছবি; লড়াইয়ের পরের কাহিনী |
| হ্যাঁ | না |
প্রতিটি মোডের জন্য একটি করে টিক।
- আপনি লড়াই করেন। পৃষ্ঠাটি ২-এর কাছে একটি টেলিগ্রাফ চায়, সেটি ২ সেকেন্ডের টাইমারসহ দেখায় এবং আপনার চাপ দেওয়া বোতামটি (বা আপনার টাইপ করা মন্ত্রটি) ফেরত পাঠায়। ২ সেটির সমাধান করে।
- বৈষম্যমূলক মডেলের লড়াই। পৃষ্ঠাটি ②-কে একটি টিকের জন্য অনুরোধ করে; ② একটি টেলিগ্রাফ আঁকে, এক কলে মডেলকে তিনটি প্রশ্ন জিজ্ঞাসা করে,
choose()চালায় এবং উত্তর ও ফলাফল ফেরত দেয়। পৃষ্ঠাটি বারগুলো আঁকে। - ওয়ার্কফ্লো ফাইট। স্টার্ট কমান্ড ②-কে ③-কে একটি সাবপ্রসেস হিসেবে চালু করতে নির্দেশ দেয় (লগ
runs/arena-workflow.logফাইলে পাওয়া যাবে)। ③ এই ফাইটটি পরিচালনা করে: এটি ②-কে প্রতিটি টেলিগ্রাফের জন্য জিজ্ঞাসা করে, মডেলটিকে কল করে এবং সিদ্ধান্তটি পোস্ট করে; জেমিনির স্পেলটি যখনই প্রস্তুত হয়, তখনই তার নিজস্ব ব্রাঞ্চে এসে পৌঁছায়। পেজটি শুধুমাত্র ②-কে পোল করে এবং ড্র করে। পজ হলো ②-এর একটি ফ্ল্যাগ যা ③ প্রতিটি টিকের আগে পরীক্ষা করে।
যেখানে ডিসিশন মডেলটি হোস্ট করা হয়। প্রতিটি কল একই typesafe-sdk মাধ্যমে যায়; শুধুমাত্র বেস URL পরিবর্তিত হয়। scripts/jevauth.py ব্যাকএন্ডের নাম দেয় এবং কী (key) ও টাইমআউট (timeout) সেট করে:
ব্যাকএন্ড | | চাবি | স্থাপন করেছেন |
টাইপসেফ, হোস্টেড | অনির্ধারিত (api.typesafe.ai) | | |
আপনার L4 VM-এ DiffusionGemma | | কোনোটিই না | |
ক্লাউড রানে ডিফিউশনজেমা | | একটি গুগল পরিচয় টোকেন, যা প্রতি ঘন্টায় সংগ্রহ করা হয় | |
মহড়া | | কোনোটিই না | |
স্পেল কার্ডের উত্তর ২: ওয়ার্কফ্লো শুধুমাত্র PNG ফাইলটি পায়, এবং ২ নম্বর ধাপটি তার ফেরত পাঠানো স্পেলটি বিচার করে। এই কারণেই স্পেলটি জেমিনির রিডিংয়ের একটি আসল পরীক্ষা হয়ে দাঁড়ায়, এবং 'ইউ ফাইট' অংশে আপনি যে স্পেলটি তৈরি করেন তা আপনার নিজেরও একটি আসল পরীক্ষা।
২. সেটআপ
আপনার কর্মশালার ক্রেডিট দাবি করুন
এই সেশনের জন্য আপনাকে যদি গুগল ক্লাউড ক্রেডিট দেওয়া হয়ে থাকে, তবে প্রথমে সেটি দাবি করুন — এতে প্রায় এক মিনিট সময় লাগে এবং আপনার জন্য বিলিং অ্যাকাউন্ট তৈরি হয়ে যায়।
ওপেন ক্লাউড শেল
গুগল ক্লাউড শেল হলো একটি ব্রাউজার-ভিত্তিক লিনাক্স পরিবেশ, যা gcloud , Python, Node.js, uv এবং git দিয়ে আগে থেকেই কনফিগার করা থাকে এবং আপনার গুগল অ্যাকাউন্ট দিয়ে প্রমাণীকৃত।
- গুগল ক্লাউড কনসোলটি খুলুন।
- আপনার ব্রাউজারের নীচে একটি টার্মিনাল সেশন খুলতে, ‘অ্যাক্টিভেট ক্লাউড শেল’ (উপরের নেভিগেশন বারে থাকা টার্মিনাল আইকন)-এ ক্লিক করুন।

ওয়ার্কবেঞ্চটি চালু করুন
ক্লাউড শেলে, বা যেখানেই gcloud এ সাইন ইন করা আছে:
git clone https://github.com/gca-americas/discriminative-models-workshop.git
cd discriminative-models-workshop
./setup_project.sh # a new project with billing, recorded in ~/project_id.txt
./setup_codelab.sh # everything else, then the workbench on port 4900
setup_project.sh একটি প্রজেক্ট ( discrim-models-XXXX ) তৈরি করে, এর সাথে বিলিং লিঙ্ক করে, আপনার ইভেন্ট ক্রেডিট অ্যাকাউন্ট থাকলে সেটিকে অগ্রাধিকার দেয় এবং প্রজেক্টটি পরিষেবা দেওয়ার জন্য প্রস্তুত না হওয়া পর্যন্ত অপেক্ষা করে। এটি পুনরায় চালালে ~/project_id.txt ফাইলে থাকা প্রজেক্টটি পুনরায় ব্যবহৃত হয়। আপনার আগে থেকে থাকা কোনো প্রজেক্ট ব্যবহার করতে চাইলে, সেটির আইডি ওই ফাইলে রাখুন এবং এই স্ক্রিপ্টটি এড়িয়ে যান।
setup_codelab.sh কোনো কিছু জানতে চায় না। এটি uv এবং পাইথন প্যাকেজগুলো ইনস্টল করে, Vertex AI, Compute Engine ও IAP সক্রিয় করে, .env ফাইলে থাকা প্রজেক্টে Gemini-কে Vertex AI-এর দিকে নির্দেশ করে, প্রজেক্টটি কল করতে পারে এমন একটি মডেল দিয়ে একটি আসল Gemini কল করে, পেজটি বিল্ড করে, ব্যাকগ্রাউন্ডে ওয়ার্কবেঞ্চ চালু করে এবং scripts/check_setup.py রান করে। এটি পুনরায় চালালে আপনার অনুশীলন ফাইলগুলো থেকে যায়; scripts/starter.sh সেগুলোকে রিসেট করে দেয়। ওয়ার্কবেঞ্চের ২য় ধাপে ডিসিশন মডেলটি বেছে নেওয়া হয়।
ক্লাউড শেলে ওয়ার্কবেঞ্চ UI খুলতে:
-
./setup_codelab.shফাইলের শেষে প্রিন্ট হওয়া প্রিভিউ লিঙ্কে ক্লিক করুন, অথবা ক্লাউড শেল টুলবারের উপরের ডান কোণায় থাকা ওয়েব প্রিভিউ- তে ক্লিক করুন। - Change port নির্বাচন করুন, 4900 লিখুন এবং Change and Preview-এ ক্লিক করুন।
আপনার প্রোজেক্টে Gemini চলে Vertex AI-এর উপর, আপনার নিজস্ব Google ক্রেডেনশিয়াল ব্যবহার করে: .env ফাইলে থাকা GOOGLE_GENAI_USE_VERTEXAI=1 , GOOGLE_CLOUD_PROJECT এবং GOOGLE_CLOUD_LOCATION=global ।
ডিসিশন মডেলটি নিজে থেকেই, ওয়ার্কবেঞ্চের ধাপ ২-এ, অথবা টার্মিনাল থেকে scripts/setup_model.sh ব্যবহার করে নির্বাচন করা হয়:
পছন্দ | চাহিদা | সেটআপ | খরচ |
বৈষম্যমূলক মডেল (টাইপসেফ, হোস্টেড) | একটি TypeSafe API কী | কোনোটিই না | প্রতি টোকেনে, সেন্টের ভগ্নাংশ |
ডিফিউশনজেমা (গুগল, ওপেন ওয়েটস) | জিপিইউ- এর জন্য বিলিং + কম্পিউট ইঞ্জিন কোটা | ~১৫ মিনিট, স্বয়ংক্রিয় | ভিএম চলার সময় প্রতি ঘন্টায় প্রায় ০.৭১ ডলার। |
মহড়া (কোন মডেল নেই) | কিছুই না | কোনোটিই না | কোনোটিই না |
Compute Engine VM-এ DiffusionGemma
scripts/setup_gemma.sh প্রথমে GPU কোটা পরীক্ষা করে, তারপর NVIDIA ড্রাইভার 580 সহ Google-এর Deep Learning ইমেজ থেকে একটি g2-standard-4 VM (1 × L4 24 GB, 4 vCPU, 16 GB) তৈরি করে। প্রথম বুটে VM-টি Docker ইনস্টল করে, Hugging Face ( nvidia/diffusiongemma-26B-A4B-it-NVFP4 , 17.5 GB, পাবলিক, কোনো টোকেন ছাড়া) থেকে ওয়েটগুলো ডাউনলোড করে এবং Discriminative মডেলের এক্সাক্ট API-এর পেছনে djev-run : DiffusionGemma রান করে। মডেলটির পোর্ট ইন্টারনেটের জন্য খোলা নয়: ওয়ার্কবেঞ্চটি localhost:8096 এ একটি IAP টানেলের মাধ্যমে এটিতে পৌঁছায়, যা scripts/start.sh খুলে দেয়।
বিরতি / পুনরায় শুরু করুন | | |
টানেল | | |
অপসারণ করুন | | |
আদেশগুলো অনুশীলন করুন | | |
রিপোজিটরি লেআউট
app/ the arena app, as built so far (see "The app, one stage at a time")
main.py the server, the "You fight" mode, and the plugin loader
engine.py the rules and the ogre's moves, the one copy
sigil.py spell cards: a color and three shapes, judged and drawn (a tiny PNG rasteriser)
static/ the page: HP bars, the telegraph and timer, the spell card; modes/ holds plugins
static/sounds/ bgm.mp3 plus optional effects: fight, ogre-attack, block, strike, hurt, charge,
cast, fizzle, ready, ko, timeup (.mp3). A missing file is silent. Add them in stages/03-you-fight/.
reflex.py step 5: the three questions and choose()
mode_model.py step 5: the server side of "Discriminative model fights"
mode_workflow.py step 6: the server side of "Workflow fights"
branches/ step 6b's exercises: each branch as a workflow of its own, nothing from the arena
slow_branch.py Gemini reads spell_card.png and is checked against spell_card.json
fast_branch.py the Discriminative model decides on a list of moves, in a loop
starter/ Reset restores from here
server/ The workbench
৩. সারসংক্ষেপ
আপনার পরিবেশ পরিষ্কার করুন
ওয়ার্কশপটি শেষ হয়ে গেলে, DiffusionGemma GPU-এর সমস্ত রিসোর্স বন্ধ করতে, ব্যাকগ্রাউন্ড ওয়ার্কবেঞ্চ ও রিহার্সাল প্রসেসগুলো থামাতে, ক্লাউড শেল থেকে ওয়ার্কশপের ফাইলগুলো সরাতে এবং (ঐচ্ছিকভাবে) আপনার ওয়ার্কশপ গুগল ক্লাউড প্রজেক্টটি ডিলিট করতে নিম্নলিখিত ধাপগুলো সম্পন্ন করুন।
- DiffusionGemma GPU VM এবং ফায়ারওয়াল নিয়ম (যদি তৈরি করা থাকে) মুছে ফেলুন : যদি আপনি ধাপ ২-এ একটি Compute Engine GPU VM-এ DiffusionGemma প্রোভিশন করে থাকেন, তাহলে VM, ডিস্ক এবং IAP ফায়ারওয়াল নিয়মটি সরিয়ে ফেলুন, যাতে কোনো চলমান কম্পিউট বা ডিস্ক স্টোরেজ চার্জ জমা না হয়:
cd ~/discriminative-models-workshop ./scripts/teardown_gemma.sh - ক্লাউড শেলে ওয়ার্কবেঞ্চ এবং রিহার্সাল প্রসেস বন্ধ করুন : আপনার ক্লাউড শেল টার্মিনালে, ব্যাকগ্রাউন্ড ওয়ার্কবেঞ্চ সার্ভার এবং যেকোনো রিহার্সাল স্ট্যান্ড-ইন প্রসেস বন্ধ করুন:
cd ~/discriminative-models-workshop ./scripts/stop.sh ./scripts/rehearsal.sh stop 2>/dev/null || true - ক্লাউড শেল থেকে ওয়ার্কশপ ফোল্ডারটি মুছুন : আপনার হোম ডিরেক্টরিতে ফিরে যান এবং ক্লোন করা রিপোজিটরি ফোল্ডার ও প্রজেক্ট আইডি ফাইলটি সরিয়ে ফেলুন:
cd ~ rm -rf ~/discriminative-models-workshop ~/project_id.txt - আপনার গুগল ক্লাউড প্রজেক্ট ডিলিট করুন : যদি
./setup_project.shএকটি ডেডিকেটেড ওয়ার্কশপ প্রজেক্ট তৈরি করে থাকে (উদাহরণস্বরূপ,discrim-models-XXXX), তাহলে প্রজেক্টটি শাটডাউন করলে এর ভেতরে তৈরি হওয়া সমস্ত রিসোর্স স্থায়ীভাবে মুছে যাবে, কিন্তু আপনার ক্লাউড বিলিং অ্যাকাউন্ট অক্ষত থাকবে:- গুগল ক্লাউড কনসোলে ম্যানেজ রিসোর্সেস পেজটি খুলুন।
- রিসোর্স তালিকা থেকে আপনার ওয়ার্কশপ প্রজেক্টটি (যেমন,
discrim-models-...) নির্বাচন করুন। - উপরের টুলবারে থাকা ডিলিট বোতামে ক্লিক করুন, নিশ্চিত করার জন্য আপনার প্রজেক্ট আইডি টাইপ করুন এবং শাট ডাউন বোতামে ক্লিক করুন।
আপনি এই কর্মশালাটি সম্পন্ন করেছেন।
ল্যাব সারাংশ
- Compute Engine GPU VM-এ Jev বা DiffusionGemma-এর মতো একটি বৈষম্যমূলক মডেল বেছে নেওয়া হয়েছে এবং পরীক্ষা করে দেখা হয়েছে যে এটি সাড়া দেয়।
- এর নিয়মকানুন শেখার জন্য, ঘড়ির কাঁটার বিপরীতে হাতে করে আখড়াটিতে খেলেছিলাম।
- শিখেছি কীভাবে একটি ডিসক্রিমিনেটিভ মডেল চয়েস, স্কোর এবং নাউল প্রশ্ন, সম্ভাবনা ও কনফিডেন্স ব্যবহার করে উত্তর দেয় এবং কীভাবে আপনার কোড সেগুলিতে থ্রেশহোল্ড প্রয়োগ করে।
- আপনার প্রথম অনুরোধটি পাঠানোর পর, মডেলটিকে এরিনার প্রতিটি চাল বেছে নিতে দিন, যেখানে
choose()ফাংশনটি তার উত্তরগুলোকে কাজে পরিণত করবে। - একটি ADK ওয়ার্কফ্লো-এর প্রতিটি শাখাকে আলাদাভাবে তৈরি করা হয়েছে, যেখানে জেমিনি একটি স্পেল কার্ডের ছবি পড়ে এবং মডেলটি একটি লুপের মধ্যে সিদ্ধান্ত নেয়।
- তাদেরকে এমন একটি কর্মপ্রবাহে যুক্ত করা হয়েছে যা অবস্থা ভাগ করে নেয়, ফলে লড়াই কখনো জেমিনির জন্য অপেক্ষা করে না এবং সুযোগ বুঝেই জাদু প্রয়োগ করা হয়।
কথোপকথন থেকে সিদ্ধান্ত পর্যন্ত

জেনারেটিভ এআই চ্যাট এবং কন্টেন্ট তৈরির মাধ্যমে বেশিরভাগ টিমের কাছে পৌঁছেছে। পরবর্তী পর্যায় হলো প্রোডাক্ট এবং পাইপলাইনের ভেতরে এআই, যেখানে মডেলের আউটপুট সরাসরি কোনো একটি অ্যাকশনকে চালনা করে: যেমন একটি সাপোর্ট টিকেট রাউট করা, একটি ট্রানজ্যাকশন ফ্ল্যাগ করা, পর্যালোচনার জন্য একটি ঝুঁকিপূর্ণ রিকোয়েস্ট আটকে রাখা, কোনো এজেন্টের টুল কল অনুমোদন বা ব্লক করা, কিংবা গেমে একটি চাল বেছে নেওয়া।
এই সিদ্ধান্তগুলোর তিনটি সাধারণ প্রয়োজনীয়তা রয়েছে যা চ্যাটের নেই:
- লেটেন্সি। উত্তরটি প্রায়শই ব্যবহারকারীর অনুরোধের পথে বা একটি রিয়েল-টাইম লুপের মধ্যে থাকে, তাই এটি সেকেন্ডে নয়, মিলিসেকেন্ডের মধ্যে পৌঁছাতে হয়।
- কাঠামো। আহ্বানকারী হলো কোড, তাই উত্তরটি এমন একটি মান হতে হবে যার উপর এটি কাজ করতে পারে, কোনো অনুচ্ছেদ নয় যা এটিকে পার্স করতে হবে।
- পূর্বাভাসযোগ্যতা। প্রতিটি সিদ্ধান্তের জন্য এমন একটি নির্ভরযোগ্যতা প্রয়োজন যা কোড যাচাই করতে পারে, এবং এর খরচ এত কম হওয়া উচিত যে প্রতিটি ঘটনার ক্ষেত্রেই তা জিজ্ঞাসা করা যায়।
একটি ল্যাঙ্গুয়েজ মডেল একবারে একটি টোকেন করে টেক্সট তৈরি করে। একে হ্যাঁ বা না-সূচক উত্তর দিতে বলা যেতে পারে, কিন্তু একটি রিয়েল-টাইম লুপের জন্য এটি ধীরগতির, এর আউটপুট পার্স করতে হয়, এবং এটি কতটা নিশ্চিত তা জানায় না।
সিদ্ধান্তের জন্য নির্মিত মডেল
একটি বৈষম্যমূলক মডেল একবারে প্রতিটি অনুমোদিত বিকল্পের জন্য একটি সম্ভাব্যতা সহ একটি টাইপ করা প্রশ্নের উত্তর দেয়। এটি কোনো টেক্সট তৈরি করে না। এই কর্মশালাটি চালানোর জন্য দুটি বিকল্প প্রদান করে:
মডেল | সরবরাহকারী | এই কর্মশালায় মডেলটি যেখানে চলে |
জেভ | টাইপসেফ এআই | TypeSafe-এর হোস্টেড পরিষেবা, যা একটি API কী দিয়ে কল করা হয়। |
ডিফিউশনজেমা | গুগল, ওপেন ওয়েটস | আপনার নিজের গুগল ক্লাউড প্রোজেক্টে একটি জিপিইউ ভিএম-এ সেলফ-হোস্টেড |
আপনার প্রয়োজন অনুযায়ী মডেলগুলো অদলবদল করা যেতে পারে; এগুলোর সাথে সংযোগকারী কোড পরিবর্তন করার কোনো প্রয়োজন নেই।
উপাদানগুলি একত্রিত করুন
একটি সফল সিস্টেম একাধিক উপাদান নিয়ে গঠিত:
উপাদান | ভূমিকা | এই কর্মশালায় |
কর্মপ্রবাহ | ধাপগুলো সমন্বয় করে, শাখাগুলো সমান্তরালভাবে চালায়, এবং শেয়ার্ড স্টেট ধরে রাখে। | একটি ADK গ্রাফ ওয়ার্কফ্লো |
নির্ণয়মূলক কোড | নিয়মকানুন, সীমা, যাচাইকরণ। তাৎক্ষণিক, বিনামূল্যে এবং নিরীক্ষণযোগ্য। | খেলার নিয়মাবলী, |
বৈষম্যমূলক মডেল | আত্মবিশ্বাসের স্কোর সহ দ্রুত, সীমাবদ্ধ সিদ্ধান্ত | প্রতি টিক-এ একটি প্রতিক্রিয়া বেছে নেওয়া |
ভাষা মডেল | উপলব্ধি ও সৃষ্টি: চিত্র এবং উন্মুক্ত পাঠ্য | মিথুন রাশি মন্ত্রের কার্ডের ছবিটি পড়ে এবং মন্ত্রটি লেখে। |
স্থাপত্য পরিবেশনকারী মডেল

ধাপ ২-এ আপনি আপনার পছন্দ এবং পরিবেশের ওপর নির্ভর করে মডেলটি বেছে নেবেন। আপনি যদি DiffusionGemma ব্যবহার করার পরিকল্পনা করেন, তবে নিশ্চিত করুন যে আপনার Google Cloud-এ একটি GPU-তে অ্যাক্সেস আছে।
জেভ | ডিফিউশনজেমা | |
সরবরাহকারী | টাইপসেফ এআই, হোস্টেড এপিআই | গুগল, ওপেন ওয়েটস |
দৌড় চলছে | TypeSafe-এর পরিকাঠামো | আপনার প্রোজেক্টে GPU সহ একটি Compute Engine VM। |
এন্ডপয়েন্ট | | একটি IAP টানেলের মাধ্যমে |
প্রমাণীকরণ | | আপনার গুগল ক্লাউড পরিচয়, যা IAP দ্বারা যাচাইকৃত। |
খরচ | প্রতি ইনপুট টোকেন | গুগল ক্লাউডের কম্পিউট ইঞ্জিন জিপিইউ-এর মূল্য নির্ধারণ, যখন ভিএমটি চলে |
সেটআপ | একটি এপিআই কী | মডেলটি একটি ভিএম বা ক্লাউড রানে ইনস্টল করুন |
ডেটা প্রবাহ
- এরিনা অ্যাপ বা ADK ওয়ার্কফ্লো একটি অনুরোধ তৈরি করে: অবস্থা (প্রতিপক্ষ কী করেছে) এবং তিনটি প্রশ্ন।
- TypeSafe SDK এটিকে কনফিগার করা বেস URL-এ
POST /v1/systemoneহিসেবে পাঠায়। - Jev- এর ক্ষেত্রে, অনুরোধটি HTTPS-এর মাধ্যমে
api.typesafe.aiতে যায়, যেখানে API কী-টি একটি বেয়ারার টোকেন হিসেবে থাকে। - DiffusionGemma-এর জন্য, অনুরোধটি
localhost:8096এ যায়। একটি ব্যাকগ্রাউন্ডgcloud compute start-iap-tunnelপ্রসেস এটিকে Identity-Aware Proxy-এর মাধ্যমে ফরওয়ার্ড করে, যা আপনার Google পরিচয় যাচাই করে, এবং VM-এর 8080 পোর্টে পাঠিয়ে দেয়। - ভিএম-এ, djev-run অনুরোধটি গ্রহণ করে, জিপিইউ-তে থাকা vLLM-এর মাধ্যমে DiffusionGemma চালায় এবং প্রতিটি অনুমোদিত বিকল্পের সম্ভাবনা পড়ে নেয়।
- উভয় ব্যাকএন্ডই একই প্রতিক্রিয়া প্রদান করে: প্রতিটি প্রশ্নের জন্য একটি উত্তর, সাথে সম্ভাবনা এবং একটি কনফিডেন্স স্কোর। ওয়ার্কশপ কোডটি তার থ্রেশহোল্ড প্রয়োগ করে সেই অনুযায়ী কাজ করে।
কম্পিউট ইঞ্জিনে ডিফিউশনজেমা
scripts/setup_gemma.sh আপনার প্রোজেক্টে এটি তৈরি করে:
- অঞ্চলটিতে জিপিইউ-এর জন্য কোটা আছে কিনা তা যাচাই করে।
- এটি Compute Engine এবং IAP API-গুলো সক্রিয় করে এবং
allow-iap-djevফায়ারওয়াল নিয়মটি তৈরি করে। এটি শুধুমাত্র IAP অ্যাড্রেস রেঞ্জকে ২২ এবং ৮০৮০ পোর্টে প্রবেশের অনুমতি দেয়। -
djev-l4নামের ভিএমটি তৈরি করে: মেশিন টাইপg2-standard-4(৪টি vCPU, ১৬ জিবি মেমরি), একটি ২৪ জিবি জিপিইউ, একটি ১০০ জিবি ডিস্ক, এবং এনভিডিয়া ড্রাইভার ৫৮০ সহ ডিপ লার্নিং ভিএম ইমেজ। যদি কোনো জোনে জিপিইউ ধারণক্ষমতা না থাকে, তবে এটি পরবর্তী জোনটি চেষ্টা করে। - প্রথমবার বুট করার সময়, ভিএম-এর স্টার্টআপ স্ক্রিপ্ট ডকার এবং এনভিডিয়া কন্টেইনার টুলকিট ইনস্টল করে, djev-run কন্টেইনার ইমেজটি পুল করে, হাগিং ফেস (Hugging Face) থেকে ওয়েটস (১৭.৫ জিবি) ডাউনলোড করে এবং পোর্ট ৮০৮০-তে জিপিইউ অ্যাক্সেস সহ কন্টেইনারটি চালু করে। এতে প্রায় ১৫ মিনিট সময় লাগে। পরবর্তী বুটগুলোতে প্রায় ২ মিনিট সময় লাগে।
- সংযোগের সেটিংস
.envফাইলে লিখে দেয় এবং টানেলটি খুলে দেয়।
কাজ | আদেশ |
ভিএম বন্ধ করুন (ডিস্কটি অক্ষত থাকবে) | |
আবার শুরু করুন | |
টানেলটি পরীক্ষা করুন | |
সবকিছু মুছে ফেলুন | |
মডেলটি সেট আপ করুন

টাইপসেফ এসডিকে
ক্লায়েন্ট লাইব্রেরিটি হলো পাইথনের জন্য typesafe-sdk । এই ওয়ার্কশপে এটি আগে থেকেই আছে: ধাপ ৬-এর জন্য google-adk পাশাপাশি এটি ওয়ার্কবেঞ্চের নিজস্ব এনভায়রনমেন্টে ইনস্টল করা আছে।
pip install typesafe-sdk # or: uv add typesafe-sdk
জেভ এন্ডপয়েন্ট
Jev মডেলটি একটি হোস্টেড এপিআই, তাই অন্য কিছু ডাউনলোড করার প্রয়োজন নেই। একটি কী (key) পেতে, TypeSafe কনসোলে সাইন আপ করুন। এসডিকে (SDK) TYPESAFE_API_KEY এনভায়রনমেন্ট ভেরিয়েবলে কী-টি খুঁজে থাকে, এবং এই ওয়ার্কশপের স্ক্রিপ্টগুলো রুটে থাকা একটি .env ফাইলও পড়ে, তাই সেখানে একটি লাইনই যথেষ্ট:
TYPESAFE_API_KEY=ts-...
ডিফিউশনজেমা ব্যবহার করুন
djev-run ডিসক্রিমিনেটিভ মডেলের এপিআই পুনরায় বাস্তবায়ন করে। এটি গুগল ডিপমাইন্ডের ওপেন ডিফিউশন মডেল ডিফিউশনজেমা (মোট ২৬ বিলিয়ন প্যারামিটার, প্রায় ৪ বিলিয়ন সক্রিয়, অ্যাপাচি ২.০) থেকে একই POST /v1/systemone এন্ডপয়েন্ট এবং একই noul, choice ও score প্রশ্নগুলো সরবরাহ করে। যেহেতু ওয়্যার ফরম্যাটটি একই, তাই TypeSafe SDK অপরিবর্তিতভাবে এর সাথে যোগাযোগ করে।
যদি আপনি অনুশীলনীতে DiffusionGemma নির্বাচন করেন, তবে এটি আপনার নিজের গুগল ক্লাউড প্রজেক্টের একটি ভিএম-এর জিপিইউ-তে চলে, এবং উপরের ডানদিকের পিলটিতে ‘gemma on vm ’ লেখা থাকে। ওয়ার্কবেঞ্চটি একটি প্রাইভেট আইএপি টানেলের মাধ্যমে এতে পৌঁছায়, এবং মডেলটির পোর্ট ইন্টারনেটের জন্য উন্মুক্ত থাকে না। ধাপ ১-এ সম্পূর্ণ আর্কিটেকচারটি বর্ণনা করা হয়েছে।
একটি ডিফিউশন মডেল কেন এটি করতে পারে: এটি একবারে পজিশনের একটি সম্পূর্ণ ব্লক পূরণ করে, যেখানে প্রতিটি পজিশন সম্পূর্ণ ইনপুট দেখতে পায়, ফলে প্রতিটি অনুমোদিত বিকল্পের সম্ভাবনা এক ধাপেই পড়া যায়। একটি সাধারণ ল্যাঙ্গুয়েজ মডেল একবারে একটি টোকেন তৈরি করে এবং সেটিকে বারবার স্যাম্পল করতে হয়।
গেমটি ম্যানুয়ালি খেলুন

এরিনা হলো সবচেয়ে ছোট ফাইটিং গেম, কিন্তু তার মানে এই নয় যে এটি সহজ: আপনাকে দ্রুত এবং বুদ্ধিমান হতে হবে। আপনার মুখোমুখি হয় একটি ওগর। এর অনেক ধরনের আক্রমণ আছে, এবং প্রতিটি আক্রমণের আগে এটি একটি সূক্ষ্ম নড়াচড়া (একটি ইঙ্গিত ) করে: এটি গদা তোলে, তেড়ে আসে, অথবা অরক্ষিত অবস্থায় টলে যায়। একজন যোদ্ধা হিসেবে, আপনি পাঁচটি ভিন্ন চালের মাধ্যমে এর প্রতিক্রিয়া জানাতে পারেন: উঁচু ব্লক, নিচু ব্লক, ডজ, আঘাত, অপেক্ষা। এটি এমন কোনো খেলা নয় যা আপনার পালার জন্য অপেক্ষা করে। ওগর আঘাত করার আগে প্রতিক্রিয়া জানানোর জন্য আপনার কাছে দুই সেকেন্ড সময় আছে। যদি টাইমার শেষ হয়ে যায়, তবে আপনি কিছুই করতে পারেননি, এবং এর জন্য আপনাকে খুব অনুশোচনা করতে হবে।
রিংটির উপরের-বাম কোণে একটি স্পেল কার্ড আছে: তিনটি আকৃতি সহ একটি রঙিন কার্ড। শুধুমাত্র এর সাথে মেলে এমন স্পেলই আসল ক্ষতি করে। গেমে আপনি লড়াইয়ের নিচের বাটনগুলো দিয়ে স্পেল কাস্ট করতে পারেন: কার্ডের রঙ বেছে নিন, তারপর বাম থেকে ডানে এর আকৃতিগুলো বেছে নিন, এরপর CAST চাপুন। আপনি যখন বেছে নিচ্ছেন তখন ঘড়ি চলতে থাকে, তাই আপনাকে একই সাথে স্পেলটি তৈরি করতে হবে এবং ওগরের আক্রমণে প্রতিক্রিয়া দেখাতে হবে। ১ থেকে ৫ পর্যন্ত কীগুলো প্রতিটি চালের জবাব দেয়। ভুল স্পেল নিষ্ক্রিয় হয়ে যায়। ধাপ ৬-এ, জেমিনি আপনার জন্য স্পেল কার্ডটি পড়ে শোনায়।
মূল কথা: লড়াই হলো ছোট ছোট সিদ্ধান্তের একটি ধারা, যার প্রতিটির জন্য একটি নির্দিষ্ট সময়সীমা থাকে। বেশিরভাগ সফটওয়্যার অটোমেশন আসলে দেখতে অনেকটা এমনই, শুধু লাঠিটা ছাড়া।
বৈষম্যমূলক মডেল ধারণা

সফটওয়্যারে সিদ্ধান্ত
ল্যাঙ্গুয়েজ মডেলগুলো বহু বছর ধরেই কথোপকথনের ক্ষেত্রে বেশ পারদর্শী। বেশিরভাগ সফটওয়্যার এখনও কোনো স্বয়ংক্রিয় কাজের জন্য এগুলো ব্যবহার করে না, এবং এর কারণ বুদ্ধিমত্তা নয়, বরং গতি।
একটি ল্যাঙ্গুয়েজ মডেলকে জিজ্ঞাসা করুন আপনার সামনে থাকা রাক্ষসটি আঘাত হানতে চলেছে কিনা, এবং এটি তার উত্তরটি একবারে একটি টোকেন করে লেখে। প্যারাগ্রাফটি আসতে আসতেই, আঘাতটি এসে পড়ে। ধাপ ৩-এ আপনি এর দুই-সেকেন্ডের সংস্করণটি অনুভব করেছেন। এবং এমনকি তখনও, 'হ্যাঁ' উত্তরটি এমন একটি প্যারাগ্রাফের গভীরে চাপা পড়ে থাকে যা আপনার কোডকে খুঁজে বের করতে এবং বিশ্বাস করতে হয়, অথচ মডেলটি কতটা নিশ্চিত ছিল সে সম্পর্কে কোনো ধারণাই থাকে না।
ডিসক্রিমিনেটিভ মডেলটি স্টেট এবং আপনার টাইপ করা প্রশ্ন ও উত্তরগুলো একবারে, মিলিসেকেন্ডের মধ্যে গ্রহণ করে। প্রতিটি উত্তরের সাথে একটি পরিমাপকৃত সম্ভাবনা থাকে: ০.৯ মানে দশবারের মধ্যে নয়বার সঠিক। এখানে পার্স করার মতো কোনো টেক্সট নেই এবং এর থেকে কোনো JSON বের করারও প্রয়োজন হয় না।
সিস্টেম ওয়ান এবং সিস্টেম টু মডেল
নামটি ড্যানিয়েল কাহনেমানের ' থিংকিং, ফাস্ট অ্যান্ড স্লো' বই থেকে নেওয়া হয়েছে। সিস্টেম টু হলো ধীর, সুচিন্তিত যুক্তি, যা একটার পর একটা ধাপে এগোয়। সিস্টেম ওয়ান হলো দ্রুত, প্যাটার্ন মেলানোর প্রক্রিয়া।
একটি ল্যাঙ্গুয়েজ মডেল হলো একটি সিস্টেম টু মেশিন। এটি টোকেনের মাধ্যমে, একবারে একটি করে যুক্তি দেয়। ডিসক্রিমিনেটিভ মডেল হলো একটি সিস্টেম ওয়ান মডেল : এটি উচ্চস্বরে যুক্তি দেয় না, কিছুই তৈরি করে না এবং একবারে প্রতিটি প্রশ্নের উত্তর দেয়। এই কারণেই এটি দ্রুত (প্রায় ৭০ থেকে ৫০০ মিলিসেকেন্ড) এবং সাশ্রয়ী (প্রতি হাজার সিদ্ধান্তে এক সেন্টের ভগ্নাংশ)।
মূল কথা: একটি ল্যাঙ্গুয়েজ মডেল লেখে। একটি ডিসিশন মডেল সিদ্ধান্ত নেয়। সফটওয়্যারের এআই-এর কাছ থেকে যা প্রয়োজন, তার বেশিরভাগই হলো একটি সিদ্ধান্ত।
সীমাবদ্ধতা
ডিসক্রিমিনেটিভ মডেল টেক্সট তৈরি করবে না, কোড লিখবে না, কথোপকথন করবে না, গাণিতিক কাজ করবে না, ছবি পড়বে না বা কোনো ধারাবাহিক ধাপ অনুসরণ করবে না।
কর্মশালায় আমরা নিম্নলিখিত বৈষম্যমূলক মডেলগুলোর মধ্যে একটি বেছে নেব:
- ডিসক্রিমিনেটিভ মডেলগুলোর মধ্যে একটি হলো Jev । এটি TypeSafe AI-এর একটি হোস্টেড এপিআই, যা সেপ্টেম্বর ২০২৬-এ প্রকাশিত হয়। প্রথম মডেলটি হলো
jev-1.13, যাjev-latestঅ্যালিয়াসের মাধ্যমে অ্যাক্সেস করা যায়। এর কোনো প্রকাশিত ওয়েট নেই, তাই এটি ডাউনলোড না করে কল করা হয়। - সিস্টেম ওয়ান মডেল পাওয়ার জন্য জেভই একমাত্র উপায় নয়। গুগলের ডিফিউশনজেমা একটি ওপেন-ওয়েটস মডেল, যা একবারে একটির পরিবর্তে সমান্তরালভাবে টোকেনের একটি সম্পূর্ণ ব্লক লেখে, এবং সেই একই সমান্তরাল পাস একটি নির্দিষ্ট সেট বিকল্পের উপর সম্ভাব্যতাগুলো পড়তে পারে। ডিজেভ-রান-এর মতো ওপেন-সোর্স সার্ভারগুলো এর সামনে জেভ-এর হুবহু এপিআই রাখে, তাই এই ওয়ার্কশপের সবকিছু অপরিবর্তিতভাবে এর বিপরীতে চলে।
রাষ্ট্র ও প্রশ্নাবলী: Choice , Score এবং Noul
প্রতিটি কল স্টেট এবং প্রশ্ন পাঠায়। স্টেট হলো সেই টেক্সট যা আপনি বিচার করাতে চান। এটি একটি স্ট্রিং, একটি JSON অবজেক্ট বা একটি লিস্ট হতে পারে। প্রশ্নগুলো জিজ্ঞাসা করে যে আপনি সেই টেক্সট সম্পর্কে কী জানতে চান। প্রতিটি প্রশ্নের একটি ধরন থাকে: চয়েস (Choice), স্কোর (Score), বা নোল (Noul)। প্রশ্নগুলো সমান্তরালভাবে প্রসেস করা হয়, যা এটিকে দ্রুত সাড়া দিতে সাহায্য করে। প্রয়োজনে আপনি একাধিক প্রশ্ন যোগ করতে পারেন।
- Choice আপনার দেওয়া ২৫৫টি পর্যন্ত বিকল্পের একটি সেট থেকে একটি বিকল্প বেছে নেয়। উত্তরটি হলো সেই বিকল্প, প্রতিটি বিকল্পের জন্য একটি সম্ভাবনা এবং একটি কনফিডেন্স। যখন বিকল্পগুলোর মধ্যে কোনো নির্দিষ্ট ক্রম থাকে না, তখন এটি ব্যবহার করুন: ব্লক হাই, ব্লক লো, ডজ, স্ট্রাইক, ওয়েট।
- স্কোর আপনার বর্ণিত দুই থেকে দশটি ক্রমিক স্তর অনুযায়ী অবস্থাটিকে মূল্যায়ন করে। উত্তরটি হলো স্কেলের একটি অবস্থান (একটি দশমিক সংখ্যা, যেমন ১.৪ মানে "এক এবং দুইয়ের মধ্যে, একের কাছাকাছি"), প্রতিটি স্তরের সম্ভাবনা এবং একটি নিশ্চয়তা। যখন উত্তরটি মাত্রাগত হয়, তখন এটি ব্যবহার করুন: যেমন আগত আঘাতটি কতটা তীব্রভাবে লাগবে।
- Choice এবং Score উভয়ই প্রতিটি বিকল্পের জন্য একটি সম্ভাবনা এবং একটি কনফিডেন্স রিটার্ন করে। মূল পার্থক্যটি হলো উত্তরে। একটি Choice সবচেয়ে সম্ভাব্য বিকল্পটি রিটার্ন করে। একটি Score বিকল্পগুলিকে ক্রমিক স্তর হিসাবে বিবেচনা করে এবং তাদের সম্ভাবনা-ভারিত গড় রিটার্ন করে, যা দুটি স্তরের মধ্যে থাকতে পারে। none 0.05, light 0.55 এবং heavy 0.40 হলে, একটি Choice উত্তর দেয় "light" এবং একটি Score উত্তর দেয় 1.35, যা light এবং heavy-এর মাঝামাঝি। এরিনা সেই মানটি ব্যবহার করে:
choose()1.5 বা তার বেশি ডেঞ্জার স্কোরকে একটি হেভি হিট হিসাবে গণ্য করে।
- Choice এবং Score উভয়ই প্রতিটি বিকল্পের জন্য একটি সম্ভাবনা এবং একটি কনফিডেন্স রিটার্ন করে। মূল পার্থক্যটি হলো উত্তরে। একটি Choice সবচেয়ে সম্ভাব্য বিকল্পটি রিটার্ন করে। একটি Score বিকল্পগুলিকে ক্রমিক স্তর হিসাবে বিবেচনা করে এবং তাদের সম্ভাবনা-ভারিত গড় রিটার্ন করে, যা দুটি স্তরের মধ্যে থাকতে পারে। none 0.05, light 0.55 এবং heavy 0.40 হলে, একটি Choice উত্তর দেয় "light" এবং একটি Score উত্তর দেয় 1.35, যা light এবং heavy-এর মাঝামাঝি। এরিনা সেই মানটি ব্যবহার করে:
- নউল একটি হ্যাঁ/না প্রশ্ন জিজ্ঞাসা করে এবং উত্তরটি হ্যাঁ হওয়ার সম্ভাবনা জানিয়ে দেয়। ১-এর কাছাকাছি হলে উত্তরটি জোরালোভাবে হ্যাঁ, ০-এর কাছাকাছি হলে জোরালোভাবে না, এবং ০.৫-এর কাছাকাছি হলে উত্তরটি "যেকোনো একটি হতে পারে"। এখানে আলাদা কোনো কনফিডেন্স নেই, কারণ সম্ভাবনাটিই হলো এর কনফিডেন্স লেভেল।
সুনির্দিষ্ট প্রশ্ন লিখুন
ডিসক্রিমিনেটিভ মডেলটি সবচেয়ে ভালোভাবে কাজ করে যখন কোনো প্রশ্ন একটি নির্দিষ্ট ও সুনির্দিষ্ট বিষয় জানতে চায়। "পরিস্থিতিটা কী?" প্রশ্নটি একটি বিশ্বাসযোগ্য, কিন্তু কম আত্মবিশ্বাসের উত্তর দেয়। অন্যদিকে, "সঠিক প্রতিক্রিয়া কী?", "প্রতিপক্ষ কি অরক্ষিত?", এবং "এই আঘাতটি কতটা জোরালো হবে?"—এই প্রশ্নগুলো তিনটি সুনির্দিষ্ট উত্তর দেয়, যেগুলোকে আপনার কোড একত্রিত করে।
বিকল্প এবং স্তরগুলির বিবরণ সস্তা এবং গুরুত্বপূর্ণ। ধাপ ৩-এ আপনি যে নিয়মগুলি পড়েন, সেগুলিই বিকল্পের বিবরণ হয়ে যায়: block_high: "Raise the shield. Right against an overhead or a high swing." এভাবেই ডিসক্রিমিনেটিভ মডেলটি অনুরোধ করার সময়, প্রতিটি নিয়ম এক লাইনে শিখে নেয়। এবং পরিস্থিতি অনুযায়ী বিকল্পগুলিও পরিবর্তিত হতে পারে: এরিনা কেবল তখনই cast সুযোগ দেয় যখন কোনো স্পেল প্রস্তুত থাকে।
সম্ভাবনা এবং আত্মবিশ্বাস
একটি বিকল্প উত্তর কোনো লেবেল নয়। এটি হলো লেবেলগুলোর উপর একটি বিন্যাস, এবং লেবেলটি হলো কেবল সর্বোচ্চ দণ্ডটি।
মডেলটি যেভাবে নম্বরটি পায়। এটি সেই একই ধাপ ব্যবহার করে যা একটি ল্যাঙ্গুয়েজ মডেল তার পরবর্তী শব্দটি বেছে নিতে ব্যবহার করে। একটি ট্রান্সফরমার টেক্সটটি পড়ে এবং একটি নির্দিষ্ট অবস্থানে তার শব্দভান্ডারের প্রতিটি টোকেনকে একটি র স্কোর দেয়, যাকে লজিট বলা হয়। একটি উচ্চতর লজিট মানে টোকেনটি সেই অবস্থানের জন্য আরও ভালোভাবে উপযুক্ত। একটি সফটম্যাক্স লজিটগুলোকে এমন সম্ভাবনায় রূপান্তরিত করে, যেগুলোর যোগফল ১ হয়। এরপর একটি ল্যাঙ্গুয়েজ মডেল একটি টোকেন বেছে নেয়, সেটিকে টেক্সটে যোগ করে এবং এই প্রক্রিয়াটি পুনরাবৃত্তি করে। একটি ডিসক্রিমিনেটিভ মডেল সম্ভাবনা গণনার পরেই থেমে যায়।
ফাঁকা স্থান হলো উত্তরপত্রের একটি শূন্যস্থান। সার্ভার নিজেই ফর্মটি লিখে দেয়, যেমন response: ▢ , এবং প্রতিটি প্রশ্নের জন্য একটি করে শূন্যস্থান রাখে। মডেলের একমাত্র কাজ হলো প্রতিটি শূন্যস্থানে কী বসবে তার স্কোর নির্ধারণ করা।
- প্রম্পটটিতে অবস্থা এবং প্রতিটি প্রশ্ন থাকে, এবং প্রতিটি অনুমোদিত উত্তর একটি সংক্ষিপ্ত লেবেল হিসাবে দেওয়া থাকে:
aদিয়ে ব্লক_হাই,bদিয়ে ব্লক_লো, ইত্যাদি। - সার্ভারটি উত্তরপত্রটি যোগ করে, যেখানে প্রতিটি প্রশ্নের জন্য একটি করে খালি জায়গা থাকে।
- মডেলটি একবারে প্রম্পট এবং ফর্মটি পড়ে এবং প্রতিটি টোকেনের প্রতিটি ফাঁকা স্থানে একটি লজিট স্কোর দেয়। ডিফিউশন মডেলটি পুরো ফর্মটি একবারে দেখে এবং সমস্ত ফাঁকা স্থানকে একসাথে স্কোর করে।
- সার্ভারটি শুধুমাত্র অনুমোদিত লেবেলগুলোর লগিট রাখে এবং সেগুলোর উপর একটি সফটম্যাক্স প্রয়োগ করে, ফলে অনুমোদিত উত্তরগুলোর যোগফল ১ হয়।
- রিডটি অনিশ্চিত মনে হলে, সার্ভারটি এলোমেলোভাবে অন্য একটি শুরু থেকে আবার রিড করে এবং রিডগুলোর গড় নেয়।
কনফিডেন্স হলো একটি সংখ্যা যা বলে দেয় উত্তরটি কতটা নিশ্চিত। TypeSafe বিভিন্ন অপশনের মধ্যে সম্ভাবনা কীভাবে বণ্টিত হয়েছে, তার উপর ভিত্তি করে এটি গণনা করে। সব সম্ভাবনা একটি অপশনের উপর থাকলে ১ হয়, এবং সমানভাবে বণ্টিত হলে ০ হয়। তিনটি অপশনের ক্ষেত্রে এটি হলো (৩ × বৃহত্তমটি − ১) / ২।
TypeSafe, Jev-কে ক্যালিব্রেটেড সম্ভাবনার জন্য প্রশিক্ষণ দেয়। এই সম্ভাবনাটি উত্তরটি কতবার সঠিক হয় তার সাথে মিলে যায়। একটি ক্যালিব্রেটেড মডেলে, ০.৭ এ দেওয়া উত্তরগুলো প্রায় ৭০% সময় সঠিক হয়, তাই আত্মবিশ্বাসের একটি থ্রেশহোল্ড হলো আপনি কতবার একটি ভুল উত্তর গ্রহণ করবেন তার একটি সীমা। এই ওয়ার্কশপের DiffusionGemma সার্ভারটি তার রিডগুলোর গড় করে সর্বোচ্চ সম্ভাবনাটিকেই আত্মবিশ্বাস হিসেবে রিপোর্ট করে। যখন রিডগুলোর মধ্যে অমিল দেখা যায়, তখন গড়টি ছড়িয়ে পড়ে এবং আত্মবিশ্বাস কমে যায়।
মূল কথা: উত্তরটি আপনাকে বলে দেয় কী করতে হবে । আর আত্মবিশ্বাস আপনাকে বলে দেয় কাজটি করতে হবে কি না ।
থ্রেশহোল্ড
কোডে আপনি কী পদক্ষেপ নেবেন, তা থ্রেশহোল্ড দ্বারাই নির্ধারিত হয়। মডেলটি একটি কনফিডেন্স বা প্রোবাবিলিটি রিটার্ন করে। আপনার কোড সেটিকে আপনার বেছে নেওয়া একটি সংখ্যার সাথে তুলনা করে এবং সেই ফলাফলের ভিত্তিতেই কী ঘটবে তা স্থির হয়।
প্রতিটি কাজের জন্য একটি নির্দিষ্ট সীমা রয়েছে। TypeSafe আত্মবিশ্বাসকে কয়েকটি স্তরে ভাগ করার পরামর্শ দেয়। উচ্চ আত্মবিশ্বাস নিজে থেকেই কাজ করে। মাঝারি আত্মবিশ্বাস যাচাইয়ের মাধ্যমে কাজ করে, যেমন নিশ্চিতকরণের জন্য জিজ্ঞাসা করা বা পর্যালোচনার জন্য কেসটি চিহ্নিত করা। নিম্ন আত্মবিশ্বাস কোনো কাজ করে না এবং নিরাপদ কোনো ব্যবস্থা বা কোনো ব্যক্তির কাছে ফিরে যায়।
TRUST = 0.40 # below this, the answer is a guess
AUTO = 0.80 # at or above this, act without a check
def route(answer):
if answer.confidence >= AUTO:
return act(answer.choice) # high: act on its own
if answer.confidence >= TRUST:
return confirm(answer.choice) # medium: act with a check
return fall_back() # low: do something safe
এরিনার নিয়মাবলী। এরিনার থ্রেশহোল্ডগুলো choose() ফাংশনে থাকে, যা আপনি ধাপ ৫-এ রান করেন।
TRUST_CONFIDENCE = 0.40 # below this, the model is guessing between responses
HEAVY_DANGER = 1.5 # a danger score at or above this is a heavy hit
SPEND_ON_OPENING = 0.60 # exposed at or above this, with a spell ready, cast
def choose(answers, spell_ready):
response = answers["response"]
exposed = answers["exposed"].noul
danger = answers["danger"].score
action = response.choice
if response.confidence < TRUST_CONFIDENCE and danger >= HEAVY_DANGER:
action = "dodge" # shaky answer, heavy hit coming
if spell_ready and action == "strike" and exposed >= SPEND_ON_OPENING:
action = "cast" # a clear opening is worth the spell
return action
সতর্কীকরণ: একটি বৈধ উত্তর সবসময় সঠিক হয় না। ডিসক্রিমিনেটিভ মডেল এমন কোনো বিকল্প দেখাতে পারে না যা আপনি দেননি, তাই এটি কখনো কোনো চাল নিজে থেকে বেছে নেয় না, কিন্তু এটি ভুলটি বেছে নিতে পারে, কখনো কখনো বেশ আত্মবিশ্বাসের সাথেই। কোনো থ্রেশহোল্ডকে বিশ্বাস করার আগে, আপনি পূর্বে বিচার করেছেন এমন পরিস্থিতির সাথে আপনার প্রশ্নগুলো মিলিয়ে পরীক্ষা করে নিন।
মডেলের সাহায্যে সিদ্ধান্ত স্বয়ংক্রিয় করুন

অনুরোধ এবং প্রতিক্রিয়া
অনুরোধ। TypeSafe Python SDK আপনাকে প্রশ্ন তৈরি করতে এবং সেগুলো মডেলে পাঠাতে সাহায্য করে।
from typesafe_sdk import Choice, Noul, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state={"opponent": OPPONENT, "telegraph": telegraph},
questions={
"response": Choice(instructions="What is the right response?", criteria=RESPONSES),
"exposed": Noul(instructions="Is the opponent exposed to a counter-attack right now?"),
},
)
response.choices["response"].choice # "strike"
response.nouls["exposed"].noul # 0.97
প্রতি টিক-এ একটি অনুরোধ
প্রতিটি টিক-এ, অ্যাপটি অবস্থা হিসেবে টেলিগ্রাফ পাঠায় এবং একটি কলে তিনটি জিনিস জিজ্ঞাসা করে:
- পাঁচটি (অথবা ছয়টি, যখন কোনো মন্ত্র প্রস্তুত থাকে) উত্তরের মধ্যে কোনটি সঠিক? একটি পছন্দ।
- দৈত্যটি এই মুহূর্তে কোনো পাল্টা আক্রমণের সম্মুখীন হচ্ছে কি না । একটি নোল।
- তিন-স্তরের একটি মাপকাঠিতে আগত আঘাতটি কতটা জোরালো । একটি স্কোর।
def reflex_questions(spell_ready):
options = dict(RESPONSES)
if spell_ready:
options["cast"] = CAST # only offered when there is a spell
return {
"response": Choice(instructions="The opponent has just done this. What is the right response?",
criteria=options),
"exposed": Noul(instructions="Is the opponent exposed to a counter-attack right now?"),
"danger": Score(instructions="How much damage is about to land if the fighter does nothing?",
criteria=["None: this is not an attack.", "A light hit.", "A heavy hit."]),
}
choose() ফাংশন
ধাপ ৪-এর থ্রেশহোল্ডগুলোর কথা মনে আছে? choose() ফাংশন মডেলের উত্তরগুলোকে কিছু নির্দিষ্ট সংখ্যার সাথে তুলনা করে, এবং এই নির্দিষ্ট সংখ্যাগুলোই হলো থ্রেশহোল্ড।
TRUST_CONFIDENCE = 0.40
HEAVY_DANGER = 1.5
SPEND_ON_OPENING = 0.60
def choose(answers, spell_ready):
action = answers["response"].choice
if answers["response"].confidence < TRUST_CONFIDENCE and answers["danger"].score >= HEAVY_DANGER:
action = "dodge" # shaky call, heavy hit coming: play it safe
if spell_ready and action == "strike" and answers["exposed"].noul >= SPEND_ON_OPENING:
action = "cast" # the Discriminative model saw the opening; the code spends the spell
...
choose() হলো দুটি নিয়মসহ টাইপ করা মান পড়ার একটি সাধারণ পাইথন ফাংশন। ডিসক্রিমিনেটিভ মডেলটি এর সম্ভাবনা ও বিশ্লেষণ প্রদান করে এবং কোডটি নিয়মগুলোর জন্য থ্রেশহোল্ড ব্যবহার করে। নির্বাচিত অ্যাকশনটি ইঞ্জিনে পাঠানো হয়, যেখানে এটি ওগরের বিরুদ্ধে লড়াইয়ে ব্যবহৃত হবে।
মূল কথা: প্রশ্নগুলো এবং থ্রেশহোল্ডগুলো এক জায়গায় রাখুন। এগুলোই সিস্টেম ওয়ান ইন্টিগ্রেশনের সেই অংশ যা আপনি সবচেয়ে বেশি টিউন করবেন।
প্রতিক্রিয়ার সময়, ইনপুট-ভিত্তিক মূল্য নির্ধারণ, এবং সিদ্ধান্ত যুক্তি
- প্রতিটি সিদ্ধান্তের জন্য প্রতিক্রিয়ার সময়। খ অংশের লড়াইয়ের প্রতিটি টিক প্রায় একশ মিলিসেকেন্ডে ফিরে এসেছিল, কয়েকটি দুই বা তিন মিলিসেকেন্ডে। একটি গেম লুপ, একটি অনুরোধের পথ, অথবা কোনো ব্যক্তি বা ভাষা মডেলের কাছে পৌঁছানোর আগে প্রতিটি বার্তা যাচাই করার জন্য এটি যথেষ্ট দ্রুত।
- ইনপুট-ভিত্তিক মূল্য নির্ধারণ। একটি সম্পূর্ণ লড়াই, যেখানে প্রতিটিতে তিনটি প্রশ্নসহ ষাটটি সিদ্ধান্ত থাকে, তার খরচ এক সেন্টের দশ ভাগের এক ভাগেরও অনেক কম। আউটপুট টোকেন শূন্য, কারণ কিছুই তৈরি হয়নি। এর ফলস্বরূপ, আপনার প্রয়োজনের চেয়ে বেশি প্রশ্ন করার সামর্থ্য থাকে। এরিনা প্রতিটি টিকে জিজ্ঞাসা করে যে ওগ্রেটি উন্মুক্ত কিনা, যদিও শুধুমাত্র
strikeএবংcastবিবেচ্য, কারণ প্রশ্ন করা প্রায় বিনামূল্যে এবং উত্তরটি ড্যাশবোর্ডে বেশ দরকারি। TypeSafe একে স্পেকুলেটিভ ফ্যান-আউট বলে। - আত্মবিশ্বাস এবং ঝুঁকিকে একত্রিত করুন। যখন ডিসক্রিমিনেটিভ মডেলের প্রতিক্রিয়ার উপর আত্মবিশ্বাস ০.৪০-এর নিচে থাকে এবং ঝুঁকির স্কোর একটি বড় ধরনের আঘাত আসার ইঙ্গিত দেয়,
choose()একটি ডজ বা এড়িয়ে যাওয়ার মাধ্যমে এটিকে অগ্রাহ্য করে। একটি ডজ কদাচিৎ সেরা উত্তর হয়, তবে এটি কদাচিৎ সবচেয়ে খারাপও হয়। কোনো পূর্ণ সংখ্যা থেকে নয়, বরং প্রতিটি ভুলের খরচের উপর ভিত্তি করে থ্রেশহোল্ড বা সীমা নির্ধারণ করুন এবং সেগুলোকে এমন সব সংকেতের সাথে মিলিয়ে পরীক্ষা করুন যা আপনি ইতিমধ্যেই হাতে-কলমে বিচার করেছেন। TypeSafe-এর নিজস্ব পরামর্শ: যদি কোনো সিদ্ধান্ত বারবার ভুল হয়, তাহলে থ্রেশহোল্ড পরিবর্তন করার আগে প্রশ্নটিকে আরও সুনির্দিষ্ট করুন।
ADK ওয়ার্কফ্লোতে মডেলগুলিকে একত্রিত করুন

প্রতি-টিক সিদ্ধান্তের সীমাবদ্ধতা এবং কেন সঠিক প্রতিক্রিয়া যথেষ্ট নয়
ধাপ ৫-এর লড়াইয়ের শেষ লাইনেই সব বলা আছে: ওগরটা প্রায় আঁচড় না খেয়েই ধুঁকতে ধুঁকতে চলে যায় । ডিসক্রিমিনেটিভ মডেলটি কোনো ক্ষতিই নেয়নি এবং প্রতি আঘাতে সামান্য ক্ষতি করেছে, আর ৩০০ হিট পয়েন্ট হলো সামান্যকে ষাট দিয়ে গুণ করার চেয়েও বেশি। রিংয়ের কোণায় থাকা স্পেল কার্ডটি পুরোটা সময় ধরেই সেখানে ছিল। ওটা পড়তে হলে এমন একটি মডেল লাগে যা ছবি দেখতে পারে।
ওগরটির ৩০০ হিট পয়েন্ট আছে। একটি সঠিক চালে ৩ পয়েন্ট যোগ হয়। চামড়া পুরু হওয়ায়, কোনো ফাঁকে আঘাত করলে ৮ পয়েন্ট পাওয়া যায়। এমনকি একটি নিখুঁত ষাট-টিকের লড়াইয়ের পরেও ওগরটি ক্ষতবিক্ষত হয়ে দাঁড়িয়ে থাকে এবং খেলাটি এটিকে ড্র ঘোষণা করে। সেখানেই ৫ নং ধাপটি শেষ হয়েছিল: মডেলটি ভালোভাবে আত্মরক্ষা করেও জিততে পারেনি।
কেবলমাত্র মন্ত্রই প্রকৃত ক্ষতি করে: নিখুঁতভাবে প্রয়োগ করলে ৪৫, আর সুযোগ বুঝে আঘাত হানলে ৬৭।
প্রতিটি কাজ সঠিক মডেলকে বরাদ্দ করুন।
রিংয়ের কোণায় থাকা স্পেল কার্ডটিই জেতার উপায়, এবং এটি পড়া কোনো পাঠ্য সমস্যা নয়: এটি একটি ছবি, যেখানে একটি রঙ এবং পরপর তিনটি আকৃতি রয়েছে, এবং এর সাথে মিলিয়ে মন্ত্রটি উচ্চারণ করতে হয়। এর জন্য এমন একজন মডেল প্রয়োজন যিনি একটি ছবির দিকে তাকিয়ে সেটির ওপর কয়েক সেকেন্ড সময় নিতে পারেন। একটি লড়াইয়ে, কয়েক সেকেন্ড মানে দশ টিক।
সুতরাং কর্মপ্রবাহটি উভয়কেই ব্যবহার করে, প্রত্যেকে তার নিজস্ব গতিতে:
- ডিসক্রিমিনেটিভ মডেলটি লড়াই করে। প্রতিটি টিক-এ, একটি কলে, একটি সিদ্ধান্তে, একশো মিলিসেকেন্ড ধরে। লুপটি নিজের চেয়ে ধীরগতির কোনো কিছুর জন্য কখনো অপেক্ষা করে না।
- জেমিনি পড়ে এবং গান গায়। ঘণ্টার শব্দ থেকে শুরু করে নিজের শাখায় থেকে, এটি এরিনার স্ক্রিন থেকে স্পেল কার্ডটি একটি ছবি হিসেবে তুলে নেয়, রঙ ও আকৃতিগুলোর নাম বলে এবং একটি মন্ত্র উচ্চারণ করে। এরিনা স্পেল কার্ডের উত্তরের সাথে গানটির বিচার করে, যা সার্ভার থেকে কখনও বের হয় না।
- প্রতিটি বিনিময়ের পর, ফাইটার স্লটটি পরীক্ষা করে। একটি
check_spellনোড অবস্থাটি দেখে। প্রস্তুত নয়: এটি জেমিনি কতক্ষণ ধরে গান গাইছে তা জানিয়ে দেয় এবং সরাসরি পরবর্তী টিকে ফিরে যায়। এটি কখনও অপেক্ষা করে না। প্রস্তুত:castDiscriminative মডেলকে দেওয়া বিকল্পগুলোর সাথে যুক্ত হয়, এবং Discriminative মডেল একটি খালি হওয়ার খবর দেওয়ার সাথে সাথেইchoose()স্পেলটি ব্যবহার করে ফেলে। যখন স্পেলটি ব্যবহার হয়ে যায়, স্ক্রিন একটি নতুন স্পেল কার্ড আঁকে এবং ধীরগতির থ্রেডটি আবার শুরু হয়। একটি ভুল পড়া গান স্পেল কার্ডটি পুড়িয়ে দেয়, এবং ধীরগতির থ্রেডটি নতুন কার্ডটি পড়ে। - মিথুন রাশি একেবারে শেষে একবার একটি ছোট গল্প লেখে ।

ভিন্ন ভিন্ন লেটেন্সি সহ সমান্তরাল শাখা এবং একটি ইভেন্ট লুপ
এটি একটি ADK ওয়ার্কফ্লো : এজ দ্বারা সংযুক্ত নোডের একটি গ্রাফ। একটি নোড হলো একটি সাধারণ পাইথন ফাংশন বা একটি LLM এজেন্ট। একটি নোড থেকে নোডের একটি টাপলে যাওয়া এজ হলো একটি ফ্যান-আউট: উভয়ই একই সাথে শুরু হয়। যে নোড একটি route সহ Event রিটার্ন করে, সেটি পরবর্তী এজটি বেছে নেয়, এবং যে নোড নিজেকেই রুট করে, সেটি একটি লুপ।
Think of it as two threads. Thread 1 is slow: read the spell card, sing, store the spell. Thread 2 is fast: tick, check the slot, tick again. Thread 1 ends in a function that writes the judged spell into session state and returns no output. Thread 2's check_spell reads that state after every exchange. Neither thread calls or waits for the other; they only share state.
Key takeaway: Put the decisions in code and give each model a narrow job at its own pace.
ADK runs both branches as tasks on one event loop, in a single thread. Only one task runs at a time. When a task reaches await , it waits for its answer, and the loop runs the other branch in the meantime. The fast branch waits for the model for about a tenth of a second, and the slow branch waits for Gemini for several seconds, so neither holds up the other.
Slow branch
read_rune() takes the spell card off the screen as an image.
def read_rune(ctx: Context, node_input) -> Event:
png = _arena(ctx).rune_png() # exactly what the screen shows
return Event(output=types.Content(role="user", parts=[
types.Part(text="This spell card is on the arena's screen right now. Sing the spell that matches it."),
types.Part.from_bytes(data=png, mime_type="image/png"),
]))
spellwright is Gemini. It reads the image and answers in a fixed shape.
class Sung(BaseModel):
element: str # fire, frost, earth, storm
glyphs: list[str] # three of: circle, ring, square, diamond, triangle, cross, crescent, bar
incantation: str
spellwright = LlmAgent(name="spellwright", model="gemini-flash-latest",
instruction="You are the spellwright ... read the three shapes left to right ...",
output_schema=Sung)
spell_ready() has the arena judge the spell, then stores it or tries again.
def spell_ready(ctx: Context, node_input: dict) -> Event:
spell = _arena(ctx).sung(dict(node_input)) # the arena judges it against the spell card
return Event(state={"spell": spell if spell["damage"] > 0 else None},
route="retry" if spell["damage"] <= 0 else "stored")
A function node can return a Content with an image part, and the LLM node receives it as its user turn. spell_ready returns an Event with a state delta and no output . The next tick reads the spell from state, and a branch with no output is not a second ending for the graph: ADK requires one terminal output, and that is the fight's.
Note: The judging is code, in the arena, against the spell card's hidden answer. A perfect reading does 45, more into an opening. Two shapes right does 25. A misread fizzles and burns the spell card. Gemini is not asked whether it was right.
Fast branch
tick() plays one exchange, then picks the next edge.
async def tick(ctx: Context, node_input) -> Event:
arena = _arena(ctx)
spell = ctx.state.get("spell") # did the slow branch deliver?
move = await asyncio.to_thread(arena.telegraph)
async with AsyncTypeSafeClient() as jev:
answers = await jev.system_one(
state={"opponent": engine.OPPONENT["description"], "telegraph": move["telegraph"]},
questions=reflex.reflex_questions(spell_ready=spell is not None),
)
decision = reflex.choose(answers.answers, spell_ready=spell is not None)
entry = await asyncio.to_thread(arena.respond, decision["action"], decision, ...)
over = entry["you"] <= 0 or entry["foe"] <= 0 or entry["tick"] >= engine.MAX_TICKS
routes = [] # which arrows in the graph to follow next
if entry["spell_used"] and not over:
routes.append("recast") # a new spell card is on the screen: read it
routes.append("done" if over else "next")
return Event(output="fight", route=routes, state={"tick": ..., "spell": None, ...})
check_spell() looks at the spell slot after every exchange.
def check_spell(ctx: Context, node_input) -> Event:
spell = ctx.state.get("spell") # thread 1 writes it; this only reads
if spell:
report = {"ready": True}
else:
report = {"ready": False, "waited": now - ctx.state["forging_since"]}
return Event(output="fight", route="again", state={"spell_check": report})
check_spell looks at the slot after every exchange. It never blocks: if the spell is not ready, it reports that and moves on.
Three things carry the design. The Discriminative model call is await ed with the async client, so the loop yields while it waits and the Gemini branch keeps running. The questions are built fresh each tick, so cast appears only when there is something to cast. And route can be a list: ["recast", "next"] takes both edges at once.
The arena itself is behind a small client: the running app over HTTP when there is one, so the page shows the fight; the engine in-process when there is not.
Graph definition
root_agent = Workflow(
name="arena",
edges=[
("START", enter),
(enter, (read_rune, tick)), # fan-out: slow branch + fast loop
(read_rune, spellwright, spell_ready),
(spell_ready, {"retry": read_rune, "stored": rest}), # misread: read the new spell card; else rest
(tick, {"next": check_spell, "recast": read_rune, "done": summarise}),
(check_spell, {"again": tick}), # not ready? keep fighting
(summarise, bard, finish),
],
)
A tuple as a target is a fan-out. A tuple as an edge is a chain. A dict maps route names to nodes. tick → check_spell → tick is the fast loop. "recast": read_rune starts the slow thread again after a spell is spent, "retry" does the same after a fizzle, and "stored": rest lets the slow thread end quietly, with no output, once the spell is in the slot. ADK requires at least one routed edge in a cycle, so an unconditional loop is rejected before it can run forever.
Note: root_agent is what ADK's tools look for. adk web agents from the root of the workshop opens the dev UI with the arena in it, if you want to see the graph and the events in a browser rather than a terminal.