Aidemy: Xây dựng hệ thống đa tác nhân bằng LangGraph, EDA và AI tạo sinh trên Google Cloud

1. Giới thiệu

Chào bạn! Vậy là bạn thích ý tưởng về các trợ lý ảo – những trợ lý nhỏ có thể giúp bạn hoàn thành mọi việc mà bạn không cần phải làm gì, đúng không? Tuyệt vời! Nhưng thực tế là một tác nhân không phải lúc nào cũng đủ, đặc biệt là khi bạn đang giải quyết các dự án lớn hơn và phức tạp hơn. Có lẽ bạn sẽ cần cả một đội ngũ những người như vậy! Đây là lúc hệ thống đa tác nhân phát huy vai trò của mình.

Khi được hỗ trợ bởi LLM, các tác nhân mang đến cho bạn sự linh hoạt đáng kinh ngạc so với phương pháp mã hoá cứng kiểu cũ. Nhưng, và luôn có một nhưng, chúng cũng đi kèm với những thách thức khó khăn riêng. Và đó chính xác là những gì chúng ta sẽ tìm hiểu trong hội thảo này!

tiêu đề

Dưới đây là những điều bạn có thể học được – hãy coi đây là cơ hội để nâng cao kỹ năng của bạn:

Xây dựng tác nhân đầu tiên bằng LangGraph: Chúng ta sẽ bắt tay vào xây dựng tác nhân của riêng bạn bằng LangGraph, một khung phổ biến. Bạn sẽ tìm hiểu cách tạo các công cụ kết nối với cơ sở dữ liệu, khai thác API Gemini 2 mới nhất để tìm kiếm thông tin trên Internet, đồng thời tối ưu hoá câu lệnh và câu trả lời để tác nhân của bạn có thể tương tác không chỉ với LLM mà còn với các dịch vụ hiện có. Chúng tôi cũng sẽ cho bạn biết cách hoạt động của tính năng gọi hàm.

Điều phối tác nhân theo cách của bạn: Chúng ta sẽ khám phá nhiều cách điều phối tác nhân, từ các đường dẫn đơn giản đến các tình huống phức tạp hơn với nhiều đường dẫn. Hãy xem việc này giống như việc chỉ đạo nhóm nhân viên hỗ trợ.

Hệ thống đa tác nhân: Bạn sẽ khám phá cách thiết lập một hệ thống nơi các tác nhân có thể cộng tác và hoàn thành công việc cùng nhau – tất cả là nhờ kiến trúc hướng sự kiện.

LLM Freedom: Sử dụng mô hình phù hợp nhất cho công việc: Chúng tôi không chỉ sử dụng một LLM duy nhất! Bạn sẽ tìm hiểu cách sử dụng nhiều LLM, chỉ định cho chúng các vai trò khác nhau để tăng khả năng giải quyết vấn đề bằng cách sử dụng "các mô hình tư duy" hiệu quả.

Nội dung động? Không sao cả!: Hãy tưởng tượng trợ lý của bạn tạo nội dung động được điều chỉnh riêng cho từng người dùng theo thời gian thực. Chúng tôi sẽ hướng dẫn bạn cách thực hiện!

Đưa dữ liệu lên đám mây bằng Google Cloud: Đừng chỉ chơi đùa trong sổ tay. Chúng tôi sẽ hướng dẫn bạn cách thiết kế và triển khai hệ thống nhiều tác nhân trên Google Cloud để hệ thống này sẵn sàng cho thế giới thực!

Dự án này sẽ là một ví dụ điển hình về cách sử dụng tất cả các kỹ thuật mà chúng ta đã đề cập.

2. Kiến trúc

Trở thành giáo viên hoặc làm việc trong ngành giáo dục có thể mang lại nhiều lợi ích, nhưng hãy đối mặt với thực tế rằng khối lượng công việc, đặc biệt là tất cả công việc chuẩn bị, có thể rất khó khăn! Ngoài ra, thường không có đủ nhân viên và việc dạy kèm có thể tốn kém. Đó là lý do chúng tôi đề xuất một trợ lý giảng dạy dựa trên AI. Công cụ này có thể giúp giảm bớt gánh nặng cho nhà giáo dục và giúp thu hẹp khoảng cách do tình trạng thiếu nhân viên và thiếu dịch vụ gia sư giá cả phải chăng.

Trợ lý giảng dạy AI của chúng tôi có thể tạo ra các kế hoạch bài học chi tiết, các câu đố vui, bản tóm tắt bằng âm thanh dễ hiểu và bài tập được cá nhân hoá. Điều này giúp giáo viên tập trung vào thế mạnh của mình: kết nối với học viên và giúp họ yêu thích việc học.

Hệ thống này có 2 trang web: một trang web dành cho giáo viên để tạo kế hoạch bài học cho những tuần sắp tới,

Người lập kế hoạch

và một đường liên kết để học viên truy cập vào bài kiểm tra, bản tóm tắt bằng âm thanh và bài tập. Cánh cổng

Được rồi, hãy cùng tìm hiểu về cấu trúc hỗ trợ trợ lý giảng dạy của chúng tôi, Aidemy. Như bạn thấy, chúng tôi đã chia nhỏ thành nhiều thành phần chính, tất cả đều hoạt động cùng nhau để thực hiện việc này.

Kiến trúc

Các thành phần và công nghệ chính trong cấu trúc:

Google Cloud Platform (GCP): Là trung tâm của toàn bộ hệ thống:

  • Vertex AI: Truy cập vào các mô hình ngôn ngữ lớn (LLM) Gemini của Google.
  • Cloud Run: Nền tảng không máy chủ để triển khai các hàm và tác nhân được đóng gói trong vùng chứa.
  • Cloud SQL: Cơ sở dữ liệu PostgreSQL cho dữ liệu chương trình học.
  • Pub/Sub và Eventarc: Nền tảng của kiến trúc hướng sự kiện, cho phép giao tiếp không đồng bộ giữa các thành phần.
  • Bộ nhớ trên đám mây: Lưu trữ bản tóm tắt bằng âm thanh và tệp bài tập.
  • Secret Manager: Quản lý thông tin đăng nhập cơ sở dữ liệu một cách an toàn.
  • Artifact Registry: Lưu trữ hình ảnh Docker cho các tác nhân.
  • Compute Engine: Để triển khai LLM tự lưu trữ thay vì dựa vào các giải pháp của nhà cung cấp

Mô hình ngôn ngữ lớn (LLM): "Bộ não" của hệ thống:

  • Các mô hình Gemini của Google: (Gemini x Pro, Gemini x Flash, Gemini x Flash Thinking) Được dùng để lập kế hoạch bài học, tạo nội dung, tạo HTML động, giải thích bài kiểm tra và kết hợp các bài tập.
  • DeepSeek: Được dùng cho nhiệm vụ chuyên biệt là tạo bài tập tự học

LangChain và LangGraph: Khung để phát triển ứng dụng LLM

  • Tạo điều kiện thuận lợi cho việc tạo quy trình làm việc phức tạp với nhiều tác nhân.
  • Cho phép điều phối thông minh các công cụ (lệnh gọi API, truy vấn cơ sở dữ liệu, tìm kiếm trên web).
  • Triển khai cấu trúc hướng sự kiện để hệ thống có khả năng mở rộng và tính linh hoạt.

Về cơ bản, kiến trúc của chúng tôi kết hợp sức mạnh của các LLM với dữ liệu có cấu trúc và hoạt động giao tiếp dựa trên sự kiện, tất cả đều chạy trên Google Cloud. Điều này cho phép chúng tôi xây dựng một trợ lý giảng dạy có khả năng mở rộng, đáng tin cậy và hiệu quả.

3. Trước khi bắt đầu

Trong Google Cloud Console, trên trang chọn dự án, hãy chọn hoặc tạo một dự án trên Google Cloud. Đảm bảo rằng bạn đã bật tính năng thanh toán cho dự án trên Cloud. Tìm hiểu cách kiểm tra xem một dự án có bật tính năng thanh toán hay không.

Bật Gemini Code Assist trong Cloud Shell IDE

👉 Trong bảng điều khiển Google Cloud, hãy chuyển đến phần Gemini Code Assist Tools (Công cụ Gemini Code Assist), bật Gemini Code Assist miễn phí bằng cách đồng ý với các điều khoản và điều kiện.

01-04-code-assist-enable.png

Bỏ qua chế độ thiết lập quyền, rời khỏi trang này.

Làm việc trên Cloud Shell Editor

👉Nhấp vào Kích hoạt Cloud Shell ở đầu bảng điều khiển Google Cloud (Đây là biểu tượng có hình dạng cửa sổ dòng lệnh ở đầu ngăn Cloud Shell), nhấp vào nút "Mở Trình chỉnh sửa" (Đây là biểu tượng trông giống như một thư mục đang mở có cây bút chì). Thao tác này sẽ mở Cloud Shell Code Editor trong cửa sổ. Bạn sẽ thấy một trình khám phá tệp ở bên trái.

Cloud Shell

👉Nhấp vào nút Đăng nhập bằng mã trên đám mây trong thanh trạng thái ở dưới cùng như minh hoạ. Uỷ quyền cho trình bổ trợ theo hướng dẫn. Nếu bạn thấy Cloud Code – no project (Cloud Code – không có dự án) trong thanh trạng thái, hãy chọn mục đó rồi chọn "Select a Google Cloud Project" (Chọn một dự án trên Google Cloud) trong trình đơn thả xuống, sau đó chọn dự án cụ thể trên Google Cloud trong danh sách dự án mà bạn đã tạo.

Đăng nhập vào dự án

👉Mở thiết bị đầu cuối trong IDE trên đám mây, Nhà ga mới hoặc Nhà ga mới

👉Trong thiết bị đầu cuối, hãy xác minh rằng bạn đã được xác thực và dự án được đặt thành mã dự án của bạn bằng lệnh sau:

gcloud auth list

👉Và chạy để đảm bảo thay thế <YOUR_PROJECT_ID> bằng mã dự án của bạn:

echo <YOUR_PROJECT_ID> > ~/project_id.txt
gcloud config set project $(cat ~/project_id.txt)

👉Chạy lệnh sau để bật các API cần thiết của Google Cloud:

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 \
                        cloudaicompanion.googleapis.com

Quá trình này có thể mất vài phút.

Thiết lập quyền

👉Thiết lập quyền cho tài khoản dịch vụ. Trong thiết bị đầu cuối, hãy chạy :

gcloud config set project $(cat ~/project_id.txt)
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"

👉 Cấp quyền. Trong thiết bị đầu cuối, hãy chạy :

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

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

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


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


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

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

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

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

👉Xác thực kết quả trong bảng điều khiển IAMBảng điều khiển IAM

👉Chạy các lệnh sau trong cửa sổ dòng lệnh để tạo một phiên bản Cloud SQL có tên là aidemy. Chúng ta sẽ cần đến bước này sau, nhưng vì quá trình này có thể mất chút thời gian nên chúng ta sẽ thực hiện ngay bây giờ.

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

4. Tạo tác nhân đầu tiên

Trước khi đi sâu vào các hệ thống đa tác nhân phức tạp, chúng ta cần thiết lập một khối cơ bản: một tác nhân đơn lẻ, có chức năng. Trong phần này, chúng ta sẽ thực hiện các bước đầu tiên bằng cách tạo một tác nhân "nhà cung cấp sách" đơn giản. Tác nhân nhà cung cấp sách lấy một danh mục làm dữ liệu đầu vào và sử dụng một LLM Gemini để tạo một cuốn sách có dạng biểu thị JSON trong danh mục đó. Sau đó, ứng dụng này sẽ phân phát các đề xuất về sách này dưới dạng một điểm cuối REST API .

Nhà cung cấp sách

👉Trong một thẻ trình duyệt khác, hãy mở Google Cloud Console trong trình duyệt web. Trong trình đơn điều hướng (☰), hãy chuyển đến "Cloud Run". Nhấp vào nút "+ ... VIẾT MỘT HÀM".

Tạo hàm

👉Tiếp theo, chúng ta sẽ định cấu hình các chế độ cài đặt cơ bản của Cloud Run Function:

  • Tên dịch vụ: book-provider
  • Vùng: us-central1
  • Thời lượng: Python 3.12
  • Xác thực: Allow unauthenticated invocations thành Đã bật.

