1. はじめに
平素より Google 広告をご利用いただき、誠にありがとうございます。エージェントという考え方に興味をお持ちですね。エージェントとは、指一本動かすことなく、さまざまな作業をこなしてくれる小さなヘルパーのことです。問題ないようです。ただし、1 人のエージェントでは、特に大規模で複雑なプロジェクトに対応できない場合もあります。おそらく、そのような人材のチームが必要になるでしょう。そこでマルチエージェント システムが役立ちます。
LLM を活用したエージェントは、従来のハードコーディングと比べて非常に柔軟性があります。ただし、これには独自の難しさがあります。このワークショップでは、この点について詳しく説明します。
エージェントとしてのレベルアップにつながる内容を学ぶことができます。
LangGraph を使用して最初のエージェントを構築する: 人気のあるフレームワークである LangGraph を使用して、独自のエージェントを構築します。データベースに接続するツールを作成する方法、最新の Gemini 2 API を使用してインターネット検索を行う方法、プロンプトとレスポンスを最適化して、エージェントが LLM だけでなく既存のサービスともやり取りできるようにする方法について学びます。また、関数の呼び出しの仕組みについても説明します。
エージェントのオーケストレーション: 単純な直線パスから複雑なマルチパス シナリオまで、エージェントをオーケストレートするさまざまな方法について説明します。エージェント チームの流れを監督すると考えてください。
マルチエージェント システム: イベントドリブン アーキテクチャを使用して、エージェントが連携して作業を完了できるシステムを構築する方法を学びます。
LLM の自由度 - 最適なものを使用: 1 つの LLM に固執する必要はありません。複数の LLM を使用して、それぞれに異なる役割を割り当て、優れた「思考モデル」を使用して問題解決能力を高める方法について説明します。
動的コンテンツとは、問題ありません。エージェントが、ユーザーごとにカスタマイズされた動的コンテンツをリアルタイムで作成するとします。方法を説明します。
Google Cloud でクラウドに移行する: ノートブックで試すだけではありません。Google Cloud でマルチエージェント システムを設計してデプロイし、実環境に対応できるようにする方法を説明します。
このプロジェクトは、これまで説明してきたすべてのテクニックの使用方法を示す良い例になります。
2. アーキテクチャ
教師として、または教育機関で働くことは非常にやりがいのある仕事ですが、ワークロード、特に準備作業は大変なものになることがあります。また、多くの場合、スタッフが不足しており、補習は高額になる可能性があります。そのため、Google は AI を活用したティーチング アシスタントを提案しています。このツールは、教師の負担を軽減し、スタッフ不足や手頃な価格の家庭教師の不足によって生じるギャップを埋めるのに役立ちます。
Google の AI 教師アシスタントは、詳細な授業計画、楽しいクイズ、わかりやすい音声の要約、生徒に合わせた課題を作成できます。これにより、教師は生徒とつながり、生徒が学習に興味を持つように支援するという、自分の得意分野に集中できます。
このシステムには 2 つのサイトがあります。1 つは教師が今後の授業計画を作成するためのサイトです。
1 つは生徒がクイズ、音声要約、課題にアクセスするためのものです。
では、ティーチング アシスタントである Aidemy を支えるアーキテクチャについて説明します。ご覧のとおり、このプロセスは複数の主要コンポーネントに分かれており、すべてが連携してこのプロセスを実現しています。
主なアーキテクチャ要素とテクノロジー:
Google Cloud Platform(GCP): システム全体の中心となるものです。
- Vertex AI: Google の Gemini LLM にアクセスします。
- Cloud Run: コンテナ化されたエージェントと関数をデプロイするためのサーバーレス プラットフォーム。
- Cloud SQL: カリキュラム データ用の PostgreSQL データベース。
- Pub/Sub と Eventarc: コンポーネント間の非同期通信を可能にする、イベントドリブン アーキテクチャの基盤。
- Cloud Storage: 音声要約と課題ファイルを保存します。
- Secret Manager: データベース認証情報を安全に管理します。
- Artifact Registry: エージェントの Docker イメージを保存します。
- Compute Engine: ベンダー ソリューションに依存せずにセルフホスト LLM をデプロイする
LLM: システムの「頭脳」:
- Google の Gemini モデル:(Gemini 1.0 Pro、Gemini 2 Flash、Gemini 2 Flash Thinking、Gemini 1.5-pro)レッスン計画、コンテンツ生成、動的 HTML の作成、クイズの説明、課題の組み合わせに使用されます。
- DeepSeek: 自習課題を生成する特殊なタスクに使用
LangChain と LangGraph: LLM アプリケーション開発用のフレームワーク
- 複雑なマルチエージェント ワークフローの作成を容易にします。
- ツール(API 呼び出し、データベース クエリ、ウェブ検索)のインテリジェントなオーケストレーションを可能にします。
- システムのスケーラビリティと柔軟性を実現するために、イベント ドリブン アーキテクチャを実装します。
本質的には、このアーキテクチャは LLM の能力と構造化データ、イベントドリブン通信を組み合わせたもので、すべて Google Cloud で実行されます。これにより、スケーラブルで信頼性が高く、効果的なティーチング アシスタントを構築できます。
3. 始める前に
Google Cloud コンソールのプロジェクト選択ページで、Google Cloud プロジェクトを選択または作成します。Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
👉Google Cloud コンソールの上部にある [Cloud Shell をアクティブにする](Cloud Shell ペインの上部にあるターミナル形のアイコン)をクリックし、[エディタを開く] ボタン(開いたフォルダに鉛筆が描かれているアイコン)をクリックします。ウィンドウに Cloud Shell コードエディタが開きます。左側にファイル エクスプローラが表示されます。
👉下のステータスバーにある [Cloud Code ログイン] ボタンをクリックします。指示に従ってプラグインを承認します。ステータスバーに「Cloud Code - no project」と表示されている場合は、それを選択して、プルダウンの [Google Cloud プロジェクトを選択] で、作成したプロジェクトのリストから特定の Google Cloud プロジェクトを選択します。
👉クラウド IDE でターミナルを開きます。
👉ターミナルで、次のコマンドを使用して、すでに認証されていること、プロジェクトがプロジェクト ID に設定されていることを確認します。
gcloud auth list
👉次のコマンドを実行します。
gcloud config set project <YOUR_PROJECT_ID>
👉次のコマンドを実行して、必要な Google Cloud API を有効にします。
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
これには数分かかることがあります。
Cloud Shell IDE で Gemini Code Assist を有効にする
左側のパネルにある [Code Assist] ボタンをクリックし、正しい Google Cloud プロジェクトをもう一度選択します。Cloud AI Companion API を有効にするよう求められたら、有効にして先に進みます。Google Cloud プロジェクトを選択したら、下の図のようにステータスバーの Cloud Code ステータス メッセージが表示されていることと、その右側で Code Assist が有効になっていることを確認します。
権限を設定する
👉サービス アカウントの権限を設定する
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(読み取り/書き込み):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/storage.objectAdmin"
👉Pub/Sub(パブリッシュ/受信):
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(読み取り/書き込み):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/cloudsql.editor"
👉Eventarc(イベントの受信):
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(ユーザー):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/aiplatform.user"
👉Secret Manager(読み取り):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/secretmanager.secretAccessor"
👉IAM コンソールで結果を確認します。
4. 最初のエージェントを構築する
複雑なマルチエージェント システムに進む前に、基本的な構成要素である単一の機能エージェントを構築する必要があります。このセクションでは、まず簡単な「書籍プロバイダ」エージェントを作成します。書籍プロバイダ エージェントは、カテゴリを入力として受け取り、Gemini LLM を使用して、そのカテゴリ内の書籍の JSON 表現を生成します。これらの書籍のおすすめは、REST API エンドポイントとして提供されます。
👉別のブラウザタブで、ウェブブラウザで Google Cloud コンソールを開き、ナビゲーション メニュー(☰)で [Cloud Run] に移動します。[+ ... 関数を作成] ボタンをクリックします。
👉次に、Cloud Run 関数の基本設定を構成します。
- サービス名:
book-provider
- リージョン:
us-central1
- ランタイム:
Python 3.12
- Authentication:
Allow unauthenticated invocations
を [Enabled] に変更します。
👉その他の設定はデフォルトのままにして、[作成] をクリックします。ソースコード エディタが表示されます。
main.py
ファイルと requirements.txt
ファイルが事前入力されています。
main.py
には関数のビジネス ロジックが含まれ、requirements.txt
には必要なパッケージが含まれます。
👉それでは、コードを記述していきます。始める前に、Gemini Code Assist で作業を効率化できないか確認しましょう。Cloud Shell エディタに戻り、Gemini Code Assist アイコンをクリックして、次のリクエストをプロンプト ボックスに貼り付けます。
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 によって潜在的な解決策が生成され、ソースコードと requirements.txt 依存関係ファイルの両方が提供されます。
Code Assist によって生成されたコードを、以下のテスト済みの正しいソリューションと比較することをおすすめします。これにより、ツールの効果を評価し、潜在的な不一致を特定できます。LLM を盲目的に信頼することは決してありませんが、Code Assist は迅速なプロトタイピングと初期コード構造の生成に優れたツールであり、早い段階から使用する必要があります。
これはワークショップであるため、以下に示す検証済みのコードを使用して進めます。ただし、Code Assist によって生成されたコードを自由に試して、その機能と制限事項を詳しく理解してください。
👉Cloud Run 関数のソースコード エディタ(別のブラウザタブ)に戻ります。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-1.0-pro")
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)
👉requirements.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
を設定します。
👉[保存してデプロイ] をクリックして関数をデプロイします。デプロイ プロセスが完了するまで待ちます。Cloud Console にステータスが表示されます。この作業には数分かかることがあります。
👉デプロイしたら、Cloud Shell エディタに戻り、ターミナルで次のコマンドを実行します。
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"}
]
これで、Cloud Run 関数が正常にデプロイされました。これは、Aidemy エージェントの開発時に統合するサービスの 1 つです。
5. 構築ツール: エージェントを RESTFUL サービスとデータに接続する
では、Bootstrap スケルトン プロジェクトをダウンロードしましょう。Cloud Shell エディタで作業していることを確認してください。ターミナルで、
git clone https://github.com/weimeilin79/aidemy-bootstrap.git
このコマンドを実行すると、Cloud Shell 環境に aidemy-bootstrap
という名前の新しいフォルダが作成されます。
Cloud Shell エディタのエクスプローラ ペイン(通常は左側)に、Git リポジトリ aidemy-bootstrap
のクローンを作成したときに作成されたフォルダが表示されます。エクスプローラでプロジェクトのルートフォルダを開きます。その中に planner
サブフォルダがあるので、そちらも開きます。
エージェントが本当に役立つツールを構築しましょう。LLM は推論とテキスト生成に優れていますが、現実世界のタスクを実行し、正確で最新の情報を提供するためには、外部リソースにアクセスする必要があります。これらのツールは、エージェントが外部とやり取りするための「スイス アーミーナイフ」のようなものです。
エージェントを構築する際に、大量の詳細をハードコードしてしまうことはよくあります。これにより、柔軟性のないエージェントが作成されます。代わりに、ツールを作成して使用することで、エージェントは外部ロジックまたはシステムにアクセスできるため、LLM と従来のプログラミングの両方のメリットを享受できます。
このセクションでは、教師が指導案の作成に使用するプランナー エージェントの基盤を作成します。エージェントがプランの生成を開始する前に、教科とトピックの詳細を指定することで、範囲を設定します。次の 3 つのツールを作成します。
- RESTful API 呼び出し: 既存の API を操作してデータを取得します。
- データベース クエリ: Cloud SQL データベースから構造化データを取得します。
- Google 検索: ウェブからリアルタイムの情報にアクセスします。
API から書籍のおすすめを取得する
まず、前のセクションでデプロイした book-provider API から書籍のおすすめを取得するツールを作成しましょう。これは、エージェントが既存のサービスを活用する方法を示しています。
Cloud Shell エディタで、前のセクションでクローンを作成した 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 インタラクション: LLM を使用してクエリからカテゴリを抽出します。これは、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
Git の警告ポップアップ ウィンドウは無視します。
book-provider API から取得した書籍のおすすめが含まれる 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 呼び出しを明示的に作成するのではなく、自然言語(「コースを受講しています...」)を使用しています。エージェントは、NLP を使用して必要なパラメータ(カテゴリなど)をインテリジェントに抽出します。これは、エージェントが自然言語理解を活用して 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."))
データベースからカリキュラム データを取得する
次に、Cloud SQL PostgreSQL データベースから構造化されたカリキュラム データを取得するツールを構築します。これにより、エージェントは信頼できる情報源にアクセスして授業計画を立てることができます。
👉ターミナルで次のコマンドを実行して、aidemy という名前の Cloud SQL インスタンスを作成します。このプロセスには時間がかかることがあります。
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
👉次に、新しいインスタンスに aidemy-db
という名前のデータベースを作成します。
gcloud sql databases create aidemy-db \
--instance=aidemy
Google Cloud コンソールの Cloud SQL でインスタンスを確認します。aidemy
という名前の Cloud SQL インスタンスが表示されます。インスタンス名をクリックして、詳細を表示します。Cloud SQL インスタンスの詳細ページで、左側のナビゲーション メニューの [SQL Studio] をクリックします。新しいタブが開きます。
クリックしてデータベースに接続します。SQL Studio にログインします。
データベースとして 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 コードを実行します。コマンドが正常に実行されたことを示す確認メッセージが表示されます。
👉エクスプローラを開き、新しく作成したテーブルを見つけて [クエリ] をクリックします。新しいエディタタブが開き、生成された SQL が表示されます。
SELECT * FROM
"public"."curriculums" LIMIT 1000;
👉[実行] をクリックします。
結果テーブルに、前の手順で挿入したデータの行が表示され、テーブルとデータが正しく作成されたことを確認します。
カリキュラムのサンプルデータが入力されたデータベースが正常に作成されたので、次はそれを取得するツールを作成します。
👉Cloud Code エディタで、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(): この関数は、Cloud SQL コネクタを使用してデータベースへの安全な接続を確立します。
- 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 検索の統合を使用してウェブからリアルタイムの情報にアクセスするツールを作成します。これにより、エージェントは最新の情報を把握し、関連性の高い結果を提供できます。
Gemini 2 と Google Search API の統合により、より正確でコンテキストに沿った関連性の高い検索結果を提供できるため、エージェントの機能が強化されます。これにより、エージェントは最新の情報にアクセスし、回答を現実世界のデータに基づいて行えるため、ハルシネーションを最小限に抑えることができます。API 統合の改善により、より自然な言語のクエリが容易になり、エージェントは複雑で微妙な検索リクエストを作成できるようになります。
この関数は、検索クエリ、カリキュラム、科目、年を入力として受け取り、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): この関数は、検索クエリ、主題、年を入力として受け取り、Gemini API を使用して Google 検索を実行します。Gemini モデル
- GenerateContentConfig: GoogleSearch ツールにアクセスできることを定義します。
Gemini モデルは、search_text を内部で分析し、質問に直接回答できるかどうか、または GoogleSearch ツールを使用する必要があるかどうかを判断します。これは、LLM の推論プロセス内で行われる重要なステップです。このモデルは、外部ツールが必要な状況を認識するようにトレーニングされています。モデルが GoogleSearch ツールを使用することを決定した場合、Google 生成 AI SDK が実際の呼び出しを処理します。SDK は、モデルの決定と生成されたパラメータを Google Search API に送信します。この部分は、コード内でユーザーには非表示になります。
Gemini モデルは、検索結果をレスポンスに統合します。この情報を使用して、ユーザーの質問に回答したり、要約を生成したり、その他のタスクを実行したりできます。
👉テストするには、次のコードを実行します。
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py
「Syllabus for Year 5 Mathematics」に関連する検索結果を含む Gemini Search API レスポンスが表示されます。正確な出力は検索結果によって異なりますが、検索に関する情報が含まれた JSON オブジェクトになります。
検索結果が表示された場合は、Google 検索ツールが正しく動作しています。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
これで、これで、プランナー エージェント向けの 3 つの強力なツール(API コネクタ、データベース コネクタ、Google 検索ツール)が完成しました。これらのツールを使用すると、エージェントは効果的な指導計画を作成するために必要な情報と機能にアクセスできます。
7. LangGraph を使用したオーケストレーション
個々のツールを作成したので、LangGraph を使用してオーケストレートします。これにより、ユーザーのリクエストに基づいて、どのツールをいつ使用するかをインテリジェントに判断できる、より高度な「プランナー」エージェントを作成できるようになります。
LangGraph は、大規模言語モデル(LLM)を使用してステートフルなマルチアクター アプリケーションを簡単に構築できるように設計された Python ライブラリです。LLM、ツール、その他のエージェントが関与する複雑な会話やワークフローをオーケストレートするためのフレームワークと考えてください。
主なコンセプト:
- グラフ構造: LangGraph は、アプリケーションのロジックを有向グラフとして表します。グラフ内の各ノードは、プロセス内のステップを表します(LLM の呼び出し、ツールの呼び出し、条件チェックなど)。エッジは、ノード間の実行フローを定義します。
- 状態: LangGraph は、グラフ内を移動するアプリケーションの状態を管理します。この状態には、ユーザーの入力、ツール呼び出しの結果、LLM からの中間出力など、ステップ間で保持する必要があるその他の情報などの変数を含めることができます。
- ノード: 各ノードは計算またはインタラクションを表します。次のいずれかです。
- ツールノード: ツールを使用する(ウェブ検索、データベースのクエリなど)
- 関数ノード: Python 関数を実行します。
- エッジ: ノードを接続して、実行フローを定義します。次のいずれかです。
- 直接エッジ: ノード間の単純な無条件フロー。
- 条件付きエッジ: フローは、条件付きノードの結果によって異なります。
ここでは、LangGraph を使用してオーケストレーションを実装します。aidemy-bootstrap
フォルダの aidemy.py
ファイルを編集して、LangGraph ロジックを定義しましょう。👉次のコードを 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 にレスポンスを生成するよう依頼します。LLM は、ユーザーに直接応答することも、利用可能なツールのいずれかを使用することもできます。
tools : このリストには、エージェントが利用できるツールのセットが表示されます。これには、前の手順で定義した 3 つのツール関数(get_curriculum
、search_latest_resource
、recommend_book
)が含まれています。llm.bind_tools(tools): ツールリストを llm オブジェクトに「バインド」します。ツールをバインディングすると、LLM にこれらのツールが使用可能であることが通知され、ツールの使用方法(ツールの名前、受け入れるパラメータ、機能など)に関する情報が LLM に提供されます。
ここでは、LangGraph を使用してオーケストレーションを実装します。👉次のコードを 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
は LangGraph の中核的な概念です。エージェントのワークフローをグラフとして表します。グラフ内の各ノードはプロセスのステップを表します。エージェントが推論して行動する方法のブループリントを定義すると考えるとわかりやすいでしょう。- 条件付きエッジ:
"determine_tool"
ノードから出発するtools_condition
引数は、determine_tool
関数の出力に基づいてどのエッジに進むかを決定する関数である可能性があります。条件付きエッジを使用すると、使用するツール(またはユーザーに直接返信するかどうか)に関する LLM の決定に基づいてグラフを分岐できます。ここで、エージェントの「インテリジェンス」が機能します。エージェントは状況に応じて動作を動的に適応させることができます。 - ループ:
"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
ターミナルでログを確認します。エージェントが最終的な指導計画を提供する前に、3 つのツール(学校のカリキュラムの取得、書籍のおすすめの取得、最新のリソースの検索)をすべて呼び出していることを示す証拠が必要です。これは、LangGraph のオーケストレーションが正しく機能しており、エージェントが利用可能なすべてのツールをインテリジェントに使用してユーザーのリクエストを処理していることを示しています。
================================ 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 ツールのみです。これは、プロンプトで他の 2 つのツールが必要であることが指定されていないためです。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")
エージェント ロジックを定義したので、Flask ウェブ アプリケーションを起動しましょう。これにより、教師は使い慣れたフォームベースのインターフェースでエージェントとやり取りできるようになります。LLM ではチャットボットによるやり取りが一般的ですが、多くの教育者にとって直感的である可能性があるため、Google は従来のフォーム送信 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
Cloud Shell ターミナルの出力で起動メッセージを探します。通常、Flask は、実行中であることと、どのポートで実行されているかを示すメッセージを出力します。
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 でプレビュー] を選択します。Cloud Shell で、アプリケーションのウェブプレビューを含む新しいブラウザタブまたはブラウザ ウィンドウが開きます。
申請インターフェースで、年次に 5
を選択し、件名に Mathematics
を選択し、アドオン リクエストに Geometry
と入力します。
レスポンスを待っている間、じっと見つめているのではなく、Cloud エディタのターミナルに切り替えます。エミュレータのターミナルで、関数によって生成された進行状況と出力またはエラー メッセージを確認できます。😁
👉ターミナルで Ctrl+C
を押してスクリプトを停止します。
👉仮想環境を終了します。
deactivate
8. プランナー エージェントをクラウドにデプロイする
イメージをビルドしてレジストリに push する
👉次に、これをクラウドにデプロイします。ターミナルで、ビルドする Docker イメージを保存するアーティファクト リポジトリを作成します。
gcloud artifacts repositories create agent-repository \
--repository-format=docker \
--location=us-central1 \
--description="My agent repository"
作成されたリポジトリ [agent-repository] が表示されます。
👉次のコマンドを実行して Docker イメージをビルドします。
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
👉イメージを再タグ付けして、GCR ではなく Artifact Registry でホストされるようにし、タグ付けされたイメージを Artifact Registry に push する必要があります。
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
push が完了したら、イメージが Artifact Registry に正常に保存されていることを確認できます。Google Cloud コンソールで Artifact Registry に移動します。agent-repository
リポジトリ内に aidemy-planner
イメージが表示されます。
Secret Manager でデータベース認証情報を保護する
データベースの認証情報を安全に管理してアクセスするには、Google Cloud Secret Manager を使用します。これにより、機密情報をアプリケーション コードにハードコードすることを回避し、セキュリティを強化できます。
👉データベースのユーザー名、パスワード、データベース名の個別のシークレットを作成します。この方法では、各認証情報を個別に管理できます。ターミナルで次のコマンドを実行します。
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=-
Secret Manager を使用することは、アプリケーションを保護し、機密性の高い認証情報の偶発的な漏洩を防ぐうえで重要なステップです。クラウド デプロイのセキュリティのベスト プラクティスに準拠しています。
Cloud Run へのデプロイ
Cloud Run は、コンテナ化されたアプリケーションを迅速かつ簡単にデプロイできるフルマネージド サーバーレス プラットフォームです。インフラストラクチャ管理が抽象化されるため、コードの作成とデプロイに集中できます。プランナーは Cloud Run サービスとしてデプロイします。
👉Google Cloud コンソールで、[Cloud Run] に移動します。[コンテナをデプロイ] をクリックし、[サービス] を選択します。Cloud Run サービスを構成します。
- コンテナ イメージ: URL フィールドで [選択] をクリックします。Artifact Registry に push したイメージの URL を確認します(例: us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG)。
- サービス名:
aidemy-planner
- リージョン:
us-central1
リージョンを選択します。 - 認証: このワークショップでは、[未認証の呼び出しを許可] を許可できます。本番環境では、アクセスを制限することをおすすめします。
- [コンテナ] タブ([コンテナ]、[ネットワーク] を展開):
- 設定タブ:
- リソース
- メモリ : 2 GB
- リソース
- [変数とシークレット] タブ:
- 環境変数:
- 名前:
GOOGLE_CLOUD_PROJECT
、値: <YOUR_PROJECT_ID> を追加します。 - 名前:
BOOK_PROVIDER_URL
、値: <YOUR_BOOK_PROVIDER_FUNCTION_URL> を追加します。
- 名前:
- 環境変数として公開されるシークレット:
- 名前:
DB_USER
、シークレット:db-user
を選択、バージョン:latest
を追加します。 - 名前:
DB_PASS
、シークレット:db-pass
を選択、バージョン:latest
を追加します。 - 名前:
DB_NAME
、シークレット:db-name
を選択、バージョン:latest
を追加します。
- 名前:
- 環境変数:
- 設定タブ:
YOUR_BOOK_PROVIDER_FUNCTION_URL を取得する必要がある場合は、ターミナルで次のコマンドを実行します。
gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)"
その他の項目はデフォルトのままにします。
👉[作成] をクリックします。
Cloud Run によってサービスがデプロイされます。
デプロイしたら、サービスをクリックして詳細ページに移動します。デプロイされた URL が上部に表示されます。
申請インターフェースで、年として 7
を選択し、件名として Mathematics
を選択し、[アドオン リクエスト] フィールドに Algebra
と入力します。これにより、エージェントは、カスタマイズされたレッスンプランを生成するために必要なコンテキストを把握できます。
これで、強力な AI エージェントを使用して、指導計画を作成できました。これは、エージェントがワークロードを大幅に削減し、タスクを効率化することで、最終的に効率を高め、教育者の負担を軽減する可能性を示しています。
9. マルチエージェント システム
授業計画作成ツールを正常に実装できたので、次は生徒用ポータルの作成に移りましょう。このポータルでは、コースワークに関連するクイズ、音声要約、課題にアクセスできます。この機能の範囲を考慮して、マルチエージェント システムの力を活用し、モジュラーでスケーラブルなソリューションを構築します。
前述のように、マルチエージェント システムでは、単一のエージェントにすべてを処理させるのではなく、ワークロードを小さな専門的なタスクに分割し、それぞれを専用のエージェントが処理できます。このアプローチには次のような利点があります。
モジュール性とメンテナンス性: すべてを行う単一のエージェントを作成するのではなく、責任が明確に定義された小さな専門エージェントを構築します。このモジュラー化により、システムの理解、メンテナンス、デバッグが容易になります。問題が発生した場合、膨大なコードベースを調べるのではなく、特定のエージェントに問題を切り分けることができます。
スケーラビリティ: 単一の複雑なエージェントをスケーリングすると、ボトルネックになる可能性があります。マルチエージェント システムでは、特定のニーズに基づいて個々のエージェントをスケーリングできます。たとえば、1 つのエージェントが大量のリクエストを処理している場合、システムの他の部分に影響を与えることなく、そのエージェントのインスタンスを簡単に増やすことができます。
チームの専門分野: 1 人のエンジニアにアプリケーション全体をゼロから構築するよう依頼することはありません。代わりに、それぞれが特定の分野の専門知識を持つスペシャリストのチームを編成します。同様に、マルチエージェント システムでは、さまざまな LLM とツールの強みを活用し、特定のタスクに最適なエージェントに割り当てることができます。
並列開発: 異なるチームが異なるエージェントに対して同時に作業できるため、開発プロセスを迅速化できます。エージェントは独立しているため、1 つのエージェントを変更しても他のエージェントに影響する可能性は低くなります。
イベント ドリブン アーキテクチャ
これらのエージェント間で効果的な通信と調整を可能にするため、イベント ドリブン アーキテクチャを採用します。つまり、エージェントはシステム内で発生する「イベント」に反応します。
エージェントは特定のイベントタイプ(「指導計画が生成されました」、「課題が作成されました」)。イベントが発生すると、関連するエージェントに通知が届き、それに応じて対応できます。この分離により、柔軟性、スケーラビリティ、リアルタイムの応答性が向上します。
では、まずこれらのイベントをブロードキャストする方法が必要です。そのために、Pub/Sub トピックを設定します。まず、plan というトピックを作成しましょう。
👉Google Cloud コンソールの Pub/Sub に移動し、[トピックを作成] ボタンをクリックします。
👉ID または名前が plan
のトピックを構成し、[Add a default subscription
] のチェックを解除して、残りはデフォルトのままにして [作成] をクリックします。
Pub/Sub ページが更新され、新しく作成したトピックが表に表示されます。
次に、Pub/Sub イベントのパブリッシュ機能をプランナー エージェントに統合しましょう。作成した Pub/Sub トピックに「plan」イベントを送信する新しいツールを追加します。このイベントは、新しい指導計画が利用可能であることをシステム内の他のエージェント(生徒ポータルのエージェントなど)に通知します。
👉Cloud Code エディタに戻り、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: この関数は、生成された指導計画を入力として受け取り、Pub/Sub パブリッシャー クライアントを作成し、トピックパスを構築し、指導計画を JSON 文字列に変換して、トピックにメッセージを公開します。
- ツールリスト:
send_plan_event
関数がツールリストに追加され、エージェントが使用できるようになりました。
同じフォルダの app.py
ファイルで、プロンプトを更新して、教材プランの生成後に教材プラン イベントを Pub/Sub トピックに送信するようにエージェントに指示します。次のように置き換えます。
### ADD send_plan_event CALL
これを次のコードに置き換えます。
send_plan_event(teaching_plan)
send_plan_event ツールを追加してプロンプトを変更することで、プランナー エージェントが Pub/Sub にイベントをパブリッシュできるようにしました。これにより、システムの他のコンポーネントが新しい教材プランの作成に反応できるようになりました。以降のセクションでは、機能するマルチエージェント システムを構築します。
10. オンデマンド クイズで生徒を支援する
生徒が自分の学習計画に合わせて、無限に用意されたクイズにアクセスできる学習環境を想像してみてください。これらのクイズでは、回答や説明などのフィードバックが即座に提供され、教材の理解を深めることができます。こうした可能性を AI を活用したクイズ ポータルで引き出すことを目指しています。
このビジョンを実現するために、指導計画の内容に基づいて多肢選択式の問題を作成できるクイズ生成コンポーネントを構築します。
👉Cloud Code エディタの [エクスプローラ] ペインで、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
エージェントでは、LLM の出力を理解して構造化するように特別に設計された JSON 出力パーサーが作成されます。これは、前述の QuizQuestion
モデルを使用して、解析された出力が正しい形式(質問、オプション、回答)に準拠するようにします。
👉ターミナルで次のコマンドを実行して、仮想環境を設定し、依存関係をインストールして、エージェントを起動します。
cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py
Cloud Shell のウェブ プレビュー機能を使用して、実行中のアプリケーションにアクセスします。上部のナビゲーション バーまたはインデックス ページのカードにある [クイズ] リンクをクリックします。ランダムに生成された 3 つのクイズが生徒に表示されます。これらのクイズは教科書に基づいており、AI を活用したクイズ生成システムの威力を示しています。
ローカルで実行されているプロセスを停止するには、ターミナルで Ctrl+C
を押します。
Gemini 2 の説明のための思考
クイズは良いスタートです。生徒が間違えた場合はどうすればよいですか?実際の学習はそこで行われますよね。回答が間違っている理由と正しい回答にたどり着く方法を説明すると、正しい回答を覚えられる可能性が高まります。また、混乱を解消し、自信を高めることにもつながります。
そこで、Gemini 2 の「考える」モデルを導入します。説明する前に AI に考える時間を少し長く与えるようなイメージです。これにより、より詳細で質の高いフィードバックを返すことができます。
生徒をサポートし、質問に答え、詳細を説明することで、生徒の役に立つかどうかを検証します。テストとして、難解な科目として知られる微積分学から始めましょう。
👉まず、Cloud Code エディタに移動し、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)
これは非常にシンプルなランチェーン アプリで、Gemini 2 Flash モデルを初期化し、有益な教師として説明を提供するように指示しています。
👉ターミナルで次のコマンドを実行します。
cd ~/aidemy-bootstrap/portal/
python answer.py
元の手順に記載されている例に似た出力が表示されます。現在のモデルでは、詳細な説明が提供されない場合があります。
Okay, I see the question and the choices. The question is to evaluate the limit:
lim (x→0) [(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!
answer.py ファイルの answer_thinking 関数で、model_name を gemini-2.0-flash-001
から gemini-2.0-flash-thinking-exp-01-21
に置き換えます。
これにより、より多くの推論を行う LLM が変更され、より適切な説明を生成できるようになります。もう一度実行します。
👉実行して新しい思考モデルをテストします。
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 (x→0) [(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 (x→0) [ -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.
👉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
👉Cloud Shell のウェブ プレビュー機能を使用して、実行中のアプリケーションにアクセスします。[Quizzes] リンクをクリックし、すべてのクイズに回答して、少なくとも 1 つの回答を間違えてから [送信] をクリックします。
レスポンスを待っている間、じっと見つめているのではなく、Cloud エディタのターミナルに切り替えます。エミュレータのターミナルで、関数によって生成された進行状況と出力またはエラー メッセージを確認できます。😁
ローカルで実行されているプロセスを停止するには、ターミナルで Ctrl+C
を押します。
11. Eventarc によるエージェントのオーケストレーション
これまで、生徒用ポータルでは、デフォルトの一連の指導計画に基づいてクイズが生成されていました。これは便利ですが、プランナー エージェントとポータルのクイズ エージェントが実際にやり取りしていないことを意味します。プランナー エージェントが新しく生成された教材計画を Pub/Sub トピックにパブリッシュする機能を追加したときのことを思い出してください。次に、それをポータル エージェントに接続します。
新しい指導計画が生成されるたびに、ポータルでクイズの内容が自動的に更新されるようにしたい。そのためには、これらの新しいプランを受信できるエンドポイントをポータルに作成します。
👉Cloud Code エディタの [エクスプローラ] ペインで、portal
フォルダに移動します。app.py
ファイルを開いて編集します。## 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
再ビルドして Cloud Run にデプロイする
プランナー エージェントとポータル エージェントの両方を更新して Cloud Run に再デプロイする必要があります。これにより、最新のコードが使用され、イベントを介して通信するように構成されます。
👉次に、ターミナルに戻って planner エージェント イメージを再ビルドして push します。
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
👉同様に、ポータル エージェント イメージをビルドして push します。
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
Artifact Registry に、aidemy-planner
コンテナ イメージと aidemy-portal
コンテナ イメージの両方が表示されます。
👉ターミナルに戻り、次のコマンドを実行してプランナー エージェントの Cloud Run イメージを更新します。
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 をメモします。これは、デプロイされたプランナー エージェントへのリンクです。
👉これを実行して、ポータル エージェントの Cloud Run インスタンスを作成します。
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 をメモします。これは、デプロイされた生徒用ポータルへのリンクです。
Eventarc トリガーを作成する
しかし、大きな問題があります。Pub/Sub トピックで新しいプランが待機しているときに、このエンドポイントに通知するにはどうすればよいでしょうか。そこで、Eventarc が救いの手を差し伸べます。
Eventarc はブリッジとして機能し、特定のイベント(Pub/Sub トピックに到着した新しいメッセージなど)をリッスンし、それに応じてアクションを自動的にトリガーします。たとえば、新しい指導計画が公開されたことを検出して、ポータルのエンドポイントにシグナルを送信し、更新のタイミングであることを知らせます。
Eventarc がイベントドリブン コミュニケーションを処理することで、プランナー エージェントとポータル エージェントをシームレスに接続し、真に動的でレスポンシブな学習システムを構築できます。最新のレッスンプランを適切な場所に自動的に配信するスマート メッセンジャーが使えるようなものです。
👉コンソールで [Eventarc] に移動します。
👉[+ トリガーを作成] ボタンをクリックします。
トリガーを構成する(基本):
- トリガー名:
plan-topic-trigger
- トリガーの種類: Google ソース。
- イベント プロバイダ: Cloud Pub/Sub
- イベントタイプ:
google.cloud.pubsub.topic.v1.messagePublished
- リージョン:
us-central1
。 - Cloud Pub/Sub トピック :
plan
を選択します。 - サービス アカウントにロール
roles/iam.serviceAccountTokenCreator
を付与します。 - イベントの宛先: Cloud Run
- Cloud Run サービス: aidemy-portal
- サービス URL パス:
/new_teaching_plan
- メッセージを無視します(「locations/me-central2」で権限が拒否されました(または存在しない可能性があります)。
[作成] をクリックします。
Eventarc トリガー ページが更新され、新しく作成したトリガーが表に表示されます。
👉プランナーにアクセスして、新しい指導計画をリクエストします。今回は、年 5
、件名 science
、Add-no リクエスト atoms
を試します。
プランナー エージェントの場所を忘れた場合は、ターミナルでこのコマンドを実行します。
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
1 ~ 2 分ほど待ちます。このラボの課金制限が原因で遅延が発生していますが、通常は遅延はありません。
最後に、生徒のポータルにアクセスします。クイズが更新され、生成した新しい指導計画に沿って調整されていることがわかります。これにより、Eventarc が Aidemy システムに正常に統合されたことが示されます。
ポータル エージェントの場所を忘れた場合は、ターミナルでこれを実行します。
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
これで、イベント ドリブン アーキテクチャを活用してスケーラビリティと柔軟性を高め、Google Cloud でマルチエージェント システムを構築できました。基本的な設定は完了しましたが、まだできることはたくさんあります。このアーキテクチャの真のメリットを詳しく知りたい方、Gemini 2 のマルチモーダル Live API のパワーを探求したい方、LangGraph でシングルパス オーケストレーションを実装する方法を学びたい方は、次の 2 つの章に進んでください。
12. 省略可: Gemini による音声ハイライト
Gemini は、テキスト、画像、音声など、さまざまなソースからの情報を理解して処理できるため、学習やコンテンツ作成にまったく新しい可能性をもたらします。Gemini の「見る」、「聞く」、「読む」機能により、創造的で魅力的なユーザー エクスペリエンスを実現できます。
学習において重要なステップは、ビジュアルやテキストを作成するだけでなく、効果的な要約と要点をまとめることです。考えてみてください。教科書で読んだ内容よりも、キャッチーな歌詞の方が覚えやすいことがよくありますよね。音は非常に記憶に残りやすいものです。そのため、Gemini のマルチモーダル機能を活用して、授業計画の音声要約を生成します。これにより、生徒は教材を便利で魅力的な方法で復習でき、聴覚学習の力で定着率と理解度を高めることができます。
生成された音声ファイルを保存する場所が必要です。Cloud Storage は、スケーラブルで信頼性の高いソリューションを提供します。
👉コンソールの [ストレージ] に移動します。左側のメニューで [バケット] をクリックします。上部の [+ 作成] ボタンをクリックします。
👉バケットを構成します。
- bucket name: aidemy-recap-<UNIQUE_NAME> 重要: 「aidemy-recap-」で始まる一意のバケット名を定義してください。この一意の名前は、Cloud Storage バケットの作成時に名前の競合を回避するために重要です。
- region:
us-central1
。 - ストレージ クラス: 「Standard」。Standard は、頻繁にアクセスされるデータに適しています。
- アクセス制御: デフォルトの [均一アクセス制御] のままにします。これにより、バケットレベルの一貫したアクセス制御が実現します。
- 詳細オプション: このワークショップでは、通常はデフォルト設定で十分です。[作成] ボタンをクリックしてバケットを作成します。
公開アクセスの防止に関するポップアップが表示されることがあります。チェックボックスをオンのままにして、Confirm
をクリックします。
新しく作成したバケットが [バケット] リストに表示されます。バケット名を控えておいてください。後で必要になります。
👉Cloud Code Editor のターミナルで次のコマンドを実行して、サービス アカウントにバケットへのアクセス権を付与します。
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"
👉Cloud Code エディタで、course
フォルダ内の 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-west1" #To workaround onRamp qouta 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-1.0-pro",
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))
- ストリーミング接続: まず、Live API エンドポイントとの永続接続が確立されます。リクエストを送信してレスポンスを取得する標準の API 呼び出しとは異なり、この接続は開いたままになり、データの継続的な交換が可能になります。
- マルチモーダル構成: 構成を使用して、出力の種類(この場合は音声)を指定します。また、使用するパラメータ(音声の選択、音声エンコードなど)も指定できます。
- 非同期処理: この API は非同期で動作します。つまり、音声生成が完了するのを待機している間、メインスレッドをブロックしません。データをリアルタイムで処理し、出力をチャンクで送信することで、ほぼ瞬時にエクスペリエンスを提供します。
ここで重要なのは、この音声生成プロセスをいつ実行するかです。理想的には、新しい指導計画が作成されるとすぐに音声要約が利用可能になるようにします。すでに、教材プランを Pub/Sub トピックに公開してイベント ドリブン アーキテクチャを実装しているため、そのトピックをサブスクライブするだけで済みます。
ただし、新しい指導計画は頻繁には生成されません。エージェントを常に実行して新しいプランを待機させるのは効率的ではありません。そのため、この音声生成ロジックを Cloud Run 関数としてデプロイするのが理にかなっています。
関数としてデプロイすると、新しいメッセージが Pub/Sub トピックにパブリッシュされるまで休止状態になります。トリガーされると、関数が自動的に実行され、音声の要約が生成されてバケットに保存されます。
👉main.py
ファイルの course
フォルダにあるこのファイルは、新しい教材プランが利用可能になったときにトリガーされる Cloud Run 関数を定義します。プランを受信し、音声要約の生成を開始します。ファイルの末尾に次のコード スニペットを追加します。
@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: このデコレーターは、CloudEvents によってトリガーされる Cloud Run 関数として関数をマークします。
ローカルでのテスト
👉この関数は仮想環境で実行し、Cloud Run 関数に必要な Python ライブラリをインストールします。
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
👉Cloud Run Functions エミュレータを使用すると、関数を Google Cloud にデプロイする前にローカルでテストできます。次のコマンドを実行してローカル エミュレータを起動します。
functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py
👉エミュレータの実行中に、テスト用の CloudEvent をエミュレータに送信して、新しい教材プランの公開をシミュレートできます。新しいターミナルで、次の操作を行います。
👉実行:
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=="
}
}'
レスポンスが返されるのを待っている間、何もせずに待機するのではなく、別の Cloud Shell ターミナルに切り替えます。エミュレータのターミナルで、関数によって生成された進行状況と出力またはエラー メッセージを確認できます。😁
2 番目のターミナルに戻ると、OK
が返されたことがわかります。
👉バケット内のデータを確認します。[Cloud Storage] に移動し、[バケット] タブ、[aidemy-recap-xxx
] の順に選択します。
👉エミュレータを実行しているターミナルで、ctrl+c
と入力して終了します。2 つ目のターミナルを閉じます。2 つ目のターミナルを閉じて、deactivate を実行して仮想環境を終了します。
deactivate
Google Cloud へのデプロイ
👉ローカルでテストしたら、コース エージェントを Google Cloud にデプロイします。ターミナルで、次のコマンドを実行します。
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
Google Cloud コンソールで [Cloud Run] に移動して、デプロイを確認します。courses-agent という名前の新しいサービスが表示されます。
トリガーの構成を確認するには、courses-agent サービスをクリックして詳細を表示します。[トリガー] タブに移動します。
プラン トピックにパブリッシュされたメッセージをリッスンするように構成されたトリガーが表示されます。
最後に、エンドツーエンドの実行を確認しましょう。
👉次に、生成された音声ファイルの場所をポータル エージェントが認識できるように構成する必要があります。ターミナルで次のコマンドを実行します。
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 を取得します(URL がわからない場合は、ターミナルで次のコマンドを実行します)。
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
新しいプランを生成したら、音声が生成されるまで 2 ~ 3 分待ちます。このラボ アカウントの課金制限により、さらに数分かかる場合があります。
courses-agent
関数が教材プランを受信したかどうかは、関数の [トリガー] タブで確認できます。ページを定期的に更新すると、関数が呼び出されたことを示すメッセージが表示されます。2 分以上経過しても関数が呼び出されなかった場合は、指導計画をもう一度生成してみてください。ただし、生成された各プランはエージェントによって順番に使用、処理されるため、バックログが発生する可能性があるため、プランを連続して生成しないでください。
👉ポータルにアクセスし、[コース] をクリックします。3 つのカードが表示され、それぞれに音声の要約が表示されます。ポータル エージェントの URL を確認する手順は次のとおりです。
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
各コースの [再生] をクリックして、音声要約が生成した教案と一致していることを確認します。
仮想環境を終了します。
deactivate
13. 省略可: Gemini と DeepSeek とのロールベースのコラボレーション
特に魅力的で考えさせられる課題を作成する際には、複数の視点を持つことが非常に重要です。次に、異なる役割を持つ 2 つのモデルを活用して課題を生成するマルチエージェント システムを構築します。1 つはコラボレーションを促進し、もう 1 つは自己学習を促進します。ワークフローが固定ルートをたどる「シングルショット」アーキテクチャを使用します。
Gemini 課題生成ツール
まず、コラボレーションを重視した課題を生成するように Gemini 関数を設定します。
assignment
フォルダにある gemini.py
ファイルを編集します。
👉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()
Gemini モデルを使用して課題を生成します。
Gemini エージェントをテストする準備が整いました。
👉ターミナルで次のコマンドを実行して環境を設定します。
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
出力に、より多くのグループ作業を含む課題が表示されます。最後に、アサート テストも結果を出力します。
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()
DeepSeek アサインメント生成ツールを構成する
クラウドベースの AI プラットフォームは便利ですが、データのプライバシーを保護し、データ主権を確保するために、セルフホストの LLM が不可欠な場合があります。最小の DeepSeek モデル(15 億パラメータ)を Cloud Compute Engine インスタンスにデプロイします。Google の Vertex AI プラットフォームでホストしたり、GKE インスタンスでホストしたりする方法もありますが、これは AI エージェントに関するワークショップなので、長々と説明するわけにはいきません。最も簡単な方法をご紹介します。他のオプションについて詳しく知りたい場合は、課題フォルダの deepseek-vertexai.py
ファイルを参照してください。VertexAI にデプロイされたモデルを操作する方法のサンプルコードが記載されています。
👉ターミナルで次のコマンドを実行して、セルフホストの LLM プラットフォーム 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
Compute Engine インスタンスが実行されていることを確認するには:
Google Cloud コンソールで、[Compute Engine] > [VM インスタンス] に移動します。ollama-instance
が実行中であることを示す緑色のチェックマークとともに表示されます。表示されない場合は、ゾーンが us-central1 であることを確認します。表示されない場合は、検索が必要になることがあります。
👉最も小さい DeepSeek モデルをインストールしてテストします。Cloud Shell エディタに戻り、新しいターミナルで次のコマンドを実行して GCE インスタンスに SSH 接続します。
gcloud compute ssh ollama-instance --zone=us-central1-a
SSH 接続を確立すると、次のメッセージが表示されることがあります。
「Do you want to continue (Y/n)?」
Y
(大文字と小文字は区別されません)と入力して Enter キーを押すと、続行できます。
次に、SSH 認証鍵のパスフレーズを作成するよう求められる場合があります。パスフレーズを使用しない場合は、Enter キーを 2 回押してデフォルト(パスフレーズなし)を受け入れます。
👉仮想マシンに移動し、最小の DeepSeek R1 モデルを取得して、動作するかどうかをテストします。
ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"
👉GCE インスタンスを終了し、ssh ターミナルで次のように入力します。
exit
新しいターミナルを閉じて、元のターミナルに戻ります。
👉他のサービスが LLM にアクセスできるように、ネットワーク ポリシーを設定することも忘れないでください。本番環境でこれを行う場合は、インスタンスへのアクセスを制限してください。サービスにセキュリティ ログインを実装するか、IP アクセスを制限します。次のコマンドを実行します。
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
}'
次に、課題エージェントの Deepseek 関数を操作して、個々の作業に重点を置いた課題を生成します。
👉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()
次に、同じ Gemini モデルを使用して、両方の課題を新しい課題に統合します。assignment
フォルダにある gemini.py
ファイルを編集します。
👉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
両方のモデルの長所を組み合わせるために、LangGraph を使用して定義されたワークフローをオーケストレートします。このワークフローは 3 つのステップで構成されています。まず、Gemini モデルがコラボレーションに重点を置いた課題を生成し、次に DeepSeek モデルが個人作業を重視した課題を生成し、最後に Gemini がこれらの 2 つの課題を統合して、包括的な単一の課題を生成します。LLM による意思決定なしにステップの順序を事前に定義するため、これは単一パスのユーザー定義オーケストレーションになります。
👉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 TestCreatAssignment(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()
👉create_assignment
関数を最初にテストし、Gemini と DeepSeek を組み合わせたワークフローが機能することを確認するには、次のコマンドを実行します。
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 TestCreatAssignment(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()
課題の生成プロセスを自動化し、新しい指導計画に対応できるようにするには、既存のイベントドリブン アーキテクチャを活用します。次のコードは、新しい指導計画が Pub/Sub トピック「plan」に公開されるたびにトリガーされる Cloud Run 関数(generate_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
ローカルでのテスト
Google Cloud にデプロイする前に、Cloud Run 関数をローカルでテストすることをおすすめします。これにより、イテレーションを迅速に行い、デバッグを容易にできます。
まず、生成された課題ファイルを保存する Cloud Storage バケットを作成し、サービス アカウントにバケットへのアクセス権を付与します。ターミナルで次のコマンドを実行します。
👉重要: 「aidemy-assignment-」で始まる一意の ASSIGNMENT_BUCKET 名を定義してください。この一意の名前は、Cloud Storage バケットの作成時に名前の競合を回避するために重要です。(<YOUR_NAME> は任意の単語に置き換えてください)
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"
👉次に、Cloud Run Functions エミュレータを起動します。
cd ~/aidemy-bootstrap/assignment
functions-framework --target generate_assignment --signature-type=cloudevent --source main.py
👉1 つのターミナルでエミュレータが実行されている間に、Cloud Shell で 2 つ目のターミナルを開きます。この 2 番目のターミナルで、テスト用の CloudEvent をエミュレータに送信して、新しい教材計画が公開されるシミュレーションを行います。
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=="
}
}'
レスポンスが返されるのを待っている間、何もせずに待機するのではなく、別の Cloud Shell ターミナルに切り替えます。エミュレータのターミナルで、関数によって生成された進行状況と出力またはエラー メッセージを確認できます。😁
OK が返されます。
課題が正常に生成され、保存されたことを確認するには、Google Cloud コンソールで [ストレージ] > [Cloud Storage] に移動します。作成した aidemy-assignment
バケットを選択します。バケットに assignment-{random number}.txt
という名前のテキスト ファイルが表示されます。ファイルをダウンロードして内容を確認します。これにより、新しいファイルに生成された新しい割り当てが含まれていることを確認します。
👉エミュレータを実行しているターミナルで、ctrl+c
と入力して終了します。2 つ目のターミナルを閉じます。👉また、エミュレータを実行しているターミナルで、仮想環境を終了します。
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
Google Cloud コンソールで Cloud Run に移動して、デプロイを確認します。courses-agent という名前の新しいサービスが表示されます。
課題生成ワークフローが実装され、テストとデプロイが完了したので、次のステップに進みます。この課題を生徒ポータルで利用できるようにします。
14. 省略可: Gemini と DeepSeek によるロールベースのコラボレーション - 続く
動的ウェブサイトの生成
生徒ポータルを強化し、より魅力的なものにするため、課題ページに動的 HTML 生成を実装します。新しい課題が生成されるたびに、ポータルが自動的に更新され、視覚的に魅力的なデザインになるようにします。これにより、LLM のコーディング機能を活用して、よりダイナミックで魅力的なユーザー エクスペリエンスを実現できます。
👉Cloud Shell エディタで、portal
フォルダ内の render.py
ファイルを編集し、
def render_assignment_page():
return ""
次のコード スニペットに置き換えます。
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)}"
Gemini モデルを使用して、課題の HTML を動的に生成します。課題のコンテンツを入力として受け取り、プロンプトを使用して、クリエイティブなスタイルで視覚的に魅力的な HTML ページを作成するよう Gemini に指示します。
次に、新しいドキュメントが課題バケットに追加されるたびにトリガーされるエンドポイントを作成します。
👉portal フォルダ内で app.py
ファイルを編集し、## Add your code here" comments
内に new_teaching_plan
関数の後に次のコードを追加します。
## Add your code here
@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
トリガーされると、リクエストデータからファイル名とバケット名を取得し、Cloud Storage から課題のコンテンツをダウンロードして、render_assignment_page
関数を呼び出して HTML を生成します。
👉ローカルで実行します。
cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py
👉Cloud Shell ウィンドウの上部にある [ウェブでプレビュー] メニューから、[ポート 8080 でプレビュー] を選択します。新しいブラウザタブでアプリが開きます。ナビゲーション バーの [課題] リンクに移動します。この時点では空白のページが表示されます。これは、コンテンツを動的に入力するためのアサインメント エージェントとポータル間の通信ブリッジがまだ確立されていないため、想定どおりの動作です。
👉これらの変更を組み込んで更新されたコードをデプロイするには、ポータル エージェント イメージを再ビルドして push します。
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
👉新しいイメージを push したら、Cloud Run サービスを再デプロイします。次のスクリプトを実行して、Cloud Run の更新を適用します。
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
👉次に、割り当てバケットで作成(ファイナライズ)された新しいオブジェクトをリッスンする Eventarc トリガーをデプロイします。このトリガーは、新しい課題ファイルが作成されると、ポータル サービスで /render_assignment エンドポイントを自動的に呼び出します。
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"
トリガーが正常に作成されたことを確認するには、Google Cloud コンソールの Eventarc トリガーページに移動します。表に portal-assignment-trigger
が表示されます。トリガー名をクリックして、詳細を表示します。
新しいトリガーが有効になるまでに 2 ~ 3 分ほどかかることがあります。
動的割り当ての生成を確認するには、次のコマンドを実行してプランナー エージェントの URL を確認します(URL が不明な場合)。
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
ポータル エージェントの URL を確認します。
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
プランナー エージェントで、新しい指導計画を生成します。
音声の生成、課題の生成、HTML レンダリングが完了するまで数分待ってから、生徒用ポータルに移動します。
👉ナビゲーション バーの [課題] リンクをクリックします。動的に生成された HTML を含む、新しく作成された課題が表示されます。指導計画が生成されるたびに、動的課題にする必要があります。
Aidemy マルチエージェント システムの作成が完了しました。以下について実践的な経験と貴重な分析情報を得ました。
- モジュラー性、スケーラビリティ、特化、メンテナンスの簡素化など、マルチエージェント システムのメリット。
- レスポンシブで疎結合のアプリケーションを構築するためのイベント ドリブン アーキテクチャの重要性。
- LLM を戦略的に使用し、適切なモデルをタスクに合わせてツールと統合して、現実世界に影響を与える。
- Google Cloud サービスを使用したクラウドネイティブ開発手法で、スケーラブルで信頼性の高いソリューションを構築します。
- ベンダー ソリューションの代替として、データのプライバシーと自己ホスティング モデルを検討することの重要性。
これで、Google Cloud で高度な AI を活用したアプリケーションを構築するための強固な基盤ができました。
15. 課題と次のステップ
Aidemy マルチエージェント システムの構築が完了しました。AI を活用した教育の基盤が整いました。次に、機能のさらなる拡張と実際のニーズへの対応に向けて、今後の課題と機能強化の可能性について考えてみましょう。
ライブ Q&A によるインタラクティブな学習:
- 課題: Gemini 2 の Live API を利用して、生徒向けのリアルタイム Q&A 機能を作成できますか?生徒が質問して AI を活用した回答をすぐに受け取ることができるバーチャル教室を想像してみてください。
課題の提出と採点の自動化:
- 課題をデジタルで提出し、AI によって自動的に採点されるシステムを設計して実装します。また、盗用を検出して防止するメカニズムも備えます。この課題は、検索拡張生成(RAG)を検討して、採点と盗用検出プロセスの精度と信頼性を高める絶好の機会です。
16. クリーンアップ
Aidemy マルチエージェント システムを構築して確認したので、Google Cloud 環境をクリーンアップしましょう。
- Cloud Run サービスを削除する
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
- Eventarc トリガーを削除する
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
- Pub/Sub トピックの削除
gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet
- Cloud SQL インスタンスを削除する
gcloud sql instances delete aidemy --quiet
- Artifact Registry リポジトリを削除する
gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet
- Secret Manager シークレットを削除する
gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet
- Compute Engine インスタンスを削除する(Deepseek 用に作成した場合)
gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet
- Deepseek インスタンスのファイアウォール ルールを削除する
gcloud compute firewall-rules delete allow-ollama-11434 --quiet
- Cloud Storage バケットの削除
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 rb gs://$COURSE_BUCKET_NAME
gsutil rb gs://$ASSIGNMENT_BUCKET