1. Wprowadzenie
Cześć! Widzę, że podoba Ci się pomysł agentów – małych pomocników, którzy mogą załatwić Ci różne sprawy bez Twojego udziału. Świetnie! Nie zawsze jednak jeden pracownik wystarczy, zwłaszcza gdy zajmujesz się większymi i bardziej złożonymi projektami. Prawdopodobnie będziesz potrzebować całego zespołu. Właśnie w tym przypadku przydają się systemy wieloagentowe.
Agenty oparte na dużych modelach językowych zapewniają niesamowitą elastyczność w porównaniu z tradycyjnym twardym kodowaniem. Ale (zawsze jest jakieś „ale”) wiążą się z nimi pewne trudności. I dokładnie tym zajmiemy się na tym szkoleniu.
Oto, czego możesz się spodziewać – możesz traktować to jak awans na wyższy poziom:
Tworzenie pierwszego agenta za pomocą LangGraph: spróbujemy stworzyć własnego agenta za pomocą popularnego frameworka LangGraph. Dowiesz się, jak tworzyć narzędzia, które łączą się z bazami danych, korzystać z najnowszej wersji interfejsu Gemini 2 do wyszukiwania w internecie oraz optymalizować prompty i odpowiedzi, aby Twój agent mógł wchodzić w interakcje nie tylko z LLM, ale też z dotychczasowymi usługami. Pokażemy Ci też, jak działa wywoływanie funkcji.
Zarządzanie pracownikami obsługi klienta: Twoje podejście: omówimy różne sposoby zarządzania pracownikami obsługi klienta, od prostych ścieżek do bardziej złożonych scenariuszy wielościeżkowych. Możesz to traktować jako kierowanie pracami zespołu obsługi klienta.
Systemy wieloagentowe: dowiesz się, jak skonfigurować system, w którym pracownicy mogą współpracować i wspólnie wykonywać zadania – wszystko dzięki architekturze opartej na zdarzeniach.
Swoboda wyboru LLM – korzystaj z najlepszego modelu do danego zadania: nie ograniczamy się do jednego LLM. Zobaczysz, jak używać wielu LLM, przypisując im różne role, aby zwiększyć skuteczność rozwiązywania problemów dzięki niesamowitym „modelom myślenia”.
Zawartość dynamiczna? Żaden problem!: Wyobraź sobie, że Twój agent tworzy dynamiczne treści, które są dostosowywane do każdego użytkownika w czasie rzeczywistym. Pokażemy Ci, jak to zrobić.
Przeniesienie aplikacji do chmury z Google Cloud: zapomnij o zabawie na komputerze. Pokażemy Ci, jak zaprojektować i wdrażać system wieloagentowy w Google Cloud, aby był gotowy do użycia w rzeczywistych warunkach.
Ten projekt będzie dobrym przykładem stosowania wszystkich omawianych przez nas technik.
2. Architektura
Bycie nauczycielem lub praca w szkole może być bardzo satysfakcjonująca, ale trzeba przyznać, że obciążenie pracą, zwłaszcza przygotowanie, może być trudne. Dodatkowo często brakuje kadry, a korepetycje mogą być drogie. Dlatego proponujemy asystenta nauczyciela opartego na sztucznej inteligencji. To narzędzie może odciążyć nauczycieli i pomóc wypełnić lukę spowodowaną brakiem personelu i nieopłacalnych korepetycji.
Nasz asystent edukacyjny AI może przygotować szczegółowe plany lekcji, ciekawe quizy, łatwe do zrozumienia podsumowania w formie audio oraz spersonalizowane zadania. Dzięki temu nauczyciele mogą skupić się na tym, co robią najlepiej: nawiązywaniu kontaktu z uczniami i pomaganiu im w rozwijaniu pasji do nauki.
System ma 2 strony: jedną dla nauczycieli, którzy mogą tworzyć plany lekcji na kolejne tygodnie,
oraz jeden dla uczniów, aby mogli korzystać z quizów, podsumowań audio i projektów.
Przyjrzyjmy się architekturze, na której opiera się nasz asystent nauczyciela, Aidemy. Jak widzisz, podzieliliśmy go na kilka kluczowych elementów, które działają razem, aby to umożliwić.
Najważniejsze elementy i technologie architektoniczne:
Google Cloud Platform (GCP): centralny element całego systemu:
- Vertex AI: uzyskuje dostęp do modeli LLM Gemini od Google.
- Cloud Run: bezserwerowa platforma do wdrażania agentów i funkcji w kontenerach.
- Cloud SQL: baza danych PostgreSQL na potrzeby danych dotyczących programu nauczania.
- Pub/Sub i Eventarc: podstawa architektury opartej na zdarzeniach, która umożliwia asynchroniczną komunikację między komponentami.
- Cloud Storage: przechowuje podsumowania audio i pliki projektów.
- Menedżer obiektów tajnych: bezpiecznie zarządza danymi logowania do bazy danych.
- Artifact Registry: przechowuje obrazy Dockera dla agentów.
- Compute Engine: wdrażanie własnych LLM zamiast polegania na rozwiązaniach dostawców
LLM: „mózg” systemu:
- Modele Gemini od Google: (Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking, Gemini 1.5-pro) służą do planowania lekcji, generowania treści, tworzenia dynamicznego kodu HTML, wyjaśniania pytań w quizach i łączenia zadań.
- DeepSeek: służy do wykonywania specjalistycznych zadań polegających na generowaniu samodzielnych zadań
LangChain i LangGraph: platformy do tworzenia aplikacji wykorzystujących LLM
- Ułatwia tworzenie złożonych przepływów pracy z wieloma agentami.
- Umożliwia inteligentną koordynację narzędzi (wywołań interfejsu API, zapytań do bazy danych, wyszukiwań w internecie).
- Wdraża architekturę opartą na zdarzeniach, aby zapewnić elastyczność i skalowalność systemu.
Nasza architektura łączy w sobie możliwości LLM z uporządkowanymi danymi i komunikacją opartą na zdarzeniach, a wszystko to działa w Google Cloud. Dzięki temu możemy tworzyć skalowalne, niezawodne i skuteczne narzędzia do pomocy w nauce.
3. Zanim zaczniesz
W konsoli Google Cloud na stronie selektora projektów wybierz lub utwórz projekt Google Cloud. Sprawdź, czy w projekcie Cloud włączone są płatności. Dowiedz się, jak sprawdzić, czy w projekcie są włączone płatności
👉 Kliknij Aktywuj Cloud Shell u góry konsoli Google Cloud (to ikona terminala u góry panelu Cloud Shell). Kliknij przycisk „Otwórz edytor” (wygląda jak otwarta teczka z ołówkiem). W oknie otworzy się edytor kodu Cloud Shell. Po lewej stronie zobaczysz Eksploratora plików.
👉 Na pasku stanu na dole kliknij przycisk Zaloguj się w Cloud Code. Autoryzuj wtyczkę zgodnie z instrukcjami. Jeśli na pasku stanu widzisz Cloud Code – brak projektu, kliknij tę opcję, a następnie w menu „Wybierz projekt Google Cloud” wybierz konkretny projekt Google Cloud z listy utworzonych projektów.
👉Otwórz terminal w IDE w chmurze,
👉 W terminalu sprawdź, czy masz już uwierzytelnienie i czy projekt jest ustawiony na identyfikator Twojego projektu, używając tego polecenia:
gcloud auth list
👉 I uruchom:
gcloud config set project <YOUR_PROJECT_ID>
👉 Aby włączyć niezbędne interfejsy Google Cloud API, uruchom to polecenie:
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
Może to potrwać kilka minut.
Włączanie Gemini Code Assist w Cloud Shell IDE
W panelu po lewej stronie kliknij przycisk Pomoc w kodzie i po raz ostatni wybierz odpowiedni projekt Google Cloud. Jeśli pojawi się prośba o włączenie interfejsu Cloud AI Companion API, zrób to i kontynuuj. Po wybraniu projektu Google Cloud sprawdź, czy na pasku stanu Cloud Code widać komunikat o stanie Cloud Code. Sprawdź też, czy po prawej stronie na pasku stanu masz włączone narzędzie Code Assist.
Konfigurowanie uprawnień
👉Skonfiguruj uprawnienia konta usługi
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"
Przyznaj uprawnienia 👉Cloud Storage (odczyt/zapis):
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 (odczyt/zapis):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/cloudsql.editor"
👉Eventarc (odbieranie zdarzeń):
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 (użytkownik):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/aiplatform.user"
👉Secret Manager (tylko do odczytu):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/secretmanager.secretAccessor"
👉Zweryfikuj wynik w konsoli administracyjnej
4. Tworzenie pierwszego agenta
Zanim przejdziemy do złożonych systemów wieloagentowych, musimy stworzyć podstawowy element: pojedynczy funkcjonalny agent. W tej sekcji zaczniemy od utworzenia prostego agenta „book provider”. Agent dostawcy książki otrzymuje jako dane wejściowe kategorię i korzysta z modelu LLM Gemini, aby wygenerować reprezentację książki w formacie JSON w ramach tej kategorii. Następnie udostępnia te rekomendacje książek jako punkt końcowy interfejsu API REST .
👉 W innej karcie przeglądarki otwórz konsolę Google Cloud, a w menu nawigacyjnym (☰) wybierz „Cloud Run”. Kliknij przycisk „+… ZAPISZ FUNKCJĘ”.
👉 Następnie skonfigurujemy podstawowe ustawienia funkcji Cloud Run:
- Nazwa usługi:
book-provider
- Region:
us-central1
- Czas wykonywania:
Python 3.12
- Uwierzytelnianie:
Allow unauthenticated invocations
na Włączone.
👉 Pozostaw inne ustawienia bez zmian i kliknij Utwórz. Przejdziesz do edytora kodu źródłowego.
Zobaczysz wypełnione wstępnie pliki main.py
i requirements.txt
.
Element main.py
będzie zawierać logikę biznesową funkcji, a element requirements.txt
– potrzebne pakiety.
👉 Teraz możesz napisać kod. Zanim jednak zaczniesz, sprawdź, czy Gemini Code Assist może Ci pomóc. Wróć do edytora Cloud Shell, kliknij ikonę Gemini Code Assist i wklej w wierszu poleceń tę prośbę:
Use the functions_framework library to be deployable as an HTTP function.
Accept a request with category and number_of_book parameters (either in JSON body or query string).
Use langchain and gemini to generate the data for book with fields bookname, author, publisher, publishing_date.
Use pydantic to define a Book model with the fields: bookname (string, description: "Name of the book"), author (string, description: "Name of the author"), publisher (string, description: "Name of the publisher"), and publishing_date (string, description: "Date of publishing").
Use langchain and gemini model to generate book data. the output should follow the format defined in Book model.
The logic should use JsonOutputParser from langchain to enforce output format defined in Book Model.
Have a function get_recommended_books(category) that internally uses langchain and gemini to return a single book object.
The main function, exposed as the Cloud Function, should call get_recommended_books() multiple times (based on number_of_book) and return a JSON list of the generated book objects.
Handle the case where category or number_of_book are missing by returning an error JSON response with a 400 status code.
return a JSON string representing the recommended books. use os library to retrieve GOOGLE_CLOUD_PROJECT env var. Use ChatVertexAI from langchain for the LLM call
Code Assist wygeneruje wtedy potencjalne rozwiązanie, podając kod źródłowy i plik zależności requirements.txt.
Porównaj kod wygenerowany przez Code Assist z poniżej podanym, sprawdzonym, prawidłowym rozwiązaniem. Dzięki temu możesz ocenić skuteczność narzędzia i sprawdzić, czy nie występują jakieś rozbieżności. Chociaż nie należy ślepo ufać LLM, Code Assist może być świetnym narzędziem do szybkiego tworzenia prototypów i generowania początkowych struktur kodu. Warto z niego korzystać, aby uzyskać przewagę.
Ponieważ to warsztaty, użyjemy podanego poniżej zweryfikowanego kodu. Możesz jednak eksperymentować z kodem wygenerowanym przez Code Assist we własnym czasie, aby lepiej poznać jego możliwości i ograniczenia.
👉 Wróć do edytora kodu źródłowego funkcji Cloud Run (na innej karcie przeglądarki). Ostrożnie zastąp dotychczasową zawartość pliku main.py
kodem podanym poniżej:
import functions_framework
import json
from flask import Flask, jsonify, request
from langchain_google_vertexai import ChatVertexAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
import os
class Book(BaseModel):
bookname: str = Field(description="Name of the book")
author: str = Field(description="Name of the author")
publisher: str = Field(description="Name of the publisher")
publishing_date: str = Field(description="Date of publishing")
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")
llm = ChatVertexAI(model_name="gemini-1.0-pro")
def get_recommended_books(category):
"""
A simple book recommendation function.
Args:
category (str): category
Returns:
str: A JSON string representing the recommended books.
"""
parser = JsonOutputParser(pydantic_object=Book)
question = f"Generate a random made up book on {category} with bookname, author and publisher and publishing_date"
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
chain = prompt | llm | parser
response = chain.invoke({"query": question})
return json.dumps(response)
@functions_framework.http
def recommended(request):
request_json = request.get_json(silent=True) # Get JSON data
if request_json and 'category' in request_json and 'number_of_book' in request_json:
category = request_json['category']
number_of_book = int(request_json['number_of_book'])
elif request.args and 'category' in request.args and 'number_of_book' in request.args:
category = request.args.get('category')
number_of_book = int(request.args.get('number_of_book'))
else:
return jsonify({'error': 'Missing category or number_of_book parameters'}), 400
recommendations_list = []
for i in range(number_of_book):
book_dict = json.loads(get_recommended_books(category))
print(f"book_dict=======>{book_dict}")
recommendations_list.append(book_dict)
return jsonify(recommendations_list)
👉Zastąp zawartość pliku requirements.txt tym tekstem:
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
👉ustawimy punkt wejścia funkcji: recommended
👉 Kliknij ZAPISZ I WDRÓŻ, aby wdrożyć funkcję. Poczekaj na zakończenie procesu wdrażania. Stan zostanie wyświetlony w konsoli Cloud. Może to potrwać kilka minut.
👉Po wdrożeniu wróć do edytora Cloud Shell i w terminalu uruchom:
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
Powinien zawierać dane książki w formacie 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"}
]
Gratulacje! Udało Ci się wdrożyć funkcję Cloud Run. Jest to jedna z usług, które będziemy integrować podczas tworzenia naszego agenta Aidemy.
5. Tworzenie narzędzi: łączenie agentów z usługą RESTFUL i danymi
Pobierz projekt szkieletu Bootstrap. Upewnij się, że jesteś w edytorze Cloud Shell. W terminalu uruchom:
git clone https://github.com/weimeilin79/aidemy-bootstrap.git
Po wykonaniu tego polecenia w środowisku Cloud Shell zostanie utworzony nowy folder o nazwie aidemy-bootstrap
.
W panelu Eksplorator edytora Cloud Shell (zwykle po lewej stronie) powinien teraz wyświetlać się folder utworzony podczas klonowania repozytorium Git aidemy-bootstrap
. Otwórz folder główny projektu w Eksploratorze. Znajdziesz w nim podfolder planner
. Otwórz go.
Zacznijmy tworzyć narzędzia, które nasi pracownicy będą wykorzystywać, aby być naprawdę pomocni. Jak wiesz, duże modele językowe świetnie radzą sobie z rozumowaniem i generowaniem tekstu, ale potrzebują dostępu do zasobów zewnętrznych, aby wykonywać zadania w rzeczywistych warunkach i podawać dokładne, aktualne informacje. Te narzędzia można porównać do szwajcarsko-szwajcarskiego scyzoryka, który umożliwia agentowi wchodzenie w interakcje ze światem.
Podczas tworzenia agenta łatwo jest zakodować na stałe mnóstwo szczegółów. Spowoduje to utworzenie agenta, który nie jest elastyczny. Dzięki tworzeniu i używaniu narzędzi agent ma dostęp do zewnętrznej logiki lub systemów, co daje mu korzyści zarówno z modelu LLM, jak i tradycyjnego programowania.
W tej sekcji utworzymy podstawę dla agenta planowania, którego nauczyciele będą używać do generowania planów lekcji. Zanim agent zacznie generować plan, chcemy określić granice, podając więcej szczegółów na temat tematu i tematu. Utworzymy 3 narzędzia:
- Wywołanie interfejsu REST API: interakcja z dotychczasowym interfejsem API w celu pobrania danych.
- Zapytanie do bazy danych: pobieranie ustrukturyzowanych danych z bazy danych Cloud SQL.
- Wyszukiwarka Google: uzyskiwanie informacji z internetu w czasie rzeczywistym.
Pobieranie rekomendacji książek z interfejsu API
Najpierw utwórzmy narzędzie, które pobiera rekomendacje książek z interfejsu API book-provider, który wdrożony został w poprzedniej sekcji. Pokazuje on, jak agent może korzystać z dotychczasowych usług.
W edytorze Cloud Shell otwórz projekt aidemy-bootstrap
, który został sklonowany w poprzedniej sekcji. 👉W folderze planner
otwórz plik book.py
i wklej ten kod:
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."))
Objaśnienie:
- recommend_book(query: str): ta funkcja przyjmuje zapytanie użytkownika jako dane wejściowe.
- Interakcja z LLM: wykorzystuje LLM do wyodrębnienia kategorii z zapytania. Ten film pokazuje, jak użyć dużego modelu językowego do utworzenia parametrów dla narzędzi.
- Wywołanie interfejsu API: wysyła żądanie POST do interfejsu API dostawcy książek, przekazując kategorię i pożądaną liczbę książek.
👉 Aby przetestować tę nową funkcję, ustaw zmienną środowiskową i uruchom :
cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
👉 Zainstaluj zależności i uruchom kod, aby sprawdzić, czy działa:
cd ~/aidemy-bootstrap/planner/
python -m venv env
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
pip install -r requirements.txt
python book.py
Zignoruj wyskakujące okienko z ostrzeżeniem Git.
Powinien pojawić się ciąg znaków JSON zawierający rekomendacje książek pobrane z interfejsu API dostawcy książek.
[{"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"}]
Jeśli widzisz to, pierwsze narzędzie działa prawidłowo.
Zamiast ręcznie tworzyć wywołania interfejsu API REST z określonymi parametrami, używamy języka naturalnego („Uczę się w ramach kursu…”). Następnie agent inteligentnie wyodrębnia niezbędne parametry (np. kategorię) za pomocą przetwarzania języka naturalnego, co pokazuje, jak agent korzysta z rozumienia języka naturalnego do interakcji z interfejsem API.
👉Usuń z pliku book.py
następujący kod testowy:
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."))
Pobieranie danych dotyczących programu nauczania z bazy danych
Następnie utworzymy narzędzie, które pobiera ustrukturyzowane dane z bazy danych Cloud SQL for PostgreSQL. Dzięki temu agent ma dostęp do wiarygodnego źródła informacji na potrzeby planowania lekcji.
👉 Aby utworzyć instancję Cloud SQL o nazwie aidemy, uruchom w terminalu te polecenia: Może to zająć trochę czasu.
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
👉 Następnie utwórz w nowej instancji bazę danych o nazwie aidemy-db
.
gcloud sql databases create aidemy-db \
--instance=aidemy
Sprawdźmy instancję w Cloud SQL w konsoli Google Cloud. Powinna być widoczna instancja Cloud SQL o nazwie aidemy
. Kliknij nazwę instancji, aby wyświetlić jej szczegóły. Na stronie ze szczegółami instancji Cloud SQL w menu nawigacyjnym po lewej stronie kliknij „SQL Studio”. Otworzy się nowa karta.
Kliknij, aby połączyć się z bazą danych. Zaloguj się w SQL Studio.
Jako bazę danych wybierz aidemy-db
. Wpisz postgres
jako użytkownika, a 1234qwer
jako hasło.
👉 W edytorze zapytań SQL Studio wklej ten kod SQL:
CREATE TABLE curriculums (
id SERIAL PRIMARY KEY,
year INT,
subject VARCHAR(255),
description TEXT
);
-- Inserting detailed curriculum data for different school years and subjects
INSERT INTO curriculums (year, subject, description) VALUES
-- Year 5
(5, 'Mathematics', 'Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.'),
(5, 'English', 'Developing reading comprehension, creative writing, and basic grammar, with a focus on storytelling and poetry.'),
(5, 'Science', 'Exploring basic physics, chemistry, and biology concepts, including forces, materials, and ecosystems.'),
(5, 'Computer Science', 'Basic coding concepts using block-based programming and an introduction to digital literacy.'),
-- Year 6
(6, 'Mathematics', 'Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.'),
(6, 'English', 'Introduction to persuasive writing, character analysis, and deeper comprehension of literary texts.'),
(6, 'Science', 'Forces and motion, the human body, and introductory chemical reactions with hands-on experiments.'),
(6, 'Computer Science', 'Introduction to algorithms, logical reasoning, and basic text-based programming (Python, Scratch).'),
-- Year 7
(7, 'Mathematics', 'Algebraic expressions, geometry, and introduction to statistics and probability.'),
(7, 'English', 'Analytical reading of classic and modern literature, essay writing, and advanced grammar skills.'),
(7, 'Science', 'Introduction to cells and organisms, chemical reactions, and energy transfer in physics.'),
(7, 'Computer Science', 'Building on programming skills with Python, introduction to web development, and cyber safety.');
Ten kod SQL tworzy tabelę o nazwie curriculums
i wstawia do niej przykładowe dane. Aby wykonać kod SQL, kliknij Uruchom. Powinien pojawić się komunikat z potwierdzeniem, że polecenia zostały wykonane.
👉 Rozwiń eksplorator, znajdź nowo utworzoną tabelę i kliknij zapytanie. Powinna otworzyć się nowa karta edytora z zagenerowanym dla Ciebie kodem SQL.
SELECT * FROM
"public"."curriculums" LIMIT 1000;
👉 Kliknij Uruchom.
Tabela wyników powinna zawierać wiersze danych wstawione w poprzednim kroku, co potwierdza, że tabela i dane zostały utworzone prawidłowo.
Teraz, gdy baza danych z przykładowymi danymi programu nauczania została już utworzona, opracujemy narzędzie do ich pobierania.
👉 W edytorze Cloud Code otwórz plik curriculums.py
w folderze aidemy-bootstrap
i wklej ten kod:
def connect_with_connector() -> sqlalchemy.engine.base.Engine:
db_user = os.environ["DB_USER"]
db_pass = os.environ["DB_PASS"]
db_name = os.environ["DB_NAME"]
encoded_db_user = os.environ.get("DB_USER")
print(f"--------------------------->db_user: {db_user!r}")
print(f"--------------------------->db_pass: {db_pass!r}")
print(f"--------------------------->db_name: {db_name!r}")
ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC
connector = Connector()
def getconn() -> pg8000.dbapi.Connection:
conn: pg8000.dbapi.Connection = connector.connect(
instance_connection_name,
"pg8000",
user=db_user,
password=db_pass,
db=db_name,
ip_type=ip_type,
)
return conn
pool = sqlalchemy.create_engine(
"postgresql+pg8000://",
creator=getconn,
pool_size=2,
max_overflow=2,
pool_timeout=30, # 30 seconds
pool_recycle=1800, # 30 minutes
)
return pool
def init_connection_pool() -> sqlalchemy.engine.base.Engine:
return (
connect_with_connector()
)
raise ValueError(
"Missing database connection type. Please define one of INSTANCE_HOST, INSTANCE_UNIX_SOCKET, or INSTANCE_CONNECTION_NAME"
)
def get_curriculum(year: int, subject: str):
"""
Get school curriculum
Args:
subject: User's request subject string
year: User's request year int
"""
try:
stmt = sqlalchemy.text(
"SELECT description FROM curriculums WHERE year = :year AND subject = :subject"
)
with db.connect() as conn:
result = conn.execute(stmt, parameters={"year": year, "subject": subject})
row = result.fetchone()
if row:
return row[0]
else:
return None
except Exception as e:
print(e)
return None
db = init_connection_pool()
Objaśnienie:
- Zmienne środowiskowe: kod pobiera dane logowania do bazy danych i informacje o połączeniu ze zmiennych środowiskowych (więcej informacji znajdziesz poniżej).
- connect_with_connector(): ta funkcja używa narzędzia Cloud SQL Connector do nawiązywania bezpiecznego połączenia z bazą danych.
- get_curriculum(year: int, subject: str): ta funkcja przyjmuje jako dane wejściowe rok i przedmiot, wysyła zapytanie do tabeli curriculums i zwraca odpowiedni opis programu nauczania.
👉 Zanim uruchomisz kod, musisz ustawić w terminalu kilka zmiennych środowiskowych. W tym celu uruchom:
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"
👉 Aby przetestować, dodaj ten kod na końcu pliku curriculums.py
:
if __name__ == "__main__":
print(get_curriculum(6, "Mathematics"))
👉Uruchom kod:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py
W konsoli powinien zostać wydrukowany opis programu nauczania matematyki dla 6 klasy.
Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.
Jeśli widzisz opis programu nauczania, oznacza to, że narzędzie do obsługi bazy danych działa prawidłowo. Aby zatrzymać skrypt, naciśnij Ctrl+C
.
👉Usuń z pliku curriculums.py
następujący kod testowy:
if __name__ == "__main__":
print(get_curriculum(6, "Mathematics"))
👉Aby zamknąć środowisko wirtualne, w terminalu uruchom:
deactivate
6. Narzędzia budowlane: dostęp do informacji z internetu w czasie rzeczywistym
Na koniec opracujemy narzędzie, które korzysta z integracji Gemini 2 z wyszukiwarką Google, aby uzyskiwać informacje z internetu w czasie rzeczywistym. Pomoże to pracownikowi obsługi klienta być na bieżąco i uzyskać trafne wyniki.
Integracja Gemini 2 z interfejsem API wyszukiwarki Google zwiększa możliwości agentów, zapewniając dokładniejsze i bardziej trafne wyniki wyszukiwania w kontekście. Dzięki temu przedstawiciele firmy mają dostęp do aktualnych informacji i mogą opierać swoje odpowiedzi na danych pochodzących z rzeczywistego świata, co minimalizuje ilość halucynacji. Ulepszona integracja interfejsu API ułatwia też tworzenie zapytań w języku naturalnym, umożliwiając pracownikom obsługi klienta formułowanie złożonych i szczegółowych zapytań.
Ta funkcja przyjmuje jako dane wejściowe zapytanie, program nauczania, przedmiot i rok, a następnie korzysta z interfejsu Gemini API i narzędzia wyszukiwarki Google, aby pobrać odpowiednie informacje z internetu. Jeśli przyjrzysz się dokładniej, zobaczysz, że do wywoływania funkcji używany jest pakiet SDK generatywnej AI od Google, a nie żadna inna platforma.
👉 W folderze aidemy-bootstrap
otwórz plik search.py
i wklej ten kod:
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)
Objaśnienie:
- Defining Tool - google_search_tool: owinięcie obiektu GoogleSearch w narzędziu
- search_latest_resource(search_text: str, subject: str, year: int): ta funkcja przyjmuje jako dane wejściowe zapytanie, temat i rok, a następnie używa interfejsu Gemini API do przeprowadzenia wyszukiwania w Google. Model Gemini
- GenerateContentConfig: określ, że ma dostęp do narzędzia Google Search.
Model Gemini analizuje wewnętrznie parametr search_text i określa, czy może bezpośrednio odpowiedzieć na pytanie, czy musi użyć narzędzia GoogleSearch. Jest to kluczowy krok w procesie wnioskowania LLM. Model został wytrenowany tak, aby rozpoznawał sytuacje, w których potrzebne są narzędzia zewnętrzne. Jeśli model zdecyduje się użyć narzędzia GoogleSearch, pakiet SDK Google Generative AI będzie odpowiedzialny za jego wywołanie. Pakiet SDK pobiera decyzję modelu i generowane przez niego parametry, a następnie wysyła je do interfejsu Google Search API. Ta część jest ukryta przed użytkownikiem w kodzie.
Model Gemini integruje wyniki wyszukiwania w swojej odpowiedzi. Może ona używać tych informacji do udzielania odpowiedzi na pytania użytkowników, generowania podsumowań lub wykonywania innych zadań.
👉 Aby przetestować kod, uruchom go:
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py
Powinieneś zobaczyć odpowiedź interfejsu Gemini Search API z wynikami wyszukiwania powiązanymi z „Syllabus for Year 5 Mathematics”. Dokładny wynik zależy od wyników wyszukiwania, ale będzie to obiekt JSON z informacjami o wyszukiwaniu.
Jeśli widzisz wyniki wyszukiwania, narzędzie wyszukiwarki Google działa prawidłowo. Aby zatrzymać skrypt, naciśnij Ctrl+C
.
👉 Usuń ostatnią część kodu.
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)
👉Aby zamknąć środowisko wirtualne, w terminalu uruchom:
deactivate
Gratulacje! Twój agent planowania ma teraz do dyspozycji 3 skuteczne narzędzia: łącznik interfejsu API, łącznik bazy danych i narzędzie do wyszukiwania w Google. Te narzędzia umożliwią agentowi dostęp do informacji i funkcji potrzebnych do tworzenia skutecznych planów nauczania.
7. Automatyzacja za pomocą LangGraph
Po utworzeniu poszczególnych narzędzi nadszedł czas na ich skoordynowanie za pomocą LangGraph. Umożliwi nam to stworzenie bardziej zaawansowanego agenta „planisty”, który na podstawie żądania użytkownika będzie mógł inteligentnie decydować, których narzędzi użyć i kiedy.
LangGraph to biblioteka Pythona, która ułatwia tworzenie aplikacji wieloczynników z stanem za pomocą dużych modeli językowych (LLM). To rodzaj ram umożliwiających prowadzenie złożonych rozmów i przepływów pracy z użyciem LLM, narzędzi i innych agentów.
Kluczowe pojęcia:
- Struktura grafu: LangGraph reprezentuje logikę aplikacji jako graf skierowany. Każdy węzeł na wykresie reprezentuje krok procesu (np. wywołanie LLM, wywołanie narzędzia, sprawdzenie warunkowe). Krawędzie określają przepływ wykonania między węzłami.
- Stan: LangGraph zarządza stanem aplikacji w miarę jej przemieszczania się po wykresie. Ten stan może obejmować zmienne takie jak dane wejściowe użytkownika, wyniki wywołań narzędzi, pośrednie dane wyjściowe z LLM oraz inne informacje, które muszą być zachowane między krokami.
- Węzły: każdy węzeł reprezentuje obliczenie lub interakcję. Mogą to być:
- Kody narzędzia: korzystanie z narzędzia (np.wyszukiwanie w internecie, wysyłanie zapytania do bazy danych).
- Węzły funkcji: wykonują funkcję Pythona.
- Krawędzie: łączą węzły, definiując przepływ wykonania. Mogą to być:
- Krawędzie bezpośrednie: prosty, bezwarunkowy przepływ z jednego węzła do drugiego.
- Krawędzie warunkowe: przepływ zależy od wyniku węzła warunkowego.
Do implementacji orkiestracji użyjemy LangGraph. Zmieńmy plik aidemy.py
w folderze aidemy-bootstrap
, aby zdefiniować logikę LangGraph. 👉 Dodaj kod do pliku 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"])}
Ta funkcja odpowiada za pobranie bieżącego stanu rozmowy, przekazanie LLM wiadomości systemowej, a następnie poproszenie LLM o wygenerowanie odpowiedzi. Model LLM może odpowiedzieć bezpośrednio użytkownikowi lub użyć jednego z dostępnych narzędzi.
Narzędzia : ta lista zawiera zestaw narzędzi dostępnych dla pracownika obsługi klienta. Zawiera 3 funkcje narzędzia zdefiniowane w poprzednich krokach: get_curriculum
, search_latest_resource
i recommend_book
. llm.bind_tools(tools): łączy listę narzędzi z obiektem llm. Powiązanie narzędzi informuje LLM, że są one dostępne, oraz przekazuje informacje o sposobie ich używania (np. nazwy narzędzi, parametry, które akceptują, i ich działanie).
Do implementacji orkiestracji użyjemy LangGraph. 👉 Dodaj ten kod na końcu pliku 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")
Objaśnienie:
StateGraph(MessagesState)
: tworzy obiektStateGraph
.StateGraph
to podstawowe pojęcie w LangGraph. Reprezentuje przepływ pracy agenta w postaci wykresu, na którym każdy węzeł odpowiada jednemu krokowi procesu. Można je traktować jako plan tego, jak agent ma myśleć i działać.- Krawędzia warunkowa: pochodzący z węzła
"determine_tool"
argumenttools_condition
jest prawdopodobnie funkcją, która określa, którą krawędzią ma być podążać na podstawie danych wyjściowych funkcjidetermine_tool
. Krańce krawędzie umożliwiają rozgałęzienie grafu na podstawie decyzji LLM dotyczącej tego, którego narzędzia użyć (lub czy odpowiedzieć bezpośrednio użytkownikowi). Właśnie w tym momencie pojawia się „inteligencja” agenta – może on dynamicznie dostosowywać swoje zachowanie do sytuacji. - Pętla:dodaje do grafu krawędzię, która łączy węzeł
"tools"
z węzłem"determine_tool"
. Tworzy to pętlę na diagramie, która pozwala czatowemu agentowi wielokrotnie używać narzędzi, dopóki nie zbierze wystarczającej ilości informacji, aby wykonać zadanie i podać zadowalającą odpowiedź. Ten cykl jest kluczowy w przypadku złożonych zadań, które wymagają wielu kroków rozumowania i gromadzenia informacji.
Teraz przetestujmy agenta planowania, aby sprawdzić, jak koordynuje on różne narzędzia.
Ten kod uruchamia funkcję prep_class z określonym wejściem użytkownika, symulując prośbę o stworzenie planu nauczania geometrii dla piątej klasy, korzystając z programu nauczania, rekomendacji książek i najnowszych materiałów internetowych.
Jeśli terminal został zamknięty lub zmienne środowiskowe nie są już ustawione, ponownie uruchom te polecenia.
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"
👉Uruchom kod:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py
Sprawdź dziennik w terminalu. Powinieneś/powinnaś zobaczyć dowody na to, że agent korzysta ze wszystkich 3 narzędzi (pobieranie szkolnego programu nauczania, pobieranie rekomendacji książek i wyszukiwanie najnowszych zasobów) przed przekazaniem ostatecznego planu nauczania. Pokazuje to, że orkiestrowanie LangGraph działa prawidłowo i agent inteligentnie korzysta ze wszystkich dostępnych narzędzi, aby spełnić prośbę użytkownika.
================================ 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**
.........
Aby zatrzymać skrypt, naciśnij Ctrl+C
.
👉Teraz zastąp kod testowy innym promptem, który wymaga wywołania innych narzędzi.
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")
Jeśli terminal został zamknięty lub zmienne środowiskowe nie są już ustawione, ponownie uruchom te polecenia.
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"
👉 Uruchom kod ponownie:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py
Co tym razem zauważyłeś/zauważyłaś? Do których narzędzi się zwrócił? Tym razem agent powinien wywołać tylko narzędzie search_latest_resource. Dzieje się tak, ponieważ prompt nie określa, że potrzebuje 2 innych narzędzi, a nasze LLM jest na tyle inteligentne, że nie wywołuje tych narzędzi.
================================ 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.
Aby zatrzymać skrypt, naciśnij Ctrl+C
. 👉Usuń kod testowy, aby utrzymać plik aidemy.py w czystości:
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")
Po zdefiniowaniu logiki pracownika obsługi klienta uruchom aplikację internetową Flask. Dzięki temu nauczyciele będą mogli korzystać z znanego interfejsu opartego na formularzach, aby kontaktować się z pracownikiem obsługi klienta. Chociaż interakcje z botami są częste w przypadku modeli LLM, wolimy interfejs tradycyjnego formularza przesyłania danych, ponieważ może on być bardziej intuicyjny dla wielu nauczycieli.
Jeśli terminal został zamknięty lub zmienne środowiskowe nie są już ustawione, ponownie uruchom te polecenia.
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"
👉 Uruchom interfejs internetowy.
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py
W danych wyjściowych terminala Cloud Shell szukaj komunikatów o uruchomieniu. Flask zwykle wyświetla komunikaty informujące o tym, że działa i na którym porcie.
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.
👉 W menu „Podgląd w przeglądarce” wybierz Podgląd na porcie 8080. Cloud Shell otworzy nową kartę lub nowe okno przeglądarki z podglądem aplikacji w przeglądarce.
W interfejsie aplikacji wybierz 5
dla roku, wybierz temat Mathematics
i wpisz Geometry
w prośbie o dodatek .
Zamiast bezczynnie czekać na odpowiedź, przełącz się na terminal edytora Cloud. W terminalu emulatora możesz obserwować postęp i komunikaty o błędach generowane przez funkcję. 😁
👉 Zatrzymaj skrypt, naciskając Ctrl+C
w terminalu.
👉Zamknij środowisko wirtualne:
deactivate
8. Wdrażanie agenta planera w chmurze
Tworzenie i przesyłanie obrazu do rejestru
👉 Czas wdrożyć to w chmurze. W terminalu utwórz repozytorium artefaktów, w którym będziesz przechowywać obraz Dockera, który zamierzasz skompilować.
gcloud artifacts repositories create agent-repository \
--repository-format=docker \
--location=us-central1 \
--description="My agent repository"
Powinien wyświetlić się komunikat „Utworzono repozytorium [agent-repository]”.
👉 Aby utworzyć obraz Dockera, uruchom to polecenie.
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
👉 Musimy ponownie oznaczyć obraz, aby był hostowany w Artifact Registry zamiast w GCR, i przesłać go do Artifact Registry:
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
Po zakończeniu przesyłania możesz sprawdzić, czy obraz został zapisany w Artifact Registry. W konsoli Google Cloud otwórz Artifact Registry. Obraz aidemy-planner
powinien znajdować się w repozytorium agent-repository
.
Zabezpieczanie danych logowania do bazy danych za pomocą usługi Secret Manager
Aby bezpiecznie zarządzać danymi logowania do bazy danych i dostępem do nich, użyjemy usługi Google Cloud Secret Manager. Zapobiega to umieszczaniu w kodzie aplikacji zaszyfrowanych informacji poufnych i zwiększa bezpieczeństwo.
👉 Utworzymy osobne sekrety dla nazwy użytkownika, hasła i nazwy bazy danych. Dzięki temu możemy zarządzać poszczególnymi danymi logowania niezależnie od siebie. W terminalu uruchom:
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=-
Korzystanie z usługi Secret Manager jest ważnym krokiem w zabezpieczeniu aplikacji i zapobieganiu przypadkowemu ujawnieniu poufnych danych logowania. Jest on zgodny ze sprawdzonymi metodami dotyczącymi bezpieczeństwa wdrożeń w chmurze.
Wdrożenie w Cloud Run
Cloud Run to w pełni zarządzana platforma bezserwerowa, która umożliwia szybkie i łatwe wdrażanie aplikacji konteneryzowanych. Usługa nie wymaga zarządzania infrastrukturą, dzięki czemu możesz skupić się na tworzeniu i wdrażaniu kodu. Nasz planer zostanie wdrożony jako usługa Cloud Run.
👉 W konsoli Google Cloud otwórz „Cloud Run”. Kliknij ROZMIESZCZ KONTENER i wybierz USŁUGA. Skonfiguruj usługę Cloud Run:
- Obraz kontenera: w polu adresu URL kliknij „Wybierz”. Znajdź adres URL obrazu przesłanego do Artifact Registry (np. us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
- Nazwa usługi:
aidemy-planner
- Region: wybierz region
us-central1
. - Uwierzytelnianie: na potrzeby tego warsztatu możesz zezwolić na „Zezwalaj na nieuwierzytelnione wywołania”. W przypadku wersji produkcyjnej prawdopodobnie ograniczysz dostęp.
- Karta Kontenery (rozwiń sekcję Kontenery, Sieć):
- Karta Ustawienia:
- Zasób
- pamięć : 2 GB
- Zasób
- Karta Zmienne i obiekty tajne:
- Zmienne środowiskowe:
- Dodaj nazwę:
GOOGLE_CLOUD_PROJECT
i wartość: <YOUR_PROJECT_ID> - Dodaj nazwę:
BOOK_PROVIDER_URL
i wartość: <YOUR_BOOK_PROVIDER_FUNCTION_URL>
- Dodaj nazwę:
- Obiekty tajne ujawnione jako zmienne środowiskowe:
- Dodaj nazwę:
DB_USER
, obiekt tajny: wybierzdb-user
i wersja:latest
- Dodaj nazwę:
DB_PASS
, obiekt tajny: wybierzdb-pass
i wersja:latest
- Dodaj nazwę:
DB_NAME
, obiekt tajny: wybierzdb-name
i wersja:latest
- Dodaj nazwę:
- Zmienne środowiskowe:
- Karta Ustawienia:
Jeśli chcesz pobrać adres URL funkcji dostawcy KSIĄŻKA:, uruchom to polecenie w terminalu:
gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)"
Pozostaw inne ustawienia jako domyślne.
👉 Kliknij UTWÓRZ.
Cloud Run wdroży Twoją usługę.
Po wdrożeniu usługi kliknij ją, aby otworzyć stronę z jej szczegółami. U góry znajdziesz adres URL wdrożonej usługi.
W interfejsie aplikacji wybierz 7
jako rok, Mathematics
jako temat i Algebra
jako typ prośby o wtyczkę. Dzięki temu agent otrzyma niezbędny kontekst, aby wygenerować spersonalizowany plan lekcji.
Gratulacje! Udało Ci się utworzyć plan nauczania za pomocą naszego zaawansowanego agenta AI. Pokazuje to, jak pracownicy obsługi klienta mogą znacznie zmniejszyć obciążenie pracą i usprawnić wykonywanie zadań, co w efekcie zwiększa wydajność i ułatwia życie nauczycielom.
9. Systemy wieloagentowe
Teraz, gdy udało nam się wdrożyć narzędzie do tworzenia planów nauczania, skupmy się na tworzeniu portalu dla uczniów. Na tym portalu uczniowie będą mieli dostęp do testów, podsumowań audio i projektów związanych z ich pracą. Ze względu na zakres tej funkcji wykorzystamy możliwości systemów wieloagentowych, aby stworzyć modułowe i skalowalne rozwiązanie.
Jak już wspomnieliśmy, zamiast polegać na jednym agencie, który zajmowałby się wszystkim, system z wieloma agentami pozwala nam podzielić obciążenie na mniejsze, wyspecjalizowane zadania, z których każde jest wykonywane przez dedykowanego agenta. Takie podejście ma kilka kluczowych zalet:
Modułowość i łatwość konserwacji: zamiast tworzyć jednego agenta, który robi wszystko, twórz mniejsze, wyspecjalizowane agenty z dobrze zdefiniowanymi zadaniami. Dzięki modularności system jest łatwiejszy do zrozumienia, utrzymania i debugowania. Gdy pojawi się problem, możesz go przypisać do konkretnego pracownika zamiast przeszukiwać całej bazy kodu.
Skuteczność: zwiększanie skali działania pojedynczego, złożonego agenta może być wąskim gardłem. Dzięki systemowi z wieloma agentami możesz skalować poszczególnych agentów na podstawie ich konkretnych potrzeb. Jeśli na przykład jeden agent obsługuje dużą liczbę żądań, możesz łatwo uruchomić więcej jego instancji bez wpływu na pozostałą część systemu.
Specjalizacje zespołów: zastanów się, czy prosisz inżyniera o stworzenie całej aplikacji od podstaw. Zamiast tego tworzysz zespół specjalistów, z których każdy ma wiedzę specjalistyczną w konkretnej dziedzinie. Podobnie system wieloagentowy pozwala wykorzystać mocne strony różnych LLM i narzędzi, przypisując je do agentów, którzy najlepiej nadają się do określonych zadań.
Równoległy rozwój: różne zespoły mogą pracować nad różnymi agentami jednocześnie, co przyspiesza proces rozwoju. Ponieważ agenci są niezależni, zmiany w jednym z nich rzadko wpływają na innych.
Architektura oparta na zdarzeniach
Aby umożliwić skuteczną komunikację i koordynację między tymi agentami, zastosujemy architekturę opartą na zdarzeniach. Oznacza to, że pracownicy obsługi klienta będą reagować na „zdarzenia” występujące w systemie.
Pracownicy obsługi klienta mogą subskrybować określone typy zdarzeń (np. „plan nauczania wygenerowany”, „utworzone zadanie”). Gdy wystąpi zdarzenie, odpowiednie osoby zostaną powiadomione i będą mogły odpowiednio zareagować. Takie rozdzielenie sprzyja elastyczności, skalowalności i szybkiej reakcji w czasie rzeczywistym.
Na początek potrzebujemy sposobu na transmitowanie tych zdarzeń. W tym celu skonfigurujemy temat Pub/Sub. Zacznijmy od utworzenia tematu o nazwie plan.
👉Otwórz Pub/Sub w konsoli Google Cloud i kliknij przycisk „Utwórz temat”.
👉Skonfiguruj temat o identyfikatorze/nazwie plan
, odznacz pole Add a default subscription
, pozostaw pozostałe ustawienia domyślne i kliknij Utwórz.
Strona Pub/Sub zostanie odświeżona i powinieneś zobaczyć nowo utworzony temat w tabeli.
Teraz zintegrujemy funkcję publikowania zdarzeń Pub/Sub z naszym agentem planowania. Dodamy nowe narzędzie, które wysyła zdarzenie „plan” do utworzonego przez nas tematu Pub/Sub. To zdarzenie będzie sygnałem dla innych agentów w systemie (np. na portalu dla studentów), że jest dostępny nowy plan nauczania.
👉 Wróć do edytora kodu Cloud i otwórz plik app.py
znajdujący się w folderze planner
. Dodamy funkcję, która publikuje wydarzenie. Zastąp:
##ADD SEND PLAN EVENT FUNCTION HERE
z
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: ta funkcja przyjmuje wygenerowany plan lekcji jako dane wejściowe, tworzy klienta publikującego Pub/Sub, tworzy ścieżkę tematu, konwertuje plan lekcji na ciąg znaków JSON i publikuje wiadomość w temacie.
- Lista narzędzi: funkcja
send_plan_event
jest dodawana do listy narzędzi, dzięki czemu jest dostępna dla agenta.
W tym samym folderze w pliku app.py
👉Zaktualizuj prompt, aby polecić agentowi wysyłanie zdarzenia dotyczącego planu nauczania do tematu Pub/Sub po wygenerowaniu planu nauczania. Zamień
### ADD send_plan_event CALL
z tymi funkcjami:
send_plan_event(teaching_plan)
Dzięki dodaniu narzędzia send_plan_event i zmodyfikowaniu prompta nasz agent planowania może publikować zdarzenia w Pub/Sub, co pozwala innym komponentom naszego systemu reagować na tworzenie nowych planów nauczania. W następnych sekcjach będziemy mieć działający system wieloagentowy.
10. Wspieranie uczniów dzięki testom na żądanie
Wyobraź sobie środowisko edukacyjne, w którym uczniowie mają dostęp do nieskończonej liczby testów dostosowanych do ich konkretnych planów nauki. Te testy zapewniają natychmiastową informację zwrotną, w tym odpowiedzi i wyjaśnienia, co ułatwia lepsze zrozumienie materiału. To właśnie chcemy osiągnąć dzięki naszemu portalowi z quizami wykorzystującemu AI.
Aby to zrealizować, utworzymy komponent generowania quizów, który będzie mógł tworzyć pytania jednokrotnego wyboru na podstawie treści planu nauczania.
👉 W panelu Eksplorator edytora Cloud Code przejdź do folderu portal
. Otwórz plik quiz.py
, skopiuj i wklej podany niżej kod na końcu pliku.
def generate_quiz_question(file_name: str, difficulty: str, region:str ):
"""Generates a single multiple-choice quiz question using the LLM.
```json
{
"question": "The question itself",
"options": ["Option A", "Option B", "Option C", "Option D"],
"answer": "The correct answer letter (A, B, C, or D)"
}
```
"""
print(f"region: {region}")
# Connect to resourse needed from Google Cloud
llm = VertexAI(model_name="gemini-1.5-pro", location=region)
plan=None
#load the file using file_name and read content into string call plan
with open(file_name, 'r') as f:
plan = f.read()
parser = JsonOutputParser(pydantic_object=QuizQuestion)
instruction = f"You'll provide one question with difficulty level of {difficulty}, 4 options as multiple choices and provide the anwsers, the quiz needs to be related to the teaching plan {plan}"
prompt = PromptTemplate(
template="Generates a single multiple-choice quiz question\n {format_instructions}\n {instruction}\n",
input_variables=["instruction"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
chain = prompt | llm | parser
response = chain.invoke({"instruction": instruction})
print(f"{response}")
return response
W ramach agenta tworzy parsownik danych wyjściowych w formacie JSON, który jest specjalnie zaprojektowany do interpretowania i strukturyzowania danych wyjściowych LLM. Używa ona zdefiniowanego wcześniej modelu QuizQuestion
, aby zapewnić, że zanalizowane dane wyjściowe będą zgodne z prawidłowym formatem (pytanie, opcje i odpowiedź).
👉 Aby skonfigurować środowisko wirtualne, zainstalować zależności i uruchomić agenta, wykonaj w terminalu te polecenia:
cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py
Aby uzyskać dostęp do uruchomionej aplikacji, użyj funkcji podglądu w przeglądarce w Cloud Shell. Kliknij link „Quizzes” (Quizy) na górnym pasku nawigacyjnym lub na karcie na stronie indeksu. Powinny być widoczne 3 losowo wygenerowane testy dla ucznia. Te testy są oparte na planie nauczania i pozwalają poznać możliwości naszego systemu do generowania testów opartego na AI.
Aby zatrzymać proces działający lokalnie, naciśnij Ctrl+C
w terminalu.
Gemini 2 Thinking do wyjaśnień
Mamy quizy, więc to świetny początek. Co jednak, jeśli uczniowie popełnią błąd? To właśnie tam dzieje się prawdziwa nauka, prawda? Jeśli możemy wyjaśnić, dlaczego odpowiedź była błędna i jak znaleźć prawidłową, użytkownik ma większe szanse na zapamiętanie prawidłowej odpowiedzi. Pozwala też rozwiać wszelkie wątpliwości i zwiększać zaufanie.
Dlatego użyjemy ciężkiej artylerii: modelu „myślenia” Gemini 2. Wyobraź sobie, że dajesz AI trochę więcej czasu na przemyślenie odpowiedzi, zanim ją poda. Dzięki temu możesz uzyskać bardziej szczegółowe i przydatne opinie.
Chcemy sprawdzić, czy może ona pomóc uczniom, udzielając im pomocy, odpowiadając na pytania i wyjaśniając szczegółowo. Aby to sprawdzić, zaczniemy od skomplikowanego tematu, czyli rachunku różniczkowego.
👉 Najpierw otwórz edytor Cloud Code i w folderze answer.py
portal
zastąp
def answer_thinking(question, options, user_response, answer, region):
return ""
z tym fragmentem kodu:
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)
To bardzo prosta aplikacja langchain, która inicjuje model Gemini 2 Flash, gdzie instruujemy ją, aby działała jak pomocny nauczyciel i podawała wyjaśnienia.
👉 Wykonaj w terminalu to polecenie:
cd ~/aidemy-bootstrap/portal/
python answer.py
Powinny wyświetlić się dane wyjściowe podobne do przykładu podanego w pierwotnych instrukcjach. Obecny model może nie zapewnić wyczerpującego wyjaśnienia.
Okay, I see the question and the choices. The question is to evaluate the limit:
lim (x→0) [(sin(5x) - 5x) / x^3]
You chose option B, which is -5/3, but the correct answer is A, which is -125/6.
It looks like you might have missed a step or made a small error in your calculations. This type of limit often involves using L'Hôpital's Rule or Taylor series expansion. Since we have the form 0/0, L'Hôpital's Rule is a good way to go! You need to apply it multiple times. Alternatively, you can use the Taylor series expansion of sin(x) which is:
sin(x) = x - x^3/3! + x^5/5! - ...
So, sin(5x) = 5x - (5x)^3/3! + (5x)^5/5! - ...
Then, (sin(5x) - 5x) = - (5x)^3/3! + (5x)^5/5! - ...
Finally, (sin(5x) - 5x) / x^3 = - 5^3/3! + (5^5 * x^2)/5! - ...
Taking the limit as x approaches 0, we get -125/6.
Keep practicing, you'll get there!
W pliku answer.py w funkcji answer_thinking zastąp wartość parametru model_name z gemini-2.0-flash-001
na gemini-2.0-flash-thinking-exp-01-21
.
Zmiana ta powoduje, że LLM bardziej sięga po wnioskowanie, co pomoże mu generować lepsze wyjaśnienia. I znów uruchom.
👉 Uruchom model, aby przetestować nowy model myślenia:
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py
Oto przykład odpowiedzi z modelu myślenia, która jest znacznie bardziej szczegółowa i dokładna. Zawiera ona szczegółowe wyjaśnienie rozwiązania zadania z rachunku różniczkowego. Pokazuje to, jak potężne są „myślące” modele w generowaniu wysokiej jakości wyjaśnień. Powinny pojawić się dane wyjściowe podobne do tych:
Hey there! Let's take a look at this limit problem together. You were asked to evaluate:
lim (x→0) [(sin(5x) - 5x) / x^3]
and you picked option B, -5/3, but the correct answer is actually A, -125/6. Let's figure out why!
It's a tricky one because if we directly substitute x=0, we get (sin(0) - 0) / 0^3 = (0 - 0) / 0 = 0/0, which is an indeterminate form. This tells us we need to use a more advanced technique like L'Hopital's Rule or Taylor series expansion.
Let's use the Taylor series expansion for sin(y) around y=0. Do you remember it? It looks like this:
sin(y) = y - y^3/3! + y^5/5! - ...
where 3! (3 factorial) is 3 × 2 × 1 = 6, 5! is 5 × 4 × 3 × 2 × 1 = 120, and so on.
In our problem, we have sin(5x), so we can substitute y = 5x into the Taylor series:
sin(5x) = (5x) - (5x)^3/3! + (5x)^5/5! - ...
sin(5x) = 5x - (125x^3)/6 + (3125x^5)/120 - ...
Now let's plug this back into our limit expression:
[(sin(5x) - 5x) / x^3] = [ (5x - (125x^3)/6 + (3125x^5)/120 - ...) - 5x ] / x^3
Notice that the '5x' and '-5x' cancel out! So we are left with:
= [ - (125x^3)/6 + (3125x^5)/120 - ... ] / x^3
Now, we can divide every term in the numerator by x^3:
= -125/6 + (3125x^2)/120 - ...
Finally, let's take the limit as x approaches 0. As x gets closer and closer to zero, terms with x^2 and higher powers will become very, very small and approach zero. So, we are left with:
lim (x→0) [ -125/6 + (3125x^2)/120 - ... ] = -125/6
Therefore, the correct answer is indeed **A) -125/6**.
It seems like your answer B, -5/3, might have come from perhaps missing a factor somewhere during calculation or maybe using an incorrect simplification. Double-check your steps when you were trying to solve it!
Don't worry, these limit problems can be a bit tricky sometimes! Keep practicing and you'll get the hang of it. Let me know if you want to go through another similar example or if you have any more questions! 😊
Now that we have confirmed it works, let's use the portal.
👉USUŃ z answer.py
ten kod testowy:
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)
👉 Aby skonfigurować środowisko wirtualne, zainstalować zależności i uruchomić agenta, wykonaj w terminalu te polecenia:
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py
👉 Aby uzyskać dostęp do uruchomionej aplikacji, użyj funkcji podglądu w przeglądarce w Cloud Shell. Kliknij link „Testy”, odpowiedz na wszystkie pytania i upewnij się, że przynajmniej jedna odpowiedź jest błędna. Następnie kliknij Prześlij.
Zamiast bezczynnie czekać na odpowiedź, przełącz się na terminal edytora Cloud. W terminalu emulatora możesz obserwować postęp i komunikaty o błędach generowane przez funkcję. 😁
Aby zatrzymać proces działający lokalnie, naciśnij Ctrl+C
w terminalu.
11. Koordynowanie działań agentów za pomocą Eventarc
Do tej pory portal dla studentów generował testy na podstawie domyślnego zestawu planów nauczania. Jest to przydatne, ale oznacza, że nasz agent planowania i agent testów w portalu nie są ze sobą w pełni zintegrowane. Pamiętacie, jak dodaliśmy funkcję, w ramach której agent planowania publikuje nowo wygenerowane plany nauczania w temacie Pub/Sub? Teraz czas połączyć to z naszym pracownikiem obsługi klienta na portalu.
Chcemy, aby portal automatycznie aktualizował treści quizów po wygenerowaniu nowego planu nauczania. W tym celu utworzymy w portalu punkt końcowy, który będzie mógł odbierać te nowe plany.
👉 W panelu Eksplorator edytora Cloud Code przejdź do folderu portal
. Otwórz plik app.py
do edycji. Dodaj ten kod między znacznikami ## Add your code here (Dodaj kod tutaj):
## Add your code here
@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
try:
# Get data from Pub/Sub message delivered via Eventarc
envelope = request.get_json()
if not envelope:
return jsonify({'error': 'No Pub/Sub message received'}), 400
if not isinstance(envelope, dict) or 'message' not in envelope:
return jsonify({'error': 'Invalid Pub/Sub message format'}), 400
pubsub_message = envelope['message']
print(f"data: {pubsub_message['data']}")
data = pubsub_message['data']
data_str = base64.b64decode(data).decode('utf-8')
data = json.loads(data_str)
teaching_plan = data['teaching_plan']
print(f"File content: {teaching_plan}")
with open("teaching_plan.txt", "w") as f:
f.write(teaching_plan)
print(f"Teaching plan saved to local file: teaching_plan.txt")
return jsonify({'message': 'File processed successfully'})
except Exception as e:
print(f"Error processing file: {e}")
return jsonify({'error': 'Error processing file'}), 500
## Add your code here
Ponowne kompilowanie i wdrażanie w Cloud Run
Musisz zaktualizować i ponownie wdrożyć agentów planera i portalu w Cloud Run. Dzięki temu mają one najnowszy kod i są skonfigurowane do komunikacji za pomocą zdarzeń.
👉 Następnie ponownie utwórz i prześlij obraz plannera, uruchamiając ponownie terminal:
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
👉 Zrobimy to samo, czyli utworzymy i prześlemy obraz agenta portalu:
cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
W Artifact Registry powinny być widoczne obrazy kontenera aidemy-planner
i aidemy-portal
.
👉 Aby zaktualizować obraz Cloud Run dla agenta planowania, w terminalu uruchom to polecenie:
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
Powinny pojawić się dane wyjściowe podobne do tych:
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
Zapisz adres URL usługi, czyli link do zaimplementowanego planera.
👉 Uruchom ten skrypt, aby utworzyć instancję Cloud Run dla agenta portal
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}
Powinny pojawić się dane wyjściowe podobne do tych:
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
Zanotuj adres URL usługi, czyli link do wdrożonego portalu dla uczniów.
Tworzenie aktywatora Eventarc
Ale pojawia się ważne pytanie: jak ten punkt końcowy zostanie powiadomiony, gdy w temacie Pub/Sub będzie czekać nowy plan? Właśnie wtedy z pomocą przychodzi Eventarc.
Eventarc działa jako łącznik, który wykrywa określone zdarzenia (np. nową wiadomość docierającą do tematu Pub/Sub) i automatycznie uruchamia działania w odpowiedzi. W naszym przypadku wykryje ono publikację nowego planu nauczania, a następnie wyśle sygnał do punktu końcowego naszego portalu, informując go, że nadszedł czas na aktualizację.
Dzięki temu, że Eventarc obsługuje komunikację sterowaną zdarzeniami, możemy bezproblemowo połączyć agenta planowania z agentem portalu, tworząc naprawdę dynamiczny i responsywny system edukacyjny. To jak mieć inteligentnego komunikatora, który automatycznie dostarcza najnowsze plany lekcji we właściwe miejsce.
👉 W konsoli otwórz Eventarc.
👉 Kliknij przycisk „+ UTWÓRZ AKTYWATOR”.
Konfigurowanie reguły (podstawy):
- Nazwa reguły:
plan-topic-trigger
- Typy reguł: źródła Google.
- Źródło zdarzenia: Cloud Pub/Sub
- Typ zdarzenia:
google.cloud.pubsub.topic.v1.messagePublished
- Region:
us-central1
. - Temat Cloud Pub/Sub : wybierz
plan
- Przypisz do konta usługi rolę
roles/iam.serviceAccountTokenCreator
. - Miejsce docelowe zdarzenia: Cloud Run
- Usługa Cloud Run: aidemy-portal
- Ścieżka URL usługi:
/new_teaching_plan
- Ignoruj wiadomość (odmowa uprawnień do „locations/me-central2” (może ono nie istnieć)).
Kliknij „Utwórz”.
Strona Aktywatory Eventarc zostanie odświeżona i powinieneś zobaczyć nowo utworzony w niej element w tabeli.
👉 Teraz otwórz Planer i poproś o nowy plan nauczania. Tym razem spróbuj użyć roku 5
, tematu science
i dopisku Add-no request atoms
Jeśli zapomnisz lokalizację planera, uruchom to w terminalu.
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Następnie zaczekaj minutę lub dwie. To opóźnienie zostało wprowadzone ze względu na ograniczenia związane z płatnościami w ramach tego laboratorium. W normalnych warunkach nie powinno być opóźnienia.
Na koniec otwórz portal dla uczniów. Powinny być widoczne zaktualizowane quizy, które są zgodne z nowym planem nauczania, który właśnie wygenerowałeś. To pokazuje, że integracja Eventarc z systemem Aidemy przebiegła pomyślnie.
Uruchom to w terminalu, jeśli zapomnisz lokalizację swojego agenta portalu
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
Gratulacje! Udało Ci się stworzyć w Google Cloud system wieloagentowy, który wykorzystuje architekturę opartą na zdarzeniach, aby zapewnić większą skalowalność i elastyczność. Masz już solidne podstawy, ale możesz jeszcze więcej odkryć. Aby dowiedzieć się więcej o korzyściach płynących z tej architektury, poznać potencjał interfejsu Gemini 2 Multimodal Live API i dowiedzieć się, jak zaimplementować sterowanie pojedynczą ścieżką za pomocą LangGraph, przejdź do kolejnych 2 rozdziałów.
12. OPCJONALNIE: podsumowania audio z Gemini
Gemini może rozumieć i przetwarzać informacje z różnych źródeł, takich jak tekst, obrazy, a nawet dźwięk, co otwiera zupełnie nowe możliwości nauki i tworzenia treści. Umiejętność „widzenia”, „słyszenia” i „czytania” przez Gemini pozwala na tworzenie kreatywnych i zaangażowanych treści.
Oprócz tworzenia wizualizacji i tekstu ważne jest też skuteczne podsumowywanie i powtarzanie. Pomyśl: czy często zdarza Ci się, że łatwiej zapamiętasz chwytliwe słowa piosenki niż coś przeczytanego w podręczniku? Dźwięk może być niesamowicie zapadający w pamięć. Dlatego będziemy korzystać z możliwości multimodalnych Gemini, aby generować podsumowania audio naszych planów nauczania. Dzięki temu uczniowie będą mogli w wygodny i ciekawy sposób powtarzać materiał, co może zwiększyć zapamiętywanie i rozumienie dzięki nauce słuchowej.
Potrzebujemy miejsca na wygenerowane pliki audio. Cloud Storage to skalowalne i niezawodne rozwiązanie.
👉 Otwórz w konsoli Miejsce na dane. W menu po lewej stronie kliknij „Klastry”. U góry kliknij przycisk „+ UTWÓRZ”.
👉Skonfiguruj zasób:
- nazwa zasobnika: aidemy-recap-<NAZWA_UNIKALNA> WAŻNE: pamiętaj, aby zdefiniować niepowtarzalną nazwę zasobnika, która zaczyna się od „aidemy-recap-”. Ta unikalna nazwa jest kluczowa, aby uniknąć konfliktów nazw podczas tworzenia zasobnika Cloud Storage.
- region:
us-central1
. - Klasa pamięci: „Standardowa”. Standardowy jest odpowiedni do danych, do których często sięga użytkownik.
- Kontrola dostępu: pozostaw wybraną domyślną opcję „Jednolita kontrola dostępu”. Dzięki temu możesz zapewnić spójną kontrolę dostępu na poziomie zasobnika.
- Opcje zaawansowane: w ramach tego warsztatu zwykle wystarczają ustawienia domyślne. Kliknij przycisk UTWÓRZ, aby utworzyć zasobnik.
Może pojawić się okienko z informacjami o blokadzie dostępu publicznego. Pozostaw pole zaznaczone i kliknij Confirm
.
Nowo utworzony zasobnik będzie widoczny na liście zasobników. Zapamiętaj nazwę zasobnika, będzie Ci ona potrzebna później.
👉 Aby przyznać kontu usługi dostęp do zasobnika, uruchom w terminalu edytora Cloud Code te polecenia:
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"
👉 W edytorze kodu Cloud otwórz folder audio.py
w folderze course
. Wklej ten kod na końcu pliku:
config = LiveConnectConfig(
response_modalities=["AUDIO"],
speech_config=SpeechConfig(
voice_config=VoiceConfig(
prebuilt_voice_config=PrebuiltVoiceConfig(
voice_name="Charon",
)
)
),
)
async def process_weeks(teaching_plan: str):
region = "us-west1" #To workaround onRamp qouta limits
client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
clientAudio = genai.Client(vertexai=True, project=PROJECT_ID, location="us-central1")
async with clientAudio.aio.live.connect(
model=MODEL_ID,
config=config,
) as session:
for week in range(1, 4):
response = client.models.generate_content(
model="gemini-1.0-pro",
contents=f"Given the following teaching plan: {teaching_plan}, Extrace content plan for week {week}. And return just the plan, nothingh else " # Clarified prompt
)
prompt = f"""
Assume you are the instructor.
Prepare a concise and engaging recap of the key concepts and topics covered.
This recap should be suitable for generating a short audio summary for students.
Focus on the most important learnings and takeaways, and frame it as a direct address to the students.
Avoid overly formal language and aim for a conversational tone, tell a few jokes.
Teaching plan: {response.text} """
print(f"prompt --->{prompt}")
await session.send(input=prompt, end_of_turn=True)
with open(f"temp_audio_week_{week}.raw", "wb") as temp_file:
async for message in session.receive():
if message.server_content.model_turn:
for part in message.server_content.model_turn.parts:
if part.inline_data:
temp_file.write(part.inline_data.data)
data, samplerate = sf.read(f"temp_audio_week_{week}.raw", channels=1, samplerate=24000, subtype='PCM_16', format='RAW')
sf.write(f"course-week-{week}.wav", data, samplerate)
storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)
blob = bucket.blob(f"course-week-{week}.wav") # Or give it a more descriptive name
blob.upload_from_filename(f"course-week-{week}.wav")
print(f"Audio saved to GCS: gs://{BUCKET_NAME}/course-week-{week}.wav")
await session.close()
def breakup_sessions(teaching_plan: str):
asyncio.run(process_weeks(teaching_plan))
- Połączenie do strumieniowego przesyłania danych: najpierw nawiązywane jest trwałe połączenie z punktem końcowym interfejsu Live API. W przeciwieństwie do standardowego wywołania interfejsu API, w którym wysyłasz żądanie i otrzymujesz odpowiedź, to połączenie pozostaje otwarte, aby umożliwić ciągłą wymianę danych.
- Konfiguracja multimodalna: w konfiguracji możesz określić, jaki typ danych wyjściowych chcesz uzyskać (w tym przypadku dźwięk). Możesz też określić parametry, których chcesz użyć (np. wybór głosu, kodowanie dźwięku).
- Przetwarzanie asynchroniczne: ten interfejs API działa asynchronicznie, co oznacza, że nie blokuje wątku głównego podczas oczekiwania na zakończenie generowania dźwięku. Przetwarzanie danych w czasie rzeczywistym i wysyłanie wyników w porcjach zapewnia niemal natychmiastowe działanie.
Kluczowe pytanie brzmi: kiedy powinien przebiegać proces generowania dźwięku? W idealnej sytuacji podsumowania audio byłyby dostępne od razu po utworzeniu nowego planu nauczania. Ponieważ już wdrożyliśmy architekturę opartą na zdarzeniach, publikując plan nauczania w temacie Pub/Sub, możemy po prostu go zasubskrybować.
Nie tworzymy jednak zbyt często nowych planów nauczania. Nie byłoby wydajne, gdyby agent stale działał i czekał na nowe plany. Dlatego warto wdrożyć tę logikę generowania dźwięku jako funkcję Cloud Run.
Po wdrożeniu jako funkcji pozostaje nieaktywna, dopóki nie zostanie opublikowana nowa wiadomość w temacie Pub/Sub. Gdy to nastąpi, funkcja zostanie automatycznie uruchomiona, wygeneruje podsumowanie audio i zapisze je w naszym zasobniku.
👉 W folderze course
w pliku main.py
definiuje on funkcję Cloud Run, która zostanie uruchomiona, gdy będzie dostępny nowy plan nauczania. Otrzymuje on plan i inicjuje generowanie podsumowania audio. Dodaj do końca pliku ten fragment kodu.
@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: ten dekorator oznacza funkcję jako funkcję Cloud Run, która będzie wywoływana przez CloudEvents.
Testowanie lokalnie
👉 Uruchomimy go w środowisku wirtualnym i zainstalujemy niezbędne biblioteki Pythona do funkcji Cloud Run.
cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
python -m venv env
source env/bin/activate
pip install -r requirements.txt
👉 Emulator funkcji Cloud Run umożliwia nam testowanie funkcji lokalnie przed wdrożeniem jej w Google Cloud. Uruchom lokalny emulator, wykonując:
functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py
👉 Podczas działania emulatora możesz wysyłać do niego testowe zdarzenia CloudEvents, aby symulować publikowanie nowego planu nauczania. W nowym terminalu:
👉 Uruchom:
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=="
}
}'
Zamiast bezczynnie czekać na odpowiedź, przełącz się na inny terminal Cloud Shell. W terminalu emulatora możesz obserwować postęp i komunikaty o błędach generowane przez funkcję. 😁
W 2 terminalu powinna zostać zwrócona wartość OK
.
👉Aby zweryfikować dane w zasobniku, otwórz Cloud Storage, kliknij kolejno karty „Zasobnik” i aidemy-recap-xxx
👉 Aby zamknąć emulator, w terminalu wpisz ctrl+c
. I zamknij drugi terminal. Zamknij drugi terminal i uruchom deactivate, aby wyjść ze środowiska wirtualnego.
deactivate
Wdrażanie w Google Cloud
👉Po przetestowaniu lokalnie nadszedł czas na wdrożenie agenta kursu w Google Cloud. W terminalu uruchom te polecenia:
cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud functions deploy courses-agent \
--region=us-central1 \
--gen2 \
--source=. \
--runtime=python312 \
--trigger-topic=plan \
--entry-point=process_teaching_plan \
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME
Aby sprawdzić wdrożenie, otwórz Cloud Run w konsoli Google Cloud.Powinieneś zobaczyć nową usługę o nazwie courses-agent.
Aby sprawdzić konfigurację reguły, kliknij usługę courses-agent, aby wyświetlić jej szczegóły. Otwórz kartę „WYZWALNIKI”.
Powinieneś zobaczyć skonfigurowany przez siebie regułkę, która będzie nasłuchiwać wiadomości publikowanych w temacie planu.
Na koniec zobaczmy, jak działa cały proces.
👉 Następnie musimy skonfigurować agenta portalu, aby wiedział, gdzie znaleźć wygenerowane pliki audio. W terminalu uruchom:
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-portal \
--region=us-central1 \
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME
👉Spróbuj wygenerować nowy plan zajęć na stronie agenta planowania. Uruchamianie może potrwać kilka minut. Nie martw się, to usługa bez serwera. Uzyskaj adres URL swojego agenta planowania (jeśli go nie masz, uruchom ten kod w terminalu):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Po wygenerowaniu nowego planu poczekaj 2–3 minuty na wygenerowanie dźwięku. Z powodu ograniczeń związanych z płatnościami na tym koncie laboratoryjnym może to potrwać jeszcze kilka minut.
Aby sprawdzić, czy funkcja courses-agent
otrzymała plan nauczania, otwórz kartę „WYZWALCZENIA”. Od czasu do czasu odświeżaj stronę. W końcu zobaczysz, że funkcja została wywołana. Jeśli funkcja nie została wywołana po upływie 2 minut, możesz spróbować wygenerować plan zajęć ponownie. Unikaj jednak wielokrotnego generowania planów w krótkich odstępach czasowych, ponieważ każdy wygenerowany plan będzie kolejno wykorzystywany i przetwarzany przez pracownika obsługi klienta, co może spowodować powstanie zaległości.
👉 Otwórz portal i kliknij „Kursy”. Powinny się wyświetlić 3 karty, z których każda zawiera podsumowanie audio. Aby znaleźć adres URL swojego agenta portalu:
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
Kliknij „odtwórz” w przypadku każdego szkolenia, aby sprawdzić, czy podsumowania audio są zgodne z wygenerowanym przez Ciebie planem nauczania.
Zamknij środowisko wirtualne.
deactivate
13. OPCJONALNIE: współpraca z Gemini i DeepSeek na podstawie ról
Wiele punktów widzenia jest bezcenne, zwłaszcza podczas tworzenia angażujących i przemyślanych projektów. Teraz zbudujemy system wieloagentowy, który wykorzystuje 2 modele o różnych rolach do generowania zadań: jeden promuje współpracę, a drugi zachęca do samodzielnej nauki. Użyjemy architektury „single-shot”, w której przepływ pracy podąża ustaloną ścieżką.
Generator zadań Gemini
Zaczniemy od skonfigurowania funkcji Gemini w celu generowania projektów z nastawieniem na współpracę. Zmodyfikuj plik
gemini.py
znajdujący się w folderze assignment
.
👉Wklej ten kod na końcu pliku 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()
Do generowania zadań używa modelu Gemini.
Możemy przetestować agenta Gemini.
👉 Aby skonfigurować środowisko, uruchom te polecenia w terminalu:
cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt
👉 Aby przetestować tę funkcję:
python gemini.py
Powinieneś zobaczyć projekt, który zawiera więcej pracy grupowej. Test asercji na końcu również wyświetli wyniki.
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.
....
Zatrzymaj ctl+c
i wyczyść kod testowy. USUŃ ten kod z pliku 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()
Konfigurowanie generatora zadań DeepSeek
Platformy AI oparte na chmurze są wygodne, ale samodzielnie hostowane modele LLM mogą być kluczowe dla ochrony prywatności danych i zapewnienia suwerenności danych. Wdrożymy najmniejszy model DeepSeek (1,5 mld parametrów) w instancji Cloud Compute Engine. Istnieją też inne sposoby, np. hostowanie na platformie Vertex AI od Google lub na instancji GKE, ale ponieważ to tylko warsztaty dotyczące agentów AI i nie chcę zatrzymywać Cię tu na zawsze, użyjemy najprostszej metody. Jeśli jednak chcesz poznać inne opcje, otwórz plik deepseek-vertexai.py
w folderze projektu. Znajdziesz w nim przykładowy kod, który pokazuje, jak korzystać z modeli wdrożonych w Vertex AI.
👉 Aby utworzyć samodzielnie hostowaną platformę Ollama dla LLM:
cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--machine-type=e2-standard-4 \
--zone=us-central1-a \
--metadata-from-file startup-script=startup.sh \
--boot-disk-size=50GB \
--tags=ollama \
--scopes=https://www.googleapis.com/auth/cloud-platform
Aby sprawdzić, czy instancja Compute Engine jest uruchomiona:
W konsoli Google Cloud otwórz Compute Engine > „Instancje maszyn wirtualnych”. Powinieneś zobaczyć ollama-instance
z zielonym znacznikiem wyboru, co oznacza, że jest uruchomione. Jeśli jej nie widzisz, sprawdź, czy strefa to us-central1. Jeśli nie, może być konieczne wyszukanie go.
👉 Zainstalujemy najmniejszy model DeepSeek i przetestujemy go. Wróć do edytora Cloud Shell i w terminalu Nowy uruchom to polecenie, aby połączyć się przez SSH z instancją GCE.
gcloud compute ssh ollama-instance --zone=us-central1-a
Po nawiązaniu połączenia SSH może pojawić się ten komunikat:
„Czy chcesz kontynuować (Y/n)?”
Aby kontynuować, wpisz Y
(bez rozróżniania wielkości liter) i naciśnij Enter.
Następnie może pojawić się prośba o utworzenie szyfrogramu dla klucza SSH. Jeśli nie chcesz używać hasła, naciśnij klawisz Enter dwukrotnie, aby zaakceptować ustawienie domyślne (bez hasła).
👉Jesteś teraz na wirtualnej maszynie. Pobierz najmniejszy model DeepSeek R1 i sprawdź, czy działa.
ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"
👉 Aby zamknąć instancję GCE, wpisz w terminalu SSH:
exit
Zamknij nowy terminal i wróć do pierwotnego terminala.
👉Nie zapomnij skonfigurować zasad sieci, aby inne usługi miały dostęp do LLM. Jeśli chcesz, aby dostęp do instancji był możliwy w środowisku produkcyjnym, ogranicz dostęp do instancji, wdrażając logowanie zabezpieczone lub ograniczając dostęp na podstawie adresu IP. Uruchomienie:
gcloud compute firewall-rules create allow-ollama-11434 \
--allow=tcp:11434 \
--target-tags=ollama \
--description="Allow access to Ollama on port 11434"
👉 Aby sprawdzić, czy polityka zapory sieciowej działa prawidłowo, uruchom:
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
}'
Następnie będziemy pracować nad funkcją Deepseek w usługach projektów, aby generować projekty z indywidualnym naciskiem na pracę.
👉Edytuj folder deepseek.py
(assignment
) i dodaj ten fragment na końcu.
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()
👉 przetestuj go, wykonując te czynności:
cd ~/aidemy-bootstrap/assignment
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
python deepseek.py
Powinien pojawić się projekt, który zawiera więcej materiału do samodzielnej nauki.
**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.
....
👉 Zatrzymaj ctl+c
, aby wyczyścić kod testowy. USUŃ ten kod z pliku 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()
Teraz użyjemy tego samego modelu Gemini, aby połączyć oba przypisania w jedną nową grupę. Zmodyfikuj plik gemini.py
znajdujący się w folderze assignment
.
👉Wklej ten kod na końcu pliku 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
Aby połączyć zalety obu modeli, zorganizujemy zdefiniowany przepływ pracy za pomocą LangGraph. Ten proces składa się z 3 etapów: najpierw model Gemini generuje zadanie skupione na współpracy, potem model DeepSeek generuje zadanie podkreślające indywidualną pracę, a na koniec Gemini syntetyzuje te 2 zadania w jedno kompleksowe zadanie. Ponieważ zdefiniowaliśmy sekwencję kroków bez podejmowania decyzji przez LLM, jest to ścieżka pojedyncza zdefiniowana przez użytkownika.
👉 Wklej ten kod na końcu pliku main.py
w folderze 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 TestCreatAssignment(unittest.TestCase):
def test_create_assignment(self):
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
updated_state = create_assignment(initial_state)
print(updated_state)
if __name__ == '__main__':
unittest.main()
👉 Aby początkowo przetestować funkcję create_assignment
i sprawdzić, czy przepływ pracy łączący Gemini i DeepSeek działa prawidłowo, uruchom to polecenie:
cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py
Powinieneś zobaczyć coś, co łączy oba modele z ich indywidualną perspektywą nauki uczniów, a także nauki w grupach.
**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
👉 Zatrzymaj ctl+c
, aby wyczyścić kod testowy. USUŃ ten kod z pliku main.py
import unittest
class TestCreatAssignment(unittest.TestCase):
def test_create_assignment(self):
test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
updated_state = create_assignment(initial_state)
print(updated_state)
if __name__ == '__main__':
unittest.main()
Aby proces generowania zadań był automatyczny i dopasowany do nowych planów nauczania, wykorzystamy dotychczasową architekturę opartą na zdarzeniach. Podany niżej kod definiuje funkcję Cloud Run (generate_assignment), która będzie wywoływana za każdym razem, gdy nowy plan nauczania zostanie opublikowany w temacie Pub/Sub „plan”.
👉 Na końcu pliku main.py
dodaj ten kod:
@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
Testowanie lokalnie
Przed wdrożeniem funkcji Cloud Run w Google Cloud warto przetestować ją lokalnie. Umożliwia to szybsze iterowanie i łatwiejsze debugowanie.
Najpierw utwórz zasobnik Cloud Storage, w którym będą przechowywane wygenerowane pliki zadań, i przyznaj konto usługi dostęp do tego zasobnika. Uruchom w terminalu te polecenia:
👉WAŻNE: pamiętaj, aby zdefiniować unikalną nazwę ASSIGNMENT_BUCKET, która zaczyna się od „aidemy-assignment-”. Ta unikalna nazwa jest kluczowa, aby uniknąć konfliktów nazw podczas tworzenia zasobnika Cloud Storage. (Zamień <TWOJE_IMIĘ> na dowolne losowe słowo)
export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue
👉 I uruchom:
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"
👉 Teraz uruchom emulator funkcji Cloud Run:
cd ~/aidemy-bootstrap/assignment
functions-framework --target generate_assignment --signature-type=cloudevent --source main.py
👉 Gdy emulator działa w jednym terminalu, otwórz drugi terminal w Cloud Shell. W tym drugim terminalu wyślij testowe zdarzenie CloudEvent do emulatora, aby symulować opublikowanie nowego planu nauczania:
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=="
}
}'
Zamiast bezczynnie czekać na odpowiedź, przełącz się na inny terminal Cloud Shell. W terminalu emulatora możesz obserwować postęp i komunikaty o błędach generowane przez funkcję. 😁
Powinien zwrócić wartość OK.
Aby sprawdzić, czy przypisanie zostało wygenerowane i zmagazynowane, otwórz konsolę Google Cloud i kliknij Storage > „Cloud Storage”. Wybierz utworzony zasobnik aidemy-assignment
. W zasobniku powinien pojawić się plik tekstowy o nazwie assignment-{random number}.txt
. Kliknij plik, aby go pobrać i potwierdzić jego zawartość. W ten sposób możesz sprawdzić, czy nowy plik zawiera nowe, właśnie wygenerowane zadanie.
👉 Aby zamknąć emulator, w terminalu wpisz ctrl+c
. I zamknij drugi terminal. 👉 W terminalu, w którym działa emulator, zamknij środowisko wirtualne.
deactivate
👉 Następnie wdrożymy agenta przypisywania w chmurze
cd ~/aidemy-bootstrap/assignment
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
export PROJECT_ID=$(gcloud config get project)
gcloud functions deploy assignment-agent \
--gen2 \
--timeout=540 \
--memory=2Gi \
--cpu=1 \
--set-env-vars="ASSIGNMENT_BUCKET=${ASSIGNMENT_BUCKET}" \
--set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
--set-env-vars=OLLAMA_HOST=${OLLAMA_HOST} \
--region=us-central1 \
--runtime=python312 \
--source=. \
--entry-point=generate_assignment \
--trigger-topic=plan
Aby sprawdzić wdrożenie, otwórz Google Cloud Console i przejdź do Cloud Run.Powinieneś zobaczyć nową usługę o nazwie courses-agent.
Teraz, gdy proces generowania projektów został wdrożony, przetestowany i zaimplementowany, możemy przejść do następnego kroku: udostępnienia tych projektów w portalu ucznia.
14. OPCJONALNIE: współpraca z Gemini i DeepSeek na podstawie ról – część 2.
Generowanie dynamicznych witryn
Aby ulepszyć portal dla uczniów i sprawić, by był bardziej angażujący, wprowadzimy dynamiczne generowanie kodu HTML na stronach projektów. Celem jest automatyczne aktualizowanie portalu o nowy, atrakcyjny wizualnie wygląd za każdym razem, gdy generowany jest nowy przydział. Wykorzystuje ona możliwości kodowania LLM, aby zapewnić użytkownikom bardziej dynamiczne i ciekawe wrażenia.
👉 W edytorze Cloud Shell otwórz plik render.py
w folderze portal
i zastąp:
def render_assignment_page():
return ""
z tym fragmentem kodu:
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)}"
Korzysta on z modelu Gemini do dynamicznego generowania kodu HTML dla projektu. Używa ona treści projektu jako danych wejściowych i prompta do utworzenia atrakcyjnej wizualnie strony HTML z kreatywnym stylem.
Następnie utworzymy punkt końcowy, który będzie uruchamiany, gdy do puli zadań zostanie dodany nowy dokument:
👉 W folderze portalu otwórz plik app.py
i dodaj ten kod w ## Add your code here" comments
po funkcji new_teaching_plan
:
## Add your code here
@app.route('/render_assignment', methods=['POST'])
def render_assignment():
try:
data = request.get_json()
file_name = data.get('name')
bucket_name = data.get('bucket')
if not file_name or not bucket_name:
return jsonify({'error': 'Missing file name or bucket name'}), 400
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(file_name)
content = blob.download_as_text()
print(f"File content: {content}")
render_assignment_page(content)
return jsonify({'message': 'Assignment rendered successfully'})
except Exception as e:
print(f"Error processing file: {e}")
return jsonify({'error': 'Error processing file'}), 500
## Add your code here
Po uruchomieniu funkcja pobiera nazwę pliku i nazwę zasobnika z danych żądania, pobiera treść projektu z Cloud Storage i wywołuje funkcję render_assignment_page
, aby wygenerować kod HTML.
👉 Uruchomimy go lokalnie:
cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py
👉 W menu „Podgląd w przeglądarce” u góry okna Cloud Shell kliknij „Podejrzyj na porcie 8080”. Spowoduje to otwarcie aplikacji w nowej karcie przeglądarki. Na pasku nawigacyjnym kliknij link Przypisanie. W tym momencie powinna wyświetlić się pusta strona. Jest to oczekiwane zachowanie, ponieważ nie mamy jeszcze ustalonego połączenia komunikacyjnego między agentem przypisywania a portalem, które umożliwiłoby dynamiczne wypełnianie treści.
👉Aby uwzględnić te zmiany i wdrożyć zaktualizowany kod, ponownie utwórz obraz agenta portalu i prześlij go:
cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
👉 Po przesłaniu nowego obrazu ponownie wdróż usługę Cloud Run. Aby wymusić aktualizację Cloud Run, uruchom ten skrypt:
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
👉 Teraz wdrożymy regułę Eventarc, która będzie nasłuchiwać każdego nowego obiektu utworzonego (ukończonego) w zasobniku projektu. Gdy zostanie utworzony nowy plik przydziału, ten wyzwalacz automatycznie wywoła punkt końcowy /render_assignment w usłudze portalu.
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"
Aby sprawdzić, czy został utworzony, otwórz w konsoli Google Cloud stronę Aktywatory Eventarc. W tabeli powinna się pojawić wartość portal-assignment-trigger
. Kliknij nazwę, aby wyświetlić szczegóły.
Aktywowanie nowego aktywatora może potrwać 2–3 minuty.
Aby zobaczyć, jak działa generowanie dynamicznych przypisań, uruchom to polecenie, aby znaleźć adres URL swojego agenta planowania (jeśli go nie masz pod ręką):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Znajdź adres URL swojego agenta portalu:
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
W agencji planowania wygeneruj nowy plan nauczania.
Po kilku minutach (aby umożliwić wygenerowanie dźwięku, wygenerowanie projektu i wyrenderowanie HTML) przejdź do portalu dla uczniów.
👉 Na pasku nawigacyjnym kliknij link „Projekt”. Powinien wyświetlić się nowo utworzony projekt z dynamicznie generowanym kodem HTML. Każdy plan nauczania powinien być generowany jako dynamiczne zadanie.
Gratulujemy ukończenia wielu systemów agentów Aidemy. Uzyskasz praktyczne doświadczenie i cenne informacje na temat:
- korzyści płynące z systemów wieloagentowych, w tym modularność, skalowalność, specjalizacja i uproszczone utrzymanie;
- znaczenie architektur opartych na zdarzeniach w przypadku tworzenia responsywnych i luźno powiązanych aplikacji;
- strategiczne korzystanie z modeli LLM, dopasowując odpowiedni model do zadania i integrując je z narzędziami do osiągania rzeczywistych wyników;
- metody programowania w chmurze z wykorzystaniem usług Google Cloud do tworzenia skalowalnych i niezawodnych rozwiązań;
- Ważność rozważenia ochrony prywatności danych i modeli hostowania własnego jako alternatywy dla rozwiązań dostawców.
Masz teraz solidne podstawy do tworzenia zaawansowanych aplikacji opartych na AI w Google Cloud.
15. Wyzwania i dalsze kroki
Gratulujemy stworzenia systemu wieloagentowego Aidemy. Udało Ci się zbudować solidne podstawy edukacji opartej na AI. Teraz przyjrzyjmy się niektórym wyzwaniom i potencjalnym przyszłym ulepszeniom, które pozwolą jeszcze bardziej rozszerzyć możliwości tej technologii i zaspokajać rzeczywiste potrzeby:
Interaktywne uczenie się dzięki sesjom pytań i odpowiedzi na żywo:
- Wyzwanie: czy możesz użyć interfejsu API Gemini 2 w wersji na żywo, aby utworzyć funkcję pytań i odpowiedzi w czasie rzeczywistym dla uczniów? Wyobraź sobie wirtualną klasę, w której uczniowie mogą zadawać pytania i otrzymywać natychmiastowe odpowiedzi oparte na AI.
Automatyczne przesyłanie i ocenianie zadań:
- Wyzwanie: zaprojektuj i wprowadź system, który pozwoli uczniom przesyłać prace w formie cyfrowej i automatycznie oceniać je za pomocą AI, z mechanizmem wykrywania i zapobiegania plagiatowi. To wyzwanie to świetna okazja do zbadania możliwości generowania rozszerzonego przez wyszukiwanie w zapisanych informacjach (RAG), aby zwiększyć dokładność i wiarygodność procesów oceniania oraz wykrywania plagiatu.
16. Czyszczenie danych
Po utworzeniu i przetestowaniu systemu wieloagentowego Aidemy nadszedł czas na uporządkowanie środowiska Google Cloud.
- Usuwanie usług 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
- Usuwanie aktywatora 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
- Usunięcie tematu Pub/Sub
gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet
- Usuwanie instancji Cloud SQL
gcloud sql instances delete aidemy --quiet
- Usuwanie repozytorium Artifact Registry
gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet
- Usuwanie obiektów tajnych usługi Secret Manager
gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet
- Usuń instancję Compute Engine (jeśli została utworzona dla Deepseek)
gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet
- Usuń regułę zapory sieciowej dla instancji Deepseek
gcloud compute firewall-rules delete allow-ollama-11434 --quiet
- Usuwanie zasobników Cloud Storage
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
gsutil rb gs://$COURSE_BUCKET_NAME
gsutil rb gs://$ASSIGNMENT_BUCKET