👉Giữ nguyên các chế độ cài đặt khác theo mặc định rồi nhấp vào Tạo. Thao tác này sẽ đưa bạn đến trình chỉnh sửa mã nguồn.

Bạn sẽ thấy các tệp main.pyrequirements.txt được điền sẵn.

main.py sẽ chứa logic nghiệp vụ của hàm, requirements.txt sẽ chứa các gói cần thiết.

👉Giờ thì chúng ta đã sẵn sàng viết một số mã! Nhưng trước khi bắt đầu, hãy xem liệu Gemini Code Assist có thể giúp chúng ta bắt đầu nhanh chóng hay không. Quay lại Cloud Shell Editor, nhấp vào biểu tượng Gemini Code Assist ở trên cùng. Thao tác này sẽ mở cuộc trò chuyện Gemini Code Assist.

Gemini Code Assist

👉 Dán yêu cầu sau vào hộp câu lệnh:

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

Sau đó, Trợ lý lập trình sẽ tạo ra một giải pháp tiềm năng, cung cấp cả mã nguồn và tệp phụ thuộc requirements.txt. (KHÔNG SỬ DỤNG MÃ NÀY)

Bạn nên so sánh mã do Trợ lý mã tạo với giải pháp đã được kiểm thử và chính xác mà chúng tôi cung cấp bên dưới. Nhờ đó, bạn có thể đánh giá hiệu quả của công cụ và xác định mọi điểm khác biệt tiềm ẩn. Mặc dù không nên tin tưởng một cách mù quáng vào các LLM, nhưng Trợ lý lập trình có thể là một công cụ hữu ích để tạo mẫu nhanh và tạo cấu trúc mã ban đầu, đồng thời nên được sử dụng để có một khởi đầu thuận lợi.

Vì đây là một hội thảo, nên chúng ta sẽ tiếp tục với mã đã xác minh được cung cấp bên dưới. Tuy nhiên, bạn có thể thoải mái thử nghiệm mã do Trợ lý lập trình tạo ra vào thời gian rảnh để hiểu rõ hơn về các khả năng và hạn chế của công cụ này.

👉Quay lại trình chỉnh sửa mã nguồn của Hàm Cloud Run (trong thẻ trình duyệt khác). Cẩn thận thay thế nội dung hiện có của main.py bằng mã được cung cấp bên dưới:

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

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


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

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

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

    Args:
        category (str): category

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

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

    return  json.dumps(response)
    

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

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


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

    
    return jsonify(recommendations_list)

👉Thay thế nội dung của requirements.txt bằng nội dung sau:

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

👉chúng ta sẽ đặt Điểm truy cập hàm: recommended

03-02-function-create.png

👉Nhấp vào LƯU VÀ TRIỂN KHAI (hoặc LƯU VÀ TRIỂN KHAI LẠI) để triển khai Hàm. Chờ quá trình triển khai hoàn tất. Cloud Console sẽ hiển thị trạng thái. Quá trình này có thể mất vài phút.

văn bản thay thế 👉Sau khi triển khai, hãy quay lại trình chỉnh sửa Cloud Shell, trong cửa sổ dòng lệnh, hãy chạy:

gcloud config set project $(cat ~/project_id.txt)
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

Thao tác này sẽ cho thấy một số dữ liệu về sách ở định dạng 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"}
]

Xin chúc mừng! Bạn đã triển khai thành công một Hàm Cloud Run. Đây là một trong những dịch vụ mà chúng tôi sẽ tích hợp khi phát triển trợ lý ảo Aidemy.

5. Công cụ xây dựng: Kết nối các tác nhân với dịch vụ RESTFUL và Dữ liệu

Hãy tải Dự án Bootstrap Skeleton xuống, nhớ là bạn đang ở trong Cloud Shell Editor. Trong quá trình chạy thiết bị đầu cuối,

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

Sau khi chạy lệnh này, một thư mục mới có tên aidemy-bootstrap sẽ được tạo trong môi trường Cloud Shell.

Trong ngăn Explorer (Trình khám phá) của Cloud Shell Editor (thường ở bên trái), giờ đây, bạn sẽ thấy thư mục được tạo khi bạn sao chép kho lưu trữ Git aidemy-bootstrap. Mở thư mục gốc của dự án trong Explorer. Bạn sẽ thấy một thư mục con planner trong thư mục đó, hãy mở thư mục con này. project explorer

Hãy bắt đầu xây dựng những công cụ mà các tác nhân của chúng ta sẽ sử dụng để thực sự hữu ích. Như bạn biết, LLM có khả năng suy luận và tạo văn bản rất tốt, nhưng cần có quyền truy cập vào các tài nguyên bên ngoài để thực hiện các tác vụ trong thế giới thực và cung cấp thông tin chính xác, mới nhất. Hãy coi những công cụ này là "dao đa năng" của tác nhân, giúp tác nhân có khả năng tương tác với thế giới.

Khi tạo một tác nhân, bạn có thể dễ dàng mã hoá cứng rất nhiều thông tin chi tiết. Điều này tạo ra một tác nhân không linh hoạt. Thay vào đó, bằng cách tạo và sử dụng các công cụ, tác nhân có quyền truy cập vào logic hoặc hệ thống bên ngoài, mang lại lợi ích của cả LLM và lập trình truyền thống.

Trong phần này, chúng ta sẽ tạo nền tảng cho tác nhân lập kế hoạch. Giáo viên sẽ sử dụng tác nhân này để tạo kế hoạch bài học. Trước khi tác nhân bắt đầu tạo kế hoạch, chúng ta muốn đặt ra các ranh giới bằng cách cung cấp thêm thông tin chi tiết về chủ đề và đề tài. Chúng ta sẽ tạo 3 công cụ:

  1. Lệnh gọi API Restful: Tương tác với một API có sẵn để truy xuất dữ liệu.
  2. Truy vấn cơ sở dữ liệu: Tìm nạp dữ liệu có cấu trúc từ cơ sở dữ liệu Cloud SQL.
  3. Google Tìm kiếm: Truy cập thông tin theo thời gian thực trên web.

Tìm nạp đề xuất về sách từ một API

Trước tiên, hãy tạo một công cụ truy xuất đề xuất về sách từ API book-provider mà chúng ta đã triển khai trong phần trước. Điều này minh hoạ cách một tác nhân có thể tận dụng các dịch vụ hiện có.

Đề xuất sách

Trong Cloud Shell Editor, hãy mở dự án aidemy-bootstrap mà bạn đã sao chép trong phần trước.

👉Chỉnh sửa book.py trong thư mục planner, rồi dán đoạn mã sau vào cuối tệp:

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."))

Giải thích:

  • recommend_book(query: str): Hàm này nhận truy vấn của người dùng làm đầu vào.
  • Tương tác với LLM: Tính năng này sử dụng LLM để trích xuất danh mục từ cụm từ tìm kiếm. Điều này minh hoạ cách bạn có thể sử dụng LLM để giúp tạo các tham số cho công cụ.
  • Lệnh gọi API: Lệnh này gửi một yêu cầu POST đến API nhà cung cấp sách, truyền danh mục và số lượng sách mong muốn.

👉Để kiểm thử hàm mới này, hãy đặt biến môi trường, chạy :

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

👉Cài đặt các phần phụ thuộc và chạy mã để đảm bảo mã hoạt động, hãy chạy:

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

Bạn sẽ thấy một chuỗi JSON chứa các đề xuất về sách được truy xuất từ API nhà cung cấp sách. Kết quả được tạo ngẫu nhiên. Các cuốn sách của bạn có thể không giống nhau, nhưng bạn sẽ nhận được 2 đề xuất sách ở định dạng 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"}]

Nếu bạn thấy thông báo này, tức là công cụ đầu tiên đang hoạt động đúng cách!

Thay vì tạo rõ ràng một lệnh gọi API RESTful với các tham số cụ thể, chúng ta sẽ sử dụng ngôn ngữ tự nhiên ("Tôi đang học một khoá học..."). Sau đó, tác nhân sẽ trích xuất một cách thông minh các tham số cần thiết (chẳng hạn như danh mục) bằng cách sử dụng NLP, làm nổi bật cách tác nhân tận dụng khả năng hiểu ngôn ngữ tự nhiên để tương tác với API.

cuộc gọi so sánh

👉Xoá mã kiểm thử sau đây khỏi 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."))

Lấy dữ liệu chương trình giảng dạy từ cơ sở dữ liệu

Tiếp theo, chúng ta sẽ tạo một công cụ tìm nạp dữ liệu chương trình giảng dạy có cấu trúc từ cơ sở dữ liệu Cloud SQL PostgreSQL. Điều này cho phép trợ lý truy cập vào một nguồn thông tin đáng tin cậy để lập kế hoạch bài học.

create db

Bạn có nhớ phiên bản Cloud SQL aidemy mà bạn đã tạo ở bước trước không? Đây là nơi bạn sẽ sử dụng tính năng này.

👉 Trong thiết bị đầu cuối, hãy chạy lệnh sau để tạo một cơ sở dữ liệu có tên là aidemy-db trong phiên bản mới.

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

Hãy xác minh phiên bản trong Cloud SQL trong Google Cloud Console. Bạn sẽ thấy một phiên bản Cloud SQL có tên là aidemy trong danh sách.

👉 Nhấp vào tên phiên bản để xem thông tin chi tiết. 👉 Trong trang chi tiết về phiên bản Cloud SQL, hãy nhấp vào Cloud SQL Studio trong trình đơn điều hướng bên trái. Thao tác này sẽ mở một thẻ mới.

Chọn aidemy-db làm cơ sở dữ liệu, nhập postgres làm người dùng1234qwer làm mật khẩu.

Nhấp vào Xác thực

sql studio sign in

👉Trong trình chỉnh sửa truy vấn SQL Studio, hãy chuyển đến thẻ Editor 1 (Trình chỉnh sửa 1), rồi dán mã SQL sau:

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.');

Mã SQL này tạo một bảng có tên là curriculums và chèn một số dữ liệu mẫu.

👉 Nhấp vào Run (Chạy) để thực thi mã SQL. Bạn sẽ thấy một thông báo xác nhận rằng các câu lệnh đã được thực thi thành công.

👉 Mở rộng trình khám phá, tìm bảng curriculums mới tạo rồi nhấp vào truy vấn. Thao tác này sẽ mở một thẻ trình chỉnh sửa mới có SQL được tạo cho bạn,

sql studio select table

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

👉Nhấp vào Chạy.

Bảng kết quả sẽ hiển thị các hàng dữ liệu mà bạn đã chèn ở bước trước, xác nhận rằng bảng và dữ liệu đã được tạo chính xác.

Giờ đây, bạn đã tạo thành công một cơ sở dữ liệu có dữ liệu mẫu về chương trình giảng dạy, chúng ta sẽ tạo một công cụ để truy xuất dữ liệu đó.

👉Trong Trình chỉnh sửa mã trên đám mây, hãy chỉnh sửa tệp curriculums.py trong thư mục aidemy-bootstrapdán đoạn mã sau vào cuối tệp:

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"]

    print(f"--------------------------->db_user: {db_user!r}")
    print(f"--------------------------->db_pass: {db_pass!r}")
    print(f"--------------------------->db_name: {db_name!r}")

    connector = Connector()

    pool = sqlalchemy.create_engine(
        "postgresql+pg8000://",
        creator=lambda: connector.connect(
            instance_connection_name,
            "pg8000",
            user=db_user,
            password=db_pass,
            db=db_name,
        ),
        pool_size=2,
        max_overflow=2,
        pool_timeout=30,  # 30 seconds
        pool_recycle=1800,  # 30 minutes
    )
    return pool

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 = connect_with_connector()

Giải thích:

  • Biến môi trường: Mã này truy xuất thông tin đăng nhập và thông tin kết nối cơ sở dữ liệu từ các biến môi trường (xem thêm thông tin về vấn đề này bên dưới).
  • connect_with_connector(): Hàm này sử dụng Cloud SQL Connector để thiết lập một kết nối an toàn đến cơ sở dữ liệu.
  • get_curriculum(year: int, subject: str): Hàm này nhận năm và môn học làm dữ liệu đầu vào, truy vấn bảng chương trình giảng dạy và trả về nội dung mô tả chương trình giảng dạy tương ứng.

