Aidemy: создание многоагентных систем с помощью LangGraph, EDA и генеративного искусственного интеллекта в Google Cloud,Aidemy: создание многоагентных систем с помощью LangGraph, EDA и генеративного искусственного интеллекта в Google Cloud

1. Введение

Привет! Итак, вам нравится идея агентов – маленьких помощников, которые могут сделать все за вас, даже не пошевелив пальцем, верно? Потрясающий! Но давайте будем честными: один агент не всегда справится, особенно когда вы занимаетесь более крупными и сложными проектами. Вам, вероятно, понадобится целая команда из них! Вот тут-то и приходят на помощь мультиагентные системы.

Агенты, работающие на базе LLM, дают вам невероятную гибкость по сравнению с жестким программированием старой школы. Но, и всегда есть «но», они сопряжены со своими собственными непростыми задачами. И это именно то, во что мы собираемся погрузиться на этом семинаре!

заголовок

Вот что вы можете узнать – думайте об этом как о повышении уровня вашей агентской игры:

Создание вашего первого агента с помощью LangGraph . Мы запачкаем руки, создав ваш собственный агент, используя LangGraph, популярную среду. Вы узнаете, как создавать инструменты, подключающиеся к базам данных, использовать новейший API Gemini 2 для поиска в Интернете, а также оптимизировать подсказки и ответы, чтобы ваш агент мог взаимодействовать не только с LLM, но и с существующими службами. Мы также покажем вам, как работает вызов функций.

Оркестрация агентов по-вашему : мы рассмотрим различные способы координации ваших агентов: от простых прямых путей до более сложных сценариев с несколькими путями. Думайте об этом как о направлении потока вашей агентской команды.

Мультиагентные системы : вы узнаете, как настроить систему, в которой ваши агенты смогут сотрудничать и решать задачи вместе — и все это благодаря архитектуре, управляемой событиями.

Свобода LLM – используйте лучшее для работы: мы не зацикливаемся на одном LLM! Вы увидите, как использовать несколько LLM, назначая им разные роли, чтобы повысить эффективность решения проблем, используя крутые «модели мышления».

Динамический контент? Без проблем! : Представьте, что ваш агент в режиме реального времени создает динамический контент, адаптированный специально для каждого пользователя. Мы покажем вам, как это сделать!

Перенесите его в облако с помощью Google Cloud . Забудьте о том, чтобы просто играть в блокноте. Мы покажем вам, как спроектировать и развернуть вашу мультиагентную систему в Google Cloud, чтобы она была готова к работе в реальном мире!

Этот проект станет хорошим примером того, как использовать все методы, о которых мы говорили.

2. Архитектура

Быть учителем или работать в сфере образования может быть очень полезно, но давайте посмотрим правде в глаза: рабочая нагрузка, особенно вся подготовительная работа, может быть сложной! Кроме того, часто не хватает персонала, а обучение может быть дорогим. Вот почему мы предлагаем помощника преподавателя на базе искусственного интеллекта. Этот инструмент может облегчить нагрузку на преподавателей и помочь преодолеть разрыв, вызванный нехваткой персонала и отсутствием доступного репетиторства.

Наш помощник преподавателя с искусственным интеллектом может составить подробные планы уроков, веселые викторины, простые для понимания аудиозаписи и персонализированные задания. Это позволяет учителям сосредоточиться на том, что они делают лучше всего: общении с учениками и помощи им влюбиться в учебу.

В системе есть два сайта: один для учителей, где они могут создавать планы уроков на предстоящие недели,

Планировщик

и один для студентов, чтобы получить доступ к викторинам, аудиозаписям и заданиям. Портал

Хорошо, давайте пройдемся по архитектуре, лежащей в основе нашего помощника преподавателя Эйдеми. Как видите, мы разбили его на несколько ключевых компонентов, которые работают вместе, чтобы это произошло.

Архитектура

Ключевые архитектурные элементы и технологии :

Google Cloud Platform (GCP) : Центральное место во всей системе:

  • Vertex AI: доступ к программам LLM Gemini от Google.
  • Cloud Run: бессерверная платформа для развертывания контейнерных агентов и функций.
  • Cloud SQL: база данных PostgreSQL для данных учебного плана.
  • Pub/Sub и Eventarc: основа архитектуры, управляемой событиями, обеспечивающая асинхронную связь между компонентами.
  • Облачное хранилище: хранит аудиозаписи и файлы заданий.
  • Секретный менеджер: безопасно управляет учетными данными базы данных.
  • Реестр артефактов: хранит образы Docker для агентов.
  • Compute Engine: для развертывания LLM на собственном хостинге вместо того, чтобы полагаться на решения поставщиков.

LLM : «мозги» системы:

  • Модели Google Gemini: (Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking, Gemini 1.5-pro). Используются для планирования уроков, создания контента, создания динамического HTML, объяснения викторин и объединения заданий.
  • DeepSeek: используется для специализированных задач по созданию заданий для самостоятельного обучения.

LangChain и LangGraph : платформы для разработки приложений LLM

  • Облегчает создание сложных многоагентных рабочих процессов.
  • Обеспечивает интеллектуальную оркестровку инструментов (вызовы API, запросы к базе данных, веб-поиск).
  • Реализует управляемую событиями архитектуру для масштабируемости и гибкости системы.

По сути, наша архитектура сочетает в себе возможности LLM со структурированными данными и связью, управляемой событиями, и все это работает в Google Cloud. Это позволяет нам создать масштабируемого, надежного и эффективного помощника преподавателя.

3. Прежде чем начать

В Google Cloud Console на странице выбора проекта выберите или создайте проект Google Cloud. Убедитесь, что для вашего облачного проекта включена оплата. Узнайте, как проверить, включена ли оплата в проекте .

👉Нажмите «Активировать Cloud Shell» в верхней части консоли Google Cloud (это значок в форме терминала в верхней части панели Cloud Shell), нажмите кнопку «Открыть редактор» (она выглядит как открытая папка с карандашом). В окне откроется редактор кода Cloud Shell. С левой стороны вы увидите файловый менеджер.

Облачная оболочка

👉Нажмите кнопку входа в Cloud Code в нижней строке состояния, как показано на рисунке. Авторизуйте плагин согласно инструкциям. Если в строке состояния вы видите Cloud Code — нет проекта , выберите его, затем в раскрывающемся списке «Выберите проект Google Cloud», а затем выберите конкретный проект Google Cloud из списка созданных вами проектов.

Войти в проект

👉Откройте терминал в облачной IDE, Новый терминал

👉В терминале убедитесь, что вы уже прошли аутентификацию и что для проекта установлен идентификатор вашего проекта, используя следующую команду:

gcloud auth list

👉И запустите:

gcloud config set project <YOUR_PROJECT_ID>

👉Выполните следующую команду, чтобы включить необходимые API Google Cloud:

gcloud services enable compute.googleapis.com  \
                        storage.googleapis.com  \
                        run.googleapis.com  \
                        artifactregistry.googleapis.com  \
                        aiplatform.googleapis.com \
                        eventarc.googleapis.com \
                        sqladmin.googleapis.com \
                        secretmanager.googleapis.com \
                        cloudbuild.googleapis.com \
                        cloudresourcemanager.googleapis.com \
                        cloudfunctions.googleapis.com

Это может занять пару минут..

Включите Gemini Code Assist в Cloud Shell IDE

Нажмите кнопку Code Assist на левой панели, как показано, и в последний раз выберите правильный проект Google Cloud. Если вас попросят включить Cloud AI Companion API, сделайте это и продолжайте. Выбрав проект Google Cloud, убедитесь, что вы видите это в сообщении о состоянии Cloud Code в строке состояния и что у вас также включен Code Assist справа в строке состояния, как показано ниже:

Включить кодассист

Настройка разрешения

👉Разрешение на настройку учетной записи службы

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")

echo "Here's your SERVICE_ACCOUNT_NAME $SERVICE_ACCOUNT_NAME"

Предоставить разрешения 👉Облачное хранилище (чтение/запись):

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

👉Pub/Sub (публикация/получение):

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

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

👉Cloud SQL (чтение/запись):

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

👉Eventarc (получение событий):

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

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


👉Vertex AI (Пользователь):

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

👉Секретный менеджер (читать):

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

👉Проверьте результат в консоли IAM. IAM-консоль

4. Создание первого агента

Прежде чем мы углубимся в сложные многоагентные системы, нам необходимо создать фундаментальный строительный блок: единого функционального агента. В этом разделе мы сделаем первые шаги, создав простой агент «поставщика книг». Агент поставщика книг принимает категорию в качестве входных данных и использует Gemini LLM для создания книги-представления JSON в этой категории. Затем он служит этим книжным рекомендациям в качестве конечной точки REST API.

Поставщик книг

👉На другой вкладке браузера откройте консоль Google Cloud в своем веб-браузере, в меню навигации (☰) выберите «Cloud Run». Нажмите кнопку «+... НАПИСАТЬ ФУНКЦИЮ».

Создать функцию

👉Далее мы настроим основные параметры функции Cloud Run:

  • Название сервиса: book-provider
  • Регион: us-central1
  • Среда выполнения: Python 3.12
  • Аутентификация: Allow unauthenticated invocations .

👉Остальные настройки оставьте по умолчанию и нажмите «Создать» . Вы попадете в редактор исходного кода.

Вы увидите предварительно заполненные файлы main.py и requirements.txt .

Файл main.py будет содержать бизнес-логику функции, requirements.txt — необходимые пакеты.

👉Теперь мы готовы написать код! Но прежде чем углубиться, давайте посмотрим, может ли Gemini Code Assist дать нам фору. Вернитесь в редактор Cloud Shell, щелкните значок Gemini Code Assist и вставьте следующий запрос в поле подсказки: Помощь по коду Близнецов

Use the functions_framework library to be deployable as an HTTP function. 
Accept a request with category and number_of_book parameters (either in JSON body or query string). 
Use langchain and gemini to generate the data for book with fields bookname, author, publisher, publishing_date. 
Use pydantic to define a Book model with the fields: bookname (string, description: "Name of the book"), author (string, description: "Name of the author"), publisher (string, description: "Name of the publisher"), and publishing_date (string, description: "Date of publishing"). 
Use langchain and gemini model to generate book data. the output should follow the format defined in Book model. 

The logic should use JsonOutputParser from langchain to enforce output format defined in Book Model. 
Have a function get_recommended_books(category) that internally uses langchain and gemini to return a single book object. 
The main function, exposed as the Cloud Function, should call get_recommended_books() multiple times (based on number_of_book) and return a JSON list of the generated book objects. 
Handle the case where category or number_of_book are missing by returning an error JSON response with a 400 status code. 
return a JSON string representing the recommended books. use os library to retrieve GOOGLE_CLOUD_PROJECT env var. Use ChatVertexAI from langchain for the LLM call

Затем Code Assist сгенерирует потенциальное решение, предоставив исходный код и файл зависимостей require.txt.

Мы рекомендуем вам сравнить код, сгенерированный Code Assist, с проверенным правильным решением, представленным ниже. Это позволяет оценить эффективность инструмента и выявить любые потенциальные несоответствия. Хотя LLM никогда не следует слепо доверять, Code Assist может стать отличным инструментом для быстрого прототипирования и создания исходных структур кода, и его следует использовать для хорошего старта.

Поскольку это семинар, мы продолжим работу с проверенным кодом, представленным ниже. Однако не стесняйтесь экспериментировать с кодом, сгенерированным Code Assist, в свободное время, чтобы глубже понять его возможности и ограничения.

