1. Introdução
Olá! Você gosta da ideia de agentes, pequenos ajudantes que podem fazer coisas para você sem que você precise fazer nada, certo? Incrível! Mas, vamos ser sinceros, um agente nem sempre é suficiente, especialmente quando você está lidando com projetos maiores e mais complexos. Você provavelmente vai precisar de uma equipe inteira! É aí que entram os sistemas de vários agentes.
Os agentes, quando ativados por LLMs, oferecem uma flexibilidade incrível em comparação com a programação fixa tradicional. Mas, e sempre há um “mas”, eles têm os próprios desafios. E é exatamente isso que vamos abordar neste workshop.
Confira o que você vai aprender:
Criar seu primeiro agente com o LangGraph: vamos criar seu próprio agente usando o LangGraph, um framework conhecido. Você vai aprender a criar ferramentas que se conectam a bancos de dados, usar a API Gemini 2 mais recente para pesquisar na Internet e otimizar as solicitações e respostas para que seu agente possa interagir não apenas com LLMs, mas também com serviços existentes. Também vamos mostrar como funciona a chamada de função.
Como orquestrar agentes: vamos explorar diferentes maneiras de orquestrar seus agentes, desde caminhos simples e diretos até cenários de vários caminhos mais complexos. Pense nisso como uma forma de direcionar o fluxo da sua equipe de agentes.
Sistemas multiagentes: você vai descobrir como configurar um sistema em que os agentes podem colaborar e fazer as coisas juntos, tudo graças a uma arquitetura orientada a eventos.
Liberdade de LLM: use o melhor para o trabalho. Não ficamos presos a apenas um LLM. Você vai aprender a usar vários LLMs, atribuindo a eles diferentes funções para aumentar o poder de resolução de problemas usando "modelos de pensamento".
Conteúdo dinâmico? Sem problemas!: Imagine que seu agente cria conteúdo dinâmico personalizado para cada usuário em tempo real. Vamos mostrar como fazer isso.
Como levar para a nuvem com o Google Cloud: esqueça brincar em um caderno. Vamos mostrar como projetar e implantar seu sistema multiagente no Google Cloud para que ele esteja pronto para o mundo real.
Este projeto será um bom exemplo de como usar todas as técnicas que abordamos.
2. Arquitetura
Ser professor ou trabalhar na área da educação pode ser muito gratificante, mas vamos encarar a realidade: a carga de trabalho, especialmente todo o trabalho de preparação, pode ser desafiadora. Além disso, muitas vezes não há funcionários suficientes e a tutoria pode ser cara. Por isso, estamos propondo um assistente de ensino com tecnologia de IA. Essa ferramenta pode aliviar a carga de trabalho dos educadores e ajudar a preencher a lacuna causada pela falta de funcionários e de aulas particulares acessíveis.
Nosso assistente de ensino por IA pode preparar planos de aula detalhados, testes divertidos, resumos em áudio fáceis de seguir e atividades personalizadas. Assim, os professores podem se concentrar no que fazem de melhor: se conectar com os estudantes e ajudar a despertar o amor deles pela aprendizagem.
O sistema tem dois sites: um para os professores criarem planos de aula para as próximas semanas,
e outro para que os estudantes acessem testes, resumos em áudio e atividades.
Vamos conferir a arquitetura que alimenta nosso assistente de ensino, o Aidemy. Como você pode ver, dividimos o processo em vários componentes-chave, que trabalham juntos para que isso aconteça.
Principais elementos e tecnologias da arquitetura:
Google Cloud Platform (GCP): é o centro de todo o sistema:
- Vertex AI: acessa os LLMs Gemini do Google.
- Cloud Run: plataforma sem servidor para implantar agentes e funções contêinerizados.
- Cloud SQL: banco de dados PostgreSQL para dados do currículo.
- Pub/Sub e Eventarc: base da arquitetura orientada a eventos, que permite a comunicação assíncrona entre componentes.
- Cloud Storage: armazena resumos de áudio e arquivos de atividades.
- Secret Manager: gerencia credenciais de banco de dados com segurança.
- Artifact Registry: armazena imagens do Docker para os agentes.
- Compute Engine: como implantar um LLM auto-hospedado em vez de depender de soluções de fornecedores
LLMs: o "cérebro" do sistema:
- Modelos Gemini do Google: Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking e Gemini 1.5 Pro. São usados para planejamento de aulas, geração de conteúdo, criação de HTML dinâmico, explicação de testes e combinação de tarefas.
- DeepSeek: é usado para a tarefa especializada de gerar atividades de autoestudo.
LangChain e LangGraph (links em inglês): frameworks para desenvolvimento de aplicativos de LLM.
- Facilita a criação de fluxos de trabalho complexos com vários agentes.
- Permite a orquestração inteligente de ferramentas (chamadas de API, consultas de banco de dados, pesquisas na Web).
- Implementa uma arquitetura orientada a eventos para escalonamento e flexibilidade do sistema.
Em essência, nossa arquitetura combina o poder dos LLMs com dados estruturados e comunicação orientada a eventos, tudo isso executado no Google Cloud. Isso nos permite criar um assistente de ensino escalonável, confiável e eficaz.
3. Antes de começar
No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud. Verifique se o faturamento está ativado para seu projeto do Cloud. Saiba como verificar se o faturamento está ativado em um projeto.
👉Clique em Ativar o Cloud Shell na parte de cima do console do Google Cloud (é o ícone em forma de terminal na parte de cima do painel do Cloud Shell) e no botão "Abrir editor" (parece uma pasta aberta com um lápis). Isso vai abrir o editor de código do Cloud Shell na janela. Um explorador de arquivos vai aparecer à esquerda.
👉Clique no botão Fazer login no Cloud Code na barra de status inferior, conforme mostrado. Autorize o plug-in conforme instruído. Se a barra de status mostrar Cloud Code – sem projeto, selecione essa opção e, no menu suspenso "Selecionar um projeto do Google Cloud", escolha o projeto específico na lista de projetos criados.
👉Abra o terminal no ambiente de desenvolvimento integrado na nuvem,
👉No terminal, verifique se você já fez a autenticação e se o projeto está definido como seu ID usando o seguinte comando:
gcloud auth list
👉E execute:
gcloud config set project <YOUR_PROJECT_ID>
👉Execute o comando a seguir para ativar as APIs do Google Cloud necessárias:
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
Isso pode levar alguns minutos.
Ativar o Gemini Code Assist no Cloud Shell IDE
Clique no botão Code Assist no painel esquerdo, conforme mostrado, e selecione mais uma vez o projeto correto do Google Cloud. Se você receber uma solicitação para ativar a API Cloud AI Companion, faça isso e continue. Depois de selecionar o projeto do Google Cloud, confira se a mensagem de status do Cloud Code aparece na barra de status. Analise também se a Code Assist está ativada à direita, na barra de status, conforme mostrado abaixo:
Configurar a permissão
👉Configurar a permissão da conta de serviço
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"
Conceder permissões 👉Cloud Storage (leitura/gravação):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/storage.objectAdmin"
👉Pub/Sub (Publicar/receber):
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 (leitura/gravação):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/cloudsql.editor"
👉Eventarc (receber eventos):
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 (usuário):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/aiplatform.user"
👉Secret Manager (leitura):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/secretmanager.secretAccessor"
👉Valide o resultado no console do IAM
4. Como criar o primeiro agente
Antes de mergulharmos em sistemas multiagentes complexos, precisamos estabelecer um bloco de construção fundamental: um agente funcional único. Nesta seção, vamos dar os primeiros passos criando um agente "provedor de livros" simples. O agente do provedor de livros usa uma categoria como entrada e um LLM do Gemini para gerar um livro de representação JSON nessa categoria. Em seguida, ele mostra essas recomendações de livros como um endpoint da API REST .
👉Em outra guia do navegador, abra o Console do Google Cloud no navegador da Web e,no menu de navegação (☰), acesse "Cloud Run". Clique no botão "+ ... WRITE A FUNCTION".
👉Em seguida, vamos configurar as configurações básicas da função do Cloud Run:
- Nome do serviço:
book-provider
- Região:
us-central1
- Ambiente de execução:
Python 3.12
- Autenticação:
Allow unauthenticated invocations
para "Ativado".
👉Deixe as outras configurações como padrão e clique em Criar. Isso vai levar você ao editor de código-fonte.
Você vai encontrar arquivos main.py
e requirements.txt
pré-preenchidos.
O main.py
vai conter a lógica de negócios da função, e o requirements.txt
vai conter os pacotes necessários.
👉Agora está tudo pronto para escrever o código. Mas, antes de começar, vamos ver se o Gemini Code Assist pode nos ajudar. Volte ao editor do Cloud Shell, clique no ícone do Gemini Code Assist e cole a seguinte solicitação na caixa de comando:
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
O Code Assist vai gerar uma possível solução, fornecendo o código-fonte e um arquivo de dependência requirements.txt.
Recomendamos que você compare o código gerado pelo Code Assist com a solução correta e testada fornecida abaixo. Isso permite avaliar a eficácia da ferramenta e identificar possíveis discrepâncias. Embora os LLMs nunca devam ser confiáveis, o Code Assist pode ser uma ótima ferramenta para prototipagem rápida e geração de estruturas de código iniciais, e deve ser usado para começar bem.
Como este é um workshop, vamos usar o código verificado fornecido abaixo. No entanto, sinta-se à vontade para testar o código gerado pelo Code Assist no seu tempo para entender melhor os recursos e as limitações dele.
👉Volte para o editor de código-fonte da função do Cloud Run (na outra guia do navegador). Substitua com cuidado o conteúdo atual de main.py
pelo código abaixo:
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)
👉Substitua o conteúdo de requirements.txt pelo seguinte:
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
👉Definimos o ponto de entrada da função: recommended
👉Clique em SALVAR E IMPLANTAR para implantar a função. Aguarde a conclusão do upgrade. O Console do Cloud vai mostrar o status. Isso pode levar algum tempo.
👉Depois de implantar, volte ao editor do Cloud Shell, na execução do terminal:
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
Ele precisa mostrar alguns dados do livro no formato 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"}
]
Parabéns! Você implantou uma função do Cloud Run. Esse é um dos serviços que vamos integrar ao desenvolver nosso agente Aidemy.
5. Ferramentas de criação: como conectar agentes a dados e serviços RESTFUL
Vamos fazer o download do projeto de esqueleto do Bootstrap. Verifique se você está no editor do Cloud Shell. Na execução do terminal,
git clone https://github.com/weimeilin79/aidemy-bootstrap.git
Depois de executar esse comando, uma nova pasta chamada aidemy-bootstrap
será criada no seu ambiente do Cloud Shell.
No painel do Explorer do editor do Cloud Shell (geralmente no lado esquerdo), você vai encontrar a pasta que foi criada quando você clonou o repositório do Git aidemy-bootstrap
. Abra a pasta raiz do projeto no Explorer. Você vai encontrar uma subpasta planner
dentro dela. Abra também essa.
Vamos começar a criar as ferramentas que nossos agentes vão usar para se tornarem realmente úteis. Como você sabe, os LLMs são excelentes para raciocinar e gerar texto, mas eles precisam de acesso a recursos externos para realizar tarefas do mundo real e fornecer informações precisas e atualizadas. Pense nessas ferramentas como o "canivete suíço" do agente, que permite interagir com o mundo.
Ao criar um agente, é fácil codificar muitos detalhes. Isso cria um agente que não é flexível. Em vez disso, ao criar e usar ferramentas, o agente tem acesso a sistemas ou lógicas externas, o que oferece os benefícios da LLM e da programação tradicional.
Nesta seção, vamos criar a base para o agente de planejamento, que os professores vão usar para gerar planos de aula. Antes que o agente comece a gerar um plano, queremos definir limites fornecendo mais detalhes sobre o assunto e o tema. Vamos criar três ferramentas:
- Chamada de API RESTful:interação com uma API já existente para recuperar dados.
- Consulta de banco de dados:busca dados estruturados em um banco de dados do Cloud SQL.
- Pesquisa Google:acesso a informações em tempo real da Web.
Como buscar recomendações de livros em uma API
Primeiro, vamos criar uma ferramenta que extrai recomendações de livros da API book-provider que implantamos na seção anterior. Isso demonstra como um agente pode aproveitar os serviços atuais.
No editor do Cloud Shell, abra o projeto aidemy-bootstrap
que você clonou na seção anterior. 👉Edite o book.py
na pasta planner
e cole o seguinte código:
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."))
Explicação:
- recommend_book(query: str): essa função usa a consulta de um usuário como entrada.
- Interação com o LLM: usa o LLM para extrair a categoria da consulta. Isso demonstra como você pode usar o LLM para ajudar a criar parâmetros para ferramentas.
- Chamada de API: faz uma solicitação POST para a API do provedor de livros, transmitindo a categoria e o número de livros desejado.
👉Para testar essa nova função, defina a variável de ambiente e execute :
cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
👉Instale as dependências e execute o código para garantir que ele funcione:
cd ~/aidemy-bootstrap/planner/
python -m venv env
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
pip install -r requirements.txt
python book.py
Ignore a janela pop-up de aviso do Git.
Uma string JSON vai aparecer com as recomendações de livros extraídas da API do provedor de livros.
[{"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"}]
Se você vir isso, a primeira ferramenta está funcionando corretamente.
Em vez de criar explicitamente uma chamada de API RESTful com parâmetros específicos, usamos a linguagem natural ("Estou fazendo um curso…"). Em seguida, o agente extrai de forma inteligente os parâmetros necessários (como a categoria) usando NLP, destacando como o agente usa a compreensão de linguagem natural para interagir com a API.
👉Remova o código de teste abaixo do book.py
.
if __name__ == "__main__":
print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))
Como acessar dados do currículo em um banco de dados
Em seguida, vamos criar uma ferramenta que extrai dados estruturados do currículo de um banco de dados PostgreSQL do Cloud SQL. Isso permite que o agente acesse uma fonte confiável de informações para planejar as aulas.
👉Execute os comandos abaixo no terminal para criar uma instância do Cloud SQL chamada aidemy . Esse processo pode levar algum tempo.
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
👉Em seguida, crie um banco de dados chamado aidemy-db
na nova instância.
gcloud sql databases create aidemy-db \
--instance=aidemy
Vamos verificar a instância no Cloud SQL no console do Google Cloud. Uma instância do Cloud SQL chamada aidemy
deve aparecer. Clique no nome da instância para conferir os detalhes dela. Na página de detalhes da instância do Cloud SQL, clique em "SQL Studio" no menu de navegação à esquerda. Uma nova guia será aberta.
Clique para se conectar ao banco de dados. Fazer login no SQL Studio
Selecione aidemy-db
como o banco de dados. Digite postgres
como usuário e 1234qwer
como senha.
👉No editor de consultas do SQL Studio, cole o seguinte código 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.');
Esse código SQL cria uma tabela chamada curriculums
e insere alguns dados de exemplo. Clique em Executar para executar o código SQL. Você vai receber uma mensagem de confirmação indicando que os comandos foram executados.
👉Expanda o Explorer, encontre a tabela recém-criada e clique em consulta. Uma nova guia do editor com o SQL gerado para você será aberta.
SELECT * FROM
"public"."curriculums" LIMIT 1000;
👉Clique em Run.
A tabela de resultados vai mostrar as linhas de dados que você inseriu na etapa anterior, confirmando que a tabela e os dados foram criados corretamente.
Agora que você criou um banco de dados com dados de currículo de amostra preenchidos, vamos criar uma ferramenta para extraí-los.
👉No editor do Cloud Code, edite o arquivo curriculums.py
na pasta aidemy-bootstrap
e cole o seguinte código:
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()
Explicação:
- Variáveis de ambiente: o código recupera as credenciais do banco de dados e as informações de conexão das variáveis de ambiente.
- connect_with_connector(): essa função usa o conector do Cloud SQL para estabelecer uma conexão segura com o banco de dados.
- get_curriculum(year: int, subject: str): essa função recebe o ano e a disciplina como entrada, consulta a tabela de currículos e retorna a descrição do currículo correspondente.
👉Antes de executar o código, precisamos definir algumas variáveis de ambiente. No terminal, execute:
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"
👉Para testar, adicione o seguinte código ao final de curriculums.py
:
if __name__ == "__main__":
print(get_curriculum(6, "Mathematics"))
👉Execute o código:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py
A descrição do currículo de matemática do 6º ano será mostrada no console.
Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.
Se a descrição do currículo aparecer, a ferramenta de banco de dados está funcionando corretamente. Para interromper o script, pressione Ctrl+C
.
👉Remova o código de teste abaixo do curriculums.py
.
if __name__ == "__main__":
print(get_curriculum(6, "Mathematics"))
👉Sair do ambiente virtual, na execução do terminal:
deactivate
6. Ferramentas de criação: acesse informações em tempo real da Web
Por fim, vamos criar uma ferramenta que usa a integração do Gemini 2 e da Pesquisa Google para acessar informações em tempo real da Web. Isso ajuda o agente a ficar atualizado e fornecer resultados relevantes.
A integração da Gemini 2 com a API Google Search aprimora os recursos do agente, fornecendo resultados de pesquisa mais precisos e relevantes para o contexto. Isso permite que os agentes acessem informações atualizadas e baseiem as respostas em dados reais, minimizando as alucinações. A integração aprimorada da API também facilita as consultas em linguagem natural, permitindo que os agentes formulem solicitações de pesquisa complexas e sutis.
Essa função recebe uma consulta de pesquisa, currículo, disciplina e ano como entrada e usa a API Gemini e a ferramenta de pesquisa do Google para extrair informações relevantes da Internet. Se você olhar com atenção, vai perceber que ele usa o SDK de IA generativa do Google para fazer chamadas de função sem usar nenhum outro framework.
👉Edite search.py
na pasta aidemy-bootstrap
e cole o seguinte código:
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)
Explicação:
- Definição da ferramenta: google_search_tool: como agrupar o objeto GoogleSearch em uma ferramenta
- search_latest_resource(search_text: str, subject: str, year: int): essa função recebe uma consulta de pesquisa, um assunto e um ano como entrada e usa a API Gemini para realizar uma pesquisa no Google. Modelo do Gemini
- GenerateContentConfig: define que ele tem acesso à ferramenta GoogleSearch
O modelo Gemini analisa internamente o search_text e determina se ele pode responder à pergunta diretamente ou se precisa usar a ferramenta GoogleSearch. Essa é uma etapa essencial que acontece no processo de raciocínio do LLM. O modelo foi treinado para reconhecer situações em que ferramentas externas são necessárias. Se o modelo decidir usar a ferramenta GoogleSearch, o SDK de IA generativa do Google vai processar a invocação real. O SDK toma a decisão do modelo e envia os parâmetros gerados para a API Google Search. Essa parte está oculta para o usuário no código.
O modelo do Gemini integra os resultados da pesquisa à resposta. Ele pode usar as informações para responder à pergunta do usuário, gerar um resumo ou realizar outra tarefa.
👉Para testar, execute o código:
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py
Você vai encontrar a resposta da API de pesquisa do Gemini com os resultados de pesquisa relacionados a "Syllabus for Year 5 Mathematics". A saída exata vai depender dos resultados da pesquisa, mas será um objeto JSON com informações sobre a pesquisa.
Se você encontrar resultados, a ferramenta da Pesquisa Google está funcionando corretamente. Para interromper o script, pressione Ctrl+C
.
👉E remova a última parte do código.
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)
👉Sair do ambiente virtual, na execução do terminal:
deactivate
Parabéns! Agora você criou três ferramentas poderosas para seu agente de planejamento: um conector de API, um conector de banco de dados e uma ferramenta de pesquisa do Google. Essas ferramentas permitem que o agente acesse as informações e os recursos necessários para criar planos de ensino eficazes.
7. Orquestração com o LangGraph
Agora que criamos nossas ferramentas individuais, é hora de orquestrá-las usando o LangGraph. Isso nos permitirá criar um agente "planejador" mais sofisticado, que pode decidir de forma inteligente quais ferramentas usar e quando, com base na solicitação do usuário.
O LangGraph é uma biblioteca Python projetada para facilitar a criação de aplicativos multiatores com estado usando modelos de linguagem grandes (LLMs). Pense nisso como uma estrutura para orquestrar conversas e fluxos de trabalho complexos envolvendo LLMs, ferramentas e outros agentes.
Principais conceitos:
- Estrutura do gráfico:o LangGraph representa a lógica do aplicativo como um gráfico direcionado. Cada nó no gráfico representa uma etapa do processo, como uma chamada para um LLM, uma invocação de ferramenta ou uma verificação condicional. As conexões definem o fluxo de execução entre os nós.
- Estado:o LangGraph gerencia o estado do aplicativo à medida que ele se move pelo gráfico. Esse estado pode incluir variáveis, como a entrada do usuário, os resultados de chamadas de ferramentas, saídas intermediárias de LLMs e qualquer outra informação que precise ser preservada entre as etapas.
- Nós:cada nó representa uma computação ou interação. Eles podem ser:
- Nós de ferramentas:use uma ferramenta (por exemplo, faça uma pesquisa na Web, consulte um banco de dados)
- Nós de função:execute uma função Python.
- Arestas:conectam nós, definindo o fluxo de execução. Eles podem ser:
- Arestas diretas:um fluxo simples e incondicional de um nó para outro.
- Arestas condicionais:o fluxo depende do resultado de um nó condicional.
Vamos usar o LangGraph para implementar a orquestração. Vamos editar o arquivo aidemy.py
na pasta aidemy-bootstrap
para definir a lógica do LangGraph. 👉Adicione o código de acompanhamento ao final de 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"])}
Essa função é responsável por capturar o estado atual da conversa, fornecer uma mensagem do sistema ao LLM e pedir que ele gere uma resposta. O LLM pode responder diretamente ao usuário ou usar uma das ferramentas disponíveis.
tools : essa lista representa o conjunto de ferramentas disponível para o agente. Ele contém três funções de ferramenta que definimos nas etapas anteriores: get_curriculum
, search_latest_resource
e recommend_book
. llm.bind_tools(tools): "vincula" a lista de ferramentas ao objeto llm. A vinculação das ferramentas informa ao LLM que elas estão disponíveis e fornece informações sobre como usá-las (por exemplo, os nomes das ferramentas, os parâmetros aceitos e o que elas fazem).
Vamos usar o LangGraph para implementar a orquestração. 👉Adicione o seguinte código ao final de 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")
Explicação:
StateGraph(MessagesState)
:cria um objetoStateGraph
. UmStateGraph
é um conceito fundamental no LangGraph. Ele representa o fluxo de trabalho do seu agente como um gráfico, em que cada nó representa uma etapa do processo. Pense nisso como a definição do plano de como o agente vai raciocinar e agir.- Aresta condicional:originada do nó
"determine_tool"
, o argumentotools_condition
provavelmente é uma função que determina qual aresta seguir com base na saída da funçãodetermine_tool
. As arestas condicionais permitem que o gráfico se ramifique com base na decisão do LLM sobre qual ferramenta usar ou se responder diretamente ao usuário. É aqui que a "inteligência" do agente entra em jogo: ele pode adaptar seu comportamento dinamicamente com base na situação. - Loop:adiciona uma aresta ao gráfico que conecta o nó
"tools"
ao nó"determine_tool"
. Isso cria um loop no gráfico, permitindo que o agente use ferramentas repetidamente até coletar informações suficientes para concluir a tarefa e fornecer uma resposta satisfatória. Esse ciclo é crucial para tarefas complexas que exigem várias etapas de raciocínio e coleta de informações.
Agora, vamos testar nosso agente de planejamento para ver como ele orquestra as diferentes ferramentas.
Esse código vai executar a função prep_class com uma entrada específica do usuário, simulando uma solicitação para criar um plano de aula de geometria para a 5ª série, usando o currículo, as recomendações de livros e os recursos mais recentes da Internet.
Se você fechou o terminal ou as variáveis de ambiente não estão mais definidas, execute novamente os comandos a seguir.
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"
👉Execute o código:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py
Confira o registro no terminal. Você precisa ter evidências de que o agente está chamando as três ferramentas (obter o currículo da escola, receber recomendações de livros e pesquisar os recursos mais recentes) antes de fornecer o plano de aula final. Isso demonstra que a orquestração do LangGraph está funcionando corretamente e que o agente está usando de forma inteligente todas as ferramentas disponíveis para atender à solicitação do usuário.
================================ 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**
.........
Para interromper o script, pressione Ctrl+C
.
👉Agora, substitua o código de teste por um comando diferente, que exige a chamada de ferramentas diferentes.
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")
Se você fechou o terminal ou as variáveis de ambiente não estão mais definidas, execute novamente os comandos a seguir.
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"
👉Execute o código novamente:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py
O que você notou dessa vez? Quais ferramentas o agente chamou? O agente só chama a ferramenta search_latest_resource desta vez. Isso ocorre porque o comando não especifica que precisa das outras duas ferramentas, e nosso LLM é inteligente o suficiente para não chamar as outras ferramentas.
================================ 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.
Para interromper o script, pressione Ctrl+C
. 👉Remova o código de teste para manter o arquivo aidemy.py limpo:
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")
Com a lógica do agente definida, vamos iniciar o aplicativo da Web do Flask. Isso vai oferecer uma interface baseada em formulários para os professores interagirem com o agente. Embora as interações com chatbots sejam comuns em LLMs, optamos por uma IU de envio de formulário tradicional, já que ela pode ser mais intuitiva para muitos educadores.
Se você fechou o terminal ou as variáveis de ambiente não estão mais definidas, execute novamente os comandos a seguir.
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"
👉Agora, inicie a interface da Web.
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py
Procure mensagens de inicialização na saída do terminal do Cloud Shell. O Flask geralmente mostra mensagens indicando que está em execução e em qual porta.
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.
👉No menu "Visualização da Web", escolha "Visualizar na porta 8080". O Cloud Shell vai abrir uma nova guia ou janela do navegador com a visualização da Web do seu aplicativo.
Na interface do aplicativo, selecione 5
para o ano, selecione o assunto Mathematics
e digite Geometry
na solicitação de complemento
Em vez de ficar olhando para o espaço enquanto espera a resposta, mude para o terminal do Cloud Editor. É possível observar o progresso e qualquer mensagem de saída ou erro gerada pela função no terminal do emulador. 😁
👉Interrompa o script pressionando Ctrl+C
no terminal.
👉Sair do ambiente virtual:
deactivate
8. Como implantar o agente do planejador na nuvem
Criar e enviar a imagem para o registro
👉É hora de implantar isso na nuvem. No terminal, crie um repositório de artefatos para armazenar a imagem do Docker que vamos criar.
gcloud artifacts repositories create agent-repository \
--repository-format=docker \
--location=us-central1 \
--description="My agent repository"
O repositório "Created repository [agent-repository]" vai aparecer.
👉Execute o comando a seguir para criar a imagem do Docker.
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
👉Precisamos marcar a imagem novamente para que ela seja hospedada no Artifact Registry em vez do GCR e enviar a imagem marcada para o 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
Quando o push for concluído, você poderá verificar se a imagem foi armazenada no Artifact Registry. Navegue até o Artifact Registry no console do Google Cloud. Você vai encontrar a imagem aidemy-planner
no repositório agent-repository
.
Como proteger credenciais de banco de dados com o Secret Manager
Para gerenciar e acessar com segurança as credenciais do banco de dados, vamos usar o Google Cloud Secret Manager. Isso evita a codificação fixa de informações sensíveis no código do aplicativo e aumenta a segurança.
👉Vamos criar segredos individuais para o nome de usuário, a senha e o nome do banco de dados. Essa abordagem nos permite gerenciar cada credencial de forma independente. No terminal, execute:
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=-
Usar o Secret Manager é uma etapa importante para proteger seu aplicativo e evitar a exposição acidental de credenciais sensíveis. Ele segue as práticas recomendadas de segurança para implantações na nuvem.
Implantar no Cloud Run
O Cloud Run é uma plataforma sem servidor totalmente gerenciada que permite implantar aplicativos conteinerizados de maneira rápida e fácil. Ele abstrai o gerenciamento de infraestrutura, permitindo que você se concentre na criação e implantação do código. Vamos implantar o planejador como um serviço do Cloud Run.
👉No console do Google Cloud, navegue até Cloud Run. Clique em IMPLANTAR CONTÊINER e selecione SERVIÇO. Configure seu serviço do Cloud Run:
- Imagem do contêiner: clique em "Selecionar" no campo de URL. Encontre o URL da imagem que você enviou para o Artifact Registry (por exemplo, us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
- Nome do serviço:
aidemy-planner
- Região: selecione a região
us-central1
. - Autenticação: para os fins deste workshop, você pode permitir "Permitir invocações não autenticadas". Para a produção, é recomendável restringir o acesso.
- Guia Contêineres (expanda "Contêineres", "Rede"):
- Guia "Configurações":
- Recurso
- memória : 2 GB
- Recurso
- Guia "Variáveis e secrets":
- Variáveis de ambiente:
- Adicionar nome:
GOOGLE_CLOUD_PROJECT
e valor: <YOUR_PROJECT_ID> - Adicionar nome:
BOOK_PROVIDER_URL
e valor: <YOUR_BOOK_PROVIDER_FUNCTION_URL>
- Adicionar nome:
- Secrets expostos como variáveis de ambiente:
- Adicionar nome:
DB_USER
, secret: selecionedb-user
e versão:latest
- Adicionar nome:
DB_PASS
, secret: selecionedb-pass
e versão:latest
- Adicionar nome:
DB_NAME
, secret: selecionedb-name
e versão:latest
- Adicionar nome:
- Variáveis de ambiente:
- Guia "Configurações":
Execute o seguinte comando no terminal se precisar recuperar o URL YOUR_BOOK_PROVIDER_FUNCTION_URL:
gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)"
Deixe o padrão para os outros.
👉Clique em CRIAR.
O Cloud Run vai implantar seu serviço.
Depois de implantar, clique no serviço para acessar a página de detalhes dele. O URL implantado estará disponível na parte de cima.
Na interface do aplicativo, selecione 7
para o ano, escolha Mathematics
como assunto e insira Algebra
no campo "Solicitação de complemento". Isso vai fornecer ao agente o contexto necessário para gerar um plano de aula personalizado.
Parabéns! Você criou um plano de ensino usando nosso poderoso agente de IA. Isso demonstra o potencial dos agentes de reduzir significativamente a carga de trabalho e agilizar as tarefas, melhorando a eficiência e facilitando a vida dos educadores.
9. Sistemas multiagente
Agora que implementamos a ferramenta de criação de planos de aula, vamos focar na criação do portal do estudante. Esse portal vai dar aos estudantes acesso a testes, resumos em áudio e atividades relacionadas ao trabalho de curso. Dado o escopo dessa funcionalidade, vamos aproveitar o poder dos sistemas multiagentes para criar uma solução modular e escalonável.
Como discutimos anteriormente, em vez de depender de um único agente para lidar com tudo, um sistema multiagente nos permite dividir a carga de trabalho em tarefas menores e especializadas, cada uma delas gerenciada por um agente dedicado. Essa abordagem tem várias vantagens:
Modularidade e capacidade de manutenção: em vez de criar um único agente que faça tudo, crie agentes menores e especializados com responsabilidades bem definidas. Essa modularidade facilita a compreensão, a manutenção e a depuração do sistema. Quando um problema surge, você pode isolá-lo a um agente específico, em vez de ter que peneirar uma base de código enorme.
Escalonabilidade: escalonar um único agente complexo pode ser um gargalo. Com um sistema multiagente, é possível escalonar agentes individuais com base nas necessidades específicas deles. Por exemplo, se um agente estiver processando um grande volume de solicitações, você poderá ativar mais instâncias dele sem afetar o restante do sistema.
Especialização da equipe: pense assim: você não pediria a um engenheiro que criasse um aplicativo inteiro do zero. Em vez disso, você reúne uma equipe de especialistas, cada um com experiência em uma área específica. Da mesma forma, um sistema multiagente permite aproveitar os pontos fortes de diferentes LLMs e ferramentas, atribuindo-as a agentes mais adequados para tarefas específicas.
Desenvolvimento paralelo: equipes diferentes podem trabalhar em agentes diferentes ao mesmo tempo, acelerando o processo de desenvolvimento. Como os agentes são independentes, as mudanças em um agente têm menos probabilidade de afetar outros agentes.
Arquitetura orientada a eventos
Para permitir a comunicação e coordenação eficaz entre esses agentes, vamos empregar uma arquitetura orientada a eventos. Isso significa que os agentes vão reagir aos "eventos" que ocorrem no sistema.
Os agentes se inscrevem em tipos de eventos específicos (por exemplo, "plano de ensino gerado", "atividade criada"). Quando um evento ocorre, os agentes relevantes são notificados e podem reagir de acordo. Essa separação promove flexibilidade, escalonabilidade e capacidade de resposta em tempo real.
Para começar, precisamos de uma forma de transmitir esses eventos. Para isso, vamos configurar um tópico do Pub/Sub. Vamos começar criando um tópico chamado plano.
👉Acesse o console do Google Cloud Pub/Sub e clique no botão "Criar tópico".
👉Configure o tópico com o ID/nome plan
e desmarque Add a default subscription
. Deixe o restante como padrão e clique em Criar.
A página do Pub/Sub será atualizada, e o tópico recém-criado vai aparecer na tabela.
Agora, vamos integrar a funcionalidade de publicação de eventos do Pub/Sub ao nosso agente de planejamento. Vamos adicionar uma nova ferramenta que envia um evento "plano" ao tópico do Pub/Sub que acabamos de criar. Esse evento vai sinalizar para outros agentes no sistema (como os do portal do estudante) que um novo plano de ensino está disponível.
👉Volte ao editor do Cloud Code e abra o arquivo app.py
localizado na pasta planner
. Vamos adicionar uma função que publica o evento. Substitua:
##ADD SEND PLAN EVENT FUNCTION HERE
com
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: essa função usa o plano de ensino gerado como entrada, cria um cliente de editor do Pub/Sub, constrói o caminho do tópico, converte o plano de ensino em uma string JSON e publica a mensagem no tópico.
- Lista de ferramentas: a função
send_plan_event
é adicionada à lista de ferramentas e fica disponível para uso do agente.
Na mesma pasta, no arquivo app.py
, atualize o comando para instruir o agente a enviar o evento do plano de aula ao tópico do Pub/Sub depois de gerar o plano de aula. Substituir
### ADD send_plan_event CALL
com o seguinte:
send_plan_event(teaching_plan)
Ao adicionar a ferramenta send_plan_event e modificar a instrução, permitimos que o agente do planejador publique eventos no Pub/Sub, permitindo que outros componentes do sistema reajam à criação de novos planos de ensino. Agora vamos ter um sistema funcional de vários agentes nas próximas seções.
10. Como os testes sob demanda ajudam os estudantes
Imagine um ambiente de aprendizagem em que os estudantes têm acesso a uma infinidade de testes adaptados aos planos de aprendizagem específicos deles. Esses testes fornecem feedback imediato, incluindo respostas e explicações, promovendo uma compreensão mais profunda do material. Esse é o potencial que queremos desbloquear com nosso portal de testes com tecnologia de IA.
Para dar vida a essa visão, vamos criar um componente de geração de testes que possa criar perguntas de múltipla escolha com base no conteúdo do plano de ensino.
👉No painel Explorer do Cloud Code Editor, navegue até a pasta portal
. Abra o arquivo quiz.py
e copie e cole o seguinte código no final do arquivo.
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
No agente, ele cria um analisador de saída JSON projetado especificamente para entender e estruturar a saída do LLM. Ele usa o modelo QuizQuestion
que definimos anteriormente para garantir que a saída analisada esteja de acordo com o formato correto (pergunta, opções e resposta).
👉Execute os comandos a seguir no terminal para configurar um ambiente virtual, instalar dependências e iniciar o agente:
cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py
Use o recurso de visualização da Web do Cloud Shell para acessar o aplicativo em execução. Clique no link "Quizzes" na barra de navegação de cima ou no card na página de índice. Três testes gerados aleatoriamente vão aparecer para o estudante. Esses testes são baseados no plano de ensino e demonstram o poder do nosso sistema de geração de testes com tecnologia de IA.
Para interromper o processo em execução localmente, pressione Ctrl+C
no terminal.
Gemini 2 Thinking for Explanations
Temos testes, o que é um ótimo começo. Mas e se os estudantes errarem? É onde o aprendizado de verdade acontece, certo? Se pudermos explicar por que a resposta estava errada e como chegar à correta, é muito mais provável que ela seja lembrada. Além disso, ajuda a esclarecer qualquer confusão e aumenta a confiança deles.
Por isso, vamos trazer as armas pesadas: o modelo de "pensamento" do Gemini 2. É como dar um pouco mais de tempo para a IA pensar antes de explicar. Isso permite que ele forneça um feedback mais detalhado e melhor.
Queremos saber se ele pode ajudar os estudantes com assistência, respostas e explicações detalhadas. Para testar, vamos começar com uma matéria conhecida por ser complicada, o cálculo.
👉Primeiro, acesse o editor do Cloud Code. Em answer.py
, dentro da pasta portal
, substitua
def answer_thinking(question, options, user_response, answer, region):
return ""
com o seguinte snippet de código:
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)
Este é um app de linguagem muito simples que inicializa o modelo Gemini 2 Flash, em que ele é instruído a agir como um professor útil e fornecer explicações.
👉Execute o seguinte comando no terminal:
cd ~/aidemy-bootstrap/portal/
python answer.py
O resultado será semelhante ao exemplo fornecido nas instruções originais. O modelo atual pode não fornecer uma explicação completa.
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!
No arquivo answer.py, substitua o model_name de gemini-2.0-flash-001
por gemini-2.0-flash-thinking-exp-01-21
na função answer_thinking.
Isso muda o LLM que raciocina mais, o que ajuda a gerar explicações melhores. E execute novamente.
👉Execute para testar o novo modelo de pensamento:
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py
Confira um exemplo de resposta do modelo de pensamento que é muito mais detalhada e fornece uma explicação detalhada de como resolver o problema de cálculo. Isso destaca o poder dos modelos de "pensamento" na geração de explicações de alta qualidade. Será exibida uma saída semelhante a esta:
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.
👉Remova o seguinte código de teste de answer.py
:
if __name__ == "__main__":
question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
user_response = "B"
answer = "A"
region = "us-central1"
result = answer_thinking(question, options, user_response, answer, region)
👉Execute os comandos a seguir no terminal para configurar um ambiente virtual, instalar dependências e iniciar o agente:
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py
👉Use o recurso de visualização da Web do Cloud Shell para acessar o aplicativo em execução. Clique no link "Quizzes", responda a todos os testes e verifique se pelo menos uma resposta está errada. Em seguida, clique em "Enviar".
Em vez de ficar esperando a resposta, mude para o terminal do Cloud Editor. É possível observar o progresso e as mensagens de saída ou de erro geradas pela função no terminal do emulador. 😁
Para interromper o processo em execução localmente, pressione Ctrl+C
no terminal.
11. Como orquestrar os agentes com o Eventarc
Até agora, o portal do estudante gera testes com base em um conjunto padrão de planos de ensino. Isso é útil, mas significa que o agente de planejamento e o agente de testes do portal não estão se comunicando. Lembra quando adicionamos o recurso em que o agente do planejador publica os planos de aula recém-gerados em um tópico do Pub/Sub? Agora é hora de conectar isso ao nosso agente do portal.
Queremos que o portal atualize automaticamente o conteúdo do teste sempre que um novo plano de ensino for gerado. Para isso, vamos criar um endpoint no portal que possa receber esses novos planos.
👉No painel Explorer do Cloud Code Editor, navegue até a pasta portal
. Abra o arquivo app.py
para edição. Adicione o código a seguir entre ## Adicione seu código aqui:
## 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
Como recriar e implantar no Cloud Run
Ótimo! Você vai precisar atualizar e implantar novamente os agentes do planejador e do portal no Cloud Run. Isso garante que eles tenham o código mais recente e estejam configurados para se comunicar por eventos.
👉Em seguida, vamos recriar e enviar a imagem do agente planner de volta à execução do 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
👉Faremos o mesmo, criando e enviando a imagem do agente do portal:
cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
No Artifact Registry, as imagens de contêiner aidemy-planner
e aidemy-portal
vão aparecer na lista.
👉De volta ao terminal, execute este comando para atualizar a imagem do Cloud Run para o agente do planejador:
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
Será exibida uma saída semelhante a esta:
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
Anote o URL do serviço, que é o link para o agente do planejador implantado.
👉Execute este comando para criar a instância do Cloud Run para o agente do 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}
Será exibida uma saída semelhante a esta:
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
Anote o URL do serviço, que é o link para o portal do estudante implantado.
Como criar o gatilho do Eventarc
Mas a grande questão é: como esse endpoint é notificado quando há um novo plano aguardando no tópico do Pub/Sub? É aí que o Eventarc entra em ação.
O Eventarc atua como uma ponte, detectando eventos específicos (como uma nova mensagem chegando no nosso tópico do Pub/Sub) e acionando ações automaticamente em resposta. No nosso caso, ele detecta quando um novo plano de ensino é publicado e envia um sinal para o endpoint do nosso portal, informando que é hora de atualizar.
Com o Eventarc processando a comunicação orientada a eventos, podemos conectar facilmente o agente do planejador e o agente do portal, criando um sistema de aprendizado dinâmico e responsivo. É como ter um mensageiro inteligente que entrega automaticamente os planos de aula mais recentes no lugar certo.
👉No console, acesse o Eventarc.
👉Clique no botão "+ CREATE TRIGGER".
Configurar o acionador (noções básicas):
- Nome do acionador:
plan-topic-trigger
- Tipos de acionador: origens do Google.
- Fornecedor de eventos: Cloud Pub/Sub
- Tipo de evento:
google.cloud.pubsub.topic.v1.messagePublished
- Região:
us-central1
. - Tópico do Cloud Pub/Sub : selecione
plan
- CONCEDA à conta de serviço o papel
roles/iam.serviceAccountTokenCreator
- Destino do evento: Cloud Run
- Serviço do Cloud Run: aidemy-portal
- Caminho do URL do serviço:
/new_teaching_plan
- Ignorar mensagem (permissão negada em "locations/me-central2" (ou pode não existir)).
Clique em "Criar".
A página "Eventarc Triggers" será atualizada, e o acionador recém-criado vai aparecer na tabela.
👉Agora, acesse o planejador e solicite um novo plano de ensino. Desta vez, tente o ano 5
, o assunto science
com a solicitação atoms
Execute isso no terminal se você esquecer o local do agente do planejador
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Depois, aguarde um ou dois minutos. Esse atraso foi introduzido devido à limitação de faturamento deste laboratório. Em condições normais, não há atraso.
Por fim, acesse o portal do estudante. Os testes foram atualizados e agora estão alinhados ao novo plano de aula que você acabou de gerar. Isso demonstra a integração bem-sucedida do Eventarc no sistema da Aidemy.
Execute isso no terminal se você esquecer o local do agente do portal.
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
Parabéns! Você criou um sistema multiagente no Google Cloud, usando a arquitetura orientada a eventos para melhorar a escalabilidade e a flexibilidade. Você já tem uma base sólida, mas ainda há mais a explorar. Para saber mais sobre os benefícios reais dessa arquitetura, descubra o poder da API Multimodal Live do Gemini 2 e aprenda a implementar a orquestração de caminho único com o LangGraph. Continue nos próximos dois capítulos.
12. OPCIONAL: Recaps de áudio com o Gemini
O Gemini pode entender e processar informações de várias fontes, como texto, imagens e até áudio, abrindo um novo leque de possibilidades de aprendizado e criação de conteúdo. A capacidade do Gemini de "ver", "ouvir" e "ler" libera experiências criativas e envolventes para os usuários.
Além de criar recursos visuais ou textos, outra etapa importante na aprendizagem é a síntese e a recapitulação eficazes. Pense nisso: com que frequência você se lembra de uma letra de música cativante com mais facilidade do que algo que leu em um livro? O som pode ser muito memorável. Por isso, vamos aproveitar os recursos multimodais do Gemini para gerar resumos em áudio dos nossos planos de aula. Assim, os estudantes terão uma maneira conveniente e envolvente de revisar o material, o que pode aumentar a retenção e a compreensão pelo poder da aprendizagem auditiva.
Precisamos de um local para armazenar os arquivos de áudio gerados. O Cloud Storage oferece uma solução confiável e escalonável.
👉Acesse o Storage no console. Clique em "Buckets" no menu à esquerda. Clique no botão "+ CRIAR" na parte de cima.
👉Configure seu bucket:
- nome do bucket: aidemy-recap-<UNIQUE_NAME> IMPORTANTE: defina um nome de bucket exclusivo que comece com "aidemy-recap-". Esse nome exclusivo é crucial para evitar conflitos de nomenclatura ao criar seu bucket do Cloud Storage.
- região:
us-central1
. - Classe de armazenamento: "Padrão". O padrão é adequado para dados acessados com frequência.
- Controle de acesso: deixe selecionado o "Controle de acesso uniforme" padrão. Isso oferece um controle de acesso consistente no nível do bucket.
- Opções avançadas: para este workshop, as configurações padrão geralmente são suficientes. Clique no botão CRIAR para criar o bucket.
Talvez apareça uma janela pop-up sobre a prevenção do acesso público. Deixe a caixa marcada e clique em Confirm
.
O bucket recém-criado vai aparecer na lista "Buckets". Lembre-se do nome do bucket, porque você vai precisar dele mais tarde.
👉No terminal do Cloud Code Editor, execute os seguintes comandos para conceder à conta de serviço acesso ao bucket:
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role "roles/storage.objectViewer"
gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
--member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role "roles/storage.objectCreator"
👉No Cloud Code Editor, abra audio.py
na pasta course
. Cole o seguinte código no final do arquivo:
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))
- Conexão de streaming: primeiro, uma conexão persistente é estabelecida com o endpoint da API ao vivo. Ao contrário de uma chamada de API padrão em que você envia uma solicitação e recebe uma resposta, essa conexão permanece aberta para uma troca contínua de dados.
- Configuração multimodal: use a configuração para especificar o tipo de saída que você quer (neste caso, áudio) e até mesmo os parâmetros que gostaria de usar (por exemplo, seleção de voz, codificação de áudio).
- Processamento assíncrono: essa API funciona de forma assíncrona, ou seja, ela não bloqueia a linha de execução principal enquanto aguarda a geração de áudio ser concluída. Ao processar dados em tempo real e enviar a saída em blocos, ele oferece uma experiência quase instantânea.
Agora, a principal pergunta é: quando esse processo de geração de áudio deve ser executado? O ideal é que as revisões em áudio fiquem disponíveis assim que um novo plano de ensino for criado. Como já implementamos uma arquitetura orientada a eventos publicando o plano de ensino em um tópico do Pub/Sub, basta se inscrever nesse tópico.
No entanto, não geramos novos planos de ensino com muita frequência. Não seria eficiente ter um agente em execução constante e aguardando novos planos. É por isso que faz sentido implantar essa lógica de geração de áudio como uma função do Cloud Run.
Ao implantar como uma função, ela permanece inativa até que uma nova mensagem seja publicada no tópico do Pub/Sub. Quando isso acontece, a função é acionada automaticamente, gerando as revisões de áudio e armazenando-as no bucket.
👉Na pasta course
do arquivo main.py
, esse arquivo define a função do Cloud Run que será acionada quando um novo plano de ensino estiver disponível. Ele recebe o plano e inicia a geração da recapitulação em áudio. Adicione o snippet de código abaixo ao final do arquivo.
@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: esse decorador marca a função como uma função do Cloud Run que será acionada por CloudEvents.
Como testar localmente
👉Vamos executar isso em um ambiente virtual e instalar as bibliotecas Python necessárias para a função do 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
👉O emulador do Cloud Run Function permite testar a função localmente antes de implantá-la no Google Cloud. Para iniciar um emulador local, execute:
functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py
👉Enquanto o emulador estiver em execução, você poderá enviar CloudEvents de teste para ele para simular a publicação de um novo plano de ensino. Em um novo terminal:
👉Executar:
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=="
}
}'
Em vez de ficar olhando para o espaço enquanto espera a resposta, mude para o outro terminal do Cloud Shell. É possível observar o progresso e qualquer mensagem de saída ou erro gerada pela função no terminal do emulador. 😁
No segundo terminal, ele vai retornar OK
.
👉Verifique os dados no bucket, acesse o Cloud Storage e selecione a guia "Bucket" e, em seguida, o aidemy-recap-xxx
👉No terminal que está executando o emulador, digite ctrl+c
para sair. E feche o segundo terminal. Feche o segundo terminal e execute o comando "deactivate" para sair do ambiente virtual.
deactivate
Implantação no Google Cloud
👉Depois de testar localmente, é hora de implantar o agente do curso no Google Cloud. No terminal, execute estes comandos:
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
Verifique a implantação acessando o Cloud Run no console do Google Cloud.Um novo serviço chamado "courses-agent" vai aparecer.
Para verificar a configuração do acionador, clique no serviço courses-agent para conferir os detalhes. Acesse a guia "TRIGGERS".
Você vai encontrar um acionador configurado para detectar mensagens publicadas no tópico do plano.
Por fim, vamos conferir a execução completa.
👉Em seguida, precisamos configurar o agente do portal para que ele saiba onde encontrar os arquivos de áudio gerados. No terminal, execute:
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
👉Tente gerar um novo plano de ensino na página do agente do planejador. Pode levar alguns minutos para iniciar, não se preocupe, é um serviço sem servidor. Pegue o URL do agente do planejador (se você não tiver um, execute este comando no terminal):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Depois de gerar o novo plano, aguarde de dois a três minutos para que o áudio seja gerado. Isso vai levar mais alguns minutos devido à limitação de faturamento com essa conta de laboratório.
É possível monitorar se a função courses-agent
recebeu o plano de ensino verificando a guia "TRIGGERS" da função. Atualize a página periodicamente. A função será invocada. Se a função não tiver sido invocada após mais de dois minutos, tente gerar o plano de aula novamente. No entanto, evite gerar planos repetidamente em sucessão rápida, porque cada plano gerado será consumido e processado sequencialmente pelo agente, o que pode criar um acúmulo.
👉Acesse o portal e clique em "Cursos". Você vai encontrar três cards, cada um com uma recapitulação em áudio. Para encontrar o URL do agente do portal:
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
Clique em "Reproduzir" em cada curso para garantir que as resumos em áudio estejam alinhados ao plano de aula que você acabou de gerar.
Saia do ambiente virtual.
deactivate
13. OPCIONAL: colaboração baseada em função com o Gemini e o DeepSeek
Ter várias perspectivas é muito importante, especialmente ao criar atividades envolventes e bem pensadas. Agora vamos criar um sistema multiagente que aproveite dois modelos diferentes com funções distintas para gerar atividades: um promove a colaboração e o outro incentiva o estudo individual. Vamos usar uma arquitetura "single-shot", em que o fluxo de trabalho segue uma rota fixa.
Gerador de atribuições do Gemini
Vamos começar configurando a função do Gemini para gerar atividades com ênfase na colaboração. Edite o arquivo
gemini.py
localizado na pasta assignment
.
👉Cole o seguinte código no final do arquivo 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()
Ele usa o modelo do Gemini para gerar atribuições.
Estamos prontos para testar o Gemini Agent.
👉Execute estes comandos no terminal para configurar o ambiente:
cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt
👉Para testar, faça o seguinte:
python gemini.py
Você vai encontrar uma atividade que tem mais trabalho em grupo na saída. O teste de afirmação no final também vai gerar os resultados.
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.
....
Pare com ctl+c
e limpe o código de teste. Remova o código abaixo do 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()
Configurar o Gerador de atribuições do DeepSeek
Embora as plataformas de IA baseadas em nuvem sejam convenientes, os LLMs auto-hospedados podem ser essenciais para proteger a privacidade dos dados e garantir a soberania deles. Vamos implantar o modelo DeepSeek menor (1,5 bilhão de parâmetros) em uma instância do Cloud Compute Engine. Há outras maneiras, como hospedar na plataforma Vertex AI do Google ou na sua instância do GKE, mas, como este é apenas um workshop sobre agentes de IA e não quero que você fique aqui para sempre, vamos usar a maneira mais simples. No entanto, se você tiver interesse e quiser conhecer outras opções, confira o arquivo deepseek-vertexai.py
na pasta de atribuição, que fornece um código de exemplo sobre como interagir com modelos implantados na VertexAI.
👉Execute este comando no terminal para criar uma plataforma LLM autohospedada Ollama:
cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--machine-type=e2-standard-4 \
--zone=us-central1-a \
--metadata-from-file startup-script=startup.sh \
--boot-disk-size=50GB \
--tags=ollama \
--scopes=https://www.googleapis.com/auth/cloud-platform
Para verificar se a instância do Compute Engine está em execução:
Acesse Compute Engine > "Instâncias de VM" no console do Google Cloud. O ollama-instance
vai aparecer com uma marca de seleção verde indicando que está em execução. Se não encontrar, verifique se a zona é us-central1. Caso contrário, talvez seja necessário pesquisar.
👉Vamos instalar e testar o modelo DeepSeek menor. No Editor do Cloud Shell, em um terminal Novo, execute o comando a seguir para fazer login na instância do GCE via SSH.
gcloud compute ssh ollama-instance --zone=us-central1-a
Ao estabelecer a conexão SSH, você pode receber a seguinte mensagem:
"Do you want to continue (Y/n)?"
Basta digitar Y
(sem distinção entre maiúsculas e minúsculas) e pressionar Enter para continuar.
Em seguida, talvez seja necessário criar uma senha longa para a chave SSH. Se você preferir não usar uma senha longa, basta pressionar Enter duas vezes para aceitar a configuração padrão (sem senha longa).
👉Agora que você está na máquina virtual, extraia o modelo DeepSeek R1 mais pequeno e teste se ele funciona.
ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"
👉Sair da instância do GCE e inserir o seguinte no terminal SSH:
exit
Feche o novo terminal e volte ao terminal original.
👉Não se esqueça de configurar a política de rede para que outros serviços possam acessar o LLM. Limite o acesso à instância se quiser fazer isso para produção, implementando o login de segurança para o serviço ou restringindo o acesso ao IP. Execute:
gcloud compute firewall-rules create allow-ollama-11434 \
--allow=tcp:11434 \
--target-tags=ollama \
--description="Allow access to Ollama on port 11434"
👉Para verificar se a política de firewall está funcionando corretamente, tente executar:
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
}'
Em seguida, vamos trabalhar na função Deepseek no agente de atividades para gerar atividades com ênfase no trabalho individual.
👉Edite deepseek.py
na pasta assignment
e adicione o seguinte snippet ao final
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()
👉Vamos testar executando:
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
Você vai encontrar uma atividade com mais trabalho de estudo individual.
**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.
....
👉Interrompa o ctl+c
e limpe o código de teste. Remova o código abaixo do 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()
Agora, vamos usar o mesmo modelo do Gemini para combinar as duas tarefas em uma nova. Edite o arquivo gemini.py
localizado na pasta assignment
.
👉Cole o seguinte código no final do arquivo 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
Para combinar os pontos fortes de ambos os modelos, vamos orquestrar um fluxo de trabalho definido usando o LangGraph. Esse fluxo de trabalho consiste em três etapas: primeiro, o modelo Gemini gera uma tarefa focada na colaboração. Em segundo lugar, o modelo DeepSeek gera uma tarefa que enfatiza o trabalho individual. Por fim, o Gemini sintetiza essas duas tarefas em uma única tarefa abrangente. Como definimos previamente a sequência de etapas sem a tomada de decisão de LLM, isso constitui uma orquestração definida pelo usuário com um único caminho.
👉Cole o seguinte código no final do arquivo main.py
na pasta 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()
👉Para testar inicialmente a função create_assignment
e confirmar se o fluxo de trabalho que combina o Gemini e o DeepSeek está funcional, execute o seguinte comando:
cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py
Você vai encontrar algo que combine os dois modelos com a perspectiva individual para o estudo dos alunos e também para trabalhos em grupo.
**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
👉Interrompa o ctl+c
e limpe o código de teste. Remova o código abaixo do 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()
Para tornar o processo de geração de tarefas automático e responsivo a novos planos de ensino, vamos aproveitar a arquitetura orientada a eventos. O código a seguir define uma função do Cloud Run (generate_assignment) que será acionada sempre que um novo plano de ensino for publicado no tópico plan do Pub/Sub.
👉Adicione o seguinte código ao final de main.py
:
@functions_framework.cloud_event
def generate_assignment(cloud_event):
print(f"CloudEvent received: {cloud_event.data}")
try:
if isinstance(cloud_event.data.get('message', {}).get('data'), str):
data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
teaching_plan = data.get('teaching_plan')
elif 'teaching_plan' in cloud_event.data:
teaching_plan = cloud_event.data["teaching_plan"]
else:
raise KeyError("teaching_plan not found")
assignment = create_assignment(teaching_plan)
print(f"Assignment---->{assignment}")
#Store the return assignment into bucket as a text file
storage_client = storage.Client()
bucket = storage_client.bucket(ASSIGNMENT_BUCKET)
file_name = f"assignment-{random.randint(1, 1000)}.txt"
blob = bucket.blob(file_name)
blob.upload_from_string(assignment)
return f"Assignment generated and stored in {ASSIGNMENT_BUCKET}/{file_name}", 200
except (json.JSONDecodeError, AttributeError, KeyError) as e:
print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
return "Error processing event", 500
except Exception as e:
print(f"Error generate assignment: {e}")
return "Error generate assignment", 500
Como testar localmente
Antes de implantar no Google Cloud, é recomendável testar a função do Cloud Run localmente. Isso permite uma iteração mais rápida e uma depuração mais fácil.
Primeiro, crie um bucket do Cloud Storage para armazenar os arquivos de atividade gerados e conceda à conta de serviço acesso ao bucket. Execute os seguintes comandos no terminal:
👉IMPORTANTE: defina um nome exclusivo para ASSIGNMENT_BUCKET que comece com "aidemy-assignment-". Esse nome exclusivo é essencial para evitar conflitos de nomenclatura ao criar seu bucket do Cloud Storage. Substitua <YOUR_NAME> por qualquer palavra aleatória.
export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue
👉E execute:
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"
👉Agora, inicie o emulador da função do Cloud Run:
cd ~/aidemy-bootstrap/assignment
functions-framework --target generate_assignment --signature-type=cloudevent --source main.py
👉Enquanto o emulador está em execução em um terminal, abra um segundo terminal no Cloud Shell. Nesse segundo terminal, envie um CloudEvent de teste para o emulador para simular a publicação de um novo plano de ensino:
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=="
}
}'
Em vez de ficar olhando para o espaço enquanto espera a resposta, mude para o outro terminal do Cloud Shell. É possível observar o progresso e qualquer mensagem de saída ou erro gerada pela função no terminal do emulador. 😁
Ele deve retornar "OK".
Para confirmar se a atividade foi gerada e armazenada, acesse o console do Google Cloud e navegue até Armazenamento > "Cloud Storage". Selecione o bucket aidemy-assignment
que você criou. Você vai encontrar um arquivo de texto chamado assignment-{random number}.txt
no bucket. Clique no arquivo para fazer o download e verificar o conteúdo. Isso verifica se um novo arquivo contém uma nova atribuição gerada.
👉No terminal que está executando o emulador, digite ctrl+c
para sair. E feche o segundo terminal. 👉Além disso, no terminal que está executando o emulador, saia do ambiente virtual.
deactivate
👉Em seguida, vamos implantar o agente de atribuição na nuvem
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
Verifique a implantação acessando o console do Google Cloud e navegando até o Cloud Run.Um novo serviço chamado courses-agent será listado.
Agora que o fluxo de trabalho de geração de atividades foi implementado, testado e implantado, podemos passar para a próxima etapa: tornar essas atividades acessíveis no portal do estudante.
14. OPCIONAL: colaboração baseada em função com o Gemini e o DeepSeek – continuação
Geração de sites dinâmicos
Para melhorar o portal do estudante e torná-lo mais envolvente, vamos implementar a geração de HTML dinâmico para páginas de atividades. O objetivo é atualizar automaticamente o portal com um design novo e visualmente atraente sempre que uma nova atividade for gerada. Isso aproveita os recursos de programação do LLM para criar uma experiência do usuário mais dinâmica e interessante.
👉No editor do Cloud Shell, edite o arquivo render.py
na pasta portal
e substitua
def render_assignment_page():
return ""
com o seguinte snippet de código:
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)}"
Ele usa o modelo Gemini para gerar HTML dinamicamente para a atividade. Ele usa o conteúdo da atividade como entrada e um comando para instruir o Gemini a criar uma página HTML visualmente atraente com um estilo criativo.
Em seguida, vamos criar um endpoint que será acionado sempre que um novo documento for adicionado ao bucket de atribuição:
👉Na pasta do portal, edite o arquivo app.py
e adicione o seguinte código dentro do ## Add your code here" comments
, APÓS a função 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
Quando acionado, ele recupera o nome do arquivo e do bucket dos dados da solicitação, faz o download do conteúdo da atribuição do Cloud Storage e chama a função render_assignment_page
para gerar o HTML.
👉Vamos executar localmente:
cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py
👉No menu "Visualização na Web", na parte de cima da janela do Cloud Shell, selecione "Visualizar na porta 8080". O aplicativo será aberto em uma nova guia do navegador. Acesse o link Atribuição na barra de navegação. Uma página em branco vai aparecer nesse momento, o que é esperado, já que ainda não estabelecemos a ponte de comunicação entre o agente de atribuição e o portal para preencher o conteúdo dinamicamente.
👉Para incorporar essas mudanças e implantar o código atualizado, recrie e envie a imagem do agente do portal:
cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
👉Depois de enviar a nova imagem, implante o serviço do Cloud Run novamente. Execute o script a seguir para aplicar a atualização do Cloud Run:
export PROJECT_ID=$(gcloud config get project)
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud run services update aidemy-portal \
--region=us-central1 \
--set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME
👉Agora, vamos implantar um acionador do Eventarc que detecta qualquer novo objeto criado (finalizado) no bucket de atribuição. Esse acionador invoca automaticamente o endpoint /render_assignment no serviço do portal quando um novo arquivo de atribuição é criado.
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"
Para verificar se o gatilho foi criado, acesse a página Eventarc Triggers no Console do Google Cloud. O portal-assignment-trigger
vai aparecer na tabela. Clique no nome do acionador para conferir os detalhes.
Pode levar até dois ou três minutos para que o novo acionador seja ativado.
Para conferir a geração de atribuição dinâmica em ação, execute o comando a seguir para encontrar o URL do agente do planejador (se você não tiver ele à mão):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Encontre o URL do seu agente do portal:
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
No agente do planejador, gere um novo plano de ensino.
Após alguns minutos (para que a geração de áudio, a geração de tarefas e a renderização de HTML sejam concluídas), acesse o portal do estudante.
👉Clique no link "Assignment" na barra de navegação. Você vai encontrar uma atividade recém-criada com um HTML gerado dinamicamente. Sempre que um plano de ensino for gerado, ele precisa ser uma atividade dinâmica.
Parabéns por concluir o sistema multiagente da Aidemy. Você ganhou experiência prática e insights valiosos sobre:
- Os benefícios dos sistemas multiagentes, incluindo modularidade, escalabilidade, especialização e manutenção simplificada.
- A importância das arquiteturas orientadas a eventos para criar aplicativos responsivos e com acoplamento flexível.
- O uso estratégico de LLMs, combinando o modelo certo à tarefa e integrando-os a ferramentas para impacto real.
- Práticas de desenvolvimento nativo da nuvem usando os serviços do Google Cloud para criar soluções escalonáveis e confiáveis.
- A importância de considerar a privacidade de dados e os modelos de autohospedagem como uma alternativa às soluções do fornecedor.
Agora você tem uma base sólida para criar aplicativos sofisticados com tecnologia de IA no Google Cloud.
15. Desafios e próximas etapas
Parabéns por criar o sistema multiagente da Aidemy. Você criou uma base sólida para a educação com tecnologia de IA. Agora, vamos considerar alguns desafios e possíveis melhorias futuras para expandir ainda mais os recursos e atender às necessidades do mundo real:
Aprendizado interativo com perguntas e respostas ao vivo:
- Desafio: você pode aproveitar a API Live do Gemini 2 para criar um recurso de perguntas e respostas em tempo real para os alunos? Imagine uma sala de aula virtual em que os estudantes podem fazer perguntas e receber respostas imediatas com tecnologia de IA.
Envio e avaliação automáticas de atividades:
- Desafio: projetar e implementar um sistema que permita aos estudantes enviar atividades digitalmente e receber notas automáticas por IA, com um mecanismo para detectar e evitar plágio. Esse desafio é uma ótima oportunidade para conhecer a geração aumentada de recuperação (RAG, na sigla em inglês) e melhorar a precisão e a confiabilidade dos processos de avaliação e detecção de plágio.
16. Limpar
Agora que criamos e exploramos nosso sistema multiagente da Aidemy, é hora de limpar nosso ambiente do Google Cloud.
- Excluir serviços do 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
- Excluir gatilho do 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
- Excluir tópico Pub/Sub
gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet
- Excluir instância do Cloud SQL
gcloud sql instances delete aidemy --quiet
- Excluir o repositório do Artifact Registry
gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet
- Excluir secrets do Secret Manager
gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet
- Excluir a instância do Compute Engine (se criada para o Deepseek)
gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet
- Excluir a regra de firewall da instância do Deepseek
gcloud compute firewall-rules delete allow-ollama-11434 --quiet
- Excluir buckets do 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