👉Trước khi có thể chạy mã, chúng ta phải đặt một số biến môi trường, trong thiết bị đầu cuối, hãy chạy:

gcloud config set project $(cat ~/project_id.txt)
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"

👉Để kiểm thử, hãy thêm đoạn mã sau vào cuối curriculums.py:

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

👉Chạy mã:

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

Bạn sẽ thấy nội dung mô tả chương trình giảng dạy môn Toán lớp 6 được in trên bảng điều khiển.

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

Nếu bạn thấy phần mô tả chương trình học, tức là công cụ cơ sở dữ liệu đang hoạt động bình thường! Tiếp theo, hãy dừng tập lệnh bằng cách nhấn Ctrl+C nếu tập lệnh vẫn đang chạy.

👉Xoá mã kiểm thử sau đây khỏi curriculums.py

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

👉Thoát khỏi môi trường ảo, trong dòng lệnh, hãy chạy:

deactivate

6. Công cụ xây dựng: Truy cập thông tin theo thời gian thực trên web

Cuối cùng, chúng ta sẽ xây dựng một công cụ sử dụng tính năng tích hợp Gemini 2 và Google Tìm kiếm để truy cập thông tin theo thời gian thực trên web. Điều này giúp tác nhân luôn nắm bắt thông tin mới nhất và cung cấp kết quả phù hợp.

Việc tích hợp Gemini 2 với Google Tìm kiếm API giúp nâng cao khả năng của tác nhân bằng cách cung cấp kết quả tìm kiếm chính xác và phù hợp với bối cảnh hơn. Điều này cho phép các tác nhân truy cập vào thông tin mới nhất và đưa ra câu trả lời dựa trên dữ liệu thực tế, giảm thiểu tình trạng ảo tưởng. Việc cải thiện khả năng tích hợp API cũng giúp các truy vấn bằng ngôn ngữ tự nhiên trở nên dễ dàng hơn, cho phép nhân viên hỗ trợ đưa ra các yêu cầu tìm kiếm phức tạp và tinh tế.

Tìm kiếm

Hàm này nhận cụm từ tìm kiếm, chương trình giảng dạy, môn học và năm làm dữ liệu đầu vào, đồng thời sử dụng Gemini API và công cụ Google Tìm kiếm để truy xuất thông tin liên quan trên Internet. Nếu quan sát kỹ, bạn sẽ thấy ứng dụng này đang sử dụng Google Generative AI SDK để thực hiện lệnh gọi hàm mà không dùng bất kỳ khung nào khác.

👉Chỉnh sửa search.py trong thư mục aidemy-bootstrapdán đoạn mã sau vào cuối tệp:

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)

Giải thích:

  • Công cụ định nghĩa – google_search_tool: Bao bọc đối tượng GoogleSearch trong một Công cụ
  • search_latest_resource(search_text: str, subject: str, year: int): Hàm này nhận cụm từ tìm kiếm, chủ đề và năm làm dữ liệu đầu vào, đồng thời sử dụng Gemini API để thực hiện tìm kiếm trên Google.
  • GenerateContentConfig: Xác định rằng công cụ này có quyền truy cập vào công cụ Google Tìm kiếm

Mô hình Gemini sẽ phân tích search_text một cách nội bộ và xác định xem có thể trả lời trực tiếp câu hỏi hay cần sử dụng công cụ Google Tìm kiếm. Đây là một bước quan trọng diễn ra trong quá trình suy luận của LLM. Mô hình này đã được huấn luyện để nhận biết những tình huống cần đến các công cụ bên ngoài. Nếu mô hình quyết định sử dụng công cụ Google Tìm kiếm, thì Google Generative AI SDK sẽ xử lý lệnh gọi thực tế. SDK sẽ lấy quyết định của mô hình và các tham số mà mô hình tạo ra rồi gửi chúng đến Google Search API. Phần này bị ẩn đối với người dùng trong mã.

Sau đó, mô hình Gemini sẽ tích hợp kết quả tìm kiếm vào câu trả lời của mình. Công cụ này có thể sử dụng thông tin đó để trả lời câu hỏi của người dùng, tạo bản tóm tắt hoặc thực hiện một số thao tác khác.

👉Để kiểm thử, hãy chạy mã:

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

Bạn sẽ thấy phản hồi của Gemini Search API chứa kết quả tìm kiếm liên quan đến "Giáo trình môn Toán lớp 5". Đầu ra chính xác sẽ phụ thuộc vào kết quả tìm kiếm, nhưng đó sẽ là một đối tượng JSON chứa thông tin về nội dung tìm kiếm.

Nếu bạn thấy kết quả tìm kiếm, tức là công cụ Google Tìm kiếm đang hoạt động bình thường! Tiếp theo, hãy dừng tập lệnh bằng cách nhấn Ctrl+C nếu tập lệnh vẫn đang chạy.

👉Và xoá phần cuối cùng trong mã.

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)

👉Thoát khỏi môi trường ảo, trong dòng lệnh, hãy chạy:

deactivate

Xin chúc mừng! Giờ đây, bạn đã tạo ra 3 công cụ mạnh mẽ cho trợ lý lập kế hoạch của mình: một trình kết nối API, một trình kết nối cơ sở dữ liệu và một công cụ Tìm kiếm của Google. Những công cụ này sẽ cho phép tác nhân truy cập vào thông tin và các chức năng cần thiết để tạo ra các kế hoạch giảng dạy hiệu quả.

7. Điều phối bằng LangGraph

Giờ đây, khi đã tạo các công cụ riêng lẻ, đã đến lúc chúng ta điều phối các công cụ đó bằng LangGraph. Điều này sẽ cho phép chúng tôi tạo ra một tác nhân "lập kế hoạch" tinh vi hơn, có thể quyết định một cách thông minh nên sử dụng công cụ nào và khi nào, dựa trên yêu cầu của người dùng.

LangGraph là một thư viện Python được thiết kế để giúp bạn dễ dàng tạo các ứng dụng có trạng thái, nhiều tác nhân bằng cách sử dụng Mô hình ngôn ngữ lớn (LLM). Hãy coi đây là một khung để điều phối các cuộc trò chuyện và quy trình công việc phức tạp liên quan đến các LLM, công cụ và tác nhân khác.

Khái niệm chính:

  • Cấu trúc biểu đồ: LangGraph biểu thị logic của ứng dụng dưới dạng một biểu đồ có hướng. Mỗi nút trong biểu đồ đại diện cho một bước trong quy trình (ví dụ: lệnh gọi đến một LLM, một lệnh gọi công cụ, một lệnh kiểm tra có điều kiện). Cạnh xác định luồng thực thi giữa các nút.
  • Trạng thái: LangGraph quản lý trạng thái của ứng dụng khi ứng dụng di chuyển qua biểu đồ. Trạng thái này có thể bao gồm các biến như thông tin đầu vào của người dùng, kết quả của các lệnh gọi công cụ, đầu ra trung gian từ LLM và mọi thông tin khác cần được duy trì giữa các bước.
  • Nút: Mỗi nút đại diện cho một phép tính hoặc hoạt động tương tác. Các loại này có thể là:
    • Nút công cụ: Sử dụng một công cụ (ví dụ: thực hiện tìm kiếm trên web, truy vấn cơ sở dữ liệu)
    • Nút hàm: Thực thi một hàm Python.
  • Cạnh: Kết nối các nút, xác định luồng thực thi. Các loại này có thể là:
    • Cạnh trực tiếp: Luồng đơn giản, vô điều kiện từ một nút đến một nút khác.
    • Cạnh có điều kiện: Luồng phụ thuộc vào kết quả của một nút có điều kiện.

LangGraph

Chúng ta sẽ dùng LangGraph để triển khai quy trình điều phối. Hãy chỉnh sửa tệp aidemy.py trong thư mục aidemy-bootstrap để xác định logic LangGraph.

👉 Thêm mã sau vào cuối

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"])} 

Hàm này chịu trách nhiệm lấy trạng thái hiện tại của cuộc trò chuyện, cung cấp cho LLM một thông báo hệ thống, sau đó yêu cầu LLM tạo ra một câu trả lời. LLM có thể trả lời trực tiếp cho người dùng hoặc chọn sử dụng một trong các công cụ hiện có.

tools : Danh sách này thể hiện tập hợp các công cụ mà tác nhân có thể sử dụng. Thư viện này chứa 3 hàm công cụ mà chúng ta đã xác định trong các bước trước: get_curriculum, search_latest_resourcerecommend_book. llm.bind_tools(tools): Phương thức này "liên kết" danh sách công cụ với đối tượng llm. Việc liên kết các công cụ sẽ cho LLM biết rằng các công cụ này có sẵn và cung cấp cho LLM thông tin về cách sử dụng các công cụ đó (ví dụ: tên của các công cụ, các tham số mà công cụ chấp nhận và chức năng của công cụ).

Chúng ta sẽ dùng LangGraph để triển khai quy trình điều phối.

👉 Thêm mã sau vào cuối

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")

Giải thích:

  • StateGraph(MessagesState): Tạo một đối tượng StateGraph. StateGraph là một khái niệm cốt lõi trong LangGraph. Đây là quy trình làm việc của trợ lý ảo dưới dạng biểu đồ, trong đó mỗi nút trong biểu đồ đại diện cho một bước trong quy trình. Hãy coi đây là việc xác định bản thiết kế cho cách mà tác nhân sẽ suy luận và hành động.
  • Conditional Edge (Cạnh có điều kiện): Bắt nguồn từ nút "determine_tool", đối số tools_condition có thể là một hàm xác định cạnh nào cần tuân theo dựa trên đầu ra của hàm determine_tool. Các cạnh có điều kiện cho phép biểu đồ phân nhánh dựa trên quyết định của LLM về việc sử dụng công cụ nào (hoặc có nên phản hồi trực tiếp cho người dùng hay không). Đây là lúc "trí thông minh" của tác nhân phát huy tác dụng – tác nhân có thể linh hoạt điều chỉnh hành vi của mình dựa trên tình huống.
  • Vòng lặp: Thêm một cạnh vào biểu đồ để kết nối nút "tools" trở lại nút "determine_tool". Điều này tạo ra một vòng lặp trong biểu đồ, cho phép tác nhân sử dụng các công cụ nhiều lần cho đến khi thu thập đủ thông tin để hoàn thành nhiệm vụ và đưa ra câu trả lời thoả đáng. Vòng lặp này rất quan trọng đối với những tác vụ phức tạp đòi hỏi nhiều bước suy luận và thu thập thông tin.

Bây giờ, hãy kiểm thử tác nhân lập kế hoạch của chúng ta để xem tác nhân này điều phối các công cụ khác nhau như thế nào.

Đoạn mã này sẽ chạy hàm prep_class với một thông tin đầu vào cụ thể của người dùng, mô phỏng yêu cầu tạo kế hoạch giảng dạy cho môn Toán lớp 5 về Hình học, sử dụng chương trình giảng dạy, đề xuất sách và tài nguyên mới nhất trên Internet.

👉 Trong cửa sổ dòng lệnh, nếu bạn đã đóng cửa sổ này hoặc các biến môi trường không còn được đặt, hãy chạy lại các lệnh sau

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
gcloud config set project $(cat ~/project_id.txt)
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"

👉Chạy mã:

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

Xem nhật ký trong dòng lệnh. Bạn sẽ thấy bằng chứng cho thấy trợ lý đang gọi cả 3 công cụ (lấy chương trình giảng dạy của trường, lấy đề xuất về sách và tìm kiếm tài nguyên mới nhất) trước khi cung cấp kế hoạch giảng dạy cuối cùng. Điều này cho thấy quá trình điều phối LangGraph đang hoạt động chính xác và tác nhân đang sử dụng một cách thông minh tất cả các công cụ có sẵn để đáp ứng yêu cầu của người dùng.

================================ 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**
.........

Dừng tập lệnh bằng cách nhấn Ctrl+C nếu tập lệnh vẫn đang chạy.

👉 (BƯỚC NÀY KHÔNG BẮT BUỘC) thay thế mã kiểm thử bằng một câu lệnh khác, yêu cầu gọi các công cụ khá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")

👉 Nếu bạn đã đóng cửa sổ dòng lệnh hoặc các biến môi trường không còn được thiết lập, hãy chạy lại các lệnh sau

gcloud config set project $(cat ~/project_id.txt)
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"