👉Вернитесь в редактор исходного кода функции Cloud Run (на другой вкладке браузера). Осторожно замените существующее содержимое main.py кодом, представленным ниже:

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

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


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

llm = ChatVertexAI(model_name="gemini-1.0-pro")

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

    Args:
        category (str): category

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

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

    return  json.dumps(response)
    

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

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


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

    
    return jsonify(recommendations_list)

👉Замените содержимое файла require.txt следующим:

functions-framework==3.*
google-genai==1.0.0
flask==3.1.0
jsonify==0.5
langchain_google_vertexai==2.0.13
langchain_core==0.3.34
pydantic==2.10.5

👉мы установим точку входа в функцию : recommended

03-02-функция-создать.png

👉Нажмите СОХРАНИТЬ И РАЗВЕРТЫВАТЬ . для развертывания функции. Дождитесь завершения процесса развертывания. Облачная консоль отобразит статус. Это может занять несколько минут.

альтернативный текст 👉После развертывания вернитесь в редактор облачной оболочки и в терминале выполните:

export PROJECT_ID=$(gcloud config get project)
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

curl -X POST -H "Content-Type: application/json" -d '{"category": "Science Fiction", "number_of_book": 2}' $BOOK_PROVIDER_URL

Он должен отображать некоторые данные книги в формате JSON.

