1. Introducción
¡Hola! Te gusta la idea de los agentes: pequeños ayudantes que pueden hacer tareas por ti sin que tengas que levantar un dedo, ¿verdad? ¡Genial! Pero seamos realistas, un agente no siempre será suficiente, especialmente cuando te enfrentes a proyectos más grandes y complejos. Es probable que necesites un equipo completo de ellos. Ahí es donde entran en juego los sistemas multiagentes.
Cuando los agentes se basan en LLM, te brindan una flexibilidad increíble en comparación con la codificación fija tradicional. Sin embargo, y siempre hay un “pero”, estos servicios presentan sus propios desafíos difíciles. Y eso es exactamente lo que analizaremos en este taller.
Esto es lo que puedes aprender, piensa en ello como un aumento de nivel de tu juego de agente:
Crea tu primer agente con LangGraph: Nos pondremos manos a la obra para crear tu propio agente con LangGraph, un framework popular. Aprenderás a crear herramientas que se conecten a bases de datos, aprovechar la API de Gemini 2 más reciente para realizar búsquedas en Internet y optimizar las instrucciones y las respuestas, de modo que tu agente pueda interactuar no solo con los LLM, sino también con los servicios existentes. También te mostraremos cómo funcionan las llamadas a funciones.
Organiza tus agentes a tu manera: Exploraremos diferentes formas de organizar tus agentes, desde rutas simples y directas hasta situaciones más complejas de varios caminos. Piensa en ello como dirigir el flujo de tu equipo de agentes.
Sistemas de múltiples agentes: Descubre cómo configurar un sistema en el que tus agentes puedan colaborar y hacer tareas en conjunto, todo gracias a una arquitectura basada en eventos.
LLM Freedom: Usa lo mejor para el trabajo. No estamos limitados a un solo LLM. Verás cómo usar varios LLM y asignarles diferentes roles para aumentar la capacidad de resolución de problemas con “modelos de pensamiento” interesantes.
¿Contenido dinámico? No hay problema: Imagina que tu agente crea contenido dinámico personalizado específicamente para cada usuario, en tiempo real. Te mostraremos cómo hacerlo.
Lleva tu trabajo a la nube con Google Cloud: Olvídate de jugar con una notebook. Te mostraremos cómo diseñar e implementar tu sistema multiagente en Google Cloud para que esté listo para el mundo real.
Este proyecto será un buen ejemplo de cómo usar todas las técnicas de las que hablamos.
2. Arquitectura
Ser profesor o trabajar en educación puede ser muy gratificante, pero seamos sinceros, la carga de trabajo, en especial todo el trabajo de preparación, puede ser un desafío. Además, a menudo no hay suficiente personal y las tutorías pueden ser costosas. Por eso, proponemos un asistente de enseñanza potenciado por IA. Esta herramienta puede aligerar la carga de los educadores y ayudar a cerrar la brecha causada por la escasez de personal y la falta de tutoría asequible.
Nuestro asistente de enseñanza con IA puede crear planes de lecciones detallados, cuestionarios divertidos, resúmenes de audio fáciles de seguir y tareas personalizadas. Esto permite que los profesores se enfoquen en lo que mejor saben hacer: conectarse con los estudiantes y ayudarlos a enamorarse del aprendizaje.
El sistema tiene dos sitios: uno para que los profesores creen planes de clase para las próximas semanas y
y uno para que los estudiantes accedan a cuestionarios, resúmenes de audio y tareas.
Muy bien, analicemos la arquitectura que impulsa a nuestro asistente de enseñanza, Aidemy. Como puedes ver, lo dividimos en varios componentes clave, que funcionan juntos para que esto suceda.
Elementos y tecnologías arquitectónicas clave:
Google Cloud Platform (GCP): Es fundamental para todo el sistema:
- Vertex AI: Accede a los LLM de Gemini de Google.
- Cloud Run: Plataforma sin servidores para implementar agentes y funciones alojados en contenedores.
- Cloud SQL: Base de datos de PostgreSQL para los datos del plan de estudios.
- Pub/Sub y Eventarc: Son la base de la arquitectura basada en eventos, que permite la comunicación asíncrona entre componentes.
- Cloud Storage: Almacena resúmenes de audio y archivos de tareas.
- Secret Manager: Administra de forma segura las credenciales de la base de datos.
- Artifact Registry: Almacena imágenes de Docker para los agentes.
- Compute Engine: Para implementar un LLM alojado por el cliente en lugar de depender de soluciones de proveedores
LLM: El "cerebro" del sistema:
- Modelos Gemini de Google: (Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking, Gemini 1.5-pro) Se usan para planificar lecciones, generar contenido, crear HTML dinámico, explicar cuestionarios y combinar las tareas.
- DeepSeek: Se usa para la tarea especializada de generar tareas de autoaprendizaje.
LangChain y LangGraph: Frameworks para el desarrollo de aplicaciones de LLM
- Facilita la creación de flujos de trabajo complejos de varios agentes.
- Permite la orquestación inteligente de herramientas (llamadas a la API, consultas a la base de datos, búsquedas web).
- Implementa una arquitectura basada en eventos para la escalabilidad y flexibilidad del sistema.
En esencia, nuestra arquitectura combina la potencia de los LLM con datos estructurados y comunicación basada en eventos, todo lo cual se ejecuta en Google Cloud. Esto nos permite crear un asistente de enseñanza escalable, confiable y eficaz.
3. Antes de comenzar
En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Obtén información para verificar si la facturación está habilitada en un proyecto.
👉Haz clic en Activar Cloud Shell en la parte superior de la consola de Google Cloud (es el ícono con forma de terminal en la parte superior del panel de Cloud Shell) y, luego, en el botón "Abrir editor" (parece una carpeta abierta con un lápiz). Se abrirá el editor de código de Cloud Shell en la ventana. Verás un explorador de archivos en el lado izquierdo.
👉Haz clic en el botón Acceder a Cloud Code en la barra de estado de la parte inferior como se muestra. Autoriza el complemento según las instrucciones. Si ves Cloud Code - no project en la barra de estado, selecciónalo y, luego, en el menú desplegable "Select a Google Cloud Project", elige el proyecto específico de Google Cloud de la lista de proyectos que creaste.
👉Abre la terminal en el IDE en la nube, .
👉En la terminal, verifica que ya hayas realizado la autenticación y que el proyecto esté configurado con tu ID de proyecto con el siguiente comando:
gcloud auth list
👉Ejecuta el siguiente comando:
gcloud config set project <YOUR_PROJECT_ID>
👉Ejecuta el siguiente comando para habilitar las APIs de Google Cloud necesarias:
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
Este proceso puede tardar unos minutos.
Habilita Gemini Code Assist en el IDE de Cloud Shell
Haz clic en el botón Code Assist en el panel izquierdo como se muestra y selecciona por última vez el proyecto de Google Cloud correcto. Si se te solicita que habilites la API de Cloud AI Companion, hazlo y continúa. Una vez que selecciones tu proyecto de Google Cloud, asegúrate de que puedas verlo en el mensaje de estado de Cloud Code en la barra de estado y de tener Code Assist habilitado en la barra de estado del lado derecho, como se muestra a continuación:
Configuración de permisos
👉Configura el permiso de la cuenta de servicio
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"
Otorga permisos 👉Cloud Storage (lectura/escritura):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/storage.objectAdmin"
👉Pub/Sub (publicación y recepción):
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 (lectura/escritura):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/cloudsql.editor"
👉Eventarc (eventos de recepción):
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 (usuario):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/aiplatform.user"
👉Secret Manager (lectura):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
--role="roles/secretmanager.secretAccessor"
👉Valida el resultado en la consola de IAM.
4. Cómo compilar el primer agente
Antes de sumergirnos en los sistemas complejos de múltiples agentes, debemos establecer un elemento básico: un agente único y funcional. En esta sección, daremos nuestros primeros pasos creando un agente simple de "proveedor de libros". El agente del proveedor de libros toma una categoría como entrada y usa un LLM de Gemini para generar un libro de representación JSON dentro de esa categoría. Luego, entrega estas recomendaciones de libros como un extremo de la API de REST .
👉En otra pestaña del navegador, abre la consola de Google Cloud en tu navegador web y,en el menú de navegación (☰), ve a "Cloud Run". Haz clic en el botón "+ … WRITE A FUNCTION".
👉 A continuación, configuraremos los parámetros básicos de la función de Cloud Run:
- Nombre del servicio:
book-provider
- Región:
us-central1
- Entorno de ejecución:
Python 3.12
- Autenticación:
Allow unauthenticated invocations
a Habilitado.
👉Deja los demás parámetros de configuración como predeterminados y haz clic en Crear. Esto te llevará al editor de código fuente.
Verás archivos main.py
y requirements.txt
prepro pagados.
main.py
contendrá la lógica empresarial de la función, y requirements.txt
contendrá los paquetes necesarios.
👉Ahora ya está todo listo para escribir código. Pero antes de comenzar, veamos si Gemini Code Assist puede darnos una ventaja. Regresa al editor de Cloud Shell, haz clic en el ícono de Gemini Code Assist y pega la siguiente solicitud en el cuadro de instrucciones:
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
Luego, Code Assist generará una posible solución, que proporcionará el código fuente y un archivo de dependencia requirements.txt.
Te recomendamos que compares el código generado por Code Assist con la solución correcta y probada que se proporciona a continuación. Esto te permite evaluar la eficacia de la herramienta y detectar posibles discrepancias. Si bien nunca se debe confiar ciegamente en los LLM, Code Assist puede ser una gran herramienta para crear prototipos rápidamente y generar estructuras de código iniciales, y se debe usar para obtener una buena ventaja.
Como se trata de un taller, usaremos el código verificado que se proporciona a continuación. Sin embargo, no dudes en experimentar con el código generado por Code Assist en tu tiempo libre para comprender mejor sus capacidades y limitaciones.
👉Regresa al editor de código fuente de la función de Cloud Run (en la otra pestaña del navegador). Reemplaza cuidadosamente el contenido existente de main.py
por el código que se proporciona a continuación:
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)
👉Reemplaza el contenido de requirements.txt con lo siguiente:
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
👉 Estableceremos el Punto de entrada de la función: recommended
👉Haz clic en GUARDAR Y IMPLEMENTAR para implementar la función. Espera a que se complete el proceso de implementación. Cloud Console mostrará el estado. Esto puede tardar varios minutos.
👉Una vez que se haya implementado, vuelve al editor de Cloud Shell y, en la terminal, ejecuta lo siguiente:
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
Debería mostrar algunos datos de libros en 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"}
]
¡Felicitaciones! Implementaste correctamente una función de Cloud Run. Este es uno de los servicios que integraremos cuando desarrollemos nuestro agente de Aidemy.
5. Herramientas de compilación: Cómo conectar agentes a servicios y datos RESTFUL
Comencemos y descarguemos el proyecto de esqueleto de Bootstrap. Asegúrate de estar en el editor de Cloud Shell. En la ejecución de la terminal,
git clone https://github.com/weimeilin79/aidemy-bootstrap.git
Después de ejecutar este comando, se creará una carpeta nueva llamada aidemy-bootstrap
en tu entorno de Cloud Shell.
En el panel del explorador del editor de Cloud Shell (por lo general, en el lado izquierdo), ahora deberías ver la carpeta que se creó cuando clonaste el repositorio de Git aidemy-bootstrap
. Abre la carpeta raíz de tu proyecto en el Explorador. Encontrarás una subcarpeta planner
dentro de ella. Ábrela también.
Comencemos a crear las herramientas que nuestros agentes usarán para ser realmente útiles. Como sabes, los LLM son excelentes para razonar y generar texto, pero necesitan acceso a recursos externos para realizar tareas del mundo real y proporcionar información precisa y actualizada. Piensa en estas herramientas como la "navaja suiza" del agente, que le permite interactuar con el mundo.
Cuando compilas un agente, es fácil caer en la codificación de una gran cantidad de detalles. Esto crea un agente que no es flexible. En cambio, cuando crea y usa herramientas, el agente tiene acceso a sistemas o lógicas externos, lo que le brinda los beneficios del LLM y de la programación tradicional.
En esta sección, crearemos la base del agente de planificación, que los profesores usarán para generar planes de lecciones. Antes de que el agente comience a generar un plan, queremos establecer límites proporcionando más detalles sobre el tema. Crearemos tres herramientas:
- Llamada a la API REST: Interacción con una API preexistente para recuperar datos.
- Consulta de base de datos: Recupera datos estructurados de una base de datos de Cloud SQL.
- Búsqueda de Google: Accede a información en tiempo real desde la Web.
Cómo recuperar recomendaciones de libros desde una API
Primero, crearemos una herramienta que recupere recomendaciones de libros de la API de book-provider que implementamos en la sección anterior. Esto demuestra cómo un agente puede aprovechar los servicios existentes.
En el Editor de Cloud Shell, abre el proyecto aidemy-bootstrap
que clonaste en la sección anterior. 👉Edita el archivo book.py
en la carpeta planner
y pega el siguiente 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."))
Explicación:
- recommend_book(query: str): Esta función toma la consulta de un usuario como entrada.
- Interacción con el LLM: Usa el LLM para extraer la categoría de la consulta. Esto demuestra cómo puedes usar el LLM para crear parámetros para herramientas.
- Llamada a la API: Realiza una solicitud POST a la API del proveedor de libros y pasa la categoría y la cantidad deseada de libros.
👉Para probar esta nueva función, configura la variable de entorno y ejecuta lo siguiente :
cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
👉Instala las dependencias y ejecuta el código para asegurarte de que funcione. Para ello, ejecuta lo siguiente:
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
Ignora la ventana emergente de advertencia de Git.
Deberías ver una cadena JSON que contiene recomendaciones de libros recuperadas de la API del proveedor de libros.
[{"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"}]
Si ves esto, significa que la primera herramienta funciona correctamente.
En lugar de crear explícitamente una llamada a la API RESTful con parámetros específicos, usamos lenguaje natural ("Estoy haciendo un curso…"). Luego, el agente extrae de forma inteligente los parámetros necesarios (como la categoría) con el NLP, lo que destaca cómo el agente aprovecha la comprensión del lenguaje natural para interactuar con la API.
👉Quita el siguiente código de prueba de 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."))
Cómo obtener datos de currículum de una base de datos
A continuación, compilaremos una herramienta que recupere datos de planes de estudios estructurados de una base de datos de PostgreSQL de Cloud SQL. Esto permite que el agente acceda a una fuente de información confiable para planificar las lecciones.
👉Ejecuta los siguientes comandos en la terminal para crear una instancia de Cloud SQL llamada aidemy . Este proceso puede tardar un poco.
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
👉A continuación, crea una base de datos llamada aidemy-db
en la instancia nueva.
gcloud sql databases create aidemy-db \
--instance=aidemy
Verifiquemos la instancia en Cloud SQL en la consola de Google Cloud. Deberías ver una instancia de Cloud SQL llamada aidemy
en la lista. Haz clic en el nombre de la instancia para ver sus detalles. En la página de detalles de la instancia de Cloud SQL, haz clic en "SQL Studio" en el menú de navegación de la izquierda. Se abrirá una pestaña nueva.
Haz clic para conectarte a la base de datos. Accede a SQL Studio
Selecciona aidemy-db
como la base de datos. Ingresa postgres
como usuario y 1234qwer
como contraseña.
👉En el editor de consultas de SQL Studio, pega el siguiente 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.');
Este código SQL crea una tabla llamada curriculums
y, luego, inserta algunos datos de muestra. Haz clic en Ejecutar para ejecutar el código SQL. Deberías ver un mensaje de confirmación que indica que los comandos se ejecutaron correctamente.
👉Expande el explorador, busca la tabla recién creada y haz clic en consulta. Se debería abrir una nueva pestaña del editor con el SQL generado por ti.
SELECT * FROM
"public"."curriculums" LIMIT 1000;
👉Haz clic en Ejecutar.
La tabla de resultados debería mostrar las filas de datos que insertaste en el paso anterior, lo que confirma que la tabla y los datos se crearon correctamente.
Ahora que creaste correctamente una base de datos con datos de planes de estudios de muestra propagados, crearemos una herramienta para recuperarlos.
👉En el editor de Cloud Code, edita el archivo curriculums.py
en la carpeta aidemy-bootstrap
y pega el siguiente 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()
Explicación:
- Variables de entorno: El código recupera las credenciales de la base de datos y la información de conexión de las variables de entorno (obtén más información a continuación).
- connect_with_connector(): Esta función usa el conector de Cloud SQL para establecer una conexión segura con la base de datos.
- get_curriculum(year: int, subject: str): Esta función toma el año y la asignatura como entrada, consulta la tabla de planes de estudios y muestra la descripción correspondiente.
👉Antes de ejecutar el código, debemos configurar algunas variables de entorno. En la terminal, ejecuta lo siguiente:
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 probarlo, agrega el siguiente código al final de curriculums.py
:
if __name__ == "__main__":
print(get_curriculum(6, "Mathematics"))
👉 Ejecuta el código:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py
Deberías ver la descripción del plan de estudios de Matemáticas de sexto grado impresa en la consola.
Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.
Si ves la descripción del plan de estudios, significa que la herramienta de la base de datos funciona correctamente. Presiona Ctrl+C
para detener la secuencia de comandos.
👉Quita el siguiente código de prueba de curriculums.py
.
if __name__ == "__main__":
print(get_curriculum(6, "Mathematics"))
👉Sal del entorno virtual y, en la terminal, ejecuta lo siguiente:
deactivate
6. Herramientas de compilación: Accede a información en tiempo real desde la Web
Por último, crearemos una herramienta que use la integración de Gemini 2 y la Búsqueda de Google para acceder a información en tiempo real desde la Web. Esto ayuda al agente a mantenerse al día y a proporcionar resultados relevantes.
La integración de Gemini 2 con la API de la Búsqueda de Google mejora las capacidades de los agentes, ya que proporciona resultados de búsqueda más precisos y relevantes según el contexto. Esto permite que los agentes accedan a información actualizada y basen sus respuestas en datos del mundo real, lo que minimiza las alucinaciones. La integración mejorada de la API también facilita las consultas de lenguaje natural, lo que permite a los agentes formular solicitudes de búsqueda complejas y matizadas.
Esta función toma una búsqueda, un plan de estudios, una asignatura y un año como entrada y usa la API de Gemini y la herramienta de Búsqueda de Google para recuperar información relevante de Internet. Si observas con atención, verás que usa el SDK de IA generativa de Google para realizar llamadas a función sin usar ningún otro framework.
👉Edita search.py
en la carpeta aidemy-bootstrap
y pega el siguiente 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)
Explicación:
- Definir herramienta: google_search_tool: une el objeto GoogleSearch dentro de una herramienta.
- search_latest_resource(search_text: str, subject: str, year: int): Esta función toma una búsqueda, un tema y un año como entrada y usa la API de Gemini para realizar una búsqueda de Google. Modelo de Gemini
- GenerateContentConfig: Define que tiene acceso a la herramienta GoogleSearch.
El modelo de Gemini analiza internamente el search_text y determina si puede responder la pregunta directamente o si necesita usar la herramienta GoogleSearch. Este es un paso fundamental que ocurre dentro del proceso de razonamiento del LLM. El modelo se entrenó para reconocer situaciones en las que se necesitan herramientas externas. Si el modelo decide usar la herramienta GoogleSearch, el SDK de Google Generative AI controla la invocación real. El SDK toma la decisión del modelo y los parámetros que genera, y los envía a la API de la Búsqueda de Google. Esta parte está oculta para el usuario en el código.
Luego, el modelo de Gemini integra los resultados de la búsqueda en su respuesta. Puede usar la información para responder la pregunta del usuario, generar un resumen o realizar alguna otra tarea.
👉Para probarlo, ejecuta el código:
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py
Deberías ver la respuesta de la API de Gemini Search que contiene resultados de la búsqueda relacionados con "Syllabus for Year 5 Mathematics". El resultado exacto dependerá de los resultados de la búsqueda, pero será un objeto JSON con información sobre la búsqueda.
Si ves resultados de la búsqueda, significa que la herramienta de la Búsqueda de Google funciona correctamente. Presiona Ctrl+C
para detener la secuencia de comandos.
👉 Y quita la última parte del 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)
👉Sal del entorno virtual y, en la terminal, ejecuta lo siguiente:
deactivate
¡Felicitaciones! Ahora compilaste tres herramientas potentes para tu agente de planificación: un conector de API, un conector de base de datos y una herramienta de Búsqueda de Google. Estas herramientas permitirán al agente acceder a la información y las capacidades que necesita para crear planes de enseñanza eficaces.
7. Organización con LangGraph
Ahora que compilamos nuestras herramientas individuales, es hora de orquestarlas con LangGraph. Esto nos permitirá crear un agente "planificador" más sofisticado que pueda decidir de forma inteligente qué herramientas usar y cuándo, según la solicitud del usuario.
LangGraph es una biblioteca de Python diseñada para facilitar la compilación de aplicaciones multiactor con estado usando modelos de lenguaje extensos (LLM). Piensa en ello como un framework para organizar conversaciones y flujos de trabajo complejos que involucran LLM, herramientas y otros agentes.
Conceptos clave:
- Estructura del gráfico: LangGraph representa la lógica de tu aplicación como un gráfico dirigido. Cada nodo del gráfico representa un paso del proceso (p.ej., una llamada a un LLM, una invocación de herramienta o una verificación condicional). Los bordes definen el flujo de ejecución entre los nodos.
- Estado: LangGraph administra el estado de tu aplicación a medida que se mueve por el gráfico. Este estado puede incluir variables como la entrada del usuario, los resultados de las llamadas a herramientas, los resultados intermedios de los LLM y cualquier otra información que se deba preservar entre pasos.
- Nodos: Cada nodo representa un cálculo o una interacción. Pueden ser los siguientes:
- Nodos de herramienta: Usar una herramienta (p.ej., realizar una búsqueda web o consultar una base de datos)
- Nodos de función: Ejecutan una función de Python.
- Ejes: Conectan los nodos y definen el flujo de ejecución. Pueden ser los siguientes:
- Ejes directos: Es un flujo simple y no condicional de un nodo a otro.
- Ejes condicionales: El flujo depende del resultado de un nodo condicional.
Usaremos LangGraph para implementar la orquestación. Editemos el archivo aidemy.py
en la carpeta aidemy-bootstrap
para definir nuestra lógica de LangGraph. 👉 Agrega el siguiente código al 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"])}
Esta función se encarga de tomar el estado actual de la conversación, proporcionarle un mensaje del sistema al LLM y, luego, pedirle que genere una respuesta. El LLM puede responder directamente al usuario o elegir usar una de las herramientas disponibles.
tools : Esta lista representa el conjunto de herramientas que el agente tiene disponibles. Contiene tres funciones de herramientas que definimos en los pasos anteriores: get_curriculum
, search_latest_resource
y recommend_book
. llm.bind_tools(tools): "Vincula" la lista de herramientas al objeto llm. La vinculación de las herramientas le indica al LLM que estas herramientas están disponibles y le proporciona información sobre cómo usarlas (p.ej., los nombres de las herramientas, los parámetros que aceptan y lo que hacen).
Usaremos LangGraph para implementar la orquestación. 👉 Agrega el siguiente código al 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")
Explicación:
StateGraph(MessagesState)
: Crea un objetoStateGraph
. UnStateGraph
es un concepto fundamental en LangGraph. Representa el flujo de trabajo de tu agente como un gráfico, en el que cada nodo representa un paso del proceso. Piensa en ello como la definición del plan de cómo el agente razonará y actuará.- Eje condicional: El argumento
tools_condition
, que se origina en el nodo"determine_tool"
, es probablemente una función que determina qué borde seguir en función del resultado de la funcióndetermine_tool
. Los bordes condicionales permiten que el gráfico se ramifique en función de la decisión del LLM sobre qué herramienta usar (o si responder directamente al usuario). Aquí es donde entra en juego la "inteligencia" del agente: puede adaptar su comportamiento de forma dinámica según la situación. - Bucle: Agrega un borde al gráfico que conecta el nodo
"tools"
al nodo"determine_tool"
. Esto crea un bucle en el gráfico, lo que permite que el agente use herramientas de forma reiterada hasta que haya recopilado suficiente información para completar la tarea y proporcionar una respuesta satisfactoria. Este bucle es fundamental para las tareas complejas que requieren varios pasos de razonamiento y recopilación de información.
Ahora, probemos nuestro agente de planificador para ver cómo orquesta las diferentes herramientas.
Este código ejecutará la función prep_class con una entrada de usuario específica, simulando una solicitud para crear un plan de enseñanza de Matemáticas en Geometría para el 5° grado, con el plan de estudios, las recomendaciones de libros y los recursos de Internet más recientes.
Si cerraste la terminal o las variables de entorno ya no están configuradas, vuelve a ejecutar los siguientes comandos.
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"
👉 Ejecuta el código:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py
Observa el registro en la terminal. Deberías ver evidencia de que el agente llama a las tres herramientas (obtener el plan de estudios de la institución, obtener recomendaciones de libros y buscar los recursos más recientes) antes de proporcionar el plan de enseñanza final. Esto demuestra que la orquestación de LangGraph funciona correctamente y que el agente usa de forma inteligente todas las herramientas disponibles para entregar la solicitud del usuario.
================================ 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 detener la secuencia de comandos, presiona Ctrl+C
.
👉Ahora, reemplaza el código de prueba por una instrucción diferente, que requiere que se llamen a diferentes herramientas.
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")
Si cerraste la terminal o las variables de entorno ya no están configuradas, vuelve a ejecutar los siguientes comandos.
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"
👉Vuelve a ejecutar el código:
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py
¿Qué notaste esta vez? ¿A qué herramientas llamó el agente? Deberías ver que, esta vez, el agente solo llama a la herramienta search_latest_resource. Esto se debe a que la instrucción no especifica que necesita las otras dos herramientas, y nuestro LLM es lo suficientemente inteligente como para no llamar a las otras herramientas.
================================ 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 detener la secuencia de comandos, presiona Ctrl+C
. 👉Quita el código de prueba para mantener limpio tu archivo aidemy.py:
if __name__ == "__main__":
prep_class("I'm doing a course for year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")
Ahora que definimos la lógica de nuestro agente, iniciemos la aplicación web de Flask. Esto proporcionará una interfaz familiar basada en formularios para que los profesores interactúen con el agente. Si bien las interacciones con chatbots son comunes con los LLM, optamos por una IU de envío de formularios tradicional, ya que puede ser más intuitiva para muchos educadores.
Si cerraste la terminal o las variables de entorno ya no están configuradas, vuelve a ejecutar los siguientes comandos.
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"
👉Ahora, inicia la IU web.
cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py
Busca mensajes de inicio en el resultado de la terminal de Cloud Shell. Por lo general, Flask imprime mensajes que indican que se está ejecutando y en qué puerto.
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.
👉En el menú “Vista previa en la Web”, elige Vista previa en el puerto 8080. Cloud Shell abrirá una pestaña o ventana nueva del navegador con la vista previa web de tu aplicación.
En la interfaz de la solicitud, selecciona 5
para el año, selecciona el asunto Mathematics
y escribe Geometry
en la solicitud de complemento
En lugar de esperar la respuesta, cambia a la terminal del editor de Cloud. Puedes observar el progreso y cualquier mensaje de salida o error que genere tu función en la terminal del emulador. 😁
👉Para detener la secuencia de comandos, presiona Ctrl+C
en la terminal.
👉Sal del entorno virtual:
deactivate
8. Implementa el agente de planificador en la nube
Compila y envía la imagen al registro
👉 Es hora de implementarlo en la nube. En la terminal, crea un repositorio de artefactos para almacenar la imagen de Docker que compilaremos.
gcloud artifacts repositories create agent-repository \
--repository-format=docker \
--location=us-central1 \
--description="My agent repository"
Deberías ver el mensaje Se creó el repositorio [agent-repository].
👉Ejecuta el siguiente comando para compilar la imagen de Docker.
cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
👉 Debemos volver a etiquetar la imagen para que se aloje en Artifact Registry en lugar de GCR y enviar la imagen etiquetada a 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
Una vez que se complete el envío, podrás verificar que la imagen se almacene correctamente en Artifact Registry. Navega a Artifact Registry en la consola de Google Cloud. Deberías encontrar la imagen aidemy-planner
dentro del repositorio agent-repository
.
Protege las credenciales de la base de datos con Secret Manager
Para administrar y acceder de forma segura a las credenciales de la base de datos, usaremos Google Cloud Secret Manager. Esto evita la codificación de información sensible en el código de nuestra aplicación y mejora la seguridad.
👉Crearemos secretos individuales para el nombre de usuario, la contraseña y el nombre de la base de datos. Este enfoque nos permite administrar cada credencial de forma independiente. En la terminal, ejecuta lo siguiente:
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 Secret Manager es un paso importante para proteger tu aplicación y evitar la exposición accidental de credenciales sensibles. Sigue las prácticas recomendadas de seguridad para las implementaciones en la nube.
Implementa en Cloud Run
Cloud Run es una plataforma sin servidores completamente administrada que te permite implementar aplicaciones alojadas en contenedores de forma rápida y sencilla. Abstrae la administración de la infraestructura, lo que te permite enfocarte en escribir y, luego, implementar tu código. Implementaremos nuestro planificador como un servicio de Cloud Run.
👉En la consola de Google Cloud, navega a "Cloud Run". Haz clic en IMPLEMENTAR CONTENEDOR y selecciona SERVICIO. Configura tu servicio de Cloud Run:
- Imagen del contenedor: Haz clic en "Seleccionar" en el campo de URL. Busca la URL de la imagen que enviaste a Artifact Registry (p.ej., us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
- Nombre del servicio:
aidemy-planner
- Región: Selecciona la región
us-central1
. - Autenticación: Para los fines de este taller, puedes permitir "Permitir invocaciones no autenticadas". Para producción, es probable que desees restringir el acceso.
- Pestaña Contenedores (expande Contenedores y Red):
- Pestaña Configuración:
- Recurso
- memoria : 2 GB
- Recurso
- Pestaña Variables y Secrets:
- Variables de entorno:
- Agrega el nombre
GOOGLE_CLOUD_PROJECT
y el valor <YOUR_PROJECT_ID>. - Agrega el nombre
BOOK_PROVIDER_URL
y el valor <YOUR_BOOK_PROVIDER_FUNCTION_URL>.
- Agrega el nombre
- Secrets expuestos como variables de entorno:
- Agrega nombre:
DB_USER
, secreto: seleccionadb-user
y versión:latest
- Agrega nombre:
DB_PASS
, secreto: seleccionadb-pass
y versión:latest
- Agrega nombre:
DB_NAME
, secreto: seleccionadb-name
y versión:latest
- Agrega nombre:
- Variables de entorno:
- Pestaña Configuración:
Ejecuta el siguiente comando en la terminal si necesitas recuperar tu YOUR_BOOK_PROVIDER_FUNCTION_URL:
gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)"
Deja el resto de los parámetros de configuración predeterminados.
👉Haz clic en CREAR.
Cloud Run implementará tu servicio.
Una vez que se implemente, haz clic en el servicio para ir a su página de detalles. Allí, encontrarás la URL implementada disponible en la parte superior.
En la interfaz de la solicitud, selecciona 7
para el año, elige Mathematics
como asunto y, luego, ingresa Algebra
en el campo Solicitud de complemento. Esto le proporcionará al agente el contexto necesario para generar un plan de lecciones personalizado.
¡Felicitaciones! Creaste correctamente un plan de enseñanza con nuestro potente agente de IA. Esto demuestra el potencial de los agentes para reducir significativamente la carga de trabajo y optimizar las tareas, lo que, en última instancia, mejora la eficiencia y facilita la vida de los educadores.
9. Sistemas de múltiples agentes
Ahora que implementamos correctamente la herramienta de creación de planes de enseñanza, enfoquémonos en crear el portal para estudiantes. Este portal les brindará a los estudiantes acceso a cuestionarios, resúmenes de audio y tareas relacionadas con su trabajo académico. Dado el alcance de esta funcionalidad, aprovecharemos la potencia de los sistemas multiagente para crear una solución modular y escalable.
Como mencionamos anteriormente, en lugar de depender de un solo agente para controlar todo, un sistema de varios agentes nos permite dividir la carga de trabajo en tareas más pequeñas y especializadas, cada una de las cuales se controla con un agente dedicado. Este enfoque ofrece varias ventajas clave:
Modularidad y mantenibilidad: En lugar de crear un solo agente que haga todo, compila agentes más pequeños y especializados con responsabilidades bien definidas. Esta modularidad facilita la comprensión, el mantenimiento y la depuración del sistema. Cuando surge un problema, puedes aislarlo en un agente específico, en lugar de tener que analizar una base de código masiva.
Escalabilidad: La escalabilidad de un solo agente complejo puede ser un cuello de botella. Con un sistema de varios agentes, puedes escalar agentes individuales según sus necesidades específicas. Por ejemplo, si un agente controla un gran volumen de solicitudes, puedes crear más instancias de ese agente fácilmente sin afectar al resto del sistema.
Especialización del equipo: Piensa en lo siguiente: no le pedirías a un ingeniero que cree una aplicación completa desde cero. En su lugar, reúnes un equipo de especialistas, cada uno con experiencia en un área en particular. Del mismo modo, un sistema de varios agentes te permite aprovechar las fortalezas de diferentes LLM y herramientas, y asignarlas a los agentes que mejor se adapten a tareas específicas.
Desarrollo en paralelo: Los diferentes equipos pueden trabajar en diferentes agentes de forma simultánea, lo que acelera el proceso de desarrollo. Dado que los agentes son independientes, es menos probable que los cambios en uno afecten a otros.
Arquitectura basada en eventos
Para permitir una comunicación y coordinación eficaces entre estos agentes, emplearemos una arquitectura orientada a eventos. Esto significa que los agentes reaccionarán a los "eventos" que ocurren dentro del sistema.
Los agentes se suscriben a tipos de eventos específicos (p.ej., “se generó el plan de enseñanza”, “se creó la tarea”). Cuando ocurre un evento, se notifica a los agentes relevantes y pueden reaccionar según corresponda. Esta desvinculación promueve la flexibilidad, la escalabilidad y la capacidad de respuesta en tiempo real.
Ahora, para comenzar, necesitamos una forma de transmitir estos eventos. Para ello, configuraremos un tema de Pub/Sub. Comencemos por crear un tema llamado plan.
👉Ve a Pub/Sub de la consola de Google Cloud y haz clic en el botón "Crear tema".
👉Configura el tema con el ID o nombre plan
y desmarca Add a default subscription
, deja el resto como predeterminado y haz clic en Crear.
Se actualizará la página de Pub/Sub y deberías ver el tema que creaste recientemente en la tabla.
Ahora, integremos la funcionalidad de publicación de eventos de Pub/Sub en nuestro agente de planificador. Agregaremos una herramienta nueva que envíe un evento "plan" al tema de Pub/Sub que acabamos de crear. Este evento indicará a otros agentes del sistema (como los del portal para estudiantes) que hay un nuevo plan de enseñanza disponible.
👉Regresa al editor de Cloud Code y abre el archivo app.py
ubicado en la carpeta planner
. Agregaremos una función que publique el evento. Reemplaza lo siguiente:
##ADD SEND PLAN EVENT FUNCTION HERE
con
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: Esta función toma el plan de estudios generado como entrada, crea un cliente publicador de Pub/Sub, construye la ruta de acceso del tema, convierte el plan de estudios en una cadena JSON y publica el mensaje en el tema.
- Lista de herramientas: La función
send_plan_event
se agrega a la lista de herramientas, lo que la pone a disposición del agente para que la use.
En la misma carpeta, en el archivo app.py
, 👉actualiza la instrucción para indicarle al agente que envíe el evento del plan de estudios al tema de Pub/Sub después de generarlo. Reemplazar
### ADD send_plan_event CALL
con lo siguiente:
send_plan_event(teaching_plan)
Cuando agregamos la herramienta send_plan_event y modificamos la instrucción, habilitamos a nuestro agente de planificación para que publique eventos en Pub/Sub, lo que permite que otros componentes de nuestro sistema reaccionen a la creación de nuevos planes de enseñanza. En las siguientes secciones, tendremos un sistema multiagente funcional.
10. Empodera a los estudiantes con cuestionarios a pedido
Imagina un entorno de aprendizaje en el que los estudiantes tengan acceso a una oferta ilimitada de cuestionarios adaptados a sus planes de aprendizaje específicos. Estos cuestionarios proporcionan comentarios inmediatos, incluidas respuestas y explicaciones, lo que fomenta una comprensión más profunda del material. Este es el potencial que buscamos aprovechar con nuestro portal de cuestionarios potenciado por IA.
Para dar vida a esta visión, crearemos un componente de generación de cuestionarios que pueda crear preguntas de opción múltiple según el contenido del plan de enseñanza.
👉En el panel Explorador del editor de Cloud Code, navega a la carpeta portal
. Abre el archivo quiz.py
y copia y pega el siguiente código al final del archivo.
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
En el agente, crea un analizador de salida JSON diseñado específicamente para comprender y estructurar el resultado del LLM. Usa el modelo QuizQuestion
que definimos antes para garantizar que el resultado analizado cumpla con el formato correcto (pregunta, opciones y respuesta).
👉Ejecuta los siguientes comandos en la terminal para configurar un entorno virtual, instalar dependencias y, luego, iniciar el agente:
cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py
Usa la función de vista previa en la Web de Cloud Shell para acceder a la aplicación en ejecución. Haz clic en el vínculo “Cuestionarios”, ya sea en la barra de navegación superior o en la tarjeta de la página de índice. Deberías ver tres cuestionarios generados de forma aleatoria para el estudiante. Estos cuestionarios se basan en el plan de enseñanza y demuestran el poder de nuestro sistema de generación de cuestionarios impulsado por IA.
Para detener el proceso que se ejecuta de forma local, presiona Ctrl+C
en la terminal.
Pensamiento de Gemini 2 para las explicaciones
Bien, tenemos cuestionarios, lo cual es un buen comienzo. Pero ¿qué sucede si los estudiantes se equivocan? Ahí es donde ocurre el verdadero aprendizaje, ¿no? Si podemos explicar por qué su respuesta estaba incorrecta y cómo llegar a la correcta, es mucho más probable que la recuerde. Además, ayuda a aclarar cualquier confusión y aumentar su confianza.
Por eso, vamos a recurrir a lo mejor: el modelo de "pensamiento" de Gemini 2. Piensa en ello como darle a la IA un poco más de tiempo para pensar antes de explicar. Te permite proporcionar comentarios más detallados y mejores.
Queremos ver si puede ayudar a los estudiantes con asistencia, respuestas y explicaciones detalladas. Para probarlo, comenzaremos con un tema muy difícil, el cálculo.
👉Primero, ve al editor de código de Cloud, en answer.py
dentro de la carpeta portal
reemplaza
def answer_thinking(question, options, user_response, answer, region):
return ""
con el siguiente fragmento 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)
Esta es una app de cadena de lenguaje muy simple en la que se inicializa el modelo de Gemini 2 Flash, en el que le indicamos que actúe como un profesor útil y proporcione explicaciones.
👉Ejecuta el siguiente comando en la terminal:
cd ~/aidemy-bootstrap/portal/
python answer.py
Deberías ver un resultado similar al ejemplo que se proporciona en las instrucciones originales. Es posible que el modelo actual no proporcione una explicación tan detallada.
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!
En el archivo answer.py, reemplaza el nombre del modelo de gemini-2.0-flash-001
a gemini-2.0-flash-thinking-exp-01-21
en la función answer_thinking.
Esto cambia el LLM que razona más, lo que lo ayudará a generar mejores explicaciones. Vuelve a ejecutarlo.
👉Ejecuta para probar el nuevo modelo de pensamiento:
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py
Este es un ejemplo de la respuesta del modelo de pensamiento que es mucho más detallada y proporciona una explicación paso a paso de cómo resolver el problema de cálculo. Esto destaca el poder de los modelos de "pensamiento" para generar explicaciones de alta calidad. Deberías ver un resultado similar a esto:
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.
👉QUITA el siguiente código de prueba 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)
👉Ejecuta los siguientes comandos en la terminal para configurar un entorno virtual, instalar dependencias y, luego, iniciar el agente:
cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py
👉Usa la función de vista previa en la Web de Cloud Shell para acceder a la aplicación en ejecución. Haz clic en el vínculo “Cuestionarios”, responde todos los cuestionarios y asegúrate de responder al menos una pregunta de forma incorrecta. Luego, haz clic en Enviar.
En lugar de esperar la respuesta, cambia a la terminal del editor de Cloud. Puedes observar el progreso y cualquier mensaje de salida o error que genere tu función en la terminal del emulador. 😁
Para detener el proceso que se ejecuta de forma local, presiona Ctrl+C
en la terminal.
11. Cómo organizar los agentes con Eventarc
Hasta ahora, el portal para estudiantes ha estado generando cuestionarios basados en un conjunto predeterminado de planes de enseñanza. Eso es útil, pero significa que nuestro agente de planificación y el agente de cuestionarios del portal no se están comunicando entre sí. ¿Recuerdas cómo agregamos esa función en la que el agente de planificación publica sus planes de enseñanza recién generados en un tema de Pub/Sub? Ahora es el momento de conectarlo a nuestro agente de portal.
Queremos que el portal actualice automáticamente el contenido de los cuestionarios cada vez que se genere un nuevo plan de enseñanza. Para ello, crearemos un extremo en el portal que pueda recibir estos planes nuevos.
👉En el panel Explorador del editor de código de Cloud, navega a la carpeta portal
. Abre el archivo app.py
para editarlo. Agrega el siguiente código entre ## Add your code here:
## Add your code here
@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
try:
# Get data from Pub/Sub message delivered via Eventarc
envelope = request.get_json()
if not envelope:
return jsonify({'error': 'No Pub/Sub message received'}), 400
if not isinstance(envelope, dict) or 'message' not in envelope:
return jsonify({'error': 'Invalid Pub/Sub message format'}), 400
pubsub_message = envelope['message']
print(f"data: {pubsub_message['data']}")
data = pubsub_message['data']
data_str = base64.b64decode(data).decode('utf-8')
data = json.loads(data_str)
teaching_plan = data['teaching_plan']
print(f"File content: {teaching_plan}")
with open("teaching_plan.txt", "w") as f:
f.write(teaching_plan)
print(f"Teaching plan saved to local file: teaching_plan.txt")
return jsonify({'message': 'File processed successfully'})
except Exception as e:
print(f"Error processing file: {e}")
return jsonify({'error': 'Error processing file'}), 500
## Add your code here
Cómo volver a compilar y, luego, implementar en Cloud Run
De acuerdo. Deberás actualizar y volver a implementar nuestros agentes de planificador y portal en Cloud Run. Esto garantiza que tengan el código más reciente y que estén configurados para comunicarse a través de eventos.
👉 A continuación, volveremos a compilar y enviaremos la imagen del agente planner en la ejecución de la 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
👉Haremos lo mismo, compilaremos y enviaremos la imagen del agente del 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
En Artifact Registry, deberías ver las imágenes de contenedores aidemy-planner
y aidemy-portal
en la lista.
👉En la terminal, ejecuta este comando para actualizar la imagen de Cloud Run del agente de planificador:
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
Deberías ver un resultado similar a esto:
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
Anota la URL del servicio, que es el vínculo a tu agente de planificador implementado.
👉Ejecuta este comando para crear la instancia de Cloud Run del agente de 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}
Deberías ver un resultado similar a esto:
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
Anota la URL del servicio, que es el vínculo a tu portal para estudiantes implementado.
Cómo crear el activador de Eventarc
Pero aquí está la gran pregunta: ¿cómo se notifica este extremo cuando hay un plan nuevo esperando en el tema de Pub/Sub? Ahí es donde Eventarc entra en acción para salvar el día.
Eventarc actúa como un puente, escucha eventos específicos (como un mensaje nuevo que llega a nuestro tema de Pub/Sub) y activa acciones automáticamente en respuesta. En nuestro caso, detectará cuando se publique un nuevo plan de enseñanza y, luego, enviará un indicador al extremo de nuestro portal para informarle que es hora de actualizarlo.
Con Eventarc manejando la comunicación basada en eventos, podemos conectar sin problemas nuestro agente de planificador y nuestro agente de portal, lo que crea un sistema de aprendizaje verdaderamente dinámico y responsivo. Es como tener un mensajero inteligente que entrega automáticamente los planes de lecciones más recientes al lugar correcto.
👉En la consola, ve a Eventarc.
👉Haz clic en el botón “+ CREATE TRIGGER”.
Configura el activador (conceptos básicos):
- Nombre del activador:
plan-topic-trigger
- Tipos de activadores: Fuentes de Google
- Proveedor de eventos: Cloud Pub/Sub
- Tipo de evento:
google.cloud.pubsub.topic.v1.messagePublished
- Región:
us-central1
. - Tema de Cloud Pub/Sub : Selecciona
plan
. - OTORGA a la cuenta de servicio el rol
roles/iam.serviceAccountTokenCreator
. - Destino del evento: Cloud Run
- Servicio de Cloud Run: aidemy-portal
- Ruta de URL del servicio:
/new_teaching_plan
- Ignora el mensaje (se denegó el permiso en "locations/me-central2" (o es posible que no exista)).
Haz clic en “Crear”.
Se actualizará la página Activadores de Eventarc y deberías ver el activador que acabas de crear en la tabla.
👉Ahora, accede al planificador y solicita un nuevo plan de enseñanza. Esta vez, prueba el año 5
, el asunto science
con la solicitud Add-no atoms
.
Ejecuta este comando en la terminal si olvidas la ubicación de tu agente de planificación.
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Luego, espera uno o dos minutos. Una vez más, esta demora se debe a la limitación de facturación de este lab. En condiciones normales, no debería haber demoras.
Por último, accede al portal para estudiantes. Deberías ver que los cuestionarios se actualizaron y ahora se alinean con el nuevo plan de enseñanza que acabas de generar. Esto demuestra la integración correcta de Eventarc en el sistema de Aidemy.
Ejecuta este comando en la terminal si olvidas la ubicación de tu agente de portal.
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
¡Felicitaciones! Compilaste correctamente un sistema multiagente en Google Cloud, aprovechando la arquitectura orientada a eventos para mejorar la escalabilidad y la flexibilidad. Ya tienes una base sólida, pero hay mucho más por explorar. Para profundizar en los beneficios reales de esta arquitectura, descubrir el poder de la API de contenido en vivo multimodal de Gemini 2 y aprender a implementar la orquestación de una sola ruta con LangGraph, no dudes en continuar con los siguientes dos capítulos.
12. OPCIONAL: Audio Recaps con Gemini
Gemini puede comprender y procesar información de varias fuentes, como texto, imágenes y hasta audio, lo que abre un nuevo abanico de posibilidades para el aprendizaje y la creación de contenido. La capacidad de Gemini de "ver", "escuchar" y "leer" realmente desbloquea experiencias del usuario creativas y atractivas.
Además de crear imágenes o texto, otro paso importante en el aprendizaje es hacer resúmenes y recapitulaciones eficaces. Piénsalo: ¿con qué frecuencia recuerdas la letra pegadiza de una canción con más facilidad que algo que leíste en un libro de texto? El sonido puede ser increíblemente memorable. Por eso, aprovecharemos las capacidades multimodales de Gemini para generar resúmenes de audio de nuestros planes de enseñanza. Esto les brindará a los estudiantes una forma conveniente y atractiva de revisar el material, lo que podría mejorar la retención y la comprensión a través del poder del aprendizaje auditivo.
Necesitamos un lugar para almacenar los archivos de audio generados. Cloud Storage proporciona una solución escalable y confiable.
👉Ve a Almacenamiento en la consola. Haz clic en "Buckets" en el menú de la izquierda. Haz clic en el botón "+ CREAR" en la parte superior.
👉Configura tu bucket:
- nombre del bucket: aidemy-recap-<UNIQUE_NAME> IMPORTANTE: Asegúrate de definir un nombre de bucket único que comience con "aidemy-recap-". Este nombre único es fundamental para evitar conflictos de nombres cuando crees tu bucket de Cloud Storage.
- región:
us-central1
. - Clase de almacenamiento: "Estándar". Standard es adecuada para los datos a los que se accede con frecuencia.
- Control de acceso: Deja seleccionada la opción predeterminada "Control de acceso uniforme". Esto proporciona un control de acceso coherente a nivel del bucket.
- Opciones avanzadas: Para este taller, suele ser suficiente la configuración predeterminada. Haz clic en el botón CREAR para crear el bucket.
Es posible que veas una ventana emergente sobre la prevención del acceso público. Deja la casilla marcada y haz clic en Confirm
.
Ahora verás el bucket que acabas de crear en la lista Buckets. Recuerda el nombre del bucket, ya que lo necesitarás más adelante.
👉En la terminal del editor de código de Cloud, ejecuta los siguientes comandos para otorgarle acceso al bucket a la cuenta de servicio:
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"
👉En el editor de Cloud Code, abre audio.py
dentro de la carpeta course
. Pega el siguiente código al final del archivo:
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))
- Conexión de transmisión: Primero, se establece una conexión persistente con el extremo de la API de Live. A diferencia de una llamada a la API estándar en la que envías una solicitud y obtienes una respuesta, esta conexión permanece abierta para un intercambio continuo de datos.
- Configuración multimodal: Usa la configuración para especificar el tipo de resultado que deseas (en este caso, audio) y hasta puedes especificar los parámetros que deseas usar (p. ej., selección de voz, codificación de audio).
- Proceso asíncrono: Esta API funciona de forma asíncrona, lo que significa que no bloquea el subproceso principal mientras espera que se complete la generación de audio. Dado que procesa los datos en tiempo real y envía el resultado en fragmentos, proporciona una experiencia casi instantánea.
Ahora, la pregunta clave es: ¿cuándo se debe ejecutar este proceso de generación de audio? Lo ideal es que los resúmenes de audio estén disponibles en cuanto se cree un nuevo plan de enseñanza. Dado que ya implementamos una arquitectura basada en eventos cuando publicamos el plan de estudios en un tema de Pub/Sub, simplemente podemos suscribirnos a ese tema.
Sin embargo, no generamos planes de enseñanza nuevos con mucha frecuencia. No sería eficiente tener un agente que se ejecute constantemente y espere planes nuevos. Por eso, tiene mucho sentido implementar esta lógica de generación de audio como una función de Cloud Run.
Cuando se implementa como una función, permanece inactivo hasta que se publica un mensaje nuevo en el tema de Pub/Sub. Cuando eso sucede, se activa automáticamente la función, que genera los resúmenes de audio y los almacena en nuestro bucket.
👉 En la carpeta course
del archivo main.py
, se define la función de Cloud Run que se activará cuando haya un nuevo plan de estudios disponible. Recibe el plan y, luego, inicia la generación del resumen de audio. Agrega el siguiente fragmento de código al final del archivo.
@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: Este decorador marca la función como una función de Cloud Run que activará CloudEvents.
Pruebas locales
👉 Ejecutaremos esto en un entorno virtual e instalaremos las bibliotecas de Python necesarias para la función de 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
👉El emulador de funciones de Cloud Run nos permite probar nuestra función de forma local antes de implementarla en Google Cloud. Para iniciar un emulador local, ejecuta lo siguiente:
functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py
👉Mientras el emulador se está ejecutando, puedes enviar CloudEvents de prueba para simular que se publica un nuevo plan de enseñanza. En una terminal nueva, haz lo siguiente:
👉Run:
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=="
}
}'
En lugar de esperar la respuesta, cambia a la otra terminal de Cloud Shell. Puedes observar el progreso y cualquier mensaje de salida o error que genere tu función en la terminal del emulador. 😁
En la segunda terminal, deberías ver que se muestra OK
.
👉Verifica los datos en el bucket, ve a Cloud Storage y selecciona la pestaña “Bucket” y, luego, aidemy-recap-xxx
.
👉En la terminal que ejecuta el emulador, escribe ctrl+c
para salir. Cierra la segunda terminal. Cierra la segunda terminal y ejecuta deactivate para salir del entorno virtual.
deactivate
Implementación en Google Cloud
👉Después de realizar pruebas de forma local, es hora de implementar el agente del curso en Google Cloud. En la terminal, ejecuta estos 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
Para verificar la implementación, ve a Cloud Run en la consola de Google Cloud.Deberías ver un servicio nuevo llamado courses-agent.
Para verificar la configuración del activador, haz clic en el servicio courses-agent para ver sus detalles. Ve a la pestaña "ACTIVATORES".
Deberías ver un activador configurado para escuchar los mensajes publicados en el tema del plan.
Por último, veamos cómo se ejecuta de extremo a extremo.
👉 A continuación, debemos configurar el agente de portal para que sepa dónde encontrar los archivos de audio generados. En la terminal, ejecuta lo siguiente:
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
👉Intenta generar un nuevo plan de enseñanza en la página del agente de planificación. Es posible que tarde unos minutos en iniciarse. No te alarmes, es un servicio sin servidores. Obtén la URL de tu agente de planificación (si no la tienes a mano, ejecuta este comando en la terminal):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Después de generar el plan nuevo, espera entre 2 y 3 minutos para que se genere el audio. Una vez más, esto tardará unos minutos más debido a la limitación de facturación de esta cuenta de lab.
Para supervisar si la función courses-agent
recibió el plan de estudios, consulta la pestaña “ACTIVATORES” de la función. Actualiza la página periódicamente. Con el tiempo, deberías ver que se invocó la función. Si la función no se invocó después de más de 2 minutos, puedes intentar volver a generar el plan de enseñanza. Sin embargo, evita generar planes de forma repetida en rápida sucesión, ya que el agente consumirá y procesará cada plan generado de forma secuencial, lo que podría crear un retraso.
👉Visita el portal y haz clic en “Cursos”. Deberías ver tres tarjetas, cada una con un resumen de audio. Para encontrar la URL de tu agente de portal, sigue estos pasos:
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
Haz clic en “Reproducir” en cada curso para asegurarte de que los resúmenes de audio estén alineados con el plan de enseñanza que acabas de generar.
Sal del entorno virtual.
deactivate
13. OPCIONAL: Colaboración basada en roles con Gemini y DeepSeek
Tener varias perspectivas es invaluable, especialmente cuando se crean tareas atractivas y reflexivas. Ahora, crearemos un sistema multiagente que aproveche dos modelos diferentes con roles distintos para generar tareas: uno promueve la colaboración y el otro fomenta el autoaprendizaje. Usaremos una arquitectura "única", en la que el flujo de trabajo sigue una ruta fija.
Generador de tareas de Gemini
Comenzaremos por configurar la función Gemini para generar tareas con un énfasis colaborativo. Edita el archivo
gemini.py
ubicado en la carpeta assignment
.
👉Pega el siguiente código al final del archivo 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()
Usa el modelo de Gemini para generar tareas.
Ya está todo listo para probar el agente de Gemini.
👉Ejecuta estos comandos en la terminal para configurar el entorno:
cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt
👉 Puedes ejecutarlo para probarlo:
python gemini.py
Deberías ver una tarea que tenga más trabajo en grupo en el resultado. La prueba de aserción al final también mostrará los 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.
....
Detén con ctl+c
y limpia el código de prueba. QUITA el siguiente código de 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()
Configura el generador de tareas de DeepSeek
Si bien las plataformas de IA basadas en la nube son convenientes, los LLMs autoalojados pueden ser fundamentales para proteger la privacidad de los datos y garantizar la soberanía de los datos. Implementaremos el modelo de DeepSeek más pequeño (1,500 millones de parámetros) en una instancia de Compute Engine de Cloud. Hay otras formas, como alojarlo en la plataforma de Vertex AI de Google o en tu instancia de GKE, pero como este es solo un taller sobre agentes de IA y no quiero que te quedes aquí para siempre, usemos la forma más sencilla. Sin embargo, si te interesa y quieres analizar otras opciones, consulta el archivo deepseek-vertexai.py
en la carpeta de tareas, donde se proporciona un código de muestra para interactuar con los modelos implementados en VertexAI.
👉Ejecuta este comando en la terminal para crear una plataforma de 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 que la instancia de Compute Engine se esté ejecutando, haz lo siguiente:
Navega a Compute Engine > "Instancias de VM" en la consola de Google Cloud. Deberías ver el ollama-instance
en la lista con una marca de verificación verde que indica que se está ejecutando. Si no la ves, asegúrate de que la zona sea us-central1. Si no es así, es posible que debas buscarlo.
👉Instalaremos el modelo de DeepSeek más pequeño y lo probaremos. En el editor de Cloud Shell, en una terminal Nueva, ejecuta el siguiente comando para establecer una conexión SSH a la instancia de GCE.
gcloud compute ssh ollama-instance --zone=us-central1-a
Cuando establezcas la conexión SSH, es posible que se te solicite lo siguiente:
"Do you want to continue (Y/n)?"
Simplemente escribe Y
(sin distinción entre mayúsculas y minúsculas) y presiona Intro para continuar.
A continuación, es posible que se te solicite crear una frase de contraseña para la clave SSH. Si prefieres no usar una frase de contraseña, presiona Intro dos veces para aceptar la configuración predeterminada (sin frase de contraseña).
👉Ahora que estás en la máquina virtual, extrae el modelo más pequeño de DeepSeek R1 y prueba si funciona.
ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"
👉Sal de la instancia de GCE y, luego, ingresa lo siguiente en la terminal de SSH:
exit
Cierra la terminal nueva y vuelve a la terminal original.
👉Y no olvides configurar la política de red para que otros servicios puedan acceder al LLM. Limita el acceso a la instancia si quieres hacerlo para producción, ya sea implementando el acceso de seguridad para el servicio o restringiendo el acceso de IP. Ejecuta lo siguiente:
gcloud compute firewall-rules create allow-ollama-11434 \
--allow=tcp:11434 \
--target-tags=ollama \
--description="Allow access to Ollama on port 11434"
👉Para verificar si tu política de firewall funciona correctamente, intenta ejecutar lo siguiente:
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
}'
A continuación, trabajaremos en la función Deepseek en el agente de tareas para generar tareas con énfasis en el trabajo individual.
👉Edita deepseek.py
en la carpeta assignment
y agrega el siguiente fragmento al 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()
👉Probemos esto ejecutando el siguiente comando:
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
Deberías ver una tarea que tenga más trabajo de autoaprendizaje.
**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.
....
👉Detén el ctl+c
y limpia el código de prueba. QUITA el siguiente código de 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()
Ahora, usaremos el mismo modelo de Gemini para combinar ambas tareas en una nueva. Edita el archivo gemini.py
ubicado en la carpeta assignment
.
👉Pega el siguiente código al final del archivo 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 las fortalezas de ambos modelos, organizaremos un flujo de trabajo definido con LangGraph. Este flujo de trabajo consta de tres pasos: primero, el modelo de Gemini genera una tarea enfocada en la colaboración; segundo, el modelo de DeepSeek genera una tarea que hace hincapié en el trabajo individual; por último, Gemini sintetiza estas dos tareas en una sola tarea integral. Debido a que definimos previamente la secuencia de pasos sin la toma de decisiones de LLM, esto constituye una orquestación de una sola ruta definida por el usuario.
👉Pega el siguiente código al final del archivo main.py
en la carpeta 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 probar inicialmente la función create_assignment
y confirmar que el flujo de trabajo que combina Gemini y DeepSeek sea funcional, ejecuta el siguiente comando:
cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py
Deberías ver algo que combine ambos modelos con su perspectiva individual para el estudio de los estudiantes y también para los trabajos grupales.
**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
👉Detén el ctl+c
y limpia el código de prueba. QUITA el siguiente código de 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 que el proceso de generación de tareas sea automático y responsivo a los nuevos planes de enseñanza, aprovecharemos la arquitectura existente basada en eventos. En el siguiente código, se define una función de Cloud Run (generate_assignment) que se activará cada vez que se publique un nuevo plan de enseñanza en el tema de Pub/Sub "plan".
👉Agrega el siguiente código al 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
Pruebas locales
Antes de implementar la función en Google Cloud, te recomendamos que la pruebes de forma local. Esto permite iterar más rápido y depurar con mayor facilidad.
Primero, crea un bucket de Cloud Storage para almacenar los archivos de tareas generados y otorga acceso al bucket a la cuenta de servicio. Ejecuta los siguientes comandos en la terminal:
👉IMPORTANTE: Asegúrate de definir un nombre único para ASSIGNMENT_BUCKET que comience con "aidemy-assignment-". Este nombre único es fundamental para evitar conflictos de nombres cuando crees tu bucket de Cloud Storage. (reemplaza <YOUR_NAME> por cualquier palabra aleatoria)
export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue
👉Ejecuta el siguiente comando:
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"
👉Ahora, inicia el emulador de Cloud Run Function:
cd ~/aidemy-bootstrap/assignment
functions-framework --target generate_assignment --signature-type=cloudevent --source main.py
👉Mientras el emulador se ejecuta en una terminal, abre una segunda terminal en Cloud Shell. En esta segunda terminal, envía un CloudEvent de prueba al emulador para simular que se publica un nuevo plan de estudios:
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=="
}
}'
En lugar de esperar la respuesta, cambia a la otra terminal de Cloud Shell. Puedes observar el progreso y cualquier resultado o mensaje de error que genere tu función en la terminal del emulador. 😁
Se debería mostrar OK.
Para confirmar que la tarea se generó y almacenó correctamente, ve a la consola de Google Cloud y navega a Almacenamiento > "Cloud Storage". Selecciona el bucket de aidemy-assignment
que creaste. Deberías ver un archivo de texto llamado assignment-{random number}.txt
en el bucket. Haz clic en el archivo para descargarlo y verificar su contenido. Esto verifica que un archivo nuevo contenga la tarea nueva que se acaba de generar.
👉En la terminal que ejecuta el emulador, escribe ctrl+c
para salir. Cierra la segunda terminal. 👉Además, en la terminal que ejecuta el emulador, sal del entorno virtual.
deactivate
👉A continuación, implementaremos el agente de asignación en la nube.
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
Para verificar la implementación, ve a la consola de Google Cloud y navega a Cloud Run.Deberías ver un servicio nuevo llamado courses-agent.
Ahora que el flujo de trabajo de generación de tareas se implementó, probó y, luego, implementó, podemos pasar al siguiente paso: permitir el acceso a estas tareas desde el portal para estudiantes.
14. OPCIONAL: Colaboración basada en roles con Gemini y DeepSeek (continuación)
Generación de sitios web dinámicos
Para mejorar el portal para estudiantes y hacerlo más atractivo, implementaremos la generación de HTML dinámico para las páginas de tareas. El objetivo es actualizar automáticamente el portal con un diseño nuevo y atractivo visualmente cada vez que se genera una tarea nueva. Esto aprovecha las capacidades de codificación del LLM para crear una experiencia del usuario más dinámica e interesante.
👉En el editor de Cloud Shell, edita el archivo render.py
dentro de la carpeta portal
y reemplaza
def render_assignment_page():
return ""
con el siguiente fragmento 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)}"
Usa el modelo de Gemini para generar HTML de forma dinámica para la tarea. Toma el contenido de la tarea como entrada y usa una instrucción para indicarle a Gemini que cree una página HTML atractiva a la vista con un estilo creativo.
A continuación, crearemos un extremo que se activará cada vez que se agregue un documento nuevo al bucket de tareas:
👉 Dentro de la carpeta del portal, edita el archivo app.py
y agrega el siguiente código dentro de ## Add your code here" comments
, DESPUÉS de la función 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
Cuando se activa, recupera el nombre del archivo y el nombre del bucket de los datos de la solicitud, descarga el contenido de la tarea desde Cloud Storage y llama a la función render_assignment_page
para generar el HTML.
👉 Ejecutaremos la prueba de forma local:
cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py
👉En el menú "Vista previa en la Web", en la parte superior de la ventana de Cloud Shell, selecciona "Vista previa en el puerto 8080". Se abrirá tu aplicación en una pestaña nueva del navegador. Navega al vínculo Tarea en la barra de navegación. En este punto, deberías ver una página en blanco, lo cual es un comportamiento esperado, ya que aún no establecimos el puente de comunicación entre el agente de tareas y el portal para propagar el contenido de forma dinámica.
👉Para incorporar estos cambios y, luego, implementar el código actualizado, vuelve a compilar y envía la imagen del agente del 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
👉Después de enviar la imagen nueva, vuelve a implementar el servicio de Cloud Run. Ejecuta la siguiente secuencia de comandos para aplicar la actualización de 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
👉Ahora, implementaremos un activador de Eventarc que detecte cualquier objeto nuevo creado (finalizado) en el bucket de tareas. Este activador invocará automáticamente el extremo /render_assignment en el servicio del portal cuando se cree un archivo de tarea nuevo.
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 que el activador se haya creado correctamente, navega a la página Activadores de Eventarc en la consola de Google Cloud. Deberías ver portal-assignment-trigger
en la tabla. Haz clic en el nombre del activador para ver sus detalles.
El activador nuevo puede tardar entre 2 y 3 minutos en activarse.
Para ver la generación de tareas dinámicas en acción, ejecuta el siguiente comando para encontrar la URL de tu agente de planificador (si no la tienes a mano):
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner
Busca la URL de tu agente de portal:
gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal
En el agente de planificación, genera un plan de enseñanza nuevo.
Después de unos minutos (para permitir que se completen la generación de audio, la generación de tareas y la renderización de HTML), navega al portal para estudiantes.
👉Haz clic en el vínculo “Tarea” en la barra de navegación. Deberías ver una tarea recién creada con un HTML generado de forma dinámica. Cada vez que se genera un plan de enseñanza, debe ser una tarea dinámica.
¡Felicitaciones por completar el sistema multiagente de Aidemy! Ganaste experiencia práctica y estadísticas valiosas sobre lo siguiente:
- Los beneficios de los sistemas multiagente, como la modularidad, la escalabilidad, la especialización y el mantenimiento simplificado.
- La importancia de las arquitecturas basadas en eventos para compilar aplicaciones responsivas y de acoplamiento bajo
- El uso estratégico de los LLM, que consiste en hacer coincidir el modelo adecuado con la tarea y, luego, integrarlos con herramientas para generar un impacto en el mundo real.
- Prácticas de desarrollo nativas de la nube con los servicios de Google Cloud para crear soluciones escalables y confiables
- La importancia de considerar la privacidad de los datos y los modelos de autoalojamiento como una alternativa a las soluciones de los proveedores
Ahora tienes una base sólida para crear aplicaciones sofisticadas impulsadas por IA en Google Cloud.
15. Desafíos y próximos pasos
¡Felicitaciones por crear el sistema multiagente de Aidemy! Creaste una base sólida para la educación potenciada por IA. Ahora, consideremos algunos desafíos y posibles mejoras futuras para expandir aún más sus capacidades y abordar las necesidades del mundo real:
Aprendizaje interactivo con sesiones de preguntas y respuestas en vivo:
- Desafío: ¿Puedes aprovechar la API de Live de Gemini 2 para crear una función de preguntas y respuestas en tiempo real para los estudiantes? Imagina un aula virtual en la que los estudiantes puedan hacer preguntas y recibir respuestas inmediatas potenciadas por IA.
Envío y calificación de tareas automatizados:
- Desafío: Diseña e implementa un sistema que permita a los estudiantes enviar tareas de forma digital y que la IA las califique automáticamente, con un mecanismo para detectar y evitar el plagio. Este desafío presenta una gran oportunidad para explorar la generación aumentada de recuperación (RAG) para mejorar la precisión y confiabilidad de los procesos de calificación y detección de plagio.
16. Limpia
Ahora que compilamos y exploramos nuestro sistema multiagente de Aidemy, es hora de limpiar nuestro entorno de Google Cloud.
- Borra servicios de 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
- Borra el activador de 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
- Borrar tema de Pub/Sub
gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet
- Borra una instancia de Cloud SQL
gcloud sql instances delete aidemy --quiet
- Borra el repositorio de Artifact Registry
gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet
- Borra secretos de Secret Manager
gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet
- Borra la instancia de Compute Engine (si se creó para Deepseek)
gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet
- Borra la regla de firewall de la instancia de Deepseek
gcloud compute firewall-rules delete allow-ollama-11434 --quiet
- Borra depósitos de 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