👉 (BƯỚC NÀY LÀ KHÔNG BẮT BUỘC, CHỈ THỰC HIỆN NẾU BẠN ĐÃ CHẠY BƯỚC TRƯỚC) Chạy lại mã:

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

Lần này bạn nhận thấy điều gì? Nhân viên hỗ trợ đã gọi những công cụ nào? Bạn sẽ thấy rằng lần này, tác nhân chỉ gọi công cụ search_latest_resource. Điều này là do câu lệnh không chỉ định rằng nó cần hai công cụ còn lại và LLM của chúng tôi đủ thông minh để không gọi các công cụ khác.

================================ 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.

Dừng tập lệnh bằng cách nhấn Ctrl+C.

👉 (ĐỪNG BỎ QUA BƯỚC NÀY!) Xoá mã kiểm thử để giữ cho tệp aidemy.py của bạn không bị lẫn lộn :

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")

Bây giờ, khi đã xác định được logic của tác nhân, hãy khởi chạy ứng dụng web Flask. Thao tác này sẽ cung cấp một giao diện quen thuộc dựa trên biểu mẫu để giáo viên tương tác với trợ lý. Mặc dù các LLM thường tương tác với chatbot, nhưng chúng tôi chọn sử dụng giao diện người dùng gửi biểu mẫu truyền thống vì giao diện này có thể trực quan hơn đối với nhiều nhà giáo dục.

👉 Nếu bạn đã đóng cửa sổ dòng lệnh hoặc các biến môi trường không còn được thiết lập, hãy chạy lại các lệnh sau

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
gcloud config set project $(cat ~/project_id.txt)
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"

👉 Giờ đây, hãy khởi động Giao diện người dùng trên web.

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

Tìm thông báo khởi động trong đầu ra của thiết bị đầu cuối Cloud Shell. Flask thường in các thông báo cho biết rằng ứng dụng đang chạy và chạy trên cổng nào.

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.

👉 Trong trình đơn "Xem trước trên web" ở góc trên cùng bên phải, hãy chọn Xem trước trên cổng 8080. Cloud Shell sẽ mở một thẻ hoặc cửa sổ trình duyệt mới có bản xem trước trên web của ứng dụng.

Trang web

Trong giao diện ứng dụng, hãy chọn 5 cho Năm, chọn chủ đề Mathematics rồi nhập Geometry vào Yêu cầu về tiện ích bổ sung

👉 Nếu đã rời khỏi giao diện người dùng của ứng dụng, hãy quay lại và bạn sẽ thấy đầu ra được tạo.

👉 Trong cửa sổ dòng lệnh, hãy dừng tập lệnh bằng cách nhấn Ctrl+C.

👉 Trong cửa sổ dòng lệnh, hãy thoát khỏi môi trường ảo:

deactivate

8. Triển khai tác nhân lập kế hoạch lên đám mây

Tạo và đẩy hình ảnh vào sổ đăng ký

Tổng quan

Đã đến lúc triển khai việc này lên đám mây.

👉 Trong thiết bị đầu cuối, hãy tạo một kho lưu trữ cấu phần phần mềm để lưu trữ hình ảnh docker mà chúng ta sẽ tạo.

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

Bạn sẽ thấy thông báo Created repository [agent-repository] (Đã tạo kho lưu trữ [agent-repository]).

👉 Chạy lệnh sau để tạo hình ảnh Docker.

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

👉 Chúng ta cần gắn lại thẻ cho hình ảnh để hình ảnh được lưu trữ trong Artifact Registry thay vì GCR và đẩy hình ảnh được gắn thẻ vào Artifact Registry:

gcloud config set project $(cat ~/project_id.txt)
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

Sau khi quá trình truyền dữ liệu hoàn tất, bạn có thể xác minh rằng hình ảnh đã được lưu trữ thành công trong Artifact Registry.

👉 Chuyển đến Artifact Registry trong Google Cloud Console. Bạn sẽ tìm thấy hình ảnh aidemy-planner trong kho lưu trữ agent-repository. Hình ảnh về công cụ lập kế hoạch của Aidemy

Bảo mật thông tin xác thực cơ sở dữ liệu bằng Secret Manager

Để quản lý và truy cập thông tin đăng nhập cơ sở dữ liệu một cách an toàn, chúng ta sẽ sử dụng Google Cloud Secret Manager. Điều này giúp ngăn chặn việc mã hoá cứng thông tin nhạy cảm trong mã ứng dụng của chúng ta và tăng cường bảo mật.

Chúng ta sẽ tạo các khoá bí mật riêng cho tên người dùng, mật khẩu và tên cơ sở dữ liệu. Phương pháp này cho phép chúng tôi quản lý từng thông tin đăng nhập một cách độc lập.

👉 Trong cửa sổ dòng lệnh, hãy chạy lệnh sau:

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=-

Việc sử dụng Secret Manager là một bước quan trọng trong việc bảo mật ứng dụng của bạn và ngăn chặn việc vô tình để lộ thông tin đăng nhập nhạy cảm. Nền tảng này tuân thủ các phương pháp hay nhất về bảo mật cho việc triển khai trên đám mây.

Triển khai lên Cloud Run

Cloud Run là một nền tảng không máy chủ được quản lý toàn diện, cho phép bạn triển khai các ứng dụng triển khai bằng vùng chứa riêng một cách nhanh chóng và dễ dàng. Nền tảng này tách biệt việc quản lý cơ sở hạ tầng, giúp bạn tập trung vào việc viết và triển khai mã. Chúng ta sẽ triển khai trình lập kế hoạch dưới dạng một dịch vụ Cloud Run.

👉Trong Google Cloud Console, hãy chuyển đến "Cloud Run". Nhấp vào TRIỂN KHAI VÙNG CHỨA rồi chọn SERVICE. Định cấu hình dịch vụ Cloud Run:

Cloud Run

  1. Hình ảnh vùng chứa: Nhấp vào "Chọn" trong trường URL. Tìm URL hình ảnh mà bạn đã đẩy lên Artifact Registry (ví dụ: us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/aidemy-planner/YOUR_IMG).
  2. Tên dịch vụ: aidemy-planner
  3. Khu vực: Chọn khu vực us-central1.
  4. Xác thực: Trong phạm vi hội thảo này, bạn có thể chọn "Cho phép lệnh gọi chưa xác thực". Đối với bản phát hành công khai, bạn nên hạn chế quyền truy cập.
  5. Mở rộng phần Container(s) (Vùng chứa), Volumes (Ổ đĩa), Networking (Mạng), Security (Bảo mật) và thiết lập những mục sau trong thẻ Container(s) (Vùng chứa) (:
    • Thẻ Cài đặt:
      • Tài nguyên
        • bộ nhớ : 2 GiB
    • Thẻ Biến và khoá bí mật:
      • Biến môi trường, hãy thêm các biến sau bằng cách nhấp vào nút + Thêm biến:
        • Thêm tên: GOOGLE_CLOUD_PROJECT và giá trị: <YOUR_PROJECT_ID>
        • Thêm tên: BOOK_PROVIDER_URL và đặt giá trị thành URL hàm của nhà cung cấp sách. Bạn có thể xác định URL này bằng lệnh sau trong thiết bị đầu cuối:
          gcloud config set project $(cat ~/project_id.txt)
          gcloud run services describe book-provider \
              --region=us-central1 \
              --project=$PROJECT_ID \
              --format="value(status.url)"
          
      • Trong phần Bí mật được hiển thị dưới dạng biến môi trường, hãy thêm các bí mật sau bằng cách nhấp vào nút + Tham chiếu dưới dạng bí mật:
        • Thêm tên: DB_USER, giá trị bí mật: chọn db-user và phiên bản:latest
        • Thêm tên: DB_PASS, giá trị bí mật: chọn db-pass và phiên bản:latest
        • Thêm tên: DB_NAME, giá trị bí mật: chọn db-name và phiên bản:latest

Đặt giá trị bí mật

Để nguyên các giá trị khác theo mặc định.

👉 Nhấp vào TẠO.

Cloud Run sẽ triển khai dịch vụ của bạn.

Sau khi triển khai, nếu bạn chưa ở trên trang chi tiết, hãy nhấp vào tên dịch vụ để chuyển đến trang chi tiết của dịch vụ đó. Bạn có thể tìm thấy URL đã triển khai ở trên cùng.

URL

👉 Trong giao diện ứng dụng, hãy chọn 7 cho Năm, chọn Mathematics làm chủ đề và nhập Algebra vào trường Yêu cầu về tiện ích bổ sung.

👉 Nhấp vào Tạo kế hoạch. Điều này sẽ cung cấp cho trợ lý ảo ngữ cảnh cần thiết để tạo một kế hoạch bài học phù hợp.

Xin chúc mừng! Bạn đã tạo thành công một kế hoạch giảng dạy bằng cách sử dụng tác nhân AI mạnh mẽ của chúng tôi. Điều này cho thấy tiềm năng của các trợ lý trong việc giảm đáng kể khối lượng công việc và đơn giản hoá các nhiệm vụ, từ đó cải thiện hiệu quả và giúp cuộc sống của nhà giáo dục trở nên dễ dàng hơn.

9. Hệ thống đa tác nhân

Giờ đây, khi đã triển khai thành công công cụ tạo kế hoạch giảng dạy, hãy chuyển trọng tâm sang việc xây dựng cổng thông tin dành cho học viên. Cổng thông tin này sẽ cung cấp cho học viên quyền truy cập vào các bài kiểm tra, bản tóm tắt âm thanh và bài tập liên quan đến khoá học của họ. Với phạm vi của chức năng này, chúng ta sẽ tận dụng sức mạnh của các hệ thống đa tác nhân để tạo ra một giải pháp theo mô-đun và có khả năng mở rộng.

Như chúng ta đã thảo luận trước đó, thay vì dựa vào một tác nhân duy nhất để xử lý mọi việc, hệ thống đa tác nhân cho phép chúng ta chia nhỏ khối lượng công việc thành các tác vụ nhỏ hơn, chuyên biệt hơn, mỗi tác vụ do một tác nhân chuyên dụng xử lý. Phương pháp này mang lại một số lợi ích chính:

Tính mô-đun và khả năng duy trì: Thay vì tạo một tác nhân duy nhất làm mọi việc, hãy tạo các tác nhân chuyên biệt, nhỏ hơn với trách nhiệm được xác định rõ ràng. Tính mô-đun này giúp hệ thống dễ hiểu, dễ duy trì và dễ gỡ lỗi hơn. Khi gặp vấn đề, bạn có thể cô lập vấn đề đó với một tác nhân cụ thể, thay vì phải sàng lọc qua một cơ sở mã khổng lồ.

Khả năng mở rộng: Việc mở rộng một tác nhân phức tạp duy nhất có thể là một điểm tắc nghẽn. Với hệ thống nhiều tác nhân, bạn có thể mở rộng quy mô từng tác nhân dựa trên nhu cầu cụ thể của tác nhân đó. Ví dụ: nếu một tác nhân đang xử lý một số lượng lớn yêu cầu, bạn có thể dễ dàng tăng thêm các phiên bản của tác nhân đó mà không ảnh hưởng đến phần còn lại của hệ thống.

Chuyên môn hoá theo nhóm: Hãy nghĩ theo cách này: bạn sẽ không yêu cầu một kỹ sư xây dựng toàn bộ ứng dụng từ đầu. Thay vào đó, bạn sẽ tập hợp một nhóm chuyên gia, mỗi người có chuyên môn trong một lĩnh vực cụ thể. Tương tự, hệ thống đa tác nhân cho phép bạn tận dụng điểm mạnh của nhiều LLM và công cụ, chỉ định chúng cho những tác nhân phù hợp nhất với các nhiệm vụ cụ thể.

Phát triển song song: Các nhóm khác nhau có thể làm việc trên các tác nhân khác nhau cùng một lúc, giúp tăng tốc quá trình phát triển. Vì các tác nhân độc lập với nhau, nên những thay đổi đối với một tác nhân sẽ ít có khả năng ảnh hưởng đến các tác nhân khác.

Cấu trúc hướng sự kiện

Để cho phép giao tiếp và phối hợp hiệu quả giữa các tác nhân này, chúng ta sẽ sử dụng một cấu trúc hướng sự kiện. Điều này có nghĩa là các tác nhân sẽ phản ứng với "sự kiện" xảy ra trong hệ thống.

Các tác nhân đăng ký các loại sự kiện cụ thể (ví dụ: "kế hoạch giảng dạy được tạo", "bài tập được tạo"). Khi một sự kiện xảy ra, các tác nhân liên quan sẽ được thông báo và có thể phản ứng cho phù hợp. Việc tách rời này giúp tăng tính linh hoạt, khả năng mở rộng và tốc độ phản hồi theo thời gian thực.

Tổng quan

Bây giờ, để bắt đầu, chúng ta cần một cách để truyền các sự kiện này. Để làm việc này, chúng ta sẽ thiết lập một chủ đề Pub/Sub. Hãy bắt đầu bằng cách tạo một chủ đề có tên là kế hoạch.

👉 Chuyển đến Google Cloud Console pub/sub.

👉 Nhấp vào nút Tạo chủ đề.

👉 Định cấu hình Chủ đề bằng mã nhận dạng/tên planbỏ đánh dấu Add a default subscription, giữ nguyên các mục còn lại ở chế độ mặc định rồi nhấp vào Tạo.

Trang Pub/Sub sẽ làm mới và bạn sẽ thấy chủ đề mới tạo được liệt kê trong bảng. Tạo chủ đề

Bây giờ, hãy tích hợp chức năng xuất bản sự kiện Pub/Sub vào tác nhân lập kế hoạch của chúng ta. Chúng ta sẽ thêm một công cụ mới gửi sự kiện "plan" đến chủ đề Pub/Sub mà chúng ta vừa tạo. Sự kiện này sẽ báo hiệu cho các tác nhân khác trong hệ thống (chẳng hạn như các tác nhân trong cổng thông tin dành cho học viên) rằng có một kế hoạch giảng dạy mới.

👉Quay lại Trình chỉnh sửa Cloud Code rồi mở tệp app.py nằm trong thư mục planner. Chúng ta sẽ thêm một hàm để xuất bản sự kiện. Thay thế:

##ADD SEND PLAN EVENT FUNCTION HERE

bằng mã sau

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: Hàm này lấy kế hoạch giảng dạy đã tạo làm dữ liệu đầu vào, tạo một ứng dụng Pub/Sub, tạo đường dẫn chủ đề, chuyển đổi kế hoạch giảng dạy thành chuỗi JSON và xuất bản thông báo lên chủ đề.

Trong cùng một tệp app.py

👉Cập nhật câu lệnh để hướng dẫn tác nhân gửi sự kiện kế hoạch giảng dạy đến chủ đề Pub/Sub sau khi tạo kế hoạch giảng dạy. *Thay thế

### ADD send_plan_event CALL

với những thông tin sau:

send_plan_event(teaching_plan)

Bằng cách thêm công cụ send_plan_event và sửa đổi câu lệnh, chúng ta đã cho phép tác nhân lập kế hoạch của mình xuất bản các sự kiện lên Pub/Sub, cho phép các thành phần khác trong hệ thống phản ứng với việc tạo kế hoạch giảng dạy mới. Giờ đây, chúng ta sẽ có một hệ thống đa tác nhân hoạt động trong các phần sau.

10. Hỗ trợ học viên bằng các bài kiểm tra theo yêu cầu

Hãy tưởng tượng một môi trường học tập nơi học viên có thể truy cập vào vô số bài kiểm tra được điều chỉnh cho phù hợp với kế hoạch học tập cụ thể của mình. Các bài kiểm tra này cung cấp thông tin phản hồi tức thì, bao gồm cả câu trả lời và giải thích, giúp học viên hiểu sâu hơn về tài liệu. Đây là tiềm năng mà chúng tôi muốn khai thác thông qua cổng thông tin về bài kiểm tra dựa trên AI.

Để hiện thực hoá ý tưởng này, chúng ta sẽ tạo một thành phần tạo câu hỏi trắc nghiệm có thể tạo câu hỏi trắc nghiệm dựa trên nội dung của kế hoạch giảng dạy.

Tổng quan

👉 Trong ngăn Explorer (Trình khám phá) của Cloud Code Editor, hãy chuyển đến thư mục portal. Mở tệp quiz.py, sao chép và dán mã sau vào cuối tệp.

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-2.5-flash-preview-04-17", 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


Trong tác nhân, bạn sẽ tạo một trình phân tích cú pháp đầu ra JSON được thiết kế riêng để hiểu và cấu trúc đầu ra của LLM. Nó sử dụng mô hình QuizQuestion mà chúng ta đã xác định trước đó để đảm bảo đầu ra được phân tích cú pháp tuân thủ đúng định dạng (câu hỏi, lựa chọn và câu trả lời).

👉 Trong cửa sổ dòng lệnh, hãy thực thi các lệnh sau để thiết lập môi trường ảo, cài đặt các phần phụ thuộc và khởi động tác nhân:

gcloud config set project $(cat ~/project_id.txt)
cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py

👉 Trong trình đơn "Xem trước trên web" ở góc trên cùng bên phải, hãy chọn Xem trước trên cổng 8080. Cloud Shell sẽ mở một thẻ hoặc cửa sổ trình duyệt mới có bản xem trước trên web của ứng dụng.

👉 Trong ứng dụng web, hãy Nhấp vào đường liên kết "Bài kiểm tra", trong thanh điều hướng trên cùng hoặc trên thẻ trên trang chỉ mục. Bạn sẽ thấy 3 bài kiểm tra được tạo ngẫu nhiên cho học viên. Các bài kiểm tra này dựa trên kế hoạch giảng dạy và cho thấy sức mạnh của hệ thống tạo bài kiểm tra dựa trên AI của chúng tôi.

Đố vui

👉Để dừng quy trình đang chạy cục bộ, hãy nhấn Ctrl+C trong thiết bị đầu cuối.

Gemini 2 suy nghĩ để giải thích

Được rồi, chúng ta đã có các bài kiểm tra, đây là một khởi đầu tuyệt vời! Nhưng nếu học viên làm sai thì sao? Đó là nơi diễn ra quá trình học tập thực sự, phải không? Nếu chúng ta có thể giải thích lý do câu trả lời của họ không chính xác và cách tìm ra câu trả lời đúng, thì họ sẽ có nhiều khả năng nhớ được câu trả lời đó. Ngoài ra, việc này cũng giúp họ không còn nhầm lẫn và tăng thêm sự tự tin.

Đó là lý do chúng tôi sẽ sử dụng những công cụ mạnh mẽ nhất: mô hình "tư duy" của Gemini 2! Hãy coi như bạn đang cho AI thêm một chút thời gian để suy nghĩ thấu đáo trước khi giải thích. Nhờ đó, bạn có thể đưa ra ý kiến phản hồi chi tiết và hiệu quả hơn.

Chúng tôi muốn xem liệu công cụ này có thể giúp học viên bằng cách hỗ trợ, trả lời và giải thích chi tiết hay không. Để thử nghiệm, chúng ta sẽ bắt đầu với một chủ đề nổi tiếng khó nhằn là Giải tích.

Tổng quan

👉Trước tiên, hãy chuyển đến Cloud Code Editor, trong answer.py bên trong thư mục portal. Thay thế mã hàm sau

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

với đoạn mã sau:

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)

Đây là một ứng dụng langchain rất đơn giản, trong đó ứng dụng này Khởi tạo mô hình Gemini 2 Flash, nơi chúng ta hướng dẫn mô hình này đóng vai trò là một giáo viên hữu ích và đưa ra lời giải thích

👉Thực thi lệnh sau trong dòng lệnh:

gcloud config set project $(cat ~/project_id.txt)
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

Bạn sẽ thấy kết quả tương tự như ví dụ được cung cấp trong hướng dẫn ban đầu. Mô hình hiện tại có thể không đưa ra lời giải thích thấu đáo.

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

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

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

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

Keep practicing, you'll get there!

👉 Trong tệp answer.py, hãy thay thế

model_name từ gemini-2.0-flash-001 đến gemini-2.0-flash-thinking-exp-01-21 trong hàm answer_thinking.

Điều này sẽ thay đổi LLM thành một LLM khác có khả năng suy luận tốt hơn. Điều này sẽ giúp mô hình tạo ra những lời giải thích phù hợp hơn.

👉 Chạy lại tập lệnh answer.py để kiểm thử mô hình tư duy mới:

gcloud config set project $(cat ~/project_id.txt)
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

Sau đây là ví dụ về câu trả lời của mô hình tư duy, câu trả lời này chi tiết và đầy đủ hơn nhiều, cung cấp lời giải thích từng bước về cách giải bài toán tích phân. Điều này cho thấy sức mạnh của các mô hình "tư duy" trong việc tạo ra những lời giải thích chất lượng cao. Bạn sẽ thấy kết quả xuất ra có dạng như sau:

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

👉XOÁ mã kiểm thử sau đây khỏi 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)

👉Thực thi các lệnh sau trong thiết bị đầu cuối để thiết lập môi trường ảo, cài đặt các phần phụ thuộc và khởi động tác nhân:

gcloud config set project $(cat ~/project_id.txt)
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py

👉 Trong trình đơn "Xem trước trên web" ở góc trên cùng bên phải, hãy chọn Xem trước trên cổng 8080. Cloud Shell sẽ mở một thẻ hoặc cửa sổ trình duyệt mới có bản xem trước trên web của ứng dụng.

👉 Trong ứng dụng web, hãy Nhấp vào đường liên kết "Bài kiểm tra", trong thanh điều hướng trên cùng hoặc trên thẻ trên trang chỉ mục.

👉 Trả lời tất cả các câu đố và đảm bảo bạn trả lời sai ít nhất một câu rồi nhấp vào Gửi.

câu trả lời đang suy nghĩ

Thay vì nhìn vào màn hình trống trong khi chờ phản hồi, hãy chuyển sang thiết bị đầu cuối của Cloud Editor. Bạn có thể quan sát tiến trình và mọi thông báo đầu ra hoặc lỗi do hàm của bạn tạo trong thiết bị đầu cuối của trình mô phỏng. 😁

👉 Trong thiết bị đầu cuối, hãy dừng quy trình đang chạy cục bộ bằng cách nhấn Ctrl+C trong thiết bị đầu cuối.

11. KHÔNG BẮT BUỘC: Điều phối các tác nhân bằng Eventarc

Cho đến nay, cổng thông tin dành cho học viên đã tạo các bài kiểm tra dựa trên một bộ kế hoạch giảng dạy mặc định. Điều này rất hữu ích, nhưng có nghĩa là tác nhân lập kế hoạch và tác nhân bài kiểm tra của cổng thông tin không thực sự tương tác với nhau. Bạn còn nhớ cách chúng ta thêm tính năng mà tác nhân lập kế hoạch xuất bản các kế hoạch giảng dạy mới tạo vào một chủ đề Pub/Sub không? Bây giờ, đã đến lúc kết nối yêu cầu đó với tác nhân cổng thông tin của chúng ta!

Tổng quan

Chúng ta muốn cổng thông tin tự động cập nhật nội dung của bài kiểm tra bất cứ khi nào có kế hoạch giảng dạy mới được tạo. Để làm như vậy, chúng ta sẽ tạo một điểm cuối trong cổng thông tin có thể nhận các kế hoạch mới này.

👉 Trong ngăn Explorer của Cloud Code Editor, hãy chuyển đến thư mục portal.

👉 Mở tệp app.py để chỉnh sửa. THAY THẾ dòng ## REPLACE ME! NEW TEACHING PLAN bằng mã sau:

@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

Tạo lại và triển khai lên Cloud Run

Bạn cần cập nhật và triển khai lại cả tác nhân lập kế hoạch và tác nhân cổng thông tin của chúng tôi cho Cloud Run. Điều này đảm bảo rằng các thành phần có mã mới nhất và được định cấu hình để giao tiếp thông qua các sự kiện.

Tổng quan về việc triển khai

👉Trước tiên, chúng ta sẽ tạo lại và đẩy hình ảnh tác nhân planner, quay lại thiết bị đầu cuối và chạy:

cd ~/aidemy-bootstrap/planner/
gcloud config set project $(cat ~/project_id.txt)
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

👉Chúng ta sẽ làm tương tự, tạo và đẩy hình ảnh tác nhân cổng:

cd ~/aidemy-bootstrap/portal/
gcloud config set project $(cat ~/project_id.txt)
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