[
  {"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},
  {"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}
]

Поздравляем! Вы успешно развернули функцию Cloud Run. Это один из сервисов, которые мы будем интегрировать при разработке нашего агента Aidemy.

5. Инструменты создания: подключение агентов к службам и данным RESTFUL.

Давайте продолжим и загрузим проект скелета Bootstrap. Убедитесь, что вы находитесь в редакторе Cloud Shell. При запуске терминала

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

После выполнения этой команды в вашей среде Cloud Shell будет создана новая папка с именем aidemy-bootstrap .

На панели проводника редактора Cloud Shell (обычно слева) вы теперь должны увидеть папку, созданную при клонировании репозитория Git aidemy-bootstrap . Откройте корневую папку вашего проекта в Проводнике. Внутри вы найдете подпапку planner , откройте и ее. обозреватель проекта

Давайте начнем создавать инструменты, которые наши агенты будут использовать, чтобы стать по-настоящему полезными. Как вы знаете, студенты LLM отлично умеют рассуждать и генерировать тексты, но им необходим доступ к внешним ресурсам для выполнения реальных задач и предоставления точной и актуальной информации. Думайте об этих инструментах как о «швейцарском армейском ноже» агента, дающем ему возможность взаимодействовать с миром.

При создании агента легко увязнуть в жестком кодировании множества деталей. Это создает агента, который не является гибким. Вместо этого, создавая и используя инструменты, агент получает доступ к внешней логике или системам, что дает ему преимущества как LLM, так и традиционного программирования.

В этом разделе мы создадим основу для агента планировщика, который учителя будут использовать для создания планов уроков. Прежде чем агент начнет составлять план, мы хотим установить границы, предоставив более подробную информацию о предмете и теме. Мы создадим три инструмента:

  1. Вызов Restful API: взаимодействие с уже существующим API для получения данных.
  2. Запрос к базе данных: получение структурированных данных из базы данных Cloud SQL.
  3. Поиск Google: доступ к информации из Интернета в режиме реального времени.

Получение рекомендаций по книгам из API

Во-первых, давайте создадим инструмент, который получает рекомендации по книгам из API поставщика книг, который мы развернули в предыдущем разделе. Это демонстрирует, как агент может использовать существующие услуги.

Рекомендовать книгу

В редакторе Cloud Shell откройте проект aidemy-bootstrap , который вы клонировали в предыдущем разделе. 👉Отредактируйте файл book.py в папке planner и вставьте следующий код:

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

Объяснение:

  • report_book(query: str) : эта функция принимает на вход запрос пользователя.
  • Взаимодействие LLM : он использует LLM для извлечения категории из запроса. Это демонстрирует, как вы можете использовать LLM для создания параметров инструментов.
  • Вызов API : он отправляет запрос POST к API поставщика книг, передавая категорию и желаемое количество книг.

👉Чтобы протестировать эту новую функцию, установите переменную среды и запустите:

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

👉Установите зависимости и запустите код, чтобы убедиться, что он работает, запустите:

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

Игнорируйте всплывающее окно с предупреждением Git.

Вы должны увидеть строку JSON, содержащую рекомендации по книгам, полученную из API поставщика книг.

[{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}]

Если вы видите это, первый инструмент работает правильно!

Вместо явного создания вызова RESTful API с конкретными параметрами мы используем естественный язык («Я прохожу курс...»). Затем агент интеллектуально извлекает необходимые параметры (например, категорию) с помощью NLP, показывая, как агент использует понимание естественного языка для взаимодействия с API.

сравнить вызов

👉 Удалите следующий тестовый код из book.py

if __name__ == "__main__":
    print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

Получение данных учебного плана из базы данных

Далее мы создадим инструмент, который будет получать структурированные данные учебной программы из базы данных Cloud SQL PostgreSQL. Это позволяет агенту получить доступ к надежному источнику информации для планирования урока.

создать базу данных

👉Выполните следующие команды в терминале, чтобы создать экземпляр Cloud SQL с именем aidemy. Этот процесс может занять некоторое время.

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

👉Далее создайте базу данных с именем aidemy-db в новом экземпляре.

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

Давайте проверим экземпляр Cloud SQL в Google Cloud Console. В списке вы должны увидеть экземпляр Cloud SQL с именем aidemy . Нажмите на имя экземпляра, чтобы просмотреть его сведения. На странице сведений об экземпляре Cloud SQL нажмите «SQL Studio» в меню навигации слева. Откроется новая вкладка.

Нажмите, чтобы подключиться к базе данных. Войдите в SQL Studio.

Выберите aidemy-db в качестве базы данных. введите postgres в качестве пользователя и 1234qwer в качестве пароля . вход в студию sql

👉В редакторе запросов SQL Studio вставьте следующий код SQL:

CREATE TABLE curriculums (
    id SERIAL PRIMARY KEY,
    year INT,
    subject VARCHAR(255),
    description TEXT
);

-- Inserting detailed curriculum data for different school years and subjects
INSERT INTO curriculums (year, subject, description) VALUES
-- Year 5
(5, 'Mathematics', 'Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.'),
(5, 'English', 'Developing reading comprehension, creative writing, and basic grammar, with a focus on storytelling and poetry.'),
(5, 'Science', 'Exploring basic physics, chemistry, and biology concepts, including forces, materials, and ecosystems.'),
(5, 'Computer Science', 'Basic coding concepts using block-based programming and an introduction to digital literacy.'),

-- Year 6
(6, 'Mathematics', 'Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.'),
(6, 'English', 'Introduction to persuasive writing, character analysis, and deeper comprehension of literary texts.'),
(6, 'Science', 'Forces and motion, the human body, and introductory chemical reactions with hands-on experiments.'),
(6, 'Computer Science', 'Introduction to algorithms, logical reasoning, and basic text-based programming (Python, Scratch).'),

-- Year 7
(7, 'Mathematics', 'Algebraic expressions, geometry, and introduction to statistics and probability.'),
(7, 'English', 'Analytical reading of classic and modern literature, essay writing, and advanced grammar skills.'),
(7, 'Science', 'Introduction to cells and organisms, chemical reactions, and energy transfer in physics.'),
(7, 'Computer Science', 'Building on programming skills with Python, introduction to web development, and cyber safety.');

Этот код SQL создает таблицу с именем curriculums и вставляет некоторые образцы данных. Нажмите «Выполнить» , чтобы выполнить код SQL. Вы должны увидеть сообщение с подтверждением, указывающее, что команды были выполнены успешно.

👉Разверните проводник, найдите вновь созданную таблицу и нажмите «Запрос» . Должна открыться новая вкладка редактора со сгенерированным для вас SQL.

Таблица выбора студии SQL

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

👉Нажмите «Выполнить» .

В таблице результатов должны отображаться строки данных, которые вы вставили на предыдущем шаге, что подтверждает правильность создания таблицы и данных.

Теперь, когда вы успешно создали базу данных с примерами данных об учебной программе, мы создадим инструмент для ее извлечения.

👉В редакторе кода Cloud отредактируйте файл curriculums.py в папке aidemy-bootstrap и вставьте следующий код:

def connect_with_connector() -> sqlalchemy.engine.base.Engine:

    db_user = os.environ["DB_USER"]
    db_pass = os.environ["DB_PASS"]
    db_name = os.environ["DB_NAME"]

    encoded_db_user = os.environ.get("DB_USER")
    print(f"--------------------------->db_user: {db_user!r}")  
    print(f"--------------------------->db_pass: {db_pass!r}") 
    print(f"--------------------------->db_name: {db_name!r}") 

    ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

    connector = Connector()

    def getconn() -> pg8000.dbapi.Connection:
        conn: pg8000.dbapi.Connection = connector.connect(
            instance_connection_name,
            "pg8000",
            user=db_user,
            password=db_pass,
            db=db_name,
            ip_type=ip_type,
        )
        return conn

    pool = sqlalchemy.create_engine(
        "postgresql+pg8000://",
        creator=getconn,
        pool_size=2,
        max_overflow=2,
        pool_timeout=30,  # 30 seconds
        pool_recycle=1800,  # 30 minutes
    )
    return pool



def init_connection_pool() -> sqlalchemy.engine.base.Engine:
    
    return (
        connect_with_connector()
    )

    raise ValueError(
        "Missing database connection type. Please define one of INSTANCE_HOST, INSTANCE_UNIX_SOCKET, or INSTANCE_CONNECTION_NAME"
    )

def get_curriculum(year: int, subject: str):
    """
    Get school curriculum
    
    Args:
        subject: User's request subject string
        year: User's request year int
    """
    try:
        stmt = sqlalchemy.text(
            "SELECT description FROM curriculums WHERE year = :year AND subject = :subject"
        )

        with db.connect() as conn:
            result = conn.execute(stmt, parameters={"year": year, "subject": subject})
            row = result.fetchone()
        if row:
            return row[0]  
        else:
            return None  

    except Exception as e:
        print(e)
        return None

db = init_connection_pool()

Объяснение:

  • Переменные среды : код извлекает учетные данные базы данных и информацию о соединении из переменных среды (подробнее об этом ниже).
  • Connect_with_connector() : эта функция использует Cloud SQL Connector для установки безопасного соединения с базой данных.
  • get_curriculum(year: int, subject: str) : эта функция принимает год и предмет в качестве входных данных, запрашивает таблицу учебных программ и возвращает соответствующее описание учебной программы.

👉Прежде чем мы сможем запустить код, мы должны установить некоторые переменные среды и запустить в терминале:

export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Для проверки добавьте следующий код в конец curriculums.py :

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

👉Запустите код:

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

Вы должны увидеть описание учебной программы по математике для 6-го класса, распечатанное на консоли.

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

Если вы видите описание учебной программы, значит, инструмент базы данных работает правильно! Продолжайте и остановите скрипт, нажав Ctrl+C .

👉 Удалите следующий код тестирования из curriculums.py

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

👉Выйдите из виртуальной среды, запустив терминал:

deactivate

6. Инструменты для создания: доступ к информации в реальном времени из Интернета.

Наконец, мы создадим инструмент, который будет использовать интеграцию Gemini 2 и Google Search для доступа к информации из Интернета в режиме реального времени. Это помогает агенту оставаться в курсе событий и предоставлять актуальные результаты.

Интеграция Gemini 2 с API поиска Google расширяет возможности агента, предоставляя более точные и контекстуально релевантные результаты поиска. Это позволяет агентам получать доступ к актуальной информации и основывать свои ответы на реальных данных, сводя к минимуму галлюцинации. Улучшенная интеграция API также упрощает запросы на естественном языке, позволяя агентам формулировать сложные и детальные поисковые запросы.

Поиск

Эта функция принимает в качестве входных данных поисковый запрос, учебную программу, предмет и год обучения и использует API Gemini и инструмент поиска Google для получения соответствующей информации из Интернета. Если присмотреться, он использует Google Generative AI SDK для вызова функций без использования какой-либо другой платформы.

👉Отредактируйте search.py ​​в папке aidemy-bootstrap и вставьте следующий код:

model_id = "gemini-2.0-flash-001"

google_search_tool = Tool(
    google_search = GoogleSearch()
)

def search_latest_resource(search_text: str, curriculum: str, subject: str, year: int):
    """
    Get latest information from the internet
    
    Args:
        search_text: User's request category   string
        subject: "User's request subject" string
        year: "User's request year"  integer
    """
    search_text = "%s in the context of year %d and subject %s with following curriculum detail %s " % (search_text, year, subject, curriculum)
    region = get_next_region()
    client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
    print(f"search_latest_resource text-----> {search_text}")
    response = client.models.generate_content(
        model=model_id,
        contents=search_text,
        config=GenerateContentConfig(
            tools=[google_search_tool],
            response_modalities=["TEXT"],
        )
    )
    print(f"search_latest_resource response-----> {response}")
    return response

if __name__ == "__main__":
  response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
  for each in response.candidates[0].content.parts:
    print(each.text)

Объяснение:

  • Определение инструмента — google_search_tool : перенос объекта GoogleSearch в инструмент.
  • search_latest_resource(search_text: str, subject: str,year: int) : эта функция принимает поисковый запрос, тему и год в качестве входных данных и использует API Gemini для выполнения поиска в Google. Модель Близнецов
  • GenerateContentConfig : определяет, что у него есть доступ к инструменту GoogleSearch.

Модель Gemini внутренне анализирует search_text и определяет, может ли она ответить на вопрос напрямую или необходимо использовать инструмент GoogleSearch. Это важный шаг, который происходит в процессе рассуждения LLM. Модель обучена распознавать ситуации, когда необходимы внешние инструменты. Если модель решает использовать инструмент GoogleSearch, фактический вызов обрабатывается Google Generative AI SDK. SDK принимает решение модели и генерируемые ею параметры и отправляет их в API поиска Google. Эта часть скрыта от пользователя в коде.

Затем модель Gemini интегрирует результаты поиска в свой ответ. Он может использовать эту информацию для ответа на вопрос пользователя, создания сводки или выполнения какой-либо другой задачи.

👉Для проверки запустите код:

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

Вы должны увидеть ответ Gemini Search API, содержащий результаты поиска, связанные с «Учебной программой по математике для 5-го класса». Точный результат будет зависеть от результатов поиска, но это будет объект JSON с информацией о поиске.

Если вы видите результаты поиска, значит, инструмент поиска Google работает правильно! Продолжайте и остановите скрипт, нажав Ctrl+C .

👉И удалите последнюю часть кода.

if __name__ == "__main__":
  response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
  for each in response.candidates[0].content.parts:
    print(each.text)

👉Выйдите из виртуальной среды, запустив терминал:

deactivate

Поздравляем! Теперь вы создали три мощных инструмента для своего агента планирования: соединитель API, соединитель базы данных и инструмент поиска Google. Эти инструменты позволят агенту получить доступ к информации и возможностям, необходимым для создания эффективных планов обучения.

7. Оркестровка с помощью LangGraph

Теперь, когда мы создали наши отдельные инструменты, пришло время организовать их с помощью LangGraph. Это позволит нам создать более сложный агент «планировщика», который сможет разумно решать, какие инструменты использовать и когда, в зависимости от запроса пользователя.

LangGraph — это библиотека Python, предназначенная для упрощения создания многоактных приложений с отслеживанием состояния с использованием моделей большого языка (LLM). Думайте об этом как о структуре для организации сложных разговоров и рабочих процессов с участием LLM, инструментов и других агентов.

Ключевые понятия:

  • Структура графа: LangGraph представляет логику вашего приложения в виде ориентированного графа. Каждый узел графа представляет собой этап процесса (например, вызов LLM, вызов инструмента, условную проверку). Ребра определяют поток выполнения между узлами.
  • Состояние: LangGraph управляет состоянием вашего приложения при его перемещении по графу. Это состояние может включать переменные, такие как ввод пользователя, результаты вызовов инструментов, промежуточные выходные данные LLM и любую другую информацию, которую необходимо сохранять между шагами.
  • Узлы: каждый узел представляет собой вычисление или взаимодействие. Они могут быть:
    • Узлы инструментов: используйте инструмент (например, выполните поиск в Интернете, запросите базу данных).
    • Функциональные узлы: выполнение функции Python.
  • Края: соединяйте узлы, определяя поток выполнения. Они могут быть:
    • Прямые края: простой, безусловный поток от одного узла к другому.
    • Условные ребра: поток зависит от результата условного узла.

Лангграф

Мы будем использовать LangGraph для реализации оркестровки. Давайте отредактируем файл aidemy.py в папке aidemy-bootstrap , чтобы определить нашу логику LangGraph. 👉Добавьте следующий код в конец aidemy.py :

tools = [get_curriculum, search_latest_resource, recommend_book]

def determine_tool(state: MessagesState):
    llm = ChatVertexAI(model_name="gemini-2.0-flash-001", location=get_next_region())
    sys_msg = SystemMessage(
                    content=(
                        f"""You are a helpful teaching assistant that helps gather all needed information. 
                            Your ultimate goal is to create a detailed 3-week teaching plan. 
                            You have access to tools that help you gather information.  
                            Based on the user request, decide which tool(s) are needed. 

                        """
                    )
                )

    llm_with_tools = llm.bind_tools(tools)
    return {"messages": llm_with_tools.invoke([sys_msg] + state["messages"])} 

Эта функция отвечает за определение текущего состояния разговора, предоставление LLM системного сообщения и последующее обращение к LLM с просьбой сгенерировать ответ. LLM может либо ответить непосредственно пользователю, либо использовать один из доступных инструментов.

инструменты : этот список представляет набор инструментов, доступных агенту. Он содержит три инструментальные функции, которые мы определили на предыдущих шагах: get_curriculum , search_latest_resource recommend_book . llm.bind_tools(tools) : он «привязывает» список инструментов к объекту llm. Привязка инструментов сообщает LLM, что эти инструменты доступны, и предоставляет LLM информацию о том, как их использовать (например, имена инструментов, параметры, которые они принимают, и что они делают).

Мы будем использовать LangGraph для реализации оркестровки. 👉Добавьте следующий код в конец aidemy.py :

def prep_class(prep_needs):
   
    builder = StateGraph(MessagesState)
    builder.add_node("determine_tool", determine_tool)
    builder.add_node("tools", ToolNode(tools))
    
    builder.add_edge(START, "determine_tool")
    builder.add_conditional_edges("determine_tool",tools_condition)
    builder.add_edge("tools", "determine_tool")

    
    memory = MemorySaver()
    graph = builder.compile(checkpointer=memory)

    config = {"configurable": {"thread_id": "1"}}
    messages = graph.invoke({"messages": prep_needs},config)
    print(messages)
    for m in messages['messages']:
        m.pretty_print()
    teaching_plan_result = messages["messages"][-1].content  


    return teaching_plan_result

if __name__ == "__main__":
  prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan")

Объяснение:

  • StateGraph(MessagesState) : создает объект StateGraph . StateGraph — это основная концепция LangGraph. Он представляет рабочий процесс вашего агента в виде графика, где каждый узел графика представляет собой этап процесса. Думайте об этом как об определении того, как агент будет рассуждать и действовать.
  • Условное ребро. Аргумент tools_condition , происходящий из узла "determine_tool" , скорее всего, является функцией, которая определяет, за каким ребром следовать, на основе выходных данных функции determine_tool . Условные ребра позволяют графу разветвляться в зависимости от решения LLM о том, какой инструмент использовать (или отвечать ли непосредственно пользователю). Здесь в игру вступает «интеллект» агента – он может динамически адаптировать свое поведение в зависимости от ситуации.
  • Цикл: добавляет ребро к графу, которое соединяет узел "tools" обратно с узлом "determine_tool" . Это создает цикл на графике, позволяя агенту неоднократно использовать инструменты, пока он не соберет достаточно информации для выполнения задачи и предоставления удовлетворительного ответа. Этот цикл имеет решающее значение для сложных задач, требующих нескольких этапов рассуждения и сбора информации.

Теперь давайте проверим наш агент планировщика, чтобы увидеть, как он координирует работу различных инструментов.

Этот код запустит функцию prep_class с определенным пользовательским вводом, имитируя запрос на создание плана обучения математике в геометрии для 5-го класса с использованием учебной программы, рекомендаций по книгам и новейших интернет-ресурсов.

Если вы закрыли терминал или переменные среды больше не установлены, перезапустите следующие команды

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Запустите код:

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

Смотрите лог в терминале. Прежде чем предоставить окончательный план обучения, вы должны увидеть доказательства того, что агент использует все три инструмента (получение школьной программы, получение рекомендаций по книгам и поиск новейших ресурсов). Это демонстрирует, что оркестровка LangGraph работает правильно, и агент разумно использует все доступные инструменты для выполнения запроса пользователя.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
  get_curriculum (xxx)
 Call ID: xxx
  Args:
    year: 5.0
    subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
  search_latest_resource (xxxx)
 Call ID: xxxx
  Args:
    year: 5.0
    search_text: Geometry
    curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
    subject: Mathematics
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.....) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================
Tool Calls:
  recommend_book (93b48189-4d69-4c09-a3bd-4e60cdc5f1c6)
 Call ID: 93b48189-4d69-4c09-a3bd-4e60cdc5f1c6
  Args:
    query: Mathematics Geometry Year 5
================================= Tool Message =================================
Name: recommend_book

[{.....}]

================================== Ai Message ==================================

Based on the curriculum outcome, here is a 3-week teaching plan for year 5 Mathematics Geometry:

**Week 1: Introduction to Shapes and Properties**
.........

Остановите скрипт, нажав Ctrl+C .

👉Теперь замените код тестирования другим приглашением, для вызова которого требуются другие инструменты.

if __name__ == "__main__":
  prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

Если вы закрыли терминал или переменные среды больше не установлены, перезапустите следующие команды

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Запустите код еще раз:

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

Что вы заметили на этот раз? Какие инструменты вызвал агент? Вы должны увидеть, что на этот раз агент вызывает только инструмент search_latest_resource. Это связано с тем, что в приглашении не указано, что ему нужны два других инструмента, а наш LLM достаточно умен, чтобы не вызывать другие инструменты.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
  get_curriculum (xxx)
 Call ID: xxx
  Args:
    year: 5.0
    subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
  search_latest_resource (xxx)
 Call ID: xxxx
  Args:
    year: 5.0
    subject: Mathematics
    curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
    search_text: Geometry
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.......token_count=40, total_token_count=772) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================

Based on the information provided, a 3-week teaching plan for Year 5 Mathematics focusing on Geometry could look like this:

