Aidemy: Google Cloud-এ LangGraph, EDA, এবং Generative AI-এর সাহায্যে মাল্টি-এজেন্ট সিস্টেম তৈরি করা, Aidemy: Google ক্লাউডে LangGraph, EDA, এবং জেনারেটিভ AI দিয়ে মাল্টি-এজেন্ট সিস্টেম তৈরি করা

Aidemy:
Google ক্লাউডে ল্যাংগ্রাফ, ইডিএ এবং জেনারেটিভ এআই সহ মাল্টি-এজেন্ট সিস্টেম তৈরি করা

এই কোডল্যাব সম্পর্কে

subjectমার্চ ১৩, ২০২৫-এ শেষবার আপডেট করা হয়েছে
account_circleChristina Lin-এর লেখা

1. ভূমিকা

এই যে! সুতরাং, আপনি এজেন্টদের ধারণার মধ্যে আছেন - সামান্য সাহায্যকারী যারা আপনার আঙুল না তুলেও আপনার জন্য কাজ করতে পারে, তাই না? অসাধারন! কিন্তু চলুন বাস্তব হয়ে উঠুন, একজন এজেন্ট সবসময় এটি কাটাতে যাচ্ছে না, বিশেষ করে যখন আপনি বড়, আরও জটিল প্রকল্পগুলি মোকাবেলা করছেন। আপনি সম্ভবত তাদের একটি সম্পূর্ণ দলের প্রয়োজন যাচ্ছে! সেখানেই মাল্টি-এজেন্ট সিস্টেম আসে।

এজেন্ট, যখন এলএলএম দ্বারা চালিত হয়, আপনাকে পুরানো-স্কুল হার্ড কোডিংয়ের তুলনায় অবিশ্বাস্য নমনীয়তা দেয়। কিন্তু, এবং সবসময় একটি কিন্তু আছে, তারা তাদের নিজস্ব জটিল চ্যালেঞ্জ নিয়ে আসে। এবং যে আমরা এই কর্মশালা মধ্যে ডুব যাচ্ছে ঠিক কি!

শিরোনাম

এখানে আপনি যা শেখার আশা করতে পারেন - এটিকে আপনার এজেন্ট গেমের সমতলকরণ হিসাবে ভাবুন:

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

এজেন্ট অর্কেস্ট্রেশন, আপনার উপায় : আমরা আপনার এজেন্টদের অর্কেস্ট্রেট করার বিভিন্ন উপায় অন্বেষণ করব, সহজ সরল পথ থেকে আরও জটিল বহু-পাথের পরিস্থিতিতে। এটিকে আপনার এজেন্ট দলের প্রবাহকে নির্দেশ করে বলে মনে করুন।

মাল্টি-এজেন্ট সিস্টেম : আপনি কীভাবে একটি সিস্টেম সেট আপ করবেন যেখানে আপনার এজেন্টরা সহযোগিতা করতে পারে এবং একসাথে কাজগুলি করতে পারে তা আবিষ্কার করবেন - সমস্ত ধন্যবাদ একটি ইভেন্ট-চালিত আর্কিটেকচারের জন্য।

এলএলএম স্বাধীনতা – কাজের জন্য সেরাটি ব্যবহার করুন: আমরা কেবল একটি এলএলএম-এ আটকে নেই! আপনি দেখতে পাবেন কিভাবে একাধিক LLM ব্যবহার করতে হয়, তাদের বিভিন্ন ভূমিকা বরাদ্দ করে শান্ত "চিন্তামূলক মডেল" ব্যবহার করে সমস্যা সমাধানের শক্তি বাড়ানোর জন্য।

গতিশীল বিষয়বস্তু? কোন সমস্যা নেই! : কল্পনা করুন যে আপনার এজেন্ট ডায়নামিক কন্টেন্ট তৈরি করছে যা বিশেষভাবে প্রতিটি ব্যবহারকারীর জন্য বাস্তব-সময়ে তৈরি করা হয়েছে। আমরা আপনাকে দেখাব কিভাবে এটি করতে হবে!

Google ক্লাউডের সাথে এটিকে ক্লাউডে নিয়ে যাওয়া : শুধু একটি নোটবুকে খেলার কথা ভুলে যান। আমরা আপনাকে দেখাব কিভাবে Google ক্লাউডে আপনার মাল্টি-এজেন্ট সিস্টেমকে আর্কিটেক্ট এবং স্থাপন করতে হয় যাতে এটি বাস্তব জগতের জন্য প্রস্তুত হয়!

এই প্রকল্পটি আমরা যে সমস্ত কৌশলগুলি নিয়ে কথা বলেছি তা কীভাবে ব্যবহার করতে হয় তার একটি ভাল উদাহরণ হবে।

2. স্থাপত্য

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

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

সিস্টেমের দুটি সাইট রয়েছে: একটি আসন্ন সপ্তাহের জন্য পাঠ পরিকল্পনা তৈরি করার জন্য শিক্ষকদের জন্য,

পরিকল্পনাকারী

এবং একটি শিক্ষার্থীদের জন্য কুইজ, অডিও রিক্যাপ এবং অ্যাসাইনমেন্ট অ্যাক্সেস করার জন্য। পোর্টাল

ঠিক আছে, আসুন আমাদের শিক্ষা সহকারী, Aidemy-কে শক্তি প্রদানকারী স্থাপত্যের মধ্য দিয়ে চলুন। আপনি দেখতে পাচ্ছেন, আমরা এটিকে কয়েকটি মূল উপাদানে বিভক্ত করেছি, সবাই এটি ঘটানোর জন্য একসাথে কাজ করছে।

স্থাপত্য

মূল স্থাপত্য উপাদান এবং প্রযুক্তি :

Google ক্লাউড প্ল্যাটফর্ম (GCP) : পুরো সিস্টেমের কেন্দ্রীয়:

  • Vertex AI: Google এর Gemini LLMs অ্যাক্সেস করে।
  • ক্লাউড রান: কনটেইনারাইজড এজেন্ট এবং ফাংশন স্থাপনের জন্য সার্ভারহীন প্ল্যাটফর্ম।
  • ক্লাউড এসকিউএল: পাঠ্যক্রমের ডেটার জন্য পোস্টগ্রেএসকিউএল ডাটাবেস।
  • পাব/সাব এবং ইভেন্টর্ক: ইভেন্ট-চালিত আর্কিটেকচারের ভিত্তি, উপাদানগুলির মধ্যে অ্যাসিঙ্ক্রোনাস যোগাযোগ সক্ষম করে।
  • ক্লাউড স্টোরেজ: অডিও রিক্যাপ এবং অ্যাসাইনমেন্ট ফাইল সঞ্চয় করে।
  • সিক্রেট ম্যানেজার: নিরাপদে ডাটাবেস শংসাপত্র পরিচালনা করে।
  • আর্টিফ্যাক্ট রেজিস্ট্রি: এজেন্টদের জন্য ডকার ছবি সংরক্ষণ করে।
  • কম্পিউট ইঞ্জিন: বিক্রেতা সমাধানের উপর নির্ভর করার পরিবর্তে স্ব-হোস্টেড এলএলএম স্থাপন করা

এলএলএম : সিস্টেমের "মস্তিষ্ক":

  • গুগলের মিথুন মডেল: (জেমিনি 1.0 প্রো, জেমিনি 2 ফ্ল্যাশ, জেমিনি 2 ফ্ল্যাশ থিঙ্কিং, জেমিনি 1.5-প্রো) পাঠ পরিকল্পনা, বিষয়বস্তু তৈরি, গতিশীল HTML তৈরি, ক্যুইজ ব্যাখ্যা এবং অ্যাসাইনমেন্টগুলি একত্রিত করার জন্য ব্যবহৃত হয়।
  • DeepSeek: স্ব-অধ্যয়নের কার্যাদি তৈরি করার বিশেষ কাজের জন্য ব্যবহার করা হয়

ল্যাংচেইন এবং ল্যাংগ্রাফ : এলএলএম অ্যাপ্লিকেশন ডেভেলপমেন্টের ফ্রেমওয়ার্ক

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

সংক্ষেপে, আমাদের আর্কিটেকচার স্ট্রাকচার্ড ডেটা এবং ইভেন্ট-চালিত যোগাযোগের সাথে LLM-এর শক্তিকে একত্রিত করে, যা Google ক্লাউডে চলে। এটি আমাদের একটি পরিমাপযোগ্য, নির্ভরযোগ্য এবং কার্যকর শিক্ষণ সহকারী তৈরি করতে দেয়।

3. আপনি শুরু করার আগে

Google ক্লাউড কনসোলে , প্রকল্প নির্বাচক পৃষ্ঠায়, একটি Google ক্লাউড প্রকল্প নির্বাচন করুন বা তৈরি করুন। নিশ্চিত করুন যে আপনার ক্লাউড প্রকল্পের জন্য বিলিং সক্ষম করা আছে৷ একটি প্রকল্পে বিলিং সক্ষম কিনা তা পরীক্ষা করতে শিখুন

👉Google ক্লাউড কনসোলের শীর্ষে ক্লাউড শেল সক্রিয় করুন- এ ক্লিক করুন (এটি ক্লাউড শেল প্যানের শীর্ষে টার্মিনাল আকৃতির আইকন), "ওপেন এডিটর" বোতামে ক্লিক করুন (এটি একটি পেন্সিল সহ একটি খোলা ফোল্ডারের মতো দেখায়)। এটি উইন্ডোতে ক্লাউড শেল কোড এডিটর খুলবে। আপনি বাম দিকে একটি ফাইল এক্সপ্লোরার দেখতে পাবেন।

মেঘের শেল

👉 নিচের স্ট্যাটাস বারে দেখানো ক্লাউড কোড সাইন-ইন বোতামে ক্লিক করুন। নির্দেশিত হিসাবে প্লাগইন অনুমোদন করুন. যদি আপনি ক্লাউড কোড দেখেন - স্ট্যাটাস বারে কোনো প্রকল্প নেই , তাহলে সেটি নির্বাচন করুন তারপর ড্রপ ডাউনে 'একটি Google ক্লাউড প্রকল্প নির্বাচন করুন' এবং তারপরে আপনার তৈরি করা প্রকল্পগুলির তালিকা থেকে নির্দিষ্ট Google ক্লাউড প্রকল্পটি নির্বাচন করুন৷

লগইন প্রকল্প

👉ক্লাউড আইডিইতে টার্মিনাল খুলুন, নতুন টার্মিনাল

👉টার্মিনালে, যাচাই করুন যে আপনি ইতিমধ্যেই প্রমাণীকরণ করেছেন এবং নিম্নলিখিত কমান্ডটি ব্যবহার করে প্রকল্পটি আপনার প্রকল্প আইডিতে সেট করা আছে:

gcloud auth list

👉এবং চালান:

gcloud config set project <YOUR_PROJECT_ID>

👉প্রয়োজনীয় Google ক্লাউড এপিআই সক্ষম করতে নিম্নলিখিত কমান্ডটি চালান:

gcloud services enable compute.googleapis.com  \
                       
storage.googleapis.com  \
                       
run.googleapis.com  \
                       
artifactregistry.googleapis.com  \
                       
aiplatform.googleapis.com \
                       
eventarc.googleapis.com \
                       
sqladmin.googleapis.com \
                       
secretmanager.googleapis.com \
                       
cloudbuild.googleapis.com \
                       
cloudresourcemanager.googleapis.com \
                       
cloudfunctions.googleapis.com

এতে কয়েক মিনিট সময় লাগতে পারে..

ক্লাউড শেল IDE-তে জেমিনি কোড সহায়তা সক্ষম করুন৷

দেখানো হিসাবে বাম প্যানেলে কোড সহায়তা বোতামে ক্লিক করুন এবং শেষবারের মতো সঠিক Google ক্লাউড প্রকল্পটি নির্বাচন করুন৷ যদি আপনাকে Cloud AI Companion API সক্ষম করতে বলা হয়, তাহলে অনুগ্রহ করে তা করুন এবং এগিয়ে যান। একবার আপনি আপনার Google ক্লাউড প্রকল্প নির্বাচন করার পরে, নিশ্চিত করুন যে আপনি স্ট্যাটাস বারে ক্লাউড কোড স্ট্যাটাস মেসেজে দেখতে পাচ্ছেন এবং নীচে দেখানো স্ট্যাটাস বারে আপনার ডানদিকে কোড অ্যাসিস্ট সক্ষম করা আছে:

কোডঅ্যাসিস্ট সক্ষম করুন

অনুমতি সেট আপ করা হচ্ছে

👉সেটআপ সার্ভিস অ্যাকাউন্টের অনুমতি। টার্মিনালে, চালান:

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")

echo "Here's your SERVICE_ACCOUNT_NAME $SERVICE_ACCOUNT_NAME"

👉 অনুমতি দিন। টার্মিনালে, চালান:

#Cloud Storage (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/storage.objectAdmin"

#Pub/Sub (Publish/Receive):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.publisher"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.subscriber"


#Cloud SQL (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/cloudsql.editor"


#Eventarc (Receive Events):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/iam.serviceAccountTokenCreator"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/eventarc.eventReceiver"

#Vertex AI (User):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/aiplatform.user"

#Secret Manager (Read):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/secretmanager.secretAccessor"

👉আপনার IAM কনসোলে ফলাফল যাচাই করুন আইএএম কনসোল

aidemy নামে একটি ক্লাউড এসকিউএল ইন্সট্যান্স তৈরি করতে টার্মিনালে নিম্নলিখিত কমান্ডগুলি চালান। আমাদের এটি পরে প্রয়োজন হবে, কিন্তু যেহেতু এই প্রক্রিয়াটিতে কিছু সময় লাগতে পারে, তাই আমরা এখনই এটি করব৷

gcloud sql instances create aidemy \
   
--database-version=POSTGRES_14 \
   
--cpu=2 \
   
--memory=4GB \
   
--region=us-central1 \
   
--root-password=1234qwer \
   
--storage-size=10GB \
   
--storage-auto-increase

4. প্রথম এজেন্ট নির্মাণ

আমরা জটিল মাল্টি-এজেন্ট সিস্টেমে ডুব দেওয়ার আগে, আমাদের একটি মৌলিক বিল্ডিং ব্লক স্থাপন করতে হবে: একটি একক, কার্যকরী এজেন্ট। এই বিভাগে, আমরা একটি সাধারণ "বই প্রদানকারী" এজেন্ট তৈরি করে আমাদের প্রথম পদক্ষেপ নেব৷ বই প্রদানকারী এজেন্ট একটি বিভাগকে ইনপুট হিসাবে নেয় এবং সেই বিভাগের মধ্যে একটি JSON প্রতিনিধিত্বমূলক বই তৈরি করতে একটি Gemini LLM ব্যবহার করে। এটি তখন এই বইয়ের সুপারিশগুলিকে একটি REST API এন্ডপয়েন্ট হিসাবে পরিবেশন করে৷

বই প্রদানকারী

👉অন্য একটি ব্রাউজার ট্যাবে, আপনার ওয়েব ব্রাউজারে Google ক্লাউড কনসোল খুলুন, নেভিগেশন মেনুতে (☰), "ক্লাউড রান" এ যান। "+... WRITE A FUNCTION" বোতামে ক্লিক করুন।

ফাংশন তৈরি করুন

👉পরবর্তীতে আমরা ক্লাউড রান ফাংশনের মৌলিক সেটিংস কনফিগার করব:

  • পরিষেবার নাম: book-provider
  • অঞ্চল: us-central1
  • রানটাইম: Python 3.12
  • প্রমাণীকরণ: Allow unauthenticated invocations

👉অন্যান্য সেটিংস ডিফল্ট হিসেবে ছেড়ে দিয়ে Create এ ক্লিক করুন। এটি আপনাকে সোর্স কোড এডিটরে নিয়ে যাবে।

আপনি প্রি-পপুলেটেড main.py এবং requirements.txt ফাইল দেখতে পাবেন।

main.py ফাংশনের ব্যবসায়িক যুক্তি থাকবে, requirements.txt প্রয়োজনীয় প্যাকেজ থাকবে।

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

Use the functions_framework library to be deployable as an HTTP function. 
Accept a request with category and number_of_book parameters (either in JSON body or query string).
Use langchain and gemini to generate the data for book with fields bookname, author, publisher, publishing_date.
Use pydantic to define a Book model with the fields: bookname (string, description: "Name of the book"), author (string, description: "Name of the author"), publisher (string, description: "Name of the publisher"), and publishing_date (string, description: "Date of publishing").
Use langchain and gemini model to generate book data. the output should follow the format defined in Book model.

The logic should use JsonOutputParser from langchain to enforce output format defined in Book Model.
Have a function get_recommended_books(category) that internally uses langchain and gemini to return a single book object.
The main function, exposed as the Cloud Function, should call get_recommended_books() multiple times (based on number_of_book) and return a JSON list of the generated book objects.
Handle the case where category or number_of_book are missing by returning an error JSON response with a 400 status code.
return a JSON string representing the recommended books. use os library to retrieve GOOGLE_CLOUD_PROJECT env var. Use ChatVertexAI from langchain for the LLM call

কোড অ্যাসিস্ট তারপরে একটি সম্ভাব্য সমাধান তৈরি করবে, সোর্স কোড এবং একটি requirements.txt নির্ভরতা ফাইল উভয়ই প্রদান করবে।

আমরা আপনাকে নিচে প্রদত্ত পরীক্ষিত, সঠিক সমাধানের সাথে কোড অ্যাসিস্টের তৈরি করা কোডের তুলনা করতে উৎসাহিত করি। এটি আপনাকে টুলটির কার্যকারিতা মূল্যায়ন করতে এবং সম্ভাব্য অসঙ্গতিগুলি সনাক্ত করতে দেয়৷ যদিও এলএলএমগুলিকে কখনই অন্ধভাবে বিশ্বাস করা উচিত নয়, কোড অ্যাসিস্ট দ্রুত প্রোটোটাইপিং এবং প্রাথমিক কোড স্ট্রাকচার তৈরি করার জন্য একটি দুর্দান্ত সরঞ্জাম হতে পারে এবং এটি একটি ভাল মাথা শুরু করার জন্য ব্যবহার করা উচিত।

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

👉ক্লাউড রান ফাংশনের সোর্স কোড এডিটরে ফিরে যান (অন্য ব্রাউজার ট্যাবে)। নিচে দেওয়া কোড দিয়ে main.py এর বিদ্যমান বিষয়বস্তু সাবধানে প্রতিস্থাপন করুন:

import functions_framework
import json
from flask import Flask, jsonify, request
from langchain_google_vertexai import ChatVertexAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
import os

class Book(BaseModel):
   
bookname: str = Field(description="Name of the book")
   
author: str = Field(description="Name of the author")
   
publisher: str = Field(description="Name of the publisher")
   
publishing_date: str = Field(description="Date of publishing")


project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")  

llm = ChatVertexAI(model_name="gemini-2.0-flash-lite-001")

def get_recommended_books(category):
    """
    A simple book recommendation function.

    Args:
        category (str): category

    Returns:
        str: A JSON string representing the recommended books.
    """
   
parser = JsonOutputParser(pydantic_object=Book)
   
question = f"Generate a random made up book on {category} with bookname, author and publisher and publishing_date"

   
prompt = PromptTemplate(
       
template="Answer the user query.\n{format_instructions}\n{query}\n",
       
input_variables=["query"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"query": question})

   
return  json.dumps(response)
   

@functions_framework.http
def recommended(request):
   
request_json = request.get_json(silent=True) # Get JSON data
   
if request_json and 'category' in request_json and 'number_of_book' in request_json:
       
category = request_json['category']
       
number_of_book = int(request_json['number_of_book'])
   
elif request.args and 'category' in request.args and 'number_of_book' in request.args:
       
category = request.args.get('category')
       
number_of_book = int(request.args.get('number_of_book'))

   
else:
       
return jsonify({'error': 'Missing category or number_of_book parameters'}), 400


   
recommendations_list = []
   
for i in range(number_of_book):
       
book_dict = json.loads(get_recommended_books(category))
       
print(f"book_dict=======>{book_dict}")
   
       
recommendations_list.append(book_dict)

   
   
return jsonify(recommendations_list)

👉 Requiments.txt-এর বিষয়বস্তুকে নিম্নলিখিত দিয়ে প্রতিস্থাপন করুন:

functions-framework==3.*
google-genai==1.0.0
flask==3.1.0
jsonify==0.5
langchain_google_vertexai==2.0.13
langchain_core==0.3.34
pydantic==2.10.5

👉আমরা ফাংশন এন্ট্রি পয়েন্ট সেট করব : recommended

03-02-function-create.png

👉Save and Deploy এ ক্লিক করুন। ফাংশন স্থাপন করতে। স্থাপন প্রক্রিয়া সম্পূর্ণ হওয়ার জন্য অপেক্ষা করুন। ক্লাউড কনসোল স্থিতি প্রদর্শন করবে। এতে কয়েক মিনিট সময় লাগতে পারে।

alt পাঠ্য 👉একবার স্থাপন করা হলে, টার্মিনাল রানে, ক্লাউড শেল সম্পাদকে ফিরে যান:

export PROJECT_ID=$(gcloud config get project)
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

curl -X POST -H "Content-Type: application/json" -d '{"category": "Science Fiction", "number_of_book": 2}' $BOOK_PROVIDER_URL

এটি JSON বিন্যাসে কিছু বই ডেটা প্রদর্শন করা উচিত।

[
 
{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},
 
{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}
]

অভিনন্দন! আপনি সফলভাবে একটি ক্লাউড রান ফাংশন স্থাপন করেছেন৷ এটি আমাদের Aidemy এজেন্ট তৈরি করার সময় আমরা যে পরিষেবাগুলিকে একীভূত করব তার মধ্যে একটি।

5. বিল্ডিং টুলস: আরামদায়ক পরিষেবা এবং ডেটাতে এজেন্টদের সংযোগ করা

আসুন এগিয়ে যান এবং বুটস্ট্র্যাপ কঙ্কাল প্রকল্প ডাউনলোড করুন, নিশ্চিত করুন যে আপনি ক্লাউড শেল এডিটরে আছেন। টার্মিনাল রানে,

git clone https://github.com/weimeilin79/aidemy-bootstrap.git

এই কমান্ডটি চালানোর পরে, আপনার ক্লাউড শেল পরিবেশে aidemy-bootstrap নামে একটি নতুন ফোল্ডার তৈরি হবে।

ক্লাউড শেল এডিটরের এক্সপ্লোরার প্যানে (সাধারণত বাম দিকে), আপনি এখন গিট রিপোজিটরি aidemy-bootstrap ক্লোন করার সময় তৈরি করা ফোল্ডারটি দেখতে পাবেন। এক্সপ্লোরারে আপনার প্রকল্পের রুট ফোল্ডারটি খুলুন। আপনি এটির মধ্যে একটি planner সাবফোল্ডার পাবেন, এটিও খুলুন। প্রকল্প এক্সপ্লোরার

আমাদের এজেন্টরা সত্যিকারের সহায়ক হতে ব্যবহার করবে এমন টুল তৈরি করা শুরু করি। আপনি জানেন যে, এলএলএমগুলি যুক্তি এবং পাঠ্য তৈরিতে দুর্দান্ত, তবে বাস্তব-বিশ্বের কাজগুলি সম্পাদন করতে এবং সঠিক, আপ-টু-ডেট তথ্য সরবরাহ করার জন্য তাদের বাহ্যিক সংস্থানগুলিতে অ্যাক্সেসের প্রয়োজন। এই সরঞ্জামগুলিকে এজেন্টের "সুইস আর্মি ছুরি" হিসাবে ভাবুন, এটি বিশ্বের সাথে যোগাযোগ করার ক্ষমতা দেয়৷

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

এই বিভাগে, আমরা পরিকল্পনাকারী এজেন্টের জন্য ভিত্তি তৈরি করব, যা শিক্ষকরা পাঠ পরিকল্পনা তৈরি করতে ব্যবহার করবেন। এজেন্ট একটি প্ল্যান তৈরি করা শুরু করার আগে, আমরা বিষয় এবং বিষয় সম্পর্কে আরও বিশদ প্রদান করে সীমানা নির্ধারণ করতে চাই। আমরা তিনটি টুল তৈরি করব:

  1. বিশ্রামপূর্ণ API কল: ডেটা পুনরুদ্ধার করতে একটি পূর্ব-বিদ্যমান API এর সাথে ইন্টারঅ্যাক্ট করা।
  2. ডেটাবেস ক্যোয়ারী: একটি ক্লাউড SQL ডাটাবেস থেকে কাঠামোগত ডেটা আনা।
  3. Google অনুসন্ধান: ওয়েব থেকে রিয়েল-টাইম তথ্য অ্যাক্সেস করা।

একটি API থেকে বই সুপারিশ আনা হচ্ছে

প্রথমে, আসুন একটি টুল তৈরি করি যেটি বই-প্রদানকারী API থেকে বইয়ের সুপারিশ পুনরুদ্ধার করে যা আমরা পূর্ববর্তী বিভাগে স্থাপন করেছি। এটি প্রদর্শন করে যে কীভাবে একজন এজেন্ট বিদ্যমান পরিষেবাগুলি লাভ করতে পারে।

বই সুপারিশ

ক্লাউড শেল এডিটরে, আপনি পূর্ববর্তী বিভাগে ক্লোন করা aidemy-bootstrap প্রকল্পটি খুলুন।

👉 planner ফোল্ডারে book.py সম্পাদনা করুন এবং ফাইলের শেষে নিম্নলিখিত কোডটি পেস্ট করুন:

def recommend_book(query: str):
    """
    Get a list of recommended book from an API endpoint
   
    Args:
        query: User's request string
    """

   
region = get_next_region();
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)

   
query = f"""The user is trying to plan a education course, you are the teaching assistant. Help define the category of what the user requested to teach, respond the categroy with no more than two word.

    user request:   {query}
    """
   
print(f"-------->{query}")
   
response = llm.invoke(query)
   
print(f"CATEGORY RESPONSE------------>: {response}")
   
   
# call this using python and parse the json back to dict
   
category = response.strip()
   
   
headers = {"Content-Type": "application/json"}
   
data = {"category": category, "number_of_book": 2}

   
books = requests.post(BOOK_PROVIDER_URL, headers=headers, json=data)
   
   
return books.text

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

ব্যাখ্যা:

  • recommend_book(query:str) : এই ফাংশনটি ব্যবহারকারীর প্রশ্নকে ইনপুট হিসেবে নেয়।
  • এলএলএম ইন্টারঅ্যাকশন : এটি কোয়েরি থেকে বিভাগটি বের করতে এলএলএম ব্যবহার করে। এটি প্রদর্শন করে কিভাবে আপনি টুলের জন্য প্যারামিটার তৈরি করতে LLM ব্যবহার করতে পারেন।
  • API কল : এটি বই-প্রদানকারী API-কে একটি POST অনুরোধ করে, ক্যাটাগরি এবং বইয়ের পছন্দসই সংখ্যা পাস করে।

👉এই নতুন ফাংশনটি পরীক্ষা করতে, পরিবেশ পরিবর্তনশীল সেট করুন, চালান:

cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

👉নির্ভরতাগুলি ইনস্টল করুন এবং এটি কাজ করে তা নিশ্চিত করতে কোডটি চালান, চালান:

cd ~/aidemy-bootstrap/planner/
python -m venv env
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
pip install -r requirements.txt
python book.py

গিট সতর্কতা পপ-আপ উইন্ডো উপেক্ষা করুন।

আপনি বই-প্রদানকারী API থেকে পুনরুদ্ধার করা বই সুপারিশ সমন্বিত একটি JSON স্ট্রিং দেখতে পাবেন। ফলাফল এলোমেলোভাবে উত্পন্ন হয়. আপনার বই একই নাও হতে পারে, তবে JSON ফর্ম্যাটে আপনার দুটি বইয়ের সুপারিশ পাওয়া উচিত।

[{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}]

আপনি যদি এটি দেখতে পান, প্রথম টুলটি সঠিকভাবে কাজ করছে!

নির্দিষ্ট পরামিতিগুলির সাথে স্পষ্টভাবে একটি RESTful API কল তৈরি করার পরিবর্তে, আমরা প্রাকৃতিক ভাষা ব্যবহার করছি ("আমি একটি কোর্স করছি...")। তারপর এজেন্ট বুদ্ধিমত্তার সাথে প্রয়োজনীয় প্যারামিটারগুলি (যেমন বিভাগ) বের করে এনএলপি ব্যবহার করে, হাইলাইট করে যে এজেন্ট কীভাবে এপিআই-এর সাথে ইন্টারঅ্যাক্ট করার জন্য প্রাকৃতিক ভাষা বোঝার সুবিধা নেয়।

কল তুলনা করুন

👉 book.py থেকে নিম্নলিখিত টেস্টিং কোডটি সরান

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

একটি ডাটাবেস থেকে পাঠ্যক্রমের তথ্য পাওয়া

এর পরে, আমরা একটি টুল তৈরি করব যা একটি ক্লাউড SQL PostgreSQL ডাটাবেস থেকে কাঠামোগত পাঠ্যক্রমের ডেটা নিয়ে আসে। এটি এজেন্টকে পাঠ পরিকল্পনার জন্য তথ্যের একটি নির্ভরযোগ্য উৎস অ্যাক্সেস করতে দেয়।

ডিবি তৈরি করুন

পূর্ববর্তী ধাপে আপনি যে সহায়ক ক্লাউড এসকিউএল ইন্সট্যান্স তৈরি করেছেন তা মনে আছে? এখানে যেখানে এটি ব্যবহার করা হবে.

👉 নতুন উদাহরণে aidemy-db নামে একটি ডাটাবেস তৈরি করুন।

gcloud sql databases create aidemy-db \
   
--instance=aidemy

আসুন Google ক্লাউড কনসোলে ক্লাউড এসকিউএল- এর উদাহরণটি যাচাই করি, আপনি তালিকাভুক্ত aidemy নামে একটি ক্লাউড SQL উদাহরণ দেখতে পাবেন। তার বিবরণ দেখতে উদাহরণ নামের উপর ক্লিক করুন. ক্লাউড এসকিউএল ইনস্ট্যান্স বিশদ বিবরণ পৃষ্ঠায়, বাম দিকের নেভিগেশন মেনুতে "SQL স্টুডিও" এ ক্লিক করুন। এটি একটি নতুন ট্যাব খুলবে।

ডাটাবেসের সাথে সংযোগ করতে ক্লিক করুন। SQL স্টুডিওতে সাইন ইন করুন

ডাটাবেস হিসাবে aidemy-db নির্বাচন করুন। ব্যবহারকারী হিসেবে postgres এবং পাসওয়ার্ড হিসেবে 1234qwer লিখুন। এসকিউএল স্টুডিও সাইন ইন করুন

👉SQL Studio ক্যোয়ারী এডিটরে, নিম্নলিখিত SQL কোড পেস্ট করুন:

CREATE TABLE curriculums (
   
id SERIAL PRIMARY KEY,
   
year INT,
   
subject VARCHAR(255),
   
description TEXT
);

-- Inserting detailed curriculum data for different school years and subjects
INSERT INTO curriculums (year, subject, description) VALUES
-- Year 5
(5, 'Mathematics', 'Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.'),
(5, 'English', 'Developing reading comprehension, creative writing, and basic grammar, with a focus on storytelling and poetry.'),
(5, 'Science', 'Exploring basic physics, chemistry, and biology concepts, including forces, materials, and ecosystems.'),
(5, 'Computer Science', 'Basic coding concepts using block-based programming and an introduction to digital literacy.'),

-- Year 6
(6, 'Mathematics', 'Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.'),
(6, 'English', 'Introduction to persuasive writing, character analysis, and deeper comprehension of literary texts.'),
(6, 'Science', 'Forces and motion, the human body, and introductory chemical reactions with hands-on experiments.'),
(6, 'Computer Science', 'Introduction to algorithms, logical reasoning, and basic text-based programming (Python, Scratch).'),

-- Year 7
(7, 'Mathematics', 'Algebraic expressions, geometry, and introduction to statistics and probability.'),
(7, 'English', 'Analytical reading of classic and modern literature, essay writing, and advanced grammar skills.'),
(7, 'Science', 'Introduction to cells and organisms, chemical reactions, and energy transfer in physics.'),
(7, 'Computer Science', 'Building on programming skills with Python, introduction to web development, and cyber safety.');

এই SQL কোড curriculums নামে একটি টেবিল তৈরি করে এবং কিছু নমুনা ডেটা সন্নিবেশ করে। এসকিউএল কোড চালানোর জন্য রান ক্লিক করুন। আপনি একটি নিশ্চিতকরণ বার্তা দেখতে পাবেন যা নির্দেশ করে যে কমান্ডগুলি সফলভাবে কার্যকর করা হয়েছে।

👉অন্বেষণকারীকে প্রসারিত করুন, নতুন তৈরি করা টেবিলটি খুঁজুন এবং ক্যোয়ারীতে ক্লিক করুন। এটি আপনার জন্য তৈরি করা SQL সহ একটি নতুন সম্পাদক ট্যাব খুলতে হবে,

এসকিউএল স্টুডিও সিলেক্ট টেবিল

SELECT * FROM
 
"public"."curriculums" LIMIT 1000;

👉 Run এ ক্লিক করুন।

ফলাফল টেবিলে আপনার পূর্ববর্তী ধাপে ঢোকানো ডেটার সারিগুলি প্রদর্শন করা উচিত, এটি নিশ্চিত করে যে টেবিল এবং ডেটা সঠিকভাবে তৈরি করা হয়েছে।

এখন আপনি সফলভাবে জনবহুল নমুনা পাঠ্যক্রমের ডেটা সহ একটি ডাটাবেস তৈরি করেছেন, আমরা এটি পুনরুদ্ধার করার জন্য একটি টুল তৈরি করব।

👉ক্লাউড কোড এডিটরে, aidemy-bootstrap ফোল্ডারে curriculums.py ফাইলটি সম্পাদনা করুন এবং ফাইলের শেষে নিম্নলিখিত কোডটি পেস্ট করুন:

def connect_with_connector() -> sqlalchemy.engine.base.Engine:

   
db_user = os.environ["DB_USER"]
   
db_pass = os.environ["DB_PASS"]
   
db_name = os.environ["DB_NAME"]

   
encoded_db_user = os.environ.get("DB_USER")
   
print(f"--------------------------->db_user: {db_user!r}")  
   
print(f"--------------------------->db_pass: {db_pass!r}")
   
print(f"--------------------------->db_name: {db_name!r}")

   
ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

   
connector = Connector()

   
def getconn() -> pg8000.dbapi.Connection:
       
conn: pg8000.dbapi.Connection = connector.connect(
           
instance_connection_name,
           
"pg8000",
           
user=db_user,
           
password=db_pass,
           
db=db_name,
           
ip_type=ip_type,
       
)
       
return conn

   
pool = sqlalchemy.create_engine(
       
"postgresql+pg8000://",
       
creator=getconn,
       
pool_size=2,
       
max_overflow=2,
       
pool_timeout=30,  # 30 seconds
       
pool_recycle=1800,  # 30 minutes
   
)
   
return pool



def init_connection_pool() -> sqlalchemy.engine.base.Engine:
   
   
return (
       
connect_with_connector()
   
)

   
raise ValueError(
       
"Missing database connection type. Please define one of INSTANCE_HOST, INSTANCE_UNIX_SOCKET, or INSTANCE_CONNECTION_NAME"
   
)

def get_curriculum(year: int, subject: str):
    """
    Get school curriculum
   
    Args:
        subject: User's request subject string
        year: User's request year int
    """
   
try:
       
stmt = sqlalchemy.text(
           
"SELECT description FROM curriculums WHERE year = :year AND subject = :subject"
       
)

       
with db.connect() as conn:
           
result = conn.execute(stmt, parameters={"year": year, "subject": subject})
           
row = result.fetchone()
       
if row:
           
return row[0]  
       
else:
           
return None  

   
except Exception as e:
       
print(e)
       
return None

db = init_connection_pool()

ব্যাখ্যা:

  • এনভায়রনমেন্ট ভেরিয়েবল : কোডটি এনভায়রনমেন্ট ভেরিয়েবল থেকে ডাটাবেস শংসাপত্র এবং সংযোগ তথ্য পুনরুদ্ধার করে (নিচে এই সম্পর্কে আরও)।
  • connect_with_connector() : এই ফাংশনটি ক্লাউড এসকিউএল কানেক্টর ব্যবহার করে ডাটাবেসের সাথে একটি নিরাপদ সংযোগ স্থাপন করে।
  • get_curriculum(year: int, subject: str) : এই ফাংশনটি বছর এবং বিষয়কে ইনপুট হিসাবে নেয়, পাঠ্যক্রমের টেবিলে প্রশ্ন করে এবং সংশ্লিষ্ট পাঠ্যক্রমের বিবরণ প্রদান করে।

👉আমরা কোড চালানোর আগে, আমাদের অবশ্যই কিছু এনভায়রনমেন্ট ভেরিয়েবল সেট করতে হবে, টার্মিনালে, রান করুন:

export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉পরীক্ষা করতে curriculums.py এর শেষে নিম্নলিখিত কোড যোগ করুন:

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉কোডটি চালান:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py

আপনি কনসোলে মুদ্রিত 6 ম-শ্রেণির গণিতের পাঠ্যক্রমের বিবরণ দেখতে পাবেন।

Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.

আপনি যদি পাঠ্যক্রমের বিবরণ দেখেন, ডাটাবেস টুল সঠিকভাবে কাজ করছে! এগিয়ে যান এবং Ctrl+C টিপে স্ক্রিপ্ট বন্ধ করুন।

👉 curriculums.py থেকে নিম্নলিখিত পরীক্ষার কোডটি সরান

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉ভার্চুয়াল পরিবেশ থেকে প্রস্থান করুন, টার্মিনাল রানে:

deactivate

6. বিল্ডিং টুলস: ওয়েব থেকে রিয়েল-টাইম তথ্য অ্যাক্সেস করুন

অবশেষে, আমরা একটি টুল তৈরি করব যা ওয়েব থেকে রিয়েল-টাইম তথ্য অ্যাক্সেস করতে Gemini 2 এবং Google অনুসন্ধান ইন্টিগ্রেশন ব্যবহার করে। এটি এজেন্টকে আপ-টু-ডেট থাকতে এবং প্রাসঙ্গিক ফলাফল প্রদান করতে সাহায্য করে।

Google Search API-এর সাথে Gemini 2-এর একীকরণ আরও নির্ভুল এবং প্রাসঙ্গিকভাবে প্রাসঙ্গিক অনুসন্ধান ফলাফল প্রদান করে এজেন্টের ক্ষমতা বাড়ায়। এটি এজেন্টদের আপ-টু-ডেট তথ্য অ্যাক্সেস করতে এবং তাদের প্রতিক্রিয়াগুলিকে বাস্তব-বিশ্বের ডেটাতে ভিত্তি করে, হ্যালুসিনেশন কমিয়ে দেয়। উন্নত এপিআই ইন্টিগ্রেশন আরও স্বাভাবিক ভাষার প্রশ্নগুলিকে সহজতর করে, এজেন্টদেরকে জটিল এবং সূক্ষ্ম অনুসন্ধানের অনুরোধগুলি তৈরি করতে সক্ষম করে৷

অনুসন্ধান করুন

এই ফাংশনটি ইনপুট হিসাবে একটি অনুসন্ধান ক্যোয়ারী, পাঠ্যক্রম, বিষয় এবং বছর নেয় এবং ইন্টারনেট থেকে প্রাসঙ্গিক তথ্য পুনরুদ্ধার করতে Gemini API এবং Google অনুসন্ধান টুল ব্যবহার করে। আপনি যদি ঘনিষ্ঠভাবে দেখেন, এটি অন্য কোনো ফ্রেমওয়ার্ক ব্যবহার না করেই ফাংশন কলিং করার জন্য Google Generative AI SDK ব্যবহার করছে।

aidemy-bootstrap ফোল্ডারে search.py ​​সম্পাদনা করুন এবং ফাইলের শেষে নিম্নলিখিত কোডটি পেস্ট করুন:

model_id = "gemini-2.0-flash-001"

google_search_tool = Tool(
   
google_search = GoogleSearch()
)

def search_latest_resource(search_text: str, curriculum: str, subject: str, year: int):
    """
    Get latest information from the internet
   
    Args:
        search_text: User's request category   string
        subject: "User's request subject" string
        year: "User's request year"  integer
    """
   
search_text = "%s in the context of year %d and subject %s with following curriculum detail %s " % (search_text, year, subject, curriculum)
   
region = get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"search_latest_resource text-----> {search_text}")
   
response = client.models.generate_content(
       
model=model_id,
       
contents=search_text,
       
config=GenerateContentConfig(
           
tools=[google_search_tool],
           
response_modalities=["TEXT"],
       
)
   
)
   
print(f"search_latest_resource response-----> {response}")
   
return response

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

ব্যাখ্যা:

  • ডিফাইনিং টুল - google_search_tool : একটি টুলের মধ্যে GoogleSearch অবজেক্টকে মোড়ানো
  • search_latest_resource(search_text: str, subject: str, year: int) : এই ফাংশনটি একটি সার্চ কোয়েরি, বিষয় এবং বছর ইনপুট হিসেবে নেয় এবং Google সার্চ করার জন্য Gemini API ব্যবহার করে। মিথুন মডেল
  • GenerateContentConfig : সংজ্ঞায়িত করুন যে এটির GoogleSearch টুলে অ্যাক্সেস আছে

মিথুন মডেলটি অভ্যন্তরীণভাবে অনুসন্ধান_পাঠ্য বিশ্লেষণ করে এবং এটি সরাসরি প্রশ্নের উত্তর দিতে পারে কিনা বা Google সার্চ টুল ব্যবহার করতে হবে কিনা তা নির্ধারণ করে। এটি একটি গুরুত্বপূর্ণ পদক্ষেপ যা এলএলএম এর যুক্তি প্রক্রিয়ার মধ্যে ঘটে। মডেলটিকে এমন পরিস্থিতিতে চিনতে প্রশিক্ষণ দেওয়া হয়েছে যেখানে বাহ্যিক সরঞ্জামগুলির প্রয়োজন। যদি মডেলটি GoogleSearch টুল ব্যবহার করার সিদ্ধান্ত নেয়, তাহলে Google Generative AI SDK প্রকৃত আহ্বান পরিচালনা করে। SDK মডেলের সিদ্ধান্ত নেয় এবং এটি যে প্যারামিটার তৈরি করে সেগুলিকে Google অনুসন্ধান API-এ পাঠায়। এই অংশটি কোডে ব্যবহারকারীর কাছ থেকে লুকানো আছে।

মিথুন মডেল তারপরে অনুসন্ধান ফলাফলগুলিকে তার প্রতিক্রিয়ার সাথে একত্রিত করে৷ এটি ব্যবহারকারীর প্রশ্নের উত্তর দিতে, একটি সারাংশ তৈরি করতে বা অন্য কিছু কাজ করতে তথ্য ব্যবহার করতে পারে।

👉পরীক্ষা করতে, কোডটি চালান:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py

আপনি "বছর 5 গণিতের সিলেবাস" এর সাথে সম্পর্কিত অনুসন্ধান ফলাফল সম্বলিত জেমিনি অনুসন্ধান API প্রতিক্রিয়া দেখতে পাবেন। সঠিক আউটপুট অনুসন্ধান ফলাফলের উপর নির্ভর করবে, কিন্তু এটি অনুসন্ধান সম্পর্কে তথ্য সহ একটি JSON অবজেক্ট হবে।

আপনি যদি অনুসন্ধান ফলাফল দেখতে পান, গুগল অনুসন্ধান টুল সঠিকভাবে কাজ করছে! এগিয়ে যান এবং Ctrl+C টিপে স্ক্রিপ্ট বন্ধ করুন।

👉এবং কোডের শেষ অংশটি সরিয়ে ফেলুন

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

👉ভার্চুয়াল পরিবেশ থেকে প্রস্থান করুন, টার্মিনাল রানে:

deactivate

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

7. ল্যাংগ্রাফের সাথে অর্কেস্ট্রেটিং

এখন যেহেতু আমরা আমাদের স্বতন্ত্র সরঞ্জামগুলি তৈরি করেছি, ল্যাংগ্রাফ ব্যবহার করে সেগুলিকে অর্কেস্ট্রেট করার সময় এসেছে৷ এটি আমাদের একটি আরও পরিশীলিত "পরিকল্পক" এজেন্ট তৈরি করার অনুমতি দেবে যা ব্যবহারকারীর অনুরোধের উপর ভিত্তি করে কোন সরঞ্জামগুলি এবং কখন ব্যবহার করতে হবে তা বুদ্ধিমানের সাথে সিদ্ধান্ত নিতে পারে।

LangGraph হল একটি পাইথন লাইব্রেরি যা লার্জ ল্যাঙ্গুয়েজ মডেল (LLMs) ব্যবহার করে স্টেটফুল, মাল্টি-অ্যাক্টর অ্যাপ্লিকেশন তৈরি করা সহজ করার জন্য ডিজাইন করা হয়েছে। LLM, টুলস এবং অন্যান্য এজেন্ট জড়িত জটিল কথোপকথন এবং ওয়ার্কফ্লো অর্কেস্ট্রেট করার জন্য এটিকে একটি কাঠামো হিসাবে মনে করুন।

মূল ধারণা:

  • গ্রাফ স্ট্রাকচার: ল্যাংগ্রাফ একটি নির্দেশিত গ্রাফ হিসাবে আপনার অ্যাপ্লিকেশনের যুক্তি উপস্থাপন করে। গ্রাফের প্রতিটি নোড প্রক্রিয়ার একটি ধাপকে প্রতিনিধিত্ব করে (যেমন, একটি এলএলএম-এ একটি কল, একটি টুল আহ্বান, একটি শর্তসাপেক্ষ চেক)। প্রান্তগুলি নোডগুলির মধ্যে নির্বাহের প্রবাহকে সংজ্ঞায়িত করে।
  • অবস্থা: ল্যাংগ্রাফ গ্রাফের মধ্য দিয়ে যাওয়ার সাথে সাথে আপনার অ্যাপ্লিকেশনের অবস্থা পরিচালনা করে। এই অবস্থায় ভেরিয়েবল অন্তর্ভুক্ত করতে পারে যেমন ব্যবহারকারীর ইনপুট, টুল কলের ফলাফল, LLM থেকে মধ্যবর্তী আউটপুট এবং অন্য যেকোন তথ্য যা ধাপের মধ্যে সংরক্ষণ করা প্রয়োজন।
  • নোড: প্রতিটি নোড একটি গণনা বা মিথস্ক্রিয়া প্রতিনিধিত্ব করে। তারা হতে পারে:
    • টুল নোড: একটি টুল ব্যবহার করুন (যেমন, একটি ওয়েব অনুসন্ধান করুন, একটি ডাটাবেস অনুসন্ধান করুন)
    • ফাংশন নোড: একটি পাইথন ফাংশন চালান।
  • প্রান্ত: নির্বাহের প্রবাহ সংজ্ঞায়িত করে নোডগুলিকে সংযুক্ত করুন। তারা হতে পারে:
    • ডাইরেক্ট এজ: এক নোড থেকে অন্য নোড পর্যন্ত একটি সহজ, শর্তহীন প্রবাহ।
    • শর্তাধীন প্রান্ত: প্রবাহ শর্তাধীন নোডের ফলাফলের উপর নির্ভর করে।

ল্যাংগ্রাফ

আমরা অর্কেস্ট্রেশন বাস্তবায়ন করতে ল্যাংগ্রাফ ব্যবহার করব। আসুন আমাদের ল্যাংগ্রাফ লজিক সংজ্ঞায়িত করতে aidemy-bootstrap ফোল্ডারের অধীনে aidemy.py ফাইলটি সম্পাদনা করি।

👉 aidemy.py এর শেষে ফলো কোড যোগ করুন:

tools = [get_curriculum, search_latest_resource, recommend_book]

def determine_tool(state: MessagesState):
   
llm = ChatVertexAI(model_name="gemini-2.0-flash-001", location=get_next_region())
   
sys_msg = SystemMessage(
                   
content=(
                       
f"""You are a helpful teaching assistant that helps gather all needed information.
                            Your ultimate goal is to create a detailed 3-week teaching plan.
                            You have access to tools that help you gather information.  
                            Based on the user request, decide which tool(s) are needed.

                        """
                   
)
               
)

   
llm_with_tools = llm.bind_tools(tools)
   
return {"messages": llm_with_tools.invoke([sys_msg] + state["messages"])}

এই ফাংশনটি কথোপকথনের বর্তমান অবস্থা নেওয়ার জন্য দায়ী, একটি সিস্টেম বার্তা সহ LLM প্রদান করে এবং তারপর একটি প্রতিক্রিয়া তৈরি করতে LLM কে জিজ্ঞাসা করে৷ এলএলএম হয় সরাসরি ব্যবহারকারীকে প্রতিক্রিয়া জানাতে পারে বা উপলব্ধ সরঞ্জামগুলির মধ্যে একটি ব্যবহার করতে বেছে নিতে পারে।

টুলস : এই তালিকাটি এজেন্টের কাছে উপলব্ধ সরঞ্জামগুলির সেট উপস্থাপন করে। এটিতে তিনটি টুল ফাংশন রয়েছে যা আমরা পূর্ববর্তী ধাপে সংজ্ঞায়িত করেছি: get_curriculum , search_latest_resource , এবং recommend_bookllm.bind_tools(tools) : এটি llm অবজেক্টের সাথে টুল তালিকাকে "আবদ্ধ" করে। টুলগুলি বাঁধাই এলএলএমকে বলে যে এই সরঞ্জামগুলি উপলব্ধ এবং কীভাবে সেগুলি ব্যবহার করতে হবে সে সম্পর্কে তথ্য এলএলএমকে প্রদান করে (যেমন, সরঞ্জামগুলির নাম, তারা যে প্যারামিটারগুলি গ্রহণ করে এবং তারা কী করে)।

আমরা অর্কেস্ট্রেশন বাস্তবায়ন করতে ল্যাংগ্রাফ ব্যবহার করব।

👉 নিম্নলিখিত কোডটি aidemy.py এর শেষে যোগ করুন:

def prep_class(prep_needs):
   
   
builder = StateGraph(MessagesState)
   
builder.add_node("determine_tool", determine_tool)
   
builder.add_node("tools", ToolNode(tools))
   
   
builder.add_edge(START, "determine_tool")
   
builder.add_conditional_edges("determine_tool",tools_condition)
   
builder.add_edge("tools", "determine_tool")

   
   
memory = MemorySaver()
   
graph = builder.compile(checkpointer=memory)

   
config = {"configurable": {"thread_id": "1"}}
   
messages = graph.invoke({"messages": prep_needs},config)
   
print(messages)
   
for m in messages['messages']:
       
m.pretty_print()
   
teaching_plan_result = messages["messages"][-1].content  


   
return teaching_plan_result

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan")

ব্যাখ্যা:

  • StateGraph(MessagesState) : একটি StateGraph অবজেক্ট তৈরি করে। একটি StateGraph ল্যাংগ্রাফের একটি মূল ধারণা। এটি একটি গ্রাফ হিসাবে আপনার এজেন্টের কর্মপ্রবাহকে উপস্থাপন করে, যেখানে গ্রাফের প্রতিটি নোড প্রক্রিয়াটির একটি ধাপ উপস্থাপন করে। এজেন্ট কীভাবে যুক্তি এবং কাজ করবে তার নীলনকশা সংজ্ঞায়িত হিসাবে এটিকে ভাবুন।
  • কন্ডিশনাল এজ: "determine_tool" নোড থেকে উদ্ভূত, tools_condition আর্গুমেন্ট সম্ভবত একটি ফাংশন যা নির্ধারণ করে যে determine_tool ফাংশনের আউটপুটের উপর ভিত্তি করে কোন প্রান্তটি অনুসরণ করতে হবে। শর্তসাপেক্ষ প্রান্তগুলি কোন টুল ব্যবহার করবে (বা ব্যবহারকারীকে সরাসরি প্রতিক্রিয়া জানাবে কিনা) সম্পর্কে এলএলএম-এর সিদ্ধান্তের উপর ভিত্তি করে গ্রাফটিকে শাখায় যেতে দেয়। এখানেই এজেন্টের "বুদ্ধিমত্তা" কার্যকর হয় - এটি পরিস্থিতির উপর ভিত্তি করে গতিশীলভাবে তার আচরণকে মানিয়ে নিতে পারে।
  • লুপ: গ্রাফে একটি প্রান্ত যোগ করে যা "tools" নোডকে "determine_tool" নোডের সাথে সংযুক্ত করে। এটি গ্রাফে একটি লুপ তৈরি করে, এজেন্টকে বারবার টুল ব্যবহার করার অনুমতি দেয় যতক্ষণ না এটি কাজটি সম্পূর্ণ করার জন্য যথেষ্ট তথ্য সংগ্রহ করে এবং একটি সন্তোষজনক উত্তর প্রদান করে। এই লুপটি জটিল কাজের জন্য অত্যন্ত গুরুত্বপূর্ণ যেগুলির জন্য যুক্তি এবং তথ্য সংগ্রহের একাধিক ধাপ প্রয়োজন।

এখন, আসুন আমাদের পরিকল্পনাকারী এজেন্টকে পরীক্ষা করে দেখি যে এটি কীভাবে বিভিন্ন সরঞ্জামকে অর্কেস্ট্রেট করে।

এই কোডটি একটি নির্দিষ্ট ব্যবহারকারীর ইনপুট সহ prep_class ফাংশন চালাবে, পাঠ্যক্রম, বইয়ের সুপারিশ এবং সর্বশেষ ইন্টারনেট সংস্থানগুলি ব্যবহার করে জ্যামিতিতে 5ম-শ্রেণির গণিতের জন্য একটি শিক্ষণ পরিকল্পনা তৈরি করার অনুরোধ অনুকরণ করে।

আপনি যদি আপনার টার্মিনাল বন্ধ করে থাকেন বা এনভায়রনমেন্ট ভেরিয়েবল আর সেট না থাকে, তাহলে নিম্নলিখিত কমান্ডগুলি পুনরায় চালান

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉কোডটি চালান:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py

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

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
search_text: Geometry
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
subject: Mathematics
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.....) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================
Tool Calls:
 
recommend_book (93b48189-4d69-4c09-a3bd-4e60cdc5f1c6)
 
Call ID: 93b48189-4d69-4c09-a3bd-4e60cdc5f1c6
 
Args:
   
query: Mathematics Geometry Year 5
================================= Tool Message =================================
Name: recommend_book

[{.....}]

================================== Ai Message ==================================

Based on the curriculum outcome, here is a 3-week teaching plan for year 5 Mathematics Geometry:

**Week 1: Introduction to Shapes and Properties**
.........

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉(এই ধাপটি ঐচ্ছিক) একটি ভিন্ন প্রম্পট দিয়ে টেস্টিং কোড প্রতিস্থাপন করুন, যার জন্য বিভিন্ন টুল কল করা প্রয়োজন।

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

আপনি যদি আপনার টার্মিনাল বন্ধ করে থাকেন বা এনভায়রনমেন্ট ভেরিয়েবল আর সেট না থাকে, তাহলে নিম্নলিখিত কমান্ডগুলি পুনরায় চালান

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉(এই পদক্ষেপটি ঐচ্ছিক, আপনি যদি পূর্ববর্তী ধাপটি চালিয়ে থাকেন তবেই এটি করুন) কোডটি আবার চালান:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py

এই সময় আপনি কি লক্ষ্য করেছেন? কোন সরঞ্জাম এজেন্ট কল? আপনার দেখা উচিত যে এজেন্ট এই সময় শুধুমাত্র search_latest_resource টুলকে কল করে। এর কারণ হল প্রম্পটটি নির্দিষ্ট করে না যে এটির অন্য দুটি সরঞ্জামের প্রয়োজন, এবং আমাদের LLM অন্য সরঞ্জামগুলিকে কল না করার জন্য যথেষ্ট স্মার্ট।

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
subject: Mathematics
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
search_text: Geometry
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.......token_count=40, total_token_count=772) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================

Based on the information provided, a 3-week teaching plan for Year 5 Mathematics focusing on Geometry could look like this:

**Week 1:  Introducing 2D Shapes**
........
* Use visuals, manipulatives, and real-world examples to make the learning experience engaging and relevant.

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉 আপনার aidemy.py ফাইলটি পরিষ্কার রাখতে টেস্টিং কোডটি সরান (এই ধাপটি এড়িয়ে যাবেন না!):

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

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

আপনি যদি আপনার টার্মিনাল বন্ধ করে থাকেন বা এনভায়রনমেন্ট ভেরিয়েবল আর সেট না থাকে, তাহলে নিম্নলিখিত কমান্ডগুলি পুনরায় চালান

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉এখন, ওয়েব UI শুরু করুন।

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py

ক্লাউড শেল টার্মিনাল আউটপুটে স্টার্টআপ বার্তাগুলি সন্ধান করুন। ফ্লাস্ক সাধারণত বার্তা প্রিন্ট করে যে এটি চলছে এবং কোন পোর্টে চলছে।

Running on http://127.0.0.1:8080
Running on http://127.0.0.1:8080
The application needs to keep running to serve requests.

👉"ওয়েব প্রিভিউ" মেনু থেকে, পোর্ট 8080-এ প্রিভিউ বেছে নিন। ক্লাউড শেল আপনার অ্যাপ্লিকেশনের ওয়েব প্রিভিউ সহ একটি নতুন ব্রাউজার ট্যাব বা উইন্ডো খুলবে।

ওয়েব পেজ

অ্যাপ্লিকেশন ইন্টারফেসে, বছরের জন্য 5 নির্বাচন করুন, Mathematics বিষয় নির্বাচন করুন এবং অ্যাড-অন অনুরোধে Geometry টাইপ করুন

প্রতিক্রিয়ার জন্য অপেক্ষা করার সময় শূন্য দৃষ্টিতে তাকিয়ে থাকার পরিবর্তে, ক্লাউড এডিটরের টার্মিনালে চলে যান। আপনি এমুলেটরের টার্মিনালে আপনার ফাংশন দ্বারা উত্পন্ন অগ্রগতি এবং কোনো আউটপুট বা ত্রুটি বার্তা পর্যবেক্ষণ করতে পারেন। 😁

👉 টার্মিনালে Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉ভার্চুয়াল পরিবেশ থেকে প্রস্থান করুন:

deactivate

8. ক্লাউডে পরিকল্পনাকারী এজেন্ট মোতায়েন করা হচ্ছে

রেজিস্ট্রিতে ছবি তৈরি করুন এবং পুশ করুন

ওভারভিউ

👉 ক্লাউডে এটি স্থাপন করার সময়। টার্মিনালে, আমরা যে ডকার ইমেজটি তৈরি করতে যাচ্ছি তা সঞ্চয় করার জন্য একটি আর্টিফ্যাক্ট রিপোজিটরি তৈরি করুন।

gcloud artifacts repositories create agent-repository \
   
--repository-format=docker \
   
--location=us-central1 \
   
--description="My agent repository"

আপনার তৈরি করা সংগ্রহস্থল [এজেন্ট-রিপোজিটরি] দেখতে হবে।

👉 ডকার ইমেজ তৈরি করতে নিম্নলিখিত কমান্ডটি চালান।

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .

👉আমাদের ছবিটিকে পুনরায় ট্যাগ করতে হবে যাতে এটি GCR-এর পরিবর্তে আর্টিফ্যাক্ট রেজিস্ট্রিতে হোস্ট করা হয় এবং ট্যাগ করা ছবিটিকে আর্টিফ্যাক্ট রেজিস্ট্রিতে পুশ করা যায়:

export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

একবার পুশ সম্পূর্ণ হলে, আপনি যাচাই করতে পারেন যে ছবিটি সফলভাবে আর্টিফ্যাক্ট রেজিস্ট্রিতে সংরক্ষণ করা হয়েছে। Google ক্লাউড কনসোলে আর্টিফ্যাক্ট রেজিস্ট্রিতে নেভিগেট করুন। আপনার agent-repository রিপোজিটরির মধ্যে aidemy-planner ইমেজ খুঁজে পাওয়া উচিত। Aidemy পরিকল্পনাকারী ইমেজ

সিক্রেট ম্যানেজারের সাথে ডেটাবেস শংসাপত্রগুলি সুরক্ষিত করা

নিরাপদে ডাটাবেস শংসাপত্রগুলি পরিচালনা এবং অ্যাক্সেস করতে, আমরা Google ক্লাউড সিক্রেট ম্যানেজার ব্যবহার করব৷ এটি আমাদের অ্যাপ্লিকেশন কোডে সংবেদনশীল তথ্য হার্ডকোডিং প্রতিরোধ করে এবং নিরাপত্তা বাড়ায়।

👉আমরা ডাটাবেসের ব্যবহারকারীর নাম, পাসওয়ার্ড এবং ডাটাবেসের নামের জন্য পৃথক গোপনীয়তা তৈরি করব। এই পদ্ধতিটি আমাদের প্রতিটি শংসাপত্র স্বাধীনভাবে পরিচালনা করতে দেয়। টার্মিনাল রানে:

gcloud secrets create db-user
printf "postgres" | gcloud secrets versions add db-user --data-file=-

gcloud secrets create db-pass
printf "1234qwer" | gcloud secrets versions add db-pass --data-file=-

gcloud secrets create db-name
printf "aidemy-db" | gcloud secrets versions add db-name --data-file=-

সিক্রেট ম্যানেজার ব্যবহার করা আপনার আবেদন সুরক্ষিত করার জন্য এবং সংবেদনশীল শংসাপত্রের দুর্ঘটনাজনিত এক্সপোজার প্রতিরোধ করার জন্য একটি গুরুত্বপূর্ণ পদক্ষেপ। এটি ক্লাউড স্থাপনার জন্য নিরাপত্তার সর্বোত্তম অনুশীলন অনুসরণ করে।

ক্লাউড রানে স্থাপন করুন

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

👉Google ক্লাউড কনসোলে, " ক্লাউড রান "-এ নেভিগেট করুন। DEPLOY CONTAINER এ ক্লিক করুন এবং SERVICE নির্বাচন করুন। আপনার ক্লাউড রান পরিষেবা কনফিগার করুন:

মেঘের দৌড়

  1. ধারক চিত্র : URL ক্ষেত্রে "নির্বাচন করুন" এ ক্লিক করুন। আপনি আর্টিফ্যাক্ট রেজিস্ট্রিতে যে ছবিটি পুশ করেছেন সেটি খুঁজুন (যেমন, us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG)।
  2. পরিষেবার নাম : aidemy-planner
  3. অঞ্চল : us-central1 অঞ্চল নির্বাচন করুন।
  4. প্রমাণীকরণ : এই কর্মশালার উদ্দেশ্যে, আপনি "অপ্রমাণিত আহ্বানের অনুমতি দিন" অনুমতি দিতে পারেন। উৎপাদনের জন্য, আপনি সম্ভবত অ্যাক্সেস সীমাবদ্ধ করতে চাইবেন।
  5. ধারক(গুলি) ট্যাব (পাত্র, নেটওয়ার্ক প্রসারিত করুন):
    • সেটিং ট্যাব:
      • সম্পদ
        • মেমরি: 2GiB
    • ভেরিয়েবল এবং সিক্রেট ট্যাব:
      • পরিবেশ পরিবর্তনশীল:
        • নাম যোগ করুন: GOOGLE_CLOUD_PROJECT এবং মান: <YOUR_PROJECT_ID>
        • নাম যোগ করুন: BOOK_PROVIDER_URL , এবং আপনার বই-প্রদানকারী ফাংশন URL-এ মান সেট করুন, যা আপনি টার্মিনালে নিম্নলিখিত কমান্ড ব্যবহার করে নির্ধারণ করতে পারেন:
          gcloud run services describe book-provider \
             
          --region=us-central1 \
             
          --project=$PROJECT_ID \
             
          --format="value(status.url)"
      • পরিবেশের পরিবর্তনশীল হিসাবে প্রকাশিত গোপনীয়তা:
        • নাম যোগ করুন: DB_USER , গোপন: db-user এবং সংস্করণ নির্বাচন করুন: latest
        • নাম যোগ করুন: DB_PASS , গোপন: db-pass এবং সংস্করণ নির্বাচন করুন: latest
        • নাম যোগ করুন: DB_NAME , গোপন: db-name এবং সংস্করণ নির্বাচন করুন: latest

গোপন সেট করুন

ডিফল্ট হিসাবে অন্য ছেড়ে.

👉 CREATE এ ক্লিক করুন।

ক্লাউড রান আপনার পরিষেবা স্থাপন করবে।

একবার স্থাপন করা হলে, পরিষেবাটির বিশদ পৃষ্ঠায় ক্লিক করুন, আপনি উপরে উপলভ্য স্থাপন করা URL খুঁজে পেতে পারেন।

URL

অ্যাপ্লিকেশন ইন্টারফেসে, বছরের জন্য 7 নির্বাচন করুন, বিষয় হিসাবে Mathematics নির্বাচন করুন এবং অ্যাড-অন অনুরোধ ক্ষেত্রে Algebra লিখুন। এটি এজেন্টকে একটি উপযোগী পাঠ পরিকল্পনা তৈরি করার জন্য প্রয়োজনীয় প্রসঙ্গ সরবরাহ করবে।

অভিনন্দন! আপনি সফলভাবে আমাদের শক্তিশালী AI এজেন্ট ব্যবহার করে একটি শিক্ষণ পরিকল্পনা তৈরি করেছেন। এটি এজেন্টদের উল্লেখযোগ্যভাবে কাজের চাপ কমাতে এবং কাজগুলিকে স্ট্রিমলাইন করার সম্ভাব্যতা প্রদর্শন করে, শেষ পর্যন্ত দক্ষতার উন্নতি করে এবং শিক্ষাবিদদের জীবনকে সহজ করে তোলে।

9. মাল্টি-এজেন্ট সিস্টেম

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

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

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

স্কেলাবিলিটি : একক, জটিল এজেন্ট স্কেলিং একটি বাধা হতে পারে। একটি মাল্টি-এজেন্ট সিস্টেমের সাহায্যে আপনি পৃথক এজেন্টদের তাদের নির্দিষ্ট প্রয়োজনের ভিত্তিতে স্কেল করতে পারেন। উদাহরণস্বরূপ, যদি কোনও এজেন্ট উচ্চ পরিমাণের অনুরোধগুলি পরিচালনা করে থাকে তবে আপনি সহজেই সেই এজেন্টের আরও দৃষ্টান্তগুলি স্পিন করতে পারেন বাকি সিস্টেমকে প্রভাবিত না করে।

টিম স্পেশালাইজেশন : এটিকে এ জাতীয় ভাবুন: আপনি একজন ইঞ্জিনিয়ারকে স্ক্র্যাচ থেকে সম্পূর্ণ অ্যাপ্লিকেশন তৈরি করতে বলবেন না। পরিবর্তে, আপনি বিশেষজ্ঞের একটি দলকে একত্রিত করেন, প্রতিটি নির্দিষ্ট ক্ষেত্রে দক্ষতার সাথে। একইভাবে, একটি মাল্টি-এজেন্ট সিস্টেম আপনাকে বিভিন্ন এলএলএম এবং সরঞ্জামগুলির শক্তিগুলি উপার্জন করতে দেয়, তাদের নির্দিষ্ট কাজের জন্য সবচেয়ে উপযুক্ত উপযুক্ত এজেন্টগুলিকে নিয়োগ করে।

সমান্তরাল বিকাশ : বিভিন্ন দলগুলি বিকাশের প্রক্রিয়াটিকে দ্রুততর করে একই সাথে বিভিন্ন এজেন্টদের উপর কাজ করতে পারে। যেহেতু এজেন্টরা স্বাধীন, তাই একজন এজেন্টের পরিবর্তনগুলি অন্যান্য এজেন্টদের প্রভাবিত করার সম্ভাবনা কম।

ইভেন্ট চালিত আর্কিটেকচার

এই এজেন্টদের মধ্যে কার্যকর যোগাযোগ এবং সমন্বয় সক্ষম করতে, আমরা একটি ইভেন্ট-চালিত আর্কিটেকচার নিয়োগ করব। এর অর্থ হ'ল এজেন্টরা সিস্টেমের মধ্যে ঘটে যাওয়া "ইভেন্টগুলি" প্রতিক্রিয়া জানাবে।

এজেন্টরা নির্দিষ্ট ইভেন্টের ধরণের সাবস্ক্রাইব করে (যেমন, "শিক্ষণ পরিকল্পনা উত্পন্ন," "অ্যাসাইনমেন্ট তৈরি")। যখন কোনও ইভেন্ট ঘটে তখন প্রাসঙ্গিক এজেন্টদের অবহিত করা হয় এবং সে অনুযায়ী প্রতিক্রিয়া জানাতে পারে। এই ডিকোপলিং নমনীয়তা, স্কেলাবিলিটি এবং রিয়েল-টাইম প্রতিক্রিয়াশীলতার প্রচার করে।

ওভারভিউ

এখন, জিনিসগুলি বন্ধ করার জন্য, আমাদের এই ইভেন্টগুলি সম্প্রচারের একটি উপায় প্রয়োজন। এটি করার জন্য, আমরা একটি পাব/সাব বিষয় সেট আপ করব। আসুন প্ল্যান নামে একটি বিষয় তৈরি করে শুরু করা যাক।

Google গুগল ক্লাউড কনসোল পাব/সাব যান এবং "বিষয় তৈরি করুন" বোতামে ক্লিক করুন।

- আইডি/নাম plan সাথে বিষয়টিকে কনফিগার করুন এবং আনচেক Add a default subscription , ডিফল্ট হিসাবে বিশ্রাম ছেড়ে দিন এবং তৈরি ক্লিক করুন।

পাব/উপ পৃষ্ঠাটি রিফ্রেশ করবে এবং আপনার এখন আপনার নতুন তৈরি করা বিষয়টিকে টেবিলে তালিকাভুক্ত করা উচিত। টপিক তৈরি করুন

এখন, আসুন আমরা আমাদের পরিকল্পনাকারী এজেন্টের সাথে পাব/সাব ইভেন্ট প্রকাশের কার্যকারিতা সংহত করি। আমরা একটি নতুন সরঞ্জাম যুক্ত করব যা আমরা সবেমাত্র তৈরি করা পাব/সাব বিষয়টিতে একটি "পরিকল্পনা" ইভেন্ট প্রেরণ করে। এই ইভেন্টটি সিস্টেমের অন্যান্য এজেন্টদের (যেমন শিক্ষার্থীদের পোর্টালের মতো) সংকেত দেবে যে একটি নতুন শিক্ষণ পরিকল্পনা উপলব্ধ।

Well ক্লাউড কোড সম্পাদকটিতে ফিরে যান এবং planner ফোল্ডারে অবস্থিত app.py ফাইলটি খুলুন। আমরা ইভেন্টটি প্রকাশ করে এমন একটি ফাংশন যুক্ত করব। প্রতিস্থাপন:

##ADD SEND PLAN EVENT FUNCTION HERE

সঙ্গে

def send_plan_event(teaching_plan:str):
    """
    Send the teaching event to the topic called plan
   
    Args:
        teaching_plan: teaching plan
    """
   
publisher = pubsub_v1.PublisherClient()
   
print(f"-------------> Sending event to topic plan: {teaching_plan}")
   
topic_path = publisher.topic_path(PROJECT_ID, "plan")

   
message_data = {"teaching_plan": teaching_plan}
   
data = json.dumps(message_data).encode("utf-8")

   
future = publisher.publish(topic_path, data)

   
return f"Published message ID: {future.result()}"

  • SEND_PLAN_EVENT : এই ফাংশনটি উত্পন্ন শিক্ষণ পরিকল্পনাটিকে ইনপুট হিসাবে গ্রহণ করে, একটি পাব/সাব প্রকাশক ক্লায়েন্ট তৈরি করে, বিষয় পথটি তৈরি করে, শিক্ষার পরিকল্পনাটিকে একটি জেএসএন স্ট্রিংয়ে রূপান্তর করে, বার্তাটি বিষয়টিতে প্রকাশ করে।

একই app.py ফাইলটিতে

Teaping শিক্ষণ পরিকল্পনা তৈরি করার পরে এজেন্টকে টিচিং প্ল্যান ইভেন্টটি পাব/সাব টপকে প্রেরণের নির্দেশ দেওয়ার প্রম্পটটি। প্রতিস্থাপন করুন

### ADD send_plan_event CALL

নিম্নলিখিত সঙ্গে:

send_plan_event(teaching_plan)

SEND_PLAN_EVENT সরঞ্জাম যুক্ত করে এবং প্রম্পটটি সংশোধন করে আমরা আমাদের পরিকল্পনাকারী এজেন্টকে পাব/সাব এ ইভেন্টগুলি প্রকাশ করতে সক্ষম করেছি, আমাদের সিস্টেমের অন্যান্য উপাদানগুলিকে নতুন শিক্ষণ পরিকল্পনা তৈরির প্রতিক্রিয়া জানাতে সক্ষম করে। আমাদের এখন নিম্নলিখিত বিভাগগুলিতে একটি কার্যকরী বহু-এজেন্ট সিস্টেম থাকবে।

10. অন-ডিমান্ড কুইজ সহ শিক্ষার্থীদের ক্ষমতায়িত করা

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

এই দৃষ্টিভঙ্গিকে প্রাণবন্ত করার জন্য, আমরা একটি কুইজ প্রজন্মের উপাদান তৈরি করব যা শিক্ষণ পরিকল্পনার সামগ্রীর ভিত্তিতে একাধিক-পছন্দ প্রশ্ন তৈরি করতে পারে।

ওভারভিউ

Clad ক্লাউড কোড সম্পাদকের এক্সপ্লোরার ফলকে portal ফোল্ডারে নেভিগেট করুন। quiz.py ফাইলটি খুলুন এবং ফাইলের শেষে নিম্নলিখিত কোডটি পেস্ট করুন।

def generate_quiz_question(file_name: str, difficulty: str, region:str ):
    """Generates a single multiple-choice quiz question using the LLM.
   
    ```json
    {
      "question": "The question itself",
      "options": ["Option A", "Option B", "Option C", "Option D"],
      "answer": "The correct answer letter (A, B, C, or D)"
    }
    ```
    """

   
print(f"region: {region}")
   
# Connect to resourse needed from Google Cloud
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)


   
plan=None
   
#load the file using file_name and read content into string call plan
   
with open(file_name, 'r') as f:
       
plan = f.read()

   
parser = JsonOutputParser(pydantic_object=QuizQuestion)


   
instruction = f"You'll provide one question with difficulty level of {difficulty}, 4 options as multiple choices and provide the anwsers, the quiz needs to be related to the teaching plan {plan}"

   
prompt = PromptTemplate(
       
template="Generates a single multiple-choice quiz question\n {format_instructions}\n  {instruction}\n",
       
input_variables=["instruction"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"instruction": instruction})

   
print(f"{response}")
   
return  response


এজেন্টে এটি একটি জেএসএন আউটপুট পার্সার তৈরি করে যা এলএলএমের আউটপুটটি বোঝার এবং গঠনের জন্য বিশেষভাবে ডিজাইন করা হয়েছে। এটি পার্সড আউটপুটটি সঠিক ফর্ম্যাট (প্রশ্ন, বিকল্পগুলি এবং উত্তর) এর সাথে সামঞ্জস্য করে তা নিশ্চিত করতে আমরা আগে সংজ্ঞায়িত QuizQuestion মডেলটি ব্যবহার করি।

ভার্চুয়াল পরিবেশ সেট আপ করতে, নির্ভরতা ইনস্টল করতে এবং এজেন্ট শুরু করার জন্য টার্মিনালে নিম্নলিখিত কমান্ডগুলি পরীক্ষা করুন:

cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py

চলমান অ্যাপ্লিকেশনটি অ্যাক্সেস করতে ক্লাউড শেল এর ওয়েব পূর্বরূপ বৈশিষ্ট্যটি ব্যবহার করুন। শীর্ষ নেভিগেশন বারে বা সূচক পৃষ্ঠায় কার্ড থেকে "কুইজেস" লিঙ্কটিতে ক্লিক করুন। শিক্ষার্থীর জন্য আপনার তিনটি এলোমেলোভাবে উত্পন্ন কুইজ প্রদর্শিত দেখতে হবে। এই কুইজগুলি শিক্ষণ পরিকল্পনার উপর ভিত্তি করে এবং আমাদের এআই-চালিত কুইজ জেনারেশন সিস্টেমের শক্তি প্রদর্শন করে।

কুইজ

স্থানীয়ভাবে চলমান প্রক্রিয়াটি বন্ধ করতে, টার্মিনালে Ctrl+C টিপুন।

মিথুন 2 ব্যাখ্যা জন্য চিন্তা

ঠিক আছে, সুতরাং আমরা কুইজ পেয়েছি, যা দুর্দান্ত শুরু! তবে শিক্ষার্থীরা যদি কিছু ভুল করে তবে কী হবে? আসল শিক্ষাটি সেখানেই ঘটে, তাই না? যদি আমরা তাদের উত্তরটি কেন বন্ধ ছিল এবং কীভাবে সঠিকটিতে যেতে হবে তা যদি আমরা ব্যাখ্যা করতে পারি তবে তারা এটি মনে রাখার সম্ভাবনা বেশি। এছাড়াও, এটি কোনও বিভ্রান্তি পরিষ্কার করতে এবং তাদের আত্মবিশ্বাস বাড়াতে সহায়তা করে।

এজন্য আমরা বড় বন্দুক আনতে যাচ্ছি: জেমিনি 2 এর "চিন্তাভাবনা" মডেল! এআইকে ব্যাখ্যা করার আগে জিনিসগুলি ভাবার জন্য কিছুটা অতিরিক্ত সময় দেওয়ার মতো ভাবুন। এটি এটি আরও বিশদ এবং আরও ভাল প্রতিক্রিয়া জানাতে দেয়।

আমরা দেখতে চাই যে এটি শিক্ষার্থীদের বিশদভাবে সহায়তা, উত্তর এবং ব্যাখ্যা করে সহায়তা করতে পারে কিনা। এটি পরীক্ষা করার জন্য, আমরা একটি কুখ্যাতভাবে জটিল বিষয়, ক্যালকুলাস দিয়ে শুরু করব।

ওভারভিউ

ফার্স্ট, ক্লাউড কোড সম্পাদকের দিকে রওনা করুন, portal ফোল্ডারের অভ্যন্তরে answer.py

def answer_thinking(question, options, user_response, answer, region):
   
return ""

নিম্নলিখিত কোড স্নিপেট সহ:

def answer_thinking(question, options, user_response, answer, region):
   
try:
       
llm = VertexAI(model_name="gemini-2.0-flash-001",location=region)
       
       
input_msg = HumanMessage(content=[f"Here the question{question}, here are the available options {options}, this student's answer {user_response}, whereas the correct answer is {answer}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                       
"You are a helpful teacher trying to teach the student on question, you were given the question and a set of multiple choices "
                       
"what's the correct answer. use friendly tone"
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)
       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"



if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

এটি একটি খুব সাধারণ ল্যাংচেইন অ্যাপ্লিকেশন যেখানে এটি জেমিনি 2 ফ্ল্যাশ মডেলকে সূচনা করে, যেখানে আমরা এটিকে সহায়ক শিক্ষক হিসাবে কাজ করার এবং ব্যাখ্যা সরবরাহ করার জন্য নির্দেশ দিচ্ছি

- টার্মিনালে নিম্নলিখিত কমান্ডটি পরীক্ষা করুন:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

আপনার মূল নির্দেশাবলীতে প্রদত্ত উদাহরণের অনুরূপ আউটপুট দেখতে হবে। বর্তমান মডেলটি ব্যাখ্যার মাধ্যমে সরবরাহ করতে পারে না।

Okay, I see the question and the choices. The question is to evaluate the limit:

lim (x0) [(sin(5x) - 5x) / x^3]

You chose option B, which is -5/3, but the correct answer is A, which is -125/6.

It looks like you might have missed a step or made a small error in your calculations. This type of limit often involves using L'Hôpital's Rule or Taylor series expansion. Since we have the form 0/0, L'Hôpital's Rule is a good way to go! You need to apply it multiple times. Alternatively, you can use the Taylor series expansion of sin(x) which is:
sin(x) = x - x^3/3! + x^5/5! - ...
So, sin(5x) = 5x - (5x)^3/3! + (5x)^5/5! - ...
Then,  (sin(5x) - 5x) = - (5x)^3/3! + (5x)^5/5! - ...
Finally, (sin(5x) - 5x) / x^3 = - 5^3/3! + (5^5 * x^2)/5! - ...
Taking the limit as x approaches 0, we get -125/6.

Keep practicing, you'll get there!

উত্তর.পি ফাইলে, gemini-2.0-flash-001 থেকে gemini-2.0-flash-thinking-exp-01-21 থেকে উত্তর_চক্র ফাংশনে মডেল_নামটি প্রতিস্থাপন করুন।

এটি এলএলএমকে আরও বেশি কারণ হিসাবে পরিবর্তন করে, যা এটি আরও ভাল ব্যাখ্যা তৈরি করতে সহায়তা করবে। এবং এটি আবার চালান।

নতুন চিন্তাভাবনা মডেলটি পরীক্ষা করার জন্য:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

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

Hey there! Let's take a look at this limit problem together. You were asked to evaluate:

lim (x0) [(sin(5x) - 5x) / x^3]

and you picked option B, -5/3, but the correct answer is actually A, -125/6. Let's figure out why!

It's a tricky one because if we directly substitute x=0, we get (sin(0) - 0) / 0^3 = (0 - 0) / 0 = 0/0, which is an indeterminate form. This tells us we need to use a more advanced technique like L'Hopital's Rule or Taylor series expansion.

Let's use the Taylor series expansion for sin(y) around y=0. Do you remember it?  It looks like this:

sin(y) = y - y^3/3! + y^5/5! - ...
where 3! (3 factorial) is 3 × 2 × 1 = 6, 5! is 5 × 4 × 3 × 2 × 1 = 120, and so on.

In our problem, we have sin(5x), so we can substitute y = 5x into the Taylor series:

sin(5x) = (5x) - (5x)^3/3! + (5x)^5/5! - ...
sin(5x) = 5x - (125x^3)/6 + (3125x^5)/120 - ...

Now let's plug this back into our limit expression:

[(sin(5x) - 5x) / x^3] =  [ (5x - (125x^3)/6 + (3125x^5)/120 - ...) - 5x ] / x^3
Notice that the '5x' and '-5x' cancel out!  So we are left with:
= [ - (125x^3)/6 + (3125x^5)/120 - ... ] / x^3
Now, we can divide every term in the numerator by x^3:
= -125/6 + (3125x^2)/120 - ...

Finally, let's take the limit as x approaches 0.  As x gets closer and closer to zero, terms with x^2 and higher powers will become very, very small and approach zero.  So, we are left with:
lim (x0) [ -125/6 + (3125x^2)/120 - ... ] = -125/6

Therefore, the correct answer is indeed **A) -125/6**.

It seems like your answer B, -5/3, might have come from perhaps missing a factor somewhere during calculation or maybe using an incorrect simplification. Double-check your steps when you were trying to solve it!

Don't worry, these limit problems can be a bit tricky sometimes! Keep practicing and you'll get the hang of it.  Let me know if you want to go through another similar example or if you have any more questions! 😊


Now that we have confirmed it works, let's use the portal.

News answer.py থেকে নিম্নলিখিত পরীক্ষার কোডটি সরান :

if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

ভার্চুয়াল পরিবেশ সেট আপ করতে, নির্ভরতা ইনস্টল করতে এবং এজেন্ট শুরু করার জন্য টার্মিনালে নিম্নলিখিত কমান্ডগুলি পরীক্ষা করুন:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py

Collowing চলমান অ্যাপ্লিকেশনটি অ্যাক্সেস করতে ক্লাউড শেলের ওয়েব পূর্বরূপ বৈশিষ্ট্যটি ব্যবহার করুন। "কুইজস" লিঙ্কটিতে ক্লিক করুন, সমস্ত কুইজের উত্তর দিন এবং কমপক্ষে একটি উত্তর ভুল পান এবং জমা দিন ক্লিক করুন

উত্তর চিন্তা

প্রতিক্রিয়াটির জন্য অপেক্ষা করার সময় ফাঁকাভাবে ঘুরে দেখার চেয়ে ক্লাউড সম্পাদকের টার্মিনালে স্যুইচ করুন। আপনি এমুলেটরের টার্মিনালে আপনার ফাংশন দ্বারা উত্পন্ন অগ্রগতি এবং যে কোনও আউটপুট বা ত্রুটি বার্তাগুলি পর্যবেক্ষণ করতে পারেন। 😁

স্থানীয়ভাবে চলমান প্রক্রিয়াটি বন্ধ করতে, টার্মিনালে Ctrl+C টিপুন।

11. Ption চ্ছিক: ইভেন্টার্কের সাথে এজেন্টদের অর্কেস্ট্রেট করা

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

ওভারভিউ

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

Clad ক্লাউড কোড সম্পাদকের এক্সপ্লোরার ফলকে portal ফোল্ডারে নেভিগেট করুন। সম্পাদনার জন্য app.py ফাইলটি খুলুন। ## এর মধ্যে ফলো কোড যুক্ত করুন আপনার কোডটি এখানে যুক্ত করুন :

## Add your code here

@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
   
try:
       
       
# Get data from Pub/Sub message delivered via Eventarc
       
envelope = request.get_json()
       
if not envelope:
           
return jsonify({'error': 'No Pub/Sub message received'}), 400

       
if not isinstance(envelope, dict) or 'message' not in envelope:
           
return jsonify({'error': 'Invalid Pub/Sub message format'}), 400

       
pubsub_message = envelope['message']
       
print(f"data: {pubsub_message['data']}")

       
data = pubsub_message['data']
       
data_str = base64.b64decode(data).decode('utf-8')
       
data = json.loads(data_str)

       
teaching_plan = data['teaching_plan']

       
print(f"File content: {teaching_plan}")

       
with open("teaching_plan.txt", "w") as f:
           
f.write(teaching_plan)

       
print(f"Teaching plan saved to local file: teaching_plan.txt")

       
return jsonify({'message': 'File processed successfully'})


   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500
## Add your code here

পুনর্নির্মাণ এবং ক্লাউড রান মোতায়েন করা

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

স্থাপনার ওভারভিউ

প্রথম আমরা টার্মিনাল রানে ফিরে আমরা পুনর্নির্মাণ করব এবং পরিকল্পনাকারী এজেন্টের চিত্রটি ধাক্কা দেব:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

আমরা একই কাজ করব, পোর্টাল এজেন্টের চিত্রটি তৈরি করব এবং ধাক্কা দেব:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

আর্টিফ্যাক্ট রেজিস্ট্রিতে , আপনার তালিকাভুক্ত aidemy-planner এবং aidemy-portal ধারক চিত্র উভয়ই দেখতে হবে।

ধারক রেপো

Termin টার্মিনালে ব্যাক, প্ল্যানার এজেন্টের জন্য ক্লাউড রান চিত্র আপডেট করতে এটি চালান:

export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-planner \
   
--region=us-central1 \
   
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner:latest

আপনার এর মতো আউটপুট দেখতে হবে:

OK Deploying... Done.                                                                                                                                                     
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
Done.                                                                                                                                                                    
Service [aidemy-planner] revision [aidemy-planner-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-planner-xxx.us-central1.run.app

পরিষেবা URL এর নোট করুন; এটি আপনার মোতায়েন করা পরিকল্পনাকারী এজেন্টের লিঙ্ক। আপনার যদি পরে পরিকল্পনাকারী এজেন্ট পরিষেবা ইউআরএল নির্ধারণ করতে হয় তবে এই কমান্ডটি ব্যবহার করুন:

gcloud run services describe aidemy-planner \
   
--region=us-central1 \
   
--format 'value(status.url)'

পোর্টাল এজেন্টের জন্য ক্লাউড রান উদাহরণ তৈরি করতে এটি রুন

export PROJECT_ID=$(gcloud config get project)
gcloud run deploy aidemy-portal \
 
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal:latest \
 
--region=us-central1 \
 
--platform=managed \
 
--allow-unauthenticated \
 
--memory=2Gi \
 
--cpu=2 \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID}

আপনার এর মতো আউটপুট দেখতে হবে:

Deploying container to Cloud Run service [aidemy-portal] in project [xxxx] region [us-central1]
OK Deploying new service... Done.                                                                                                                                        
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
 
OK Setting IAM Policy...                                                                                                                                                
Done.                                                                                                                                                                    
Service [aidemy-portal] revision [aidemy-portal-xxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-portal-xxxx.us-central1.run.app

পরিষেবা URL এর নোট করুন; এটি আপনার মোতায়েন করা শিক্ষার্থী পোর্টালের লিঙ্ক। আপনার যদি পরে শিক্ষার্থী পোর্টাল পরিষেবা ইউআরএল নির্ধারণ করতে হয় তবে এই কমান্ডটি ব্যবহার করুন:

gcloud run services describe aidemy-portal \
   
--region=us-central1 \
   
--format 'value(status.url)'

ইভেন্টার্ক ট্রিগার তৈরি করা

তবে এখানে বড় প্রশ্ন: পাব/সাব টপিকটিতে কোনও নতুন পরিকল্পনা অপেক্ষা করার সময় এই শেষ পয়েন্টটি কীভাবে অবহিত হবে? সেখানেই ঘটনার দিকে দিনটি বাঁচাতে!

ইভেন্টার্ক একটি সেতু হিসাবে কাজ করে, নির্দিষ্ট ইভেন্টগুলি (যেমন আমাদের পাব/সাব টপিকটিতে আগত একটি নতুন বার্তার মতো) শুনছে এবং প্রতিক্রিয়াতে স্বয়ংক্রিয়ভাবে ক্রিয়াকলাপকে ট্রিগার করে। আমাদের ক্ষেত্রে, এটি সনাক্ত করবে যখন একটি নতুন শিক্ষণ পরিকল্পনা প্রকাশিত হবে এবং তারপরে আমাদের পোর্টালের শেষ পয়েন্টে একটি সংকেত প্রেরণ করবে, এটি জানাতে যে এটি আপডেট করার সময় এসেছে।

ইভেন্ট্ক ইভেন্ট-চালিত যোগাযোগ পরিচালনা করার সাথে সাথে আমরা আমাদের পরিকল্পনাকারী এজেন্ট এবং পোর্টাল এজেন্টকে নির্বিঘ্নে সংযুক্ত করতে পারি, সত্যিকারের গতিশীল এবং প্রতিক্রিয়াশীল শিক্ষার ব্যবস্থা তৈরি করতে পারি। এটি এমন একটি স্মার্ট মেসেঞ্জার থাকার মতো যা স্বয়ংক্রিয়ভাবে সর্বশেষ পাঠের পরিকল্পনাগুলি সঠিক জায়গায় সরবরাহ করে!

কনসোলের দিকে ইভেন্টকার্কের দিকে।

- "+ ট্রিগার তৈরি করুন" বোতামটি ক্লিক করুন।

ট্রিগার (বেসিক) কনফিগার করুন:

  • ট্রিগার নাম: plan-topic-trigger
  • ট্রিগার প্রকার: গুগল উত্স
  • ইভেন্ট সরবরাহকারী: ক্লাউড পাব/সাব
  • ইভেন্টের ধরণ: google.cloud.pubsub.topic.v1.messagePublished
  • ক্লাউড পাব/সাব বিষয়: projects/PROJECT_ID/topics/plan নির্বাচন করুন
  • অঞ্চল: us-central1
  • পরিষেবা অ্যাকাউন্ট:
    • ভূমিকা roles/iam.serviceAccountTokenCreator সহ পরিষেবা অ্যাকাউন্টটি মঞ্জুর করুন
    • ডিফল্ট মানটি ব্যবহার করুন: ডিফল্ট গণনা পরিষেবা অ্যাকাউন্ট
  • ইভেন্টের গন্তব্য: ক্লাউড রান
  • ক্লাউড রান পরিষেবা: aidemy-portal
  • ত্রুটি বার্তাটি উপেক্ষা করুন: 'লোকেশন/এমই-সেন্ট্রাল 2' এ অনুমতি অস্বীকার করা হয়েছে (বা এটি বিদ্যমান নাও থাকতে পারে)।
  • পরিষেবা ইউআরএল পাথ: /new_teaching_plan

"তৈরি করুন" এ ক্লিক করুন।

ইভেন্টার্ক ট্রিগার পৃষ্ঠাটি রিফ্রেশ করবে এবং আপনার এখন আপনার নতুন তৈরি ট্রিগারটি টেবিলে তালিকাভুক্ত দেখতে হবে।

নতুন, নতুন শিক্ষণ পরিকল্পনার জন্য অনুরোধ করতে তার পরিষেবা ইউআরএল ব্যবহার করে পরিকল্পনাকারী এজেন্টকে অ্যাক্সেস করুন।

পরিকল্পনাকারী এজেন্ট পরিষেবা ইউআরএল নির্ধারণ করতে এটি টার্মিনালে চালান:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

এই সময় 5 বছর, সাবজেক্ট Science এবং অ্যাড-অন অনুরোধ atoms চেষ্টা করুন।

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

অবশেষে, তার পরিষেবা ইউআরএল ব্যবহার করে শিক্ষার্থী পোর্টালটি অ্যাক্সেস করুন।

শিক্ষার্থী পোর্টাল এজেন্ট পরিষেবা ইউআরএল নির্ধারণ করতে এটি টার্মিনালে চালান:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

আপনার দেখতে হবে যে কুইজগুলি আপডেট করা হয়েছে এবং এখন আপনি সবেমাত্র তৈরি নতুন শিক্ষণ পরিকল্পনার সাথে একত্রিত হন! এটি এইডেমি সিস্টেমে ইভেন্টার্কের সফল সংহতকরণ প্রদর্শন করে!

এইডেমি-সেলিব্রেট

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

12. Ption চ্ছিক: মিথুনের সাথে অডিও রেক্যাপস

জেমিনি বিভিন্ন উত্স থেকে যেমন পাঠ্য, চিত্র এবং এমনকি অডিও থেকে তথ্য বুঝতে এবং প্রক্রিয়া করতে পারে, শেখার এবং বিষয়বস্তু তৈরির জন্য সম্ভাবনার সম্পূর্ণ নতুন পরিসীমা উন্মুক্ত করে। মিথুনের "দেখুন," "শুনুন" এবং "রিড" করার দক্ষতা সত্যই সৃজনশীল এবং আকর্ষক ব্যবহারকারীর অভিজ্ঞতাগুলি আনলক করে।

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

লাইভ এপিআই ওভারভিউ

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

কনসোলে স্টোরেজে মাথা। বাম-হাতের মেনুতে "বালতি" এ ক্লিক করুন। শীর্ষে "+ তৈরি করুন" বোতামে ক্লিক করুন।

- আপনার নতুন বালতি কনফিগার করুন:

  • বালতি নাম: aidemy-recap-UNIQUE_NAME
    • গুরুত্বপূর্ণ : নিশ্চিত করুন যে আপনি একটি অনন্য বালতি নাম সংজ্ঞায়িত করেছেন যা aidemy-recap- দিয়ে শুরু হয়। আপনার ক্লাউড স্টোরেজ বালতি তৈরি করার সময় নামকরণ দ্বন্দ্ব এড়ানোর জন্য এই অনন্য উপসর্গটি গুরুত্বপূর্ণ।
  • অঞ্চল: us-central1
  • স্টোরেজ ক্লাস: "স্ট্যান্ডার্ড"। স্ট্যান্ডার্ড প্রায়শই অ্যাক্সেস করা ডেটার জন্য উপযুক্ত।
  • অ্যাক্সেস নিয়ন্ত্রণ: নির্বাচিত ডিফল্ট "ইউনিফর্ম" অ্যাক্সেস নিয়ন্ত্রণ ছেড়ে দিন। এটি ধারাবাহিক, বালতি স্তরের অ্যাক্সেস নিয়ন্ত্রণ সরবরাহ করে।
  • উন্নত বিকল্পগুলি: এই কর্মশালার জন্য, ডিফল্ট সেটিংস সাধারণত যথেষ্ট।

আপনার বালতি তৈরি করতে তৈরি বোতামটি ক্লিক করুন।

  • আপনি জনসাধারণের অ্যাক্সেস প্রতিরোধ সম্পর্কে একটি পপ আপ দেখতে পাবেন। "এই বালতিতে জনসাধারণের অ্যাক্সেস প্রতিরোধ প্রয়োগ করুন" বাক্সটি চেক করা হয়েছে এবং Confirm ক্লিক করুন।

আপনি এখন বালতি তালিকায় আপনার নতুন তৈরি বালতি দেখতে পাবেন। আপনার বালতি নামটি মনে রাখবেন, আপনার এটি পরে দরকার।

Well ক্লাউড কোড সম্পাদকের টার্মিনালে, বালতিতে পরিষেবা অ্যাকাউন্টে অ্যাক্সেস দেওয়ার জন্য নিম্নলিখিত কমান্ডগুলি চালান:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

Well ক্লাউড কোড সম্পাদকটিতে, courses ফোল্ডারের অভ্যন্তরে audio.py খুলুন। ফাইলের শেষে নিম্নলিখিত কোডটি আটকান:

config = LiveConnectConfig(
   
response_modalities=["AUDIO"],
   
speech_config=SpeechConfig(
       
voice_config=VoiceConfig(
           
prebuilt_voice_config=PrebuiltVoiceConfig(
               
voice_name="Charon",
           
)
       
)
   
),
)

async def process_weeks(teaching_plan: str):
   
region = "us-east5" #To workaround onRamp quota limits
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
   
clientAudio = genai.Client(vertexai=True, project=PROJECT_ID, location="us-central1")
   
async with clientAudio.aio.live.connect(
       
model=MODEL_ID,
       
config=config,
   
) as session:
       
for week in range(1, 4):  
           
response = client.models.generate_content(
               
model="gemini-2.0-flash-001",
               
contents=f"Given the following teaching plan: {teaching_plan}, Extrace content plan for week {week}. And return just the plan, nothingh else  " # Clarified prompt
           
)

           
prompt = f"""
                Assume you are the instructor.  
                Prepare a concise and engaging recap of the key concepts and topics covered.
                This recap should be suitable for generating a short audio summary for students.
                Focus on the most important learnings and takeaways, and frame it as a direct address to the students.  
                Avoid overly formal language and aim for a conversational tone, tell a few jokes.
               
                Teaching plan: {response.text} """
           
print(f"prompt --->{prompt}")

           
await session.send(input=prompt, end_of_turn=True)
           
with open(f"temp_audio_week_{week}.raw", "wb") as temp_file:
               
async for message in session.receive():
                   
if message.server_content.model_turn:
                       
for part in message.server_content.model_turn.parts:
                           
if part.inline_data:
                               
temp_file.write(part.inline_data.data)
                           
           
data, samplerate = sf.read(f"temp_audio_week_{week}.raw", channels=1, samplerate=24000, subtype='PCM_16', format='RAW')
           
sf.write(f"course-week-{week}.wav", data, samplerate)
       
           
storage_client = storage.Client()
           
bucket = storage_client.bucket(BUCKET_NAME)
           
blob = bucket.blob(f"course-week-{week}.wav")  # Or give it a more descriptive name
           
blob.upload_from_filename(f"course-week-{week}.wav")
           
print(f"Audio saved to GCS: gs://{BUCKET_NAME}/course-week-{week}.wav")
   
await session.close()

 
def breakup_sessions(teaching_plan: str):
   
asyncio.run(process_weeks(teaching_plan))
  • স্ট্রিমিং সংযোগ : প্রথমত, লাইভ এপিআই শেষ পয়েন্টের সাথে একটি অবিরাম সংযোগ স্থাপন করা হয়। আপনি একটি অনুরোধ প্রেরণ করেন এবং একটি প্রতিক্রিয়া পান এমন একটি স্ট্যান্ডার্ড এপিআই কলের বিপরীতে, এই সংযোগটি অবিচ্ছিন্ন ডেটা বিনিময় করার জন্য উন্মুক্ত থাকে।
  • কনফিগারেশন মাল্টিমোডাল : আপনি কী ধরণের আউটপুট চান তা নির্দিষ্ট করার জন্য কনফিগারেশন ব্যবহার করুন (এই ক্ষেত্রে, অডিও) এবং আপনি কী পরামিতিগুলি ব্যবহার করতে চান তাও নির্দিষ্ট করতে পারেন (যেমন, ভয়েস নির্বাচন, অডিও এনকোডিং)
  • অ্যাসিঙ্ক্রোনাস প্রসেসিং : এই এপিআই অ্যাসিনক্রোনালিভাবে কাজ করে, যার অর্থ অডিও প্রজন্মের সমাপ্তির জন্য অপেক্ষা করার সময় এটি মূল থ্রেডটি অবরুদ্ধ করে না। রিয়েল-টাইমে ডেটা প্রক্রিয়াকরণ করে এবং খণ্ডগুলিতে আউটপুট প্রেরণ করে এটি একটি নিকট-উদ্যোগী অভিজ্ঞতা সরবরাহ করে।

এখন, মূল প্রশ্নটি হ'ল: এই অডিও প্রজন্মের প্রক্রিয়াটি কখন চলবে? আদর্শভাবে, আমরা চাই যে নতুন শিক্ষণ পরিকল্পনা তৈরি হওয়ার সাথে সাথে অডিও রেক্যাপগুলি উপলব্ধ হোক। যেহেতু আমরা ইতিমধ্যে একটি পাব/সাব বিষয়কে শিক্ষণ পরিকল্পনা প্রকাশ করে একটি ইভেন্ট-চালিত আর্কিটেকচার বাস্তবায়ন করেছি, তাই আমরা কেবল সেই বিষয়টিতে সাবস্ক্রাইব করতে পারি।

যাইহোক, আমরা খুব প্রায়ই নতুন শিক্ষণ পরিকল্পনা তৈরি করি না। কোনও এজেন্ট ক্রমাগত দৌড়াদৌড়ি করা এবং নতুন পরিকল্পনার জন্য অপেক্ষা করা দক্ষ হবে না। এজন্য এটি ক্লাউড রান ফাংশন হিসাবে এই অডিও প্রজন্মের যুক্তিটি মোতায়েন করার জন্য সঠিক ধারণা তৈরি করে।

এটি একটি ফাংশন হিসাবে মোতায়েন করে, পাব/সাব টপিকটিতে একটি নতুন বার্তা প্রকাশিত না হওয়া পর্যন্ত এটি সুপ্ত থাকে। যখন এটি ঘটে, এটি স্বয়ংক্রিয়ভাবে ফাংশনটিকে ট্রিগার করে, যা অডিও পুনরুদ্ধার উত্পন্ন করে এবং আমাদের বালতিতে সংরক্ষণ করে।

courses main.py এটি পরিকল্পনা গ্রহণ করে এবং অডিও রেকাপ প্রজন্মের সূচনা করে। ফাইলের শেষে নিম্নলিখিত কোড স্নিপেট যুক্ত করুন।

@functions_framework.cloud_event
def process_teaching_plan(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")
   
time.sleep(60)
   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):  # Check for base64 encoding
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan') # Get the teaching plan
       
elif 'teaching_plan' in cloud_event.data: # No base64
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found") # Handle error explicitly

       
#Load the teaching_plan as string and from cloud event, call audio breakup_sessions
       
breakup_sessions(teaching_plan)

       
return "Teaching plan processed successfully", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error processing teaching plan: {e}")
       
return "Error processing teaching plan", 500

@ফাংশন_ফ্রেম ওয়ার্ক.ক্লাউড_এভেন্ট : এই ডেকরেটরটি ক্লাউডভেন্টস দ্বারা ট্রিগার করা হবে এমন ক্লাউড রান ফাংশন হিসাবে ফাংশনটিকে চিহ্নিত করে।

স্থানীয়ভাবে পরীক্ষা করা

আমরা এটি ভার্চুয়াল পরিবেশে চালাব এবং ক্লাউড রান ফাংশনের জন্য প্রয়োজনীয় পাইথন লাইব্রেরি ইনস্টল করব।

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

- ক্লাউড রান ফাংশন এমুলেটর আমাদের গুগল ক্লাউডে স্থাপনের আগে স্থানীয়ভাবে আমাদের ফাংশনটি পরীক্ষা করার অনুমতি দেয়। চলমান দ্বারা একটি স্থানীয় এমুলেটর শুরু করুন:

functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py

- যখন এমুলেটরটি চলছে, আপনি প্রকাশিত হচ্ছে একটি নতুন শিক্ষণ পরিকল্পনা অনুকরণ করতে এমুলেটরটিতে টেস্ট ক্লাউডএভেন্টস প্রেরণ করতে পারেন। একটি নতুন টার্মিনালে:

দুটি টার্মিনাল

আরুন:

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

প্রতিক্রিয়াটির জন্য অপেক্ষা করার সময় ফাঁকাভাবে ঘুরে দেখার পরিবর্তে, অন্যান্য ক্লাউড শেল টার্মিনালে স্যুইচ করুন। আপনি এমুলেটরের টার্মিনালে আপনার ফাংশন দ্বারা উত্পন্ন অগ্রগতি এবং যে কোনও আউটপুট বা ত্রুটি বার্তাগুলি পর্যবেক্ষণ করতে পারেন। 😁

২ য় টার্মিনালে ফিরে আপনার দেখতে হবে এটি OK

আপনি বালতিতে ডেটা যাচাই করবেন, ক্লাউড স্টোরেজে যান এবং "বালতি" ট্যাবটি নির্বাচন করুন এবং তারপরে aidemy-recap-UNIQUE_NAME

বালতি

এমুলেটর চলমান টার্মিনালে, প্রস্থান করতে ctrl+c টাইপ করুন। এবং দ্বিতীয় টার্মিনাল বন্ধ করুন। এবং দ্বিতীয় টার্মিনাল বন্ধ করুন। এবং ভার্চুয়াল পরিবেশ থেকে প্রস্থান করতে নিষ্ক্রিয় চালান।

deactivate

গুগল ক্লাউডে মোতায়েন করা

স্থাপনার ওভারভিউ স্থানীয়ভাবে পরীক্ষার পরে, কোর্স এজেন্টকে গুগল ক্লাউডে স্থাপন করার সময় এসেছে। টার্মিনালে, এই কমান্ডগুলি চালান:

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud functions deploy courses-agent \
 
--region=us-central1 \
 
--gen2 \
 
--source=. \
 
--runtime=python312 \
 
--trigger-topic=plan \
 
--entry-point=process_teaching_plan \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

গুগল ক্লাউড কনসোলে ক্লাউড রান করে মোতায়েন যাচাই করুন You আপনার কোর্স-এজেন্ট তালিকাভুক্ত একটি নতুন পরিষেবা দেখতে হবে।

ক্লাউড রান তালিকা

ট্রিগার কনফিগারেশনটি পরীক্ষা করতে, এর বিশদটি দেখতে কোর্স-এজেন্ট পরিষেবাটিতে ক্লিক করুন। "ট্রিগার" ট্যাবে যান।

পরিকল্পনার বিষয়টিতে প্রকাশিত বার্তাগুলি শোনার জন্য আপনার কনফিগার করা একটি ট্রিগার দেখতে হবে।

ক্লাউড রান ট্রিগার

অবশেষে, আসুন এটি শেষ অবধি চলমান দেখি।

- আমাদের পোর্টাল এজেন্টটি কনফিগার করতে হবে যাতে এটি উত্পন্ন অডিও ফাইলগুলি কোথায় পাওয়া যায় তা জানে। টার্মিনালে, চালান:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

পরিকল্পনাকারী এজেন্ট ওয়েব পৃষ্ঠা ব্যবহার করে একটি নতুন শিক্ষণ পরিকল্পনা তৈরি করা চেষ্টা করুন। এটি শুরু হতে কয়েক মিনিট সময় নিতে পারে, শঙ্কিত হবেন না, এটি একটি সার্ভারলেস পরিষেবা।

পরিকল্পনাকারী এজেন্ট অ্যাক্সেস করতে, টার্মিনালে এটি চালিয়ে এর পরিষেবা URL পান:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep planner

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

courses-agent ফাংশনটি ফাংশনের "ট্রিগারস" ট্যাবটি পরীক্ষা করে শিক্ষণ পরিকল্পনা পেয়েছে কিনা তা আপনি পর্যবেক্ষণ করতে পারেন। পর্যায়ক্রমে পৃষ্ঠাটি রিফ্রেশ করুন; আপনার শেষ পর্যন্ত দেখতে হবে যে ফাংশনটি আহ্বান করা হয়েছে। যদি 2 মিনিটের বেশি পরে ফাংশনটি আহ্বান না করা হয় তবে আপনি আবার শিক্ষণ পরিকল্পনা তৈরি করার চেষ্টা করতে পারেন। যাইহোক, দ্রুত উত্তরাধিকারে বারবার পরিকল্পনা তৈরি করা এড়িয়ে চলুন, কারণ প্রতিটি উত্পন্ন পরিকল্পনা ধারাবাহিকভাবে এজেন্ট দ্বারা গ্রাস করা এবং প্রক্রিয়াজাত করা হবে, সম্ভাব্যভাবে একটি ব্যাকলগ তৈরি করবে।

ট্রিগার পর্যবেক্ষণ

Port পোর্টালটি দেখুন এবং "কোর্স" এ ক্লিক করুন। আপনার তিনটি কার্ড দেখতে হবে, প্রতিটি একটি অডিও পুনরুদ্ধার প্রদর্শন করে। আপনার পোর্টাল এজেন্টের ইউআরএল সন্ধান করতে:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep portal

আপনি সবেমাত্র উত্পন্ন শিক্ষণ পরিকল্পনার সাথে অডিও রিক্যাপগুলি একত্রিত হয়েছে তা নিশ্চিত করতে প্রতিটি কোর্সে "প্লে" ক্লিক করুন! পোর্টাল কোর্স

ভার্চুয়াল পরিবেশ থেকে প্রস্থান করুন।

deactivate

13. Al চ্ছিক: জেমিনি এবং ডিপসেকের সাথে ভূমিকা ভিত্তিক সহযোগিতা

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

মিথুন অ্যাসাইনমেন্ট জেনারেটর

মিথুন ওভারভিউ আমরা একটি সহযোগী জোর দিয়ে অ্যাসাইনমেন্ট তৈরি করতে জেমিনি ফাংশন স্থাপন করে শুরু করব। assignment ফোল্ডারে অবস্থিত gemini.py ফাইলটি সম্পাদনা করুন।

Me gemini.py ফাইলের শেষে নিম্নলিখিত কোডটি সন্ধান করুন:

def gen_assignment_gemini(state):
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"---------------gen_assignment_gemini")
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        You are an instructor

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {state["teaching_plan"]}
        """
   
)

   
print(f"---------------gen_assignment_gemini answer {response.text}")
   
   
state["model_one_assignment"] = response.text
   
   
return state


import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

এটি অ্যাসাইনমেন্ট তৈরি করতে জেমিনি মডেল ব্যবহার করে।

আমরা জেমিনি এজেন্ট পরীক্ষা করতে প্রস্তুত।

পরিবেশ সেটআপ করতে এই কমান্ডগুলি টার্মিনালে রুন:

cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

আপনি এটি পরীক্ষা করতে দৌড়াতে পারেন:

python gemini.py

আপনার এমন একটি অ্যাসাইনমেন্ট দেখতে হবে যা আউটপুটটিতে আরও গ্রুপের কাজ রয়েছে। শেষে দৃ sert ় পরীক্ষাটি ফলাফলগুলিও আউটপুট করবে।

Here are some engaging and practical assignments for each week, designed to build progressively upon the teaching plan's objectives:

**Week 1: Exploring the World of 2D Shapes**

* **Learning Objectives Assessed:**
   
* Identify and name basic 2D shapes (squares, rectangles, triangles, circles).
   
* .....

* **Description:**
   
* **Shape Scavenger Hunt:** Students will go on a scavenger hunt in their homes or neighborhoods, taking pictures of objects that represent different 2D shapes. They will then create a presentation or poster showcasing their findings, classifying each shape and labeling its properties (e.g., number of sides, angles, etc.).
   
* **Triangle Trivia:** Students will research and create a short quiz or presentation about different types of triangles, focusing on their properties and real-world examples.
   
* **Angle Exploration:** Students will use a protractor to measure various angles in their surroundings, such as corners of furniture, windows, or doors. They will record their measurements and create a chart categorizing the angles as right, acute, or obtuse.
....

**Week 2: Delving into the World of 3D Shapes and Symmetry**

* **Learning Objectives Assessed:**
   
* Identify and name basic 3D shapes.
   
* ....

* **Description:**
   
* **3D Shape Construction:** Students will work in groups to build 3D shapes using construction paper, cardboard, or other materials. They will then create a presentation showcasing their creations, describing the number of faces, edges, and vertices for each shape.
   
* **Symmetry Exploration:** Students will investigate the concept of symmetry by creating a visual representation of various symmetrical objects (e.g., butterflies, leaves, snowflakes) using drawing or digital tools. They will identify the lines of symmetry and explain their findings.
   
* **Symmetry Puzzles:** Students will be given a half-image of a symmetrical figure and will be asked to complete the other half, demonstrating their understanding of symmetry. This can be done through drawing, cut-out activities, or digital tools.

**Week 3: Navigating Position, Direction, and Problem Solving**

* **Learning Objectives Assessed:**
   
* Describe position using coordinates in the first quadrant.
   
* ....

* **Description:**
   
* **Coordinate Maze:** Students will create a maze using coordinates on a grid paper. They will then provide directions for navigating the maze using a combination of coordinate movements and translation/reflection instructions.
   
* **Shape Transformations:** Students will draw shapes on a grid paper and then apply transformations such as translation and reflection, recording the new coordinates of the transformed shapes.
   
* **Geometry Challenge:** Students will solve real-world problems involving perimeter, area, and angles. For example, they could be asked to calculate the perimeter of a room, the area of a garden, or the missing angle in a triangle.
....

ctl+c দিয়ে থামুন এবং পরীক্ষার কোডটি পরিষ্কার করতে। gemini.py থেকে নিম্নলিখিত কোডটি সরান

import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

ডিপসেক অ্যাসাইনমেন্ট জেনারেটর কনফিগার করুন

ক্লাউড-ভিত্তিক এআই প্ল্যাটফর্মগুলি সুবিধাজনক হলেও, ডেটা গোপনীয়তা রক্ষা এবং ডেটা সার্বভৌমত্ব নিশ্চিত করার জন্য স্ব-হোস্টিং এলএলএমগুলি গুরুত্বপূর্ণ হতে পারে। আমরা ক্লাউড কম্পিউট ইঞ্জিনের উদাহরণে সবচেয়ে ছোট ডিপসেক মডেল (1.5 বি প্যারামিটার) স্থাপন করব। গুগলের ভার্টেক্স এআই প্ল্যাটফর্মে এটি হোস্টিং করা বা এটি আপনার GKE উদাহরণে হোস্টিংয়ের মতো অন্যান্য উপায় রয়েছে তবে যেহেতু এটি এআই এজেন্টদের উপর কেবল একটি কর্মশালা, এবং আমি আপনাকে এখানে চিরকাল রাখতে চাই না, আসুন আমরা কেবল সবচেয়ে সহজ উপায়টি ব্যবহার করি। তবে আপনি যদি আগ্রহী হন এবং অন্যান্য বিকল্পগুলিতে খনন করতে চান তবে অ্যাসাইনমেন্ট ফোল্ডারের অধীনে deepseek-vertexai.py ফাইলটি একবার দেখুন, যেখানে এটি ভার্টেক্সাইতে মোতায়েন করা মডেলগুলির সাথে কীভাবে ইন্টারঅ্যাক্ট করবেন তার একটি নমুনা কোড সরবরাহ করে।

ডিপসেক ওভারভিউ

একটি স্ব-হোস্টেড এলএলএম প্ল্যাটফর্ম ওলামা তৈরি করতে এই কমান্ডটি টার্মিনালে রুন:

cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
   
--image-family=ubuntu-2204-lts \
   
--image-project=ubuntu-os-cloud \
   
--machine-type=e2-standard-4 \
   
--zone=us-central1-a \
   
--metadata-from-file startup-script=startup.sh \
   
--boot-disk-size=50GB \
   
--tags=ollama \
   
--scopes=https://www.googleapis.com/auth/cloud-platform

গণনা ইঞ্জিন উদাহরণটি যাচাই করতে চলছে:

গুগল ক্লাউড কনসোলে গণনা ইঞ্জিন > "ভিএম দৃষ্টান্তগুলি" নেভিগেট করুন। আপনার দেখানো উচিত যে ollama-instance একটি সবুজ চেক চিহ্নের সাথে তালিকাভুক্ত করা উচিত যা এটি চলছে তা নির্দেশ করে। আপনি যদি এটি দেখতে না পান তবে নিশ্চিত করুন যে অঞ্চলটি মার্কিন-সেন্ট্রাল 1। যদি এটি না হয় তবে আপনার এটি অনুসন্ধান করার প্রয়োজন হতে পারে।

গণনা ইঞ্জিন তালিকা

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

gcloud compute ssh ollama-instance --zone=us-central1-a

এসএসএইচ সংযোগ স্থাপনের পরে, আপনাকে নিম্নলিখিতগুলির সাথে অনুরোধ করা যেতে পারে:

"আপনি কি চালিয়ে যেতে চান (ওয়াই/এন)?"

কেবল Y টাইপ করুন (কেস-সংবেদনশীল) এবং এগিয়ে যেতে এন্টার টিপুন।

এরপরে, আপনাকে এসএসএইচ কীটির জন্য একটি পাসফ্রেজ তৈরি করতে বলা হতে পারে। আপনি যদি কোনও পাসফ্রেজ ব্যবহার না করতে পছন্দ করেন তবে ডিফল্টটি গ্রহণ করতে কেবল দু'বার এন্টার টিপুন (কোনও পাসফ্রেজ নেই)।

আপনি কি ভার্টাল মেশিনে রয়েছেন, সবচেয়ে ছোট ডিপসেক আর 1 মডেলটি টানুন এবং এটি যদি কাজ করে তবে পরীক্ষা করে?

ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"

👉 এসএসএইচ টার্মিনালে নিম্নলিখিত প্রবেশ করুন জিসিই উদাহরণটি প্রকাশ করুন:

exit

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

gcloud compute firewall-rules create allow-ollama-11434 \
   
--allow=tcp:11434 \
   
--target-tags=ollama \
   
--description="Allow access to Ollama on port 11434"

আপনার ফায়ারওয়াল নীতিটি সঠিকভাবে কাজ করছে কিনা তা যাচাই করার জন্য, চলার চেষ্টা করুন:

export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
curl -X POST "${OLLAMA_HOST}/api/generate" \
     
-H "Content-Type: application/json" \
     
-d '{
         
"prompt": "Hello, what are you?",
         
"model": "deepseek-r1:1.5b",
         
"stream": false
       
}'

এরপরে, আমরা পৃথক কাজের জোর দিয়ে অ্যাসাইনমেন্ট তৈরি করতে অ্যাসাইনমেন্ট এজেন্টে ডিপসেক ফাংশনে কাজ করব।

assignment ফোল্ডারের অধীনে deepseek.py শেষে স্নিপেট যুক্ত করুন:

def gen_assignment_deepseek(state):
   
print(f"---------------gen_assignment_deepseek")

   
template = """
        You are an instructor who favor student to focus on individual work.

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {teaching_plan}
        """

   
   
prompt = ChatPromptTemplate.from_template(template)

   
model = OllamaLLM(model="deepseek-r1:1.5b",
                   
base_url=OLLAMA_HOST)

   
chain = prompt | model


   
response = chain.invoke({"teaching_plan":state["teaching_plan"]})
   
state["model_two_assignment"] = response
   
   
return state

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

এটি চালানোর মাধ্যমে এটি পরীক্ষা করুন:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
python deepseek.py

আপনার এমন একটি অ্যাসাইনমেন্ট দেখতে হবে যা আরও স্ব -অধ্যয়নের কাজ রয়েছে।

**Assignment Plan for Each Week**

---

### **Week 1: 2D Shapes and Angles**
- **Week Title:** "Exploring 2D Shapes"
Assign students to research and present on various 2D shapes. Include a project where they create models using straws and tape for triangles, draw quadrilaterals with specific measurements, and compare their properties.

### **Week 2: 3D Shapes and Symmetry**
Assign students to create models or nets for cubes and cuboids. They will also predict how folding these nets form the 3D shapes. Include a project where they identify symmetrical properties using mirrors or folding techniques.

### **Week 3: Position, Direction, and Problem Solving**

Assign students to use mirrors or folding techniques for reflections. Include activities where they measure angles, use a protractor, solve problems involving perimeter/area, and create symmetrical designs.
....

ctl+c স্টপ করুন এবং পরীক্ষার কোডটি পরিষ্কার করতে। deepseek.py থেকে নিম্নলিখিত কোডটি সরান

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

এখন, আমরা উভয় অ্যাসাইনমেন্টকে নতুন একটিতে একত্রিত করতে একই জেমিনি মডেলটি ব্যবহার করব। assignment ফোল্ডারে অবস্থিত gemini.py ফাইলটি সম্পাদনা করুন।

Me gemini.py ফাইলের শেষে নিম্নলিখিত কোডটি সন্ধান করুন:

def combine_assignments(state):
   
print(f"---------------combine_assignments ")
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        Look at all the proposed assignment so far {state["model_one_assignment"]} and {state["model_two_assignment"]}, combine them and come up with a final assignment for student.
        """
   
)

   
state["final_assignment"] = response.text
   
   
return state

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

ল্যাংগ্রাফের সংমিশ্রণ ওভারভিউ

assignment ফোল্ডারের অধীনে main.py ফাইলের শেষে নিম্নলিখিত কোডটি রাখুন:

def create_assignment(teaching_plan: str):
   
print(f"create_assignment---->{teaching_plan}")
   
builder = StateGraph(State)
   
builder.add_node("gen_assignment_gemini", gen_assignment_gemini)
   
builder.add_node("gen_assignment_deepseek", gen_assignment_deepseek)
   
builder.add_node("combine_assignments", combine_assignments)
   
   
builder.add_edge(START, "gen_assignment_gemini")
   
builder.add_edge("gen_assignment_gemini", "gen_assignment_deepseek")
   
builder.add_edge("gen_assignment_deepseek", "combine_assignments")
   
builder.add_edge("combine_assignments", END)

   
graph = builder.compile()
   
state = graph.invoke({"teaching_plan": teaching_plan})

   
return state["final_assignment"]



import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

Creation প্রাথমিকভাবে create_assignment ফাংশনটি পরীক্ষা করে দেখুন এবং নিশ্চিত করুন যে জেমিনি এবং ডিপসিকের সংমিশ্রণকারী কর্মপ্রবাহটি কার্যকরী, নিম্নলিখিত কমান্ডটি চালান:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py

আপনার এমন কিছু দেখতে হবে যা উভয় মডেলকে শিক্ষার্থীদের অধ্যয়নের জন্য এবং শিক্ষার্থীদের গ্রুপ কাজের জন্য তাদের স্বতন্ত্র দৃষ্টিভঙ্গির সাথে একত্রিত করে।

**Tasks:**

1. **Clue Collection:** Gather all the clues left by the thieves. These clues will include:
   
* Descriptions of shapes and their properties (angles, sides, etc.)
   
* Coordinate grids with hidden messages
   
* Geometric puzzles requiring transformation (translation, reflection, rotation)
   
* Challenges involving area, perimeter, and angle calculations

2. **Clue Analysis:** Decipher each clue using your geometric knowledge. This will involve:
   
* Identifying the shape and its properties
   
* Plotting coordinates and interpreting patterns on the grid
   
* Solving geometric puzzles by applying transformations
   
* Calculating area, perimeter, and missing angles

3. **Case Report:** Create a comprehensive case report outlining your findings. This report should include:
   
* A detailed explanation of each clue and its solution
   
* Sketches and diagrams to support your explanations
   
* A step-by-step account of how you followed the clues to locate the artifact
   
* A final conclusion about the thieves and their motives

ctl+c স্টপ করুন এবং পরীক্ষার কোডটি পরিষ্কার করতে। main.py থেকে নিম্নলিখিত কোডটি সরান

import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

অ্যাসাইনমেন্ট.পিএনজি উত্পন্ন করুন

অ্যাসাইনমেন্ট প্রজন্মের প্রক্রিয়াটিকে নতুন শিক্ষণ পরিকল্পনার জন্য স্বয়ংক্রিয় এবং প্রতিক্রিয়াশীল করার জন্য, আমরা বিদ্যমান ইভেন্ট-চালিত আর্কিটেকচারটি উপার্জন করব। নিম্নলিখিত কোডটি একটি ক্লাউড রান ফাংশন (জেনারেট_অ্যাসাইনমেন্ট) সংজ্ঞায়িত করে যা যখনই পাব/সাব টপিক ' প্ল্যান ' তে কোনও নতুন শিক্ষণ পরিকল্পনা প্রকাশিত হয় তখন ট্রিগার করা হবে।

assignment ফোল্ডারে main.py এর শেষে নিম্নলিখিত কোডটি যুক্ত করুন:

@functions_framework.cloud_event
def generate_assignment(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")

   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan')
       
elif 'teaching_plan' in cloud_event.data:
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found")

       
assignment = create_assignment(teaching_plan)

       
print(f"Assignment---->{assignment}")

       
#Store the return assignment into bucket as a text file
       
storage_client = storage.Client()
       
bucket = storage_client.bucket(ASSIGNMENT_BUCKET)
       
file_name = f"assignment-{random.randint(1, 1000)}.txt"
       
blob = bucket.blob(file_name)
       
blob.upload_from_string(assignment)

       
return f"Assignment generated and stored in {ASSIGNMENT_BUCKET}/{file_name}", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error generate assignment: {e}")
       
return "Error generate assignment", 500

স্থানীয়ভাবে পরীক্ষা করা

গুগল ক্লাউডে মোতায়েন করার আগে, স্থানীয়ভাবে ক্লাউড রান ফাংশনটি পরীক্ষা করা ভাল অনুশীলন। এটি দ্রুত পুনরাবৃত্তি এবং সহজ ডিবাগিংয়ের অনুমতি দেয়।

প্রথমে উত্পন্ন অ্যাসাইনমেন্ট ফাইলগুলি সঞ্চয় করতে একটি ক্লাউড স্টোরেজ বালতি তৈরি করুন এবং পরিষেবা অ্যাকাউন্টটি বালতিতে অ্যাক্সেস মঞ্জুর করুন। টার্মিনালে নিম্নলিখিত কমান্ডগুলি চালান:

👉 গুরুত্বপূর্ণ : নিশ্চিত করুন যে আপনি একটি অনন্য অ্যাসাইনমেন্ট_বকেটের নাম সংজ্ঞায়িত করেছেন যা " এইডেমি-অ্যাসাইনমেন্ট- " দিয়ে শুরু হয়। আপনার ক্লাউড স্টোরেজ বালতি তৈরি করার সময় নামকরণ দ্বন্দ্ব এড়ানোর জন্য এই অনন্য নামটি গুরুত্বপূর্ণ। (কোনও এলোমেলো শব্দের সাথে <আপনার_নাম> প্রতিস্থাপন করুন)

export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue

এবং রান:

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gsutil mb -p $PROJECT_ID -l us-central1 gs://$ASSIGNMENT_BUCKET

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

এখন, ক্লাউড রান ফাংশন এমুলেটর শুরু করুন:

cd ~/aidemy-bootstrap/assignment
functions-framework \
   
--target generate_assignment \
   
--signature-type=cloudevent \
   
--source main.py

- যখন এমুলেটরটি একটি টার্মিনালে চলছে, ক্লাউড শেলটিতে একটি দ্বিতীয় টার্মিনাল খুলুন। এই দ্বিতীয় টার্মিনালে, প্রকাশিত হচ্ছে একটি নতুন শিক্ষণ পরিকল্পনা অনুকরণ করতে এমুলেটরকে একটি পরীক্ষা ক্লাউডএভেন্ট প্রেরণ করুন:

দুটি টার্মিনাল

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

প্রতিক্রিয়াটির জন্য অপেক্ষা করার সময় ফাঁকাভাবে ঘুরে দেখার পরিবর্তে, অন্যান্য ক্লাউড শেল টার্মিনালে স্যুইচ করুন। আপনি এমুলেটরের টার্মিনালে আপনার ফাংশন দ্বারা উত্পন্ন অগ্রগতি এবং যে কোনও আউটপুট বা ত্রুটি বার্তাগুলি পর্যবেক্ষণ করতে পারেন। 😁

কার্ল কমান্ডটি "ওকে" মুদ্রণ করা উচিত (একটি নতুনলাইন ছাড়াই, সুতরাং "ঠিক আছে" একই লাইনে আপনার টার্মিনাল শেল প্রম্পটে উপস্থিত হতে পারে)।

অ্যাসাইনমেন্টটি সফলভাবে উত্পন্ন এবং সঞ্চিত ছিল তা নিশ্চিত করার জন্য, গুগল ক্লাউড কনসোলে যান এবং স্টোরেজ > "ক্লাউড স্টোরেজ" এ নেভিগেট করুন। আপনার তৈরি করা aidemy-assignment বালতি নির্বাচন করুন। আপনার বালতিতে assignment-{random number}.txt নামের একটি পাঠ্য ফাইল দেখতে হবে। এটি ডাউনলোড করতে ফাইলটিতে ক্লিক করুন এবং এর সামগ্রীগুলি যাচাই করুন। এটি যাচাই করে যে একটি নতুন ফাইলটিতে সবেমাত্র উত্পন্ন নতুন অ্যাসাইনমেন্ট রয়েছে।

12-01-অ্যাসাইনমেন্ট-বকেট

এমুলেটর চলমান টার্মিনালে, প্রস্থান করতে ctrl+c টাইপ করুন। এবং দ্বিতীয় টার্মিনাল বন্ধ করুন। Allso, এমুলেটর চালানো টার্মিনালে ভার্চুয়াল পরিবেশ থেকে প্রস্থান করুন।

deactivate

স্থাপনার ওভারভিউ

নেক্সট, আমরা ক্লাউডে অ্যাসাইনমেন্ট এজেন্ট স্থাপন করব

cd ~/aidemy-bootstrap/assignment
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
export PROJECT_ID=$(gcloud config get project)
gcloud functions deploy assignment-agent \
 
--gen2 \
 
--timeout=540 \
 
--memory=2Gi \
 
--cpu=1 \
 
--set-env-vars="ASSIGNMENT_BUCKET=${ASSIGNMENT_BUCKET}" \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
 
--set-env-vars=OLLAMA_HOST=${OLLAMA_HOST} \
 
--region=us-central1 \
 
--runtime=python312 \
 
--source=. \
 
--entry-point=generate_assignment \
 
--trigger-topic=plan

গুগল ক্লাউড কনসোলে গিয়ে ক্লাউড রানে নেভিগেট করে মোতায়েন যাচাই করুন। আপনার তালিকাভুক্ত কোর্স-এজেন্ট নামে একটি নতুন পরিষেবা দেখতে হবে। 12-03-ফাংশন-তালিকা

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

14. Ption চ্ছিক: জেমিনি এবং ডিপসেকের সাথে ভূমিকা ভিত্তিক সহযোগিতা - কনড।

Dynamic website generation

To enhance the student portal and make it more engaging, we'll implement dynamic HTML generation for assignment pages. The goal is to automatically update the portal with a fresh, visually appealing design whenever a new assignment is generated. This leverages the LLM's coding capabilities to create a more dynamic and interesting user experience.

14-01-generate-html

👉In Cloud Shell Editor, edit the render.py file within the portal folder, replace

def render_assignment_page():
   
return ""

with following code snippet:

def render_assignment_page(assignment: str):
   
try:
       
region=get_next_region()
       
llm = VertexAI(model_name="gemini-2.0-flash-001", location=region)
       
input_msg = HumanMessage(content=[f"Here the assignment {assignment}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                        """
                        As a frontend developer, create HTML to display a student assignment with a creative look and feel. Include the following navigation bar at the top:
                        ```
                        <nav>
                            <a href="/">Home</a>
                            <a href="/quiz">Quizzes</a>
                            <a href="/courses">Courses</a>
                            <a href="/assignment">Assignments</a>
                        </nav>
                        ```
                        Also include these links in the <head> section:
                        ```
                        <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
                        <link rel="preconnect" href="https://fonts.googleapis.com">
                        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                        <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">

                        ```
                        Do not apply inline styles to the navigation bar.
                        The HTML should display the full assignment content. In its CSS, be creative with the rainbow colors and aesthetic.
                        Make it creative and pretty
                        The assignment content should be well-structured and easy to read.
                        respond with JUST the html file
                        """
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)

       
response = response.replace("```html", "")
       
response = response.replace("```", "")
       
with open("templates/assignment.html", "w") as f:
           
f.write(response)


       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"

It uses the Gemini model to dynamically generate HTML for the assignment. It takes the assignment content as input and uses a prompt to instruct Gemini to create a visually appealing HTML page with a creative style.

Next, we'll create an endpoint that will be triggered whenever a new document is added to the assignment bucket:

👉Within the portal folder, edit the app.py file and add the following code within the ## Add your code here" comments , AFTER the new_teaching_plan function:

## Add your code here

def new_teaching_plan():
       
...
       
...
       
...

   
except Exception as e:
       
...
       
...

@app.route('/render_assignment', methods=['POST'])
def render_assignment():
   
try:
       
data = request.get_json()
       
file_name = data.get('name')
       
bucket_name = data.get('bucket')

       
if not file_name or not bucket_name:
           
return jsonify({'error': 'Missing file name or bucket name'}), 400

       
storage_client = storage.Client()
       
bucket = storage_client.bucket(bucket_name)
       
blob = bucket.blob(file_name)
       
content = blob.download_as_text()

       
print(f"File content: {content}")

       
render_assignment_page(content)

       
return jsonify({'message': 'Assignment rendered successfully'})

   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500

## Add your code here

When triggered, it retrieves the file name and bucket name from the request data, downloads the assignment content from Cloud Storage, and calls the render_assignment_page function to generate the HTML.

👉We'll go ahead and run it locally:

cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py

👉From the "Web preview" menu at the top of the Cloud Shell window, select "Preview on port 8080". This will open your application in a new browser tab. Navigate to the Assignment link in the navigation bar. You should see a blank page at this point, which is expected behavior since we haven't yet established the communication bridge between the assignment agent and the portal to dynamically populate the content.

14-02-deployment-overview

o ahead and stop the script by pressing Ctrl+C .

👉To incorporate these changes and deploy the updated code, rebuild and push the portal agent image:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

👉After pushing the new image, redeploy the Cloud Run service. Run the following script to force the Cloud Run update:

export PROJECT_ID=$(gcloud config get project)
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Now, we'll deploy an Eventarc trigger that listens for any new object created (finalized) in the assignment bucket. This trigger will automatically invoke the /render_assignment endpoint on the portal service when a new assignment file is created.

export PROJECT_ID=$(gcloud config get project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$(gcloud storage service-agent --project $PROJECT_ID)" \
 
--role="roles/pubsub.publisher"
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud eventarc triggers create portal-assignment-trigger \
--location=us-central1 \
--service-account=$SERVICE_ACCOUNT_NAME \
--destination-run-service=aidemy-portal \
--destination-run-region=us-central1 \
--destination-run-path="/render_assignment" \
--event-filters="bucket=$ASSIGNMENT_BUCKET" \
--event-filters="type=google.cloud.storage.object.v1.finalized"

To verify that the trigger was created successfully, navigate to the Eventarc Triggers page in the Google Cloud Console. You should see portal-assignment-trigger listed in the table. Click on the trigger name to view its details. Assignment Trigger

It may take up to 2-3 minutes for the new trigger to become active.

To see the dynamic assignment generation in action, run the following command to find the URL of your planner agent (if you don't have it handy):

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

Find the URL of your portal agent:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

In the planner agent, generate a new teaching plan.

13-02-assignment

After a few minutes (to allow for the audio generation, assignment generation, and HTML rendering to complete), navigate to the student portal.

👉Click on the "Assignment" link in the navigation bar. You should see a newly created assignment with a dynamically generated HTML. Each time a teaching plan is generated it should be a dynamic assignment.

13-02-assignment

Congratulations on completing the Aidemy multi-agent system ! You've gained practical experience and valuable insights into:

  • The benefits of multi-agent systems, including modularity, scalability, specialization, and simplified maintenance.
  • The importance of event-driven architectures for building responsive and loosely coupled applications.
  • The strategic use of LLMs, matching the right model to the task and integrating them with tools for real-world impact.
  • Cloud-native development practices using Google Cloud services to create scalable and reliable solutions.
  • The importance of considering data privacy and self-hosting models as an alternative to vendor solutions.

You now have a solid foundation for building sophisticated AI-powered applications on Google Cloud!

15. চ্যালেঞ্জ এবং পরবর্তী পদক্ষেপ

Congratulations on building the Aidemy multi-agent system! You've laid a strong foundation for AI-powered education. Now, let's consider some challenges and potential future enhancements to further expand its capabilities and address real-world needs:

Interactive Learning with Live Q&A:

  • Challenge: Can you leverage Gemini 2's Live API to create a real-time Q&A feature for students? Imagine a virtual classroom where students can ask questions and receive immediate, AI-powered responses.

Automated Assignment Submission and Grading:

  • Challenge: Design and implement a system that allows students to submit assignments digitally and have them automatically graded by AI, with a mechanism to detect and prevent plagiarism. This challenge presents a great opportunity to explore Retrieval Augmented Generation (RAG) to enhance the accuracy and reliability of the grading and plagiarism detection processes.

aidemy-climb

16. পরিষ্কার করুন

Now that we've built and explored our Aidemy multi-agent system, it's time to clean up our Google Cloud environment.

👉Delete Cloud Run services

gcloud run services delete aidemy-planner --region=us-central1 --quiet
gcloud run services delete aidemy-portal --region=us-central1 --quiet
gcloud run services delete courses-agent --region=us-central1 --quiet
gcloud run services delete book-provider --region=us-central1 --quiet
gcloud run services delete assignment-agent --region=us-central1 --quiet

👉Delete Eventarc trigger

gcloud eventarc triggers delete portal-assignment-trigger --location=us --quiet
gcloud eventarc triggers delete plan-topic-trigger --location=us-central1 --quiet
gcloud eventarc triggers delete portal-assignment-trigger --location=us-central1 --quiet
ASSIGNMENT_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:assignment-agent" --format="value(name)")
COURSES_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:courses-agent" --format="value(name)")
gcloud eventarc triggers delete $ASSIGNMENT_AGENT_TRIGGER --location=us-central1 --quiet
gcloud eventarc triggers delete $COURSES_AGENT_TRIGGER --location=us-central1 --quiet

👉Delete Pub/Sub topic

gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet

👉Delete Cloud SQL instance

gcloud sql instances delete aidemy --quiet

👉Delete Artifact Registry repository

gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet

👉Delete Secret Manager secrets

gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet

👉Delete Compute Engine instance (if created for Deepseek)

gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet

👉Delete the firewall rule for Deepseek instance

gcloud compute firewall-rules delete allow-ollama-11434 --quiet

👉Delete Cloud Storage buckets

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
gsutil rm -r gs://$COURSE_BUCKET_NAME
gsutil rm -r gs://$ASSIGNMENT_BUCKET

aidemy-broom

,
Aidemy:
Building Multi-Agent Systems with LangGraph, EDA, and Generative AI on Google Cloud

এই কোডল্যাব সম্পর্কে

subjectমার্চ ১৩, ২০২৫-এ শেষবার আপডেট করা হয়েছে
account_circleChristina Lin-এর লেখা

1. ভূমিকা

এই যে! So, you're into the idea of agents – little helpers that can get things done for you without you even lifting a finger, right? অসাধারন! But let's be real, one agent isn't always going to cut it, especially when you're tackling bigger, more complex projects. You're probably going to need a whole team of them! That's where multi-agent systems come in.

Agents, when powered by LLMs, give you incredible flexibility compared to old-school hard coding. But, and there's always a but, they come with their own set of tricky challenges. And that's exactly what we're going to dive into in this workshop!

শিরোনাম

Here's what you can expect to learn – think of it as leveling up your agent game:

Building Your First Agent with LangGraph : We'll get our hands dirty building your very own agent using LangGraph, a popular framework. You'll learn how to create tools that connect to databases, tap into the latest Gemini 2 API for some internet searching, and optimize the prompts and response, so your agent can interact with not only LLMs but existing services. We'll also show you how function calling works.

Agent Orchestration, Your Way : We'll explore different ways to orchestrate your agents, from simple straight paths to more complex multi-path scenarios. Think of it as directing the flow of your agent team.

Multi-Agent Systems : You'll discover how to set up a system where your agents can collaborate, and get things done together – all thanks to an event-driven architecture.

LLM Freedom – Use the Best for the Job: We're not stuck on just one LLM! You'll see how to use multiple LLMs, assigning them different roles to boost problem-solving power using cool "thinking models."

Dynamic Content? কোন সমস্যা নেই! : Imagine your agent creating dynamic content that's tailored specifically for each user, in real-time. আমরা আপনাকে দেখাব কিভাবে এটি করতে হবে!

Taking it to the Cloud with Google Cloud : Forget just playing around in a notebook. We'll show you how to architect and deploy your multi-agent system on Google Cloud so it's ready for the real world!

This project will be a good example of how to use all the techniques we talked about.

2. স্থাপত্য

Being a teacher or working in education can be super rewarding, but let's face it, the workload, especially all the prep work, can be challenging! Plus, there's often not enough staff and tutoring can be expensive. That's why we're proposing an AI-powered teaching assistant. This tool can lighten the load for educators and help bridge the gap caused by staff shortages and the lack of affordable tutoring.

Our AI teaching assistant can whip up detailed lesson plans, fun quizzes, easy-to-follow audio recaps, and personalized assignments. This lets teachers focus on what they do best: connecting with students and helping them fall in love with learning.

The system has two sites: one for teachers to create lesson plans for upcoming weeks,

পরিকল্পনাকারী

and one for students to access quizzes, audio recaps, and assignments. পোর্টাল

Alright, let's walk through the architecture powering our teaching assistant, Aidemy. As you can see, we've broken it down into several key components, all working together to make this happen.

স্থাপত্য

Key Architectural Elements and Technologies :

Google Cloud Platform (GCP) : Central to the entire system:

  • Vertex AI: Accesses Google's Gemini LLMs.
  • Cloud Run: Serverless platform for deploying containerized agents and functions.
  • Cloud SQL: PostgreSQL database for curriculum data.
  • Pub/Sub & Eventarc: Foundation of the event-driven architecture, enabling asynchronous communication between components.
  • Cloud Storage: Stores audio recaps and assignment files.
  • Secret Manager: Securely manages database credentials.
  • Artifact Registry: Stores Docker images for the agents.
  • Compute Engine: To deploy self-hosted LLM instead of relying on vendor solutions

LLMs : The "brains" of the system:

  • Google's Gemini models: (Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking, Gemini 1.5-pro) Used for lesson planning, content generation, dynamic HTML creation, quiz explanation and combining the assignments.
  • DeepSeek: Utilized for the specialized task of generating self-study assignments

LangChain & LangGraph : Frameworks for LLM Application Development

  • Facilitates the creation of complex multi-agent workflows.
  • Enables the intelligent orchestration of tools (API calls, database queries, web searches).
  • Implements event-driven architecture for system scalability and flexibility.

In essence, our architecture combines the power of LLMs with structured data and event-driven communication, all running on Google Cloud. This lets us build a scalable, reliable, and effective teaching assistant.

3. আপনি শুরু করার আগে

In the Google Cloud Console , on the project selector page, select or create a Google Cloud project . Make sure that billing is enabled for your Cloud project. Learn how to check if billing is enabled on a project .

👉Click Activate Cloud Shell at the top of the Google Cloud console (It's the terminal shape icon at the top of the Cloud Shell pane), click on the "Open Editor" button (it looks like an open folder with a pencil). This will open the Cloud Shell Code Editor in the window. You'll see a file explorer on the left side.

মেঘের শেল

👉Click on the Cloud Code Sign-in button in the bottom status bar as shown. Authorize the plugin as instructed. If you see Cloud Code - no project in the status bar, select that then in the drop down 'Select a Google Cloud Project' and then select the specific Google Cloud Project from the list of projects that you created.

Login project

👉Open the terminal in the cloud IDE, নতুন টার্মিনাল

👉In the terminal, verify that you're already authenticated and that the project is set to your project ID using the following command:

gcloud auth list

👉And run:

gcloud config set project <YOUR_PROJECT_ID>

👉Run the following command to enable the necessary Google Cloud APIs:

gcloud services enable compute.googleapis.com  \
                       
storage.googleapis.com  \
                       
run.googleapis.com  \
                       
artifactregistry.googleapis.com  \
                       
aiplatform.googleapis.com \
                       
eventarc.googleapis.com \
                       
sqladmin.googleapis.com \
                       
secretmanager.googleapis.com \
                       
cloudbuild.googleapis.com \
                       
cloudresourcemanager.googleapis.com \
                       
cloudfunctions.googleapis.com

This may take a couple of minutes..

Enable Gemini Code Assist in Cloud Shell IDE

Click on the Code Assist button in the on left panel as shown and select one last time the correct Google Cloud project. If you are asked to enable the Cloud AI Companion API, please do so and move forward. Once you've selected your Google Cloud project, ensure that you are able to see that in the Cloud Code status message in the status bar and that you also have Code Assist enabled on the right, in the status bar as shown below:

Enable codeassist

Setting up permission

👉Setup service account permission. In the terminal, run :

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")

echo "Here's your SERVICE_ACCOUNT_NAME $SERVICE_ACCOUNT_NAME"

👉 Grant Permissions. In the terminal, run :

#Cloud Storage (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/storage.objectAdmin"

#Pub/Sub (Publish/Receive):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.publisher"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.subscriber"


#Cloud SQL (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/cloudsql.editor"


#Eventarc (Receive Events):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/iam.serviceAccountTokenCreator"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/eventarc.eventReceiver"

#Vertex AI (User):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/aiplatform.user"

#Secret Manager (Read):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/secretmanager.secretAccessor"

👉Validate result in your IAM console আইএএম কনসোল

👉Run the following commands in the terminal to create a Cloud SQL instance named aidemy . We'll need this later, but since this process can take some time, we'll do it now.

gcloud sql instances create aidemy \
   
--database-version=POSTGRES_14 \
   
--cpu=2 \
   
--memory=4GB \
   
--region=us-central1 \
   
--root-password=1234qwer \
   
--storage-size=10GB \
   
--storage-auto-increase

4. Building the first agent

Before we dive into complex multi-agent systems, we need to establish a fundamental building block: a single, functional agent. In this section, we'll take our first steps by creating a simple "book provider" agent. The book provider agent takes a category as input and uses a Gemini LLM to generate a JSON representation book within that category. It then serves these book recommendations as a REST API endpoint .

Book Provider

👉In another browser tab, open the Google Cloud Console in your web browser,in the navigation menu (☰), go to "Cloud Run". Click the "+ ... WRITE A FUNCTION" button.

ফাংশন তৈরি করুন

👉Next we'll configures the basic settings of the Cloud Run Function:

  • Service name: book-provider
  • Region: us-central1
  • Runtime: Python 3.12
  • Authentication: Allow unauthenticated invocations to Enabled.

👉Leave other settings as default and click Create . This will take you to the source code editor.

You'll see pre-populated main.py and requirements.txt files.

The main.py will contain the business logic of the function, requirements.txt will contain the packages needed.

👉Now we are ready to write some code! But before diving in, let's see if Gemini Code Assist can give us a head start. Return to the Cloud Shell Editor, click on the Gemini Code Assist icon, and paste the following request into the prompt box: মিথুন কোড সহায়তা

Use the functions_framework library to be deployable as an HTTP function. 
Accept a request with category and number_of_book parameters (either in JSON body or query string).
Use langchain and gemini to generate the data for book with fields bookname, author, publisher, publishing_date.
Use pydantic to define a Book model with the fields: bookname (string, description: "Name of the book"), author (string, description: "Name of the author"), publisher (string, description: "Name of the publisher"), and publishing_date (string, description: "Date of publishing").
Use langchain and gemini model to generate book data. the output should follow the format defined in Book model.

The logic should use JsonOutputParser from langchain to enforce output format defined in Book Model.
Have a function get_recommended_books(category) that internally uses langchain and gemini to return a single book object.
The main function, exposed as the Cloud Function, should call get_recommended_books() multiple times (based on number_of_book) and return a JSON list of the generated book objects.
Handle the case where category or number_of_book are missing by returning an error JSON response with a 400 status code.
return a JSON string representing the recommended books. use os library to retrieve GOOGLE_CLOUD_PROJECT env var. Use ChatVertexAI from langchain for the LLM call

Code Assist will then generate a potential solution, providing both the source code and a requirements.txt dependency file.

We encourage you to compare the Code Assist's generated code with the tested, correct solution provided below. This allows you to evaluate the tool's effectiveness and identify any potential discrepancies. While LLMs should never be blindly trusted, Code Assist can be a great tool for rapid prototyping and generating initial code structures, and should be use for a good head start.

Since this is a workshop, we'll proceed with the verified code provided below. However, feel free to experiment with the Code Assist-generated code in your own time to gain a deeper understanding of its capabilities and limitations.

👉Return to the Cloud Run Function's source code editor (in the other browser tab). Carefully replace the existing content of main.py with the code provided below:

import functions_framework
import json
from flask import Flask, jsonify, request
from langchain_google_vertexai import ChatVertexAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
import os

class Book(BaseModel):
   
bookname: str = Field(description="Name of the book")
   
author: str = Field(description="Name of the author")
   
publisher: str = Field(description="Name of the publisher")
   
publishing_date: str = Field(description="Date of publishing")


project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")  

llm = ChatVertexAI(model_name="gemini-2.0-flash-lite-001")

def get_recommended_books(category):
    """
    A simple book recommendation function.

    Args:
        category (str): category

    Returns:
        str: A JSON string representing the recommended books.
    """
   
parser = JsonOutputParser(pydantic_object=Book)
   
question = f"Generate a random made up book on {category} with bookname, author and publisher and publishing_date"

   
prompt = PromptTemplate(
       
template="Answer the user query.\n{format_instructions}\n{query}\n",
       
input_variables=["query"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"query": question})

   
return  json.dumps(response)
   

@functions_framework.http
def recommended(request):
   
request_json = request.get_json(silent=True) # Get JSON data
   
if request_json and 'category' in request_json and 'number_of_book' in request_json:
       
category = request_json['category']
       
number_of_book = int(request_json['number_of_book'])
   
elif request.args and 'category' in request.args and 'number_of_book' in request.args:
       
category = request.args.get('category')
       
number_of_book = int(request.args.get('number_of_book'))

   
else:
       
return jsonify({'error': 'Missing category or number_of_book parameters'}), 400


   
recommendations_list = []
   
for i in range(number_of_book):
       
book_dict = json.loads(get_recommended_books(category))
       
print(f"book_dict=======>{book_dict}")
   
       
recommendations_list.append(book_dict)

   
   
return jsonify(recommendations_list)

👉Replace the contents of requirements.txt with the following:

functions-framework==3.*
google-genai==1.0.0
flask==3.1.0
jsonify==0.5
langchain_google_vertexai==2.0.13
langchain_core==0.3.34
pydantic==2.10.5

👉we'll set the Function entry point : recommended

03-02-function-create.png

👉Click SAVE AND DEPLOY . to deploy the Function. Wait for the deployment process to complete. The Cloud Console will display the status. এতে কয়েক মিনিট সময় লাগতে পারে।

alt পাঠ্য 👉Once deployed, go back in the cloud shell editor, in the terminal run:

export PROJECT_ID=$(gcloud config get project)
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

curl -X POST -H "Content-Type: application/json" -d '{"category": "Science Fiction", "number_of_book": 2}' $BOOK_PROVIDER_URL

It should show some book data in JSON format.

[
 
{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},
 
{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}
]

অভিনন্দন! You have successfully deployed a Cloud Run Function. This is one of the services we will be integrating when developing our Aidemy agent.

5. Building Tools: Connecting Agents to RESTFUL service and Data

Let's go ahead and download the Bootstrap Skeleton Project, make sure you are in the Cloud Shell Editor. In the terminal run,

git clone https://github.com/weimeilin79/aidemy-bootstrap.git

After running this command, a new folder named aidemy-bootstrap will be created in your Cloud Shell environment.

In the Cloud Shell Editor's Explorer pane (usually on the left side), you should now see the folder that was created when you cloned the Git repository aidemy-bootstrap . Open the root folder of your project in the Explorer. You'll find a planner subfolder within it, open that as well. প্রকল্প এক্সপ্লোরার

Let's start building the tools our agents will use to become truly helpful. As you know, LLMs are excellent at reasoning and generating text, but they need access to external resources to perform real-world tasks and provide accurate, up-to-date information. Think of these tools as the agent's "Swiss Army knife," giving it the ability to interact with the world.

When building an agent, it's easy to fall into hard-coding a ton of details. This creates an agent that is not flexible. Instead, by creating and using tools, the agent has access to external logic or systems which gives it the benefits of both the LLM and traditional programming.

In this section, we'll create the foundation for the planner agent, which teachers will use to generate lesson plans. Before the agent starts generating a plan, we want to set boundaries by providing more details on the subject and topic. We'll build three tools:

  1. Restful API Call: Interacting with a pre-existing API to retrieve data.
  2. Database Query: Fetching structured data from a Cloud SQL database.
  3. Google Search: Accessing real-time information from the web.

Fetching Book Recommendations from an API

First, let's create a tool that retrieves book recommendations from the book-provider API we deployed in the previous section. This demonstrates how an agent can leverage existing services.

Recommend book

In the Cloud Shell Editor, open the aidemy-bootstrap project that you cloned in the previous section.

👉Edit the book.py in the planner folder, and paste the following code at the end of the file:

def recommend_book(query: str):
    """
    Get a list of recommended book from an API endpoint
   
    Args:
        query: User's request string
    """

   
region = get_next_region();
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)

   
query = f"""The user is trying to plan a education course, you are the teaching assistant. Help define the category of what the user requested to teach, respond the categroy with no more than two word.

    user request:   {query}
    """
   
print(f"-------->{query}")
   
response = llm.invoke(query)
   
print(f"CATEGORY RESPONSE------------>: {response}")
   
   
# call this using python and parse the json back to dict
   
category = response.strip()
   
   
headers = {"Content-Type": "application/json"}
   
data = {"category": category, "number_of_book": 2}

   
books = requests.post(BOOK_PROVIDER_URL, headers=headers, json=data)
   
   
return books.text

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

ব্যাখ্যা:

  • recommend_book(query: str) : This function takes a user's query as input.
  • LLM Interaction : It uses the LLM to extract the category from the query. This demonstrates how you can use the LLM to help create parameters for tools.
  • API Call : It makes a POST request to the book-provider API, passing the category and the desired number of books.

👉To test this new function, set the environment variable, run :

cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

👉Install the dependencies and run the code to ensure it works, run:

cd ~/aidemy-bootstrap/planner/
python -m venv env
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
pip install -r requirements.txt
python book.py

Ignore the Git warning pop-up window.

You should see a JSON string containing book recommendations retrieved from the book-provider API. The results are randomly generated. Your books may not be the same, but you should receive two book recommendations in JSON format.

[{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}]

If you see this, the first tool is working correctly!

Instead of explicitly crafting a RESTful API call with specific parameters, we're using natural language ("I'm doing a course..."). The agent then intelligently extracts the necessary parameters (like the category) using NLP, highlighting how the agent leverages natural language understanding to interact with the API.

compare call

👉 Remove the following testing code from the book.py

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

Getting Curriculum Data from a Database

Next, we'll build a tool that fetches structured curriculum data from a Cloud SQL PostgreSQL database. This allows the agent to access a reliable source of information for lesson planning.

create db

Remember the aidemy Cloud SQL instance you've created in previous step? Here's where it will be used.

👉Create a database named aidemy-db in the new instance.

gcloud sql databases create aidemy-db \
   
--instance=aidemy

Let's verify the instance in the Cloud SQL in the Google Cloud Console, You should see a Cloud SQL instance named aidemy listed. Click on the instance name to view its details. In the Cloud SQL instance details page, click on "SQL Studio" in the left-hand navigation menu. এটি একটি নতুন ট্যাব খুলবে।

Click to connect to the database. Sign in to the SQL Studio

Select aidemy-db as the database. enter postgres as user and 1234qwer as the password . sql studio sign in

👉In the SQL Studio query editor, paste the following SQL code:

CREATE TABLE curriculums (
   
id SERIAL PRIMARY KEY,
   
year INT,
   
subject VARCHAR(255),
   
description TEXT
);

-- Inserting detailed curriculum data for different school years and subjects
INSERT INTO curriculums (year, subject, description) VALUES
-- Year 5
(5, 'Mathematics', 'Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.'),
(5, 'English', 'Developing reading comprehension, creative writing, and basic grammar, with a focus on storytelling and poetry.'),
(5, 'Science', 'Exploring basic physics, chemistry, and biology concepts, including forces, materials, and ecosystems.'),
(5, 'Computer Science', 'Basic coding concepts using block-based programming and an introduction to digital literacy.'),

-- Year 6
(6, 'Mathematics', 'Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.'),
(6, 'English', 'Introduction to persuasive writing, character analysis, and deeper comprehension of literary texts.'),
(6, 'Science', 'Forces and motion, the human body, and introductory chemical reactions with hands-on experiments.'),
(6, 'Computer Science', 'Introduction to algorithms, logical reasoning, and basic text-based programming (Python, Scratch).'),

-- Year 7
(7, 'Mathematics', 'Algebraic expressions, geometry, and introduction to statistics and probability.'),
(7, 'English', 'Analytical reading of classic and modern literature, essay writing, and advanced grammar skills.'),
(7, 'Science', 'Introduction to cells and organisms, chemical reactions, and energy transfer in physics.'),
(7, 'Computer Science', 'Building on programming skills with Python, introduction to web development, and cyber safety.');

This SQL code creates a table named curriculums and inserts some sample data. Click Run to execute the SQL code. You should see a confirmation message indicating that the commands were executed successfully.

👉Expand the explorer, find the newly created table and click query . It should open a new editor tab with SQL generated for you,

sql studio select table

SELECT * FROM
 
"public"."curriculums" LIMIT 1000;

👉Click Run .

The results table should display the rows of data you inserted in the previous step, confirming that the table and data were created correctly.

Now that you have successfully created a database with populated sample curriculum data, we'll build a tool to retrieve it.

👉In the Cloud Code Editor, edit file curriculums.py in the aidemy-bootstrap folder and paste the following code at the end of the file:

def connect_with_connector() -> sqlalchemy.engine.base.Engine:

   
db_user = os.environ["DB_USER"]
   
db_pass = os.environ["DB_PASS"]
   
db_name = os.environ["DB_NAME"]

   
encoded_db_user = os.environ.get("DB_USER")
   
print(f"--------------------------->db_user: {db_user!r}")  
   
print(f"--------------------------->db_pass: {db_pass!r}")
   
print(f"--------------------------->db_name: {db_name!r}")

   
ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

   
connector = Connector()

   
def getconn() -> pg8000.dbapi.Connection:
       
conn: pg8000.dbapi.Connection = connector.connect(
           
instance_connection_name,
           
"pg8000",
           
user=db_user,
           
password=db_pass,
           
db=db_name,
           
ip_type=ip_type,
       
)
       
return conn

   
pool = sqlalchemy.create_engine(
       
"postgresql+pg8000://",
       
creator=getconn,
       
pool_size=2,
       
max_overflow=2,
       
pool_timeout=30,  # 30 seconds
       
pool_recycle=1800,  # 30 minutes
   
)
   
return pool



def init_connection_pool() -> sqlalchemy.engine.base.Engine:
   
   
return (
       
connect_with_connector()
   
)

   
raise ValueError(
       
"Missing database connection type. Please define one of INSTANCE_HOST, INSTANCE_UNIX_SOCKET, or INSTANCE_CONNECTION_NAME"
   
)

def get_curriculum(year: int, subject: str):
    """
    Get school curriculum
   
    Args:
        subject: User's request subject string
        year: User's request year int
    """
   
try:
       
stmt = sqlalchemy.text(
           
"SELECT description FROM curriculums WHERE year = :year AND subject = :subject"
       
)

       
with db.connect() as conn:
           
result = conn.execute(stmt, parameters={"year": year, "subject": subject})
           
row = result.fetchone()
       
if row:
           
return row[0]  
       
else:
           
return None  

   
except Exception as e:
       
print(e)
       
return None

db = init_connection_pool()

ব্যাখ্যা:

  • Environment Variables : The code retrieves database credentials and connection information from environment variables (more on this below).
  • connect_with_connector() : This function uses the Cloud SQL Connector to establish a secure connection to the database.
  • get_curriculum(year: int, subject: str) : This function takes the year and subject as input, queries the curriculums table, and returns the corresponding curriculum description.

👉Before we can run the code, we must set some environment variables, in the terminal, run:

export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉To test add the following code to the end of curriculums.py :

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉Run the code:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py

You should see the curriculum description for 6th-grade Mathematics printed to the console.

Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.

If you see the curriculum description, the database tool is working correctly! Go ahead and stop the script by pressing Ctrl+C .

👉 Remove the following testing code from the curriculums.py

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉Exit virtual environment, in terminal run:

deactivate

6. Building Tools: Access real-time information from the web

Finally, we'll build a tool that uses the Gemini 2 and Google Search integration to access real-time information from the web. This helps the agent stay up-to-date and provide relevant results.

Gemini 2's integration with the Google Search API enhances agent capabilities by providing more accurate and contextually relevant search results. This allows agents to access up-to-date information and ground their responses in real-world data, minimizing hallucinations. The improved API integration also facilitates more natural language queries, enabling agents to formulate complex and nuanced search requests.

অনুসন্ধান করুন

This function takes a search query, curriculum, subject, and year as input and uses the Gemini API and the Google Search tool to retrieve relevant information from the internet. If you look closely, it's using the Google Generative AI SDK to do function calling without using any other framework.

👉Edit search.py in the aidemy-bootstrap folder and paste the following code at the end of the file:

model_id = "gemini-2.0-flash-001"

google_search_tool = Tool(
   
google_search = GoogleSearch()
)

def search_latest_resource(search_text: str, curriculum: str, subject: str, year: int):
    """
    Get latest information from the internet
   
    Args:
        search_text: User's request category   string
        subject: "User's request subject" string
        year: "User's request year"  integer
    """
   
search_text = "%s in the context of year %d and subject %s with following curriculum detail %s " % (search_text, year, subject, curriculum)
   
region = get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"search_latest_resource text-----> {search_text}")
   
response = client.models.generate_content(
       
model=model_id,
       
contents=search_text,
       
config=GenerateContentConfig(
           
tools=[google_search_tool],
           
response_modalities=["TEXT"],
       
)
   
)
   
print(f"search_latest_resource response-----> {response}")
   
return response

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

ব্যাখ্যা:

  • Defining Tool - google_search_tool : Wrapping the GoogleSearch object within a Tool
  • search_latest_resource(search_text: str, subject: str, year: int) : This function takes a search query, subject, and year as input and uses the Gemini API to perform a Google search. Gemini model
  • GenerateContentConfig : Define that it has access to the GoogleSearch tool

The Gemini model internally analyzes the search_text and determines whether it can answer the question directly or if it needs to use the GoogleSearch tool. This is a critical step that happens within the LLM's reasoning process. The model has been trained to recognize situations where external tools are necessary. If the model decides to use the GoogleSearch tool, the Google Generative AI SDK handles the actual invocation. The SDK takes the model's decision and the parameters it generates and sends them to the Google Search API. This part is hidden from the user in the code.

The Gemini model then integrates the search results into its response. It can use the information to answer the user's question, generate a summary, or perform some other task.

👉To test, run the code:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py

You should see the Gemini Search API response containing search results related to "Syllabus for Year 5 Mathematics." The exact output will depend on the search results, but it will be a JSON object with information about the search.

If you see search results, the Google Search tool is working correctly! Go ahead and stop the script by pressing Ctrl+C .

👉And remove the last part in the code.

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

👉Exit virtual environment, in terminal run:

deactivate

অভিনন্দন! You have now built three powerful tools for your planner agent: an API connector, a database connector, and a Google Search tool. These tools will enable the agent to access the information and capabilities it needs to create effective teaching plans.

7. Orchestrating with LangGraph

Now that we have built our individual tools, it's time to orchestrate them using LangGraph. This will allow us to create a more sophisticated "planner" agent that can intelligently decide which tools to use and when, based on the user's request.

LangGraph is a Python library designed to make it easier to build stateful, multi-actor applications using Large Language Models (LLMs). Think of it as a framework for orchestrating complex conversations and workflows involving LLMs, tools, and other agents.

মূল ধারণা:

  • Graph Structure: LangGraph represents your application's logic as a directed graph. Each node in the graph represents a step in the process (eg, a call to an LLM, a tool invocation, a conditional check). Edges define the flow of execution between nodes.
  • State: LangGraph manages the state of your application as it moves through the graph. This state can include variables like the user's input, the results of tool calls, intermediate outputs from LLMs, and any other information that needs to be preserved between steps.
  • Nodes: Each node represents a computation or interaction. তারা হতে পারে:
    • Tool Nodes: Use a tool (eg, perform a web search, query a database)
    • Function Nodes: Execute a Python function.
  • Edges: Connect nodes, defining the flow of execution. তারা হতে পারে:
    • Direct Edges: A simple, unconditional flow from one node to another.
    • Conditional Edges: The flow depends on the outcome of a conditional node.

ল্যাংগ্রাফ

We will use LangGraph to implement the orchestration. Let's edit the aidemy.py file under aidemy-bootstrap folder to define our LangGraph logic.

👉Append follow code to the end of aidemy.py :

tools = [get_curriculum, search_latest_resource, recommend_book]

def determine_tool(state: MessagesState):
   
llm = ChatVertexAI(model_name="gemini-2.0-flash-001", location=get_next_region())
   
sys_msg = SystemMessage(
                   
content=(
                       
f"""You are a helpful teaching assistant that helps gather all needed information.
                            Your ultimate goal is to create a detailed 3-week teaching plan.
                            You have access to tools that help you gather information.  
                            Based on the user request, decide which tool(s) are needed.

                        """
                   
)
               
)

   
llm_with_tools = llm.bind_tools(tools)
   
return {"messages": llm_with_tools.invoke([sys_msg] + state["messages"])}

This function is responsible for taking the current state of the conversation, providing the LLM with a system message, and then asking the LLM to generate a response. The LLM can either respond directly to the user or choose to use one of the available tools.

tools : This list represents the set of tools that the agent has available to it. It contains three tool functions that we defined in the previous steps: get_curriculum , search_latest_resource , and recommend_book . llm.bind_tools(tools) : It "binds" the tools list to the llm object. Binding the tools tells the LLM that these tools are available and provides the LLM with information about how to use them (eg, the names of the tools, the parameters they accept, and what they do).

We will use LangGraph to implement the orchestration.

👉Append following code to the end of aidemy.py :

def prep_class(prep_needs):
   
   
builder = StateGraph(MessagesState)
   
builder.add_node("determine_tool", determine_tool)
   
builder.add_node("tools", ToolNode(tools))
   
   
builder.add_edge(START, "determine_tool")
   
builder.add_conditional_edges("determine_tool",tools_condition)
   
builder.add_edge("tools", "determine_tool")

   
   
memory = MemorySaver()
   
graph = builder.compile(checkpointer=memory)

   
config = {"configurable": {"thread_id": "1"}}
   
messages = graph.invoke({"messages": prep_needs},config)
   
print(messages)
   
for m in messages['messages']:
       
m.pretty_print()
   
teaching_plan_result = messages["messages"][-1].content  


   
return teaching_plan_result

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan")

ব্যাখ্যা:

  • StateGraph(MessagesState) : Creates a StateGraph object. A StateGraph is a core concept in LangGraph. It represents the workflow of your agent as a graph, where each node in the graph represents a step in the process. Think of it as defining the blueprint for how the agent will reason and act.
  • Conditional Edge: Originating from the "determine_tool" node, the tools_condition argument is likely a function that determines which edge to follow based on the output of the determine_tool function. Conditional edges allow the graph to branch based on the LLM's decision about which tool to use (or whether to respond to the user directly). This is where the agent's "intelligence" comes into play – it can dynamically adapt its behavior based on the situation.
  • Loop: Adds an edge to the graph that connects the "tools" node back to the "determine_tool" node. This creates a loop in the graph, allowing the agent to repeatedly use tools until it has gathered enough information to complete the task and provide a satisfactory answer. This loop is crucial for complex tasks that require multiple steps of reasoning and information gathering.

Now, let's test our planner agent to see how it orchestrates the different tools.

This code will run the prep_class function with a specific user input, simulating a request to create a teaching plan for 5th-grade Mathematics in Geometry, using the curriculum, book recommendations, and the latest internet resources.

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Run the code:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py

Watch the log in the terminal. You should see evidence that the agent is calling all three tools (getting the school curriculum, getting book recommendations, and searching for the latest resources) before providing the final teaching plan. This demonstrates that the LangGraph orchestration is working correctly, and the agent is intelligently using all available tools to fulfill the user's request.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
search_text: Geometry
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
subject: Mathematics
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.....) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================
Tool Calls:
 
recommend_book (93b48189-4d69-4c09-a3bd-4e60cdc5f1c6)
 
Call ID: 93b48189-4d69-4c09-a3bd-4e60cdc5f1c6
 
Args:
   
query: Mathematics Geometry Year 5
================================= Tool Message =================================
Name: recommend_book

[{.....}]

================================== Ai Message ==================================

Based on the curriculum outcome, here is a 3-week teaching plan for year 5 Mathematics Geometry:

**Week 1: Introduction to Shapes and Properties**
.........

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉(THIS STEP IS OPTIONAL) replace the testing code with a different prompt, which requires different tools to be called.

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉(THIS STEP IS OPTIONAL, do this ONLY IF you ran the previous step) Run the code again:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py

এই সময় আপনি কি লক্ষ্য করেছেন? Which tools did the agent call? You should see that the agent only calls the search_latest_resource tool this time. This is because the prompt does not specify that it needs the other two tools, and our LLM is smart enough to not call the other tools.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
subject: Mathematics
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
search_text: Geometry
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.......token_count=40, total_token_count=772) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================

Based on the information provided, a 3-week teaching plan for Year 5 Mathematics focusing on Geometry could look like this:

**Week 1:  Introducing 2D Shapes**
........
* Use visuals, manipulatives, and real-world examples to make the learning experience engaging and relevant.

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉 Remove the testing code to keep your aidemy.py file clean (DO NOT SKIP THIS STEP!):

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

With our agent logic now defined, let's launch the Flask web application. This will provide a familiar form-based interface for teachers to interact with the agent. While chatbot interactions are common with LLMs, we're opting for a traditional form submit UI, as it may be more intuitive for many educators.

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Now, start the Web UI.

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py

Look for startup messages in the Cloud Shell terminal output. Flask usually prints messages indicating that it's running and on what port.

Running on http://127.0.0.1:8080
Running on http://127.0.0.1:8080
The application needs to keep running to serve requests.

👉From the "Web preview" menu, choose Preview on port 8080. Cloud Shell will open a new browser tab or window with the web preview of your application.

ওয়েব পেজ

In the application interface, select 5 for Year, select subject Mathematics and type in Geometry in the Add-on Request

Rather than staring blankly while waiting for the response, switch over to the Cloud Editor's terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

👉Stop the script by pressing Ctrl+C in the terminal.

👉Exit the virtual environment:

deactivate

8. Deploying planner agent to the cloud

Build and push image to registry

ওভারভিউ

👉Time to deploy this to the cloud. In the terminal, create an artifacts repository to store the docker image we are going to build.

gcloud artifacts repositories create agent-repository \
   
--repository-format=docker \
   
--location=us-central1 \
   
--description="My agent repository"

You should see Created repository [agent-repository].

👉Run the following command to build the Docker image.

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .

👉We need to retag the image so that it's hosted in Artifact Registry instead of GCR and push the tagged image to Artifact Registry:

export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

Once the push is complete, you can verify that the image is successfully stored in Artifact Registry. Navigate to the Artifact Registry in the Google Cloud Console. You should find the aidemy-planner image within the agent-repository repository. Aidemy planner image

Securing Database Credentials with Secret Manager

To securely manage and access database credentials, we'll use Google Cloud Secret Manager. This prevents hardcoding sensitive information in our application code and enhances security.

👉We'll create individual secrets for the database username, password, and database name. This approach allows us to manage each credential independently. In the terminal run:

gcloud secrets create db-user
printf "postgres" | gcloud secrets versions add db-user --data-file=-

gcloud secrets create db-pass
printf "1234qwer" | gcloud secrets versions add db-pass --data-file=-

gcloud secrets create db-name
printf "aidemy-db" | gcloud secrets versions add db-name --data-file=-

Using Secret Manager is a important step in securing your application and preventing accidental exposure of sensitive credentials. It follows security best practices for cloud deployments.

ক্লাউড রানে স্থাপন করুন

Cloud Run is a fully managed serverless platform that allows you to deploy containerized applications quickly and easily. It abstracts away the infrastructure management, letting you focus on writing and deploying your code. We'll be deploying our planner as a Cloud Run service.

👉In the Google Cloud Console, navigate to " Cloud Run ". Click on DEPLOY CONTAINER and select SERVICE . Configure your Cloud Run service:

Cloud run

  1. Container image : Click "Select" in the URL field. Find the image URL you pushed to Artifact Registry (eg, us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
  2. Service name : aidemy-planner
  3. Region : Select the us-central1 region.
  4. Authentication : For the purpose of this workshop, you can allow "Allow unauthenticated invocations". For production, you'll likely want to restrict access.
  5. Container(s) tab (Expand the Containers, Network):
    • Setting tab:
      • সম্পদ
        • memory : 2GiB
    • Variables & Secrets tab:
      • পরিবেশ পরিবর্তনশীল:
        • Add name: GOOGLE_CLOUD_PROJECT and value: <YOUR_PROJECT_ID>
        • Add name: BOOK_PROVIDER_URL , and set the value to your book-provider function URL, which you can determine using the following command in the terminal:
          gcloud run services describe book-provider \
             
          --region=us-central1 \
             
          --project=$PROJECT_ID \
             
          --format="value(status.url)"
      • Secrets exposed as environment variables:
        • Add name: DB_USER , secret: select db-user and version: latest
        • Add name: DB_PASS , secret: select db-pass and version: latest
        • Add name: DB_NAME , secret: select db-name and version: latest

Set secret

Leave other as default.

👉Click CREATE .

Cloud Run will deploy your service.

Once deployed, click on the service to its detail page, you can find the deployed URL available on the top.

URL

In the application interface, select 7 for the Year, choose Mathematics as the subject, and enter Algebra in the Add-on Request field. This will provide the agent with the necessary context to generate a tailored lesson plan.

অভিনন্দন! You've successfully created a teaching plan using our powerful AI agent. This demonstrates the potential of agents to significantly reduce workload and streamline tasks, ultimately improving efficiency and making life easier for educators.

9. মাল্টি-এজেন্ট সিস্টেম

Now that we've successfully implemented the teaching plan creation tool, let's shift our focus to building the student portal. This portal will provide students with access to quizzes, audio recaps, and assignments related to their coursework. Given the scope of this functionality, we'll leverage the power of multi-agent systems to create a modular and scalable solution.

As we discussed earlier, instead of relying on a single agent to handle everything, a multi-agent system allows us to break down the workload into smaller, specialized tasks, each handled by a dedicated agent. এই পদ্ধতিটি বেশ কয়েকটি মূল সুবিধা প্রদান করে:

Modularity and Maintainability : Instead of creating a single agent that does everything, build smaller, specialized agents with well-defined responsibilities. This modularity makes the system easier to understand, maintain, and debug. When a problem arises, you can isolate it to a specific agent, rather than having to sift through a massive codebase.

Scalability : Scaling a single, complex agent can be a bottleneck. With a multi-agent system, you can scale individual agents based on their specific needs. For example, if one agent is handling a high volume of requests, you can easily spin up more instances of that agent without affecting the rest of the system.

Team Specialization : Think of it like this: you wouldn't ask one engineer to build an entire application from scratch. Instead, you assemble a team of specialists, each with expertise in a particular area. Similarly, a multi-agent system allows you to leverage the strengths of different LLMs and tools, assigning them to agents that are best suited for specific tasks.

Parallel Development : Different teams can work on different agents concurrently, speeding up the development process. Since agents are independent, changes to one agent are less likely to impact other agents.

ইভেন্ট চালিত আর্কিটেকচার

To enable effective communication and coordination between these agents, we'll employ an event-driven architecture. This means that agents will react to "events" happening within the system.

Agents subscribe to specific event types (eg, "teaching plan generated," "assignment created"). When an event occurs, the relevant agents are notified and can react accordingly. This decoupling promotes flexibility, scalability, and real-time responsiveness.

ওভারভিউ

Now, to kick things off, we need a way to broadcast these events. To do this, we will set up a Pub/Sub topic. Let's start by creating a topic called plan .

👉Go to Google Cloud Console pub/sub and click on the "Create Topic" button.

👉Configure the Topic with ID/name plan and uncheck Add a default subscription , leave rest as default and click Create .

The Pub/Sub page will refresh, and you should now see your newly created topic listed in the table. টপিক তৈরি করুন

Now, let's integrate the Pub/Sub event publishing functionality into our planner agent. We'll add a new tool that sends a "plan" event to the Pub/Sub topic we just created. This event will signal to other agents in the system (like those in the student portal) that a new teaching plan is available.

👉Go back to the Cloud Code Editor and open the app.py file located in the planner folder. We will be adding a function that publishes the event. প্রতিস্থাপন:

##ADD SEND PLAN EVENT FUNCTION HERE

সঙ্গে

def send_plan_event(teaching_plan:str):
    """
    Send the teaching event to the topic called plan
   
    Args:
        teaching_plan: teaching plan
    """
   
publisher = pubsub_v1.PublisherClient()
   
print(f"-------------> Sending event to topic plan: {teaching_plan}")
   
topic_path = publisher.topic_path(PROJECT_ID, "plan")

   
message_data = {"teaching_plan": teaching_plan}
   
data = json.dumps(message_data).encode("utf-8")

   
future = publisher.publish(topic_path, data)

   
return f"Published message ID: {future.result()}"

  • send_plan_event : This function takes the generated teaching plan as input, creates a Pub/Sub publisher client, constructs the topic path, converts the teaching plan into a JSON string , publishes the message to the topic.

In the same app.py file

👉Update the prompt to instruct the agent to send the teaching plan event to the Pub/Sub topic after generating the teaching plan. প্রতিস্থাপন করুন

### ADD send_plan_event CALL

নিম্নলিখিত সঙ্গে:

send_plan_event(teaching_plan)

By adding the send_plan_event tool and modifying the prompt, we've enabled our planner agent to publish events to Pub/Sub, allowing other components of our system to react to the creation of new teaching plans. We will now have a functional multi-agent system in the following sections.

10. Empowering Students with On-Demand Quizzes

Imagine a learning environment where students have access to an endless supply of quizzes tailored to their specific learning plans. These quizzes provide immediate feedback, including answers and explanations, fostering a deeper understanding of the material. This is the potential we aim to unlock with our AI-powered quiz portal.

To bring this vision to life, we'll build a quiz generation component that can create multiple-choice questions based on the content of the teaching plan.

ওভারভিউ

👉In the Cloud Code Editor's Explorer pane, navigate to the portal folder. Open the quiz.py file copy and paste the following code to the end of the file.

def generate_quiz_question(file_name: str, difficulty: str, region:str ):
    """Generates a single multiple-choice quiz question using the LLM.
   
    ```json
    {
      "question": "The question itself",
      "options": ["Option A", "Option B", "Option C", "Option D"],
      "answer": "The correct answer letter (A, B, C, or D)"
    }
    ```
    """

   
print(f"region: {region}")
   
# Connect to resourse needed from Google Cloud
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)


   
plan=None
   
#load the file using file_name and read content into string call plan
   
with open(file_name, 'r') as f:
       
plan = f.read()

   
parser = JsonOutputParser(pydantic_object=QuizQuestion)


   
instruction = f"You'll provide one question with difficulty level of {difficulty}, 4 options as multiple choices and provide the anwsers, the quiz needs to be related to the teaching plan {plan}"

   
prompt = PromptTemplate(
       
template="Generates a single multiple-choice quiz question\n {format_instructions}\n  {instruction}\n",
       
input_variables=["instruction"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"instruction": instruction})

   
print(f"{response}")
   
return  response


In the agent it creates a JSON output parser that's specifically designed to understand and structure the LLM's output. It uses the QuizQuestion model we defined earlier to ensure the parsed output conforms to the correct format (question, options, and answer).

👉Execute the following commands in the terminal to set up a virtual environment, install dependencies, and start the agent:

cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py

Use the Cloud Shell's web preview feature to access the running application. Click on the "Quizzes" link, either in the top navigation bar or from the card on the index page. You should see three randomly generated quizzes displayed for the student. These quizzes are based on the teaching plan and demonstrate the power of our AI-powered quiz generation system.

কুইজ

👉To stop the locally running process, press Ctrl+C in the terminal.

Gemini 2 Thinking for Explanations

Okay, so we've got quizzes, which is a great start! But what if students get something wrong? That's where the real learning happens, right? If we can explain why their answer was off and how to get to the correct one, they're way more likely to remember it. Plus, it helps clear up any confusion and boost their confidence.

That's why we're going to bring in the big guns: Gemini 2's "thinking" model! Think of it like giving the AI a little extra time to think things through before explaining. It lets it give more detailed and better feedback.

We want to see if it can help students by assisting, answering and explaining in detail. To test it out, we'll start with a notoriously tricky subject, Calculus.

ওভারভিউ

👉First, head over to the Cloud Code Editor, in answer.py inside the portal folder replace

def answer_thinking(question, options, user_response, answer, region):
   
return ""

with following code snippet:

def answer_thinking(question, options, user_response, answer, region):
   
try:
       
llm = VertexAI(model_name="gemini-2.0-flash-001",location=region)
       
       
input_msg = HumanMessage(content=[f"Here the question{question}, here are the available options {options}, this student's answer {user_response}, whereas the correct answer is {answer}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                       
"You are a helpful teacher trying to teach the student on question, you were given the question and a set of multiple choices "
                       
"what's the correct answer. use friendly tone"
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)
       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"



if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

This is a very simple langchain app where it Initializes the Gemini 2 Flash model, where we are instructing it to act as a helpful teacher and provide explanations

👉Execute the following command in the terminal:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

You should see output similar to the example provided in the original instructions. The current model may not provide as through explanation.

Okay, I see the question and the choices. The question is to evaluate the limit:

lim (x0) [(sin(5x) - 5x) / x^3]

You chose option B, which is -5/3, but the correct answer is A, which is -125/6.

It looks like you might have missed a step or made a small error in your calculations. This type of limit often involves using L'Hôpital's Rule or Taylor series expansion. Since we have the form 0/0, L'Hôpital's Rule is a good way to go! You need to apply it multiple times. Alternatively, you can use the Taylor series expansion of sin(x) which is:
sin(x) = x - x^3/3! + x^5/5! - ...
So, sin(5x) = 5x - (5x)^3/3! + (5x)^5/5! - ...
Then,  (sin(5x) - 5x) = - (5x)^3/3! + (5x)^5/5! - ...
Finally, (sin(5x) - 5x) / x^3 = - 5^3/3! + (5^5 * x^2)/5! - ...
Taking the limit as x approaches 0, we get -125/6.

Keep practicing, you'll get there!

In the answer.py file, replace the model_name from gemini-2.0-flash-001 to gemini-2.0-flash-thinking-exp-01-21 in the answer_thinking function.

This changes the LLM that reasons more, which will help it generate better explanations. And run it again.

👉Run to test the new thinking model:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

Here is an example of the response from the thinking model that is much more thorough and detailed, providing a step-by-step explanation of how to solve the calculus problem. This highlights the power of "thinking" models in generating high-quality explanations. You should see output similar to this:

Hey there! Let's take a look at this limit problem together. You were asked to evaluate:

lim (x0) [(sin(5x) - 5x) / x^3]

and you picked option B, -5/3, but the correct answer is actually A, -125/6. Let's figure out why!

It's a tricky one because if we directly substitute x=0, we get (sin(0) - 0) / 0^3 = (0 - 0) / 0 = 0/0, which is an indeterminate form. This tells us we need to use a more advanced technique like L'Hopital's Rule or Taylor series expansion.

Let's use the Taylor series expansion for sin(y) around y=0. Do you remember it?  It looks like this:

sin(y) = y - y^3/3! + y^5/5! - ...
where 3! (3 factorial) is 3 × 2 × 1 = 6, 5! is 5 × 4 × 3 × 2 × 1 = 120, and so on.

In our problem, we have sin(5x), so we can substitute y = 5x into the Taylor series:

sin(5x) = (5x) - (5x)^3/3! + (5x)^5/5! - ...
sin(5x) = 5x - (125x^3)/6 + (3125x^5)/120 - ...

Now let's plug this back into our limit expression:

[(sin(5x) - 5x) / x^3] =  [ (5x - (125x^3)/6 + (3125x^5)/120 - ...) - 5x ] / x^3
Notice that the '5x' and '-5x' cancel out!  So we are left with:
= [ - (125x^3)/6 + (3125x^5)/120 - ... ] / x^3
Now, we can divide every term in the numerator by x^3:
= -125/6 + (3125x^2)/120 - ...

Finally, let's take the limit as x approaches 0.  As x gets closer and closer to zero, terms with x^2 and higher powers will become very, very small and approach zero.  So, we are left with:
lim (x0) [ -125/6 + (3125x^2)/120 - ... ] = -125/6

Therefore, the correct answer is indeed **A) -125/6**.

It seems like your answer B, -5/3, might have come from perhaps missing a factor somewhere during calculation or maybe using an incorrect simplification. Double-check your steps when you were trying to solve it!

Don't worry, these limit problems can be a bit tricky sometimes! Keep practicing and you'll get the hang of it.  Let me know if you want to go through another similar example or if you have any more questions! 😊


Now that we have confirmed it works, let's use the portal.

👉 REMOVE the following test code from answer.py :

if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

👉Execute the following commands in the terminal to set up a virtual environment, install dependencies, and start the agent:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py

👉Use the Cloud Shell's web preview feature to access the running application. Click on the "Quizzes" link, answer all the quizzes and make sure at least get one answer wrong and click submit

thinking answers

Rather than staring blankly while waiting for the response, switch over to the Cloud Editor's terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

To stop the locally running process, press Ctrl+C in the terminal.

11. OPTIONAL: Orchestrating the Agents with Eventarc

So far, the student portal has been generating quizzes based on a default set of teaching plans. That's helpful, but it means our planner agent and portal's quiz agent aren't really talking to each other. Remember how we added that feature where the planner agent publishes its newly generated teaching plans to a Pub/Sub topic? Now it's time to connect that to our portal agent!

ওভারভিউ

We want the portal to automatically update its quiz content whenever a new teaching plan is generated. To do that, we'll create an endpoint in the portal that can receive these new plans.

👉In the Cloud Code Editor's Explorer pane, navigate to the portal folder. Open the app.py file for editing. Add the follow code in between ## Add your code here :

## Add your code here

@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
   
try:
       
       
# Get data from Pub/Sub message delivered via Eventarc
       
envelope = request.get_json()
       
if not envelope:
           
return jsonify({'error': 'No Pub/Sub message received'}), 400

       
if not isinstance(envelope, dict) or 'message' not in envelope:
           
return jsonify({'error': 'Invalid Pub/Sub message format'}), 400

       
pubsub_message = envelope['message']
       
print(f"data: {pubsub_message['data']}")

       
data = pubsub_message['data']
       
data_str = base64.b64decode(data).decode('utf-8')
       
data = json.loads(data_str)

       
teaching_plan = data['teaching_plan']

       
print(f"File content: {teaching_plan}")

       
with open("teaching_plan.txt", "w") as f:
           
f.write(teaching_plan)

       
print(f"Teaching plan saved to local file: teaching_plan.txt")

       
return jsonify({'message': 'File processed successfully'})


   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500
## Add your code here

Rebuilding and Deploying to Cloud Run

You'll need to update and redeploy both our planner and portal agents to Cloud Run. This ensures they have the latest code and are configured to communicate via events.

স্থাপনার ওভারভিউ

👉First we'll rebuild and push the planner agent image, back in the terminal run:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

👉We'll do the same, build and push the portal agent image:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

In Artifact Registry , you should see both the aidemy-planner and aidemy-portal container images listed.

Container Repo

👉Back in the terminal, run this to update the Cloud Run image for the planner agent:

export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-planner \
   
--region=us-central1 \
   
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner:latest

You should see output similar to this:

OK Deploying... Done.                                                                                                                                                     
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
Done.                                                                                                                                                                    
Service [aidemy-planner] revision [aidemy-planner-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-planner-xxx.us-central1.run.app

Make note of the Service URL; this is the link to your deployed planner agent. If you need to later determine the planner agent Service URL, use this command:

gcloud run services describe aidemy-planner \
   
--region=us-central1 \
   
--format 'value(status.url)'

👉Run this to create the Cloud Run instance for the portal agent

export PROJECT_ID=$(gcloud config get project)
gcloud run deploy aidemy-portal \
 
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal:latest \
 
--region=us-central1 \
 
--platform=managed \
 
--allow-unauthenticated \
 
--memory=2Gi \
 
--cpu=2 \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID}

You should see output similar to this:

Deploying container to Cloud Run service [aidemy-portal] in project [xxxx] region [us-central1]
OK Deploying new service... Done.                                                                                                                                        
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
 
OK Setting IAM Policy...                                                                                                                                                
Done.                                                                                                                                                                    
Service [aidemy-portal] revision [aidemy-portal-xxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-portal-xxxx.us-central1.run.app

Make note of the Service URL; this is the link to your deployed student portal. If you need to later determine the student portal Service URL, use this command:

gcloud run services describe aidemy-portal \
   
--region=us-central1 \
   
--format 'value(status.url)'

Creating the Eventarc Trigger

But here's the big question: how does this endpoint get notified when there's a fresh plan waiting in the Pub/Sub topic? That's where Eventarc swoops in to save the day!

Eventarc acts as a bridge, listening for specific events (like a new message arriving in our Pub/Sub topic) and automatically triggering actions in response. In our case, it will detect when a new teaching plan is published and then send a signal to our portal's endpoint, letting it know that it's time to update.

With Eventarc handling the event-driven communication, we can seamlessly connect our planner agent and portal agent, creating a truly dynamic and responsive learning system. It's like having a smart messenger that automatically delivers the latest lesson plans to the right place!

👉In the console head to the Eventarc .

👉Click the "+ CREATE TRIGGER" button.

Configure the Trigger (Basics):

  • Trigger name: plan-topic-trigger
  • Trigger type: Google sources
  • Event provider: Cloud Pub/Sub
  • Event type: google.cloud.pubsub.topic.v1.messagePublished
  • Cloud Pub/Sub Topic: select projects/PROJECT_ID/topics/plan
  • Region: us-central1 .
  • Service account:
    • GRANT the service account with role roles/iam.serviceAccountTokenCreator
    • Use the default value: Default compute service account
  • Event destination: Cloud Run
  • Cloud Run service: aidemy-portal
  • Ignore error message: Permission denied on 'locations/me-central2' (or it may not exist).
  • Service URL path: /new_teaching_plan

"তৈরি করুন" এ ক্লিক করুন।

The Eventarc Triggers page will refresh, and you should now see your newly created trigger listed in the table.

👉Now, access the planner agent using its Service URL to request a new teaching plan.

Run this in the terminal to determine the planner agent Service URL:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

This time try Year 5 , Subject Science , and Add-on Request atoms .

Then, wait a minute or two, again this delay has been introduced due to billing limitation of this lab, under normal condition, there shouldn't be a delay.

Finally, access the student portal using its Service URL.

Run this in the terminal to determine the student portal agent Service URL:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

You should see that the quizzes have been updated and now align with the new teaching plan you just generated! This demonstrates the successful integration of Eventarc in the Aidemy system!

Aidemy-celebrate

অভিনন্দন! You've successfully built a multi-agent system on Google Cloud, leveraging event-driven architecture for enhanced scalability and flexibility! You've laid a solid foundation, but there's even more to explore. To delve deeper into the real benefits of this architecture, discover the power of Gemini 2's multimodal Live API, and learn how to implement single-path orchestration with LangGraph, feel free to continue on to the next two chapters.

12. OPTIONAL: Audio Recaps with Gemini

Gemini can understand and process information from various sources, like text, images, and even audio, opening up a whole new range of possibilities for learning and content creation. Gemini's ability to "see," "hear," and "read" truly unlocks creative and engaging user experiences.

Beyond just creating visuals or text, another important step in learning is effective summarization and recap. Think about it: how often do you remember a catchy song lyric more easily than something you read in a textbook? Sound can be incredibly memorable! That's why we're going to leverage Gemini's multimodal capabilities to generate audio recaps of our teaching plans. This will provide students with a convenient and engaging way to review material, potentially boosting retention and comprehension through the power of auditory learning.

Live API Overview

We need a place to store the generated audio files. Cloud Storage provides a scalable and reliable solution.

👉Head to the Storage in the console. Click on "Buckets" in the left-hand menu. Click on the "+ CREATE" button at the top.

👉Configure your new bucket:

  • bucket name: aidemy-recap-UNIQUE_NAME .
    • IMPORTANT : Ensure you define a unique bucket name that begins with aidemy-recap- . This unique prefix is crucial for avoiding naming conflicts when creating your Cloud Storage bucket.
  • region: us-central1 .
  • Storage class: "Standard". Standard is suitable for frequently accessed data.
  • Access control: Leave the default "Uniform" access control selected. This provides consistent, bucket-level access control.
  • Advanced options: For this workshop, the default settings are usually sufficient.

Click the CREATE button to create your bucket.

  • You may see a pop up about public access prevention. Leave the "Enforce public access prevention on this bucket" box checked and click Confirm .

You will now see your newly created bucket in the Buckets list. Remember your bucket name, you'll need it later.

👉In the Cloud Code Editor's terminal, run the following commands to grant the service account access to the bucket:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

👉In the Cloud Code Editor, open audio.py inside the courses folder. Paste the following code to the end of the file:

config = LiveConnectConfig(
   
response_modalities=["AUDIO"],
   
speech_config=SpeechConfig(
       
voice_config=VoiceConfig(
           
prebuilt_voice_config=PrebuiltVoiceConfig(
               
voice_name="Charon",
           
)
       
)
   
),
)

async def process_weeks(teaching_plan: str):
   
region = "us-east5" #To workaround onRamp quota limits
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
   
clientAudio = genai.Client(vertexai=True, project=PROJECT_ID, location="us-central1")
   
async with clientAudio.aio.live.connect(
       
model=MODEL_ID,
       
config=config,
   
) as session:
       
for week in range(1, 4):  
           
response = client.models.generate_content(
               
model="gemini-2.0-flash-001",
               
contents=f"Given the following teaching plan: {teaching_plan}, Extrace content plan for week {week}. And return just the plan, nothingh else  " # Clarified prompt
           
)

           
prompt = f"""
                Assume you are the instructor.  
                Prepare a concise and engaging recap of the key concepts and topics covered.
                This recap should be suitable for generating a short audio summary for students.
                Focus on the most important learnings and takeaways, and frame it as a direct address to the students.  
                Avoid overly formal language and aim for a conversational tone, tell a few jokes.
               
                Teaching plan: {response.text} """
           
print(f"prompt --->{prompt}")

           
await session.send(input=prompt, end_of_turn=True)
           
with open(f"temp_audio_week_{week}.raw", "wb") as temp_file:
               
async for message in session.receive():
                   
if message.server_content.model_turn:
                       
for part in message.server_content.model_turn.parts:
                           
if part.inline_data:
                               
temp_file.write(part.inline_data.data)
                           
           
data, samplerate = sf.read(f"temp_audio_week_{week}.raw", channels=1, samplerate=24000, subtype='PCM_16', format='RAW')
           
sf.write(f"course-week-{week}.wav", data, samplerate)
       
           
storage_client = storage.Client()
           
bucket = storage_client.bucket(BUCKET_NAME)
           
blob = bucket.blob(f"course-week-{week}.wav")  # Or give it a more descriptive name
           
blob.upload_from_filename(f"course-week-{week}.wav")
           
print(f"Audio saved to GCS: gs://{BUCKET_NAME}/course-week-{week}.wav")
   
await session.close()

 
def breakup_sessions(teaching_plan: str):
   
asyncio.run(process_weeks(teaching_plan))
  • Streaming Connection : First, a persistent connection is established with the Live API endpoint. Unlike a standard API call where you send a request and get a response, this connection remains open for a continuous exchange of data.
  • Configuration Multimodal : Use configuration to specifying what type of output you want (in this case, audio), and you can even specify what parameters you'd like to use (eg, voice selection, audio encoding)
  • Asynchronous Processing : This API works asynchronously, meaning it doesn't block the main thread while waiting for the audio generation to complete. By processing data in real-time and sending the output in chunks, it provides a near-instantaneous experience.

Now, the key question is: when should this audio generation process run? Ideally, we want the audio recaps to be available as soon as a new teaching plan is created. Since we've already implemented an event-driven architecture by publishing the teaching plan to a Pub/Sub topic, we can simply subscribe to that topic.

However, we don't generate new teaching plans very often. It wouldn't be efficient to have an agent constantly running and waiting for new plans. That's why it makes perfect sense to deploy this audio generation logic as a Cloud Run Function.

By deploying it as a function, it remains dormant until a new message is published to the Pub/Sub topic. When that happens, it automatically triggers the function, which generates the audio recaps and stores them in our bucket.

👉Under the courses folder in main.py file, this file defines the Cloud Run Function that will be triggered when a new teaching plan is available. It receives the plan and initiates the audio recap generation. Add the following code snippet to the end of the file.

@functions_framework.cloud_event
def process_teaching_plan(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")
   
time.sleep(60)
   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):  # Check for base64 encoding
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan') # Get the teaching plan
       
elif 'teaching_plan' in cloud_event.data: # No base64
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found") # Handle error explicitly

       
#Load the teaching_plan as string and from cloud event, call audio breakup_sessions
       
breakup_sessions(teaching_plan)

       
return "Teaching plan processed successfully", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error processing teaching plan: {e}")
       
return "Error processing teaching plan", 500

@functions_framework.cloud_event : This decorator marks the function as a Cloud Run Function that will be triggered by CloudEvents.

Testing locally

👉We'll run this in a virtual environment and install the necessary Python libraries for the Cloud Run function.

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉The Cloud Run Function emulator allows us to test our function locally before deploying it to Google Cloud. Start a local emulator by running:

functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py

👉While the emulator is running, you can send test CloudEvents to the emulator to simulate a new teaching plan being published. In a new terminal:

Two terminal

👉Run:

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

Rather than staring blankly while waiting for the response, switch over to the other Cloud Shell terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

Back in the 2nd terminal you should see it should returned OK .

👉You'll verify Data in bucket, go to Cloud Storage and select the "Bucket" tab and then the aidemy-recap-UNIQUE_NAME

বালতি

👉In the terminal running the emulator, type ctrl+c to exit. And close the second terminal. And close the second terminal. and run deactivate to exit the virtual environment.

deactivate

Deploying to Google Cloud

স্থাপনার ওভারভিউ 👉After testing locally, it's time to deploy the course agent to Google Cloud. In the terminal, run these commands:

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud functions deploy courses-agent \
 
--region=us-central1 \
 
--gen2 \
 
--source=. \
 
--runtime=python312 \
 
--trigger-topic=plan \
 
--entry-point=process_teaching_plan \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

Verify deployment by going Cloud Run in the Google Cloud Console.You should see a new service named courses-agent listed.

Cloud Run List

To check the trigger configuration, click on the courses-agent service to view its details. Go to the "TRIGGERS" tab.

You should see a trigger configured to listen for messages published to the plan topic.

Cloud Run Trigger

Finally, let's see it running end to end.

👉We need to configure the portal agent so it knows where to find the generated audio files. টার্মিনালে, চালান:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Try generating a new teaching plan using the planner agent web page. It might take a few minutes to start, don't be alarmed, it's a serverless service.

To access the planner agent, get its Service URL by running this in the terminal:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep planner

After generating the new plan, wait 2-3 minutes for the audio to be generated, again this will take a few more minutes due to billing limitation with this lab account.

You can monitor whether the courses-agent function has received the teaching plan by checking the function's "TRIGGERS" tab. Refresh the page periodically; you should eventually see that the function has been invoked. If the function hasn't been invoked after more than 2 minutes, you can try generating the teaching plan again. However, avoid generating plans repeatedly in quick succession, as each generated plan will be sequentially consumed and processed by the agent, potentially creating a backlog.

Trigger Observe

👉Visit the portal and click on "Courses". You should see three cards, each displaying an audio recap. To find the URL of your portal agent:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep portal

Click "play" on each course to ensure the audio recaps are aligned with the teaching plan you just generated! Portal Courses

Exit the virtual environment.

deactivate

13. OPTIONAL: Role-Based collaboration with Gemini and DeepSeek

Having multiple perspectives is invaluable, especially when crafting engaging and thoughtful assignments. We'll now build a multi-agent system that leverages two different models with distinct roles, to generate assignments: one promotes collaboration, and the other encourages self-study. We'll use a "single-shot" architecture, where the workflow follows a fixed route.

Gemini Assignment Generator

মিথুন ওভারভিউ We'll start by setting up the Gemini function to generate assignments with a collaborative emphasis. Edit the gemini.py file located in the assignment folder.

👉Paste the following code to the end of the gemini.py file:

def gen_assignment_gemini(state):
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"---------------gen_assignment_gemini")
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        You are an instructor

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {state["teaching_plan"]}
        """
   
)

   
print(f"---------------gen_assignment_gemini answer {response.text}")
   
   
state["model_one_assignment"] = response.text
   
   
return state


import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

It uses the Gemini model to generate assignments.

We are ready to test the Gemini Agent.

👉Run these commands in the terminal to setup the environment:

cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉You can run to test it:

python gemini.py

You should see an assignment that has more group work in the output. The assert test at the end will also output the results.

Here are some engaging and practical assignments for each week, designed to build progressively upon the teaching plan's objectives:

**Week 1: Exploring the World of 2D Shapes**

* **Learning Objectives Assessed:**
   
* Identify and name basic 2D shapes (squares, rectangles, triangles, circles).
   
* .....

* **Description:**
   
* **Shape Scavenger Hunt:** Students will go on a scavenger hunt in their homes or neighborhoods, taking pictures of objects that represent different 2D shapes. They will then create a presentation or poster showcasing their findings, classifying each shape and labeling its properties (e.g., number of sides, angles, etc.).
   
* **Triangle Trivia:** Students will research and create a short quiz or presentation about different types of triangles, focusing on their properties and real-world examples.
   
* **Angle Exploration:** Students will use a protractor to measure various angles in their surroundings, such as corners of furniture, windows, or doors. They will record their measurements and create a chart categorizing the angles as right, acute, or obtuse.
....

**Week 2: Delving into the World of 3D Shapes and Symmetry**

* **Learning Objectives Assessed:**
   
* Identify and name basic 3D shapes.
   
* ....

* **Description:**
   
* **3D Shape Construction:** Students will work in groups to build 3D shapes using construction paper, cardboard, or other materials. They will then create a presentation showcasing their creations, describing the number of faces, edges, and vertices for each shape.
   
* **Symmetry Exploration:** Students will investigate the concept of symmetry by creating a visual representation of various symmetrical objects (e.g., butterflies, leaves, snowflakes) using drawing or digital tools. They will identify the lines of symmetry and explain their findings.
   
* **Symmetry Puzzles:** Students will be given a half-image of a symmetrical figure and will be asked to complete the other half, demonstrating their understanding of symmetry. This can be done through drawing, cut-out activities, or digital tools.

**Week 3: Navigating Position, Direction, and Problem Solving**

* **Learning Objectives Assessed:**
   
* Describe position using coordinates in the first quadrant.
   
* ....

* **Description:**
   
* **Coordinate Maze:** Students will create a maze using coordinates on a grid paper. They will then provide directions for navigating the maze using a combination of coordinate movements and translation/reflection instructions.
   
* **Shape Transformations:** Students will draw shapes on a grid paper and then apply transformations such as translation and reflection, recording the new coordinates of the transformed shapes.
   
* **Geometry Challenge:** Students will solve real-world problems involving perimeter, area, and angles. For example, they could be asked to calculate the perimeter of a room, the area of a garden, or the missing angle in a triangle.
....

Stop with ctl+c , and to clean up the test code. REMOVE the following code from gemini.py

import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

Configure the DeepSeek Assignment Generator

While cloud-based AI platforms are convenient, self-hosting LLMs can be crucial for protecting data privacy and ensuring data sovereignty. We'll deploy the smallest DeepSeek model (1.5B parameters) on a Cloud Compute Engine instance. There are other ways like hosting it on Google's Vertex AI platform or hosting it on your GKE instance, but since this is just a workshop on AI agents, and I don't want to keep you here forever, let's just use the most simplest way. But if you are interested and want to dig into other options, take a look at deepseek-vertexai.py file under assignment folder, where it provides an sample code of how to interact with models deployed on VertexAI.

Deepseek Overview

👉Run this command in the terminal to create a self-hosted LLM platform Ollama:

cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
   
--image-family=ubuntu-2204-lts \
   
--image-project=ubuntu-os-cloud \
   
--machine-type=e2-standard-4 \
   
--zone=us-central1-a \
   
--metadata-from-file startup-script=startup.sh \
   
--boot-disk-size=50GB \
   
--tags=ollama \
   
--scopes=https://www.googleapis.com/auth/cloud-platform

To verify the Compute Engine instance is running:

Navigate to Compute Engine > "VM instances" in the Google Cloud Console. You should see the ollama-instance listed with a green check mark indicating that it's running. If you can't see it, make sure the zone is us-central1. If it's not, you may need to search for it.

Compute Engine List

👉We'll install the smallest DeepSeek model and test it, back in the Cloud Shell Editor, in a New terminal, run following command to ssh into the GCE instance.

gcloud compute ssh ollama-instance --zone=us-central1-a

Upon establishing the SSH connection, you may be prompted with the following:

"Do you want to continue (Y/n)?"

Simply type Y (case-insensitive) and press Enter to proceed.

Next, you might be asked to create a passphrase for the SSH key. If you prefer not to use a passphrase, just press Enter twice to accept the default (no passphrase).

👉Now you are in the virutal machine, pull the smallest DeepSeek R1 model, and test if it works?

ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"

👉Exit the GCE instance enter following in the ssh terminal:

exit

👉Next, setup the network policy, so other services can access the LLM, please limit the access to the instance if you want to do this for production, either implement security login for the service or restrict IP access. চালান:

gcloud compute firewall-rules create allow-ollama-11434 \
   
--allow=tcp:11434 \
   
--target-tags=ollama \
   
--description="Allow access to Ollama on port 11434"

👉To verify if your firewall policy is working correctly, try running:

export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
curl -X POST "${OLLAMA_HOST}/api/generate" \
     
-H "Content-Type: application/json" \
     
-d '{
         
"prompt": "Hello, what are you?",
         
"model": "deepseek-r1:1.5b",
         
"stream": false
       
}'

Next, we'll work on the Deepseek function in the assignment agent to generate assignments with individual work emphasis.

👉Edit deepseek.py under assignment folder add following snippet to the end:

def gen_assignment_deepseek(state):
   
print(f"---------------gen_assignment_deepseek")

   
template = """
        You are an instructor who favor student to focus on individual work.

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {teaching_plan}
        """

   
   
prompt = ChatPromptTemplate.from_template(template)

   
model = OllamaLLM(model="deepseek-r1:1.5b",
                   
base_url=OLLAMA_HOST)

   
chain = prompt | model


   
response = chain.invoke({"teaching_plan":state["teaching_plan"]})
   
state["model_two_assignment"] = response
   
   
return state

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

👉let's test it by running:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
python deepseek.py

You should see an assignment that has more self study work.

**Assignment Plan for Each Week**

---

### **Week 1: 2D Shapes and Angles**
- **Week Title:** "Exploring 2D Shapes"
Assign students to research and present on various 2D shapes. Include a project where they create models using straws and tape for triangles, draw quadrilaterals with specific measurements, and compare their properties.

### **Week 2: 3D Shapes and Symmetry**
Assign students to create models or nets for cubes and cuboids. They will also predict how folding these nets form the 3D shapes. Include a project where they identify symmetrical properties using mirrors or folding techniques.

### **Week 3: Position, Direction, and Problem Solving**

Assign students to use mirrors or folding techniques for reflections. Include activities where they measure angles, use a protractor, solve problems involving perimeter/area, and create symmetrical designs.
....

👉Stop the ctl+c , and to clean up the test code. REMOVE the following code from deepseek.py

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

Now, we'll use the same gemini model to combine both assignments into a new one. Edit the gemini.py file located in the assignment folder.

👉Paste the following code to the end of the gemini.py file:

def combine_assignments(state):
   
print(f"---------------combine_assignments ")
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        Look at all the proposed assignment so far {state["model_one_assignment"]} and {state["model_two_assignment"]}, combine them and come up with a final assignment for student.
        """
   
)

   
state["final_assignment"] = response.text
   
   
return state

To combine the strengths of both models, we'll orchestrate a defined workflow using LangGraph. This workflow consists of three steps: first, the Gemini model generates an assignment focused on collaboration; second, the DeepSeek model generates an assignment emphasizing individual work; finally, Gemini synthesizes these two assignments into a single, comprehensive assignment. Because we predefine the sequence of steps without LLM decision-making, this constitutes a single-path, user-defined orchestration.

Langraph combine overview

👉Paste the following code to the end of the main.py file under assignment folder:

def create_assignment(teaching_plan: str):
   
print(f"create_assignment---->{teaching_plan}")
   
builder = StateGraph(State)
   
builder.add_node("gen_assignment_gemini", gen_assignment_gemini)
   
builder.add_node("gen_assignment_deepseek", gen_assignment_deepseek)
   
builder.add_node("combine_assignments", combine_assignments)
   
   
builder.add_edge(START, "gen_assignment_gemini")
   
builder.add_edge("gen_assignment_gemini", "gen_assignment_deepseek")
   
builder.add_edge("gen_assignment_deepseek", "combine_assignments")
   
builder.add_edge("combine_assignments", END)

   
graph = builder.compile()
   
state = graph.invoke({"teaching_plan": teaching_plan})

   
return state["final_assignment"]



import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

👉To initially test the create_assignment function and confirm that the workflow combining Gemini and DeepSeek is functional, run the following command:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py

You should see something that combine both models with their individual perspective for student study and also for student group works.

**Tasks:**

1. **Clue Collection:** Gather all the clues left by the thieves. These clues will include:
   
* Descriptions of shapes and their properties (angles, sides, etc.)
   
* Coordinate grids with hidden messages
   
* Geometric puzzles requiring transformation (translation, reflection, rotation)
   
* Challenges involving area, perimeter, and angle calculations

2. **Clue Analysis:** Decipher each clue using your geometric knowledge. This will involve:
   
* Identifying the shape and its properties
   
* Plotting coordinates and interpreting patterns on the grid
   
* Solving geometric puzzles by applying transformations
   
* Calculating area, perimeter, and missing angles

3. **Case Report:** Create a comprehensive case report outlining your findings. This report should include:
   
* A detailed explanation of each clue and its solution
   
* Sketches and diagrams to support your explanations
   
* A step-by-step account of how you followed the clues to locate the artifact
   
* A final conclusion about the thieves and their motives

👉Stop the ctl+c , and to clean up the test code. REMOVE the following code from main.py

import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

Generate Assignment.png

To make the assignment generation process automatic and responsive to new teaching plans, we'll leverage the existing event-driven architecture. The following code defines a Cloud Run Function (generate_assignment) that will be triggered whenever a new teaching plan is published to the Pub/Sub topic ' plan '.

👉Add the following code to the end of main.py in the assignment folder:

@functions_framework.cloud_event
def generate_assignment(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")

   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan')
       
elif 'teaching_plan' in cloud_event.data:
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found")

       
assignment = create_assignment(teaching_plan)

       
print(f"Assignment---->{assignment}")

       
#Store the return assignment into bucket as a text file
       
storage_client = storage.Client()
       
bucket = storage_client.bucket(ASSIGNMENT_BUCKET)
       
file_name = f"assignment-{random.randint(1, 1000)}.txt"
       
blob = bucket.blob(file_name)
       
blob.upload_from_string(assignment)

       
return f"Assignment generated and stored in {ASSIGNMENT_BUCKET}/{file_name}", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error generate assignment: {e}")
       
return "Error generate assignment", 500

Testing locally

Before deploying to Google Cloud, it's good practice to test the Cloud Run Function locally. This allows for faster iteration and easier debugging.

First, create a Cloud Storage bucket to store the generated assignment files and grant the service account access to the bucket. Run the following commands in the terminal:

👉 IMPORTANT : Ensure you define a unique ASSIGNMENT_BUCKET name that begins with " aidemy-assignment- ". This unique name is crucial for avoiding naming conflicts when creating your Cloud Storage bucket. (Replace <YOUR_NAME> with any random word)

export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue

👉And run:

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gsutil mb -p $PROJECT_ID -l us-central1 gs://$ASSIGNMENT_BUCKET

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

👉Now, start the Cloud Run Function emulator:

cd ~/aidemy-bootstrap/assignment
functions-framework \
   
--target generate_assignment \
   
--signature-type=cloudevent \
   
--source main.py

👉While the emulator is running in one terminal, open a second terminal in the Cloud Shell. In this second terminal, send a test CloudEvent to the emulator to simulate a new teaching plan being published:

Two terminal

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

Rather than staring blankly while waiting for the response, switch over to the other Cloud Shell terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

The curl command should print "OK" (without a newline, so "OK" may appear on the same line your terminal shell prompt).

To confirm that the assignment was successfully generated and stored, go to the Google Cloud Console and navigate to Storage > "Cloud Storage". Select the aidemy-assignment bucket you created. You should see a text file named assignment-{random number}.txt in the bucket. Click on the file to download it and verify its contents. This verifies that a new file contains new assignment just generated.

12-01-assignment-bucket

👉In the terminal running the emulator, type ctrl+c to exit. And close the second terminal. 👉Also, in the terminal running the emulator, exit the virtual environment.

deactivate

স্থাপনার ওভারভিউ

👉Next, we'll deploy the assignment agent to the cloud

cd ~/aidemy-bootstrap/assignment
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
export PROJECT_ID=$(gcloud config get project)
gcloud functions deploy assignment-agent \
 
--gen2 \
 
--timeout=540 \
 
--memory=2Gi \
 
--cpu=1 \
 
--set-env-vars="ASSIGNMENT_BUCKET=${ASSIGNMENT_BUCKET}" \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
 
--set-env-vars=OLLAMA_HOST=${OLLAMA_HOST} \
 
--region=us-central1 \
 
--runtime=python312 \
 
--source=. \
 
--entry-point=generate_assignment \
 
--trigger-topic=plan

Verify deployment by going to Google Cloud Console, navigate to Cloud Run. You should see a new service named courses-agent listed. 12-03-function-list

With the assignment generation workflow now implemented and tested and deployed, we can move on to the next step: making these assignments accessible within the student portal.

14. OPTIONAL: Role-Based collaboration with Gemini and DeepSeek - Contd.

Dynamic website generation

To enhance the student portal and make it more engaging, we'll implement dynamic HTML generation for assignment pages. The goal is to automatically update the portal with a fresh, visually appealing design whenever a new assignment is generated. This leverages the LLM's coding capabilities to create a more dynamic and interesting user experience.

14-01-generate-html

👉In Cloud Shell Editor, edit the render.py file within the portal folder, replace

def render_assignment_page():
   
return ""

with following code snippet:

def render_assignment_page(assignment: str):
   
try:
       
region=get_next_region()
       
llm = VertexAI(model_name="gemini-2.0-flash-001", location=region)
       
input_msg = HumanMessage(content=[f"Here the assignment {assignment}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                        """
                        As a frontend developer, create HTML to display a student assignment with a creative look and feel. Include the following navigation bar at the top:
                        ```
                        <nav>
                            <a href="/">Home</a>
                            <a href="/quiz">Quizzes</a>
                            <a href="/courses">Courses</a>
                            <a href="/assignment">Assignments</a>
                        </nav>
                        ```
                        Also include these links in the <head> section:
                        ```
                        <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
                        <link rel="preconnect" href="https://fonts.googleapis.com">
                        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                        <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">

                        ```
                        Do not apply inline styles to the navigation bar.
                        The HTML should display the full assignment content. In its CSS, be creative with the rainbow colors and aesthetic.
                        Make it creative and pretty
                        The assignment content should be well-structured and easy to read.
                        respond with JUST the html file
                        """
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)

       
response = response.replace("```html", "")
       
response = response.replace("```", "")
       
with open("templates/assignment.html", "w") as f:
           
f.write(response)


       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"

It uses the Gemini model to dynamically generate HTML for the assignment. It takes the assignment content as input and uses a prompt to instruct Gemini to create a visually appealing HTML page with a creative style.

Next, we'll create an endpoint that will be triggered whenever a new document is added to the assignment bucket:

👉Within the portal folder, edit the app.py file and add the following code within the ## Add your code here" comments , AFTER the new_teaching_plan function:

## Add your code here

def new_teaching_plan():
       
...
       
...
       
...

   
except Exception as e:
       
...
       
...

@app.route('/render_assignment', methods=['POST'])
def render_assignment():
   
try:
       
data = request.get_json()
       
file_name = data.get('name')
       
bucket_name = data.get('bucket')

       
if not file_name or not bucket_name:
           
return jsonify({'error': 'Missing file name or bucket name'}), 400

       
storage_client = storage.Client()
       
bucket = storage_client.bucket(bucket_name)
       
blob = bucket.blob(file_name)
       
content = blob.download_as_text()

       
print(f"File content: {content}")

       
render_assignment_page(content)

       
return jsonify({'message': 'Assignment rendered successfully'})

   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500

## Add your code here

When triggered, it retrieves the file name and bucket name from the request data, downloads the assignment content from Cloud Storage, and calls the render_assignment_page function to generate the HTML.

👉We'll go ahead and run it locally:

cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py

👉From the "Web preview" menu at the top of the Cloud Shell window, select "Preview on port 8080". This will open your application in a new browser tab. Navigate to the Assignment link in the navigation bar. You should see a blank page at this point, which is expected behavior since we haven't yet established the communication bridge between the assignment agent and the portal to dynamically populate the content.

14-02-deployment-overview

o ahead and stop the script by pressing Ctrl+C .

👉To incorporate these changes and deploy the updated code, rebuild and push the portal agent image:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

👉After pushing the new image, redeploy the Cloud Run service. Run the following script to force the Cloud Run update:

export PROJECT_ID=$(gcloud config get project)
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Now, we'll deploy an Eventarc trigger that listens for any new object created (finalized) in the assignment bucket. This trigger will automatically invoke the /render_assignment endpoint on the portal service when a new assignment file is created.

export PROJECT_ID=$(gcloud config get project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$(gcloud storage service-agent --project $PROJECT_ID)" \
 
--role="roles/pubsub.publisher"
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud eventarc triggers create portal-assignment-trigger \
--location=us-central1 \
--service-account=$SERVICE_ACCOUNT_NAME \
--destination-run-service=aidemy-portal \
--destination-run-region=us-central1 \
--destination-run-path="/render_assignment" \
--event-filters="bucket=$ASSIGNMENT_BUCKET" \
--event-filters="type=google.cloud.storage.object.v1.finalized"

To verify that the trigger was created successfully, navigate to the Eventarc Triggers page in the Google Cloud Console. You should see portal-assignment-trigger listed in the table. Click on the trigger name to view its details. Assignment Trigger

It may take up to 2-3 minutes for the new trigger to become active.

To see the dynamic assignment generation in action, run the following command to find the URL of your planner agent (if you don't have it handy):

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

Find the URL of your portal agent:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

In the planner agent, generate a new teaching plan.

13-02-assignment

After a few minutes (to allow for the audio generation, assignment generation, and HTML rendering to complete), navigate to the student portal.

👉Click on the "Assignment" link in the navigation bar. You should see a newly created assignment with a dynamically generated HTML. Each time a teaching plan is generated it should be a dynamic assignment.

13-02-assignment

Congratulations on completing the Aidemy multi-agent system ! You've gained practical experience and valuable insights into:

  • The benefits of multi-agent systems, including modularity, scalability, specialization, and simplified maintenance.
  • The importance of event-driven architectures for building responsive and loosely coupled applications.
  • The strategic use of LLMs, matching the right model to the task and integrating them with tools for real-world impact.
  • Cloud-native development practices using Google Cloud services to create scalable and reliable solutions.
  • The importance of considering data privacy and self-hosting models as an alternative to vendor solutions.

You now have a solid foundation for building sophisticated AI-powered applications on Google Cloud!

15. চ্যালেঞ্জ এবং পরবর্তী পদক্ষেপ

Congratulations on building the Aidemy multi-agent system! You've laid a strong foundation for AI-powered education. Now, let's consider some challenges and potential future enhancements to further expand its capabilities and address real-world needs:

Interactive Learning with Live Q&A:

  • Challenge: Can you leverage Gemini 2's Live API to create a real-time Q&A feature for students? Imagine a virtual classroom where students can ask questions and receive immediate, AI-powered responses.

Automated Assignment Submission and Grading:

  • Challenge: Design and implement a system that allows students to submit assignments digitally and have them automatically graded by AI, with a mechanism to detect and prevent plagiarism. This challenge presents a great opportunity to explore Retrieval Augmented Generation (RAG) to enhance the accuracy and reliability of the grading and plagiarism detection processes.

aidemy-climb

16. পরিষ্কার করুন

Now that we've built and explored our Aidemy multi-agent system, it's time to clean up our Google Cloud environment.

👉Delete Cloud Run services

gcloud run services delete aidemy-planner --region=us-central1 --quiet
gcloud run services delete aidemy-portal --region=us-central1 --quiet
gcloud run services delete courses-agent --region=us-central1 --quiet
gcloud run services delete book-provider --region=us-central1 --quiet
gcloud run services delete assignment-agent --region=us-central1 --quiet

👉Delete Eventarc trigger

gcloud eventarc triggers delete portal-assignment-trigger --location=us --quiet
gcloud eventarc triggers delete plan-topic-trigger --location=us-central1 --quiet
gcloud eventarc triggers delete portal-assignment-trigger --location=us-central1 --quiet
ASSIGNMENT_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:assignment-agent" --format="value(name)")
COURSES_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:courses-agent" --format="value(name)")
gcloud eventarc triggers delete $ASSIGNMENT_AGENT_TRIGGER --location=us-central1 --quiet
gcloud eventarc triggers delete $COURSES_AGENT_TRIGGER --location=us-central1 --quiet

👉Delete Pub/Sub topic

gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet

👉Delete Cloud SQL instance

gcloud sql instances delete aidemy --quiet

👉Delete Artifact Registry repository

gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet

👉Delete Secret Manager secrets

gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet

👉Delete Compute Engine instance (if created for Deepseek)

gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet

👉Delete the firewall rule for Deepseek instance

gcloud compute firewall-rules delete allow-ollama-11434 --quiet

👉Delete Cloud Storage buckets

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
gsutil rm -r gs://$COURSE_BUCKET_NAME
gsutil rm -r gs://$ASSIGNMENT_BUCKET

aidemy-broom

,
Aidemy:
Building Multi-Agent Systems with LangGraph, EDA, and Generative AI on Google Cloud

এই কোডল্যাব সম্পর্কে

subjectমার্চ ১৩, ২০২৫-এ শেষবার আপডেট করা হয়েছে
account_circleChristina Lin-এর লেখা

1. ভূমিকা

এই যে! So, you're into the idea of agents – little helpers that can get things done for you without you even lifting a finger, right? অসাধারন! But let's be real, one agent isn't always going to cut it, especially when you're tackling bigger, more complex projects. You're probably going to need a whole team of them! That's where multi-agent systems come in.

Agents, when powered by LLMs, give you incredible flexibility compared to old-school hard coding. But, and there's always a but, they come with their own set of tricky challenges. And that's exactly what we're going to dive into in this workshop!

শিরোনাম

Here's what you can expect to learn – think of it as leveling up your agent game:

Building Your First Agent with LangGraph : We'll get our hands dirty building your very own agent using LangGraph, a popular framework. You'll learn how to create tools that connect to databases, tap into the latest Gemini 2 API for some internet searching, and optimize the prompts and response, so your agent can interact with not only LLMs but existing services. We'll also show you how function calling works.

Agent Orchestration, Your Way : We'll explore different ways to orchestrate your agents, from simple straight paths to more complex multi-path scenarios. Think of it as directing the flow of your agent team.

Multi-Agent Systems : You'll discover how to set up a system where your agents can collaborate, and get things done together – all thanks to an event-driven architecture.

LLM Freedom – Use the Best for the Job: We're not stuck on just one LLM! You'll see how to use multiple LLMs, assigning them different roles to boost problem-solving power using cool "thinking models."

Dynamic Content? কোন সমস্যা নেই! : Imagine your agent creating dynamic content that's tailored specifically for each user, in real-time. আমরা আপনাকে দেখাব কিভাবে এটি করতে হবে!

Taking it to the Cloud with Google Cloud : Forget just playing around in a notebook. We'll show you how to architect and deploy your multi-agent system on Google Cloud so it's ready for the real world!

This project will be a good example of how to use all the techniques we talked about.

2. স্থাপত্য

Being a teacher or working in education can be super rewarding, but let's face it, the workload, especially all the prep work, can be challenging! Plus, there's often not enough staff and tutoring can be expensive. That's why we're proposing an AI-powered teaching assistant. This tool can lighten the load for educators and help bridge the gap caused by staff shortages and the lack of affordable tutoring.

Our AI teaching assistant can whip up detailed lesson plans, fun quizzes, easy-to-follow audio recaps, and personalized assignments. This lets teachers focus on what they do best: connecting with students and helping them fall in love with learning.

The system has two sites: one for teachers to create lesson plans for upcoming weeks,

পরিকল্পনাকারী

and one for students to access quizzes, audio recaps, and assignments. পোর্টাল

Alright, let's walk through the architecture powering our teaching assistant, Aidemy. As you can see, we've broken it down into several key components, all working together to make this happen.

স্থাপত্য

Key Architectural Elements and Technologies :

Google Cloud Platform (GCP) : Central to the entire system:

  • Vertex AI: Accesses Google's Gemini LLMs.
  • Cloud Run: Serverless platform for deploying containerized agents and functions.
  • Cloud SQL: PostgreSQL database for curriculum data.
  • Pub/Sub & Eventarc: Foundation of the event-driven architecture, enabling asynchronous communication between components.
  • Cloud Storage: Stores audio recaps and assignment files.
  • Secret Manager: Securely manages database credentials.
  • Artifact Registry: Stores Docker images for the agents.
  • Compute Engine: To deploy self-hosted LLM instead of relying on vendor solutions

LLMs : The "brains" of the system:

  • Google's Gemini models: (Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking, Gemini 1.5-pro) Used for lesson planning, content generation, dynamic HTML creation, quiz explanation and combining the assignments.
  • DeepSeek: Utilized for the specialized task of generating self-study assignments

LangChain & LangGraph : Frameworks for LLM Application Development

  • Facilitates the creation of complex multi-agent workflows.
  • Enables the intelligent orchestration of tools (API calls, database queries, web searches).
  • Implements event-driven architecture for system scalability and flexibility.

In essence, our architecture combines the power of LLMs with structured data and event-driven communication, all running on Google Cloud. This lets us build a scalable, reliable, and effective teaching assistant.

3. আপনি শুরু করার আগে

In the Google Cloud Console , on the project selector page, select or create a Google Cloud project . Make sure that billing is enabled for your Cloud project. Learn how to check if billing is enabled on a project .

👉Click Activate Cloud Shell at the top of the Google Cloud console (It's the terminal shape icon at the top of the Cloud Shell pane), click on the "Open Editor" button (it looks like an open folder with a pencil). This will open the Cloud Shell Code Editor in the window. You'll see a file explorer on the left side.

মেঘের শেল

👉Click on the Cloud Code Sign-in button in the bottom status bar as shown. Authorize the plugin as instructed. If you see Cloud Code - no project in the status bar, select that then in the drop down 'Select a Google Cloud Project' and then select the specific Google Cloud Project from the list of projects that you created.

Login project

👉Open the terminal in the cloud IDE, নতুন টার্মিনাল

👉In the terminal, verify that you're already authenticated and that the project is set to your project ID using the following command:

gcloud auth list

👉And run:

gcloud config set project <YOUR_PROJECT_ID>

👉Run the following command to enable the necessary Google Cloud APIs:

gcloud services enable compute.googleapis.com  \
                       
storage.googleapis.com  \
                       
run.googleapis.com  \
                       
artifactregistry.googleapis.com  \
                       
aiplatform.googleapis.com \
                       
eventarc.googleapis.com \
                       
sqladmin.googleapis.com \
                       
secretmanager.googleapis.com \
                       
cloudbuild.googleapis.com \
                       
cloudresourcemanager.googleapis.com \
                       
cloudfunctions.googleapis.com

This may take a couple of minutes..

Enable Gemini Code Assist in Cloud Shell IDE

Click on the Code Assist button in the on left panel as shown and select one last time the correct Google Cloud project. If you are asked to enable the Cloud AI Companion API, please do so and move forward. Once you've selected your Google Cloud project, ensure that you are able to see that in the Cloud Code status message in the status bar and that you also have Code Assist enabled on the right, in the status bar as shown below:

Enable codeassist

Setting up permission

👉Setup service account permission. In the terminal, run :

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")

echo "Here's your SERVICE_ACCOUNT_NAME $SERVICE_ACCOUNT_NAME"

👉 Grant Permissions. In the terminal, run :

#Cloud Storage (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/storage.objectAdmin"

#Pub/Sub (Publish/Receive):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.publisher"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.subscriber"


#Cloud SQL (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/cloudsql.editor"


#Eventarc (Receive Events):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/iam.serviceAccountTokenCreator"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/eventarc.eventReceiver"

#Vertex AI (User):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/aiplatform.user"

#Secret Manager (Read):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/secretmanager.secretAccessor"

👉Validate result in your IAM console আইএএম কনসোল

👉Run the following commands in the terminal to create a Cloud SQL instance named aidemy . We'll need this later, but since this process can take some time, we'll do it now.

gcloud sql instances create aidemy \
   
--database-version=POSTGRES_14 \
   
--cpu=2 \
   
--memory=4GB \
   
--region=us-central1 \
   
--root-password=1234qwer \
   
--storage-size=10GB \
   
--storage-auto-increase

4. Building the first agent

Before we dive into complex multi-agent systems, we need to establish a fundamental building block: a single, functional agent. In this section, we'll take our first steps by creating a simple "book provider" agent. The book provider agent takes a category as input and uses a Gemini LLM to generate a JSON representation book within that category. It then serves these book recommendations as a REST API endpoint .

Book Provider

👉In another browser tab, open the Google Cloud Console in your web browser,in the navigation menu (☰), go to "Cloud Run". Click the "+ ... WRITE A FUNCTION" button.

ফাংশন তৈরি করুন

👉Next we'll configures the basic settings of the Cloud Run Function:

  • Service name: book-provider
  • Region: us-central1
  • Runtime: Python 3.12
  • Authentication: Allow unauthenticated invocations to Enabled.

👉Leave other settings as default and click Create . This will take you to the source code editor.

You'll see pre-populated main.py and requirements.txt files.

The main.py will contain the business logic of the function, requirements.txt will contain the packages needed.

👉Now we are ready to write some code! But before diving in, let's see if Gemini Code Assist can give us a head start. Return to the Cloud Shell Editor, click on the Gemini Code Assist icon, and paste the following request into the prompt box: মিথুন কোড সহায়তা

Use the functions_framework library to be deployable as an HTTP function. 
Accept a request with category and number_of_book parameters (either in JSON body or query string).
Use langchain and gemini to generate the data for book with fields bookname, author, publisher, publishing_date.
Use pydantic to define a Book model with the fields: bookname (string, description: "Name of the book"), author (string, description: "Name of the author"), publisher (string, description: "Name of the publisher"), and publishing_date (string, description: "Date of publishing").
Use langchain and gemini model to generate book data. the output should follow the format defined in Book model.

The logic should use JsonOutputParser from langchain to enforce output format defined in Book Model.
Have a function get_recommended_books(category) that internally uses langchain and gemini to return a single book object.
The main function, exposed as the Cloud Function, should call get_recommended_books() multiple times (based on number_of_book) and return a JSON list of the generated book objects.
Handle the case where category or number_of_book are missing by returning an error JSON response with a 400 status code.
return a JSON string representing the recommended books. use os library to retrieve GOOGLE_CLOUD_PROJECT env var. Use ChatVertexAI from langchain for the LLM call

Code Assist will then generate a potential solution, providing both the source code and a requirements.txt dependency file.

We encourage you to compare the Code Assist's generated code with the tested, correct solution provided below. This allows you to evaluate the tool's effectiveness and identify any potential discrepancies. While LLMs should never be blindly trusted, Code Assist can be a great tool for rapid prototyping and generating initial code structures, and should be use for a good head start.

Since this is a workshop, we'll proceed with the verified code provided below. However, feel free to experiment with the Code Assist-generated code in your own time to gain a deeper understanding of its capabilities and limitations.

👉Return to the Cloud Run Function's source code editor (in the other browser tab). Carefully replace the existing content of main.py with the code provided below:

import functions_framework
import json
from flask import Flask, jsonify, request
from langchain_google_vertexai import ChatVertexAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
import os

class Book(BaseModel):
   
bookname: str = Field(description="Name of the book")
   
author: str = Field(description="Name of the author")
   
publisher: str = Field(description="Name of the publisher")
   
publishing_date: str = Field(description="Date of publishing")


project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")  

llm = ChatVertexAI(model_name="gemini-2.0-flash-lite-001")

def get_recommended_books(category):
    """
    A simple book recommendation function.

    Args:
        category (str): category

    Returns:
        str: A JSON string representing the recommended books.
    """
   
parser = JsonOutputParser(pydantic_object=Book)
   
question = f"Generate a random made up book on {category} with bookname, author and publisher and publishing_date"

   
prompt = PromptTemplate(
       
template="Answer the user query.\n{format_instructions}\n{query}\n",
       
input_variables=["query"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"query": question})

   
return  json.dumps(response)
   

@functions_framework.http
def recommended(request):
   
request_json = request.get_json(silent=True) # Get JSON data
   
if request_json and 'category' in request_json and 'number_of_book' in request_json:
       
category = request_json['category']
       
number_of_book = int(request_json['number_of_book'])
   
elif request.args and 'category' in request.args and 'number_of_book' in request.args:
       
category = request.args.get('category')
       
number_of_book = int(request.args.get('number_of_book'))

   
else:
       
return jsonify({'error': 'Missing category or number_of_book parameters'}), 400


   
recommendations_list = []
   
for i in range(number_of_book):
       
book_dict = json.loads(get_recommended_books(category))
       
print(f"book_dict=======>{book_dict}")
   
       
recommendations_list.append(book_dict)

   
   
return jsonify(recommendations_list)

👉Replace the contents of requirements.txt with the following:

functions-framework==3.*
google-genai==1.0.0
flask==3.1.0
jsonify==0.5
langchain_google_vertexai==2.0.13
langchain_core==0.3.34
pydantic==2.10.5

👉we'll set the Function entry point : recommended

03-02-function-create.png

👉Click SAVE AND DEPLOY . to deploy the Function. Wait for the deployment process to complete. The Cloud Console will display the status. এতে কয়েক মিনিট সময় লাগতে পারে।

alt পাঠ্য 👉Once deployed, go back in the cloud shell editor, in the terminal run:

export PROJECT_ID=$(gcloud config get project)
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

curl -X POST -H "Content-Type: application/json" -d '{"category": "Science Fiction", "number_of_book": 2}' $BOOK_PROVIDER_URL

It should show some book data in JSON format.

[
 
{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},
 
{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}
]

অভিনন্দন! You have successfully deployed a Cloud Run Function. This is one of the services we will be integrating when developing our Aidemy agent.

5. Building Tools: Connecting Agents to RESTFUL service and Data

Let's go ahead and download the Bootstrap Skeleton Project, make sure you are in the Cloud Shell Editor. In the terminal run,

git clone https://github.com/weimeilin79/aidemy-bootstrap.git

After running this command, a new folder named aidemy-bootstrap will be created in your Cloud Shell environment.

In the Cloud Shell Editor's Explorer pane (usually on the left side), you should now see the folder that was created when you cloned the Git repository aidemy-bootstrap . Open the root folder of your project in the Explorer. You'll find a planner subfolder within it, open that as well. প্রকল্প এক্সপ্লোরার

Let's start building the tools our agents will use to become truly helpful. As you know, LLMs are excellent at reasoning and generating text, but they need access to external resources to perform real-world tasks and provide accurate, up-to-date information. Think of these tools as the agent's "Swiss Army knife," giving it the ability to interact with the world.

When building an agent, it's easy to fall into hard-coding a ton of details. This creates an agent that is not flexible. Instead, by creating and using tools, the agent has access to external logic or systems which gives it the benefits of both the LLM and traditional programming.

In this section, we'll create the foundation for the planner agent, which teachers will use to generate lesson plans. Before the agent starts generating a plan, we want to set boundaries by providing more details on the subject and topic. We'll build three tools:

  1. Restful API Call: Interacting with a pre-existing API to retrieve data.
  2. Database Query: Fetching structured data from a Cloud SQL database.
  3. Google Search: Accessing real-time information from the web.

Fetching Book Recommendations from an API

First, let's create a tool that retrieves book recommendations from the book-provider API we deployed in the previous section. This demonstrates how an agent can leverage existing services.

Recommend book

In the Cloud Shell Editor, open the aidemy-bootstrap project that you cloned in the previous section.

👉Edit the book.py in the planner folder, and paste the following code at the end of the file:

def recommend_book(query: str):
    """
    Get a list of recommended book from an API endpoint
   
    Args:
        query: User's request string
    """

   
region = get_next_region();
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)

   
query = f"""The user is trying to plan a education course, you are the teaching assistant. Help define the category of what the user requested to teach, respond the categroy with no more than two word.

    user request:   {query}
    """
   
print(f"-------->{query}")
   
response = llm.invoke(query)
   
print(f"CATEGORY RESPONSE------------>: {response}")
   
   
# call this using python and parse the json back to dict
   
category = response.strip()
   
   
headers = {"Content-Type": "application/json"}
   
data = {"category": category, "number_of_book": 2}

   
books = requests.post(BOOK_PROVIDER_URL, headers=headers, json=data)
   
   
return books.text

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

ব্যাখ্যা:

  • recommend_book(query: str) : This function takes a user's query as input.
  • LLM Interaction : It uses the LLM to extract the category from the query. This demonstrates how you can use the LLM to help create parameters for tools.
  • API Call : It makes a POST request to the book-provider API, passing the category and the desired number of books.

👉To test this new function, set the environment variable, run :

cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

👉Install the dependencies and run the code to ensure it works, run:

cd ~/aidemy-bootstrap/planner/
python -m venv env
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
pip install -r requirements.txt
python book.py

Ignore the Git warning pop-up window.

You should see a JSON string containing book recommendations retrieved from the book-provider API. The results are randomly generated. Your books may not be the same, but you should receive two book recommendations in JSON format.

[{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}]

If you see this, the first tool is working correctly!

Instead of explicitly crafting a RESTful API call with specific parameters, we're using natural language ("I'm doing a course..."). The agent then intelligently extracts the necessary parameters (like the category) using NLP, highlighting how the agent leverages natural language understanding to interact with the API.

compare call

👉 Remove the following testing code from the book.py

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

Getting Curriculum Data from a Database

Next, we'll build a tool that fetches structured curriculum data from a Cloud SQL PostgreSQL database. This allows the agent to access a reliable source of information for lesson planning.

create db

Remember the aidemy Cloud SQL instance you've created in previous step? Here's where it will be used.

👉Create a database named aidemy-db in the new instance.

gcloud sql databases create aidemy-db \
   
--instance=aidemy

Let's verify the instance in the Cloud SQL in the Google Cloud Console, You should see a Cloud SQL instance named aidemy listed. Click on the instance name to view its details. In the Cloud SQL instance details page, click on "SQL Studio" in the left-hand navigation menu. এটি একটি নতুন ট্যাব খুলবে।

Click to connect to the database. Sign in to the SQL Studio

Select aidemy-db as the database. enter postgres as user and 1234qwer as the password . sql studio sign in

👉In the SQL Studio query editor, paste the following SQL code:

CREATE TABLE curriculums (
   
id SERIAL PRIMARY KEY,
   
year INT,
   
subject VARCHAR(255),
   
description TEXT
);

-- Inserting detailed curriculum data for different school years and subjects
INSERT INTO curriculums (year, subject, description) VALUES
-- Year 5
(5, 'Mathematics', 'Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.'),
(5, 'English', 'Developing reading comprehension, creative writing, and basic grammar, with a focus on storytelling and poetry.'),
(5, 'Science', 'Exploring basic physics, chemistry, and biology concepts, including forces, materials, and ecosystems.'),
(5, 'Computer Science', 'Basic coding concepts using block-based programming and an introduction to digital literacy.'),

-- Year 6
(6, 'Mathematics', 'Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.'),
(6, 'English', 'Introduction to persuasive writing, character analysis, and deeper comprehension of literary texts.'),
(6, 'Science', 'Forces and motion, the human body, and introductory chemical reactions with hands-on experiments.'),
(6, 'Computer Science', 'Introduction to algorithms, logical reasoning, and basic text-based programming (Python, Scratch).'),

-- Year 7
(7, 'Mathematics', 'Algebraic expressions, geometry, and introduction to statistics and probability.'),
(7, 'English', 'Analytical reading of classic and modern literature, essay writing, and advanced grammar skills.'),
(7, 'Science', 'Introduction to cells and organisms, chemical reactions, and energy transfer in physics.'),
(7, 'Computer Science', 'Building on programming skills with Python, introduction to web development, and cyber safety.');

This SQL code creates a table named curriculums and inserts some sample data. Click Run to execute the SQL code. You should see a confirmation message indicating that the commands were executed successfully.

👉Expand the explorer, find the newly created table and click query . It should open a new editor tab with SQL generated for you,

sql studio select table

SELECT * FROM
 
"public"."curriculums" LIMIT 1000;

👉Click Run .

The results table should display the rows of data you inserted in the previous step, confirming that the table and data were created correctly.

Now that you have successfully created a database with populated sample curriculum data, we'll build a tool to retrieve it.

👉In the Cloud Code Editor, edit file curriculums.py in the aidemy-bootstrap folder and paste the following code at the end of the file:

def connect_with_connector() -> sqlalchemy.engine.base.Engine:

   
db_user = os.environ["DB_USER"]
   
db_pass = os.environ["DB_PASS"]
   
db_name = os.environ["DB_NAME"]

   
encoded_db_user = os.environ.get("DB_USER")
   
print(f"--------------------------->db_user: {db_user!r}")  
   
print(f"--------------------------->db_pass: {db_pass!r}")
   
print(f"--------------------------->db_name: {db_name!r}")

   
ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

   
connector = Connector()

   
def getconn() -> pg8000.dbapi.Connection:
       
conn: pg8000.dbapi.Connection = connector.connect(
           
instance_connection_name,
           
"pg8000",
           
user=db_user,
           
password=db_pass,
           
db=db_name,
           
ip_type=ip_type,
       
)
       
return conn

   
pool = sqlalchemy.create_engine(
       
"postgresql+pg8000://",
       
creator=getconn,
       
pool_size=2,
       
max_overflow=2,
       
pool_timeout=30,  # 30 seconds
       
pool_recycle=1800,  # 30 minutes
   
)
   
return pool



def init_connection_pool() -> sqlalchemy.engine.base.Engine:
   
   
return (
       
connect_with_connector()
   
)

   
raise ValueError(
       
"Missing database connection type. Please define one of INSTANCE_HOST, INSTANCE_UNIX_SOCKET, or INSTANCE_CONNECTION_NAME"
   
)

def get_curriculum(year: int, subject: str):
    """
    Get school curriculum
   
    Args:
        subject: User's request subject string
        year: User's request year int
    """
   
try:
       
stmt = sqlalchemy.text(
           
"SELECT description FROM curriculums WHERE year = :year AND subject = :subject"
       
)

       
with db.connect() as conn:
           
result = conn.execute(stmt, parameters={"year": year, "subject": subject})
           
row = result.fetchone()
       
if row:
           
return row[0]  
       
else:
           
return None  

   
except Exception as e:
       
print(e)
       
return None

db = init_connection_pool()

ব্যাখ্যা:

  • Environment Variables : The code retrieves database credentials and connection information from environment variables (more on this below).
  • connect_with_connector() : This function uses the Cloud SQL Connector to establish a secure connection to the database.
  • get_curriculum(year: int, subject: str) : This function takes the year and subject as input, queries the curriculums table, and returns the corresponding curriculum description.

👉Before we can run the code, we must set some environment variables, in the terminal, run:

export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉To test add the following code to the end of curriculums.py :

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉Run the code:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py

You should see the curriculum description for 6th-grade Mathematics printed to the console.

Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.

If you see the curriculum description, the database tool is working correctly! Go ahead and stop the script by pressing Ctrl+C .

👉 Remove the following testing code from the curriculums.py

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉Exit virtual environment, in terminal run:

deactivate

6. Building Tools: Access real-time information from the web

Finally, we'll build a tool that uses the Gemini 2 and Google Search integration to access real-time information from the web. This helps the agent stay up-to-date and provide relevant results.

Gemini 2's integration with the Google Search API enhances agent capabilities by providing more accurate and contextually relevant search results. This allows agents to access up-to-date information and ground their responses in real-world data, minimizing hallucinations. The improved API integration also facilitates more natural language queries, enabling agents to formulate complex and nuanced search requests.

অনুসন্ধান করুন

This function takes a search query, curriculum, subject, and year as input and uses the Gemini API and the Google Search tool to retrieve relevant information from the internet. If you look closely, it's using the Google Generative AI SDK to do function calling without using any other framework.

👉Edit search.py in the aidemy-bootstrap folder and paste the following code at the end of the file:

model_id = "gemini-2.0-flash-001"

google_search_tool = Tool(
   
google_search = GoogleSearch()
)

def search_latest_resource(search_text: str, curriculum: str, subject: str, year: int):
    """
    Get latest information from the internet
   
    Args:
        search_text: User's request category   string
        subject: "User's request subject" string
        year: "User's request year"  integer
    """
   
search_text = "%s in the context of year %d and subject %s with following curriculum detail %s " % (search_text, year, subject, curriculum)
   
region = get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"search_latest_resource text-----> {search_text}")
   
response = client.models.generate_content(
       
model=model_id,
       
contents=search_text,
       
config=GenerateContentConfig(
           
tools=[google_search_tool],
           
response_modalities=["TEXT"],
       
)
   
)
   
print(f"search_latest_resource response-----> {response}")
   
return response

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

ব্যাখ্যা:

  • Defining Tool - google_search_tool : Wrapping the GoogleSearch object within a Tool
  • search_latest_resource(search_text: str, subject: str, year: int) : This function takes a search query, subject, and year as input and uses the Gemini API to perform a Google search. Gemini model
  • GenerateContentConfig : Define that it has access to the GoogleSearch tool

The Gemini model internally analyzes the search_text and determines whether it can answer the question directly or if it needs to use the GoogleSearch tool. This is a critical step that happens within the LLM's reasoning process. The model has been trained to recognize situations where external tools are necessary. If the model decides to use the GoogleSearch tool, the Google Generative AI SDK handles the actual invocation. The SDK takes the model's decision and the parameters it generates and sends them to the Google Search API. This part is hidden from the user in the code.

The Gemini model then integrates the search results into its response. It can use the information to answer the user's question, generate a summary, or perform some other task.

👉To test, run the code:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py

You should see the Gemini Search API response containing search results related to "Syllabus for Year 5 Mathematics." The exact output will depend on the search results, but it will be a JSON object with information about the search.

If you see search results, the Google Search tool is working correctly! Go ahead and stop the script by pressing Ctrl+C .

👉And remove the last part in the code.

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

👉Exit virtual environment, in terminal run:

deactivate

অভিনন্দন! You have now built three powerful tools for your planner agent: an API connector, a database connector, and a Google Search tool. These tools will enable the agent to access the information and capabilities it needs to create effective teaching plans.

7. Orchestrating with LangGraph

Now that we have built our individual tools, it's time to orchestrate them using LangGraph. This will allow us to create a more sophisticated "planner" agent that can intelligently decide which tools to use and when, based on the user's request.

LangGraph is a Python library designed to make it easier to build stateful, multi-actor applications using Large Language Models (LLMs). Think of it as a framework for orchestrating complex conversations and workflows involving LLMs, tools, and other agents.

মূল ধারণা:

  • Graph Structure: LangGraph represents your application's logic as a directed graph. Each node in the graph represents a step in the process (eg, a call to an LLM, a tool invocation, a conditional check). Edges define the flow of execution between nodes.
  • State: LangGraph manages the state of your application as it moves through the graph. This state can include variables like the user's input, the results of tool calls, intermediate outputs from LLMs, and any other information that needs to be preserved between steps.
  • Nodes: Each node represents a computation or interaction. তারা হতে পারে:
    • Tool Nodes: Use a tool (eg, perform a web search, query a database)
    • Function Nodes: Execute a Python function.
  • Edges: Connect nodes, defining the flow of execution. তারা হতে পারে:
    • Direct Edges: A simple, unconditional flow from one node to another.
    • Conditional Edges: The flow depends on the outcome of a conditional node.

ল্যাংগ্রাফ

We will use LangGraph to implement the orchestration. Let's edit the aidemy.py file under aidemy-bootstrap folder to define our LangGraph logic.

👉Append follow code to the end of aidemy.py :

tools = [get_curriculum, search_latest_resource, recommend_book]

def determine_tool(state: MessagesState):
   
llm = ChatVertexAI(model_name="gemini-2.0-flash-001", location=get_next_region())
   
sys_msg = SystemMessage(
                   
content=(
                       
f"""You are a helpful teaching assistant that helps gather all needed information.
                            Your ultimate goal is to create a detailed 3-week teaching plan.
                            You have access to tools that help you gather information.  
                            Based on the user request, decide which tool(s) are needed.

                        """
                   
)
               
)

   
llm_with_tools = llm.bind_tools(tools)
   
return {"messages": llm_with_tools.invoke([sys_msg] + state["messages"])}

This function is responsible for taking the current state of the conversation, providing the LLM with a system message, and then asking the LLM to generate a response. The LLM can either respond directly to the user or choose to use one of the available tools.

tools : This list represents the set of tools that the agent has available to it. It contains three tool functions that we defined in the previous steps: get_curriculum , search_latest_resource , and recommend_book . llm.bind_tools(tools) : It "binds" the tools list to the llm object. Binding the tools tells the LLM that these tools are available and provides the LLM with information about how to use them (eg, the names of the tools, the parameters they accept, and what they do).

We will use LangGraph to implement the orchestration.

👉Append following code to the end of aidemy.py :

def prep_class(prep_needs):
   
   
builder = StateGraph(MessagesState)
   
builder.add_node("determine_tool", determine_tool)
   
builder.add_node("tools", ToolNode(tools))
   
   
builder.add_edge(START, "determine_tool")
   
builder.add_conditional_edges("determine_tool",tools_condition)
   
builder.add_edge("tools", "determine_tool")

   
   
memory = MemorySaver()
   
graph = builder.compile(checkpointer=memory)

   
config = {"configurable": {"thread_id": "1"}}
   
messages = graph.invoke({"messages": prep_needs},config)
   
print(messages)
   
for m in messages['messages']:
       
m.pretty_print()
   
teaching_plan_result = messages["messages"][-1].content  


   
return teaching_plan_result

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan")

ব্যাখ্যা:

  • StateGraph(MessagesState) : Creates a StateGraph object. A StateGraph is a core concept in LangGraph. It represents the workflow of your agent as a graph, where each node in the graph represents a step in the process. Think of it as defining the blueprint for how the agent will reason and act.
  • Conditional Edge: Originating from the "determine_tool" node, the tools_condition argument is likely a function that determines which edge to follow based on the output of the determine_tool function. Conditional edges allow the graph to branch based on the LLM's decision about which tool to use (or whether to respond to the user directly). This is where the agent's "intelligence" comes into play – it can dynamically adapt its behavior based on the situation.
  • Loop: Adds an edge to the graph that connects the "tools" node back to the "determine_tool" node. This creates a loop in the graph, allowing the agent to repeatedly use tools until it has gathered enough information to complete the task and provide a satisfactory answer. This loop is crucial for complex tasks that require multiple steps of reasoning and information gathering.

Now, let's test our planner agent to see how it orchestrates the different tools.

This code will run the prep_class function with a specific user input, simulating a request to create a teaching plan for 5th-grade Mathematics in Geometry, using the curriculum, book recommendations, and the latest internet resources.

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Run the code:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py

Watch the log in the terminal. You should see evidence that the agent is calling all three tools (getting the school curriculum, getting book recommendations, and searching for the latest resources) before providing the final teaching plan. This demonstrates that the LangGraph orchestration is working correctly, and the agent is intelligently using all available tools to fulfill the user's request.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
search_text: Geometry
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
subject: Mathematics
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.....) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================
Tool Calls:
 
recommend_book (93b48189-4d69-4c09-a3bd-4e60cdc5f1c6)
 
Call ID: 93b48189-4d69-4c09-a3bd-4e60cdc5f1c6
 
Args:
   
query: Mathematics Geometry Year 5
================================= Tool Message =================================
Name: recommend_book

[{.....}]

================================== Ai Message ==================================

Based on the curriculum outcome, here is a 3-week teaching plan for year 5 Mathematics Geometry:

**Week 1: Introduction to Shapes and Properties**
.........

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉(THIS STEP IS OPTIONAL) replace the testing code with a different prompt, which requires different tools to be called.

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉(THIS STEP IS OPTIONAL, do this ONLY IF you ran the previous step) Run the code again:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py

এই সময় আপনি কি লক্ষ্য করেছেন? Which tools did the agent call? You should see that the agent only calls the search_latest_resource tool this time. This is because the prompt does not specify that it needs the other two tools, and our LLM is smart enough to not call the other tools.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
subject: Mathematics
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
search_text: Geometry
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.......token_count=40, total_token_count=772) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================

Based on the information provided, a 3-week teaching plan for Year 5 Mathematics focusing on Geometry could look like this:

**Week 1:  Introducing 2D Shapes**
........
* Use visuals, manipulatives, and real-world examples to make the learning experience engaging and relevant.

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉 Remove the testing code to keep your aidemy.py file clean (DO NOT SKIP THIS STEP!):

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

With our agent logic now defined, let's launch the Flask web application. This will provide a familiar form-based interface for teachers to interact with the agent. While chatbot interactions are common with LLMs, we're opting for a traditional form submit UI, as it may be more intuitive for many educators.

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Now, start the Web UI.

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py

Look for startup messages in the Cloud Shell terminal output. Flask usually prints messages indicating that it's running and on what port.

Running on http://127.0.0.1:8080
Running on http://127.0.0.1:8080
The application needs to keep running to serve requests.

👉From the "Web preview" menu, choose Preview on port 8080. Cloud Shell will open a new browser tab or window with the web preview of your application.

ওয়েব পেজ

In the application interface, select 5 for Year, select subject Mathematics and type in Geometry in the Add-on Request

Rather than staring blankly while waiting for the response, switch over to the Cloud Editor's terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

👉Stop the script by pressing Ctrl+C in the terminal.

👉Exit the virtual environment:

deactivate

8. Deploying planner agent to the cloud

Build and push image to registry

ওভারভিউ

👉Time to deploy this to the cloud. In the terminal, create an artifacts repository to store the docker image we are going to build.

gcloud artifacts repositories create agent-repository \
   
--repository-format=docker \
   
--location=us-central1 \
   
--description="My agent repository"

You should see Created repository [agent-repository].

👉Run the following command to build the Docker image.

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .

👉We need to retag the image so that it's hosted in Artifact Registry instead of GCR and push the tagged image to Artifact Registry:

export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

Once the push is complete, you can verify that the image is successfully stored in Artifact Registry. Navigate to the Artifact Registry in the Google Cloud Console. You should find the aidemy-planner image within the agent-repository repository. Aidemy planner image

Securing Database Credentials with Secret Manager

To securely manage and access database credentials, we'll use Google Cloud Secret Manager. This prevents hardcoding sensitive information in our application code and enhances security.

👉We'll create individual secrets for the database username, password, and database name. This approach allows us to manage each credential independently. In the terminal run:

gcloud secrets create db-user
printf "postgres" | gcloud secrets versions add db-user --data-file=-

gcloud secrets create db-pass
printf "1234qwer" | gcloud secrets versions add db-pass --data-file=-

gcloud secrets create db-name
printf "aidemy-db" | gcloud secrets versions add db-name --data-file=-

Using Secret Manager is a important step in securing your application and preventing accidental exposure of sensitive credentials. It follows security best practices for cloud deployments.

ক্লাউড রানে স্থাপন করুন

Cloud Run is a fully managed serverless platform that allows you to deploy containerized applications quickly and easily. It abstracts away the infrastructure management, letting you focus on writing and deploying your code. We'll be deploying our planner as a Cloud Run service.

👉In the Google Cloud Console, navigate to " Cloud Run ". Click on DEPLOY CONTAINER and select SERVICE . Configure your Cloud Run service:

Cloud run

  1. Container image : Click "Select" in the URL field. Find the image URL you pushed to Artifact Registry (eg, us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
  2. Service name : aidemy-planner
  3. Region : Select the us-central1 region.
  4. Authentication : For the purpose of this workshop, you can allow "Allow unauthenticated invocations". For production, you'll likely want to restrict access.
  5. Container(s) tab (Expand the Containers, Network):
    • Setting tab:
      • সম্পদ
        • memory : 2GiB
    • Variables & Secrets tab:
      • পরিবেশ পরিবর্তনশীল:
        • Add name: GOOGLE_CLOUD_PROJECT and value: <YOUR_PROJECT_ID>
        • Add name: BOOK_PROVIDER_URL , and set the value to your book-provider function URL, which you can determine using the following command in the terminal:
          gcloud run services describe book-provider \
             
          --region=us-central1 \
             
          --project=$PROJECT_ID \
             
          --format="value(status.url)"
      • Secrets exposed as environment variables:
        • Add name: DB_USER , secret: select db-user and version: latest
        • Add name: DB_PASS , secret: select db-pass and version: latest
        • Add name: DB_NAME , secret: select db-name and version: latest

Set secret

Leave other as default.

👉Click CREATE .

Cloud Run will deploy your service.

Once deployed, click on the service to its detail page, you can find the deployed URL available on the top.

URL

In the application interface, select 7 for the Year, choose Mathematics as the subject, and enter Algebra in the Add-on Request field. This will provide the agent with the necessary context to generate a tailored lesson plan.

অভিনন্দন! You've successfully created a teaching plan using our powerful AI agent. This demonstrates the potential of agents to significantly reduce workload and streamline tasks, ultimately improving efficiency and making life easier for educators.

9. মাল্টি-এজেন্ট সিস্টেম

Now that we've successfully implemented the teaching plan creation tool, let's shift our focus to building the student portal. This portal will provide students with access to quizzes, audio recaps, and assignments related to their coursework. Given the scope of this functionality, we'll leverage the power of multi-agent systems to create a modular and scalable solution.

As we discussed earlier, instead of relying on a single agent to handle everything, a multi-agent system allows us to break down the workload into smaller, specialized tasks, each handled by a dedicated agent. এই পদ্ধতিটি বেশ কয়েকটি মূল সুবিধা প্রদান করে:

Modularity and Maintainability : Instead of creating a single agent that does everything, build smaller, specialized agents with well-defined responsibilities. This modularity makes the system easier to understand, maintain, and debug. When a problem arises, you can isolate it to a specific agent, rather than having to sift through a massive codebase.

Scalability : Scaling a single, complex agent can be a bottleneck. With a multi-agent system, you can scale individual agents based on their specific needs. For example, if one agent is handling a high volume of requests, you can easily spin up more instances of that agent without affecting the rest of the system.

Team Specialization : Think of it like this: you wouldn't ask one engineer to build an entire application from scratch. Instead, you assemble a team of specialists, each with expertise in a particular area. Similarly, a multi-agent system allows you to leverage the strengths of different LLMs and tools, assigning them to agents that are best suited for specific tasks.

Parallel Development : Different teams can work on different agents concurrently, speeding up the development process. Since agents are independent, changes to one agent are less likely to impact other agents.

ইভেন্ট চালিত আর্কিটেকচার

To enable effective communication and coordination between these agents, we'll employ an event-driven architecture. This means that agents will react to "events" happening within the system.

Agents subscribe to specific event types (eg, "teaching plan generated," "assignment created"). When an event occurs, the relevant agents are notified and can react accordingly. This decoupling promotes flexibility, scalability, and real-time responsiveness.

ওভারভিউ

Now, to kick things off, we need a way to broadcast these events. To do this, we will set up a Pub/Sub topic. Let's start by creating a topic called plan .

👉Go to Google Cloud Console pub/sub and click on the "Create Topic" button.

👉Configure the Topic with ID/name plan and uncheck Add a default subscription , leave rest as default and click Create .

The Pub/Sub page will refresh, and you should now see your newly created topic listed in the table. টপিক তৈরি করুন

Now, let's integrate the Pub/Sub event publishing functionality into our planner agent. We'll add a new tool that sends a "plan" event to the Pub/Sub topic we just created. This event will signal to other agents in the system (like those in the student portal) that a new teaching plan is available.

👉Go back to the Cloud Code Editor and open the app.py file located in the planner folder. We will be adding a function that publishes the event. প্রতিস্থাপন:

##ADD SEND PLAN EVENT FUNCTION HERE

সঙ্গে

def send_plan_event(teaching_plan:str):
    """
    Send the teaching event to the topic called plan
   
    Args:
        teaching_plan: teaching plan
    """
   
publisher = pubsub_v1.PublisherClient()
   
print(f"-------------> Sending event to topic plan: {teaching_plan}")
   
topic_path = publisher.topic_path(PROJECT_ID, "plan")

   
message_data = {"teaching_plan": teaching_plan}
   
data = json.dumps(message_data).encode("utf-8")

   
future = publisher.publish(topic_path, data)

   
return f"Published message ID: {future.result()}"

  • send_plan_event : This function takes the generated teaching plan as input, creates a Pub/Sub publisher client, constructs the topic path, converts the teaching plan into a JSON string , publishes the message to the topic.

In the same app.py file

👉Update the prompt to instruct the agent to send the teaching plan event to the Pub/Sub topic after generating the teaching plan. প্রতিস্থাপন করুন

### ADD send_plan_event CALL

নিম্নলিখিত সঙ্গে:

send_plan_event(teaching_plan)

By adding the send_plan_event tool and modifying the prompt, we've enabled our planner agent to publish events to Pub/Sub, allowing other components of our system to react to the creation of new teaching plans. We will now have a functional multi-agent system in the following sections.

10. Empowering Students with On-Demand Quizzes

Imagine a learning environment where students have access to an endless supply of quizzes tailored to their specific learning plans. These quizzes provide immediate feedback, including answers and explanations, fostering a deeper understanding of the material. This is the potential we aim to unlock with our AI-powered quiz portal.

To bring this vision to life, we'll build a quiz generation component that can create multiple-choice questions based on the content of the teaching plan.

ওভারভিউ

👉In the Cloud Code Editor's Explorer pane, navigate to the portal folder. Open the quiz.py file copy and paste the following code to the end of the file.

def generate_quiz_question(file_name: str, difficulty: str, region:str ):
    """Generates a single multiple-choice quiz question using the LLM.
   
    ```json
    {
      "question": "The question itself",
      "options": ["Option A", "Option B", "Option C", "Option D"],
      "answer": "The correct answer letter (A, B, C, or D)"
    }
    ```
    """

   
print(f"region: {region}")
   
# Connect to resourse needed from Google Cloud
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)


   
plan=None
   
#load the file using file_name and read content into string call plan
   
with open(file_name, 'r') as f:
       
plan = f.read()

   
parser = JsonOutputParser(pydantic_object=QuizQuestion)


   
instruction = f"You'll provide one question with difficulty level of {difficulty}, 4 options as multiple choices and provide the anwsers, the quiz needs to be related to the teaching plan {plan}"

   
prompt = PromptTemplate(
       
template="Generates a single multiple-choice quiz question\n {format_instructions}\n  {instruction}\n",
       
input_variables=["instruction"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"instruction": instruction})

   
print(f"{response}")
   
return  response


In the agent it creates a JSON output parser that's specifically designed to understand and structure the LLM's output. It uses the QuizQuestion model we defined earlier to ensure the parsed output conforms to the correct format (question, options, and answer).

👉Execute the following commands in the terminal to set up a virtual environment, install dependencies, and start the agent:

cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py

Use the Cloud Shell's web preview feature to access the running application. Click on the "Quizzes" link, either in the top navigation bar or from the card on the index page. You should see three randomly generated quizzes displayed for the student. These quizzes are based on the teaching plan and demonstrate the power of our AI-powered quiz generation system.

কুইজ

👉To stop the locally running process, press Ctrl+C in the terminal.

Gemini 2 Thinking for Explanations

Okay, so we've got quizzes, which is a great start! But what if students get something wrong? That's where the real learning happens, right? If we can explain why their answer was off and how to get to the correct one, they're way more likely to remember it. Plus, it helps clear up any confusion and boost their confidence.

That's why we're going to bring in the big guns: Gemini 2's "thinking" model! Think of it like giving the AI a little extra time to think things through before explaining. It lets it give more detailed and better feedback.

We want to see if it can help students by assisting, answering and explaining in detail. To test it out, we'll start with a notoriously tricky subject, Calculus.

ওভারভিউ

👉First, head over to the Cloud Code Editor, in answer.py inside the portal folder replace

def answer_thinking(question, options, user_response, answer, region):
   
return ""

with following code snippet:

def answer_thinking(question, options, user_response, answer, region):
   
try:
       
llm = VertexAI(model_name="gemini-2.0-flash-001",location=region)
       
       
input_msg = HumanMessage(content=[f"Here the question{question}, here are the available options {options}, this student's answer {user_response}, whereas the correct answer is {answer}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                       
"You are a helpful teacher trying to teach the student on question, you were given the question and a set of multiple choices "
                       
"what's the correct answer. use friendly tone"
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)
       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"



if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

This is a very simple langchain app where it Initializes the Gemini 2 Flash model, where we are instructing it to act as a helpful teacher and provide explanations

👉Execute the following command in the terminal:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

You should see output similar to the example provided in the original instructions. The current model may not provide as through explanation.

Okay, I see the question and the choices. The question is to evaluate the limit:

lim (x0) [(sin(5x) - 5x) / x^3]

You chose option B, which is -5/3, but the correct answer is A, which is -125/6.

It looks like you might have missed a step or made a small error in your calculations. This type of limit often involves using L'Hôpital's Rule or Taylor series expansion. Since we have the form 0/0, L'Hôpital's Rule is a good way to go! You need to apply it multiple times. Alternatively, you can use the Taylor series expansion of sin(x) which is:
sin(x) = x - x^3/3! + x^5/5! - ...
So, sin(5x) = 5x - (5x)^3/3! + (5x)^5/5! - ...
Then,  (sin(5x) - 5x) = - (5x)^3/3! + (5x)^5/5! - ...
Finally, (sin(5x) - 5x) / x^3 = - 5^3/3! + (5^5 * x^2)/5! - ...
Taking the limit as x approaches 0, we get -125/6.

Keep practicing, you'll get there!

In the answer.py file, replace the model_name from gemini-2.0-flash-001 to gemini-2.0-flash-thinking-exp-01-21 in the answer_thinking function.

This changes the LLM that reasons more, which will help it generate better explanations. And run it again.

👉Run to test the new thinking model:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

Here is an example of the response from the thinking model that is much more thorough and detailed, providing a step-by-step explanation of how to solve the calculus problem. This highlights the power of "thinking" models in generating high-quality explanations. You should see output similar to this:

Hey there! Let's take a look at this limit problem together. You were asked to evaluate:

lim (x0) [(sin(5x) - 5x) / x^3]

and you picked option B, -5/3, but the correct answer is actually A, -125/6. Let's figure out why!

It's a tricky one because if we directly substitute x=0, we get (sin(0) - 0) / 0^3 = (0 - 0) / 0 = 0/0, which is an indeterminate form. This tells us we need to use a more advanced technique like L'Hopital's Rule or Taylor series expansion.

Let's use the Taylor series expansion for sin(y) around y=0. Do you remember it?  It looks like this:

sin(y) = y - y^3/3! + y^5/5! - ...
where 3! (3 factorial) is 3 × 2 × 1 = 6, 5! is 5 × 4 × 3 × 2 × 1 = 120, and so on.

In our problem, we have sin(5x), so we can substitute y = 5x into the Taylor series:

sin(5x) = (5x) - (5x)^3/3! + (5x)^5/5! - ...
sin(5x) = 5x - (125x^3)/6 + (3125x^5)/120 - ...

Now let's plug this back into our limit expression:

[(sin(5x) - 5x) / x^3] =  [ (5x - (125x^3)/6 + (3125x^5)/120 - ...) - 5x ] / x^3
Notice that the '5x' and '-5x' cancel out!  So we are left with:
= [ - (125x^3)/6 + (3125x^5)/120 - ... ] / x^3
Now, we can divide every term in the numerator by x^3:
= -125/6 + (3125x^2)/120 - ...

Finally, let's take the limit as x approaches 0.  As x gets closer and closer to zero, terms with x^2 and higher powers will become very, very small and approach zero.  So, we are left with:
lim (x0) [ -125/6 + (3125x^2)/120 - ... ] = -125/6

Therefore, the correct answer is indeed **A) -125/6**.

It seems like your answer B, -5/3, might have come from perhaps missing a factor somewhere during calculation or maybe using an incorrect simplification. Double-check your steps when you were trying to solve it!

Don't worry, these limit problems can be a bit tricky sometimes! Keep practicing and you'll get the hang of it.  Let me know if you want to go through another similar example or if you have any more questions! 😊


Now that we have confirmed it works, let's use the portal.

👉 REMOVE the following test code from answer.py :

if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

👉Execute the following commands in the terminal to set up a virtual environment, install dependencies, and start the agent:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py

👉Use the Cloud Shell's web preview feature to access the running application. Click on the "Quizzes" link, answer all the quizzes and make sure at least get one answer wrong and click submit

thinking answers

Rather than staring blankly while waiting for the response, switch over to the Cloud Editor's terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

To stop the locally running process, press Ctrl+C in the terminal.

11. OPTIONAL: Orchestrating the Agents with Eventarc

So far, the student portal has been generating quizzes based on a default set of teaching plans. That's helpful, but it means our planner agent and portal's quiz agent aren't really talking to each other. Remember how we added that feature where the planner agent publishes its newly generated teaching plans to a Pub/Sub topic? Now it's time to connect that to our portal agent!

ওভারভিউ

We want the portal to automatically update its quiz content whenever a new teaching plan is generated. To do that, we'll create an endpoint in the portal that can receive these new plans.

👉In the Cloud Code Editor's Explorer pane, navigate to the portal folder. Open the app.py file for editing. Add the follow code in between ## Add your code here :

## Add your code here

@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
   
try:
       
       
# Get data from Pub/Sub message delivered via Eventarc
       
envelope = request.get_json()
       
if not envelope:
           
return jsonify({'error': 'No Pub/Sub message received'}), 400

       
if not isinstance(envelope, dict) or 'message' not in envelope:
           
return jsonify({'error': 'Invalid Pub/Sub message format'}), 400

       
pubsub_message = envelope['message']
       
print(f"data: {pubsub_message['data']}")

       
data = pubsub_message['data']
       
data_str = base64.b64decode(data).decode('utf-8')
       
data = json.loads(data_str)

       
teaching_plan = data['teaching_plan']

       
print(f"File content: {teaching_plan}")

       
with open("teaching_plan.txt", "w") as f:
           
f.write(teaching_plan)

       
print(f"Teaching plan saved to local file: teaching_plan.txt")

       
return jsonify({'message': 'File processed successfully'})


   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500
## Add your code here

Rebuilding and Deploying to Cloud Run

You'll need to update and redeploy both our planner and portal agents to Cloud Run. This ensures they have the latest code and are configured to communicate via events.

স্থাপনার ওভারভিউ

👉First we'll rebuild and push the planner agent image, back in the terminal run:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

👉We'll do the same, build and push the portal agent image:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

In Artifact Registry , you should see both the aidemy-planner and aidemy-portal container images listed.

Container Repo

👉Back in the terminal, run this to update the Cloud Run image for the planner agent:

export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-planner \
   
--region=us-central1 \
   
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner:latest

You should see output similar to this:

OK Deploying... Done.                                                                                                                                                     
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
Done.                                                                                                                                                                    
Service [aidemy-planner] revision [aidemy-planner-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-planner-xxx.us-central1.run.app

Make note of the Service URL; this is the link to your deployed planner agent. If you need to later determine the planner agent Service URL, use this command:

gcloud run services describe aidemy-planner \
   
--region=us-central1 \
   
--format 'value(status.url)'

👉Run this to create the Cloud Run instance for the portal agent

export PROJECT_ID=$(gcloud config get project)
gcloud run deploy aidemy-portal \
 
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal:latest \
 
--region=us-central1 \
 
--platform=managed \
 
--allow-unauthenticated \
 
--memory=2Gi \
 
--cpu=2 \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID}

You should see output similar to this:

Deploying container to Cloud Run service [aidemy-portal] in project [xxxx] region [us-central1]
OK Deploying new service... Done.                                                                                                                                        
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
 
OK Setting IAM Policy...                                                                                                                                                
Done.                                                                                                                                                                    
Service [aidemy-portal] revision [aidemy-portal-xxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-portal-xxxx.us-central1.run.app

Make note of the Service URL; this is the link to your deployed student portal. If you need to later determine the student portal Service URL, use this command:

gcloud run services describe aidemy-portal \
   
--region=us-central1 \
   
--format 'value(status.url)'

Creating the Eventarc Trigger

But here's the big question: how does this endpoint get notified when there's a fresh plan waiting in the Pub/Sub topic? That's where Eventarc swoops in to save the day!

Eventarc acts as a bridge, listening for specific events (like a new message arriving in our Pub/Sub topic) and automatically triggering actions in response. In our case, it will detect when a new teaching plan is published and then send a signal to our portal's endpoint, letting it know that it's time to update.

With Eventarc handling the event-driven communication, we can seamlessly connect our planner agent and portal agent, creating a truly dynamic and responsive learning system. It's like having a smart messenger that automatically delivers the latest lesson plans to the right place!

👉In the console head to the Eventarc .

👉Click the "+ CREATE TRIGGER" button.

Configure the Trigger (Basics):

  • Trigger name: plan-topic-trigger
  • Trigger type: Google sources
  • Event provider: Cloud Pub/Sub
  • Event type: google.cloud.pubsub.topic.v1.messagePublished
  • Cloud Pub/Sub Topic: select projects/PROJECT_ID/topics/plan
  • Region: us-central1 .
  • Service account:
    • GRANT the service account with role roles/iam.serviceAccountTokenCreator
    • Use the default value: Default compute service account
  • Event destination: Cloud Run
  • Cloud Run service: aidemy-portal
  • Ignore error message: Permission denied on 'locations/me-central2' (or it may not exist).
  • Service URL path: /new_teaching_plan

"তৈরি করুন" এ ক্লিক করুন।

The Eventarc Triggers page will refresh, and you should now see your newly created trigger listed in the table.

👉Now, access the planner agent using its Service URL to request a new teaching plan.

Run this in the terminal to determine the planner agent Service URL:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

This time try Year 5 , Subject Science , and Add-on Request atoms .

Then, wait a minute or two, again this delay has been introduced due to billing limitation of this lab, under normal condition, there shouldn't be a delay.

Finally, access the student portal using its Service URL.

Run this in the terminal to determine the student portal agent Service URL:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

You should see that the quizzes have been updated and now align with the new teaching plan you just generated! This demonstrates the successful integration of Eventarc in the Aidemy system!

Aidemy-celebrate

অভিনন্দন! You've successfully built a multi-agent system on Google Cloud, leveraging event-driven architecture for enhanced scalability and flexibility! You've laid a solid foundation, but there's even more to explore. To delve deeper into the real benefits of this architecture, discover the power of Gemini 2's multimodal Live API, and learn how to implement single-path orchestration with LangGraph, feel free to continue on to the next two chapters.

12. OPTIONAL: Audio Recaps with Gemini

Gemini can understand and process information from various sources, like text, images, and even audio, opening up a whole new range of possibilities for learning and content creation. Gemini's ability to "see," "hear," and "read" truly unlocks creative and engaging user experiences.

Beyond just creating visuals or text, another important step in learning is effective summarization and recap. Think about it: how often do you remember a catchy song lyric more easily than something you read in a textbook? Sound can be incredibly memorable! That's why we're going to leverage Gemini's multimodal capabilities to generate audio recaps of our teaching plans. This will provide students with a convenient and engaging way to review material, potentially boosting retention and comprehension through the power of auditory learning.

Live API Overview

We need a place to store the generated audio files. Cloud Storage provides a scalable and reliable solution.

👉Head to the Storage in the console. Click on "Buckets" in the left-hand menu. Click on the "+ CREATE" button at the top.

👉Configure your new bucket:

  • bucket name: aidemy-recap-UNIQUE_NAME .
    • IMPORTANT : Ensure you define a unique bucket name that begins with aidemy-recap- . This unique prefix is crucial for avoiding naming conflicts when creating your Cloud Storage bucket.
  • region: us-central1 .
  • Storage class: "Standard". Standard is suitable for frequently accessed data.
  • Access control: Leave the default "Uniform" access control selected. This provides consistent, bucket-level access control.
  • Advanced options: For this workshop, the default settings are usually sufficient.

Click the CREATE button to create your bucket.

  • You may see a pop up about public access prevention. Leave the "Enforce public access prevention on this bucket" box checked and click Confirm .

You will now see your newly created bucket in the Buckets list. Remember your bucket name, you'll need it later.

👉In the Cloud Code Editor's terminal, run the following commands to grant the service account access to the bucket:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

👉In the Cloud Code Editor, open audio.py inside the courses folder. Paste the following code to the end of the file:

config = LiveConnectConfig(
   
response_modalities=["AUDIO"],
   
speech_config=SpeechConfig(
       
voice_config=VoiceConfig(
           
prebuilt_voice_config=PrebuiltVoiceConfig(
               
voice_name="Charon",
           
)
       
)
   
),
)

async def process_weeks(teaching_plan: str):
   
region = "us-east5" #To workaround onRamp quota limits
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
   
clientAudio = genai.Client(vertexai=True, project=PROJECT_ID, location="us-central1")
   
async with clientAudio.aio.live.connect(
       
model=MODEL_ID,
       
config=config,
   
) as session:
       
for week in range(1, 4):  
           
response = client.models.generate_content(
               
model="gemini-2.0-flash-001",
               
contents=f"Given the following teaching plan: {teaching_plan}, Extrace content plan for week {week}. And return just the plan, nothingh else  " # Clarified prompt
           
)

           
prompt = f"""
                Assume you are the instructor.  
                Prepare a concise and engaging recap of the key concepts and topics covered.
                This recap should be suitable for generating a short audio summary for students.
                Focus on the most important learnings and takeaways, and frame it as a direct address to the students.  
                Avoid overly formal language and aim for a conversational tone, tell a few jokes.
               
                Teaching plan: {response.text} """
           
print(f"prompt --->{prompt}")

           
await session.send(input=prompt, end_of_turn=True)
           
with open(f"temp_audio_week_{week}.raw", "wb") as temp_file:
               
async for message in session.receive():
                   
if message.server_content.model_turn:
                       
for part in message.server_content.model_turn.parts:
                           
if part.inline_data:
                               
temp_file.write(part.inline_data.data)
                           
           
data, samplerate = sf.read(f"temp_audio_week_{week}.raw", channels=1, samplerate=24000, subtype='PCM_16', format='RAW')
           
sf.write(f"course-week-{week}.wav", data, samplerate)
       
           
storage_client = storage.Client()
           
bucket = storage_client.bucket(BUCKET_NAME)
           
blob = bucket.blob(f"course-week-{week}.wav")  # Or give it a more descriptive name
           
blob.upload_from_filename(f"course-week-{week}.wav")
           
print(f"Audio saved to GCS: gs://{BUCKET_NAME}/course-week-{week}.wav")
   
await session.close()

 
def breakup_sessions(teaching_plan: str):
   
asyncio.run(process_weeks(teaching_plan))
  • Streaming Connection : First, a persistent connection is established with the Live API endpoint. Unlike a standard API call where you send a request and get a response, this connection remains open for a continuous exchange of data.
  • Configuration Multimodal : Use configuration to specifying what type of output you want (in this case, audio), and you can even specify what parameters you'd like to use (eg, voice selection, audio encoding)
  • Asynchronous Processing : This API works asynchronously, meaning it doesn't block the main thread while waiting for the audio generation to complete. By processing data in real-time and sending the output in chunks, it provides a near-instantaneous experience.

Now, the key question is: when should this audio generation process run? Ideally, we want the audio recaps to be available as soon as a new teaching plan is created. Since we've already implemented an event-driven architecture by publishing the teaching plan to a Pub/Sub topic, we can simply subscribe to that topic.

However, we don't generate new teaching plans very often. It wouldn't be efficient to have an agent constantly running and waiting for new plans. That's why it makes perfect sense to deploy this audio generation logic as a Cloud Run Function.

By deploying it as a function, it remains dormant until a new message is published to the Pub/Sub topic. When that happens, it automatically triggers the function, which generates the audio recaps and stores them in our bucket.

👉Under the courses folder in main.py file, this file defines the Cloud Run Function that will be triggered when a new teaching plan is available. It receives the plan and initiates the audio recap generation. Add the following code snippet to the end of the file.

@functions_framework.cloud_event
def process_teaching_plan(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")
   
time.sleep(60)
   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):  # Check for base64 encoding
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan') # Get the teaching plan
       
elif 'teaching_plan' in cloud_event.data: # No base64
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found") # Handle error explicitly

       
#Load the teaching_plan as string and from cloud event, call audio breakup_sessions
       
breakup_sessions(teaching_plan)

       
return "Teaching plan processed successfully", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error processing teaching plan: {e}")
       
return "Error processing teaching plan", 500

@functions_framework.cloud_event : This decorator marks the function as a Cloud Run Function that will be triggered by CloudEvents.

Testing locally

👉We'll run this in a virtual environment and install the necessary Python libraries for the Cloud Run function.

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉The Cloud Run Function emulator allows us to test our function locally before deploying it to Google Cloud. Start a local emulator by running:

functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py

👉While the emulator is running, you can send test CloudEvents to the emulator to simulate a new teaching plan being published. In a new terminal:

Two terminal

👉Run:

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

Rather than staring blankly while waiting for the response, switch over to the other Cloud Shell terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

Back in the 2nd terminal you should see it should returned OK .

👉You'll verify Data in bucket, go to Cloud Storage and select the "Bucket" tab and then the aidemy-recap-UNIQUE_NAME

বালতি

👉In the terminal running the emulator, type ctrl+c to exit. And close the second terminal. And close the second terminal. and run deactivate to exit the virtual environment.

deactivate

Deploying to Google Cloud

স্থাপনার ওভারভিউ 👉After testing locally, it's time to deploy the course agent to Google Cloud. In the terminal, run these commands:

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud functions deploy courses-agent \
 
--region=us-central1 \
 
--gen2 \
 
--source=. \
 
--runtime=python312 \
 
--trigger-topic=plan \
 
--entry-point=process_teaching_plan \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

Verify deployment by going Cloud Run in the Google Cloud Console.You should see a new service named courses-agent listed.

Cloud Run List

To check the trigger configuration, click on the courses-agent service to view its details. Go to the "TRIGGERS" tab.

You should see a trigger configured to listen for messages published to the plan topic.

Cloud Run Trigger

Finally, let's see it running end to end.

👉We need to configure the portal agent so it knows where to find the generated audio files. টার্মিনালে, চালান:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Try generating a new teaching plan using the planner agent web page. It might take a few minutes to start, don't be alarmed, it's a serverless service.

To access the planner agent, get its Service URL by running this in the terminal:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep planner

After generating the new plan, wait 2-3 minutes for the audio to be generated, again this will take a few more minutes due to billing limitation with this lab account.

You can monitor whether the courses-agent function has received the teaching plan by checking the function's "TRIGGERS" tab. Refresh the page periodically; you should eventually see that the function has been invoked. If the function hasn't been invoked after more than 2 minutes, you can try generating the teaching plan again. However, avoid generating plans repeatedly in quick succession, as each generated plan will be sequentially consumed and processed by the agent, potentially creating a backlog.

Trigger Observe

👉Visit the portal and click on "Courses". You should see three cards, each displaying an audio recap. To find the URL of your portal agent:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep portal

Click "play" on each course to ensure the audio recaps are aligned with the teaching plan you just generated! Portal Courses

Exit the virtual environment.

deactivate

13. OPTIONAL: Role-Based collaboration with Gemini and DeepSeek

Having multiple perspectives is invaluable, especially when crafting engaging and thoughtful assignments. We'll now build a multi-agent system that leverages two different models with distinct roles, to generate assignments: one promotes collaboration, and the other encourages self-study. We'll use a "single-shot" architecture, where the workflow follows a fixed route.

Gemini Assignment Generator

মিথুন ওভারভিউ We'll start by setting up the Gemini function to generate assignments with a collaborative emphasis. Edit the gemini.py file located in the assignment folder.

👉Paste the following code to the end of the gemini.py file:

def gen_assignment_gemini(state):
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"---------------gen_assignment_gemini")
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        You are an instructor

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {state["teaching_plan"]}
        """
   
)

   
print(f"---------------gen_assignment_gemini answer {response.text}")
   
   
state["model_one_assignment"] = response.text
   
   
return state


import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

It uses the Gemini model to generate assignments.

We are ready to test the Gemini Agent.

👉Run these commands in the terminal to setup the environment:

cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉You can run to test it:

python gemini.py

You should see an assignment that has more group work in the output. The assert test at the end will also output the results.

Here are some engaging and practical assignments for each week, designed to build progressively upon the teaching plan's objectives:

**Week 1: Exploring the World of 2D Shapes**

* **Learning Objectives Assessed:**
   
* Identify and name basic 2D shapes (squares, rectangles, triangles, circles).
   
* .....

* **Description:**
   
* **Shape Scavenger Hunt:** Students will go on a scavenger hunt in their homes or neighborhoods, taking pictures of objects that represent different 2D shapes. They will then create a presentation or poster showcasing their findings, classifying each shape and labeling its properties (e.g., number of sides, angles, etc.).
   
* **Triangle Trivia:** Students will research and create a short quiz or presentation about different types of triangles, focusing on their properties and real-world examples.
   
* **Angle Exploration:** Students will use a protractor to measure various angles in their surroundings, such as corners of furniture, windows, or doors. They will record their measurements and create a chart categorizing the angles as right, acute, or obtuse.
....

**Week 2: Delving into the World of 3D Shapes and Symmetry**

* **Learning Objectives Assessed:**
   
* Identify and name basic 3D shapes.
   
* ....

* **Description:**
   
* **3D Shape Construction:** Students will work in groups to build 3D shapes using construction paper, cardboard, or other materials. They will then create a presentation showcasing their creations, describing the number of faces, edges, and vertices for each shape.
   
* **Symmetry Exploration:** Students will investigate the concept of symmetry by creating a visual representation of various symmetrical objects (e.g., butterflies, leaves, snowflakes) using drawing or digital tools. They will identify the lines of symmetry and explain their findings.
   
* **Symmetry Puzzles:** Students will be given a half-image of a symmetrical figure and will be asked to complete the other half, demonstrating their understanding of symmetry. This can be done through drawing, cut-out activities, or digital tools.

**Week 3: Navigating Position, Direction, and Problem Solving**

* **Learning Objectives Assessed:**
   
* Describe position using coordinates in the first quadrant.
   
* ....

* **Description:**
   
* **Coordinate Maze:** Students will create a maze using coordinates on a grid paper. They will then provide directions for navigating the maze using a combination of coordinate movements and translation/reflection instructions.
   
* **Shape Transformations:** Students will draw shapes on a grid paper and then apply transformations such as translation and reflection, recording the new coordinates of the transformed shapes.
   
* **Geometry Challenge:** Students will solve real-world problems involving perimeter, area, and angles. For example, they could be asked to calculate the perimeter of a room, the area of a garden, or the missing angle in a triangle.
....

Stop with ctl+c , and to clean up the test code. REMOVE the following code from gemini.py

import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

Configure the DeepSeek Assignment Generator

While cloud-based AI platforms are convenient, self-hosting LLMs can be crucial for protecting data privacy and ensuring data sovereignty. We'll deploy the smallest DeepSeek model (1.5B parameters) on a Cloud Compute Engine instance. There are other ways like hosting it on Google's Vertex AI platform or hosting it on your GKE instance, but since this is just a workshop on AI agents, and I don't want to keep you here forever, let's just use the most simplest way. But if you are interested and want to dig into other options, take a look at deepseek-vertexai.py file under assignment folder, where it provides an sample code of how to interact with models deployed on VertexAI.

Deepseek Overview

👉Run this command in the terminal to create a self-hosted LLM platform Ollama:

cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
   
--image-family=ubuntu-2204-lts \
   
--image-project=ubuntu-os-cloud \
   
--machine-type=e2-standard-4 \
   
--zone=us-central1-a \
   
--metadata-from-file startup-script=startup.sh \
   
--boot-disk-size=50GB \
   
--tags=ollama \
   
--scopes=https://www.googleapis.com/auth/cloud-platform

To verify the Compute Engine instance is running:

Navigate to Compute Engine > "VM instances" in the Google Cloud Console. You should see the ollama-instance listed with a green check mark indicating that it's running. If you can't see it, make sure the zone is us-central1. If it's not, you may need to search for it.

Compute Engine List

👉We'll install the smallest DeepSeek model and test it, back in the Cloud Shell Editor, in a New terminal, run following command to ssh into the GCE instance.

gcloud compute ssh ollama-instance --zone=us-central1-a

Upon establishing the SSH connection, you may be prompted with the following:

"Do you want to continue (Y/n)?"

Simply type Y (case-insensitive) and press Enter to proceed.

Next, you might be asked to create a passphrase for the SSH key. If you prefer not to use a passphrase, just press Enter twice to accept the default (no passphrase).

👉Now you are in the virutal machine, pull the smallest DeepSeek R1 model, and test if it works?

ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"

👉Exit the GCE instance enter following in the ssh terminal:

exit

👉Next, setup the network policy, so other services can access the LLM, please limit the access to the instance if you want to do this for production, either implement security login for the service or restrict IP access. চালান:

gcloud compute firewall-rules create allow-ollama-11434 \
   
--allow=tcp:11434 \
   
--target-tags=ollama \
   
--description="Allow access to Ollama on port 11434"

👉To verify if your firewall policy is working correctly, try running:

export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
curl -X POST "${OLLAMA_HOST}/api/generate" \
     
-H "Content-Type: application/json" \
     
-d '{
         
"prompt": "Hello, what are you?",
         
"model": "deepseek-r1:1.5b",
         
"stream": false
       
}'

Next, we'll work on the Deepseek function in the assignment agent to generate assignments with individual work emphasis.

👉Edit deepseek.py under assignment folder add following snippet to the end:

def gen_assignment_deepseek(state):
   
print(f"---------------gen_assignment_deepseek")

   
template = """
        You are an instructor who favor student to focus on individual work.

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {teaching_plan}
        """

   
   
prompt = ChatPromptTemplate.from_template(template)

   
model = OllamaLLM(model="deepseek-r1:1.5b",
                   
base_url=OLLAMA_HOST)

   
chain = prompt | model


   
response = chain.invoke({"teaching_plan":state["teaching_plan"]})
   
state["model_two_assignment"] = response
   
   
return state

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

👉let's test it by running:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
python deepseek.py

You should see an assignment that has more self study work.

**Assignment Plan for Each Week**

---

### **Week 1: 2D Shapes and Angles**
- **Week Title:** "Exploring 2D Shapes"
Assign students to research and present on various 2D shapes. Include a project where they create models using straws and tape for triangles, draw quadrilaterals with specific measurements, and compare their properties.

### **Week 2: 3D Shapes and Symmetry**
Assign students to create models or nets for cubes and cuboids. They will also predict how folding these nets form the 3D shapes. Include a project where they identify symmetrical properties using mirrors or folding techniques.

### **Week 3: Position, Direction, and Problem Solving**

Assign students to use mirrors or folding techniques for reflections. Include activities where they measure angles, use a protractor, solve problems involving perimeter/area, and create symmetrical designs.
....

👉Stop the ctl+c , and to clean up the test code. REMOVE the following code from deepseek.py

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

Now, we'll use the same gemini model to combine both assignments into a new one. Edit the gemini.py file located in the assignment folder.

👉Paste the following code to the end of the gemini.py file:

def combine_assignments(state):
   
print(f"---------------combine_assignments ")
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        Look at all the proposed assignment so far {state["model_one_assignment"]} and {state["model_two_assignment"]}, combine them and come up with a final assignment for student.
        """
   
)

   
state["final_assignment"] = response.text
   
   
return state

To combine the strengths of both models, we'll orchestrate a defined workflow using LangGraph. This workflow consists of three steps: first, the Gemini model generates an assignment focused on collaboration; second, the DeepSeek model generates an assignment emphasizing individual work; finally, Gemini synthesizes these two assignments into a single, comprehensive assignment. Because we predefine the sequence of steps without LLM decision-making, this constitutes a single-path, user-defined orchestration.

Langraph combine overview

👉Paste the following code to the end of the main.py file under assignment folder:

def create_assignment(teaching_plan: str):
   
print(f"create_assignment---->{teaching_plan}")
   
builder = StateGraph(State)
   
builder.add_node("gen_assignment_gemini", gen_assignment_gemini)
   
builder.add_node("gen_assignment_deepseek", gen_assignment_deepseek)
   
builder.add_node("combine_assignments", combine_assignments)
   
   
builder.add_edge(START, "gen_assignment_gemini")
   
builder.add_edge("gen_assignment_gemini", "gen_assignment_deepseek")
   
builder.add_edge("gen_assignment_deepseek", "combine_assignments")
   
builder.add_edge("combine_assignments", END)

   
graph = builder.compile()
   
state = graph.invoke({"teaching_plan": teaching_plan})

   
return state["final_assignment"]



import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

👉To initially test the create_assignment function and confirm that the workflow combining Gemini and DeepSeek is functional, run the following command:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py

You should see something that combine both models with their individual perspective for student study and also for student group works.

**Tasks:**

1. **Clue Collection:** Gather all the clues left by the thieves. These clues will include:
   
* Descriptions of shapes and their properties (angles, sides, etc.)
   
* Coordinate grids with hidden messages
   
* Geometric puzzles requiring transformation (translation, reflection, rotation)
   
* Challenges involving area, perimeter, and angle calculations

2. **Clue Analysis:** Decipher each clue using your geometric knowledge. This will involve:
   
* Identifying the shape and its properties
   
* Plotting coordinates and interpreting patterns on the grid
   
* Solving geometric puzzles by applying transformations
   
* Calculating area, perimeter, and missing angles

3. **Case Report:** Create a comprehensive case report outlining your findings. This report should include:
   
* A detailed explanation of each clue and its solution
   
* Sketches and diagrams to support your explanations
   
* A step-by-step account of how you followed the clues to locate the artifact
   
* A final conclusion about the thieves and their motives

👉Stop the ctl+c , and to clean up the test code. REMOVE the following code from main.py

import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

Generate Assignment.png

To make the assignment generation process automatic and responsive to new teaching plans, we'll leverage the existing event-driven architecture. The following code defines a Cloud Run Function (generate_assignment) that will be triggered whenever a new teaching plan is published to the Pub/Sub topic ' plan '.

👉Add the following code to the end of main.py in the assignment folder:

@functions_framework.cloud_event
def generate_assignment(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")

   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan')
       
elif 'teaching_plan' in cloud_event.data:
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found")

       
assignment = create_assignment(teaching_plan)

       
print(f"Assignment---->{assignment}")

       
#Store the return assignment into bucket as a text file
       
storage_client = storage.Client()
       
bucket = storage_client.bucket(ASSIGNMENT_BUCKET)
       
file_name = f"assignment-{random.randint(1, 1000)}.txt"
       
blob = bucket.blob(file_name)
       
blob.upload_from_string(assignment)

       
return f"Assignment generated and stored in {ASSIGNMENT_BUCKET}/{file_name}", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error generate assignment: {e}")
       
return "Error generate assignment", 500

Testing locally

Before deploying to Google Cloud, it's good practice to test the Cloud Run Function locally. This allows for faster iteration and easier debugging.

First, create a Cloud Storage bucket to store the generated assignment files and grant the service account access to the bucket. Run the following commands in the terminal:

👉 IMPORTANT : Ensure you define a unique ASSIGNMENT_BUCKET name that begins with " aidemy-assignment- ". This unique name is crucial for avoiding naming conflicts when creating your Cloud Storage bucket. (Replace <YOUR_NAME> with any random word)

export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue

👉And run:

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gsutil mb -p $PROJECT_ID -l us-central1 gs://$ASSIGNMENT_BUCKET

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

👉Now, start the Cloud Run Function emulator:

cd ~/aidemy-bootstrap/assignment
functions-framework \
   
--target generate_assignment \
   
--signature-type=cloudevent \
   
--source main.py

👉While the emulator is running in one terminal, open a second terminal in the Cloud Shell. In this second terminal, send a test CloudEvent to the emulator to simulate a new teaching plan being published:

Two terminal

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

Rather than staring blankly while waiting for the response, switch over to the other Cloud Shell terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

The curl command should print "OK" (without a newline, so "OK" may appear on the same line your terminal shell prompt).

To confirm that the assignment was successfully generated and stored, go to the Google Cloud Console and navigate to Storage > "Cloud Storage". Select the aidemy-assignment bucket you created. You should see a text file named assignment-{random number}.txt in the bucket. Click on the file to download it and verify its contents. This verifies that a new file contains new assignment just generated.

12-01-assignment-bucket

👉In the terminal running the emulator, type ctrl+c to exit. And close the second terminal. 👉Also, in the terminal running the emulator, exit the virtual environment.

deactivate

স্থাপনার ওভারভিউ

👉Next, we'll deploy the assignment agent to the cloud

cd ~/aidemy-bootstrap/assignment
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
export PROJECT_ID=$(gcloud config get project)
gcloud functions deploy assignment-agent \
 
--gen2 \
 
--timeout=540 \
 
--memory=2Gi \
 
--cpu=1 \
 
--set-env-vars="ASSIGNMENT_BUCKET=${ASSIGNMENT_BUCKET}" \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
 
--set-env-vars=OLLAMA_HOST=${OLLAMA_HOST} \
 
--region=us-central1 \
 
--runtime=python312 \
 
--source=. \
 
--entry-point=generate_assignment \
 
--trigger-topic=plan

Verify deployment by going to Google Cloud Console, navigate to Cloud Run. You should see a new service named courses-agent listed. 12-03-function-list

With the assignment generation workflow now implemented and tested and deployed, we can move on to the next step: making these assignments accessible within the student portal.

14. OPTIONAL: Role-Based collaboration with Gemini and DeepSeek - Contd.

Dynamic website generation

To enhance the student portal and make it more engaging, we'll implement dynamic HTML generation for assignment pages. The goal is to automatically update the portal with a fresh, visually appealing design whenever a new assignment is generated. This leverages the LLM's coding capabilities to create a more dynamic and interesting user experience.

14-01-generate-html

👉In Cloud Shell Editor, edit the render.py file within the portal folder, replace

def render_assignment_page():
   
return ""

with following code snippet:

def render_assignment_page(assignment: str):
   
try:
       
region=get_next_region()
       
llm = VertexAI(model_name="gemini-2.0-flash-001", location=region)
       
input_msg = HumanMessage(content=[f"Here the assignment {assignment}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                        """
                        As a frontend developer, create HTML to display a student assignment with a creative look and feel. Include the following navigation bar at the top:
                        ```
                        <nav>
                            <a href="/">Home</a>
                            <a href="/quiz">Quizzes</a>
                            <a href="/courses">Courses</a>
                            <a href="/assignment">Assignments</a>
                        </nav>
                        ```
                        Also include these links in the <head> section:
                        ```
                        <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
                        <link rel="preconnect" href="https://fonts.googleapis.com">
                        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                        <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">

                        ```
                        Do not apply inline styles to the navigation bar.
                        The HTML should display the full assignment content. In its CSS, be creative with the rainbow colors and aesthetic.
                        Make it creative and pretty
                        The assignment content should be well-structured and easy to read.
                        respond with JUST the html file
                        """
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)

       
response = response.replace("```html", "")
       
response = response.replace("```", "")
       
with open("templates/assignment.html", "w") as f:
           
f.write(response)


       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"

It uses the Gemini model to dynamically generate HTML for the assignment. It takes the assignment content as input and uses a prompt to instruct Gemini to create a visually appealing HTML page with a creative style.

Next, we'll create an endpoint that will be triggered whenever a new document is added to the assignment bucket:

👉Within the portal folder, edit the app.py file and add the following code within the ## Add your code here" comments , AFTER the new_teaching_plan function:

## Add your code here

def new_teaching_plan():
       
...
       
...
       
...

   
except Exception as e:
       
...
       
...

@app.route('/render_assignment', methods=['POST'])
def render_assignment():
   
try:
       
data = request.get_json()
       
file_name = data.get('name')
       
bucket_name = data.get('bucket')

       
if not file_name or not bucket_name:
           
return jsonify({'error': 'Missing file name or bucket name'}), 400

       
storage_client = storage.Client()
       
bucket = storage_client.bucket(bucket_name)
       
blob = bucket.blob(file_name)
       
content = blob.download_as_text()

       
print(f"File content: {content}")

       
render_assignment_page(content)

       
return jsonify({'message': 'Assignment rendered successfully'})

   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500

## Add your code here

When triggered, it retrieves the file name and bucket name from the request data, downloads the assignment content from Cloud Storage, and calls the render_assignment_page function to generate the HTML.

👉We'll go ahead and run it locally:

cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py

👉From the "Web preview" menu at the top of the Cloud Shell window, select "Preview on port 8080". This will open your application in a new browser tab. Navigate to the Assignment link in the navigation bar. You should see a blank page at this point, which is expected behavior since we haven't yet established the communication bridge between the assignment agent and the portal to dynamically populate the content.

14-02-deployment-overview

o ahead and stop the script by pressing Ctrl+C .

👉To incorporate these changes and deploy the updated code, rebuild and push the portal agent image:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

👉After pushing the new image, redeploy the Cloud Run service. Run the following script to force the Cloud Run update:

export PROJECT_ID=$(gcloud config get project)
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Now, we'll deploy an Eventarc trigger that listens for any new object created (finalized) in the assignment bucket. This trigger will automatically invoke the /render_assignment endpoint on the portal service when a new assignment file is created.

export PROJECT_ID=$(gcloud config get project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$(gcloud storage service-agent --project $PROJECT_ID)" \
 
--role="roles/pubsub.publisher"
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud eventarc triggers create portal-assignment-trigger \
--location=us-central1 \
--service-account=$SERVICE_ACCOUNT_NAME \
--destination-run-service=aidemy-portal \
--destination-run-region=us-central1 \
--destination-run-path="/render_assignment" \
--event-filters="bucket=$ASSIGNMENT_BUCKET" \
--event-filters="type=google.cloud.storage.object.v1.finalized"

To verify that the trigger was created successfully, navigate to the Eventarc Triggers page in the Google Cloud Console. You should see portal-assignment-trigger listed in the table. Click on the trigger name to view its details. Assignment Trigger

It may take up to 2-3 minutes for the new trigger to become active.

To see the dynamic assignment generation in action, run the following command to find the URL of your planner agent (if you don't have it handy):

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

Find the URL of your portal agent:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

In the planner agent, generate a new teaching plan.

13-02-assignment

After a few minutes (to allow for the audio generation, assignment generation, and HTML rendering to complete), navigate to the student portal.

👉Click on the "Assignment" link in the navigation bar. You should see a newly created assignment with a dynamically generated HTML. Each time a teaching plan is generated it should be a dynamic assignment.

13-02-assignment

Congratulations on completing the Aidemy multi-agent system ! You've gained practical experience and valuable insights into:

  • The benefits of multi-agent systems, including modularity, scalability, specialization, and simplified maintenance.
  • The importance of event-driven architectures for building responsive and loosely coupled applications.
  • The strategic use of LLMs, matching the right model to the task and integrating them with tools for real-world impact.
  • Cloud-native development practices using Google Cloud services to create scalable and reliable solutions.
  • The importance of considering data privacy and self-hosting models as an alternative to vendor solutions.

You now have a solid foundation for building sophisticated AI-powered applications on Google Cloud!

15. চ্যালেঞ্জ এবং পরবর্তী পদক্ষেপ

Congratulations on building the Aidemy multi-agent system! You've laid a strong foundation for AI-powered education. Now, let's consider some challenges and potential future enhancements to further expand its capabilities and address real-world needs:

Interactive Learning with Live Q&A:

  • Challenge: Can you leverage Gemini 2's Live API to create a real-time Q&A feature for students? Imagine a virtual classroom where students can ask questions and receive immediate, AI-powered responses.

Automated Assignment Submission and Grading:

  • Challenge: Design and implement a system that allows students to submit assignments digitally and have them automatically graded by AI, with a mechanism to detect and prevent plagiarism. This challenge presents a great opportunity to explore Retrieval Augmented Generation (RAG) to enhance the accuracy and reliability of the grading and plagiarism detection processes.

aidemy-climb

16. পরিষ্কার করুন

Now that we've built and explored our Aidemy multi-agent system, it's time to clean up our Google Cloud environment.

👉Delete Cloud Run services

gcloud run services delete aidemy-planner --region=us-central1 --quiet
gcloud run services delete aidemy-portal --region=us-central1 --quiet
gcloud run services delete courses-agent --region=us-central1 --quiet
gcloud run services delete book-provider --region=us-central1 --quiet
gcloud run services delete assignment-agent --region=us-central1 --quiet

👉Delete Eventarc trigger

gcloud eventarc triggers delete portal-assignment-trigger --location=us --quiet
gcloud eventarc triggers delete plan-topic-trigger --location=us-central1 --quiet
gcloud eventarc triggers delete portal-assignment-trigger --location=us-central1 --quiet
ASSIGNMENT_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:assignment-agent" --format="value(name)")
COURSES_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:courses-agent" --format="value(name)")
gcloud eventarc triggers delete $ASSIGNMENT_AGENT_TRIGGER --location=us-central1 --quiet
gcloud eventarc triggers delete $COURSES_AGENT_TRIGGER --location=us-central1 --quiet

👉Delete Pub/Sub topic

gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet

👉Delete Cloud SQL instance

gcloud sql instances delete aidemy --quiet

👉Delete Artifact Registry repository

gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet

👉Delete Secret Manager secrets

gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet

👉Delete Compute Engine instance (if created for Deepseek)

gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet

👉Delete the firewall rule for Deepseek instance

gcloud compute firewall-rules delete allow-ollama-11434 --quiet

👉Delete Cloud Storage buckets

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
gsutil rm -r gs://$COURSE_BUCKET_NAME
gsutil rm -r gs://$ASSIGNMENT_BUCKET

aidemy-broom

,
Aidemy:
Building Multi-Agent Systems with LangGraph, EDA, and Generative AI on Google Cloud

এই কোডল্যাব সম্পর্কে

subjectমার্চ ১৩, ২০২৫-এ শেষবার আপডেট করা হয়েছে
account_circleChristina Lin-এর লেখা

1. ভূমিকা

এই যে! So, you're into the idea of agents – little helpers that can get things done for you without you even lifting a finger, right? অসাধারন! But let's be real, one agent isn't always going to cut it, especially when you're tackling bigger, more complex projects. You're probably going to need a whole team of them! That's where multi-agent systems come in.

Agents, when powered by LLMs, give you incredible flexibility compared to old-school hard coding. But, and there's always a but, they come with their own set of tricky challenges. And that's exactly what we're going to dive into in this workshop!

শিরোনাম

Here's what you can expect to learn – think of it as leveling up your agent game:

Building Your First Agent with LangGraph : We'll get our hands dirty building your very own agent using LangGraph, a popular framework. You'll learn how to create tools that connect to databases, tap into the latest Gemini 2 API for some internet searching, and optimize the prompts and response, so your agent can interact with not only LLMs but existing services. We'll also show you how function calling works.

Agent Orchestration, Your Way : We'll explore different ways to orchestrate your agents, from simple straight paths to more complex multi-path scenarios. Think of it as directing the flow of your agent team.

Multi-Agent Systems : You'll discover how to set up a system where your agents can collaborate, and get things done together – all thanks to an event-driven architecture.

LLM Freedom – Use the Best for the Job: We're not stuck on just one LLM! You'll see how to use multiple LLMs, assigning them different roles to boost problem-solving power using cool "thinking models."

Dynamic Content? কোন সমস্যা নেই! : Imagine your agent creating dynamic content that's tailored specifically for each user, in real-time. আমরা আপনাকে দেখাব কিভাবে এটি করতে হবে!

Taking it to the Cloud with Google Cloud : Forget just playing around in a notebook. We'll show you how to architect and deploy your multi-agent system on Google Cloud so it's ready for the real world!

This project will be a good example of how to use all the techniques we talked about.

2. স্থাপত্য

Being a teacher or working in education can be super rewarding, but let's face it, the workload, especially all the prep work, can be challenging! Plus, there's often not enough staff and tutoring can be expensive. That's why we're proposing an AI-powered teaching assistant. This tool can lighten the load for educators and help bridge the gap caused by staff shortages and the lack of affordable tutoring.

Our AI teaching assistant can whip up detailed lesson plans, fun quizzes, easy-to-follow audio recaps, and personalized assignments. This lets teachers focus on what they do best: connecting with students and helping them fall in love with learning.

The system has two sites: one for teachers to create lesson plans for upcoming weeks,

পরিকল্পনাকারী

and one for students to access quizzes, audio recaps, and assignments. পোর্টাল

Alright, let's walk through the architecture powering our teaching assistant, Aidemy. As you can see, we've broken it down into several key components, all working together to make this happen.

স্থাপত্য

Key Architectural Elements and Technologies :

Google Cloud Platform (GCP) : Central to the entire system:

  • Vertex AI: Accesses Google's Gemini LLMs.
  • Cloud Run: Serverless platform for deploying containerized agents and functions.
  • Cloud SQL: PostgreSQL database for curriculum data.
  • Pub/Sub & Eventarc: Foundation of the event-driven architecture, enabling asynchronous communication between components.
  • Cloud Storage: Stores audio recaps and assignment files.
  • Secret Manager: Securely manages database credentials.
  • Artifact Registry: Stores Docker images for the agents.
  • Compute Engine: To deploy self-hosted LLM instead of relying on vendor solutions

LLMs : The "brains" of the system:

  • Google's Gemini models: (Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking, Gemini 1.5-pro) Used for lesson planning, content generation, dynamic HTML creation, quiz explanation and combining the assignments.
  • DeepSeek: Utilized for the specialized task of generating self-study assignments

LangChain & LangGraph : Frameworks for LLM Application Development

  • Facilitates the creation of complex multi-agent workflows.
  • Enables the intelligent orchestration of tools (API calls, database queries, web searches).
  • Implements event-driven architecture for system scalability and flexibility.

In essence, our architecture combines the power of LLMs with structured data and event-driven communication, all running on Google Cloud. This lets us build a scalable, reliable, and effective teaching assistant.

3. আপনি শুরু করার আগে

In the Google Cloud Console , on the project selector page, select or create a Google Cloud project . Make sure that billing is enabled for your Cloud project. Learn how to check if billing is enabled on a project .

👉Click Activate Cloud Shell at the top of the Google Cloud console (It's the terminal shape icon at the top of the Cloud Shell pane), click on the "Open Editor" button (it looks like an open folder with a pencil). This will open the Cloud Shell Code Editor in the window. You'll see a file explorer on the left side.

মেঘের শেল

👉Click on the Cloud Code Sign-in button in the bottom status bar as shown. Authorize the plugin as instructed. If you see Cloud Code - no project in the status bar, select that then in the drop down 'Select a Google Cloud Project' and then select the specific Google Cloud Project from the list of projects that you created.

Login project

👉Open the terminal in the cloud IDE, নতুন টার্মিনাল

👉In the terminal, verify that you're already authenticated and that the project is set to your project ID using the following command:

gcloud auth list

👉And run:

gcloud config set project <YOUR_PROJECT_ID>

👉Run the following command to enable the necessary Google Cloud APIs:

gcloud services enable compute.googleapis.com  \
                       
storage.googleapis.com  \
                       
run.googleapis.com  \
                       
artifactregistry.googleapis.com  \
                       
aiplatform.googleapis.com \
                       
eventarc.googleapis.com \
                       
sqladmin.googleapis.com \
                       
secretmanager.googleapis.com \
                       
cloudbuild.googleapis.com \
                       
cloudresourcemanager.googleapis.com \
                       
cloudfunctions.googleapis.com

This may take a couple of minutes..

Enable Gemini Code Assist in Cloud Shell IDE

Click on the Code Assist button in the on left panel as shown and select one last time the correct Google Cloud project. If you are asked to enable the Cloud AI Companion API, please do so and move forward. Once you've selected your Google Cloud project, ensure that you are able to see that in the Cloud Code status message in the status bar and that you also have Code Assist enabled on the right, in the status bar as shown below:

Enable codeassist

Setting up permission

👉Setup service account permission. In the terminal, run :

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")

echo "Here's your SERVICE_ACCOUNT_NAME $SERVICE_ACCOUNT_NAME"

👉 Grant Permissions. In the terminal, run :

#Cloud Storage (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/storage.objectAdmin"

#Pub/Sub (Publish/Receive):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.publisher"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/pubsub.subscriber"


#Cloud SQL (Read/Write):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/cloudsql.editor"


#Eventarc (Receive Events):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/iam.serviceAccountTokenCreator"

gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/eventarc.eventReceiver"

#Vertex AI (User):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/aiplatform.user"

#Secret Manager (Read):
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 
--role="roles/secretmanager.secretAccessor"

👉Validate result in your IAM console আইএএম কনসোল

👉Run the following commands in the terminal to create a Cloud SQL instance named aidemy . We'll need this later, but since this process can take some time, we'll do it now.

gcloud sql instances create aidemy \
   
--database-version=POSTGRES_14 \
   
--cpu=2 \
   
--memory=4GB \
   
--region=us-central1 \
   
--root-password=1234qwer \
   
--storage-size=10GB \
   
--storage-auto-increase

4. Building the first agent

Before we dive into complex multi-agent systems, we need to establish a fundamental building block: a single, functional agent. In this section, we'll take our first steps by creating a simple "book provider" agent. The book provider agent takes a category as input and uses a Gemini LLM to generate a JSON representation book within that category. It then serves these book recommendations as a REST API endpoint .

Book Provider

👉In another browser tab, open the Google Cloud Console in your web browser,in the navigation menu (☰), go to "Cloud Run". Click the "+ ... WRITE A FUNCTION" button.

ফাংশন তৈরি করুন

👉Next we'll configures the basic settings of the Cloud Run Function:

  • Service name: book-provider
  • Region: us-central1
  • Runtime: Python 3.12
  • Authentication: Allow unauthenticated invocations to Enabled.

👉Leave other settings as default and click Create . This will take you to the source code editor.

You'll see pre-populated main.py and requirements.txt files.

The main.py will contain the business logic of the function, requirements.txt will contain the packages needed.

👉Now we are ready to write some code! But before diving in, let's see if Gemini Code Assist can give us a head start. Return to the Cloud Shell Editor, click on the Gemini Code Assist icon, and paste the following request into the prompt box: মিথুন কোড সহায়তা

Use the functions_framework library to be deployable as an HTTP function. 
Accept a request with category and number_of_book parameters (either in JSON body or query string).
Use langchain and gemini to generate the data for book with fields bookname, author, publisher, publishing_date.
Use pydantic to define a Book model with the fields: bookname (string, description: "Name of the book"), author (string, description: "Name of the author"), publisher (string, description: "Name of the publisher"), and publishing_date (string, description: "Date of publishing").
Use langchain and gemini model to generate book data. the output should follow the format defined in Book model.

The logic should use JsonOutputParser from langchain to enforce output format defined in Book Model.
Have a function get_recommended_books(category) that internally uses langchain and gemini to return a single book object.
The main function, exposed as the Cloud Function, should call get_recommended_books() multiple times (based on number_of_book) and return a JSON list of the generated book objects.
Handle the case where category or number_of_book are missing by returning an error JSON response with a 400 status code.
return a JSON string representing the recommended books. use os library to retrieve GOOGLE_CLOUD_PROJECT env var. Use ChatVertexAI from langchain for the LLM call

Code Assist will then generate a potential solution, providing both the source code and a requirements.txt dependency file.

We encourage you to compare the Code Assist's generated code with the tested, correct solution provided below. This allows you to evaluate the tool's effectiveness and identify any potential discrepancies. While LLMs should never be blindly trusted, Code Assist can be a great tool for rapid prototyping and generating initial code structures, and should be use for a good head start.

Since this is a workshop, we'll proceed with the verified code provided below. However, feel free to experiment with the Code Assist-generated code in your own time to gain a deeper understanding of its capabilities and limitations.

👉Return to the Cloud Run Function's source code editor (in the other browser tab). Carefully replace the existing content of main.py with the code provided below:

import functions_framework
import json
from flask import Flask, jsonify, request
from langchain_google_vertexai import ChatVertexAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
import os

class Book(BaseModel):
   
bookname: str = Field(description="Name of the book")
   
author: str = Field(description="Name of the author")
   
publisher: str = Field(description="Name of the publisher")
   
publishing_date: str = Field(description="Date of publishing")


project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")  

llm = ChatVertexAI(model_name="gemini-2.0-flash-lite-001")

def get_recommended_books(category):
    """
    A simple book recommendation function.

    Args:
        category (str): category

    Returns:
        str: A JSON string representing the recommended books.
    """
   
parser = JsonOutputParser(pydantic_object=Book)
   
question = f"Generate a random made up book on {category} with bookname, author and publisher and publishing_date"

   
prompt = PromptTemplate(
       
template="Answer the user query.\n{format_instructions}\n{query}\n",
       
input_variables=["query"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"query": question})

   
return  json.dumps(response)
   

@functions_framework.http
def recommended(request):
   
request_json = request.get_json(silent=True) # Get JSON data
   
if request_json and 'category' in request_json and 'number_of_book' in request_json:
       
category = request_json['category']
       
number_of_book = int(request_json['number_of_book'])
   
elif request.args and 'category' in request.args and 'number_of_book' in request.args:
       
category = request.args.get('category')
       
number_of_book = int(request.args.get('number_of_book'))

   
else:
       
return jsonify({'error': 'Missing category or number_of_book parameters'}), 400


   
recommendations_list = []
   
for i in range(number_of_book):
       
book_dict = json.loads(get_recommended_books(category))
       
print(f"book_dict=======>{book_dict}")
   
       
recommendations_list.append(book_dict)

   
   
return jsonify(recommendations_list)

👉Replace the contents of requirements.txt with the following:

functions-framework==3.*
google-genai==1.0.0
flask==3.1.0
jsonify==0.5
langchain_google_vertexai==2.0.13
langchain_core==0.3.34
pydantic==2.10.5

👉we'll set the Function entry point : recommended

03-02-function-create.png

👉Click SAVE AND DEPLOY . to deploy the Function. Wait for the deployment process to complete. The Cloud Console will display the status. এতে কয়েক মিনিট সময় লাগতে পারে।

alt পাঠ্য 👉Once deployed, go back in the cloud shell editor, in the terminal run:

export PROJECT_ID=$(gcloud config get project)
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

curl -X POST -H "Content-Type: application/json" -d '{"category": "Science Fiction", "number_of_book": 2}' $BOOK_PROVIDER_URL

It should show some book data in JSON format.

[
 
{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},
 
{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}
]

অভিনন্দন! You have successfully deployed a Cloud Run Function. This is one of the services we will be integrating when developing our Aidemy agent.

5. Building Tools: Connecting Agents to RESTFUL service and Data

Let's go ahead and download the Bootstrap Skeleton Project, make sure you are in the Cloud Shell Editor. In the terminal run,

git clone https://github.com/weimeilin79/aidemy-bootstrap.git

After running this command, a new folder named aidemy-bootstrap will be created in your Cloud Shell environment.

In the Cloud Shell Editor's Explorer pane (usually on the left side), you should now see the folder that was created when you cloned the Git repository aidemy-bootstrap . Open the root folder of your project in the Explorer. You'll find a planner subfolder within it, open that as well. প্রকল্প এক্সপ্লোরার

Let's start building the tools our agents will use to become truly helpful. As you know, LLMs are excellent at reasoning and generating text, but they need access to external resources to perform real-world tasks and provide accurate, up-to-date information. Think of these tools as the agent's "Swiss Army knife," giving it the ability to interact with the world.

When building an agent, it's easy to fall into hard-coding a ton of details. This creates an agent that is not flexible. Instead, by creating and using tools, the agent has access to external logic or systems which gives it the benefits of both the LLM and traditional programming.

In this section, we'll create the foundation for the planner agent, which teachers will use to generate lesson plans. Before the agent starts generating a plan, we want to set boundaries by providing more details on the subject and topic. We'll build three tools:

  1. Restful API Call: Interacting with a pre-existing API to retrieve data.
  2. Database Query: Fetching structured data from a Cloud SQL database.
  3. Google Search: Accessing real-time information from the web.

Fetching Book Recommendations from an API

First, let's create a tool that retrieves book recommendations from the book-provider API we deployed in the previous section. This demonstrates how an agent can leverage existing services.

Recommend book

In the Cloud Shell Editor, open the aidemy-bootstrap project that you cloned in the previous section.

👉Edit the book.py in the planner folder, and paste the following code at the end of the file:

def recommend_book(query: str):
    """
    Get a list of recommended book from an API endpoint
   
    Args:
        query: User's request string
    """

   
region = get_next_region();
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)

   
query = f"""The user is trying to plan a education course, you are the teaching assistant. Help define the category of what the user requested to teach, respond the categroy with no more than two word.

    user request:   {query}
    """
   
print(f"-------->{query}")
   
response = llm.invoke(query)
   
print(f"CATEGORY RESPONSE------------>: {response}")
   
   
# call this using python and parse the json back to dict
   
category = response.strip()
   
   
headers = {"Content-Type": "application/json"}
   
data = {"category": category, "number_of_book": 2}

   
books = requests.post(BOOK_PROVIDER_URL, headers=headers, json=data)
   
   
return books.text

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

ব্যাখ্যা:

  • recommend_book(query: str) : This function takes a user's query as input.
  • LLM Interaction : It uses the LLM to extract the category from the query. This demonstrates how you can use the LLM to help create parameters for tools.
  • API Call : It makes a POST request to the book-provider API, passing the category and the desired number of books.

👉To test this new function, set the environment variable, run :

cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

👉Install the dependencies and run the code to ensure it works, run:

cd ~/aidemy-bootstrap/planner/
python -m venv env
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
pip install -r requirements.txt
python book.py

Ignore the Git warning pop-up window.

You should see a JSON string containing book recommendations retrieved from the book-provider API. The results are randomly generated. Your books may not be the same, but you should receive two book recommendations in JSON format.

[{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}]

If you see this, the first tool is working correctly!

Instead of explicitly crafting a RESTful API call with specific parameters, we're using natural language ("I'm doing a course..."). The agent then intelligently extracts the necessary parameters (like the category) using NLP, highlighting how the agent leverages natural language understanding to interact with the API.

compare call

👉 Remove the following testing code from the book.py

if __name__ == "__main__":
   
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

Getting Curriculum Data from a Database

Next, we'll build a tool that fetches structured curriculum data from a Cloud SQL PostgreSQL database. This allows the agent to access a reliable source of information for lesson planning.

create db

Remember the aidemy Cloud SQL instance you've created in previous step? Here's where it will be used.

👉Create a database named aidemy-db in the new instance.

gcloud sql databases create aidemy-db \
   
--instance=aidemy

Let's verify the instance in the Cloud SQL in the Google Cloud Console, You should see a Cloud SQL instance named aidemy listed. Click on the instance name to view its details. In the Cloud SQL instance details page, click on "SQL Studio" in the left-hand navigation menu. এটি একটি নতুন ট্যাব খুলবে।

Click to connect to the database. Sign in to the SQL Studio

Select aidemy-db as the database. enter postgres as user and 1234qwer as the password . sql studio sign in

👉In the SQL Studio query editor, paste the following SQL code:

CREATE TABLE curriculums (
   
id SERIAL PRIMARY KEY,
   
year INT,
   
subject VARCHAR(255),
   
description TEXT
);

-- Inserting detailed curriculum data for different school years and subjects
INSERT INTO curriculums (year, subject, description) VALUES
-- Year 5
(5, 'Mathematics', 'Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.'),
(5, 'English', 'Developing reading comprehension, creative writing, and basic grammar, with a focus on storytelling and poetry.'),
(5, 'Science', 'Exploring basic physics, chemistry, and biology concepts, including forces, materials, and ecosystems.'),
(5, 'Computer Science', 'Basic coding concepts using block-based programming and an introduction to digital literacy.'),

-- Year 6
(6, 'Mathematics', 'Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.'),
(6, 'English', 'Introduction to persuasive writing, character analysis, and deeper comprehension of literary texts.'),
(6, 'Science', 'Forces and motion, the human body, and introductory chemical reactions with hands-on experiments.'),
(6, 'Computer Science', 'Introduction to algorithms, logical reasoning, and basic text-based programming (Python, Scratch).'),

-- Year 7
(7, 'Mathematics', 'Algebraic expressions, geometry, and introduction to statistics and probability.'),
(7, 'English', 'Analytical reading of classic and modern literature, essay writing, and advanced grammar skills.'),
(7, 'Science', 'Introduction to cells and organisms, chemical reactions, and energy transfer in physics.'),
(7, 'Computer Science', 'Building on programming skills with Python, introduction to web development, and cyber safety.');

This SQL code creates a table named curriculums and inserts some sample data. Click Run to execute the SQL code. You should see a confirmation message indicating that the commands were executed successfully.

👉Expand the explorer, find the newly created table and click query . It should open a new editor tab with SQL generated for you,

sql studio select table

SELECT * FROM
 
"public"."curriculums" LIMIT 1000;

👉Click Run .

The results table should display the rows of data you inserted in the previous step, confirming that the table and data were created correctly.

Now that you have successfully created a database with populated sample curriculum data, we'll build a tool to retrieve it.

👉In the Cloud Code Editor, edit file curriculums.py in the aidemy-bootstrap folder and paste the following code at the end of the file:

def connect_with_connector() -> sqlalchemy.engine.base.Engine:

   
db_user = os.environ["DB_USER"]
   
db_pass = os.environ["DB_PASS"]
   
db_name = os.environ["DB_NAME"]

   
encoded_db_user = os.environ.get("DB_USER")
   
print(f"--------------------------->db_user: {db_user!r}")  
   
print(f"--------------------------->db_pass: {db_pass!r}")
   
print(f"--------------------------->db_name: {db_name!r}")

   
ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

   
connector = Connector()

   
def getconn() -> pg8000.dbapi.Connection:
       
conn: pg8000.dbapi.Connection = connector.connect(
           
instance_connection_name,
           
"pg8000",
           
user=db_user,
           
password=db_pass,
           
db=db_name,
           
ip_type=ip_type,
       
)
       
return conn

   
pool = sqlalchemy.create_engine(
       
"postgresql+pg8000://",
       
creator=getconn,
       
pool_size=2,
       
max_overflow=2,
       
pool_timeout=30,  # 30 seconds
       
pool_recycle=1800,  # 30 minutes
   
)
   
return pool



def init_connection_pool() -> sqlalchemy.engine.base.Engine:
   
   
return (
       
connect_with_connector()
   
)

   
raise ValueError(
       
"Missing database connection type. Please define one of INSTANCE_HOST, INSTANCE_UNIX_SOCKET, or INSTANCE_CONNECTION_NAME"
   
)

def get_curriculum(year: int, subject: str):
    """
    Get school curriculum
   
    Args:
        subject: User's request subject string
        year: User's request year int
    """
   
try:
       
stmt = sqlalchemy.text(
           
"SELECT description FROM curriculums WHERE year = :year AND subject = :subject"
       
)

       
with db.connect() as conn:
           
result = conn.execute(stmt, parameters={"year": year, "subject": subject})
           
row = result.fetchone()
       
if row:
           
return row[0]  
       
else:
           
return None  

   
except Exception as e:
       
print(e)
       
return None

db = init_connection_pool()

ব্যাখ্যা:

  • Environment Variables : The code retrieves database credentials and connection information from environment variables (more on this below).
  • connect_with_connector() : This function uses the Cloud SQL Connector to establish a secure connection to the database.
  • get_curriculum(year: int, subject: str) : This function takes the year and subject as input, queries the curriculums table, and returns the corresponding curriculum description.

👉Before we can run the code, we must set some environment variables, in the terminal, run:

export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉To test add the following code to the end of curriculums.py :

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉Run the code:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py

You should see the curriculum description for 6th-grade Mathematics printed to the console.

Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.

If you see the curriculum description, the database tool is working correctly! Go ahead and stop the script by pressing Ctrl+C .

👉 Remove the following testing code from the curriculums.py

if __name__ == "__main__":
   
print(get_curriculum(6, "Mathematics"))

👉Exit virtual environment, in terminal run:

deactivate

6. Building Tools: Access real-time information from the web

Finally, we'll build a tool that uses the Gemini 2 and Google Search integration to access real-time information from the web. This helps the agent stay up-to-date and provide relevant results.

Gemini 2's integration with the Google Search API enhances agent capabilities by providing more accurate and contextually relevant search results. This allows agents to access up-to-date information and ground their responses in real-world data, minimizing hallucinations. The improved API integration also facilitates more natural language queries, enabling agents to formulate complex and nuanced search requests.

অনুসন্ধান করুন

This function takes a search query, curriculum, subject, and year as input and uses the Gemini API and the Google Search tool to retrieve relevant information from the internet. If you look closely, it's using the Google Generative AI SDK to do function calling without using any other framework.

👉Edit search.py in the aidemy-bootstrap folder and paste the following code at the end of the file:

model_id = "gemini-2.0-flash-001"

google_search_tool = Tool(
   
google_search = GoogleSearch()
)

def search_latest_resource(search_text: str, curriculum: str, subject: str, year: int):
    """
    Get latest information from the internet
   
    Args:
        search_text: User's request category   string
        subject: "User's request subject" string
        year: "User's request year"  integer
    """
   
search_text = "%s in the context of year %d and subject %s with following curriculum detail %s " % (search_text, year, subject, curriculum)
   
region = get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"search_latest_resource text-----> {search_text}")
   
response = client.models.generate_content(
       
model=model_id,
       
contents=search_text,
       
config=GenerateContentConfig(
           
tools=[google_search_tool],
           
response_modalities=["TEXT"],
       
)
   
)
   
print(f"search_latest_resource response-----> {response}")
   
return response

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

ব্যাখ্যা:

  • Defining Tool - google_search_tool : Wrapping the GoogleSearch object within a Tool
  • search_latest_resource(search_text: str, subject: str, year: int) : This function takes a search query, subject, and year as input and uses the Gemini API to perform a Google search. Gemini model
  • GenerateContentConfig : Define that it has access to the GoogleSearch tool

The Gemini model internally analyzes the search_text and determines whether it can answer the question directly or if it needs to use the GoogleSearch tool. This is a critical step that happens within the LLM's reasoning process. The model has been trained to recognize situations where external tools are necessary. If the model decides to use the GoogleSearch tool, the Google Generative AI SDK handles the actual invocation. The SDK takes the model's decision and the parameters it generates and sends them to the Google Search API. This part is hidden from the user in the code.

The Gemini model then integrates the search results into its response. It can use the information to answer the user's question, generate a summary, or perform some other task.

👉To test, run the code:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py

You should see the Gemini Search API response containing search results related to "Syllabus for Year 5 Mathematics." The exact output will depend on the search results, but it will be a JSON object with information about the search.

If you see search results, the Google Search tool is working correctly! Go ahead and stop the script by pressing Ctrl+C .

👉And remove the last part in the code.

if __name__ == "__main__":
 
response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
 
for each in response.candidates[0].content.parts:
   
print(each.text)

👉Exit virtual environment, in terminal run:

deactivate

অভিনন্দন! You have now built three powerful tools for your planner agent: an API connector, a database connector, and a Google Search tool. These tools will enable the agent to access the information and capabilities it needs to create effective teaching plans.

7. Orchestrating with LangGraph

Now that we have built our individual tools, it's time to orchestrate them using LangGraph. This will allow us to create a more sophisticated "planner" agent that can intelligently decide which tools to use and when, based on the user's request.

LangGraph is a Python library designed to make it easier to build stateful, multi-actor applications using Large Language Models (LLMs). Think of it as a framework for orchestrating complex conversations and workflows involving LLMs, tools, and other agents.

মূল ধারণা:

  • Graph Structure: LangGraph represents your application's logic as a directed graph. Each node in the graph represents a step in the process (eg, a call to an LLM, a tool invocation, a conditional check). Edges define the flow of execution between nodes.
  • State: LangGraph manages the state of your application as it moves through the graph. This state can include variables like the user's input, the results of tool calls, intermediate outputs from LLMs, and any other information that needs to be preserved between steps.
  • Nodes: Each node represents a computation or interaction. তারা হতে পারে:
    • Tool Nodes: Use a tool (eg, perform a web search, query a database)
    • Function Nodes: Execute a Python function.
  • Edges: Connect nodes, defining the flow of execution. তারা হতে পারে:
    • Direct Edges: A simple, unconditional flow from one node to another.
    • Conditional Edges: The flow depends on the outcome of a conditional node.

ল্যাংগ্রাফ

We will use LangGraph to implement the orchestration. Let's edit the aidemy.py file under aidemy-bootstrap folder to define our LangGraph logic.

👉Append follow code to the end of aidemy.py :

tools = [get_curriculum, search_latest_resource, recommend_book]

def determine_tool(state: MessagesState):
   
llm = ChatVertexAI(model_name="gemini-2.0-flash-001", location=get_next_region())
   
sys_msg = SystemMessage(
                   
content=(
                       
f"""You are a helpful teaching assistant that helps gather all needed information.
                            Your ultimate goal is to create a detailed 3-week teaching plan.
                            You have access to tools that help you gather information.  
                            Based on the user request, decide which tool(s) are needed.

                        """
                   
)
               
)

   
llm_with_tools = llm.bind_tools(tools)
   
return {"messages": llm_with_tools.invoke([sys_msg] + state["messages"])}

This function is responsible for taking the current state of the conversation, providing the LLM with a system message, and then asking the LLM to generate a response. The LLM can either respond directly to the user or choose to use one of the available tools.

tools : This list represents the set of tools that the agent has available to it. It contains three tool functions that we defined in the previous steps: get_curriculum , search_latest_resource , and recommend_book . llm.bind_tools(tools) : It "binds" the tools list to the llm object. Binding the tools tells the LLM that these tools are available and provides the LLM with information about how to use them (eg, the names of the tools, the parameters they accept, and what they do).

We will use LangGraph to implement the orchestration.

👉Append following code to the end of aidemy.py :

def prep_class(prep_needs):
   
   
builder = StateGraph(MessagesState)
   
builder.add_node("determine_tool", determine_tool)
   
builder.add_node("tools", ToolNode(tools))
   
   
builder.add_edge(START, "determine_tool")
   
builder.add_conditional_edges("determine_tool",tools_condition)
   
builder.add_edge("tools", "determine_tool")

   
   
memory = MemorySaver()
   
graph = builder.compile(checkpointer=memory)

   
config = {"configurable": {"thread_id": "1"}}
   
messages = graph.invoke({"messages": prep_needs},config)
   
print(messages)
   
for m in messages['messages']:
       
m.pretty_print()
   
teaching_plan_result = messages["messages"][-1].content  


   
return teaching_plan_result

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan")

ব্যাখ্যা:

  • StateGraph(MessagesState) : Creates a StateGraph object. A StateGraph is a core concept in LangGraph. It represents the workflow of your agent as a graph, where each node in the graph represents a step in the process. Think of it as defining the blueprint for how the agent will reason and act.
  • Conditional Edge: Originating from the "determine_tool" node, the tools_condition argument is likely a function that determines which edge to follow based on the output of the determine_tool function. Conditional edges allow the graph to branch based on the LLM's decision about which tool to use (or whether to respond to the user directly). This is where the agent's "intelligence" comes into play – it can dynamically adapt its behavior based on the situation.
  • Loop: Adds an edge to the graph that connects the "tools" node back to the "determine_tool" node. This creates a loop in the graph, allowing the agent to repeatedly use tools until it has gathered enough information to complete the task and provide a satisfactory answer. This loop is crucial for complex tasks that require multiple steps of reasoning and information gathering.

Now, let's test our planner agent to see how it orchestrates the different tools.

This code will run the prep_class function with a specific user input, simulating a request to create a teaching plan for 5th-grade Mathematics in Geometry, using the curriculum, book recommendations, and the latest internet resources.

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Run the code:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py

Watch the log in the terminal. You should see evidence that the agent is calling all three tools (getting the school curriculum, getting book recommendations, and searching for the latest resources) before providing the final teaching plan. This demonstrates that the LangGraph orchestration is working correctly, and the agent is intelligently using all available tools to fulfill the user's request.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
search_text: Geometry
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
subject: Mathematics
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.....) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================
Tool Calls:
 
recommend_book (93b48189-4d69-4c09-a3bd-4e60cdc5f1c6)
 
Call ID: 93b48189-4d69-4c09-a3bd-4e60cdc5f1c6
 
Args:
   
query: Mathematics Geometry Year 5
================================= Tool Message =================================
Name: recommend_book

[{.....}]

================================== Ai Message ==================================

Based on the curriculum outcome, here is a 3-week teaching plan for year 5 Mathematics Geometry:

**Week 1: Introduction to Shapes and Properties**
.........

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉(THIS STEP IS OPTIONAL) replace the testing code with a different prompt, which requires different tools to be called.

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉(THIS STEP IS OPTIONAL, do this ONLY IF you ran the previous step) Run the code again:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py

এই সময় আপনি কি লক্ষ্য করেছেন? Which tools did the agent call? You should see that the agent only calls the search_latest_resource tool this time. This is because the prompt does not specify that it needs the other two tools, and our LLM is smart enough to not call the other tools.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
 
get_curriculum (xxx)
 
Call ID: xxx
 
Args:
   
year: 5.0
   
subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
 
search_latest_resource (xxx)
 
Call ID: xxxx
 
Args:
   
year: 5.0
   
subject: Mathematics
   
curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
   
search_text: Geometry
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.......token_count=40, total_token_count=772) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================

Based on the information provided, a 3-week teaching plan for Year 5 Mathematics focusing on Geometry could look like this:

**Week 1:  Introducing 2D Shapes**
........
* Use visuals, manipulatives, and real-world examples to make the learning experience engaging and relevant.

Ctrl+C চেপে স্ক্রিপ্ট বন্ধ করুন।

👉 Remove the testing code to keep your aidemy.py file clean (DO NOT SKIP THIS STEP!):

if __name__ == "__main__":
 
prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

With our agent logic now defined, let's launch the Flask web application. This will provide a familiar form-based interface for teachers to interact with the agent. While chatbot interactions are common with LLMs, we're opting for a traditional form submit UI, as it may be more intuitive for many educators.

If you've closed your terminal or the environment variables are no longer set, re-run the following commands

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Now, start the Web UI.

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py

Look for startup messages in the Cloud Shell terminal output. Flask usually prints messages indicating that it's running and on what port.

Running on http://127.0.0.1:8080
Running on http://127.0.0.1:8080
The application needs to keep running to serve requests.

👉From the "Web preview" menu, choose Preview on port 8080. Cloud Shell will open a new browser tab or window with the web preview of your application.

ওয়েব পেজ

In the application interface, select 5 for Year, select subject Mathematics and type in Geometry in the Add-on Request

Rather than staring blankly while waiting for the response, switch over to the Cloud Editor's terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

👉Stop the script by pressing Ctrl+C in the terminal.

👉Exit the virtual environment:

deactivate

8. Deploying planner agent to the cloud

Build and push image to registry

ওভারভিউ

👉Time to deploy this to the cloud. In the terminal, create an artifacts repository to store the docker image we are going to build.

gcloud artifacts repositories create agent-repository \
   
--repository-format=docker \
   
--location=us-central1 \
   
--description="My agent repository"

You should see Created repository [agent-repository].

👉Run the following command to build the Docker image.

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .

👉We need to retag the image so that it's hosted in Artifact Registry instead of GCR and push the tagged image to Artifact Registry:

export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

Once the push is complete, you can verify that the image is successfully stored in Artifact Registry. Navigate to the Artifact Registry in the Google Cloud Console. You should find the aidemy-planner image within the agent-repository repository. Aidemy planner image

Securing Database Credentials with Secret Manager

To securely manage and access database credentials, we'll use Google Cloud Secret Manager. This prevents hardcoding sensitive information in our application code and enhances security.

👉We'll create individual secrets for the database username, password, and database name. This approach allows us to manage each credential independently. In the terminal run:

gcloud secrets create db-user
printf "postgres" | gcloud secrets versions add db-user --data-file=-

gcloud secrets create db-pass
printf "1234qwer" | gcloud secrets versions add db-pass --data-file=-

gcloud secrets create db-name
printf "aidemy-db" | gcloud secrets versions add db-name --data-file=-

Using Secret Manager is a important step in securing your application and preventing accidental exposure of sensitive credentials. It follows security best practices for cloud deployments.

ক্লাউড রানে স্থাপন করুন

Cloud Run is a fully managed serverless platform that allows you to deploy containerized applications quickly and easily. It abstracts away the infrastructure management, letting you focus on writing and deploying your code. We'll be deploying our planner as a Cloud Run service.

👉In the Google Cloud Console, navigate to " Cloud Run ". Click on DEPLOY CONTAINER and select SERVICE . Configure your Cloud Run service:

Cloud run

  1. Container image : Click "Select" in the URL field. Find the image URL you pushed to Artifact Registry (eg, us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
  2. Service name : aidemy-planner
  3. Region : Select the us-central1 region.
  4. Authentication : For the purpose of this workshop, you can allow "Allow unauthenticated invocations". For production, you'll likely want to restrict access.
  5. Container(s) tab (Expand the Containers, Network):
    • Setting tab:
      • সম্পদ
        • memory : 2GiB
    • Variables & Secrets tab:
      • পরিবেশ পরিবর্তনশীল:
        • Add name: GOOGLE_CLOUD_PROJECT and value: <YOUR_PROJECT_ID>
        • Add name: BOOK_PROVIDER_URL , and set the value to your book-provider function URL, which you can determine using the following command in the terminal:
          gcloud run services describe book-provider \
             
          --region=us-central1 \
             
          --project=$PROJECT_ID \
             
          --format="value(status.url)"
      • Secrets exposed as environment variables:
        • Add name: DB_USER , secret: select db-user and version: latest
        • Add name: DB_PASS , secret: select db-pass and version: latest
        • Add name: DB_NAME , secret: select db-name and version: latest

Set secret

Leave other as default.

👉Click CREATE .

Cloud Run will deploy your service.

Once deployed, click on the service to its detail page, you can find the deployed URL available on the top.

URL

In the application interface, select 7 for the Year, choose Mathematics as the subject, and enter Algebra in the Add-on Request field. This will provide the agent with the necessary context to generate a tailored lesson plan.

অভিনন্দন! You've successfully created a teaching plan using our powerful AI agent. This demonstrates the potential of agents to significantly reduce workload and streamline tasks, ultimately improving efficiency and making life easier for educators.

9. মাল্টি-এজেন্ট সিস্টেম

Now that we've successfully implemented the teaching plan creation tool, let's shift our focus to building the student portal. This portal will provide students with access to quizzes, audio recaps, and assignments related to their coursework. Given the scope of this functionality, we'll leverage the power of multi-agent systems to create a modular and scalable solution.

As we discussed earlier, instead of relying on a single agent to handle everything, a multi-agent system allows us to break down the workload into smaller, specialized tasks, each handled by a dedicated agent. এই পদ্ধতিটি বেশ কয়েকটি মূল সুবিধা প্রদান করে:

Modularity and Maintainability : Instead of creating a single agent that does everything, build smaller, specialized agents with well-defined responsibilities. This modularity makes the system easier to understand, maintain, and debug. When a problem arises, you can isolate it to a specific agent, rather than having to sift through a massive codebase.

Scalability : Scaling a single, complex agent can be a bottleneck. With a multi-agent system, you can scale individual agents based on their specific needs. For example, if one agent is handling a high volume of requests, you can easily spin up more instances of that agent without affecting the rest of the system.

Team Specialization : Think of it like this: you wouldn't ask one engineer to build an entire application from scratch. Instead, you assemble a team of specialists, each with expertise in a particular area. Similarly, a multi-agent system allows you to leverage the strengths of different LLMs and tools, assigning them to agents that are best suited for specific tasks.

Parallel Development : Different teams can work on different agents concurrently, speeding up the development process. Since agents are independent, changes to one agent are less likely to impact other agents.

ইভেন্ট চালিত আর্কিটেকচার

To enable effective communication and coordination between these agents, we'll employ an event-driven architecture. This means that agents will react to "events" happening within the system.

Agents subscribe to specific event types (eg, "teaching plan generated," "assignment created"). When an event occurs, the relevant agents are notified and can react accordingly. This decoupling promotes flexibility, scalability, and real-time responsiveness.

ওভারভিউ

Now, to kick things off, we need a way to broadcast these events. To do this, we will set up a Pub/Sub topic. Let's start by creating a topic called plan .

👉Go to Google Cloud Console pub/sub and click on the "Create Topic" button.

👉Configure the Topic with ID/name plan and uncheck Add a default subscription , leave rest as default and click Create .

The Pub/Sub page will refresh, and you should now see your newly created topic listed in the table. টপিক তৈরি করুন

Now, let's integrate the Pub/Sub event publishing functionality into our planner agent. We'll add a new tool that sends a "plan" event to the Pub/Sub topic we just created. This event will signal to other agents in the system (like those in the student portal) that a new teaching plan is available.

👉Go back to the Cloud Code Editor and open the app.py file located in the planner folder. We will be adding a function that publishes the event. প্রতিস্থাপন:

##ADD SEND PLAN EVENT FUNCTION HERE

সঙ্গে

def send_plan_event(teaching_plan:str):
    """
    Send the teaching event to the topic called plan
   
    Args:
        teaching_plan: teaching plan
    """
   
publisher = pubsub_v1.PublisherClient()
   
print(f"-------------> Sending event to topic plan: {teaching_plan}")
   
topic_path = publisher.topic_path(PROJECT_ID, "plan")

   
message_data = {"teaching_plan": teaching_plan}
   
data = json.dumps(message_data).encode("utf-8")

   
future = publisher.publish(topic_path, data)

   
return f"Published message ID: {future.result()}"

  • send_plan_event : This function takes the generated teaching plan as input, creates a Pub/Sub publisher client, constructs the topic path, converts the teaching plan into a JSON string , publishes the message to the topic.

In the same app.py file

👉Update the prompt to instruct the agent to send the teaching plan event to the Pub/Sub topic after generating the teaching plan. প্রতিস্থাপন করুন

### ADD send_plan_event CALL

নিম্নলিখিত সঙ্গে:

send_plan_event(teaching_plan)

By adding the send_plan_event tool and modifying the prompt, we've enabled our planner agent to publish events to Pub/Sub, allowing other components of our system to react to the creation of new teaching plans. We will now have a functional multi-agent system in the following sections.

10. Empowering Students with On-Demand Quizzes

Imagine a learning environment where students have access to an endless supply of quizzes tailored to their specific learning plans. These quizzes provide immediate feedback, including answers and explanations, fostering a deeper understanding of the material. This is the potential we aim to unlock with our AI-powered quiz portal.

To bring this vision to life, we'll build a quiz generation component that can create multiple-choice questions based on the content of the teaching plan.

ওভারভিউ

👉In the Cloud Code Editor's Explorer pane, navigate to the portal folder. Open the quiz.py file copy and paste the following code to the end of the file.

def generate_quiz_question(file_name: str, difficulty: str, region:str ):
    """Generates a single multiple-choice quiz question using the LLM.
   
    ```json
    {
      "question": "The question itself",
      "options": ["Option A", "Option B", "Option C", "Option D"],
      "answer": "The correct answer letter (A, B, C, or D)"
    }
    ```
    """

   
print(f"region: {region}")
   
# Connect to resourse needed from Google Cloud
   
llm = VertexAI(model_name="gemini-1.5-pro", location=region)


   
plan=None
   
#load the file using file_name and read content into string call plan
   
with open(file_name, 'r') as f:
       
plan = f.read()

   
parser = JsonOutputParser(pydantic_object=QuizQuestion)


   
instruction = f"You'll provide one question with difficulty level of {difficulty}, 4 options as multiple choices and provide the anwsers, the quiz needs to be related to the teaching plan {plan}"

   
prompt = PromptTemplate(
       
template="Generates a single multiple-choice quiz question\n {format_instructions}\n  {instruction}\n",
       
input_variables=["instruction"],
       
partial_variables={"format_instructions": parser.get_format_instructions()},
   
)
   
   
chain = prompt | llm | parser
   
response = chain.invoke({"instruction": instruction})

   
print(f"{response}")
   
return  response


In the agent it creates a JSON output parser that's specifically designed to understand and structure the LLM's output. It uses the QuizQuestion model we defined earlier to ensure the parsed output conforms to the correct format (question, options, and answer).

👉Execute the following commands in the terminal to set up a virtual environment, install dependencies, and start the agent:

cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py

Use the Cloud Shell's web preview feature to access the running application. Click on the "Quizzes" link, either in the top navigation bar or from the card on the index page. You should see three randomly generated quizzes displayed for the student. These quizzes are based on the teaching plan and demonstrate the power of our AI-powered quiz generation system.

কুইজ

👉To stop the locally running process, press Ctrl+C in the terminal.

Gemini 2 Thinking for Explanations

Okay, so we've got quizzes, which is a great start! But what if students get something wrong? That's where the real learning happens, right? If we can explain why their answer was off and how to get to the correct one, they're way more likely to remember it. Plus, it helps clear up any confusion and boost their confidence.

That's why we're going to bring in the big guns: Gemini 2's "thinking" model! Think of it like giving the AI a little extra time to think things through before explaining. It lets it give more detailed and better feedback.

We want to see if it can help students by assisting, answering and explaining in detail. To test it out, we'll start with a notoriously tricky subject, Calculus.

ওভারভিউ

👉First, head over to the Cloud Code Editor, in answer.py inside the portal folder replace

def answer_thinking(question, options, user_response, answer, region):
   
return ""

with following code snippet:

def answer_thinking(question, options, user_response, answer, region):
   
try:
       
llm = VertexAI(model_name="gemini-2.0-flash-001",location=region)
       
       
input_msg = HumanMessage(content=[f"Here the question{question}, here are the available options {options}, this student's answer {user_response}, whereas the correct answer is {answer}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                       
"You are a helpful teacher trying to teach the student on question, you were given the question and a set of multiple choices "
                       
"what's the correct answer. use friendly tone"
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)
       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"



if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

This is a very simple langchain app where it Initializes the Gemini 2 Flash model, where we are instructing it to act as a helpful teacher and provide explanations

👉Execute the following command in the terminal:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

You should see output similar to the example provided in the original instructions. The current model may not provide as through explanation.

Okay, I see the question and the choices. The question is to evaluate the limit:

lim (x0) [(sin(5x) - 5x) / x^3]

You chose option B, which is -5/3, but the correct answer is A, which is -125/6.

It looks like you might have missed a step or made a small error in your calculations. This type of limit often involves using L'Hôpital's Rule or Taylor series expansion. Since we have the form 0/0, L'Hôpital's Rule is a good way to go! You need to apply it multiple times. Alternatively, you can use the Taylor series expansion of sin(x) which is:
sin(x) = x - x^3/3! + x^5/5! - ...
So, sin(5x) = 5x - (5x)^3/3! + (5x)^5/5! - ...
Then,  (sin(5x) - 5x) = - (5x)^3/3! + (5x)^5/5! - ...
Finally, (sin(5x) - 5x) / x^3 = - 5^3/3! + (5^5 * x^2)/5! - ...
Taking the limit as x approaches 0, we get -125/6.

Keep practicing, you'll get there!

In the answer.py file, replace the model_name from gemini-2.0-flash-001 to gemini-2.0-flash-thinking-exp-01-21 in the answer_thinking function.

This changes the LLM that reasons more, which will help it generate better explanations. And run it again.

👉Run to test the new thinking model:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

Here is an example of the response from the thinking model that is much more thorough and detailed, providing a step-by-step explanation of how to solve the calculus problem. This highlights the power of "thinking" models in generating high-quality explanations. You should see output similar to this:

Hey there! Let's take a look at this limit problem together. You were asked to evaluate:

lim (x0) [(sin(5x) - 5x) / x^3]

and you picked option B, -5/3, but the correct answer is actually A, -125/6. Let's figure out why!

It's a tricky one because if we directly substitute x=0, we get (sin(0) - 0) / 0^3 = (0 - 0) / 0 = 0/0, which is an indeterminate form. This tells us we need to use a more advanced technique like L'Hopital's Rule or Taylor series expansion.

Let's use the Taylor series expansion for sin(y) around y=0. Do you remember it?  It looks like this:

sin(y) = y - y^3/3! + y^5/5! - ...
where 3! (3 factorial) is 3 × 2 × 1 = 6, 5! is 5 × 4 × 3 × 2 × 1 = 120, and so on.

In our problem, we have sin(5x), so we can substitute y = 5x into the Taylor series:

sin(5x) = (5x) - (5x)^3/3! + (5x)^5/5! - ...
sin(5x) = 5x - (125x^3)/6 + (3125x^5)/120 - ...

Now let's plug this back into our limit expression:

[(sin(5x) - 5x) / x^3] =  [ (5x - (125x^3)/6 + (3125x^5)/120 - ...) - 5x ] / x^3
Notice that the '5x' and '-5x' cancel out!  So we are left with:
= [ - (125x^3)/6 + (3125x^5)/120 - ... ] / x^3
Now, we can divide every term in the numerator by x^3:
= -125/6 + (3125x^2)/120 - ...

Finally, let's take the limit as x approaches 0.  As x gets closer and closer to zero, terms with x^2 and higher powers will become very, very small and approach zero.  So, we are left with:
lim (x0) [ -125/6 + (3125x^2)/120 - ... ] = -125/6

Therefore, the correct answer is indeed **A) -125/6**.

It seems like your answer B, -5/3, might have come from perhaps missing a factor somewhere during calculation or maybe using an incorrect simplification. Double-check your steps when you were trying to solve it!

Don't worry, these limit problems can be a bit tricky sometimes! Keep practicing and you'll get the hang of it.  Let me know if you want to go through another similar example or if you have any more questions! 😊


Now that we have confirmed it works, let's use the portal.

👉 REMOVE the following test code from answer.py :

if __name__ == "__main__":
   
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
   
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
   
user_response = "B"
   
answer = "A"
   
region = "us-central1"
   
result = answer_thinking(question, options, user_response, answer, region)

👉Execute the following commands in the terminal to set up a virtual environment, install dependencies, and start the agent:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py

👉Use the Cloud Shell's web preview feature to access the running application. Click on the "Quizzes" link, answer all the quizzes and make sure at least get one answer wrong and click submit

thinking answers

Rather than staring blankly while waiting for the response, switch over to the Cloud Editor's terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

To stop the locally running process, press Ctrl+C in the terminal.

11. OPTIONAL: Orchestrating the Agents with Eventarc

So far, the student portal has been generating quizzes based on a default set of teaching plans. That's helpful, but it means our planner agent and portal's quiz agent aren't really talking to each other. Remember how we added that feature where the planner agent publishes its newly generated teaching plans to a Pub/Sub topic? Now it's time to connect that to our portal agent!

ওভারভিউ

We want the portal to automatically update its quiz content whenever a new teaching plan is generated. To do that, we'll create an endpoint in the portal that can receive these new plans.

👉In the Cloud Code Editor's Explorer pane, navigate to the portal folder. Open the app.py file for editing. Add the follow code in between ## Add your code here :

## Add your code here

@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
   
try:
       
       
# Get data from Pub/Sub message delivered via Eventarc
       
envelope = request.get_json()
       
if not envelope:
           
return jsonify({'error': 'No Pub/Sub message received'}), 400

       
if not isinstance(envelope, dict) or 'message' not in envelope:
           
return jsonify({'error': 'Invalid Pub/Sub message format'}), 400

       
pubsub_message = envelope['message']
       
print(f"data: {pubsub_message['data']}")

       
data = pubsub_message['data']
       
data_str = base64.b64decode(data).decode('utf-8')
       
data = json.loads(data_str)

       
teaching_plan = data['teaching_plan']

       
print(f"File content: {teaching_plan}")

       
with open("teaching_plan.txt", "w") as f:
           
f.write(teaching_plan)

       
print(f"Teaching plan saved to local file: teaching_plan.txt")

       
return jsonify({'message': 'File processed successfully'})


   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500
## Add your code here

Rebuilding and Deploying to Cloud Run

You'll need to update and redeploy both our planner and portal agents to Cloud Run. This ensures they have the latest code and are configured to communicate via events.

স্থাপনার ওভারভিউ

👉First we'll rebuild and push the planner agent image, back in the terminal run:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

👉We'll do the same, build and push the portal agent image:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

In Artifact Registry , you should see both the aidemy-planner and aidemy-portal container images listed.

Container Repo

👉Back in the terminal, run this to update the Cloud Run image for the planner agent:

export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-planner \
   
--region=us-central1 \
   
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner:latest

You should see output similar to this:

OK Deploying... Done.                                                                                                                                                     
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
Done.                                                                                                                                                                    
Service [aidemy-planner] revision [aidemy-planner-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-planner-xxx.us-central1.run.app

Make note of the Service URL; this is the link to your deployed planner agent. If you need to later determine the planner agent Service URL, use this command:

gcloud run services describe aidemy-planner \
   
--region=us-central1 \
   
--format 'value(status.url)'

👉Run this to create the Cloud Run instance for the portal agent

export PROJECT_ID=$(gcloud config get project)
gcloud run deploy aidemy-portal \
 
--image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal:latest \
 
--region=us-central1 \
 
--platform=managed \
 
--allow-unauthenticated \
 
--memory=2Gi \
 
--cpu=2 \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID}

You should see output similar to this:

Deploying container to Cloud Run service [aidemy-portal] in project [xxxx] region [us-central1]
OK Deploying new service... Done.                                                                                                                                        
 
OK Creating Revision...                                                                                                                                                
 
OK Routing traffic...                                                                                                                                                  
 
OK Setting IAM Policy...                                                                                                                                                
Done.                                                                                                                                                                    
Service [aidemy-portal] revision [aidemy-portal-xxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-portal-xxxx.us-central1.run.app

Make note of the Service URL; this is the link to your deployed student portal. If you need to later determine the student portal Service URL, use this command:

gcloud run services describe aidemy-portal \
   
--region=us-central1 \
   
--format 'value(status.url)'

Creating the Eventarc Trigger

But here's the big question: how does this endpoint get notified when there's a fresh plan waiting in the Pub/Sub topic? That's where Eventarc swoops in to save the day!

Eventarc acts as a bridge, listening for specific events (like a new message arriving in our Pub/Sub topic) and automatically triggering actions in response. In our case, it will detect when a new teaching plan is published and then send a signal to our portal's endpoint, letting it know that it's time to update.

With Eventarc handling the event-driven communication, we can seamlessly connect our planner agent and portal agent, creating a truly dynamic and responsive learning system. It's like having a smart messenger that automatically delivers the latest lesson plans to the right place!

👉In the console head to the Eventarc .

👉Click the "+ CREATE TRIGGER" button.

Configure the Trigger (Basics):

  • Trigger name: plan-topic-trigger
  • Trigger type: Google sources
  • Event provider: Cloud Pub/Sub
  • Event type: google.cloud.pubsub.topic.v1.messagePublished
  • Cloud Pub/Sub Topic: select projects/PROJECT_ID/topics/plan
  • Region: us-central1 .
  • Service account:
    • GRANT the service account with role roles/iam.serviceAccountTokenCreator
    • Use the default value: Default compute service account
  • Event destination: Cloud Run
  • Cloud Run service: aidemy-portal
  • Ignore error message: Permission denied on 'locations/me-central2' (or it may not exist).
  • Service URL path: /new_teaching_plan

"তৈরি করুন" এ ক্লিক করুন।

The Eventarc Triggers page will refresh, and you should now see your newly created trigger listed in the table.

👉Now, access the planner agent using its Service URL to request a new teaching plan.

Run this in the terminal to determine the planner agent Service URL:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

This time try Year 5 , Subject Science , and Add-on Request atoms .

Then, wait a minute or two, again this delay has been introduced due to billing limitation of this lab, under normal condition, there shouldn't be a delay.

Finally, access the student portal using its Service URL.

Run this in the terminal to determine the student portal agent Service URL:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

You should see that the quizzes have been updated and now align with the new teaching plan you just generated! This demonstrates the successful integration of Eventarc in the Aidemy system!

Aidemy-celebrate

অভিনন্দন! You've successfully built a multi-agent system on Google Cloud, leveraging event-driven architecture for enhanced scalability and flexibility! You've laid a solid foundation, but there's even more to explore. To delve deeper into the real benefits of this architecture, discover the power of Gemini 2's multimodal Live API, and learn how to implement single-path orchestration with LangGraph, feel free to continue on to the next two chapters.

12. OPTIONAL: Audio Recaps with Gemini

Gemini can understand and process information from various sources, like text, images, and even audio, opening up a whole new range of possibilities for learning and content creation. Gemini's ability to "see," "hear," and "read" truly unlocks creative and engaging user experiences.

Beyond just creating visuals or text, another important step in learning is effective summarization and recap. Think about it: how often do you remember a catchy song lyric more easily than something you read in a textbook? Sound can be incredibly memorable! That's why we're going to leverage Gemini's multimodal capabilities to generate audio recaps of our teaching plans. This will provide students with a convenient and engaging way to review material, potentially boosting retention and comprehension through the power of auditory learning.

Live API Overview

We need a place to store the generated audio files. Cloud Storage provides a scalable and reliable solution.

👉Head to the Storage in the console. Click on "Buckets" in the left-hand menu. Click on the "+ CREATE" button at the top.

👉Configure your new bucket:

  • bucket name: aidemy-recap-UNIQUE_NAME .
    • IMPORTANT : Ensure you define a unique bucket name that begins with aidemy-recap- . This unique prefix is crucial for avoiding naming conflicts when creating your Cloud Storage bucket.
  • region: us-central1 .
  • Storage class: "Standard". Standard is suitable for frequently accessed data.
  • Access control: Leave the default "Uniform" access control selected. This provides consistent, bucket-level access control.
  • Advanced options: For this workshop, the default settings are usually sufficient.

Click the CREATE button to create your bucket.

  • You may see a pop up about public access prevention. Leave the "Enforce public access prevention on this bucket" box checked and click Confirm .

You will now see your newly created bucket in the Buckets list. Remember your bucket name, you'll need it later.

👉In the Cloud Code Editor's terminal, run the following commands to grant the service account access to the bucket:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

👉In the Cloud Code Editor, open audio.py inside the courses folder. Paste the following code to the end of the file:

config = LiveConnectConfig(
   
response_modalities=["AUDIO"],
   
speech_config=SpeechConfig(
       
voice_config=VoiceConfig(
           
prebuilt_voice_config=PrebuiltVoiceConfig(
               
voice_name="Charon",
           
)
       
)
   
),
)

async def process_weeks(teaching_plan: str):
   
region = "us-east5" #To workaround onRamp quota limits
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
   
clientAudio = genai.Client(vertexai=True, project=PROJECT_ID, location="us-central1")
   
async with clientAudio.aio.live.connect(
       
model=MODEL_ID,
       
config=config,
   
) as session:
       
for week in range(1, 4):  
           
response = client.models.generate_content(
               
model="gemini-2.0-flash-001",
               
contents=f"Given the following teaching plan: {teaching_plan}, Extrace content plan for week {week}. And return just the plan, nothingh else  " # Clarified prompt
           
)

           
prompt = f"""
                Assume you are the instructor.  
                Prepare a concise and engaging recap of the key concepts and topics covered.
                This recap should be suitable for generating a short audio summary for students.
                Focus on the most important learnings and takeaways, and frame it as a direct address to the students.  
                Avoid overly formal language and aim for a conversational tone, tell a few jokes.
               
                Teaching plan: {response.text} """
           
print(f"prompt --->{prompt}")

           
await session.send(input=prompt, end_of_turn=True)
           
with open(f"temp_audio_week_{week}.raw", "wb") as temp_file:
               
async for message in session.receive():
                   
if message.server_content.model_turn:
                       
for part in message.server_content.model_turn.parts:
                           
if part.inline_data:
                               
temp_file.write(part.inline_data.data)
                           
           
data, samplerate = sf.read(f"temp_audio_week_{week}.raw", channels=1, samplerate=24000, subtype='PCM_16', format='RAW')
           
sf.write(f"course-week-{week}.wav", data, samplerate)
       
           
storage_client = storage.Client()
           
bucket = storage_client.bucket(BUCKET_NAME)
           
blob = bucket.blob(f"course-week-{week}.wav")  # Or give it a more descriptive name
           
blob.upload_from_filename(f"course-week-{week}.wav")
           
print(f"Audio saved to GCS: gs://{BUCKET_NAME}/course-week-{week}.wav")
   
await session.close()

 
def breakup_sessions(teaching_plan: str):
   
asyncio.run(process_weeks(teaching_plan))
  • Streaming Connection : First, a persistent connection is established with the Live API endpoint. Unlike a standard API call where you send a request and get a response, this connection remains open for a continuous exchange of data.
  • Configuration Multimodal : Use configuration to specifying what type of output you want (in this case, audio), and you can even specify what parameters you'd like to use (eg, voice selection, audio encoding)
  • Asynchronous Processing : This API works asynchronously, meaning it doesn't block the main thread while waiting for the audio generation to complete. By processing data in real-time and sending the output in chunks, it provides a near-instantaneous experience.

Now, the key question is: when should this audio generation process run? Ideally, we want the audio recaps to be available as soon as a new teaching plan is created. Since we've already implemented an event-driven architecture by publishing the teaching plan to a Pub/Sub topic, we can simply subscribe to that topic.

However, we don't generate new teaching plans very often. It wouldn't be efficient to have an agent constantly running and waiting for new plans. That's why it makes perfect sense to deploy this audio generation logic as a Cloud Run Function.

By deploying it as a function, it remains dormant until a new message is published to the Pub/Sub topic. When that happens, it automatically triggers the function, which generates the audio recaps and stores them in our bucket.

👉Under the courses folder in main.py file, this file defines the Cloud Run Function that will be triggered when a new teaching plan is available. It receives the plan and initiates the audio recap generation. Add the following code snippet to the end of the file.

@functions_framework.cloud_event
def process_teaching_plan(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")
   
time.sleep(60)
   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):  # Check for base64 encoding
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan') # Get the teaching plan
       
elif 'teaching_plan' in cloud_event.data: # No base64
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found") # Handle error explicitly

       
#Load the teaching_plan as string and from cloud event, call audio breakup_sessions
       
breakup_sessions(teaching_plan)

       
return "Teaching plan processed successfully", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error processing teaching plan: {e}")
       
return "Error processing teaching plan", 500

@functions_framework.cloud_event : This decorator marks the function as a Cloud Run Function that will be triggered by CloudEvents.

Testing locally

👉We'll run this in a virtual environment and install the necessary Python libraries for the Cloud Run function.

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉The Cloud Run Function emulator allows us to test our function locally before deploying it to Google Cloud. Start a local emulator by running:

functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py

👉While the emulator is running, you can send test CloudEvents to the emulator to simulate a new teaching plan being published. In a new terminal:

Two terminal

👉Run:

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

Rather than staring blankly while waiting for the response, switch over to the other Cloud Shell terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

Back in the 2nd terminal you should see it should returned OK .

👉You'll verify Data in bucket, go to Cloud Storage and select the "Bucket" tab and then the aidemy-recap-UNIQUE_NAME

বালতি

👉In the terminal running the emulator, type ctrl+c to exit. And close the second terminal. And close the second terminal. and run deactivate to exit the virtual environment.

deactivate

Deploying to Google Cloud

স্থাপনার ওভারভিউ 👉After testing locally, it's time to deploy the course agent to Google Cloud. In the terminal, run these commands:

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud functions deploy courses-agent \
 
--region=us-central1 \
 
--gen2 \
 
--source=. \
 
--runtime=python312 \
 
--trigger-topic=plan \
 
--entry-point=process_teaching_plan \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

Verify deployment by going Cloud Run in the Google Cloud Console.You should see a new service named courses-agent listed.

Cloud Run List

To check the trigger configuration, click on the courses-agent service to view its details. Go to the "TRIGGERS" tab.

You should see a trigger configured to listen for messages published to the plan topic.

Cloud Run Trigger

Finally, let's see it running end to end.

👉We need to configure the portal agent so it knows where to find the generated audio files. টার্মিনালে, চালান:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Try generating a new teaching plan using the planner agent web page. It might take a few minutes to start, don't be alarmed, it's a serverless service.

To access the planner agent, get its Service URL by running this in the terminal:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep planner

After generating the new plan, wait 2-3 minutes for the audio to be generated, again this will take a few more minutes due to billing limitation with this lab account.

You can monitor whether the courses-agent function has received the teaching plan by checking the function's "TRIGGERS" tab. Refresh the page periodically; you should eventually see that the function has been invoked. If the function hasn't been invoked after more than 2 minutes, you can try generating the teaching plan again. However, avoid generating plans repeatedly in quick succession, as each generated plan will be sequentially consumed and processed by the agent, potentially creating a backlog.

Trigger Observe

👉Visit the portal and click on "Courses". You should see three cards, each displaying an audio recap. To find the URL of your portal agent:

gcloud run services list \
   
--platform=managed \
   
--region=us-central1 \
   
--format='value(URL)' | grep portal

Click "play" on each course to ensure the audio recaps are aligned with the teaching plan you just generated! Portal Courses

Exit the virtual environment.

deactivate

13. OPTIONAL: Role-Based collaboration with Gemini and DeepSeek

Having multiple perspectives is invaluable, especially when crafting engaging and thoughtful assignments. We'll now build a multi-agent system that leverages two different models with distinct roles, to generate assignments: one promotes collaboration, and the other encourages self-study. We'll use a "single-shot" architecture, where the workflow follows a fixed route.

Gemini Assignment Generator

মিথুন ওভারভিউ We'll start by setting up the Gemini function to generate assignments with a collaborative emphasis. Edit the gemini.py file located in the assignment folder.

👉Paste the following code to the end of the gemini.py file:

def gen_assignment_gemini(state):
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
print(f"---------------gen_assignment_gemini")
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        You are an instructor

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {state["teaching_plan"]}
        """
   
)

   
print(f"---------------gen_assignment_gemini answer {response.text}")
   
   
state["model_one_assignment"] = response.text
   
   
return state


import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

It uses the Gemini model to generate assignments.

We are ready to test the Gemini Agent.

👉Run these commands in the terminal to setup the environment:

cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉You can run to test it:

python gemini.py

You should see an assignment that has more group work in the output. The assert test at the end will also output the results.

Here are some engaging and practical assignments for each week, designed to build progressively upon the teaching plan's objectives:

**Week 1: Exploring the World of 2D Shapes**

* **Learning Objectives Assessed:**
   
* Identify and name basic 2D shapes (squares, rectangles, triangles, circles).
   
* .....

* **Description:**
   
* **Shape Scavenger Hunt:** Students will go on a scavenger hunt in their homes or neighborhoods, taking pictures of objects that represent different 2D shapes. They will then create a presentation or poster showcasing their findings, classifying each shape and labeling its properties (e.g., number of sides, angles, etc.).
   
* **Triangle Trivia:** Students will research and create a short quiz or presentation about different types of triangles, focusing on their properties and real-world examples.
   
* **Angle Exploration:** Students will use a protractor to measure various angles in their surroundings, such as corners of furniture, windows, or doors. They will record their measurements and create a chart categorizing the angles as right, acute, or obtuse.
....

**Week 2: Delving into the World of 3D Shapes and Symmetry**

* **Learning Objectives Assessed:**
   
* Identify and name basic 3D shapes.
   
* ....

* **Description:**
   
* **3D Shape Construction:** Students will work in groups to build 3D shapes using construction paper, cardboard, or other materials. They will then create a presentation showcasing their creations, describing the number of faces, edges, and vertices for each shape.
   
* **Symmetry Exploration:** Students will investigate the concept of symmetry by creating a visual representation of various symmetrical objects (e.g., butterflies, leaves, snowflakes) using drawing or digital tools. They will identify the lines of symmetry and explain their findings.
   
* **Symmetry Puzzles:** Students will be given a half-image of a symmetrical figure and will be asked to complete the other half, demonstrating their understanding of symmetry. This can be done through drawing, cut-out activities, or digital tools.

**Week 3: Navigating Position, Direction, and Problem Solving**

* **Learning Objectives Assessed:**
   
* Describe position using coordinates in the first quadrant.
   
* ....

* **Description:**
   
* **Coordinate Maze:** Students will create a maze using coordinates on a grid paper. They will then provide directions for navigating the maze using a combination of coordinate movements and translation/reflection instructions.
   
* **Shape Transformations:** Students will draw shapes on a grid paper and then apply transformations such as translation and reflection, recording the new coordinates of the transformed shapes.
   
* **Geometry Challenge:** Students will solve real-world problems involving perimeter, area, and angles. For example, they could be asked to calculate the perimeter of a room, the area of a garden, or the missing angle in a triangle.
....

Stop with ctl+c , and to clean up the test code. REMOVE the following code from gemini.py

import unittest

class TestGenAssignmentGemini(unittest.TestCase):
   
def test_gen_assignment_gemini(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

       
updated_state = gen_assignment_gemini(initial_state)

       
self.assertIn("model_one_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_one_assignment"])
       
self.assertIsInstance(updated_state["model_one_assignment"], str)
       
self.assertGreater(len(updated_state["model_one_assignment"]), 0)
       
print(updated_state["model_one_assignment"])


if __name__ == '__main__':
   
unittest.main()

Configure the DeepSeek Assignment Generator

While cloud-based AI platforms are convenient, self-hosting LLMs can be crucial for protecting data privacy and ensuring data sovereignty. We'll deploy the smallest DeepSeek model (1.5B parameters) on a Cloud Compute Engine instance. There are other ways like hosting it on Google's Vertex AI platform or hosting it on your GKE instance, but since this is just a workshop on AI agents, and I don't want to keep you here forever, let's just use the most simplest way. But if you are interested and want to dig into other options, take a look at deepseek-vertexai.py file under assignment folder, where it provides an sample code of how to interact with models deployed on VertexAI.

Deepseek Overview

👉Run this command in the terminal to create a self-hosted LLM platform Ollama:

cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
   
--image-family=ubuntu-2204-lts \
   
--image-project=ubuntu-os-cloud \
   
--machine-type=e2-standard-4 \
   
--zone=us-central1-a \
   
--metadata-from-file startup-script=startup.sh \
   
--boot-disk-size=50GB \
   
--tags=ollama \
   
--scopes=https://www.googleapis.com/auth/cloud-platform

To verify the Compute Engine instance is running:

Navigate to Compute Engine > "VM instances" in the Google Cloud Console. You should see the ollama-instance listed with a green check mark indicating that it's running. If you can't see it, make sure the zone is us-central1. If it's not, you may need to search for it.

Compute Engine List

👉We'll install the smallest DeepSeek model and test it, back in the Cloud Shell Editor, in a New terminal, run following command to ssh into the GCE instance.

gcloud compute ssh ollama-instance --zone=us-central1-a

Upon establishing the SSH connection, you may be prompted with the following:

"Do you want to continue (Y/n)?"

Simply type Y (case-insensitive) and press Enter to proceed.

Next, you might be asked to create a passphrase for the SSH key. If you prefer not to use a passphrase, just press Enter twice to accept the default (no passphrase).

👉Now you are in the virutal machine, pull the smallest DeepSeek R1 model, and test if it works?

ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"

👉Exit the GCE instance enter following in the ssh terminal:

exit

👉Next, setup the network policy, so other services can access the LLM, please limit the access to the instance if you want to do this for production, either implement security login for the service or restrict IP access. চালান:

gcloud compute firewall-rules create allow-ollama-11434 \
   
--allow=tcp:11434 \
   
--target-tags=ollama \
   
--description="Allow access to Ollama on port 11434"

👉To verify if your firewall policy is working correctly, try running:

export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
curl -X POST "${OLLAMA_HOST}/api/generate" \
     
-H "Content-Type: application/json" \
     
-d '{
         
"prompt": "Hello, what are you?",
         
"model": "deepseek-r1:1.5b",
         
"stream": false
       
}'

Next, we'll work on the Deepseek function in the assignment agent to generate assignments with individual work emphasis.

👉Edit deepseek.py under assignment folder add following snippet to the end:

def gen_assignment_deepseek(state):
   
print(f"---------------gen_assignment_deepseek")

   
template = """
        You are an instructor who favor student to focus on individual work.

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {teaching_plan}
        """

   
   
prompt = ChatPromptTemplate.from_template(template)

   
model = OllamaLLM(model="deepseek-r1:1.5b",
                   
base_url=OLLAMA_HOST)

   
chain = prompt | model


   
response = chain.invoke({"teaching_plan":state["teaching_plan"]})
   
state["model_two_assignment"] = response
   
   
return state

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

👉let's test it by running:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
python deepseek.py

You should see an assignment that has more self study work.

**Assignment Plan for Each Week**

---

### **Week 1: 2D Shapes and Angles**
- **Week Title:** "Exploring 2D Shapes"
Assign students to research and present on various 2D shapes. Include a project where they create models using straws and tape for triangles, draw quadrilaterals with specific measurements, and compare their properties.

### **Week 2: 3D Shapes and Symmetry**
Assign students to create models or nets for cubes and cuboids. They will also predict how folding these nets form the 3D shapes. Include a project where they identify symmetrical properties using mirrors or folding techniques.

### **Week 3: Position, Direction, and Problem Solving**

Assign students to use mirrors or folding techniques for reflections. Include activities where they measure angles, use a protractor, solve problems involving perimeter/area, and create symmetrical designs.
....

👉Stop the ctl+c , and to clean up the test code. REMOVE the following code from deepseek.py

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
   
def test_gen_assignment_deepseek(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

       
updated_state = gen_assignment_deepseek(initial_state)

       
self.assertIn("model_two_assignment", updated_state)
       
self.assertIsNotNone(updated_state["model_two_assignment"])
       
self.assertIsInstance(updated_state["model_two_assignment"], str)
       
self.assertGreater(len(updated_state["model_two_assignment"]), 0)
       
print(updated_state["model_two_assignment"])


if __name__ == '__main__':
   
unittest.main()

Now, we'll use the same gemini model to combine both assignments into a new one. Edit the gemini.py file located in the assignment folder.

👉Paste the following code to the end of the gemini.py file:

def combine_assignments(state):
   
print(f"---------------combine_assignments ")
   
region=get_next_region()
   
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
   
response = client.models.generate_content(
       
model=MODEL_ID, contents=f"""
        Look at all the proposed assignment so far {state["model_one_assignment"]} and {state["model_two_assignment"]}, combine them and come up with a final assignment for student.
        """
   
)

   
state["final_assignment"] = response.text
   
   
return state

To combine the strengths of both models, we'll orchestrate a defined workflow using LangGraph. This workflow consists of three steps: first, the Gemini model generates an assignment focused on collaboration; second, the DeepSeek model generates an assignment emphasizing individual work; finally, Gemini synthesizes these two assignments into a single, comprehensive assignment. Because we predefine the sequence of steps without LLM decision-making, this constitutes a single-path, user-defined orchestration.

Langraph combine overview

👉Paste the following code to the end of the main.py file under assignment folder:

def create_assignment(teaching_plan: str):
   
print(f"create_assignment---->{teaching_plan}")
   
builder = StateGraph(State)
   
builder.add_node("gen_assignment_gemini", gen_assignment_gemini)
   
builder.add_node("gen_assignment_deepseek", gen_assignment_deepseek)
   
builder.add_node("combine_assignments", combine_assignments)
   
   
builder.add_edge(START, "gen_assignment_gemini")
   
builder.add_edge("gen_assignment_gemini", "gen_assignment_deepseek")
   
builder.add_edge("gen_assignment_deepseek", "combine_assignments")
   
builder.add_edge("combine_assignments", END)

   
graph = builder.compile()
   
state = graph.invoke({"teaching_plan": teaching_plan})

   
return state["final_assignment"]



import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

👉To initially test the create_assignment function and confirm that the workflow combining Gemini and DeepSeek is functional, run the following command:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py

You should see something that combine both models with their individual perspective for student study and also for student group works.

**Tasks:**

1. **Clue Collection:** Gather all the clues left by the thieves. These clues will include:
   
* Descriptions of shapes and their properties (angles, sides, etc.)
   
* Coordinate grids with hidden messages
   
* Geometric puzzles requiring transformation (translation, reflection, rotation)
   
* Challenges involving area, perimeter, and angle calculations

2. **Clue Analysis:** Decipher each clue using your geometric knowledge. This will involve:
   
* Identifying the shape and its properties
   
* Plotting coordinates and interpreting patterns on the grid
   
* Solving geometric puzzles by applying transformations
   
* Calculating area, perimeter, and missing angles

3. **Case Report:** Create a comprehensive case report outlining your findings. This report should include:
   
* A detailed explanation of each clue and its solution
   
* Sketches and diagrams to support your explanations
   
* A step-by-step account of how you followed the clues to locate the artifact
   
* A final conclusion about the thieves and their motives

👉Stop the ctl+c , and to clean up the test code. REMOVE the following code from main.py

import unittest

class TestCreateAssignment(unittest.TestCase):
   
def test_create_assignment(self):
       
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
       
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
       
updated_state = create_assignment(initial_state)
       
       
print(updated_state)


if __name__ == '__main__':
   
unittest.main()

Generate Assignment.png

To make the assignment generation process automatic and responsive to new teaching plans, we'll leverage the existing event-driven architecture. The following code defines a Cloud Run Function (generate_assignment) that will be triggered whenever a new teaching plan is published to the Pub/Sub topic ' plan '.

👉Add the following code to the end of main.py in the assignment folder:

@functions_framework.cloud_event
def generate_assignment(cloud_event):
   
print(f"CloudEvent received: {cloud_event.data}")

   
try:
       
if isinstance(cloud_event.data.get('message', {}).get('data'), str):
           
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
           
teaching_plan = data.get('teaching_plan')
       
elif 'teaching_plan' in cloud_event.data:
           
teaching_plan = cloud_event.data["teaching_plan"]
       
else:
           
raise KeyError("teaching_plan not found")

       
assignment = create_assignment(teaching_plan)

       
print(f"Assignment---->{assignment}")

       
#Store the return assignment into bucket as a text file
       
storage_client = storage.Client()
       
bucket = storage_client.bucket(ASSIGNMENT_BUCKET)
       
file_name = f"assignment-{random.randint(1, 1000)}.txt"
       
blob = bucket.blob(file_name)
       
blob.upload_from_string(assignment)

       
return f"Assignment generated and stored in {ASSIGNMENT_BUCKET}/{file_name}", 200

   
except (json.JSONDecodeError, AttributeError, KeyError) as e:
       
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
       
return "Error processing event", 500

   
except Exception as e:
       
print(f"Error generate assignment: {e}")
       
return "Error generate assignment", 500

Testing locally

Before deploying to Google Cloud, it's good practice to test the Cloud Run Function locally. This allows for faster iteration and easier debugging.

First, create a Cloud Storage bucket to store the generated assignment files and grant the service account access to the bucket. Run the following commands in the terminal:

👉 IMPORTANT : Ensure you define a unique ASSIGNMENT_BUCKET name that begins with " aidemy-assignment- ". This unique name is crucial for avoiding naming conflicts when creating your Cloud Storage bucket. (Replace <YOUR_NAME> with any random word)

export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue

👉And run:

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gsutil mb -p $PROJECT_ID -l us-central1 gs://$ASSIGNMENT_BUCKET

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
   
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
   
--role "roles/storage.objectCreator"

👉Now, start the Cloud Run Function emulator:

cd ~/aidemy-bootstrap/assignment
functions-framework \
   
--target generate_assignment \
   
--signature-type=cloudevent \
   
--source main.py

👉While the emulator is running in one terminal, open a second terminal in the Cloud Shell. In this second terminal, send a test CloudEvent to the emulator to simulate a new teaching plan being published:

Two terminal

  curl -X POST \
 
http://localhost:8080/ \
 
-H "Content-Type: application/json" \
 
-H "ce-id: event-id-01" \
 
-H "ce-source: planner-agent" \
 
-H "ce-specversion: 1.0" \
 
-H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
 
-d '{
   
"message": {
     
"data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
   
}
 
}'

Rather than staring blankly while waiting for the response, switch over to the other Cloud Shell terminal. You can observe the progress and any output or error messages generated by your function in the emulator's terminal. 😁

The curl command should print "OK" (without a newline, so "OK" may appear on the same line your terminal shell prompt).

To confirm that the assignment was successfully generated and stored, go to the Google Cloud Console and navigate to Storage > "Cloud Storage". Select the aidemy-assignment bucket you created. You should see a text file named assignment-{random number}.txt in the bucket. Click on the file to download it and verify its contents. This verifies that a new file contains new assignment just generated.

12-01-assignment-bucket

👉In the terminal running the emulator, type ctrl+c to exit. And close the second terminal. 👉Also, in the terminal running the emulator, exit the virtual environment.

deactivate

স্থাপনার ওভারভিউ

👉Next, we'll deploy the assignment agent to the cloud

cd ~/aidemy-bootstrap/assignment
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
export PROJECT_ID=$(gcloud config get project)
gcloud functions deploy assignment-agent \
 
--gen2 \
 
--timeout=540 \
 
--memory=2Gi \
 
--cpu=1 \
 
--set-env-vars="ASSIGNMENT_BUCKET=${ASSIGNMENT_BUCKET}" \
 
--set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
 
--set-env-vars=OLLAMA_HOST=${OLLAMA_HOST} \
 
--region=us-central1 \
 
--runtime=python312 \
 
--source=. \
 
--entry-point=generate_assignment \
 
--trigger-topic=plan

Verify deployment by going to Google Cloud Console, navigate to Cloud Run. You should see a new service named courses-agent listed. 12-03-function-list

With the assignment generation workflow now implemented and tested and deployed, we can move on to the next step: making these assignments accessible within the student portal.

14. OPTIONAL: Role-Based collaboration with Gemini and DeepSeek - Contd.

Dynamic website generation

To enhance the student portal and make it more engaging, we'll implement dynamic HTML generation for assignment pages. The goal is to automatically update the portal with a fresh, visually appealing design whenever a new assignment is generated. This leverages the LLM's coding capabilities to create a more dynamic and interesting user experience.

14-01-generate-html

👉In Cloud Shell Editor, edit the render.py file within the portal folder, replace

def render_assignment_page():
   
return ""

with following code snippet:

def render_assignment_page(assignment: str):
   
try:
       
region=get_next_region()
       
llm = VertexAI(model_name="gemini-2.0-flash-001", location=region)
       
input_msg = HumanMessage(content=[f"Here the assignment {assignment}"])
       
prompt_template = ChatPromptTemplate.from_messages(
           
[
               
SystemMessage(
                   
content=(
                        """
                        As a frontend developer, create HTML to display a student assignment with a creative look and feel. Include the following navigation bar at the top:
                        ```
                        <nav>
                            <a href="/">Home</a>
                            <a href="/quiz">Quizzes</a>
                            <a href="/courses">Courses</a>
                            <a href="/assignment">Assignments</a>
                        </nav>
                        ```
                        Also include these links in the <head> section:
                        ```
                        <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
                        <link rel="preconnect" href="https://fonts.googleapis.com">
                        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                        <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">

                        ```
                        Do not apply inline styles to the navigation bar.
                        The HTML should display the full assignment content. In its CSS, be creative with the rainbow colors and aesthetic.
                        Make it creative and pretty
                        The assignment content should be well-structured and easy to read.
                        respond with JUST the html file
                        """
                   
)
               
),
               
input_msg,
           
]
       
)

       
prompt = prompt_template.format()
       
       
response = llm.invoke(prompt)

       
response = response.replace("```html", "")
       
response = response.replace("```", "")
       
with open("templates/assignment.html", "w") as f:
           
f.write(response)


       
print(f"response: {response}")

       
return response
   
except Exception as e:
       
print(f"Error sending message to chatbot: {e}") # Log this error too!
       
return f"Unable to process your request at this time. Due to the following reason: {str(e)}"

It uses the Gemini model to dynamically generate HTML for the assignment. It takes the assignment content as input and uses a prompt to instruct Gemini to create a visually appealing HTML page with a creative style.

Next, we'll create an endpoint that will be triggered whenever a new document is added to the assignment bucket:

👉Within the portal folder, edit the app.py file and add the following code within the ## Add your code here" comments , AFTER the new_teaching_plan function:

## Add your code here

def new_teaching_plan():
       
...
       
...
       
...

   
except Exception as e:
       
...
       
...

@app.route('/render_assignment', methods=['POST'])
def render_assignment():
   
try:
       
data = request.get_json()
       
file_name = data.get('name')
       
bucket_name = data.get('bucket')

       
if not file_name or not bucket_name:
           
return jsonify({'error': 'Missing file name or bucket name'}), 400

       
storage_client = storage.Client()
       
bucket = storage_client.bucket(bucket_name)
       
blob = bucket.blob(file_name)
       
content = blob.download_as_text()

       
print(f"File content: {content}")

       
render_assignment_page(content)

       
return jsonify({'message': 'Assignment rendered successfully'})

   
except Exception as e:
       
print(f"Error processing file: {e}")
       
return jsonify({'error': 'Error processing file'}), 500

## Add your code here

When triggered, it retrieves the file name and bucket name from the request data, downloads the assignment content from Cloud Storage, and calls the render_assignment_page function to generate the HTML.

👉We'll go ahead and run it locally:

cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py

👉From the "Web preview" menu at the top of the Cloud Shell window, select "Preview on port 8080". This will open your application in a new browser tab. Navigate to the Assignment link in the navigation bar. You should see a blank page at this point, which is expected behavior since we haven't yet established the communication bridge between the assignment agent and the portal to dynamically populate the content.

14-02-deployment-overview

o ahead and stop the script by pressing Ctrl+C .

👉To incorporate these changes and deploy the updated code, rebuild and push the portal agent image:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

👉After pushing the new image, redeploy the Cloud Run service. Run the following script to force the Cloud Run update:

export PROJECT_ID=$(gcloud config get project)
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud run services update aidemy-portal \
   
--region=us-central1 \
   
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Now, we'll deploy an Eventarc trigger that listens for any new object created (finalized) in the assignment bucket. This trigger will automatically invoke the /render_assignment endpoint on the portal service when a new assignment file is created.

export PROJECT_ID=$(gcloud config get project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
 
--member="serviceAccount:$(gcloud storage service-agent --project $PROJECT_ID)" \
 
--role="roles/pubsub.publisher"
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud eventarc triggers create portal-assignment-trigger \
--location=us-central1 \
--service-account=$SERVICE_ACCOUNT_NAME \
--destination-run-service=aidemy-portal \
--destination-run-region=us-central1 \
--destination-run-path="/render_assignment" \
--event-filters="bucket=$ASSIGNMENT_BUCKET" \
--event-filters="type=google.cloud.storage.object.v1.finalized"

To verify that the trigger was created successfully, navigate to the Eventarc Triggers page in the Google Cloud Console. You should see portal-assignment-trigger listed in the table. Click on the trigger name to view its details. Assignment Trigger

It may take up to 2-3 minutes for the new trigger to become active.

To see the dynamic assignment generation in action, run the following command to find the URL of your planner agent (if you don't have it handy):

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

Find the URL of your portal agent:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

In the planner agent, generate a new teaching plan.

13-02-assignment

After a few minutes (to allow for the audio generation, assignment generation, and HTML rendering to complete), navigate to the student portal.

👉Click on the "Assignment" link in the navigation bar. You should see a newly created assignment with a dynamically generated HTML. Each time a teaching plan is generated it should be a dynamic assignment.

13-02-assignment

Congratulations on completing the Aidemy multi-agent system ! You've gained practical experience and valuable insights into:

  • The benefits of multi-agent systems, including modularity, scalability, specialization, and simplified maintenance.
  • The importance of event-driven architectures for building responsive and loosely coupled applications.
  • The strategic use of LLMs, matching the right model to the task and integrating them with tools for real-world impact.
  • Cloud-native development practices using Google Cloud services to create scalable and reliable solutions.
  • The importance of considering data privacy and self-hosting models as an alternative to vendor solutions.

You now have a solid foundation for building sophisticated AI-powered applications on Google Cloud!

15. চ্যালেঞ্জ এবং পরবর্তী পদক্ষেপ

Congratulations on building the Aidemy multi-agent system! You've laid a strong foundation for AI-powered education. Now, let's consider some challenges and potential future enhancements to further expand its capabilities and address real-world needs:

Interactive Learning with Live Q&A:

  • Challenge: Can you leverage Gemini 2's Live API to create a real-time Q&A feature for students? Imagine a virtual classroom where students can ask questions and receive immediate, AI-powered responses.

Automated Assignment Submission and Grading:

  • Challenge: Design and implement a system that allows students to submit assignments digitally and have them automatically graded by AI, with a mechanism to detect and prevent plagiarism. This challenge presents a great opportunity to explore Retrieval Augmented Generation (RAG) to enhance the accuracy and reliability of the grading and plagiarism detection processes.

aidemy-climb

16. পরিষ্কার করুন

Now that we've built and explored our Aidemy multi-agent system, it's time to clean up our Google Cloud environment.

👉Delete Cloud Run services

gcloud run services delete aidemy-planner --region=us-central1 --quiet
gcloud run services delete aidemy-portal --region=us-central1 --quiet
gcloud run services delete courses-agent --region=us-central1 --quiet
gcloud run services delete book-provider --region=us-central1 --quiet
gcloud run services delete assignment-agent --region=us-central1 --quiet

👉Delete Eventarc trigger

gcloud eventarc triggers delete portal-assignment-trigger --location=us --quiet
gcloud eventarc triggers delete plan-topic-trigger --location=us-central1 --quiet
gcloud eventarc triggers delete portal-assignment-trigger --location=us-central1 --quiet
ASSIGNMENT_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:assignment-agent" --format="value(name)")
COURSES_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:courses-agent" --format="value(name)")
gcloud eventarc triggers delete $ASSIGNMENT_AGENT_TRIGGER --location=us-central1 --quiet
gcloud eventarc triggers delete $COURSES_AGENT_TRIGGER --location=us-central1 --quiet

👉Delete Pub/Sub topic

gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet

👉Delete Cloud SQL instance

gcloud sql instances delete aidemy --quiet

👉Delete Artifact Registry repository

gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet

👉Delete Secret Manager secrets

gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet

👉Delete Compute Engine instance (if created for Deepseek)

gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet

👉Delete the firewall rule for Deepseek instance

gcloud compute firewall-rules delete allow-ollama-11434 --quiet

👉Delete Cloud Storage buckets

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
gsutil rm -r gs://$COURSE_BUCKET_NAME
gsutil rm -r gs://$ASSIGNMENT_BUCKET

aidemy-broom