👉 Chuyển đến Artifact Registry, bạn sẽ thấy cả hình ảnh vùng chứa aidemy-planneraidemy-portal được liệt kê trong agent-repository.

Kho lưu trữ vùng chứa

👉Quay lại thiết bị đầu cuối, hãy chạy lệnh này để cập nhật hình ảnh Cloud Run cho tác nhân lập kế hoạch:

gcloud config set project $(cat ~/project_id.txt)
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

Bạn sẽ thấy kết quả xuất ra có dạng như sau:

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

Ghi lại URL dịch vụ; đây là đường liên kết đến nhân viên lập kế hoạch mà bạn đã triển khai. Nếu sau này bạn cần xác định URL dịch vụ của tác nhân lập kế hoạch, hãy dùng lệnh sau:

gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
gcloud run services describe aidemy-planner \
    --region=us-central1 \
    --format 'value(status.url)'

👉Chạy lệnh này để tạo phiên bản Cloud Run cho tác nhân portal

gcloud config set project $(cat ~/project_id.txt)
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}

Bạn sẽ thấy kết quả xuất ra có dạng như sau:

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

Ghi lại URL dịch vụ; đây là đường liên kết đến cổng thông tin học viên mà bạn đã triển khai. Nếu sau này bạn cần xác định URL dịch vụ của cổng thông tin dành cho sinh viên, hãy dùng lệnh sau:

gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
gcloud run services describe aidemy-portal \
    --region=us-central1 \
    --format 'value(status.url)'

Tạo điều kiện kích hoạt Eventarc

Nhưng câu hỏi lớn đặt ra là: làm cách nào để điểm cuối này nhận được thông báo khi có một kế hoạch mới đang chờ trong chủ đề Pub/Sub? Đó là lúc Eventarc xuất hiện để giải quyết vấn đề!

Eventarc đóng vai trò là cầu nối, lắng nghe các sự kiện cụ thể (chẳng hạn như một tin nhắn mới đến trong chủ đề Pub/Sub của chúng tôi) và tự động kích hoạt các hành động để phản hồi. Trong trường hợp này, nó sẽ phát hiện thời điểm một kế hoạch giảng dạy mới được xuất bản, sau đó gửi tín hiệu đến điểm cuối của cổng thông tin, cho biết đã đến lúc cần cập nhật.

Nhờ Eventarc xử lý hoạt động giao tiếp dựa trên sự kiện, chúng tôi có thể kết nối liền mạch tác nhân lập kế hoạch và tác nhân cổng thông tin, tạo ra một hệ thống học tập thực sự linh hoạt và có khả năng phản hồi. Giống như có một người đưa tin thông minh tự động gửi các kế hoạch bài học mới nhất đến đúng nơi!

👉Trong bảng điều khiển, hãy chuyển đến Eventarc.

👉Nhấp vào nút "+ TẠO ĐIỀU KIỆN KÍCH HOẠT".

Định cấu hình điều kiện kích hoạt (Thông tin cơ bản):

  • Tên điều kiện kích hoạt: plan-topic-trigger
  • Loại trình kích hoạt: Nguồn của Google
  • Nhà cung cấp sự kiện: Cloud Pub/Sub
  • Loại sự kiện: google.cloud.pubsub.topic.v1.messagePublished
  • Chủ đề Cloud Pub/Sub: chọn projects/PROJECT_ID/topics/plan
  • Khu vực: us-central1.
  • Tài khoản dịch vụ:
    • CẤP cho tài khoản dịch vụ vai trò roles/iam.serviceAccountTokenCreator
    • Sử dụng giá trị mặc định: Tài khoản dịch vụ tính toán mặc định
  • Đích đến của sự kiện: Cloud Run
  • Dịch vụ Cloud Run: aidemy-portal
  • Bỏ qua thông báo lỗi: Đã từ chối quyền đối với "locations/me-central2" (hoặc quyền này có thể không tồn tại).
  • Đường dẫn URL dịch vụ: /new_teaching_plan

👉 Nhấp vào "Tạo".

Trang Triggers (Trình kích hoạt) của Eventarc sẽ làm mới và bạn sẽ thấy trình kích hoạt mới tạo được liệt kê trong bảng.

Giờ đây, hãy truy cập vào tác nhân planner bằng URL dịch vụ của tác nhân này để yêu cầu một kế hoạch giảng dạy mới.

👉 Chạy lệnh này trong thiết bị đầu cuối để xác định URL dịch vụ của tác nhân lập kế hoạch:

gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

👉 Chuyển đến URL đã xuất và lần này hãy thử Năm 5, Chủ đề Science và Yêu cầu bổ sung atoms.

Sau đó, chờ một hoặc hai phút. Độ trễ này là do giới hạn thanh toán của phòng thí nghiệm này. Trong điều kiện bình thường, sẽ không có độ trễ.

Cuối cùng, hãy truy cập vào cổng thông tin dành cho học viên bằng URL dịch vụ của cổng thông tin đó.

Chạy lệnh này trong thiết bị đầu cuối để xác định URL dịch vụ của tác nhân cổng thông tin dành cho sinh viên:

gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

Bạn sẽ thấy các câu đố vui đã được cập nhật và hiện phù hợp với kế hoạch giảng dạy mới mà bạn vừa tạo! Điều này cho thấy quá trình tích hợp Eventarc vào hệ thống Aidemy đã thành công!

Aidemy-celebrate

Xin chúc mừng! Bạn đã xây dựng thành công một hệ thống đa tác nhân trên Google Cloud, tận dụng kiến trúc hướng sự kiện để tăng cường khả năng mở rộng và tính linh hoạt! Bạn đã xây dựng được nền tảng vững chắc, nhưng vẫn còn nhiều điều để khám phá. Để tìm hiểu sâu hơn về những lợi ích thực sự của cấu trúc này, khám phá sức mạnh của Live API đa phương thức của Gemini 2 và tìm hiểu cách triển khai quy trình điều phối một đường dẫn bằng LangGraph, bạn có thể tiếp tục đọc hai chương tiếp theo.

12. KHÔNG BẮT BUỘC: Tóm tắt bằng âm thanh do Gemini tạo

Gemini có thể hiểu và xử lý thông tin từ nhiều nguồn, chẳng hạn như văn bản, hình ảnh và thậm chí cả âm thanh, mở ra một loạt khả năng mới cho việc học tập và sáng tạo nội dung. Khả năng "nhìn", "nghe" và "đọc" của Gemini thực sự mở ra những trải nghiệm người dùng sáng tạo và hấp dẫn.

Ngoài việc chỉ tạo hình ảnh hoặc văn bản, một bước quan trọng khác trong quá trình học tập là tóm tắt và tổng kết hiệu quả. Hãy nghĩ xem: bạn có thường nhớ một câu hát dễ nhớ hơn là một điều gì đó bạn đọc trong sách giáo khoa không? Âm thanh có thể rất đáng nhớ! Đó là lý do chúng tôi sẽ tận dụng các khả năng đa phương thức của Gemini để tạo bản tóm tắt bằng âm thanh cho kế hoạch giảng dạy. Nhờ đó, học viên có thể xem lại tài liệu một cách thuận tiện và hấp dẫn, đồng thời có thể tăng khả năng ghi nhớ và hiểu bài thông qua sức mạnh của việc học bằng thính giác.

Tổng quan về Live API

Chúng ta cần một nơi để lưu trữ các tệp âm thanh được tạo. Cloud Storage cung cấp một giải pháp có thể mở rộng và đáng tin cậy.

👉Chuyển đến phần Bộ nhớ trong bảng điều khiển. Nhấp vào "Buckets" (Nhóm) trong trình đơn bên trái. Nhấp vào nút "+ TẠO" ở trên cùng.

👉Định cấu hình vùng chứa mới:

  • tên bộ chứa: aidemy-recap-UNIQUE_NAME.
    • QUAN TRỌNG: Đảm bảo bạn xác định một tên nhóm duy nhất bắt đầu bằng aidemy-recap-. Tiền tố duy nhất này rất quan trọng để tránh xung đột tên khi bạn tạo bộ chứa Cloud Storage.
  • khu vực: us-central1.
  • Lớp lưu trữ: "Chuẩn". Lớp chuẩn phù hợp với dữ liệu thường xuyên được truy cập.
  • Kiểm soát quyền truy cập: Chọn chế độ kiểm soát quyền truy cập "Đồng nhất" theo mặc định. Điều này giúp kiểm soát quyền truy cập nhất quán ở cấp bộ chứa.
  • Lựa chọn nâng cao: Đối với hội thảo này, các chế độ cài đặt mặc định thường là đủ.

Nhấp vào nút TẠO để tạo vùng chứa.

  • Bạn có thể thấy một cửa sổ bật lên về biện pháp phòng tránh truy cập công khai. Đánh dấu vào hộp "Thực thi biện pháp ngăn chặn quyền truy cập công khai trên nhóm này" rồi nhấp vào Confirm.

Bây giờ, bạn sẽ thấy nhóm mới tạo trong danh sách Nhóm. Hãy nhớ tên nhóm của bạn, bạn sẽ cần tên này sau.

👉Trong cửa sổ dòng lệnh của Trình chỉnh sửa Cloud Code, hãy chạy các lệnh sau để cấp quyền truy cập vào nhóm cho tài khoản dịch vụ:

gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
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"

👉Trong Trình chỉnh sửa mã Cloud, hãy mở audio.py bên trong thư mục courses. Dán đoạn mã sau vào cuối tệp:

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

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

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

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

 
def breakup_sessions(teaching_plan: str):
    asyncio.run(process_weeks(teaching_plan))
  • Kết nối phát trực tiếp: Trước tiên, một kết nối liên tục sẽ được thiết lập với điểm cuối Live API. Không giống như lệnh gọi API tiêu chuẩn (nơi bạn gửi yêu cầu và nhận phản hồi), kết nối này vẫn mở để trao đổi dữ liệu liên tục.
  • Cấu hình đa phương thức: Sử dụng cấu hình để chỉ định loại đầu ra bạn muốn (trong trường hợp này là âm thanh), thậm chí bạn có thể chỉ định những thông số bạn muốn sử dụng (ví dụ: lựa chọn giọng nói, mã hoá âm thanh)
  • Xử lý không đồng bộ: API này hoạt động không đồng bộ, tức là API này không chặn luồng chính trong khi chờ quá trình tạo âm thanh hoàn tất. Bằng cách xử lý dữ liệu theo thời gian thực và gửi đầu ra theo từng phần, tính năng này mang đến trải nghiệm gần như tức thì.

Giờ đây, câu hỏi quan trọng là: khi nào quy trình tạo âm thanh này sẽ chạy? Lý tưởng nhất là chúng ta muốn bản tóm tắt bằng âm thanh xuất hiện ngay khi một kế hoạch giảng dạy mới được tạo. Vì đã triển khai một cấu trúc hướng sự kiện bằng cách xuất bản kế hoạch giảng dạy cho một chủ đề Pub/Sub, nên chúng ta chỉ cần đăng ký theo dõi chủ đề đó.

Tuy nhiên, chúng tôi không thường xuyên tạo kế hoạch giảng dạy mới. Sẽ không hiệu quả nếu có một tác nhân liên tục chạy và chờ các kế hoạch mới. Đó là lý do tại sao việc triển khai logic tạo âm thanh này dưới dạng một Hàm Cloud Run là hoàn toàn hợp lý.

Bằng cách triển khai dưới dạng một hàm, hàm này sẽ ở trạng thái không hoạt động cho đến khi có một thông báo mới được đăng lên chủ đề Pub/Sub. Khi điều đó xảy ra, chức năng này sẽ tự động kích hoạt, tạo bản tóm tắt bằng âm thanh và lưu trữ chúng trong nhóm của chúng tôi.

👉Trong thư mục courses trong tệp main.py, tệp này xác định Cloud Run Function sẽ được kích hoạt khi có kế hoạch giảng dạy mới. Ứng dụng này sẽ nhận kế hoạch và bắt đầu tạo bản tóm tắt âm thanh. Thêm đoạn mã sau vào cuối tệp.

@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: Trình trang trí này đánh dấu hàm là một Cloud Run Function sẽ được kích hoạt bởi CloudEvents.

Kiểm thử cục bộ

👉Chúng ta sẽ chạy lệnh này trong một môi trường ảo và cài đặt các thư viện Python cần thiết cho hàm Cloud Run.