**Week 1:  Introducing 2D Shapes**
........
* Use visuals, manipulatives, and real-world examples to make the learning experience engaging and relevant.

Остановите скрипт, нажав Ctrl+C . 👉 Удалите тестовый код, чтобы сохранить файл aidemy.py в чистоте:

if __name__ == "__main__":
  prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

Теперь, когда логика нашего агента определена, давайте запустим веб-приложение Flask. Это предоставит учителям знакомый интерфейс на основе форм для взаимодействия с агентом. Хотя взаимодействие с чат-ботами является обычным явлением для студентов LLM, мы выбираем традиционный пользовательский интерфейс отправки формы, поскольку он может быть более интуитивным для многих преподавателей.

Если вы закрыли терминал или переменные среды больше не установлены, повторно запустите следующие команды

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Теперь запустите веб-интерфейс.

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

Найдите сообщения о запуске в выходных данных терминала Cloud Shell. Flask обычно печатает сообщения, указывающие, что он запущен и на каком порту.

Running on http://127.0.0.1:8080
Running on http://127.0.0.1:8080
The application needs to keep running to serve requests.

👉В меню «Предварительный просмотр в Интернете» выберите «Просмотр на порту 8080». Cloud Shell откроет новую вкладку или окно браузера с предварительным просмотром вашего приложения в Интернете.

веб-страница

В интерфейсе приложения выберите 5 для параметра «Год», выберите предмет Mathematics и введите Geometry в запросе на дополнение. Предел квоты

Вместо того, чтобы тупо смотреть в ожидании ответа, переключитесь на терминал Cloud Editor. Вы можете наблюдать за ходом выполнения и любыми выводами или сообщениями об ошибках, генерируемыми вашей функцией, в терминале эмулятора. 😁

👉Остановите скрипт, нажав Ctrl+C в терминале.

👉Выйдите из виртуальной среды:

deactivate

8. Развертывание агента планировщика в облаке

Создайте и загрузите образ в реестр.

Обзор

👉Пришло время развернуть это в облаке. В терминале создайте репозиторий артефактов для хранения образа докера, который мы собираемся создать.

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

Вы должны увидеть Созданный репозиторий [агент-репозиторий].

👉Выполните следующую команду, чтобы создать образ Docker.

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

👉Нам нужно изменить тег изображения, чтобы оно размещалось в реестре артефактов, а не в GCR, и отправить помеченное изображение в реестр артефактов:

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

После завершения отправки вы можете убедиться, что изображение успешно сохранено в реестре артефактов. Перейдите в реестр артефактов в Google Cloud Console. Вы должны найти образ aidemy-planner в репозитории agent-repository . Изображение планировщика Aidemy

Защита учетных данных базы данных с помощью Secret Manager

Для безопасного управления учетными данными базы данных и доступа к ним мы будем использовать Google Cloud Secret Manager. Это предотвращает жесткое кодирование конфиденциальной информации в коде нашего приложения и повышает безопасность.

👉Мы создадим отдельные секреты для имени пользователя, пароля и имени базы данных. Такой подход позволяет нам управлять каждыми учетными данными независимо. В терминале запустите:

gcloud secrets create db-user
printf "postgres" | gcloud secrets versions add db-user --data-file=-

gcloud secrets create db-pass
printf "1234qwer" | gcloud secrets versions add db-pass --data-file=- 

gcloud secrets create db-name
printf "aidemy-db" | gcloud secrets versions add db-name --data-file=-

Использование Secret Manager — важный шаг в обеспечении безопасности вашего приложения и предотвращении случайного раскрытия конфиденциальных учетных данных. Он соответствует передовым практикам безопасности для облачных развертываний.

Развертывание в Cloud Run

Cloud Run — это полностью управляемая бессерверная платформа, которая позволяет быстро и легко развертывать контейнерные приложения. Он абстрагирует управление инфраструктурой, позволяя вам сосредоточиться на написании и развертывании кода. Мы будем развертывать наш планировщик как сервис Cloud Run.

👉В консоли Google Cloud перейдите к « Cloud Run ». Нажмите РАЗВЕРТЫВАТЬ КОНТЕЙНЕР и выберите СЕРВИС . Настройте службу Cloud Run:

Облачный бег

  1. Изображение контейнера : нажмите «Выбрать» в поле URL. Найдите URL-адрес изображения, которое вы отправили в реестр артефактов (например, us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
  2. Название сервиса : aidemy-planner
  3. Регион : выберите регион us-central1 .
  4. Аутентификация : Для целей этого семинара вы можете разрешить «Разрешить неаутентифицированные вызовы». Для производства вы, вероятно, захотите ограничить доступ.
  5. Вкладка «Контейнер(ы)» (разверните «Контейнеры», «Сеть»):
    • Вкладка «Настройки»:
      • Ресурс
        • память: 2 ГБ
    • Вкладка «Переменные и секреты»:
      • Переменные среды:
        • Добавьте имя: GOOGLE_CLOUD_PROJECT и значение: <YOUR_PROJECT_ID>.
        • Добавьте имя: BOOK_PROVIDER_URL и значение: <YOUR_BOOK_PROVIDER_FUNCTION_URL>
      • Секреты, представленные как переменные среды:
        • Добавьте имя: DB_USER , секрет: выберите db-user и версию: latest
        • Добавьте имя: DB_PASS , секрет: выберите db-pass и версию: latest
        • Добавить имя: DB_NAME , секрет: выберите db-name и версию: latest

Запустите следующую команду в терминале, если вам нужно получить URL-адрес YOUR_BOOK_PROVIDER_FUNCTION_URL:

gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)"

Установить секрет

Оставьте другое по умолчанию.

👉Нажмите СОЗДАТЬ .

Cloud Run развернет ваш сервис.

После развертывания нажмите на услугу, чтобы перейти на страницу ее сведений. Вы можете найти развернутый URL-адрес вверху.

URL-адрес

В интерфейсе приложения выберите 7 для года, выберите предмет Mathematics и введите Algebra в поле «Запрос на дополнение». Это предоставит агенту необходимый контекст для составления индивидуального плана урока.

Поздравляем! Вы успешно создали план обучения с помощью нашего мощного агента искусственного интеллекта. Это демонстрирует потенциал агентов по значительному снижению рабочей нагрузки и оптимизации задач, что в конечном итоге повышает эффективность и облегчает жизнь преподавателям.

9. Мультиагентные системы

Теперь, когда мы успешно внедрили инструмент создания плана обучения, давайте сосредоточим внимание на создании студенческого портала. Этот портал предоставит студентам доступ к викторинам, аудиозаписям и заданиям, связанным с их курсовой работой. Учитывая объем этой функциональности, мы воспользуемся возможностями мультиагентных систем для создания модульного и масштабируемого решения.

Как мы обсуждали ранее, вместо того, чтобы полагаться на одного агента для выполнения всего, многоагентная система позволяет нам разбить рабочую нагрузку на более мелкие специализированные задачи, каждая из которых выполняется выделенным агентом. Такой подход дает несколько ключевых преимуществ:

Модульность и удобство обслуживания . Вместо создания одного агента, который делает все, создайте более мелкие специализированные агенты с четко определенными обязанностями. Такая модульность упрощает понимание, обслуживание и отладку системы. При возникновении проблемы вы можете изолировать ее от конкретного агента, вместо того чтобы анализировать огромную базу кода.

Масштабируемость . Масштабирование одного сложного агента может стать узким местом. С помощью мультиагентной системы вы можете масштабировать отдельных агентов в соответствии с их конкретными потребностями. Например, если один агент обрабатывает большой объем запросов, вы можете легко развернуть больше экземпляров этого агента, не затрагивая остальную часть системы.

Специализация команды . Подумайте об этом так: вы не станете просить одного инженера создать целое приложение с нуля. Вместо этого вы собираете команду специалистов, каждый из которых имеет опыт в определенной области. Аналогичным образом, многоагентная система позволяет вам использовать сильные стороны различных LLM и инструментов, назначая их агентам, которые лучше всего подходят для конкретных задач.

Параллельная разработка : разные команды могут работать на разных агентах одновременно, ускоряя процесс разработки. Поскольку агенты независимы, изменения в одном агенте с меньшей вероятностью влияют на других агентов.

Архитектура, управляемая событиями

Чтобы обеспечить эффективное общение и координацию между этими агентами, мы используем архитектуру, основанную на событиях. Это означает, что агенты будут реагировать на «события», происходящие в системе.

Агенты подписываются на конкретные типы событий (например, «Сгенерированный план преподавания», «создано назначение»). Когда происходит событие, соответствующие агенты уведомляются и могут реагировать соответствующим образом. Эта развязка способствует гибкости, масштабируемости и отзывчивости в реальном времени.

Обзор

Теперь, чтобы начать все, нам нужен способ транслировать эти события. Для этого мы создадим паб/суб -тему. Давайте начнем с создания темы под названием Plan .

👉go в Google Cloud Console Pub/sub и нажмите кнопку «Создать тему».

👉configure Темой с plan идентификатора/имени и Uncheck Add a default subscription , оставьте в качестве по умолчанию и нажмите «Создать» .

Страница Pub/Sub будет обновляться, и теперь вы должны увидеть свою недавно созданную тему, указанную в таблице. Создать тему

Теперь давайте интегрируем функциональность публикации Pub/Sub Event в нашем агенте планировщика. Мы добавим новый инструмент, который отправляет событие «Плана» в паб/подменную тему, которую мы только что создали. Это событие будет сигнализировать другим агентам в системе (например, в студенческом портале), что новый план преподавания доступен.

👉 Вернитесь в редактор облачного кода и откройте файл app.py , расположенный в папке planner . Мы будем добавлять функцию, которая публикует событие. Заменять:

##ADD SEND PLAN EVENT FUNCTION HERE

с

def send_plan_event(teaching_plan:str):
    """
    Send the teaching event to the topic called plan
    
    Args:
        teaching_plan: teaching plan
    """
    publisher = pubsub_v1.PublisherClient()
    print(f"-------------> Sending event to topic plan: {teaching_plan}")
    topic_path = publisher.topic_path(PROJECT_ID, "plan")

    message_data = {"teaching_plan": teaching_plan} 
    data = json.dumps(message_data).encode("utf-8") 

    future = publisher.publish(topic_path, data)

    return f"Published message ID: {future.result()}"

  • send_plan_event : Эта функция принимает сгенерированный план обучения в качестве ввода, создает клиент паба/суб -публикации, строит тему, преобразует план обучения в строку JSON, публикует сообщение в тему.
  • Список инструментов : функция send_plan_event добавляется в список инструментов, что делает ее доступным для использования агента.

И в той же папке, в файле app.py 👉update подсказка для того, чтобы поручить агенту отправить событие плана обучения в паб/суб -тему после создания плана обучения. Заменять

### ADD send_plan_event CALL

со следующим:

send_plan_event(teaching_plan)

Добавив инструмент send_plan_event и изменив подсказку, мы позволили нашему агенту планировщика публиковать события в Pub/sub, что позволило другим компонентам нашей системы отреагировать на создание новых планов обучения. Теперь у нас будет функциональная многоагентная система в следующих разделах.

10. Расширение прав и возможностей студентов по запросу по требованию

Представьте себе учебную среду, в которой учащиеся имеют доступ к бесконечному запасу викторинов, адаптированных к их конкретным планам обучения. Эти тесты обеспечивают немедленную обратную связь, включая ответы и объяснения, способствуя более глубокому пониманию материала. Это потенциал, который мы стремимся разблокировать на нашем викторине с AI.

