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

Так родилась идея нашего ИИ-чат-бота, который теперь берёт на себя всю рутину и создаёт презентации, от которых клиенты в восторге. Расскажу, как мы к этому пришли и что из этого получилось.

молодой немец и логотип gamma_gpt_bot
молодой немец и логотип gamma_gpt_bot

О чем вообще речь

В конце лета мы запустили ии чат бот в телеграме @gamma_gpt_bot и поначалу было не понятно для кого. Было страшно его делать, потому что совсем не понятно как продвигать эффективно, на какие сценарии ориентироваться, какие фичи добавлять.

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

Раннее развитие бота и открытие возможности создавать презентации

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

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

Во-первых, 100 символов это очень мало, когда мы говорим об ответах больших языковых моделей. Они любят изрядно налить воды в сообщение и рассказать обо всех своих мыслях и написать по два дисклеймера в ходе решения поставленной задачи.

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

В-третьих, и самое главное, при большом числе пользователей все сообщения перемешиваются и невозможно проследить диалог.

галлюцинация при просьбе создать презентацию
галлюцинация при просьбе создать презентацию

Уже в это время появились ответы в духе: "я не могу создавать презентации, но могу дать план для неё..."

Доработка логирования

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

Захотелось за вечер, чтобы не тратить время команды, как-то расширить количество информации в системных сообщениях. И в контексте чата с уведомлениями это действительно выглядело довольно странно.

К счастью, у меня есть экспертиза в разработке интерфейсов. И я давно ждал идеального случая применить petite-vue. Этот случай настал.

Основной бекенд бота написан на golang, там нет никаких js библиотек, фреймворков, даже нода не запущена. И для внутреннего продукта настраивать целый отдельный подпроект настоящего фронтенда бессмысленно. Зато вот так выглядит минимальный веб сервер на go:

http.HandleFunc('GET /path', func(w http.ResponseWriter, r *http.Request) {
    api.Conversation(w, r, db)
})
go http.ListenAndServe(fmt.Sprintf(":%s", port), nil)

А логика формирования html странички целиком описана в одном файле на 123 строки кода.

func Conversation(w http.ResponseWriter, r *http.Request, db *sql.DB) {
  conversation := parseConversation(r)
  messages := getMessages(db, conversation)
  jsonBlob := json.Marshal(messages)
  tpl.Execute(w, jsonBlob)
}

Выше привел псевдокод, потому что количество if err != nil затрудняет понимание. И самое интересное, это как раз шаблон для html странички на petite-vue:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Conversation</title>
  <style>
    /* стили, нагенеренные grok'ом */
  </style>
  <script src="https://unpkg.com/petite-vue" defer init></script>
</head>

<body>
  <div class="container" v-scope="{messages: {{.JSON}} }">
    <ul>
      <li v-for="msg of messages" :key="msg.id">
        <span class="author">{{"{{ msg.Role }}"}}</span>
        <br>
        <pre class="content">{{"{{ msg.Content }}"}}</pre>
      </li>
    </ul>
  </div>
</body>
</html>

Как же это просто и красиво! Подставляешь JSON как есть строкой в шаблон, проходишь по циклу все сообщения в списке и остаётся лишь подрядить grok'а написать стили. И вот как выглядит результат:

пример логов разговора в интерфейсе
пример логов разговора в интерфейсе

И только в этом интерфейсе мы увидели возможное решение проблемы.

Как генерировать презентации при помощи llm?

Нейросети очень умные, но они любят нарушать правила разметки. А в презентации очень много разметки. При этом нет уверенности, что провайдеры тренируют модели на сырых xml файлах презентаций, а не используют OCR или альтернативный пайплан по получению текстового представления слайдов. Сомнительно что корпорации вообще рассматривают презентации как источник тренировочных данных в силу их малого числа относительно книг, веб страниц или кодовых баз.

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

Python-pptx это популярная библиотека для создания и редактирования pptx файлов на питоне. И можно ведь просто генерировать код на питоне и потом исполнять его для получения необходимого артефакта.

И вот как выглядит ранний эксперимент на питоне кода для генерации кода на питоне для генерации презентации:

prompt = "сделай презентацию про виды кофе из Италии"