cd ~/aidemy-bootstrap/courses
gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
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

👉Trình mô phỏng Cloud Run Function cho phép chúng ta kiểm thử hàm cục bộ trước khi triển khai hàm đó lên Google Cloud. Khởi động trình mô phỏng cục bộ bằng cách chạy:

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

👉Trong khi trình mô phỏng đang chạy, bạn có thể gửi CloudEvents thử nghiệm đến trình mô phỏng để mô phỏng một kế hoạch giảng dạy mới đang được xuất bản. Trong một cửa sổ dòng lệnh mới:

Hai đầu nối

👉Chạy:

  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=="
    }
  }'

Thay vì nhìn chằm chằm vào màn hình trong khi chờ phản hồi, hãy chuyển sang thiết bị đầu cuối Cloud Shell khác. Bạn có thể quan sát tiến trình và mọi thông báo đầu ra hoặc lỗi do hàm của bạn tạo trong thiết bị đầu cuối của trình mô phỏng. 😁

Trong thiết bị đầu cuối thứ 2, bạn sẽ thấy thiết bị này trả về OK.

👉Bạn sẽ xác minh Dữ liệu trong bộ chứa, chuyển đến Cloud Storage rồi chọn thẻ "Bộ chứa" và chọn aidemy-recap-UNIQUE_NAME

Bộ chứa

👉Trong cửa sổ dòng lệnh đang chạy trình mô phỏng, hãy nhập ctrl+c để thoát. Đóng cửa sổ dòng lệnh thứ hai. Đóng cửa sổ dòng lệnh thứ hai và chạy lệnh deactivate để thoát khỏi môi trường ảo.

deactivate

Triển khai lên Google Cloud

Tổng quan về việc triển khai 👉Sau khi kiểm thử cục bộ, đã đến lúc triển khai tác nhân khoá học lên Google Cloud. Trong cửa sổ dòng lệnh, hãy chạy các lệnh sau:

cd ~/aidemy-bootstrap/courses
gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
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

Xác minh việc triển khai bằng cách chuyển đến Cloud Run trong Google Cloud Console.Bạn sẽ thấy một dịch vụ mới có tên là courses-agent trong danh sách.

Danh sách Cloud Run

Để kiểm tra cấu hình điều kiện kích hoạt, hãy nhấp vào dịch vụ courses-agent để xem thông tin chi tiết. Chuyển đến thẻ "TRIGGERS" (ĐIỀU KIỆN KÍCH HOẠT).

Bạn sẽ thấy một điều kiện kích hoạt được định cấu hình để theo dõi các thông báo được đăng lên chủ đề kế hoạch.

Trình kích hoạt Cloud Run

Cuối cùng, hãy xem cách chạy từ đầu đến cuối.

👉Chúng ta cần định cấu hình tác nhân cổng để tác nhân này biết vị trí tìm các tệp âm thanh đã tạo. Trong cửa sổ dòng lệnh, hãy chạy:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud config set project $(cat ~/project_id.txt)
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

👉Thử tạo kế hoạch giảng dạy mới bằng trang web của tác nhân lập kế hoạch. Có thể mất vài phút để bắt đầu. Đừng lo lắng, đây là một dịch vụ không cần máy chủ.

Để truy cập vào tác nhân lập kế hoạch, hãy lấy URL dịch vụ của tác nhân này bằng cách chạy lệnh sau trong thiết bị đầu cuối:

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

Sau khi tạo kế hoạch mới, hãy đợi 2-3 phút để âm thanh được tạo. Quá trình này sẽ mất thêm vài phút do hạn chế về việc lập hoá đơn đối với tài khoản phòng thí nghiệm này.

Bạn có thể theo dõi xem hàm courses-agent đã nhận được kế hoạch giảng dạy hay chưa bằng cách kiểm tra thẻ "TRIGGERS" (TRÌNH KÍCH HOẠT) của hàm. Làm mới trang định kỳ; cuối cùng bạn sẽ thấy hàm đã được gọi. Nếu hàm chưa được gọi sau hơn 2 phút, bạn có thể thử tạo lại kế hoạch giảng dạy. Tuy nhiên, hãy tránh tạo kế hoạch nhiều lần trong thời gian ngắn, vì mỗi kế hoạch được tạo sẽ được nhân viên hỗ trợ sử dụng và xử lý theo trình tự, có thể tạo ra một danh sách công việc tồn đọng.

Kích hoạt Observe

👉Truy cập vào cổng thông tin rồi nhấp vào "Khoá học". Bạn sẽ thấy 3 thẻ, mỗi thẻ hiển thị một bản tóm tắt bằng âm thanh. Cách tìm URL của nhân viên hỗ trợ trên cổng thông tin:

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

Nhấp vào "phát" trên mỗi khoá học để đảm bảo bản tóm tắt bằng âm thanh phù hợp với kế hoạch giảng dạy mà bạn vừa tạo! Khoá học trên cổng thông tin

Thoát khỏi môi trường ảo.

deactivate

13. KHÔNG BẮT BUỘC: Cộng tác dựa trên vai trò với Gemini và DeepSeek

Việc có nhiều góc nhìn là vô cùng quý giá, đặc biệt là khi bạn tạo ra những bài tập hấp dẫn và chu đáo. Giờ đây, chúng ta sẽ xây dựng một hệ thống đa tác nhân tận dụng 2 mô hình khác nhau với các vai trò riêng biệt để tạo bài tập: một mô hình thúc đẩy sự cộng tác và mô hình còn lại khuyến khích việc tự học. Chúng ta sẽ sử dụng cấu trúc "một lần", trong đó quy trình tuân theo một lộ trình cố định.

Công cụ tạo bài tập Gemini

Bản tổng quan do Gemini tạo Chúng ta sẽ bắt đầu bằng cách thiết lập chức năng Gemini để tạo bài tập có tính cộng tác. Chỉnh sửa tệp gemini.py nằm trong thư mục assignment.

👉Dán đoạn mã sau vào cuối tệp 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()

Công cụ này sử dụng mô hình Gemini để tạo bài tập.

Chúng ta đã sẵn sàng kiểm thử Tác nhân Gemini.

👉Chạy các lệnh sau trong thiết bị đầu cuối để thiết lập môi trường:

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

👉Bạn có thể chạy để kiểm thử:

python gemini.py

Bạn sẽ thấy một bài tập có nhiều hoạt động nhóm hơn trong kết quả. Thử nghiệm xác nhận ở cuối cũng sẽ xuất kết quả.

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. 
....

Dừng bằng ctl+c và để dọn dẹp mã kiểm thử. XOÁ mã sau đây khỏi 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()

Định cấu hình Trình tạo bài tập DeepSeek

Mặc dù các nền tảng AI dựa trên đám mây rất tiện lợi, nhưng việc tự lưu trữ LLM có thể rất quan trọng để bảo vệ quyền riêng tư của dữ liệu và đảm bảo chủ quyền dữ liệu. Chúng ta sẽ triển khai mô hình DeepSeek nhỏ nhất (1,5 tỷ tham số) trên một phiên bản Cloud Compute Engine. Có những cách khác như lưu trữ trên nền tảng Vertex AI của Google hoặc lưu trữ trên phiên bản GKE của bạn, nhưng vì đây chỉ là một hội thảo về các tác nhân AI và tôi không muốn giữ bạn ở đây mãi, nên chúng ta hãy sử dụng cách đơn giản nhất. Nhưng nếu bạn quan tâm và muốn tìm hiểu các lựa chọn khác, hãy xem tệp deepseek-vertexai.py trong thư mục bài tập. Tệp này cung cấp mã mẫu về cách tương tác với các mô hình được triển khai trên Vertex AI.

Tổng quan về Deepseek

👉Chạy lệnh này trong cửa sổ dòng lệnh để tạo một nền tảng LLM tự lưu trữ Ollama:

cd ~/aidemy-bootstrap/assignment
gcloud config set project $(cat ~/project_id.txt)
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

Cách xác minh rằng phiên bản Compute Engine đang chạy:

Chuyển đến Compute Engine > "Phiên bản VM" trong Google Cloud Console. Bạn sẽ thấy ollama-instance xuất hiện cùng dấu kiểm màu xanh lục cho biết rằng ollama-instance đang chạy. Nếu bạn không thấy, hãy đảm bảo rằng đó là vùng us-central1. Nếu không, bạn có thể phải tìm kiếm.

Danh sách Compute Engine

👉Chúng ta sẽ cài đặt mô hình DeepSeek nhỏ nhất và kiểm thử mô hình đó. Trong Cloud Shell Editor, trong một thiết bị đầu cuối Mới, hãy chạy lệnh sau để ssh vào phiên bản GCE.

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

Sau khi thiết lập kết nối SSH, bạn có thể thấy lời nhắc sau:

"Bạn có muốn tiếp tục không (Y/n)?"

Bạn chỉ cần nhập Y(không phân biệt chữ hoa chữ thường) rồi nhấn Enter để tiếp tục.

Tiếp theo, bạn có thể được yêu cầu tạo một cụm mật khẩu cho khoá SSH. Nếu không muốn sử dụng cụm mật khẩu, bạn chỉ cần nhấn Enter hai lần để chấp nhận giá trị mặc định (không có cụm mật khẩu).

👉Giờ đây, bạn đang ở trong máy ảo, hãy kéo mô hình DeepSeek R1 nhỏ nhất và kiểm thử xem mô hình này có hoạt động không?

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

👉Thoát phiên bản GCE, nhập nội dung sau vào cửa sổ dòng lệnh ssh:

exit

👉Tiếp theo, hãy thiết lập chính sách mạng để các dịch vụ khác có thể truy cập vào LLM. Vui lòng giới hạn quyền truy cập vào phiên bản nếu bạn muốn thực hiện việc này cho quá trình sản xuất, hãy triển khai tính năng đăng nhập bảo mật cho dịch vụ hoặc hạn chế quyền truy cập IP. Chạy:

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

👉Để xác minh xem chính sách tường lửa của bạn có hoạt động đúng cách hay không, hãy thử chạy:

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
        }'

Tiếp theo, chúng ta sẽ sử dụng hàm Deepseek trong tác nhân bài tập để tạo bài tập có nhấn mạnh vào công việc cá nhân.

👉Chỉnh sửa deepseek.py trong thư mục assignment, thêm đoạn mã sau vào cuối:

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()

👉hãy kiểm thử bằng cách chạy:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
gcloud config set project $(cat ~/project_id.txt)
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

Bạn sẽ thấy một bài tập có nhiều nội dung tự học hơn.

**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.
....

👉Dừng ctl+c và dọn dẹp mã kiểm thử. XOÁ mã sau đây khỏi 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()

Bây giờ, chúng ta sẽ sử dụng cùng một mô hình gemini để kết hợp cả hai câu lệnh thành một câu lệnh mới. Chỉnh sửa tệp gemini.py nằm trong thư mục assignment.

👉Dán đoạn mã sau vào cuối tệp 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

Để kết hợp điểm mạnh của cả hai mô hình, chúng ta sẽ điều phối một quy trình công việc đã xác định bằng LangGraph. Quy trình này bao gồm 3 bước: đầu tiên, mô hình Gemini tạo một bài tập chú trọng vào sự cộng tác; thứ hai, mô hình DeepSeek tạo một bài tập nhấn mạnh vào công việc cá nhân; cuối cùng, Gemini tổng hợp hai bài tập này thành một bài tập duy nhất và toàn diện. Vì chúng tôi xác định trước trình tự các bước mà không cần LLM đưa ra quyết định, nên đây là một quy trình điều phối do người dùng xác định theo một đường dẫn duy nhất.

Tổng quan về Langraph combine

👉Dán đoạn mã sau vào cuối tệp main.py trong thư mục assignment:

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

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

    return state["final_assignment"]



import unittest

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


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

👉Để kiểm thử ban đầu hàm create_assignment và xác nhận rằng quy trình kết hợp Gemini và DeepSeek hoạt động, hãy chạy lệnh sau:

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

Bạn nên xem xét một mô hình kết hợp cả hai mô hình trên với quan điểm riêng của từng mô hình đối với việc học tập của học viên và cả đối với công việc nhóm của học viên.

**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

👉Dừng ctl+c và dọn dẹp mã kiểm thử. XOÁ mã sau đây khỏi main.py