Чтобы оживить это видение, мы создадим компонент генерации викторины, который сможет создавать вопросы с несколькими вариантами выбора на основе содержания плана обучения.

Обзор

👉in На панели исследователей облачного кода перейдите в папку portal . Откройте копию файла quiz.py и вставьте следующий код в конце файла.

def generate_quiz_question(file_name: str, difficulty: str, region:str ):
    """Generates a single multiple-choice quiz question using the LLM.
   
    ```json
    {
      "question": "The question itself",
      "options": ["Option A", "Option B", "Option C", "Option D"],
      "answer": "The correct answer letter (A, B, C, or D)"
    }
    ```
    """

    print(f"region: {region}")
    # Connect to resourse needed from Google Cloud
    llm = VertexAI(model_name="gemini-1.5-pro", location=region)


    plan=None
    #load the file using file_name and read content into string call plan
    with open(file_name, 'r') as f:
        plan = f.read()

    parser = JsonOutputParser(pydantic_object=QuizQuestion)


    instruction = f"You'll provide one question with difficulty level of {difficulty}, 4 options as multiple choices and provide the anwsers, the quiz needs to be related to the teaching plan {plan}"

    prompt = PromptTemplate(
        template="Generates a single multiple-choice quiz question\n {format_instructions}\n  {instruction}\n",
        input_variables=["instruction"],
        partial_variables={"format_instructions": parser.get_format_instructions()},
    )
    
    chain = prompt | llm | parser
    response = chain.invoke({"instruction": instruction})

    print(f"{response}")
    return  response


В агенте он создает анализатор вывода JSON, который специально разработан для понимания и структуры вывода LLM. Он использует модель QuizQuestion , которую мы определили ранее, чтобы гарантировать, что проанализированный выход соответствовал правильному формату (вопрос, опции и ответ).

👉ceceed Следующие команды в терминале для настройки виртуальной среды, установить зависимости и запустить агент:

cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py

Используйте функцию веб -предварительного просмотра Cloud Shell для доступа к запущению приложения. Нажмите на ссылку «Викторины», либо в верхней панели навигации, либо с карты на странице индекса. Вы должны увидеть три случайно сгенерированные викторины, отображаемые для студента. Эти тесты основаны на плане преподавания и демонстрируют силу нашей системы генерации викторины с AI.

Викторины

Чтобы остановить локально запущенный процесс, нажмите Ctrl+C в терминале.

Gemini 2 мышление для объяснений

Итак, у нас есть тесты, что отличное начало! Но что, если ученики что -то не так? Вот где происходит настоящее обучение, верно? Если мы сможем объяснить, почему их ответ был отключен и как добраться до правильного, они гораздо чаще запомнят его. Кроме того, это помогает прояснить любую путаницу и повысить их уверенность.

Вот почему мы собираемся принести модель «Мышление» Близнецов 2! Подумайте об этом, как о том, чтобы дать ИИ немного дополнительного времени, чтобы продумать вещи, прежде чем объяснить. Это позволяет дать более подробную и лучшую обратную связь.

Мы хотим посмотреть, может ли это помочь студентам, помогая, отвечая и объясняя подробно. Чтобы проверить это, мы начнем с общеизвестно сложной темы, исчисления.

Обзор

👉 Первый, перейдите к редактору облачного кода, в answer.py в папке portal заменить

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

С следующим фрагментом кода:

def answer_thinking(question, options, user_response, answer, region):
    try:
        llm = VertexAI(model_name="gemini-2.0-flash-001",location=region)
        
        input_msg = HumanMessage(content=[f"Here the question{question}, here are the available options {options}, this student's answer {user_response}, whereas the correct answer is {answer}"])
        prompt_template = ChatPromptTemplate.from_messages(
            [
                SystemMessage(
                    content=(
                        "You are a helpful teacher trying to teach the student on question, you were given the question and a set of multiple choices "
                        "what's the correct answer. use friendly tone"
                    )
                ),
                input_msg,
            ]
        )

        prompt = prompt_template.format()
        
        response = llm.invoke(prompt)
        print(f"response: {response}")

        return response
    except Exception as e:
        print(f"Error sending message to chatbot: {e}") # Log this error too!
        return f"Unable to process your request at this time. Due to the following reason: {str(e)}"



if __name__ == "__main__":
    question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
    options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
    user_response = "B"
    answer = "A"
    region = "us-central1"
    result = answer_thinking(question, options, user_response, answer, region)

Это очень простое приложение Langchain, где оно инициализирует модель Flash Gemini 2, где мы инструктируем ее в качестве полезного учителя и дают объяснения

Execte следующую команду в терминале:

cd ~/aidemy-bootstrap/portal/
python answer.py

Вы должны увидеть вывод, аналогичный примеру, приведенному в исходных инструкциях. Текущая модель может не предоставлять так, как посредством объяснения.

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

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

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

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

Keep practicing, you'll get there!

В файле answer.py замените Model_Name от gemini-2.0-flash-001 на gemini-2.0-flash-thinking-exp-01-21 в функции ответа.

Это меняет LLM, что причина больше, что поможет ему привести к лучшему объяснениям. И запустить его снова.

👉run для проверки новой модели мышления:

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

Вот пример ответа от модели мышления, которая является гораздо более тщательной и подробной, предоставляя пошаговое объяснение того, как решить проблему исчисления. Это подчеркивает силу «мышления» моделей в создании высококачественных объяснений. Вы должны увидеть вывод, похожий на это:

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

👉 Удалить следующий тестовый код из 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)

👉ceceed Следующие команды в терминале для настройки виртуальной среды, установить зависимости и запустить агент:

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

👉 Используйте функцию веб -предварительного просмотра Cloud Shell для доступа к запущению приложения. Нажмите на ссылку «Викторины», ответьте на все викторины и убедитесь, что хотя бы не неправильно ответить и нажмите «Отправить

мыслительные ответы

Вместо того, чтобы искательно смотреть, ожидая ответа, переключитесь на терминал редактора облака. Вы можете наблюдать за прогрессом и любыми выводами или сообщениями об ошибках, сгенерированных вашей функцией в терминале эмулятора. 😁

Чтобы остановить локально запущенный процесс, нажмите Ctrl+C в терминале.

11. Орхенция агентов с Eventarc

До сих пор студенческий портал генерирует тесты на основе набора планов обучения по умолчанию. Это полезно, но это означает, что наш агент по планированию и агент викторины портала на самом деле не разговаривают друг с другом. Помните, как мы добавили эту функцию, где агент планировщика публикует свои недавно сгенерированные планы обучения в паб/суб -тему? Теперь пришло время подключить это к нашему агенту портала!

Обзор

Мы хотим, чтобы портал автоматически обновлял свой контент викторины всякий раз, когда генерируется новый план обучения. Для этого мы создадим конечную точку на портале, которая сможет получить эти новые планы.

👉in На панели исследователей облачного кода перейдите в папку portal . Откройте файл app.py для редактирования. Добавьте код FELLE MOUNTER MIFTER ## Добавьте свой код здесь :

## Add your code here

@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
    try:
       
        # Get data from Pub/Sub message delivered via Eventarc
        envelope = request.get_json()
        if not envelope:
            return jsonify({'error': 'No Pub/Sub message received'}), 400

        if not isinstance(envelope, dict) or 'message' not in envelope:
            return jsonify({'error': 'Invalid Pub/Sub message format'}), 400

        pubsub_message = envelope['message']
        print(f"data: {pubsub_message['data']}")

        data = pubsub_message['data']
        data_str = base64.b64decode(data).decode('utf-8')
        data = json.loads(data_str)

        teaching_plan = data['teaching_plan']

        print(f"File content: {teaching_plan}")

        with open("teaching_plan.txt", "w") as f:
            f.write(teaching_plan)

        print(f"Teaching plan saved to local file: teaching_plan.txt")

        return jsonify({'message': 'File processed successfully'})


    except Exception as e:
        print(f"Error processing file: {e}")
        return jsonify({'error': 'Error processing file'}), 500
## Add your code here

Восстановление и развертывание в Cloud Run

ОК! Вам нужно обновить и переделать как нашего планировщика, так и портальных агентов, чтобы запустить Cloud. Это гарантирует, что они имеют последний код и настроены на общение через события.

Обзор развертывания

👉next мы восстановим и нажмите на изображение агента планировщика , обратно в терминал:

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

👉 Мы сделаем то же самое, построить и раздвигать изображение агента портала :

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

В реестре артефактов вы должны увидеть перечисленные изображения контейнеров aidemy-planner и aidemy-portal .

Контейнер репо

👉back в терминале, запустите его, чтобы обновить изображение Cloud Run для агента планировщика:

export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-planner \
    --region=us-central1 \
    --image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner:latest

Вы должны увидеть вывод, похожий на это:

OK Deploying... Done.                                                                                                                                                     
  OK Creating Revision...                                                                                                                                                 
  OK Routing traffic...                                                                                                                                                   
Done.                                                                                                                                                                     
Service [aidemy-planner] revision [aidemy-planner-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-planner-xxx.us-central1.run.app

Отметить URL -адрес услуги; Это ссылка на вашего развернутого агента планировщика.

👉run это для создания экземпляра Cloud Run для агента портала

export PROJECT_ID=$(gcloud config get project)
gcloud run deploy aidemy-portal \
  --image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal:latest \
  --region=us-central1 \
  --platform=managed \
  --allow-unauthenticated \
  --memory=2Gi \
  --cpu=2 \
  --set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID}

Вы должны увидеть вывод, похожий на это:

Deploying container to Cloud Run service [aidemy-portal] in project [xxxx] region [us-central1]
OK Deploying new service... Done.                                                                                                                                         
  OK Creating Revision...                                                                                                                                                 
  OK Routing traffic...                                                                                                                                                   
  OK Setting IAM Policy...                                                                                                                                                
