1. 簡介
你好啊!您對服務專員的概念很感興趣,也就是說,您希望有個小幫手幫您處理事情,而您不必動手,對嗎?太棒了!不過,坦白說,一個業務人員不一定能勝任,特別是當您處理更大、更複雜的專案時。你可能需要一整個團隊!這時就輪到多代理系統登場了。
與傳統的硬式編碼相比,在 LLM 的支援下,服務機器人可提供極高的彈性。不過,這類應用程式也存在一些難題。而這正是我們在本工作坊中要深入探討的內容!
以下是您可以學習的內容,可視為提升服務團隊實力:
使用 LangGraph 建立第一個代理程式:我們將使用熱門架構 LangGraph 親手建立自己的代理程式。您將瞭解如何建立可連線至資料庫的工具、運用最新的 Gemini 2 API 進行部分網際網路搜尋,以及最佳化提示和回覆,讓您的對話方能與大型語言模型和現有服務互動。我們也會說明函式呼叫的運作方式。
自訂介面調度:我們將探討各種介面調度方式,從簡單的直線路徑到更複雜的多路徑情境皆有涵蓋。不妨將其視為指揮服務團隊的流程。
多代理系統:您將瞭解如何設定系統,讓代理程式能夠協同合作,共同完成工作,這一切都歸功於事件驅動式架構。
LLM 自由 – 使用最適合的工作方式:我們不會只使用單一 LLM!您將瞭解如何使用多個 LLM,並為其指派不同的角色,藉由使用厲害的「思考模型」提升解決問題的能力。
動態內容?沒問題!:想像一下,您的代理程式會即時為每位使用者製作動態內容。我們會說明如何操作!
使用 Google Cloud 將模型上傳至雲端:別再只在筆記本中玩模型了。我們將說明如何在 Google Cloud 上建構及部署多代理系統,讓系統可實際運作!
這個專案將是如何運用我們討論的所有技巧的絕佳範例。
2. 架構
擔任教師或從事教育工作雖然很有成就感,但坦白說,工作量 (尤其是準備工作) 可能很吃力!此外,學校通常人手不足,補習費用也相當昂貴。因此,我們建議使用 AI 輔助教學。這項工具可減輕教師負擔,並協助彌補因人力不足和缺乏負擔得起的輔導服務而造成的缺口。
我們的 AI 教學助理可提供詳細的教學計畫、有趣的測驗、易於理解的音訊重點摘要,以及個人化作業。這樣一來,老師就能專注於他們最擅長的部分:與學生互動,並協助學生愛上學習。
系統有兩個網站:一個是教師用來建立未來幾週的課程計畫,
另一個則是學生用來存取測驗、音訊重點摘要和作業的連結。
好,讓我們逐步瞭解 Aidemy 教學助理的架構。如您所見,我們將其分解為幾個主要元件,所有元件都會協同運作,以便實現這項功能。
主要架構元素和技術:
Google Cloud Platform (GCP):整個系統的核心:
- Vertex AI:存取 Google 的 Gemini 大型語言模型。
- 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 Code Sign-in」按鈕,如下圖所示。按照指示授權外掛程式。如果狀態列顯示「Cloud Code - no project」,請選取該項目,然後在下拉式選單中選取「Select a Google Cloud Project」,接著從您建立的專案清單中選取特定 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」。按一下「+ ... WRITE A FUNCTION」按鈕。
👉接下來,我們將設定 Cloud Run 函式的基本設定:
- 服務名稱:
book-provider
- 區域:
us-central1
- 執行階段:
Python 3.12
- 驗證:將
Allow unauthenticated invocations
設為「已啟用」。
👉 將其他設定保留預設值,然後按一下「建立」。系統會將您導向原始碼編輯器。
您會看到預先填入的 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 控制台會顯示狀態。可能需要幾分鐘的時間。
👉 部署完成後,請返回 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 代理程式時會整合的其中一個服務。
5. 建構工具:將代理程式連結至 RESTFUL 服務和資料
我們現在就下載 Bootstrap 骨架專案,請確認您已在 Cloud Shell 編輯器中操作。在終端機中執行
git clone https://github.com/weimeilin79/aidemy-bootstrap.git
執行這項指令後,Cloud Shell 環境中會建立名為 aidemy-bootstrap
的新資料夾。
在 Cloud Shell 編輯器的 Explorer 窗格 (通常位於左側) 中,您現在應該會看到複製 Git 存放區 aidemy-bootstrap
時建立的資料夾。在 Explorer 中開啟專案的根資料夾。你會在其中找到 planner
子資料夾,請一併開啟。
讓我們開始建構服務專員將用來提供實用服務的工具。如你所知,大型語言模型擅長推理和產生文字,但需要存取外部資源才能執行實際任務,並提供準確且符合現況的資訊。您可以將這些工具視為「萬用工具」,讓服務能夠與外界互動。
建構代理程式時,很容易就會陷入硬式編碼的陷阱,這會建立不具彈性的服務專員。而是透過建立及使用工具,讓機器人可以存取外部邏輯或系統,同時享有 LLM 和傳統程式設計的優點。
在本節中,我們將建立企劃書代理程式的基礎,供老師用來產生教學計畫。在代理程式開始產生計畫前,我們會提供主題和主題的更多詳細資料,以設定限制。我們將建立三種工具:
- 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 協助建立工具的參數。
- 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 警告彈出式視窗。
您應該會看到 JSON 字串,其中包含從書籍供應商 API 擷取的書籍推薦內容。
[{"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
的資料表,並插入一些範例資料。按一下「Run」執行 SQL 程式碼。畫面上應會顯示指令已成功執行的確認訊息。
👉展開探索工具,找出新建立的表格,然後按一下「查詢」。系統應該會開啟新的編輯器分頁,並為您產生 SQL 語法。
SELECT * FROM
"public"."curriculums" LIMIT 1000;
👉 按一下「Run」。
結果資料表應會顯示您在先前步驟中插入的資料列,確認資料表和資料已正確建立。
您已成功建立資料庫,並匯入範例課程資料,接下來我們將建立工具來擷取資料。
👉在 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 搜尋 API 整合後,可提供更準確且符合情境的搜尋結果,進而提升服務機器人的功能。這麼做可讓服務專員存取最新資訊,並根據實際資料回覆,盡量減少幻覺。改良版 API 整合功能也能讓使用者更容易以自然語言進行查詢,讓服務機器人能夠提出複雜且精細的搜尋要求。
這個函式會將搜尋查詢、課程、科目和年份做為輸入內容,並使用 Gemini API 和 Google 搜尋工具,從網際網路擷取相關資訊。仔細觀察後,您會發現它使用 Google 生成式 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 搜尋 API。這個部分會在程式碼中隱藏,使用者無法看到。
接著,Gemini 模型會將搜尋結果整合至回覆內容。可以使用這些資訊回答使用者的問題、產生摘要,或執行其他工作。
👉如要進行測試,請執行以下程式碼:
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py
您應該會看到 Gemini Search API 回應,其中包含與「Syllabus for Year 5 Mathematics」相關的搜尋結果。實際輸出內容取決於搜尋結果,但會是包含搜尋相關資訊的 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
恭喜!您現在已為企劃書服務專員建立三個強大的工具:API 連接器、資料庫連接器和 Google 搜尋工具。這些工具可讓服務專員存取所需的資訊和功能,以便建立有效的教學計畫。
7. 使用 LangGraph 進行自動化調度管理
我們已建立個別工具,現在是時候使用 LangGraph 進行協調了。這可讓我們建立更精密的「企劃書」代理程式,根據使用者要求,智能化地決定要使用哪些工具,以及使用時機。
LangGraph 是一種 Python 程式庫,可讓您更輕鬆地使用大型語言模型 (LLM) 建構具狀態的多方應用程式。您可以將其視為用於協調涉及 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 可以直接回覆使用者,也可以選擇使用其中一個可用工具。
tools:這個清單代表服務專員可用的工具組合。其中包含我們在前幾個步驟中定義的三個工具函式: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
查看終端機中的記錄。您應該會看到證據,證明服務專員在提供最終教學計畫前,已使用所有三種工具 (取得學校課程、取得書籍推薦內容,以及搜尋最新資源)。這表示 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 工具。這是因為提示未指定需要其他兩個工具,而我們的 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 的互動相當常見,但我們選擇採用傳統的表單提交 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 會開啟新的瀏覽器分頁或視窗,並顯示應用程式的網頁預覽畫面。
在應用程式介面中,選取「Year」的 5
,選取主題 Mathematics
,然後在外掛程式要求 中輸入
Geometry
請不要在等待回應時傻傻地盯著螢幕,改為切換至 Cloud 編輯器的終端機。您可以在模擬器的終端機中,觀察函式產生的進度和任何輸出或錯誤訊息。😁
👉 在終端機中按下 Ctrl+C
即可停止指令碼。
👉退出虛擬環境:
deactivate
8. 將企劃書代理程式部署至雲端
建構並推送映像檔至登錄檔
👉現在是時候將這個項目部署至雲端了。在終端機中建立構件存放區,用於儲存要建構的 Docker 映像檔。
gcloud artifacts repositories create agent-repository \
--repository-format=docker \
--location=us-central1 \
--description="My agent repository"
畫面上應會顯示「Created repository [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 .
👉我們需要重新標記映像檔,讓它託管在 Artifact Registry 中,而非 GCR,並將標記的映像檔推送至 Artifact Registry:
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
推送完成後,您可以確認映像檔是否已成功儲存在 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 服務:
- 容器映像檔:按一下網址欄位中的「選取」。找出推送至 Artifact Registry 的映像檔網址 (例如 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 會部署您的服務。
部署完成後,按一下服務的詳細資料頁面,即可在頂端找到已部署的網址。
在應用程式介面中,選取「Year」的 7
,然後選擇「Subject」的 Mathematics
,並在「Add-on Request」欄位中輸入 Algebra
。這樣一來,服務專員就能根據必要的背景資訊,為您提供量身打造的課程計畫。
恭喜!您已成功使用強大的 AI 服務建立教學計畫。這項研究顯示,服務專員可大幅減少工作量並簡化工作,進而提高效率,讓教師的生活更輕鬆。
9. 多代理系統
既然我們已成功實作教學計畫建立工具,接下來就讓我們專注於建構學生入口網站。這個入口網站可讓學生存取與課程相關的測驗、重點摘要和作業。考量這項功能的範圍,我們將運用多代理系統的強大功能,建立模組化且可擴充的解決方案。
如先前所述,多代理程式系統可將工作負載細分為較小且專門的工作,並由專屬代理程式處理。這種做法有幾個主要優點:
模組化和可維護性:不要建立一個可執行所有操作的單一代理程式,而是建構較小且專門的代理程式,並明確定義其職責。這種模組化設計可讓系統更容易理解、維護及偵錯。發生問題時,您可以將問題歸咎於特定服務項目,而不用篩選大量程式碼集。
可擴充性:擴充單一複雜的代理程式可能會造成瓶頸。有了多虛擬服務專員系統,您就能根據個別虛擬服務專員的需求調整其規模。舉例來說,如果一個服務代理處理大量要求,您可以輕鬆啟動更多該服務代理的執行個體,而不會影響系統的其他部分。
團隊專長:想想看,您不會要求一位工程師從頭開始建構整個應用程式。而是組成專家團隊,每位成員都專精於特定領域。同樣地,多代理系統可讓您運用不同 LLM 和工具的優勢,將這些代理程式指派給最適合特定任務的代理程式。
平行開發:不同團隊可同時處理不同的代理程式,加快開發流程。由於代理程式是獨立的,因此對某個代理程式所做的變更不太可能影響其他代理程式。
事件導向架構
為了讓這些代理程式之間能有效溝通及協調,我們會採用事件導向架構。也就是說,服務專員會回應系統內發生的「事件」。
代理人會訂閱特定事件類型 (例如「產生教學計畫」、「建立作業」)。發生事件時,系統會通知相關的服務專員,以便他們採取適當行動。這種解耦合做法可提升彈性、可擴充性和即時回應能力。
首先,我們需要一種方法來廣播這些事件。為此,我們會設定 Pub/Sub 主題。首先,我們要建立名為「plan」的主題。
👉前往 Google Cloud 控制台的 Pub/Sub,然後按一下「Create Topic」(建立主題) 按鈕。
👉 使用 ID/名稱 plan
設定主題,取消勾選 Add a default subscription
,保留其他預設值,然後按一下「建立」。
Pub/Sub 頁面會重新整理,您應該會在表格中看到新建立的主題。
現在,我們來將 Pub/Sub 事件發布功能整合至企劃書服務代理程式。我們將新增一項工具,將「plan」事件傳送至剛剛建立的 Pub/Sub 主題。這項事件會向系統中的其他代理程式 (例如學生入口網站中的代理程式) 發出信號,表示有新的教學計畫可供使用。
👉返回 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 編輯器的「Explorer」面板中,前往 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
在這個代理程式中,它會建立 JSON 輸出剖析器,專門用於瞭解及建構 LLM 的輸出內容。它會使用先前定義的 QuizQuestion
模型,確保剖析的輸出內容符合正確格式 (問題、選項和答案)。
👉在終端機中執行下列指令,即可設定虛擬環境、安裝依附元件,並啟動代理程式:
cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py
使用 Cloud Shell 的「網頁預覽」功能存取執行中的應用程式。按一下頂端導覽列或索引頁面資訊卡中的「測驗」連結。您應該會看到系統為學生隨機產生的三個測驗。這些測驗是根據教學計畫設計,可讓您瞭解 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)
這是一個非常簡單的 langchain 應用程式,可用於初始化 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 的網頁預覽功能存取執行中的應用程式。點選「測驗」連結,回答所有測驗,並確保至少答錯一題,然後按一下「提交」
請不要在等待回應時傻傻地盯著螢幕,改為切換至 Cloud 編輯器的終端機。您可以在模擬器的終端機中,觀察函式產生的進度和任何輸出或錯誤訊息。😁
如要停止本機執行的程序,請在終端機中按下 Ctrl+C
。
11. 使用 Eventarc 自動調度處理服務
到目前為止,學生入口網站一直是根據預設的教學計畫產生測驗。這雖然很有幫助,但也表示規劃器代理程式和入口網站的測驗代理程式並未互相溝通。還記得我們新增的功能嗎?這個功能可讓企劃書服務代理程式將新產生的教學計畫發布到 Pub/Sub 主題。接下來,我們要將這個連線連結到我們的入口網站代理程式!
我們希望每當產生新的教學計畫時,入口網站都能自動更新測驗內容。為此,我們會在入口網站中建立可接收這些新企劃書的端點。
👉在 Cloud Code 編輯器的「Explorer」面板中,前往 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 代理程式映像檔,並在終端機中執行:
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
👉 我們會採取相同做法,建構並推送 Portal 代理程式映像檔:
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
請記下服務網址,這是已部署的企劃書代理程式連結。
👉 執行這段程式碼,為 portal 代理程式建立 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
請記下服務網址,這是已部署學生入口網站的連結。
建立 Eventarc 觸發條件
但問題是:當 Pub/Sub 主題中出現新的待處理計畫時,這個端點要如何收到通知?這時 Eventarc 就能派上用場!
Eventarc 可做為橋樑,監聽特定事件 (例如 Pub/Sub 主題收到的新訊息),並自動觸發動作做出回應。在我們的情況下,系統會偵測新教學計畫發布時機,然後傳送信號至入口端點,通知對方更新時間到了。
有了 Eventarc 處理事件驅動通訊功能,我們就能順暢地連結規劃工具代理程式和入口網站代理程式,打造真正動態且反應靈敏的學習系統。就像有個聰明的傳訊工具,自動將最新的教學計畫傳送到正確的位置!
👉在控制台中前往「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
- 服務網址路徑:
/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
然後請等待一兩分鐘,由於本實驗室的結帳限制,因此會出現這項延遲,在正常情況下,不會有延遲。
最後,請前往學生入口網站。您應該會看到測驗已更新,並與您剛建立的新教學計畫保持一致!這表示 Eventarc 已成功整合至 Aidemy 系統!
如果忘記入口網站代理人的所在位置,請在終端機中執行這項操作
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
恭喜!您已成功在 Google Cloud 上建構多代理人系統,並運用事件導向架構提升擴充性和彈性!您已奠定穩固基礎,但還有更多內容等您探索。如要深入瞭解這個架構的實際優點,請探索 Gemini 2 的多模態 Live API 強大功能,並瞭解如何使用 LangGraph 實作單一路徑協調作業,歡迎繼續閱讀接下來兩章。
12. 選用:使用 Gemini 製作音訊回顧
Gemini 可解讀及處理文字、圖片甚至音訊等各種來源的資訊,為學習和內容創作帶來全新的可能性。Gemini 的「看見」、「聽見」和「讀取」能力,可帶來創意十足且引人入勝的使用者體驗。
除了製作圖像或文字之外,有效的摘要和重點回顧也是學習的另一個重要步驟。想想看:你是否常常比起從課本讀到的內容,更容易記得朗朗上口的歌詞?聲音可以讓人留下深刻印象!因此,我們將運用 Gemini 的多模態功能,產生教學計畫的音訊重點摘要,為學生提供方便又有趣的學習內容,並透過聽覺學習的力量,提升學生的學習成效和理解力。
我們需要一個位置來儲存產生的音訊檔案。Cloud Storage 提供可擴充且可靠的解決方案。
👉前往控制台的「儲存空間」,按一下左側選單中的「分類」。按一下頂端的「+ 建立」按鈕。
👉設定值區:
- 值區名稱:aidemy-recap-<UNIQUE_NAME> 重要事項:請務必定義不重複的值區名稱,且開頭必須是「aidemy-recap-」。這個不重複名稱非常重要,可避免在建立 Cloud Storage 值區時發生命名衝突。
- region:
us-central1
。 - 儲存空間級別:「標準」。Standard 適合用於經常存取的資料。
- 存取權控管:請保留預設的「統一存取權控管」選項。這可提供一致的值區層級存取權控管機制。
- 進階選項:在本工作坊中,預設設定通常就足夠了。按一下「CREATE」按鈕,建立值區。
您可能會看到有關禁止公開存取的彈出式視窗。請保留勾選狀態,然後按一下 Confirm
。
您現在會在「值區」清單中看到新建立的值區。請記下 bucket 名稱,後續步驟會用到。
👉在 Cloud Code 編輯器的終端機中執行下列指令,授予服務帳戶存取儲存桶的權限:
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:這個修飾符會將函式標示為 Cloud 事件觸發的 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 函式模擬器可讓我們在本機測試函式,然後再部署至 Google Cloud。執行下列指令,啟動本機模擬器:
functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py
👉模擬器執行期間,您可以將測試 CloudEvents 傳送至模擬器,模擬發布新的教學計畫。在新的終端機中:
👉執行:
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
即可退出。並關閉第二個終端機。然後關閉第二個終端機,並執行 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
👉請嘗試在規劃工具代理程式頁面中產生新的教學計畫。這項服務是無伺服器服務,因此可能需要幾分鐘的時間才能啟動,請不要擔心。取得企劃書代理程式的網址 (如果沒有,請在終端機中執行以下指令):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
產生新企劃書後,請等候 2 到 3 分鐘,讓系統產生音訊。由於這個實驗室帳戶有帳單限制,因此這項作業可能需要多等候幾分鐘。
您可以查看函式的「觸發事件」分頁,監控 courses-agent
函式是否已收到教學計畫。定期重新整理頁面,您應該會看到函式已成功叫用。如果函式在 2 分鐘後仍未叫用,您可以嘗試再次產生教學計畫。不過,請勿快速連續產生企劃書,因為每個產生的企劃書都會依序由服務人員使用及處理,可能會造成積壓。
👉 前往入口網站,然後按一下「課程」。你應該會看到三張資訊卡,每張都會顯示音訊重點摘要。如何找出入口網站代理程式的網址:
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
請在每門課程上按一下「播放」,確保音訊重點摘要與您剛才產生的教學計畫相符!
結束虛擬環境。
deactivate
13. 選用:使用 Gemini 和 DeepSeek 進行角色式協作
多方觀點非常重要,尤其是在設計引人入勝的作業時。我們現在將建立多代理系統,利用兩種不同角色的模型產生作業:一種促進協作,另一種鼓勵自學。我們會使用「單拍」架構,其中工作流程會遵循固定路徑。
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 Agent。
👉在終端機中執行下列指令,設定環境:
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 對於保護資料隱私權和確保資料主權至關重要。我們會在 Cloud Compute Engine 執行個體上部署最小的 DeepSeek 模型 (15 億個參數)。您也可以選擇在 Google 的 Vertex AI 平台或 GKE 執行個體上託管,但這只是 AI 代理程式工作坊,我不想讓您一直停留在這裡,因此我們就使用最簡單的方式。不過,如果您有興趣想深入瞭解其他選項,請查看作業資料夾中的 deepseek-vertexai.py
檔案,其中提供範例程式碼,說明如何與在 Vertex AI 上部署的模型互動。
👉 在終端機中執行下列指令,建立自架式 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 編輯器,在新終端機中執行下列指令,透過 SSH 連線至 GCE 執行個體。
gcloud compute ssh ollama-instance --zone=us-central1-a
建立 SSH 連線後,系統可能會提示您以下內容:
「Do you want to continue (Y/n)?」
只要輸入 Y
(大小寫不拘),然後按下 Enter 鍵即可繼續操作。
接著,系統可能會要求您為 SSH 金鑰建立通關密碼。如果不想使用通關密語,只要按兩下 Enter 鍵即可接受預設值 (不使用通關密語)。
👉現在您已進入虛擬機器,請下載最小的 DeepSeek R1 模型,並測試是否可用。
ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"
👉 在 SSH 終端機中輸入以下指令,退出 GCE 執行個體:
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 調度管理已定義的工作流程。這個工作流程包含三個步驟:首先,Gemini 模型會產生以協作為重點的作業;其次,DeepSeek 模型會產生以個人工作為重點的作業;最後,Gemini 會將這兩項作業整合為單一綜合作業。由於我們預先定義步驟順序,且不經過 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()
為讓作業產生程序自動化,並能回應新的教學計畫,我們將運用現有的事件驅動式架構。下列程式碼定義了 Cloud Run 函式 (generate_assignment),每當新的教學計畫發布至 Pub/Sub 主題「plan」時,就會觸發該函式。
👉將下列程式碼新增至 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 值區來儲存產生的作業檔案,並授予服務帳戶對該值區的存取權。在終端機中執行下列指令:
👉重要事項:請務必定義不重複的 ASSIGNMENT_BUCKET 名稱,且名稱開頭必須是「aidemy-assignment-」。這個不重複名稱對於避免建立 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 函式模擬器:
cd ~/aidemy-bootstrap/assignment
functions-framework --target generate_assignment --signature-type=cloudevent --source main.py
👉在其中一個終端機中執行模擬器時,在 Cloud Shell 中開啟第二個終端機。在第二個終端機中,將測試 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
即可退出。並關閉第二個終端機。👉此外,請在執行模擬器的終端機中,退出虛擬環境。
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。這項功能會將作業內容做為輸入內容,並使用提示指示 Gemini 以創意風格建立視覺效果吸睛的 HTML 網頁。
接下來,我們會建立端點,只要有新文件新增至作業值區,就會觸發這個端點:
👉在入口網站資料夾中,編輯 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」。系統會在新瀏覽器分頁中開啟應用程式。前往導覽列中的「指派」連結。此時您應該會看到空白頁面,這是正常現象,因為我們尚未在指派代理程式和入口網站之間建立通訊橋接,無法動態填入內容。
👉如要納入這些變更並部署更新的程式碼,請重新建構並推送 Portal 代理程式映像檔:
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
👉推送新映像檔後,請重新部署 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 分鐘才會生效。
如要查看動態指派作業的實際運作情形,請執行下列指令,找出企劃書代理人的網址 (如果您沒有方便取得的話):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
找出入口網站代理人的網址:
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 輔助教育奠定穩固基礎。接下來,我們來探討一些挑戰和未來可能的強化功能,進一步擴展其功能並滿足實際需求:
透過即時問答功能進行互動式學習:
- 挑戰:您能否運用 Gemini 2 的 Live API,為學生建立即時問答功能?想像一下,在虛擬教室中,學生可以提出問題,並立即獲得 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