def generate_script():
    system_prompt = f"""
Write a complete Python script using the python-pptx library to create a PowerPoint presentation (PPTX).
The script must:
- Use appropriate slide layouts (e.g., title slide, bullet slide).
- Save the presentation to a file named 'test.pptx'.
Output ONLY the Python code as plain text, without markdown, code block markers (e.g., ` + "```" + `python)

Example:
from pptx import Presentation
from pptx.util import Inches

prs = Presentation()
title_slide_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(title_slide_layout)
title = slide.shapes.title
subtitle = slide.placeholders[1]

title.text = "Hello, World!"
subtitle.text = "python-pptx was here!"

prs.save('test.pptx')"""
    payload = {
        "model": model,
        "messages": [
		{"role": "system", "content": system_prompt},
		{"role": "user", "content": prompt},
	],
        "stream": False
    }
    headers = {
        "Authorization": f"Bearer {key}",
    }

    try:
        response = requests.post("https://openrouter.ai/api/v1/chat/completions", json=payload, headers=headers)
        response.raise_for_status()
        result = response.json()
        generated_code = result["choices"][0]["message"]["content"].strip()

        if not generated_code:
            raise ValueError("No code generated.")

        # Clean up any code block markers or unwanted markdown
        generated_code = re.sub(r'```python\n|```', '', generated_code).strip()

        return generated_code

    except requests.exceptions.RequestException as e:
        response_text = response.text
        print(response_text)
        print(f"Error connecting: {e}")
        sys.exit(1)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

def execute_generated_script(script):
    try:
        result = subprocess.run(["python3", "-c", script], capture_output=True, text=True)
        if result.returncode == 0:
            print(f"Presentation generated successfully as 'test.pptx'")
        else:
            print(f"Error executing generated script:\n{result.stderr}")
    except Exception as e:
        print(f"Error running script: {e}")

if __name__ == "__main__":
    script = generate_script()
    execute_generated_script(script)

Лаконичный человеческий промпт и минимальный системный промпт с примером hello world презентации внутри. Удивительно, но этого вполне достаточно чтобы генерировать нечто красивое. А так как pptx файлы нативно открываются в телеграме, это выглядит вот так:

пример превью презентации про виды кофе из Италии
пример превью презентации про виды кофе из Италии

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

Агентский режим и безопасность

Код на питоне, который я показал выше имеет два принципиальных недостатка.

Первое - он написан на питоне, а наш бек на go. Так что что-то не сходится.

Второе - ответ llm'ки скармливается в интерпретатор питона как есть. Если думать о prompt injection, то становится не по себе.

Ещё хочется чтобы в тех запросах, где генерировать презентации пользователь не просил, не тратить дополнительное время и токены на эту функциональность.

Все эти факторы вместе кричат "давайте сделаем агентную архитектуру и пойдём к VC просить миллиарды долларов". Но зачем тратить на разработку месяцы если можно следать всё за вечер?

Функциональность презентации была добавлена в код бекенда за один вечер. И вот как мы понимаем что надо генерировать презентацию:

func ShouldInvoke(request, response string) bool {
  return strings.Contains(request+response, "резентаци")
    || strings.Contains(request+response, "resentation")
}

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

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

Возможности полученного продукта

Помимо генерации слайдов с текстом можно создавать таблицы:

пример слайда с таблицей
пример слайда с таблицей

У нейросети хватает ментальных способностей совместить создание таблицы с раскраской текста в другой цвет:

слайд с таблицей с цветным текстом
слайд с таблицей с цветным текстом

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

слайд с фигурой и текстом
слайд с фигурой и текстом

Спасибо за внимание

Если тебе понравилось, то что увидел(о), в моём микроблоге я каждую неделю и чаще пытаюсь придумывать необычные эксперименты с кодом: Страх или любовь.

А в Gamma Code мы создаём много развлекательных продуктов, которые можно попробовать: стиль Гибли, генерация видео, виртуальная примерка одежды и много чего ещё.

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

Комментарии (2)


  1. gmtd
    03.10.2025 09:17

    Как-то не понял - pptx презентации создавать в tg-боте?

    На сайте не увидел такой возможности


  1. kurdlyplot
    03.10.2025 09:17

    Кажется оно не работает