Done.                                                                                                                                                                     
Service [aidemy-portal] revision [aidemy-portal-xxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-portal-xxxx.us-central1.run.app

Отметить URL -адрес услуги; Это ссылка на ваш развернутый студенческий портал.

Создание триггера Eventarc

Но вот большой вопрос: как эта конечная точка уведомляется, когда в пабе/суб -теме ожидается новый план? Вот где Eventarc поднимается, чтобы спасти день!

Eventarc действует как мост, прислушиваясь к конкретным событиям (например, новое сообщение, поступающее в наш паб/суб -тема) и автоматически вызывает действия в ответ. В нашем случае он обнаружит, когда будет опубликован новый план обучения, а затем отправит сигнал в конечную точку нашего портала, сообщая ему, что пришло время обновлять.

С помощью EventArcr, управляемой событиями, мы можем беспрепятственно подключить нашего агента планировщика и агента портала, создавая по-настоящему динамичную и отзывчивую систему обучения. Это как иметь умного посланника, который автоматически предоставляет последние планы урока в нужное место!

👉 В ходе консоли в Eventarc .

👉 Нажмите кнопку «+ Создать триггер».

Настройте триггер (Основы):

  • Название триггера: plan-topic-trigger
  • Типы триггеров: источники Google.
  • Событие Providwe: Cloud Pub/sub
  • Тип события: google.cloud.pubsub.topic.v1.messagePublished
  • Регион: us-central1 .
  • Cloud Pub/sub topoc: выберите plan
  • Предоставьте счет услуги с roles/iam.serviceAccountTokenCreator
  • Место назначения события: Cloud Run
  • Облачная служба: Aidemy-Portal
  • Путь URL -адреса службы: /new_teaching_plan
  • Игнорировать сообщение (разрешение отказано в «Местоположения/Me-Central2» (или оно не может существовать).)

Нажмите «Создать».

Страница Triggers EventARC будет обновляться, и теперь вы должны увидеть свой недавно созданный триггер, указанный в таблице.

👉now, доступ к планировщику и запросите новый план преподавания. На этот раз, год, год 5 , тематическая science с atoms запроса add-no

Запустите это в терминале, если вы забудете местоположение своего агента планировщика

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

Затем, подождите минуту или две, опять же, эта задержка была введена из -за ограничения выставления счетов этой лаборатории, при нормальном состоянии не должно быть задержки.

Наконец, получить доступ к студенческому порталу . Вы должны увидеть, что тесты были обновлены и теперь согласуются с новым планом обучения, который вы только что сгенерировали! Это демонстрирует успешную интеграцию Eventarc в системе Aidemy!

Запустите это в терминале, если вы забудете местоположение вашего агента портала

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

Aidemy-Celebrate

Поздравляем! Вы успешно создали многоагентную систему в Google Cloud, используя архитектуру, управляемую событиями, для повышения масштабируемости и гибкости! Вы заложили прочную основу, но есть еще больше, чтобы исследовать. Чтобы углубиться в реальные преимущества этой архитектуры, обнаружите силу мультимодального живого API Gemini 2 и узнайте, как реализовать оркестровку с одним путем с Langgraph, не стесняйтесь продолжать к следующим двум главам.

12. Необязательно: звуковые резюме с Близнецами

Близнецы могут понимать и обрабатывать информацию из различных источников, таких как текст, изображения и даже аудио, открывая целый ряд возможностей для обучения и создания контента. Способность Близнецов «видеть», «слышать» и «читать» действительно открывает творческий и привлекательный опыт пользователей.

Помимо просто создания визуальных эффектов или текста, еще одним важным шагом в обучении является эффективное суммирование и резюме. Подумайте об этом: как часто вы помните о броской песне, лирике легче, чем то, что вы читали в учебнике? Звук может быть невероятно запоминающимся! Вот почему мы собираемся использовать мультимодальные возможности Gemini для создания аудио -резюме наших планов преподавания. Это предоставит студентам удобный и увлекательный способ рассмотрения материала, потенциально увеличивая удержание и понимание с помощью мощности слухового обучения.

Live API обзор

Нам нужно место для хранения сгенерированных аудиофайлов. Облачное хранилище предоставляет масштабируемое и надежное решение.

👉 Управление хранилищем в консоли. Нажмите на «ведра» в левом меню. Нажмите кнопку «+ Создать» в верхней части.

👉configure Your Bucket:

  • Имя ведра: Aidemy-recap- <уникальный_name> Важно : убедитесь, что вы определяете уникальное имя ведра, которое начинается с « Aidemy-recap- ». Это уникальное имя имеет решающее значение для избежания конфликтов именования при создании ведра облачного хранилища.
  • Регион: us-central1 .
  • Класс хранения: «Стандарт». Стандарт подходит для часто доступных данных.
  • Контроль доступа: оставьте выбранный «Управление единообразным доступом». Это обеспечивает последовательный контроль доступа на уровне ведра.
  • Расширенные параметры: для этого семинара настройки по умолчанию обычно достаточно. Нажмите кнопку «Создать» , чтобы создать свое ведро.

Вы можете увидеть всплывающее окно о предотвращении общественного доступа. Оставьте флажок, проверенный и нажмите Confirm .

Теперь вы увидите свое недавно созданное ведро в списке ведра. Помните свое имя, вам понадобится позже.

👉in Терминал редактора облачного кода, запустите следующие команды, чтобы предоставить учетную запись услуги доступ к ведро:

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"

👉n В Редакторе облачного кода, откройте audio.py в папке course . Вставьте следующий код в конце файла:

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))
  • Потоковое соединение : во -первых, постоянное соединение устанавливается с конечной точкой API Live. В отличие от стандартного вызова API, где вы отправляете запрос и получаете ответ, это соединение остается открытым для непрерывного обмена данными.
  • Конфигурация Мультимодальная : используйте конфигурацию, чтобы указать, какой тип вывода вы хотите (в данном случае, аудио), и вы даже можете указать, какие параметры вы хотите использовать (например, выбор голоса, кодирование звука)
  • Асинхронная обработка : этот API работает асинхронно, что означает, что она не блокирует основной поток в ожидании завершения генерации звука. Обработка данных в режиме реального времени и отправляя вывод в куски, он обеспечивает почти мгновенный опыт.

Теперь ключевой вопрос: когда должен работать этот процесс генерации звука? В идеале мы хотим, чтобы Audio Recaps были доступны, как только создается новый план обучения. Поскольку мы уже внедрили архитектуру, управляемую событиями, опубликовав план обучения в паб/подменную тему, мы можем просто подписаться на эту тему.

Тем не менее, мы не очень часто генерируем новые планы обучения. Было бы не эффективно, чтобы агент постоянно работал и ожидал новых планов. Вот почему имеет смысл развернуть эту логику генерации звука в качестве функции Cloud Run.

Развертывая его как функцию, он остается бездействующим до тех пор, пока новое сообщение не будет опубликовано в паб/суб -тему. Когда это происходит, он автоматически запускает функцию, которая генерирует звуковые резюме и хранит их в нашем ведре.

👉 Ольтрасы папки course в файле main.py , этот файл определяет функцию Cloud Run, которая будет запускаться, когда будет доступен новый план обучения. Он получает план и инициирует генерацию звука. Добавьте следующий фрагмент кода в конце файла.

@functions_framework.cloud_event
def process_teaching_plan(cloud_event):
    print(f"CloudEvent received: {cloud_event.data}")
    time.sleep(60)
    try:
        if isinstance(cloud_event.data.get('message', {}).get('data'), str):  # Check for base64 encoding
            data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
            teaching_plan = data.get('teaching_plan') # Get the teaching plan
        elif 'teaching_plan' in cloud_event.data: # No base64
            teaching_plan = cloud_event.data["teaching_plan"]
        else:
            raise KeyError("teaching_plan not found") # Handle error explicitly

        #Load the teaching_plan as string and from cloud event, call audio breakup_sessions
        breakup_sessions(teaching_plan)

        return "Teaching plan processed successfully", 200

    except (json.JSONDecodeError, AttributeError, KeyError) as e:
        print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
        return "Error processing event", 500

    except Exception as e:
        print(f"Error processing teaching plan: {e}")
        return "Error processing teaching plan", 500

@functions_framework.cloud_event : Этот декоратор отмечает функцию как функцию Cloud Run, которая будет вызвана CloudEvents.

Тестирование локально

👉 Мы запустим это в виртуальной среде и установим необходимые библиотеки Python для функции 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

👉 Эмулятор функции Cloud Run позволяет нам тестировать нашу функцию локально, прежде чем развернуть ее в Google Cloud. Начните локальный эмулятор с работы:

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

👉 Хотя эмулятор работает, вы можете отправлять тестовые облачные средства в эмулятор для моделирования публикации нового плана обучения. В новом терминале:

Два терминала

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

Вместо того, чтобы искательно смотреть, ожидая ответа, переключитесь на другой терминал облачной оболочки. Вы можете наблюдать за прогрессом и любыми выводами или сообщениями об ошибках, сгенерированных вашей функцией в терминале эмулятора. 😁

Вернувшись во 2 -й терминал, вы должны увидеть, что он должен вернуть OK .

👉 Вы проверите данные в ведре, перейдите в облачное хранилище и выберите вкладку «Ведре», а затем aidemy-recap-xxx

Ведро

👉 В терминале, работающем с эмулятором, введите ctrl+c для выхода. И закрыть второй терминал. И закрыть второй терминал. и запустить деактивирование, чтобы выйти из виртуальной среды.

deactivate

Развертывание в Google Cloud

Обзор развертывания 👉fter тестирование локально, пришло время развернуть агента курса в Google Cloud. В терминале запустите эти команды:

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud functions deploy courses-agent \
  --region=us-central1 \
  --gen2 \
  --source=. \
  --runtime=python312 \
  --trigger-topic=plan \
  --entry-point=process_teaching_plan \
  --set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

Проверьте развертывание, пройдя Cloud Run в консоли Google Cloud. Вы должны увидеть новую службу с именем в списке курсов.

Облачный запуск списка

Чтобы проверить конфигурацию триггера, нажмите на сервис курсов-агентов, чтобы просмотреть ее данные. Перейдите на вкладку «Триггеры».

Вы должны увидеть триггер, настроенный для прослушивания сообщений, опубликованных в тему плана.

Cloud Run Trigger

Наконец, давайте посмотрим, как он работает в конце до конца.

👉next, нам нужно настроить агент портала, чтобы он знал, где найти сгенерированные аудиофайлы. В терминале беги:

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

👉 Посмотреть новый план преподавания на странице агента планировщика. Это может занять несколько минут, не волнуйтесь, это служба без сервера. Получите URL вашего агента планировщика (если у вас нет удобного запустить это в терминале):

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

После создания нового плана, подождите 2-3 минуты, чтобы создать звук, опять же, это займет еще несколько минут из-за ограничения с выставлением счетов с этим лабораторным счетом.

Вы можете отслеживать, получила ли функция courses-agent план преподавания, проверив вкладку «Триггеры» функции. Периодически освещать страницу; В конце концов вы должны увидеть, что функция была вызвана. Если функция не была вызвана через более 2 минут, вы можете попробовать снова создать план преподавания. Тем не менее, избегайте создания планов неоднократно в быстрой последовательности, так как каждый сгенерированный план будет последовательно потреблять и обрабатываться агентом, что потенциально создает отставание.

Триггер наблюдать

👉visit the Portal и нажмите на «Курсы». Вы должны увидеть три карты, каждая из которых отображает звуковой резюме. Чтобы найти URL вашего портального агента:

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

Нажмите «Играть» на каждом курсе, чтобы убедиться, что звуковые резюме соответствуют толькому плану обучения, который вы только что сгенерировали! Портальные курсы

Выйдите из виртуальной среды.

deactivate

13. Необязательно: сотрудничество на основе ролей с Gemini и DeepSeek

Наличие нескольких точек зрения неоценимо, особенно при создании привлекательных и вдумчивых заданий. Теперь мы построим многоагентную систему, которая использует две разные модели с различными ролями, для создания заданий: одна способствует сотрудничеству, а другая поощряет самообучение. Мы будем использовать архитектуру «одноразового», где рабочий процесс следует фиксированному маршруту.

Генератор назначений Близнецов

Обзор Близнецов Мы начнем с настройки функции Близнецов для создания заданий с помощью совместного акцента. Отредактируйте файл gemini.py , расположенный в папке assignment .

👉 Паста Следующий код в конце файла 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()

Он использует модель Близнецов для генерации назначений.

Мы готовы проверить агент Близнецов.

👉run эти команды в терминале для настройки среды:

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

👉 вы можете запустить, чтобы проверить это:

python gemini.py

Вы должны увидеть назначение, которое имеет больше групповой работы в результате вывода. Тест Assert в конце также выведет результаты.

Here are some engaging and practical assignments for each week, designed to build progressively upon the teaching plan's objectives:

**Week 1: Exploring the World of 2D Shapes**

* **Learning Objectives Assessed:**
    * Identify and name basic 2D shapes (squares, rectangles, triangles, circles).
    * .....

