Щасливого Нового року та нового посту! Останнього разу ми ознайомились з тим, як взаємодіяти з API Gemini з Python і створювали чат-бота. Цього разу давайте розв’яжемо задачу ще глибше і знайдемо спосіб дозволити користувачу описати картину, а Vertex AI створить відповідне зображення. І цього разу ми побачимо, як розгорнути це на вебі, щоб інші могли скористатися.
Цей пост намагається бути доступним для початківців, але все ж корисним для досвідчених розробників. Якщо ви вже знайомі з написанням веб-додатків на Flask, можете пропустити до розділу Генерація зображення і працювати з того місця.
Налаштування
Якщо ви слідували останній пост і самостійно створили чат-бота, ви вже створили необхідне середовище для цього прикладу. Вам потрібен обліковий запис Google Cloud і проект для роботи. Ви будете працювати в “локальному” середовищі Python із необхідними інструментами, встановленими в ньому. Це означає нову папку та нове віртуальне середовище Python. (Чому "локальне" в лапках? Тому що я маю на увазі використання Cloud Shell як "локальне", оскільки він працює майже як оболонка на вашій машині, але з більшістю необхідних компонентів вже на місці.)
Створіть нову робочу папку та налаштуйте проект за допомогою gcloud config, а також створіть порожні файли для роботи: main.py для коду Python та requirements.txt для пакетів, які будуть необхідні:
mkdir demo-image-generation
cd demo-image-generation
gcloud config set project silver-origin-447121-e9
touch main.py
touch requirements.txt
ID проекту, зазначений вище, є випадковим і згенерований Google Cloud, оскільки вибраний мною проект demo-image-generation вже був зайнятий.
Структура додатку
Ви створюватимете веб-додаток на Python за допомогою Flask. Це має бути одна сторінка: редаговане текстове поле для введення опису (називається prompt в AI) і згенероване зображення. Коли користувач відправить форму на цій сторінці, ваш додаток зчитає опис, викликає необхідне API і поверне нову сторінку, що містить нове зображення. Користувач зможе потім уточнити опис і відправити нове для генерації іншого зображення.
Воно повинно виглядати ось так:
Опис “милого щеняти” має бути тим, що ввів користувач, а сірий блок — це саме згенероване зображення. Основна веб-сторінка для цього виглядатиме так:
Demo Image Generation
``` Вашому додатку потрібно буде вставити правильні значення для **{{prompt}}**, який ввів користувач, та **{{image_url}}**, яке ми згенерували, тому ми будемо розглядати цей HTML як шаблон для поверненої сторінки. Створіть папку з назвою **templates** і збережіть вище наданий код як **index.html**. Тепер давайте поглянемо на код Python. Ви будете використовувати [фреймворк Flask](https://flask.palletsprojects.com/en/stable/), який є потужним і простим, поєднання, яке я дійсно ціную. Основна програма на Flask виглядатиме так: ``` from flask import Flask app = Flask(__name__) # Обробники запитів будуть тут if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=8080) ``` Перші два рядки імпортують бібліотеку Flask і створюють об'єкт Flask в глобальній змінній **app**. Після цього йдуть обробники запитів, які описують, як обробляти запити для різних шляхів, таких як домашня сторінка — ми подивимося на них через хвилину.
І останній розділ говорить, що якщо цей код запускається безпосередньо, а не імпортується іншою програмою, то слід запустити об'єкт Flask **app** в режимі налагодження.
Цей код може працювати самостійно або бути імпортованим в інший веб-сервер, який є більш масштабованим і продуктивним. У такому випадку інший веб-сервер буде викликати змінну **app** цього модуля для обробки запитів для нього. Це відбувається в Cloud Run, який ми будемо використовувати для розгортання цього на вебі в наступному розділі.
Продовжуйте та вставте вищезгаданий код у ваш файл **main.py**, а також додайте рядок нижче до **requirements.txt**, після чого встановіть модулі та запустіть програму:
pip install -r requirements.txt
python main.py
```
Коли ви відкриєте сторінку цього сервера, ви отримаєте відповідь 404 Not Found. Це тому, що програма поки що не вказує, як обробляти запити для будь-якої сторінки. Давайте додамо це до каркасної програми, перш ніж переходити до реальної функціональності. Коли буде запитана домашня сторінка, програма повинна повернути файл index.html, який ми показали раніше.
Ви оголосите обробник запиту за допомогою декоратора Flask app.route. Цей декоратор змусить будь-яку функцію, яка оголошена безпосередньо після нього, виконуватись кожного разу, коли запитується вказаний шлях. Ось код:
@app.route("/"):
def home():
return render_template("index.html")
Це оголошує, що запити до / (домашня сторінка) викликають функцію home і повертають її результат в браузер. У цьому випадку результатом буде вміст templates/index.html. Додайте цю функцію до вашої програми main.py і змініть початкове імпортоване речення на:
from flask import Flask, render_template
Тепер спробуйте знову запустити. Ви повинні побачити сторінку, схожу на макет, який був показаний раніше. Там не буде заповненого prompt або зображення, тому що функція render_template замінить їх на порожні значення, оскільки ми не передали жодних значень для них. Ви виправите це в наступному розділі.
Обробник домашньої сторінки
Коли домашня сторінка буде викликана за допомогою запиту POST, що вказує на відправку форми, обробник повинен отримати prompt з форми, згенерувати зображення для нього та повернути сторінку index.html з цим prompt і URL зображення, заповненими.
Програма може отримати наданий prompt з форми за допомогою об'єкта Flask request. Вам потрібно змінити перший рядок, щоб також імпортувати його:
from flask import Flask, render_template, request
Функція може заповнювати місця для шаблонів у файлі index.html, надаючи правильні значення як параметри для render_template. Отже, ваш код виглядатиме так:
@app.route("/", methods=["GET", "POST"])
def home():
if request.method == "GET":
# Форма не була відправлена
return render_template("index.html")
# POST з відправленою формою
prompt = request.form["prompt"]
image_data = generate_image(prompt)
image_url = get_url(image_data)
return render_template("index.html", prompt=prompt, image_url=image_url)
У коді вище є кілька нових моментів. По-перше, цей обробник тепер отримує запити як HTTP GET (отримати сторінку) і POST (відправити форму). Без властивості methods в декораторі @app.route, Flask би приймав тільки GET-запити; POST-запити отримували б відповідь 405 Method Not Allowed.
По-друге, обробник перевіряє, чи є це GET-запитом. Для GET-запиту він просто повертає порожній шаблон, як було раніше. Але для POST-запиту він використовує об'єкт Flask request, щоб отримати значення наданого prompt в формі. Потім генерується зображення, і сторінка повертається з заповненими prompt і URL зображення. Ось відповідь на запит “милого щасливого чорно-білого шіх-цу цуценя” з завершеною програмою (з наступного розділу):
Далі йде реальна робота з генерацією зображення.
Але в дусі поступових кроків, давайте просто створимо заповнювачі для важкої роботи з генеруванням зображення та створенням URL для нього:
def generate_image(prompt):
return b"" # Порожній байтовий рядок
def get_url(image_data):
return ""
Оскільки generate_image повинна повертати бінарні дані, заповнювач повертає байтовий рядок замість звичайного рядка.
Оновіть файл main.py, додавши вищезгаданий код, і спробуйте запустити його знову. Перший запит до домашньої сторінки має повернути порожню форму, а після її відправки повернеться сторінка з заповненим prompt. Вона також поверне URL зображення, але оскільки це порожнє, зображення не відобразиться.
Генерація зображення
Готові до основної частини цього посту — генерації зображення на основі запиту? Ось, нарешті, вона!
Функція буде використовувати Google Cloud Vertex AI бібліотеку для генерації зображення, тому потрібно додати імпорт цього модуля на початку програми. Також використовується стандартна бібліотека Python tempfile:
from vertexai.preview.vision_models import ImageGenerationModel
import tempfile
Оскільки це не стандартна бібліотека, її також потрібно буде встановити, тому додайте цей рядок до requirements.txt:
vertexai
А тепер код для створення зображення:
def generate_image(prompt):
model = ImageGenerationModel.from_pretrained("imagegeneration@006")
response = model.generate_images(prompt=prompt)[0]
with tempfile.NamedTemporaryFile("wb") as f:
filename = f.name
response.save(filename, include_generation_parameters=False)
with open(filename, "rb") as image_file:
binary_image = image_file.read()
return binary_image
Фактична генерація зображення AI відбувається в перших двох рядках. ImageGenerationModel.from_pretrained створює об'єкт моделі з вже збереженої, попередньо навченої моделі. Повернута модель має метод generate_images, який використовує модель для створення зображень на основі запиту. Цей метод повертає ітерабельний список з кількістю зображень, які були запитані, за замовчуванням — одне зображення. Отже, елемент 0 цього списку є об'єктом, який представляє згенероване зображення.
Це одразу піднімає питання: де взяти попередньо навчені моделі? Виглядає так, що imagegeneration@006 є однією з них, але звідки це дивне ім'я? Воно є в документації з генерації зображень Vertex AI Google, хоча для того, щоб знайти його, потрібно трошки покопатися. Документація AI неймовірно широка і глибока, тому це може бути однією з найбільш корисних підказок у цьому блозі для вас!
Як тільки функція отримує цей об'єкт зображення, потрібно отримати бінарні вмісти цього зображення, щоб повернути їх викликачеві функції. Документація для об'єкта показує єдиний спосіб отримати вміст: зберегти його у файл. Мене здивувало, що не існує більш прямого способу зробити це, але можливо майбутнє оновлення бібліотеки вирішить це питання. У будь-якому випадку, наразі зберігання в файл — єдиний спосіб, що і робить решта функції, записуючи в тимчасовий файл, а потім читаючи його в бінарному режимі.
Створення URL
Код повинен створити URL, що вказує на зображення. Це буде трохи складно, адже зображення знаходиться лише в пам'яті, і наразі немає налаштованого веб-сервера, який би повертав його. І веб-сторінка зазвичай буде отримувати зображення за окремим запитом, і локальна змінна з даними зображення не буде існувати в тому контексті. Можливо, вона навіть не існуватиме, оскільки ми сподіваємося розгорнути це на Cloud Run, безсерверній платформі. Є ймовірність, що кожен окремий веб-запит може оброблятися окремим сервером, тому навіть якщо дані зображення збережені у файлі, цей файл може бути відсутнім на сервері, який отримує URL.
Програма не може зберігати дані зображення ані в пам'яті, ані в файловій системі свого сервера. Хоча збереження в файловій системі зазвичай працює в реальному світі, що в певному сенсі навіть гірше, ніж коли це взагалі не працює.
Це призвело б до очікування, яке не виправдалося б саме тоді, коли це було б найважливіше.
Отже, як отримати зображення в веб-браузер? Програма могла б зберігати дані зображення поза веб-сервером, наприклад, у хмарному сховищі Google Cloud. Але вам доведеться організувати їх очистку в майбутньому та переконатися, що кожне зображення має унікальне ім’я, щоб браузери не отримували зображення з інших сторінок. Або, можна вбудувати все зображення в URL, який потім буде вставлений у веб-сторінку!
Data URLs не дуже відомі, але це чудовий інструмент для безсерверних застосунків, які створюють та повертають зображення. Коли веб-сервер отримує запит на створення зображення, він може створити URL, який містить зображення, та повернути його як частину веб-сторінки. Тоді буде лише один HTTP-запит — той, який відправляє форму для запиту зображення. Коли браузер має відобразити зображення, дані будуть вже вбудовані в саму сторінку.
Формат Data URL складається з ключового слова data, після якого йде двокрапка, потім тип вмісту, крапка з комою, ключове слово base64, кома і потім base64 закодовані дані, наприклад:
data:image/png;base64,YWJjZGVm
Звісно, реальний Data URL для файлу зображення буде значно більшим. Це нормально, Python, веб-сервери та веб-браузери працюватимуть з ним без проблем.
Ось код для створення Data URL для бінарних даних зображення:
def get_url(image_data):
base64_image = base64.b64encode(image_data).decode("utf-8")
content_type = "image/png"
image_uri = f"data:{content_type};base64,{base64_image}"
return image_uri
Цей код використовує стандартну бібліотеку base64 Python, тому потрібно додати рядок імпорту на початку файлу:
import base64
Завершена програма
Ось і всі частини. Поєднавши їх, ви отримаєте програму для генерації зображень на основі описів, які ви вводите. Повний код також доступний у цьому репозиторії GitHub.
Хочете, щоб ваш друг використовував програму? Тоді йому потрібно буде створити власний обліковий запис Google Cloud або, можливо, зайти та скористатися вашим ноутбуком, на якому ви залогінені. Це менш ідеально. Останній крок сьогодні — це розгорнути цю програму в Інтернеті, щоб ви могли поділитися нею. Існує багато способів зробити це, але ми використаємо Cloud Run.
Розгортання на Cloud Run
Після всього цього крок може бути неприємно простим. Запустіть цю команду у вашій оболонці:
gcloud run deploy
Відповідайте на всі запитання. За замовчуванням все буде працювати, за винятком кінця, коли вас запитають, чи дозволяти неавтентифіковані виклики. Відповідайте Y замість стандартного N.
Зачекайте хвилинку — чи справді ви хочете дозволити всім використовувати вашу програму? Пам’ятайте, що ви будете за це платити. Напевно, ви хочете обмежити доступ до цього лише для вибраних осіб?
Мабуть, так, але обмеження для автентифікації на Cloud Run не може це зробити. Якщо ви не дозволите неавтентифіковані виклики, жоден веб-браузер не зможе звертатися до вашого сервісу. Це тому, що механізм автентифікації Cloud Run призначений для використання іншими програмами, які підключаються до вашого сервісу Cloud Run як API, а не для користувачів, що працюють через веб-браузери.
Ви можете захистити свій сервіс за допомогою екрану входу, але це значно складніше, ніж може здатися. Можливо, я напишу пост про це найближчим часом. Однак концепції, але не те, як все це об'єднати для вашого сервісу, описані в цьому моєму блозі.
Повертаючись до основної мети цього посту, після того як ви розгорнете цю програму на Cloud Run, ви отримаєте веб-адресу, якою може скористатися будь-хто, щоб створювати зображення за допомогою вашої програми. За ваш рахунок. Це приводить нас до заключної частини посту.
Очищення
Ваша програма буде генерувати певні витрати щоразу, коли хтось її використовує.
Якщо це вам не підходить, ви можете або використати консоль хмари для видалення створеного сервісу, або перейти на панель управління та закрити весь проект, який ви створили для цього. Закриття проекту є найбільш надійним способом переконатися, що з нього більше не буде нараховано жодних витрат.
Сподіваюся, цей дуже детальний пост був для вас корисним. Зазвичай я не включатиму кожен дрібний крок, як це зробив тут, але коли я вперше стикався з цими штучними інтелектами, деякі, здавалося б, прості кроки справляли на мене деяке непорозуміння. Я намагався все це прояснити тут.
Спочатку опубліковано на http://engelke.dev 10 січня 2025 року.
Перекладено з: Using AI image generation