Как я построил AI-генератор креативных стратегий на Google Apps Script: архитектура, челленджи и решения

TL;DR: Я автоматизировал процесс создания креативных стратегий, используя Google Apps Script как оркестратор, Gemini API как движок вывода и Google Slides/Docs как шаблонизатор PDF. Результат: полностью serverless-пайплайн с маржой >300%, который доставляет ценность асинхронно. В статье — архитектура, код и решения технических проблем.

Введение: Почему креатив — это инженерная задача

Рекламные тендеры — это боль. Агентства тратят сотни часов на брейнштормы, а клиенты часто выбирают самые безопасные идеи. Я задался вопросом: можно ли заменить хаос креативного поиска системой?Моя гипотеза: если декомпозировать успешные кейсы на атомарные механизмы, их можно алгоритмизировать. Не чтобы заменить креатора, а чтобы дать ему инструмент для навигации в пространстве смыслов.Так родился Ad Concept Compiler — генератор стратегий, который:

  • Принимает структурированный бриф через Google Form
  • Анализирует контекст через авторскую таксономию (47 механизмов)
  • Генерирует JSON-структуру через Gemini API
  • Рендерит два артефакта: Pitch Deck (Slides) и Text Brief (Docs)
  • Отправляет результат на email за 2–3 минуты

Всё это работает на Google Apps Script — бесплатно, без серверов, с полной автоматизацией.

🏗 Архитектура системы: Data Flow

Google Form ↓ (onFormSubmit trigger) Google Apps Script (Orchestrator) ↓ 1. extractBrief() → парсинг ответов 2. filterTaxonomy() → скоринг механизмов под бриф 3. buildPrompt() → формирование контекстного промпта 4. callGemini() → вызов API с retry-логикой 5. generatePitchDeck() / generateTextBriefPdf() → рендеринг PDF 6. sendDualFormatEmail() → доставка результата ↓ Google Drive (папка с PDF) + Gmail (письмо клиенту)

Ключевая особенность: асинхронность. Сразу после отправки формы пользователь получает письмо «В работе», а через 2–3 минуты — финальный результат с вложениями. Это критично для UX, так как генерация через LLM может занимать время.

🔧 Ключевые технические решения

1. Умный скоринг таксономии: фильтрация шума перед AI

Проблема: если скормить модели все 47 механизмов, она начнет «галлюцинировать» или выбирать случайные.Решение: функция filterTaxonomy() анализирует сигналы из брифа и присваивает веса каждому механизму.