* **Description:**
    * **Shape Scavenger Hunt:** Students will go on a scavenger hunt in their homes or neighborhoods, taking pictures of objects that represent different 2D shapes. They will then create a presentation or poster showcasing their findings, classifying each shape and labeling its properties (e.g., number of sides, angles, etc.). 
    * **Triangle Trivia:** Students will research and create a short quiz or presentation about different types of triangles, focusing on their properties and real-world examples. 
    * **Angle Exploration:** Students will use a protractor to measure various angles in their surroundings, such as corners of furniture, windows, or doors. They will record their measurements and create a chart categorizing the angles as right, acute, or obtuse. 
....

**Week 2: Delving into the World of 3D Shapes and Symmetry**

* **Learning Objectives Assessed:**
    * Identify and name basic 3D shapes.
    * ....

* **Description:**
    * **3D Shape Construction:** Students will work in groups to build 3D shapes using construction paper, cardboard, or other materials. They will then create a presentation showcasing their creations, describing the number of faces, edges, and vertices for each shape. 
    * **Symmetry Exploration:** Students will investigate the concept of symmetry by creating a visual representation of various symmetrical objects (e.g., butterflies, leaves, snowflakes) using drawing or digital tools. They will identify the lines of symmetry and explain their findings. 
    * **Symmetry Puzzles:** Students will be given a half-image of a symmetrical figure and will be asked to complete the other half, demonstrating their understanding of symmetry. This can be done through drawing, cut-out activities, or digital tools.

**Week 3: Navigating Position, Direction, and Problem Solving**

* **Learning Objectives Assessed:**
    * Describe position using coordinates in the first quadrant.
    * ....

* **Description:**
    * **Coordinate Maze:** Students will create a maze using coordinates on a grid paper. They will then provide directions for navigating the maze using a combination of coordinate movements and translation/reflection instructions. 
    * **Shape Transformations:** Students will draw shapes on a grid paper and then apply transformations such as translation and reflection, recording the new coordinates of the transformed shapes. 
    * **Geometry Challenge:** Students will solve real-world problems involving perimeter, area, and angles. For example, they could be asked to calculate the perimeter of a room, the area of a garden, or the missing angle in a triangle. 
....

Остановитесь с ctl+c и очистите тестовый код. Удалить следующий код из gemini.py

import unittest

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

        updated_state = gen_assignment_gemini(initial_state)

        self.assertIn("model_one_assignment", updated_state)
        self.assertIsNotNone(updated_state["model_one_assignment"])
        self.assertIsInstance(updated_state["model_one_assignment"], str)
        self.assertGreater(len(updated_state["model_one_assignment"]), 0)
        print(updated_state["model_one_assignment"])


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

Настройте генератор заданий DeepSeek

В то время как облачные платформы ИИ удобны, самостоятельные LLM могут иметь решающее значение для защиты конфиденциальности данных и обеспечения суверенитета данных. Мы развертываем наименьшую модель DeepSeek (параметры 1,5B) на экземпляре вычислителя облака. Есть и другие способы, например, размещение его на платформе Google Vertex AI или размещение его в вашем экземпляре GKE, но, поскольку это всего лишь семинар для агентов искусственного интеллекта, и я не хочу держать вас здесь навсегда, давайте просто используем самый простой способ. Но если вы заинтересованы и хотите покопаться в других вариантах, посмотрите на файл deepseek-vertexai.py в папке назначения, где он предоставляет пример кода того, как взаимодействовать с моделями, развернутыми на Vertexai.

Обзор DeepSeek

👉run Это команда в терминале для создания самостоятельной платформы LLM Ollama:

cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
    --image-family=ubuntu-2204-lts \
    --image-project=ubuntu-os-cloud \
    --machine-type=e2-standard-4 \
    --zone=us-central1-a \
    --metadata-from-file startup-script=startup.sh \
    --boot-disk-size=50GB \
    --tags=ollama \
    --scopes=https://www.googleapis.com/auth/cloud-platform

Чтобы проверить запуск экземпляра вычислительного двигателя:

Перейдите, чтобы вычислить Engine > «экземпляры виртуальной машины» в консоли Cloud Google. Вы должны увидеть, что ollama-instance перечислен с зеленой галочкой, указывающей, что он работает. Если вы не видите этого, убедитесь, что зона является US-Central1. Если это не так, вам может потребоваться искать это.

Вычислить список двигателей

👉 Мы установим наименьшую модель DeepSeek и протестируйте ее, обратно в редактор облачных оболочков в новом терминале, запустите следующую команду SSH в экземпляр GCE.

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

После установления соединения SSH вам может быть предложено следующим образом:

"Хотите продолжить (Y/N)?"

Просто введите Y (нечувствителен к случаям) и нажмите Enter, чтобы продолжить.

Затем вас могут попросить создать фразу для ключа SSH. Если вы предпочитаете не использовать PassFrase, просто нажмите Enter дважды, чтобы принять по умолчанию (без пассийной фразы).

👉 теперь вы находитесь в вирусальной машине, вытащите наименьшую модель Deepseek R1 и проверяете, работает ли она?

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

Exexit экземпляр GCE введите следующее в терминале SSH:

exit

Закройте новый терминал, вернитесь к оригинальному терминалу.

👉 И не забудьте настроить сетевую политику, чтобы другие службы могли получить доступ к LLM, пожалуйста, ограничить доступ к экземпляру, если вы хотите сделать это для производства, либо реализовать вход в безопасность для службы или ограничить доступ к IP. Бегать:

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

Тведрись, чтобы убедиться, работает ли политика брандмауэра правильно, попробуйте запустить:

export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
curl -X POST "${OLLAMA_HOST}/api/generate" \
     -H "Content-Type: application/json" \
     -d '{
          "prompt": "Hello, what are you?",
          "model": "deepseek-r1:1.5b",
          "stream": false
        }'

Далее мы будем работать над функцией DeepSeek в агенте назначения, чтобы генерировать задания с индивидуальным рабочим акцентом.

👉edit deepseek.py в папке при assignment добавить следующий фрагмент до конца

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

👉Let's Test It запустив:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
python deepseek.py

Вы должны увидеть задание, которое имеет больше работы самоубийства.

**Assignment Plan for Each Week**

---

### **Week 1: 2D Shapes and Angles**
- **Week Title:** "Exploring 2D Shapes"
Assign students to research and present on various 2D shapes. Include a project where they create models using straws and tape for triangles, draw quadrilaterals with specific measurements, and compare their properties. 

### **Week 2: 3D Shapes and Symmetry**
Assign students to create models or nets for cubes and cuboids. They will also predict how folding these nets form the 3D shapes. Include a project where they identify symmetrical properties using mirrors or folding techniques.

### **Week 3: Position, Direction, and Problem Solving**

Assign students to use mirrors or folding techniques for reflections. Include activities where they measure angles, use a protractor, solve problems involving perimeter/area, and create symmetrical designs.
....

👉 Установите ctl+c , и очистить тестовый код. Удалить следующий код из deepseek.py

import unittest

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

        updated_state = gen_assignment_deepseek(initial_state)

        self.assertIn("model_two_assignment", updated_state)
        self.assertIsNotNone(updated_state["model_two_assignment"])
        self.assertIsInstance(updated_state["model_two_assignment"], str)
        self.assertGreater(len(updated_state["model_two_assignment"]), 0)
        print(updated_state["model_two_assignment"])


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

Теперь мы будем использовать одну и ту же модель Близнецов, чтобы объединить обе задания в новую. Отредактируйте файл gemini.py , расположенный в папке assignment .

👉 Паста Следующий код в конце файла gemini.py :

def combine_assignments(state):
    print(f"---------------combine_assignments ")
    region=get_next_region()
    client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
    response = client.models.generate_content(
        model=MODEL_ID, contents=f"""
        Look at all the proposed assignment so far {state["model_one_assignment"]} and {state["model_two_assignment"]}, combine them and come up with a final assignment for student. 
        """
    )

    state["final_assignment"] = response.text
    
    return state

Чтобы объединить сильные стороны обеих моделей, мы организуем определенный рабочий процесс с помощью Langgraph. Этот рабочий процесс состоит из трех шагов: во -первых, модель Близнецов генерирует задание, ориентированное на сотрудничество; Во -вторых, модель DeepSeek генерирует задание, подчеркивающее индивидуальную работу; Наконец, Близнецы синтезируют эти два назначения в одно всеобъемлющее задание. Поскольку мы предварительно определяем последовательность шагов без принятия решений LLM, это представляет собой единую пользовательскую оркестровку.

Langraph сочетает обзор

👉 Паста следующий код к концу файла main.py в папке 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()

Твращение изначально проверить функцию create_assignment и подтвердить, что рабочий процесс, объединяющий Gemini и DeepSeek, функционален, запустите следующую команду:

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

Вы должны увидеть что -то, что объединяет обе модели с их индивидуальной перспективой для изучения студентов, а также для работы студентов.

**Tasks:**

1. **Clue Collection:** Gather all the clues left by the thieves. These clues will include:
    * Descriptions of shapes and their properties (angles, sides, etc.)
    * Coordinate grids with hidden messages
    * Geometric puzzles requiring transformation (translation, reflection, rotation)
    * Challenges involving area, perimeter, and angle calculations

2. **Clue Analysis:** Decipher each clue using your geometric knowledge. This will involve:
    * Identifying the shape and its properties
    * Plotting coordinates and interpreting patterns on the grid
    * Solving geometric puzzles by applying transformations
    * Calculating area, perimeter, and missing angles 

3. **Case Report:** Create a comprehensive case report outlining your findings. This report should include:
    * A detailed explanation of each clue and its solution
    * Sketches and diagrams to support your explanations
    * A step-by-step account of how you followed the clues to locate the artifact
    * A final conclusion about the thieves and their motives

👉 Установите ctl+c , и очистить тестовый код. Удалить следующий код из main.py

import unittest

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


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

Генерировать назначение.png

Чтобы сделать процесс генерации назначений автоматическим и отзывчивым на новые планы обучения, мы используем существующую архитектуру, управляемую событиями. Следующий код определяет функцию Cloud Run (Generate_assignment), которая будет запускаться всякий раз, когда новый план преподавания публикуется в плане Pub/Sub Topic '.

👉 Добавьте следующий код до конца main.py :

@functions_framework.cloud_event
def generate_assignment(cloud_event):
    print(f"CloudEvent received: {cloud_event.data}")

    try:
        if isinstance(cloud_event.data.get('message', {}).get('data'), str): 
            data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
            teaching_plan = data.get('teaching_plan')
        elif 'teaching_plan' in cloud_event.data: 
            teaching_plan = cloud_event.data["teaching_plan"]
        else:
            raise KeyError("teaching_plan not found") 

        assignment = create_assignment(teaching_plan)

        print(f"Assignment---->{assignment}")

        #Store the return assignment into bucket as a text file
        storage_client = storage.Client()
        bucket = storage_client.bucket(ASSIGNMENT_BUCKET)
        file_name = f"assignment-{random.randint(1, 1000)}.txt"
        blob = bucket.blob(file_name)
        blob.upload_from_string(assignment)

        return f"Assignment generated and stored in {ASSIGNMENT_BUCKET}/{file_name}", 200

    except (json.JSONDecodeError, AttributeError, KeyError) as e:
        print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
        return "Error processing event", 500

    except Exception as e:
        print(f"Error generate assignment: {e}")
        return "Error generate assignment", 500

Тестирование локально

Перед тем, как развернуть в Google Cloud, это хорошая практика, чтобы проверить функцию Cloud Run локально. Это обеспечивает более быструю итерацию и легкую отладку.