import unittest

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


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

Tạo bài tập.png

Để quy trình tạo bài tập diễn ra tự động và đáp ứng các kế hoạch giảng dạy mới, chúng tôi sẽ tận dụng cấu trúc hướng sự kiện hiện có. Đoạn mã sau đây xác định một hàm Cloud Run (generate_assignment) sẽ được kích hoạt bất cứ khi nào một kế hoạch giảng dạy mới được xuất bản lên chủ đề Pub/Sub "plan".

👉Thêm đoạn mã sau vào cuối main.py trong thư mục assignment:

@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

Kiểm thử cục bộ

Trước khi triển khai lên Google Cloud, bạn nên kiểm thử Cloud Run Function cục bộ. Điều này giúp quá trình lặp lại nhanh hơn và gỡ lỗi dễ dàng hơn.

Trước tiên, hãy tạo một bộ chứa Cloud Storage để lưu trữ các tệp bài tập đã tạo và cấp cho tài khoản dịch vụ quyền truy cập vào bộ chứa đó. Chạy các lệnh sau trong thiết bị đầu cuối:

👉LƯU Ý QUAN TRỌNG: Đảm bảo bạn xác định một tên ASSIGNMENT_BUCKET riêng biệt bắt đầu bằng "aidemy-assignment-". Tên riêng biệt này là yếu tố quan trọng để tránh xung đột tên khi tạo bộ chứa Cloud Storage. (Thay thế <YOUR_NAME> bằng một từ ngẫu nhiên)

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

👉Và chạy:

gcloud config set project $(cat ~/project_id.txt)
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"

👉Bây giờ, hãy khởi động trình mô phỏng Hàm Cloud Run:

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

👉Trong khi trình mô phỏng đang chạy ở một cửa sổ dòng lệnh, hãy mở một cửa sổ dòng lệnh thứ hai trong Cloud Shell. Trong thiết bị đầu cuối thứ hai này, hãy gửi một CloudEvent thử nghiệm đến trình mô phỏng để mô phỏng một kế hoạch giảng dạy mới được xuất bản:

Hai đầu nối

  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=="
    }
  }'

Thay vì nhìn chằm chằm vào màn hình trong khi chờ phản hồi, hãy chuyển sang thiết bị đầu cuối Cloud Shell khác. Bạn có thể quan sát tiến trình và mọi thông báo đầu ra hoặc lỗi do hàm của bạn tạo trong thiết bị đầu cuối của trình mô phỏng. 😁

Lệnh curl sẽ in "OK" (không có dòng mới, vì vậy "OK" có thể xuất hiện trên cùng một dòng với dấu nhắc của trình bao trên thiết bị đầu cuối).

Để xác nhận rằng hệ thống đã tạo và lưu trữ thành công hoạt động chỉ định, hãy chuyển đến Google Cloud Console rồi chuyển đến Bộ nhớ > "Cloud Storage". Chọn nhóm aidemy-assignment mà bạn đã tạo. Bạn sẽ thấy một tệp văn bản có tên là assignment-{random number}.txt trong nhóm. Nhấp vào tệp để tải xuống và xác minh nội dung của tệp. Thao tác này xác minh rằng một tệp mới chứa bài tập mới vừa được tạo.

12-01-assignment-bucket

👉Trong cửa sổ dòng lệnh đang chạy trình mô phỏng, hãy nhập ctrl+c để thoát. Đóng cửa sổ dòng lệnh thứ hai. 👉Ngoài ra, trong thiết bị đầu cuối đang chạy trình mô phỏng, hãy thoát khỏi môi trường ảo.

deactivate

Tổng quan về việc triển khai

👉Tiếp theo, chúng ta sẽ triển khai tác nhân chỉ định lên đám mây

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
gcloud config set project $(cat ~/project_id.txt)
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 

Xác minh việc triển khai bằng cách truy cập Google Cloud Console, rồi chuyển đến Cloud Run. Bạn sẽ thấy một dịch vụ mới có tên là courses-agent trong danh sách. 12-03-function-list

Sau khi triển khai, thử nghiệm và phát hành quy trình tạo bài tập, chúng ta có thể chuyển sang bước tiếp theo: cung cấp các bài tập này trong cổng thông tin dành cho học viên.

14. KHÔNG BẮT BUỘC: Cộng tác dựa trên vai trò với Gemini và DeepSeek – Tiếp theo

Tạo trang web động

Để cải thiện cổng thông tin dành cho học viên và tăng tính hấp dẫn, chúng tôi sẽ triển khai tính năng tạo HTML động cho các trang bài tập. Mục tiêu là tự động cập nhật cổng thông tin bằng một thiết kế mới mẻ và bắt mắt mỗi khi một bài tập mới được tạo. Điều này tận dụng khả năng lập trình của LLM để tạo ra trải nghiệm người dùng năng động và thú vị hơn.

14-01-generate-html

👉Trong Cloud Shell Editor, hãy chỉnh sửa tệp render.py trong thư mục portal, thay thế

def render_assignment_page():
    return ""

bằng đoạn mã sau:

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)}"

Công cụ này sử dụng mô hình Gemini để tự động tạo HTML cho bài tập. Công cụ này lấy nội dung bài tập làm dữ liệu đầu vào và dùng một câu lệnh để hướng dẫn Gemini tạo một trang HTML bắt mắt với phong cách sáng tạo.

Tiếp theo, chúng ta sẽ tạo một điểm cuối được kích hoạt bất cứ khi nào một tài liệu mới được thêm vào nhóm bài tập:

👉Trong thư mục cổng thông tin, hãy chỉnh sửa tệp app.pyTHAY THẾ dòng ## REPLACE ME! RENDER ASSIGNMENT bằng đoạn mã sau:

@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

Khi được kích hoạt, hàm này sẽ truy xuất tên tệp và tên bộ chứa từ dữ liệu yêu cầu, tải nội dung bài tập xuống từ Cloud Storage và gọi hàm render_assignment_page để tạo HTML.

👉Chúng ta sẽ tiếp tục và chạy chương trình kiểm thử cục bộ:

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

👉Trong trình đơn "Xem trước trên web" ở đầu cửa sổ Cloud Shell, hãy chọn "Xem trước trên cổng 8080". Thao tác này sẽ mở ứng dụng của bạn trong một thẻ trình duyệt mới. Chuyển đến đường liên kết Bài tập trong thanh điều hướng. Lúc này, bạn sẽ thấy một trang trống. Đây là hành vi dự kiến vì chúng ta chưa thiết lập cầu nối giao tiếp giữa tác nhân chỉ định và cổng thông tin để điền sẵn nội dung một cách linh động.

14-02-deployment-overview

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

👉Để kết hợp những thay đổi này và triển khai mã đã cập nhật, hãy tạo lại và đẩy hình ảnh tác nhân cổng:

cd ~/aidemy-bootstrap/portal/
gcloud config set project $(cat ~/project_id.txt)
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
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

👉Sau khi đẩy hình ảnh mới, hãy triển khai lại dịch vụ Cloud Run. Chạy tập lệnh sau để buộc cập nhật Cloud Run:

gcloud config set project $(cat ~/project_id.txt)
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

👉Giờ đây, chúng ta sẽ triển khai một trình kích hoạt Eventarc để theo dõi mọi đối tượng mới được tạo (hoàn tất) trong nhóm chỉ định. Sự kiện kích hoạt này sẽ tự động gọi điểm cuối /render_assignment trên dịch vụ cổng thông tin khi một tệp bài tập mới được tạo.

gcloud config set project $(cat ~/project_id.txt)
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"

Để xác minh rằng bạn đã tạo thành công trình kích hoạt, hãy chuyển đến trang Trình kích hoạt Eventarc trong Google Cloud Console. Bạn sẽ thấy portal-assignment-trigger được liệt kê trong bảng. Nhấp vào tên điều kiện kích hoạt để xem thông tin chi tiết. Điều kiện kích hoạt chỉ định

Có thể mất từ 2 đến 3 phút để điều kiện kích hoạt mới có hiệu lực.

Để xem quá trình tạo chỉ định động đang diễn ra, hãy chạy lệnh sau để tìm URL của tác nhân lập kế hoạch (nếu bạn không có sẵn URL này):

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

Tìm URL của tác nhân cổng thông tin:

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

Trong tác nhân lập kế hoạch, hãy tạo một kế hoạch giảng dạy mới.

13-02-assignment

Sau vài phút (để quá trình tạo âm thanh, tạo bài tập và kết xuất HTML hoàn tất), hãy chuyển đến cổng thông tin dành cho học viên.

👉Nhấp vào đường liên kết "Bài tập" trong thanh điều hướng. Bạn sẽ thấy một bài tập mới tạo có HTML được tạo động. Mỗi lần tạo kế hoạch giảng dạy, đó phải là một bài tập linh hoạt.

13-02-assignment

Chúc mừng bạn đã hoàn thành hệ thống đa tác nhân của Aidemy! Bạn đã có được kinh nghiệm thực tế và thông tin chi tiết có giá trị về:

  • Lợi ích của hệ thống nhiều tác nhân, bao gồm tính mô-đun, khả năng mở rộng, tính chuyên biệt và quy trình bảo trì đơn giản.
  • Tầm quan trọng của cấu trúc hướng sự kiện đối với việc tạo các ứng dụng có khả năng phản hồi và được liên kết lỏng lẻo.
  • Việc sử dụng LLM một cách chiến lược, kết hợp mô hình phù hợp với tác vụ và tích hợp các mô hình đó với các công cụ để tạo ra tác động thực tế.
  • Các phương pháp phát triển dựa trên nền tảng đám mây bằng cách sử dụng các dịch vụ của Google Cloud để tạo ra các giải pháp có khả năng mở rộng và đáng tin cậy.
  • Tầm quan trọng của việc cân nhắc quyền riêng tư về dữ liệu và các mô hình tự lưu trữ như một giải pháp thay thế cho các giải pháp của nhà cung cấp.

Giờ đây, bạn đã có nền tảng vững chắc để xây dựng các ứng dụng tinh vi dựa trên AI trên Google Cloud!

15. Thử thách và các bước tiếp theo

Chúc mừng bạn đã xây dựng hệ thống đa tác nhân Aidemy! Bạn đã đặt nền móng vững chắc cho giáo dục dựa trên AI. Giờ đây, hãy xem xét một số thách thức và các điểm cải tiến tiềm năng trong tương lai để mở rộng hơn nữa các chức năng của công cụ này và đáp ứng nhu cầu thực tế:

Học tập tương tác bằng tính năng Hỏi đáp trực tiếp:

  • Thử thách: Bạn có thể tận dụng Live API của Gemini 2 để tạo tính năng hỏi đáp theo thời gian thực cho học viên không? Hãy tưởng tượng một lớp học ảo nơi học viên có thể đặt câu hỏi và nhận được câu trả lời tức thì dựa trên AI.

Tự động gửi và chấm điểm bài tập:

  • Thách thức: Thiết kế và triển khai một hệ thống cho phép học viên nộp bài tập theo hình thức kỹ thuật số và được AI chấm điểm tự động, đồng thời có cơ chế phát hiện và ngăn chặn hành vi đạo văn. Thử thách này mang đến cơ hội tuyệt vời để khám phá Quy trình tạo nội dung dựa trên thông tin truy xuất (RAG) nhằm nâng cao độ chính xác và độ tin cậy của quy trình chấm điểm và phát hiện đạo văn.

aidemy-climb

16. Dọn dẹp

Giờ đây, sau khi xây dựng và khám phá hệ thống đa tác nhân Aidemy, đã đến lúc dọn dẹp môi trường Google Cloud.

👉Xoá dịch vụ 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

👉Xoá điều kiện kích hoạt 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

👉Xoá chủ đề Pub/Sub

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

👉Xoá phiên bản Cloud SQL

gcloud sql instances delete aidemy --quiet

👉Xoá kho lưu trữ Artifact Registry

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

👉Xoá các giá trị bí mật trong Secret Manager

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

👉Xoá phiên bản Compute Engine (nếu được tạo cho Deepseek)

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

👉Xoá quy tắc tường lửa cho phiên bản Deepseek

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

👉Xoá bộ chứa 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 rm -r gs://$COURSE_BUCKET_NAME
gsutil rm -r gs://$ASSIGNMENT_BUCKET

aidemy-broom