function filterTaxonomy(taxonomy, brief) { const signals = { isReels: /reels|tiktok|shorts/i.test(brief.channel), isLowBudget: /маленький|low|низкий/i.test(brief.budget), isFood: /кофе|food|еда|напит/i.test(brief.product), // ... еще 15+ сигналов }; // Скоринг: повышение баллов за релевантность let scored = taxonomy.map(t => { let score = 0; if (signals.isReels && /reels|тикток|вертикаль/i.test(t.whenToUse)) { score += 4; } if (signals.isFood && /натурализац|ритуал/i.test(t.semiotic_essence)) { score += 3; } // ... штрафы за нерелевантность return { ...t, score }; }); // Возвращаем топ-8 сбалансированных механизмов return scored.sort((a,b) => b.score - a.score).slice(0, 8); }

Результат: Модель получает не «всё подряд», а релевантный контекст. Это повышает качество вывода и снижает токен-расход.

2. Работа с Gemini API: стабильность через retry и парсинг

Проблема: API может возвращать ошибки High Demand, а модель — «грязный» JSON с markdown-обертками.Решение: цикл повторных попыток + многоступенчатый парсинг.

function callGemini(prompt) { // Retry-логика: до 3 попыток при ошибках for (var attempt = 1; attempt <= 3; attempt++) { try { var res = UrlFetchApp.fetch(url, { /* config */ }); var json = JSON.parse(res.getContentText()); // Обработка специфичных ошибок API if (json.error?.message?.includes('high demand')) { Utilities.sleep(10000); // Ждем и пробуем снова continue; } break; } catch (e) { if (attempt < 3) Utilities.sleep(5000); } } // Парсинг JSON: 3 уровня устойчивости var raw = json.candidates[0].content.parts[0].text.trim(); raw = raw.replace(/^```json\s*/i, '').replace(/\s*```$/i, ''); // Попытка 1: прямой парсинг try { return JSON.parse(raw); } catch(e1) {} // Попытка 2: экстракция JSON из текста try { var extracted = raw.substring(raw.indexOf('{'), raw.lastIndexOf('}')+1); return JSON.parse(extracted); } catch(e2) {} return null; // Fallback }

Результат: Система устойчива к временным сбоям API и «кривому» выводу модели.

3. Генерация PDF через шаблоны: почему не HTML-to-PDF?

Проблема: GAS не имеет нативного HTML-to-PDF конвертера с поддержкой кириллицы и сложной верстки.Решение: использовать Google Slides/Docs как шаблонизатор.

function generatePitchDeck(pitchData, brief) { // 1. Копируем шаблон var copy = DriveApp.getFileById(TEMPLATE_ID).makeCopy(...); var presentation = SlidesApp.openById(copy.getId()); var slides = presentation.getSlides(); // 2. Заменяем плейсхолдеры с авто-масштабированием replaceInSlideAutoFit(slides[2], { '{{concept_name}}': con.concept_name, '{{consumer_insight}}': sf.consumer_insight, // ... }); // 3. Динамические визуальные акценты addRiskBar(slides[2], riskColor); // цветная полоса по уровню риска // 4. Конвертация в PDF и сохранение в папку var pdfBlob = copy.getAs('application/pdf'); return createPdfInFolder(pdfBlob, fileName, CONFIG.OUTPUT_FOLDER_ID); }

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

Решение: функция replaceAndFit() динамически уменьшает шрифт:

function replaceAndFit(shape, key, value) { // ... замена текста ... var len = newText.length; var textStyle = textRange.getTextStyle(); // Эвристика: чем длиннее текст, тем меньше шрифт if (len > 800) textStyle.setFontSize(8); else if (len > 500) textStyle.setFontSize(9); else if (len > 300) textStyle.setFontSize(10); // ... }

Результат: PDF всегда выглядит аккуратно, независимо от длины сгенерированного контента.

💻 Code Highlights: что стоит посмотреть

Оркестрация: onFormSubmitHandler

function onFormSubmitHandler(e) { var brief = extractBrief(e.response.getItemResponses()); sendStartEmail(brief.email, brief.brand); // Асинхронное уведомление var taxonomy = getTaxonomyFromSheet(); var filtered = filterTaxonomy(taxonomy, brief); var pitchData = callGemini(buildPrompt(brief, filtered)); // Параллельная генерация двух артефактов var pitchPdf = generatePitchDeck(pitchData, brief); var textPdf = generateTextBriefPdf(pitchData, brief); sendDualFormatEmail(brief.email, brand, title, pitchPdf, textPdf); }

Зачем: Разделение ответственности, обработка ошибок на каждом этапе, асинхронная коммуникация с пользователем.

Промпт-инжиниринг: buildPrompt

Ключевые элементы промпта:

  • Role priming: «You are a Senior Creative at Wieden+Kennedy»
  • Structured output: жесткий JSON-схем с примерами
  • Anti-banality gate: чек-лист из 5 пунктов для самопроверки модели
  • Language constraint: «ALL OUTPUT MUST BE IN RUSSIAN»
'ANTI-BANALITY GATE (MANDATORY CHECK):\n' + '[ ] Insight is about humans, not product?\n' + '[ ] No smiling people looking at camera?\n' + // ... 'If ANY check fails -> REVISE before output.\n\n' +

Результат: Модель генерирует не «текст», а структурированные данные, готовые к рендерингу.

🛠 Troubleshooting: с чем пришлось повозиться

1. Лимиты времени выполнения GAS (6 минут)

Проблема: Генерация + рендеринг PDF может занять больше 6 минут, особенно при перегрузке API.

Решение:

  • Оптимизация промпта (сокращение токенов)
  • Retry-логика с экспоненциальной задержкой
  • Асинхронная отправка «стартового» письма, чтобы пользователь не ждал в тишине

2. Кодировка и переносы строк в PDF

Проблема: При замене текста в Slides/Docs длинные ссылки или кириллические слова могли «ломаться» при экспорте в PDF.

Решение:

  • Использование коротких ссылок (bit.ly) для CTA
  • Растягивание текстовых блоков в шаблоне, чтобы ссылка помещалась в одну строку
  • Экранирование спецсимволов в плейсхолдерах: key.replace(/[.*+?^${}()|[\]\\]/g, '\\')

3. Стабилизация вывода модели

Проблема: При temperature: 0.7 модель иногда генерировала разные форматы JSON.

Решение:

  • Жесткий JSON-схем в промпте с примерами
  • Многоступенчатый парсинг (прямой → экстракция → fallback)
  • Валидация обязательных полей перед рендерингом

📈 Итоги и метрики MVP

Как я построил AI-генератор креативных стратегий на Google Apps Script: архитектура, челленджи и решения

Что работает хорошо:

  • Полная автоматизация: от формы до PDF в почте
  • Гибкость: легко добавить новые механизмы в таксономию
  • Масштабируемость: GAS выдерживает ~20 запросов/час без проблем

Что требует доработки:

  • Обработка edge-cases в парсинге JSON (модель иногда генерирует «почти JSON»)
  • Улучшение авто-масштабирования текста в Slides (сейчас эвристика по длине строки)
  • Добавление кэширования таксономии для ускорения filterTaxonomy()

Заключение: Serverless AI — это реально

Ad Concept Compiler доказывает, что для создания сложного AI-продукта не нужны Kubernetes, микросервисы и команда DevOps. Google Apps Script + Gemini API + Google Workspace — это полноценный стек для MVP с нулевой инфраструктурной сложностью.

Ключевые инсайты:

  1. Контекст важнее модели. Правильно подготовленный промпт с таксономией дает лучший результат, чем «просто попросить AI придумать идею».
  2. Асинхронность — must-have. Для LLM-приложений с задержкой критично информировать пользователя о статусе.
  3. Шаблонизация через нативные инструменты (Slides/Docs) надежнее, чем кастомные HTML-to-PDF решения в GAS.

Проект открыт для тестирования. Если вы маркетолог, стратег или разработчик — попробуйте сгенерировать концепт для своего бренда и дайте фидбек.
👉 Попробовать Ad Concept Compiler

Код проекта (без таксономии) доступен по запросу. Если хотите обсудить архитектуру, парсинг JSON или авто-масштабирование в GAS — пишите в комментариях.

Об авторе: Системный аналитик и исследователь рекламы. Специализируюсь на декомпозиции сложных систем, построении таксономий и автоматизации бизнес-процессов. Разрабатываю интеллектуальные продукты для маркетинга с фокусом на автоматизацию и нулевую инфраструктурную сложность.

11