Во -первых, создайте ведро облачного хранилища для хранения сгенерированных файлов назначения и предоставления учетной записи службы в ведро. Запустите следующие команды в терминале:

👉 Важно : убедитесь, что вы определяете уникальное имя задания_bucket , которое начинается с " Aidemy-Assignment- ". Это уникальное имя имеет решающее значение для избежания конфликтов именования при создании ведра облачного хранилища. (Замените <your_name> на любое случайное слово)

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

👉 и беги:

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gsutil mb -p $PROJECT_ID -l us-central1 gs://$ASSIGNMENT_BUCKET

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
    --member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
    --role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
    --member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
    --role "roles/storage.objectCreator"

👉now, запустите эмулятор функции Cloud Run:

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

👉 Хотя эмулятор работает в одном терминале, откройте второй терминал в облачной оболочке. Во втором терминале отправьте тестовый облачный эмулятор для моделирования нового учебного плана:

Два терминала

  curl -X POST \
  http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -H "ce-id: event-id-01" \
  -H "ce-source: planner-agent" \
  -H "ce-specversion: 1.0" \
  -H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
  -d '{
    "message": {
      "data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
    }
  }'

Вместо того, чтобы искательно смотреть, ожидая ответа, переключитесь на другой терминал облачной оболочки. Вы можете наблюдать за прогрессом и любыми выводами или сообщениями об ошибках, сгенерированных вашей функцией в терминале эмулятора. 😁

Это должно вернуть ОК.

Чтобы подтвердить, что назначение было успешно сгенерировано и сохранено, перейдите в Cloud Console Google и перейдите к хранилищу > «Облачное хранилище». Выберите созданное вами ведро aidemy-assignment . Вы должны увидеть текстовый файл с именем assignment-{random number}.txt в ведре. Нажмите на файл, чтобы загрузить его и проверить его содержимое. Это подтверждает, что новый файл содержит только что сгенерированное новое назначение.

12-01-Assignment Bucket

👉 В терминале, работающем с эмулятором, введите ctrl+c для выхода. И закрыть второй терминал. 👉also, в терминале, управляющем эмулятором, выходите из виртуальной среды.

deactivate

Обзор развертывания

👉next, мы развернем агента назначения в облако

cd ~/aidemy-bootstrap/assignment
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
export PROJECT_ID=$(gcloud config get project)
gcloud functions deploy assignment-agent \
 --gen2 \
 --timeout=540 \
 --memory=2Gi \
 --cpu=1 \
 --set-env-vars="ASSIGNMENT_BUCKET=${ASSIGNMENT_BUCKET}" \
 --set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
 --set-env-vars=OLLAMA_HOST=${OLLAMA_HOST} \
 --region=us-central1 \
 --runtime=python312 \
 --source=. \
 --entry-point=generate_assignment \
 --trigger-topic=plan 

Проверьте развертывание, отправляясь в Google Cloud Console, перейдите к Cloud Run. Вы должны увидеть новую службу с именем в списке. 12-03-функциональный список

С учетом того, что рабочий процесс генерации назначений теперь реализован и протестирован и развернут, мы можем перейти к следующему шагу: предоставление этих заданий доступными на студенческом портале.

14. Необязательно: сотрудничество на основе ролей с Gemini и Deepseek - Contr.

Динамическое поколение веб -сайта

Чтобы улучшить студенческий портал и сделать его более привлекательным, мы внедрим динамическое генерацию HTML для страниц назначения. Цель состоит в том, чтобы автоматически обновить портал с помощью свежего, визуально привлекательного дизайна всякий раз, когда генерируется новое назначение. Это использует возможности кодирования LLM для создания более динамичного и интересного пользовательского опыта.

14-01-Генератный-HTML

👉in Редактор облачных оболочек, отредактируйте файл render.py в папке portal , заменить

def render_assignment_page():
    return ""

С следующим фрагментом кода:

def render_assignment_page(assignment: str):
    try:
        region=get_next_region()
        llm = VertexAI(model_name="gemini-2.0-flash-001", location=region)
        input_msg = HumanMessage(content=[f"Here the assignment {assignment}"])
        prompt_template = ChatPromptTemplate.from_messages(
            [
                SystemMessage(
                    content=(
                        """
                        As a frontend developer, create HTML to display a student assignment with a creative look and feel. Include the following navigation bar at the top:
                        ```
                        <nav>
                            <a href="/">Home</a>
                            <a href="/quiz">Quizzes</a>
                            <a href="/courses">Courses</a>
                            <a href="/assignment">Assignments</a>
                        </nav>
                        ```
                        Also include these links in the <head> section:
                        ```
                        <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
                        <link rel="preconnect" href="https://fonts.googleapis.com">
                        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                        <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">

                        ```
                        Do not apply inline styles to the navigation bar. 
                        The HTML should display the full assignment content. In its CSS, be creative with the rainbow colors and aesthetic. 
                        Make it creative and pretty
                        The assignment content should be well-structured and easy to read.
                        respond with JUST the html file
                        """
                    )
                ),
                input_msg,
            ]
        )

        prompt = prompt_template.format()
        
        response = llm.invoke(prompt)

        response = response.replace("```html", "")
        response = response.replace("```", "")
        with open("templates/assignment.html", "w") as f:
            f.write(response)


        print(f"response: {response}")

        return response
    except Exception as e:
        print(f"Error sending message to chatbot: {e}") # Log this error too!
        return f"Unable to process your request at this time. Due to the following reason: {str(e)}"

Он использует модель Близнецов для динамического генерации HTML для назначения. Он принимает контент назначения в качестве ввода и использует подсказку для обучения Близнецов для создания визуально привлекательной HTML -страницы с творческим стилем.

Далее мы создадим конечную точку, которая будет инициирована всякий раз, когда в ведре назначения добавляется новый документ:

👉 В течение папки портала отредактируйте файл app.py и добавьте следующий код в ## Add your code here" comments после функции new_teaching_plan :

## Add your code here

@app.route('/render_assignment', methods=['POST'])
def render_assignment():
    try:
        data = request.get_json()
        file_name = data.get('name')
        bucket_name = data.get('bucket')

        if not file_name or not bucket_name:
            return jsonify({'error': 'Missing file name or bucket name'}), 400

        storage_client = storage.Client()
        bucket = storage_client.bucket(bucket_name)
        blob = bucket.blob(file_name)
        content = blob.download_as_text()

        print(f"File content: {content}")

        render_assignment_page(content)

        return jsonify({'message': 'Assignment rendered successfully'})

    except Exception as e:
        print(f"Error processing file: {e}")
        return jsonify({'error': 'Error processing file'}), 500


## Add your code here

При срабатывании он получает имя файла и имя ведра из данных запроса, загружает содержимое назначения из облачного хранилища и вызывает функцию render_assignment_page для генерации HTML.

👉 Мы пойдем вперед и запускаем его на местном уровне:

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

👉 Из меню «Интернет -предварительный просмотр» в верхней части окна облачной оболочки выберите «Предварительный просмотр на порту 8080». Это откроет ваше приложение на новой вкладке браузера. Перейдите к ссылке на задание в панели навигации. На этом этапе вы должны увидеть пустую страницу , которая является ожидаемым поведением, поскольку мы еще не установили мост связи между агентом назначения и порталом для динамического заполнения контента.

14-02-прерывание

Wto включить эти изменения и развернуть обновленный код, перестроить и нажмите на изображение агента портала:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

👉 Нажатие нового изображения, переведите сервис Cloud Run. Запустите следующий скрипт, чтобы обеспечить обновление Cloud Run:

export PROJECT_ID=$(gcloud config get project)
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud run services update aidemy-portal \
    --region=us-central1 \
    --set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉now, мы развертываем триггер Eventarc, который прослушивает любой новый объект, созданный (завершен) в ведре назначения. Этот триггер автоматически вызовет конечную точку /render_assignment в службе портала при создании нового файла назначения.

export PROJECT_ID=$(gcloud config get project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$(gcloud storage service-agent --project $PROJECT_ID)" \
  --role="roles/pubsub.publisher"
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud eventarc triggers create portal-assignment-trigger \
--location=us-central1 \
--service-account=$SERVICE_ACCOUNT_NAME \
--destination-run-service=aidemy-portal \
--destination-run-region=us-central1 \
--destination-run-path="/render_assignment" \
--event-filters="bucket=$ASSIGNMENT_BUCKET" \
--event-filters="type=google.cloud.storage.object.v1.finalized"

Чтобы убедиться, что спусковой крючок был успешно создан, перейдите на страницу Tevelarc Triggers в консоли Google Cloud. Вы должны увидеть, что в таблице перечислен portal-assignment-trigger . Нажмите на имя триггера, чтобы просмотреть его данные. Назначение триггер

Это может занять до 2-3 минут, чтобы новый триггер стал активным.

Чтобы увидеть генерацию динамического назначения в действии, запустите следующую команду, чтобы найти URL -адрес вашего агента планировщика (если у вас нет удобства):

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

Найдите URL своего агента портала:

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

В агенте планировщика создайте новый план преподавания.

13-02-повышение

Через несколько минут (чтобы завершить генерацию звука, генерация заданий и рендеринг HTML), перейдите к студенческому порталу.

👉Click on the "Assignment" link in the navigation bar. You should see a newly created assignment with a dynamically generated HTML. Each time a teaching plan is generated it should be a dynamic assignment.

13-02-assignment

Congratulations on completing the Aidemy multi-agent system ! You've gained practical experience and valuable insights into:

  • The benefits of multi-agent systems, including modularity, scalability, specialization, and simplified maintenance.
  • The importance of event-driven architectures for building responsive and loosely coupled applications.
  • The strategic use of LLMs, matching the right model to the task and integrating them with tools for real-world impact.
  • Cloud-native development practices using Google Cloud services to create scalable and reliable solutions.
  • The importance of considering data privacy and self-hosting models as an alternative to vendor solutions.

You now have a solid foundation for building sophisticated AI-powered applications on Google Cloud!

15. Challenges and Next Steps

Congratulations on building the Aidemy multi-agent system! You've laid a strong foundation for AI-powered education. Now, let's consider some challenges and potential future enhancements to further expand its capabilities and address real-world needs:

Interactive Learning with Live Q&A:

  • Challenge: Can you leverage Gemini 2's Live API to create a real-time Q&A feature for students? Imagine a virtual classroom where students can ask questions and receive immediate, AI-powered responses.

Automated Assignment Submission and Grading:

  • Challenge: Design and implement a system that allows students to submit assignments digitally and have them automatically graded by AI, with a mechanism to detect and prevent plagiarism. This challenge presents a great opportunity to explore Retrieval Augmented Generation (RAG) to enhance the accuracy and reliability of the grading and plagiarism detection processes.

aidemy-climb

16. Clean up

Now that we've built and explored our Aidemy multi-agent system, it's time to clean up our Google Cloud environment.

  1. Delete Cloud Run services
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
  1. Delete Eventarc trigger
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
  1. Delete Pub/Sub topic
gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet
  1. Delete Cloud SQL instance
gcloud sql instances delete aidemy --quiet
  1. Delete Artifact Registry repository
gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet
  1. Delete Secret Manager secrets
gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet
  1. Delete Compute Engine instance (if created for Deepseek)
gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet
  1. Delete the firewall rule for Deepseek instance
gcloud compute firewall-rules delete allow-ollama-11434 --quiet
  1. Delete Cloud Storage buckets
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

aidemy-broom