Тайм коды со слайдами
**слайд** обо мне [0:00:02 → 0:00:50] 
**слайд** компании использующие AI [0:00:50 → 0:02:10] 
**слайд** демонстрация работы сервиса gamma.app [0:02:10 → 0:03:12] 
**слайд** задачи которые нужно решить чтобы сделать аналог [0:03:12 → 0:04:15] 
**слайд** план создания презентации в видео строки [0:04:15 → 0:05:14] 
**слайд** о langChain и langGraph [0:05:15 → 0:05:40] 
**слайд** промт для генерации презентации [0:05:40 → 0:06:23] 
**слайд** описание json схемы [0:06:23 → 0:08:00] 
**слайд** описание работы парсера ответа от LLM [0:08:00 → 0:09:25] 
**слайд** демонстрация распарсеного json [0:09:25 → 0:09:55]
**слайд** демонстрация аналога сервиса gamma.app [0:09:55 → 0:10:57]
**слайд** объяснение работы langGraph [0:10:57 → 0:26:20]

[0:00:00 → 0:00:01]
Смотрим, как они устроены.

[0:00:02 → 0:00:03]
Пару слов о себе.

[0:00:03 → 0:00:09]
Меня зовут Александр, около 10 лет опыта в IT и 6 из них это работа в Сбере, уже больше половины получается.

[0:00:10 → 0:00:14]
В Сбере я фронт-энд разработчик и мой основной инструмент это TypeScript.

[0:00:16 → 0:00:20]
Но кроме того, что я работаю в Сбере, я еще преподаю в университетах.

[0:00:21 → 0:00:28]
С коллегами моими используем учебную платформу самописную, на которой я как бы full-stack разработчик получается.

[0:00:28 → 0:00:31]
Там и фронт-энд, и бэк-энд, и ICD, база данных, в общем, все на мне.

[0:00:32 → 0:00:35]
И в этом году мы запустили курс по созданию AI-агентов.

[0:00:35 → 0:00:45]
И успешно с магистрами Казанского федерального университета прошли этот курс, они создали приложение с AI-агентами.

[0:00:46 → 0:00:48]
Вот, но о чем я хотел поговорить.

[0:00:48 → 0:00:52]
Хайповые AI-сервисы у Forbes, даже есть список на этот счет.

[0:00:52 → 0:00:58]
Топ 50 компаний, которые на AI-сервисах основаны.

[0:00:58 → 0:01:01]
Здесь не очень удобно, потому что они отсортированы по алфавиту.

[0:01:01 → 0:01:05]
Наверное, интереснее посмотреть по деньгам, если их пересортировать.

[0:01:05 → 0:01:09]
Вот видно, что OpenAI на первом месте, понятно.

[0:01:10 → 0:01:15]
Но здесь очень много компаний, которые вы наверняка слышали или пользовались их сервисами.

[0:01:22 → 0:01:25]
Создатели Bolt New, Cursor, все здесь.

[0:01:26 → 0:01:28]
Ну, меня заинтересовало.

[0:01:28 → 0:01:33]
Приложение, которое, точнее, компания, вот чуть ниже, Speak называется тоже.

[0:01:34 → 0:01:36]
Топ 50 входит.

[0:01:36 → 0:01:41]
Они разработали приложение для изучения иностранных языков с помощью искусственного интеллекта.

[0:01:41 → 0:01:43]
С помощью AI-агента, помощника.

[0:01:44 → 0:01:47]
Я его скачал, попробовал, в принципе, неплохо.

[0:01:47 → 0:01:52]
Но я вам так скажу, что наши студенты, в принципе, делают не хуже, уж точно, не хуже приложения.

[0:01:52 → 0:01:56]
Хотя здесь они, например, с 2016 года давно работали над ним.

[0:01:57 → 0:01:58]
Но вот сейчас.

[0:01:58 → 0:02:01]
Уже можно довольно быстро создавать такие вещи.

[0:02:01 → 0:02:04]
Давайте разбираться, как они делаются.

[0:02:04 → 0:02:07]
Как разрабатываются приложения с AI-агентами.

[0:02:07 → 0:02:11]
В качестве подобного я решил выбрать GumUp.

[0:02:12 → 0:02:13]
Вспомним, что такое GumUp.

[0:02:13 → 0:02:16]
Это приложение для создания презентаций.

[0:02:17 → 0:02:20]
Вот, вводим, что мы хотим, на какую тему мы хотим презентацию.

[0:02:20 → 0:02:26]
Она нам создает план презентации, где расписывает про каждый слайд, заголовок.

[0:02:26 → 0:02:27]
Ну и буллеты, коротко там.

[0:02:27 → 0:02:28]
Один, два, три просто.

[0:02:28 → 0:02:30]
О чем слайд должен быть.

[0:02:30 → 0:02:34]
После создания плана нажимаем создать презентацию и она создается.

[0:02:35 → 0:02:37]
Просто волшебство какое-то.

[0:02:37 → 0:02:39]
Каждый слайд появляется последовательно.

[0:02:39 → 0:02:42]
Для каждого слайда там почти для каждого.

[0:02:42 → 0:02:43]
Генерируется изображение.

[0:02:43 → 0:02:45]
Буллеты как-то оформляются.

[0:02:47 → 0:02:51]
Текст тоже как-то все красиво компонуется.

[0:02:52 → 0:02:55]
И вуаля, просто вот презентация готова.

[0:02:56 → 0:02:56]
Сгенерирована.

[0:02:56 → 0:02:59]
Причем довольно красиво получается.

[0:02:59 → 0:03:01]
В общем-то он все логично расписал.

[0:03:01 → 0:03:09]
То, что если хотим учиться фотографировать котят, нам нужно подумать об свете, о настройках камеры, выбор ракурса, в общем, такого.

[0:03:09 → 0:03:11]
Ну давайте декомпозировать.

[0:03:11 → 0:03:12]
Как такие вещи делать?

[0:03:12 → 0:03:13]
Значит, первое и второе.

[0:03:13 → 0:03:16]
Нам глобально надо решить две задачи.

[0:03:16 → 0:03:20]
Это генерировать план презентации.

[0:03:20 → 0:03:22]
Ну и саму презентацию на основе этого плана.

[0:03:23 → 0:03:24]
Вот такие две части.

[0:03:24 → 0:03:26]
Как разрабатывать план презентации?

[0:03:26 → 0:03:28]
Ну самый простой путь просто.

[0:03:28 → 0:03:34]
Берем, открываем любой чатик с искусственным интеллектом, просим его создать нам план презентации.

[0:03:34 → 0:03:36]
Довольно несложный промпт.

[0:03:36 → 0:03:38]
Вот я открыл гигачат, посмотрел.

[0:03:38 → 0:03:41]
Он мне генерирует, в принципе, примерно то же самое.

[0:03:42 → 0:03:46]
То есть какие-то слайды, темы для каждого слайда и буллеты.

[0:03:47 → 0:03:48]
О чем должен быть этот слайд?

[0:03:48 → 0:03:51]
И здесь, в принципе-то, и план такой же.

[0:03:51 → 0:03:53]
Может быть, чуть другая последовательность.

[0:03:53 → 0:03:55]
Подумать о композиции, ракурсе.

[0:03:56 → 0:03:57]
Настроить камеру, настроить свет.

[0:03:57 → 0:04:01]
Как-то договориться с котенком о том, что мы его будем фотографировать.

[0:04:01 → 0:04:02]
Ну, в таком плане.

[0:04:04 → 0:04:07]
Я разработал приложение, которое похоже на Gama App.

[0:04:07 → 0:04:11]
Можно сказать, клон Gama App, на основе которого я это все буду рассказывать.

[0:04:11 → 0:04:14]
И у меня в приложении промпт выглядит чуть больше.

[0:04:14 → 0:04:16]
Вообще, он выглядит вот так.

[0:04:16 → 0:04:17]
Это огромная-огромная строка.

[0:04:18 → 0:04:21]
Не пугайтесь, я сейчас разберу, из чего она состоит.

[0:04:21 → 0:04:25]
В общем-то, тут есть первая такая часть, где я уговариваю его.

[0:04:26 → 0:04:27]
Создать план.

[0:04:27 → 0:04:28]
Объясняю, что мы будем создавать план.

[0:04:31 → 0:04:34]
И расписываю, какие виды слайдов существуют.

[0:04:34 → 0:04:37]
Какие бывают, которые моя система готова.

[0:04:37 → 0:04:40]
Потом воспроизводить титульный слайд, там, контентный и так далее.

[0:04:41 → 0:04:45]
Потом довольно большая часть, которая относится к форматированию ответа.

[0:04:45 → 0:04:49]
Я хочу получить ответ, ну, не в формате текста или Markdown.

[0:04:49 → 0:04:51]
Мне будет удобнее работать с форматом JSON.

[0:04:53 → 0:04:55]
И здесь описывается этот формат.

[0:04:56 → 0:04:59]
Ну, и в конце я добавляю еще текущую дату.

[0:04:59 → 0:05:04]
Нейросетям неплохо бы в промптах добавлять информацию о текущей дате.

[0:05:04 → 0:05:08]
Чтобы он учитывал и понимал, что если просят что-то новенькое,

[0:05:08 → 0:05:10]
то нужно сходить в интернет и поискать.

[0:05:13 → 0:05:17]
Делаются такие вещи, в общем-то, не руками, а с помощью фреймворков.

[0:05:17 → 0:05:21]
Хотя, безусловно, все это и руками можно создать и отправить, но это неудобно.

[0:05:21 → 0:05:24]
При масштабировании уже становится понятно.

[0:05:25 → 0:05:26]
Сложно расти.

[0:05:26 → 0:05:30]
Нужно пользоваться фреймворками, которые как раз позволяют свое решение масштабировать.

[0:05:31 → 0:05:32]
Лонгчейн и лонгграф, в общем.

[0:05:34 → 0:05:37]
Для создания плана презентации нам будет достаточно лонгчейна.

[0:05:37 → 0:05:39]
А вот для самой презентации уже будем использовать лонгграф.

[0:05:40 → 0:05:46]
Как с помощью лонгчейна создать вот этот большой-большой промпт для генерации плана?

[0:05:46 → 0:05:49]
В общем-то, используется для этого шаблоны.

[0:05:49 → 0:05:52]
В лонгчейне есть такой механизм, как вот шаблоны.

[0:05:53 → 0:05:55]
Мы тут здесь описываем то,

[0:05:55 → 0:05:57]
что хотим описать, то, что статично,

[0:05:57 → 0:06:01]
а все вещи, которые будут потом должны быть заполнены,

[0:06:01 → 0:06:04]
мы обрамляем вот так, фигурные скобки, и название включаем.

[0:06:04 → 0:06:07]
То есть потом пользователь к нам придет с названием презентации,

[0:06:07 → 0:06:10]
и мы вот топик подменим на тему, которую он нам прислал.

[0:06:11 → 0:06:13]
И так далее, все в шаблоне заменим.

[0:06:13 → 0:06:14]
Это самое простое.

[0:06:14 → 0:06:17]
А вот насчет инструкций по форматированию, вот можно видеть,

[0:06:17 → 0:06:20]
что внизу есть вот такая вещь, формат instructions.

[0:06:21 → 0:06:24]
Вот ее нужно заполнить этими инструкциями для форматирования.

[0:06:24 → 0:06:25]
Как они делаются?

[0:06:26 → 0:06:30]
В общем-то, можно JSON-схему, конечно, положить туда руками,

[0:06:30 → 0:06:33]
но на разных языках это делается чуть по-разному.

[0:06:34 → 0:06:36]
Примерно всегда есть какая-то обертка.

[0:06:37 → 0:06:40]
На TypeScript у нас это Zot библиотечка,

[0:06:40 → 0:06:44]
которая очень популярна стала для описания форм объектов.

[0:06:45 → 0:06:47]
И вот здесь я как раз описываю, что у нас есть презентация.

[0:06:48 → 0:06:51]
Глобального презентации в целом есть какое-то заголовок,

[0:06:51 → 0:06:52]
какое-то описание.

[0:06:52 → 0:06:54]
Я это делаю, потому что, ну, по-моему,

[0:06:54 → 0:06:58]
пользователь может опечататься или как-то несуразно сформулировать тему,

[0:06:58 → 0:07:00]
которую, может быть, нейросеть решит переформулировать,

[0:07:01 → 0:07:02]
ну, плюс добавить описание.

[0:07:02 → 0:07:04]
Ну и когда мы генерируем план презентации,

[0:07:04 → 0:07:06]
в этот момент неплохо бы добавить что-то общее,

[0:07:06 → 0:07:09]
что будет актуально для всей презентации.

[0:07:09 → 0:07:11]
Например, общий стиль изображений.

[0:07:11 → 0:07:14]
Что гамап вы видели, что у меня, посмотрите,

[0:07:14 → 0:07:16]
что картинки генерируются в едином стиле,

[0:07:16 → 0:07:19]
едином и для всей презентации.

[0:07:19 → 0:07:22]
Вот здесь формируется этот стиль глобальный.

[0:07:22 → 0:07:23]
Ну, а потом каждый слайд.

[0:07:24 → 0:07:25]
У слайда должен быть заголовок,

[0:07:25 → 0:07:27]
у него может быть контентная часть,

[0:07:27 → 0:07:30]
но на этапе планирования, в общем-то, она не заполняется.

[0:07:31 → 0:07:34]
Буллеты — это как раз вот массив строк про то,

[0:07:34 → 0:07:36]
о чем слайд, первый, второй, третий,

[0:07:36 → 0:07:38]
ну и какие-то еще дополнительные вещи,

[0:07:38 → 0:07:40]
которые мы можем захотеть.

[0:07:40 → 0:07:43]
Естественно, мы это описываем не просто набором ключей,

[0:07:43 → 0:07:46]
а для каждого ключа описываем тип этого ключа

[0:07:46 → 0:07:51]
и через describe описываем, о чем этот ключ.

[0:07:51 → 0:07:53]
То есть если title — это строка, понятно,

[0:07:54 → 0:07:56]
то что это за строка описывается уже через describe,

[0:07:56 → 0:07:57]
как ее заполнять.

[0:07:58 → 0:07:59]
Вот.

[0:08:00 → 0:08:03]
После того, как мы эти вещи применим,

[0:08:03 → 0:08:05]
мы сможем получить финальный промт.

[0:08:05 → 0:08:06]
Как это делается?

[0:08:06 → 0:08:09]
Мы берем, тут пример из документации,

[0:08:10 → 0:08:13]
мы берем Structured Output Parser,

[0:08:14 → 0:08:17]
который инициализируем из этой ZOT-схемы.

[0:08:17 → 0:08:19]
Потом нам нужен промт,

[0:08:19 → 0:08:23]
в котором мы пометим какое-то место,

[0:08:23 → 0:08:27]
в которое попадет инструкция по форматированию в конечном итоге.

[0:08:27 → 0:08:29]
Вот такой формат instructions

[0:08:29 → 0:08:32]
где-то нужно разместить внутри нашего промта.

[0:08:33 → 0:08:35]
Затем туда добавляются модели parser,

[0:08:35 → 0:08:39]
то есть мы формируем chain для long chain.

[0:08:39 → 0:08:42]
Первым в этой цепочке получается template,

[0:08:42 → 0:08:44]
потом модель, потом parser,

[0:08:44 → 0:08:46]
который нам на выходе выдаст как раз JSON.

[0:08:47 → 0:08:48]
Вот.

[0:08:49 → 0:08:51]
Инструкции по форматированию получаются, ну,

[0:08:51 → 0:08:53]
вызовом метода getFormatInstructions.

[0:08:53 → 0:08:55]
Тут, в общем-то, тоже как бы магии нету.

[0:08:55 → 0:08:57]
В момент, когда мы запускаем цепочку,

[0:08:57 → 0:09:01]
мы этот ключ формат instructions заполняем вызовом этого метода

[0:09:02 → 0:09:02]
getFormatInstructions.

[0:09:03 → 0:09:07]
То есть getFormatInstructions просто выдает нам конкретное сообщение,

[0:09:07 → 0:09:11]
что там, пожалуйста, следуй вот такой JSON-схеме,

[0:09:11 → 0:09:16]
отдай мне ответ, строго соблюдая этот контракт.

[0:09:18 → 0:09:20]
Ну и, кроме того, можем заполнить дополнительные вещи,

[0:09:20 → 0:09:24]
то есть в моем случае пользователь приходит с темой презентации,

[0:09:24 → 0:09:26]
может быть, с каким-то дополнительным описанием,

[0:09:26 → 0:09:27]
я все это прокидываю в prompt.

[0:09:28 → 0:09:29]
Выглядит это вот таким образом.

[0:09:29 → 0:09:33]
Если это вызвать, то искусственный интеллект начинает нам возвращать

[0:09:33 → 0:09:36]
большую-большую строку, которая внутри содержит JSON.

[0:09:38 → 0:09:42]
В этом JSON просто вот заполнены те ключи, которые я описывал.

[0:09:42 → 0:09:44]
Ну, где-то он даже контент заполняет,

[0:09:44 → 0:09:46]
но потом я в любом случае контент формирую уже отдельно,

[0:09:46 → 0:09:49]
когда презентация целиком генерируется.

[0:09:50 → 0:09:53]
Вот. Значит, ну тут примерно то же самое.

[0:09:53 → 0:09:54]
Профотографирование котят.

[0:09:55 → 0:09:57]
Посмотрим, как это выглядит у меня.

[0:09:59 → 0:10:02]
Я закидываю тему презентации, прошу сгенерировать план,

[0:10:02 → 0:10:06]
и вот начинает прилетать этот JSON, который я показывал,

[0:10:06 → 0:10:08]
он разбирается, и мы генерируем план.

[0:10:09 → 0:10:11]
Ну, интересно, что в моем случае я решил,

[0:10:12 → 0:10:15]
что было бы неплохо ходить в интернет для некоторых тем,

[0:10:15 → 0:10:17]
а для некоторых слайдов,

[0:10:17 → 0:10:20]
если искусственный интеллект так решит,

[0:10:20 → 0:10:23]
если мой AI-агент захочет что-то поискать в интернете.

[0:10:23 → 0:10:26]
Ну и здесь я генерирую промпт для изображения

[0:10:26 → 0:10:28]
и негативный промпт для изображения,

[0:10:28 → 0:10:30]
чтобы потом диффузионной моделькой, собственно,

[0:10:30 → 0:10:33]
сгенерировать изображение с промптом и негативным промптом.

[0:10:34 → 0:10:37]
Вот. Затем нажимаем «Создать презентацию».

[0:10:37 → 0:10:39]
В принципе, с планом мы разобрались.

[0:10:39 → 0:10:43]
И вот как выглядит создание презентации.

[0:10:44 → 0:10:44]
Нажимаем.

[0:10:46 → 0:10:47]
Немножко подвисло.

[0:10:51 → 0:10:53]
Видео немножко подвисло, к сожалению.

[0:10:53 → 0:10:56]
Но можно сходить на сайт и посмотреть,

[0:10:56 → 0:10:57]
как оно там генерируется.

[0:10:58 → 0:11:02]
Как же генерировать вот такие более сложные вещи,

[0:11:02 → 0:11:03]
как слайды?

[0:11:03 → 0:11:05]
Это уже не просто план, где, в принципе,

[0:11:05 → 0:11:08]
одной текстовкой можно одним запросом обойтись.

[0:11:08 → 0:11:10]
А здесь вот, когда мы генерируем слайды,

[0:11:10 → 0:11:11]
каждый слайд — это какой-то контент,

[0:11:11 → 0:11:14]
тоже заголовок, изображение.

[0:11:14 → 0:11:16]
Может, надо сходить в интернет, чего-то поискать,

[0:11:16 → 0:11:18]
и всё это собрать в кучу.

[0:11:18 → 0:11:20]
В общем, здесь уже более сложные вещи.

[0:11:20 → 0:11:21]
Здесь нам поможет ланграф.

[0:11:22 → 0:11:25]
Как раз этот фреймворк позволяет уже более сложные вещи делать.

[0:11:27 → 0:11:30]
Когда мы пользуемся ланграфом,

[0:11:30 → 0:11:32]
мы описываем логику в форме нод,

[0:11:32 → 0:11:34]
в форме узлов логики,

[0:11:34 → 0:11:37]
каждый из которых является функцией

[0:11:37 → 0:11:38]
и делает что-то своё.

[0:11:39 → 0:11:41]
У такого графа будет какая-то входная нода

[0:11:41 → 0:11:42]
и какая-то выходная нода.

[0:11:43 → 0:11:44]
И между ними будет, конечно,

[0:11:44 → 0:11:46]
как-то распределяться логика.

[0:11:46 → 0:11:48]
То есть мы можем делать что-то последовательно.

[0:11:49 → 0:11:50]
Каждая нода делает что-то своё.

[0:11:51 → 0:11:53]
Может быть, генерирует изображение,

[0:11:53 → 0:11:54]
может быть, входит в интернет,

[0:11:54 → 0:11:55]
может быть, что-то ещё.

[0:11:55 → 0:11:59]
Не обязательно, чтобы путь,

[0:11:59 → 0:12:00]
по которому перемещается логика,

[0:12:00 → 0:12:02]
был именно таким последовательным.

[0:12:03 → 0:12:05]
Потому что это, в принципе, делает лангчейн.

[0:12:05 → 0:12:07]
А для более сложных вещей

[0:12:07 → 0:12:09]
у нас есть возможность описать роутинг

[0:12:09 → 0:12:13]
и по каким-то условиям ходить в одни ноды,

[0:12:13 → 0:12:14]
не ходить в другие.

[0:12:14 → 0:12:15]
В общем-то, менять путь.

[0:12:16 → 0:12:19]
При этом мы можем написать логику так,

[0:12:19 → 0:12:20]
что в конце у нас будет вариант

[0:12:21 → 0:12:23]
вернуться в самое начало.

[0:12:23 → 0:12:24]
У нас может быть какой-то

[0:12:24 → 0:12:26]
дополнительный агент-критик,

[0:12:26 → 0:12:28]
который посмотрит, что мы сделали,

[0:12:28 → 0:12:30]
то есть что сделал AI-агент наш.

[0:12:30 → 0:12:32]
Если ему что-то не понравится,

[0:12:32 → 0:12:33]
он скажет «переделай» и вернётся

[0:12:33 → 0:12:35]
на какой-то этап начала,

[0:12:36 → 0:12:38]
чтобы сделать заново, учитывая правки.

[0:12:39 → 0:12:42]
Весь этот граф, он работает

[0:12:42 → 0:12:44]
с единым состоянием.

[0:12:44 → 0:12:47]
Мы определяем изначальное состояние

[0:12:47 → 0:12:48]
и потом передаём его между нодами,

[0:12:48 → 0:12:50]
и каждая нода работает с этим состоянием,

[0:12:50 → 0:12:51]
что-то там обновляет.

[0:12:52 → 0:12:54]
По итогу работа AI-агента является

[0:12:55 → 0:12:56]
заполненное состояние,

[0:12:56 → 0:12:58]
которое мы потом снаружи считываем

[0:12:58 → 0:12:59]
и что-то с ним делаем.

[0:12:59 → 0:13:00]
Например, рисуем презентацию.

[0:13:02 → 0:13:03]
Когда я разрабатывал...

[0:13:03 → 0:13:05]
А, бывают более сложные схемы,

[0:13:05 → 0:13:06]
более интересные даже.

[0:13:07 → 0:13:09]
Когда у нас есть агент-супервайзер,

[0:13:09 → 0:13:12]
который сам принимает решение,

[0:13:12 → 0:13:13]
нужно ли ему воспользоваться

[0:13:13 → 0:13:15]
другим каким-то агентом,

[0:13:15 → 0:13:17]
и сам принимает решение,

[0:13:17 → 0:13:18]
когда ему закончить,

[0:13:18 → 0:13:21]
например, ему дали задачу нарисовать презентацию,

[0:13:21 → 0:13:22]
он может подумать так,

[0:13:22 → 0:13:24]
мне сейчас нужно пойти в агента

[0:13:24 → 0:13:25]
для поиска в интернете,

[0:13:25 → 0:13:26]
поискать информацию,

[0:13:26 → 0:13:28]
потом сходить к художнику нарисовать,

[0:13:28 → 0:13:31]
потом сходить там и контент нагенерировать.

[0:13:31 → 0:13:34]
Но при этом он сам принимает все эти решения

[0:13:34 → 0:13:35]
и самостоятельно решает,

[0:13:35 → 0:13:39]
куда пойти и какого агента сейчас задействовать.

[0:13:39 → 0:13:41]
Получается такая мультиагентная система.

[0:13:42 → 0:13:44]
В моем случае

[0:13:44 → 0:13:46]
настолько сложная вещь не понадобилась.

[0:13:47 → 0:13:49]
Я пошел по более простому пути.

[0:13:49 → 0:13:50]
Я рисовал графы.

[0:13:51 → 0:13:52]
У меня получилось несколько вариантов

[0:13:52 → 0:13:54]
в ходе разработки этого клона.

[0:13:56 → 0:13:59]
И сейчас я вам покажу финальный вариант.

[0:13:59 → 0:14:00]
Он получился довольно компактным,

[0:14:01 → 0:14:03]
довольно дешевым в использовании

[0:14:03 → 0:14:04]
и довольно быстрым.

[0:14:05 → 0:14:06]
Дело в том, что некоторые ноды

[0:14:06 → 0:14:08]
можно запускать параллельно,

[0:14:08 → 0:14:11]
одновременно работая над несколькими задачами.

[0:14:11 → 0:14:13]
Посмотрим поочередно.

[0:14:13 → 0:14:15]
Сначала шаг подготовка.

[0:14:15 → 0:14:18]
В моем случае, что такое подготовка?

[0:14:18 → 0:14:19]
Я просто прохожусь по слайдам,

[0:14:19 → 0:14:21]
сгенерированным на этапе планирования

[0:14:21 → 0:14:23]
и назначаю им ID.

[0:14:23 → 0:14:26]
Здесь будет неплохо показать,

[0:14:26 → 0:14:28]
как выглядит нода графа.

[0:14:28 → 0:14:29]
В общем-то,

[0:14:29 → 0:14:31]
это функция,

[0:14:31 → 0:14:33]
которая принимает состояние

[0:14:33 → 0:14:35]
и она должна обещать вернуть нам

[0:14:35 → 0:14:38]
какую-то часть состояния на апдейт.

[0:14:38 → 0:14:39]
Может целиком состояние обновить,

[0:14:39 → 0:14:41]
может какие-то конкретные ключи.

[0:14:42 → 0:14:43]
Поэтому, по итогу,

[0:14:43 → 0:14:45]
мы должны вернуть объект,

[0:14:45 → 0:14:47]
у которого будут заполнены ключи

[0:14:47 → 0:14:49]
состояния, в данном случае один ключ

[0:14:49 → 0:14:51]
Presentation, в котором уже лежит план

[0:14:51 → 0:14:53]
на этапе генерации.

[0:14:54 → 0:14:56]
Это планирование мы его создали

[0:14:56 → 0:14:57]
и когда приходим к генерации,

[0:14:57 → 0:14:59]
состояние заполняется этим планом.

[0:15:00 → 0:15:02]
Я прохожусь внутри плана

[0:15:02 → 0:15:03]
по всем слайдам и просто

[0:15:03 → 0:15:04]
генерирую ID.

[0:15:05 → 0:15:06]
Давайте дальше.

[0:15:06 → 0:15:09]
Нам нужно принять решение, куда пойти.

[0:15:09 → 0:15:10]
Либо на веб-ресерч,

[0:15:10 → 0:15:12]
либо на генерацию.

[0:15:13 → 0:15:15]
То есть нам нужно пройтись

[0:15:15 → 0:15:17]
по плану, по всем слайдам

[0:15:17 → 0:15:19]
и найти, есть ли такие слайды,

[0:15:19 → 0:15:21]
для которых есть промт поиска в интернете.

[0:15:21 → 0:15:22]
Для этого используется

[0:15:23 → 0:15:25]
Conditional Edge, так называемый.

[0:15:25 → 0:15:27]
Что это такое

[0:15:27 → 0:15:28]
Conditional Edge?

[0:15:29 → 0:15:31]
Тоже функция, она тоже принимает состояние,

[0:15:31 → 0:15:32]
но у нее задача вернуть

[0:15:32 → 0:15:34]
не апдейт состояния,

[0:15:34 → 0:15:35]
а куда пойти дальше.

[0:15:36 → 0:15:38]
И здесь я создаю

[0:15:40 → 0:15:42]
массив, куда можно пойти

[0:15:42 → 0:15:44]
после работы этой

[0:15:44 → 0:15:45]
предыдущей ноды.

[0:15:46 → 0:15:47]
И либо его заполняю,

[0:15:48 → 0:15:49]
либо, если он не заполнен, мы идем на exit.

[0:15:50 → 0:15:51]
Exit в данном случае имеется ввиду

[0:15:51 → 0:15:53]
пойти на to generate, то есть

[0:15:53 → 0:15:54]
презентацию генерировать,

[0:15:54 → 0:15:57]
как будто в поиск не потребовался.

[0:15:59 → 0:16:00]
Массив send-off я создаю

[0:16:00 → 0:16:01]
пустым,

[0:16:01 → 0:16:02]
а потом пытаюсь его заполнить.

[0:16:02 → 0:16:04]
Я пробегаюсь по всем слайдам,

[0:16:04 → 0:16:06]
ищу слайды, у которых заполнен

[0:16:06 → 0:16:07]
ключ websearch query.

[0:16:08 → 0:16:10]
На этапе планирования,

[0:16:10 → 0:16:11]
напомню, это происходит.

[0:16:11 → 0:16:13]
И если такой слайд нашелся,

[0:16:13 → 0:16:15]
то я его помещаю в push,

[0:16:16 → 0:16:18]
в sends, массив send-off,

[0:16:18 → 0:16:19]
ну и заполняю

[0:16:19 → 0:16:22]
с помощью специального класса send.

[0:16:23 → 0:16:26]
Send передается, какую ноду надо запустить,

[0:16:26 → 0:16:28]
в данном случае webresearch называется нода,

[0:16:28 → 0:16:29]
и какие данные в него прокинуть.

[0:16:30 → 0:16:31]
Можно прокидывать

[0:16:31 → 0:16:32]
хоть все состояние,

[0:16:32 → 0:16:34]
но довольно удобно

[0:16:34 → 0:16:36]
прокидывать только то, что нужно.

[0:16:36 → 0:16:38]
Например, один слайд, с которым

[0:16:38 → 0:16:40]
эта нода для research

[0:16:40 → 0:16:42]
будет работать. Ей, в общем-то, нужен

[0:16:42 → 0:16:44]
только websearch query, но чтобы сохранить

[0:16:44 → 0:16:46]
в состоянии информацию, понадобится

[0:16:46 → 0:16:48]
id-шник слайда, ну, в общем, проще

[0:16:48 → 0:16:50]
передать туда целиком слайд.

[0:16:50 → 0:16:52]
Внутри она считает id-шник и

[0:16:52 → 0:16:54]
websearch query. Как будет

[0:16:54 → 0:16:56]
websearch query работать? В общем-то,

[0:16:56 → 0:16:58]
есть разные способы, как

[0:16:58 → 0:17:00]
поискать в интернете, можно даже

[0:17:00 → 0:17:02]
самостоятельно написать краулер,

[0:17:02 → 0:17:04]
какой-нибудь, но я воспользовался

[0:17:04 → 0:17:06]
удобной утилитой Tavili,

[0:17:06 → 0:17:08]
которая позволяет искать

[0:17:08 → 0:17:09]
в интернете.

[0:17:10 → 0:17:12]
Вот напомню, что в Send мы отправляли

[0:17:12 → 0:17:14]
один слайд, поэтому

[0:17:14 → 0:17:16]
здесь функция принимает тоже

[0:17:16 → 0:17:17]
один слайд.

[0:17:18 → 0:17:20]
Внутри я инициирую этот

[0:17:20 → 0:17:22]
инструмент Tavili,

[0:17:22 → 0:17:24]
ну, а затем просто из слайда

[0:17:24 → 0:17:26]
беру websearch query и, в общем,

[0:17:26 → 0:17:28]
иду искать и жду, пока этот

[0:17:28 → 0:17:30]
инструмент мне найдет то, что нужно.

[0:17:31 → 0:17:32]
Затем я обновляю

[0:17:32 → 0:17:34]
состояние, специально

[0:17:34 → 0:17:36]
созданный для этого ключик

[0:17:36 → 0:17:38]
websearch-result, и по id-шнику слайда

[0:17:38 → 0:17:40]
сохраняю информацию, которую нашел в интернете.

[0:17:41 → 0:17:42]
Интересный моментик, вот с фигурными

[0:17:42 → 0:17:44]
скобками, если создавать презентации

[0:17:44 → 0:17:46]
на какие-то темы для программистов,

[0:17:46 → 0:17:48]
там наверняка будут блоки кода, блоки кода

[0:17:48 → 0:17:50]
содержат фигурные скобки, а лонгчейн

[0:17:50 → 0:17:52]
воспринимает фигурные скобки как

[0:17:52 → 0:17:54]
часть темплейта, которую надо заполнить

[0:17:54 → 0:17:56]
какими-то данными, и

[0:17:56 → 0:17:57]
будет ругаться, что

[0:17:58 → 0:18:00]
вы не предоставили мне данные

[0:18:00 → 0:18:02]
для заполнения этого ключа.

[0:18:03 → 0:18:04]
Поэтому я их

[0:18:05 → 0:18:06]
экранирую, я нахожу фигурные

[0:18:06 → 0:18:08]
скобки и

[0:18:08 → 0:18:10]
делаю двойными фигурными скобками,

[0:18:10 → 0:18:13]
в общем-то это способ заэкранировать

[0:18:13 → 0:18:13]
их.

[0:18:14 → 0:18:16]
Вот и все, то есть точно так же,

[0:18:17 → 0:18:18]
как и в prepare, в общем

[0:18:18 → 0:18:20]
я что-то делаю и кладу это

[0:18:20 → 0:18:22]
в состояние. Далее нам

[0:18:22 → 0:18:24]
надо принять решение, то есть мы, понятно,

[0:18:24 → 0:18:26]
уже идем на generate, и далее

[0:18:26 → 0:18:28]
надо понять, хотим ли мы сгенерировать

[0:18:28 → 0:18:31]
картинки или начинаем контент генерировать.

[0:18:32 → 0:18:34]
Что здесь происходит? Это тоже

[0:18:34 → 0:18:37]
роутер, он тоже принимает состояние

[0:18:37 → 0:18:38]
и должен вернуть, куда идем дальше.

[0:18:39 → 0:18:41]
Точно так же работает с массивом

[0:18:41 → 0:18:42]
sendов. Здесь есть

[0:18:42 → 0:18:44]
вот такой цикл,

[0:18:44 → 0:18:46]
я пробегаюсь по слайдам,

[0:18:47 → 0:18:48]
по каждому из них, потом

[0:18:48 → 0:18:50]
смотрю, есть ли там prompt на генерацию

[0:18:50 → 0:18:53]
изображения, и массив sendов заполняю

[0:18:53 → 0:18:54]
опять экземпляром класса

[0:18:54 → 0:18:56]
send, название нода, куда

[0:18:56 → 0:18:58]
нужно пойти, и информация, которая

[0:18:58 → 0:19:01]
нужна для генерации. Это слайд

[0:19:01 → 0:19:02]
с его image prompt и negative

[0:19:02 → 0:19:05]
image prompt, и также глобальный

[0:19:05 → 0:19:07]
стиль изображения, который тоже надо учесть,

[0:19:07 → 0:19:09]
чтобы во всей презентации

[0:19:09 → 0:19:11]
и во всех слайдах стили изображения

[0:19:11 → 0:19:12]
были в едином стиле.

[0:19:13 → 0:19:14]
Вот. Ну,

[0:19:14 → 0:19:16]
как генерировать картинки?

[0:19:16 → 0:19:18]
Я думаю, вы знаете, что есть

[0:19:18 → 0:19:20]
такая у нас нероссеть Кандинский,

[0:19:20 → 0:19:22]
которая умеет изображения генерировать,

[0:19:22 → 0:19:24]
и у нее недавно появилась такая кнопочка API

[0:19:24 → 0:19:25]
позволяющая

[0:19:27 → 0:19:29]
взаимодействовать программным способом

[0:19:29 → 0:19:31]
с Кандинским и генерировать изображения

[0:19:31 → 0:19:31]
изображения.

[0:19:33 → 0:19:34]
Ну, не стану

[0:19:35 → 0:19:36]
врать, в общем, кода получилось много,

[0:19:36 → 0:19:38]
здесь он вроде бы

[0:19:38 → 0:19:40]
даже весь. Я написал

[0:19:40 → 0:19:42]
обертку для работы с API

[0:19:44 → 0:19:44]
Кандинского,

[0:19:44 → 0:19:45]
назвал его собачка-броджия

[0:19:45 → 0:19:48]
это чисто обертка

[0:19:48 → 0:19:50]
над API,

[0:19:50 → 0:19:52]
позволяющая чуть более удобно с ней работать,

[0:19:52 → 0:19:54]
но даже с учетом такой обертки

[0:19:55 → 0:19:56]
кода получилось много.

[0:19:56 → 0:19:58]
Моя обертка над API

[0:19:59 → 0:19:59]
Кандинский

[0:20:01 → 0:20:02]
насосная, в общем, ее можно найти

[0:20:02 → 0:20:03]
на Gitverse,

[0:20:04 → 0:20:06]
если интересно, можете попробовать.

[0:20:08 → 0:20:08]
Внутри я просто

[0:20:08 → 0:20:10]
смотрю, что некоторые слайды

[0:20:10 → 0:20:11]
я хочу квадратные,

[0:20:12 → 0:20:14]
один к одному, иногда 16 к 9,

[0:20:14 → 0:20:16]
в зависимости от того, какой слайд.

[0:20:16 → 0:20:18]
В общем, сохраняю изображение в файловой

[0:20:18 → 0:20:20]
системе и сохраняю в состоянии

[0:20:20 → 0:20:22]
ссылку на это изображение.

[0:20:23 → 0:20:24]
В общем-то и все.

[0:20:25 → 0:20:26]
Идем дальше.

[0:20:26 → 0:20:29]
Ну, наконец-то, генерация контента.

[0:20:29 → 0:20:30]
Как же сгенерировать контент?

[0:20:31 → 0:20:32]
В общем-то, контент мне нужен, в принципе,

[0:20:32 → 0:20:35]
в Markdown, который я на UI

[0:20:35 → 0:20:36]
потом смогу красиво

[0:20:37 → 0:20:37]
отображать.

[0:20:38 → 0:20:40]
Я принял такое решение.

[0:20:40 → 0:20:42]
Но тут есть интересный момент.

[0:20:42 → 0:20:44]
Когда мы идем генерировать такие уже

[0:20:44 → 0:20:46]
более сложные вещи, мы будем

[0:20:46 → 0:20:48]
пользоваться блокчейном, и для начала

[0:20:48 → 0:20:50]
нам понадобится системный промт,

[0:20:50 → 0:20:51]
где мы описываем,

[0:20:52 → 0:20:53]
что мы сейчас будем генерировать.

[0:20:53 → 0:20:56]
Значит, слайды, контент для слайдов,

[0:20:56 → 0:20:58]
там внутри,

[0:20:59 → 0:21:00]
имея в виду, что на слайде

[0:21:01 → 0:21:02]
очень много текста влезает,

[0:21:02 → 0:21:03]
нам нужны какие-то буллеты,

[0:21:03 → 0:21:05]
ну, в общем, какое-то описание, как мы видим

[0:21:06 → 0:21:08]
генерацию этих контента для слайда.

[0:21:09 → 0:21:10]
Это системный промт.

[0:21:10 → 0:21:12]
Потом мы досылаем туда

[0:21:13 → 0:21:14]
запрос. Вот данные, сгенерируй

[0:21:14 → 0:21:15]
контент для слайда.

[0:21:16 → 0:21:18]
То есть, вот результат поиска в интернете

[0:21:18 → 0:21:20]
мы туда дописываем,

[0:21:20 → 0:21:23]
о чем у нас был этот слайд

[0:21:23 → 0:21:25]
на этапе планирования

[0:21:25 → 0:21:26]
названия, там буллеты,

[0:21:26 → 0:21:28]
в общем, все, что надо уже для слайда,

[0:21:28 → 0:21:30]
готовое мы туда складываем,

[0:21:30 → 0:21:31]
вот, сгенерируй нам, пожалуйста, слайд.

[0:21:32 → 0:21:33]
Нейросеть нам отвечает.

[0:21:33 → 0:21:35]
Хорошо, вот контент. И в Markdown, значит,

[0:21:35 → 0:21:36]
отдает контент.

[0:21:38 → 0:21:39]
После чего

[0:21:39 → 0:21:41]
мне хочется пойти немножко дальше

[0:21:41 → 0:21:43]
и сгенерировать не только контент,

[0:21:43 → 0:21:46]
но и описание, о чем этот слайд,

[0:21:46 → 0:21:47]
о чем думала нейросеть,

[0:21:48 → 0:21:49]
создавая этот слайд,

[0:21:49 → 0:21:51]
и о чем надо, собственно, рассказывать

[0:21:52 → 0:21:54]
тому, кто будет

[0:21:55 → 0:21:56]
презентацию показывать

[0:21:56 → 0:21:58]
и рассказывать что-то по ней.

[0:21:58 → 0:21:59]
Поэтому я решил

[0:21:59 → 0:22:01]
сделать так, что мне нужно пойти

[0:22:01 → 0:22:04]
в нейросеть и попросить ее сгенерировать.

[0:22:04 → 0:22:04]
Пожалуйста, теперь

[0:22:06 → 0:22:08]
комментарий для этого слайда,

[0:22:08 → 0:22:10]
где опиши, что рассказывать.

[0:22:10 → 0:22:12]
Я мог бы отдельно

[0:22:12 → 0:22:13]
создать для этого системный промт,

[0:22:13 → 0:22:16]
отдельно там запрос сделать,

[0:22:16 → 0:22:16]
но я делаю так.

[0:22:17 → 0:22:19]
В массив сообщений, которые

[0:22:20 → 0:22:21]
мне получился,

[0:22:21 → 0:22:23]
там системный промт изначальный,

[0:22:23 → 0:22:25]
первый запрос на генерацию

[0:22:26 → 0:22:28]
контента я тоже туда дописываю,

[0:22:28 → 0:22:29]
ответ нейросети хорошо,

[0:22:30 → 0:22:32]
контент я тоже в этот массив сообщений

[0:22:32 → 0:22:34]
складываю и далее добавляю

[0:22:34 → 0:22:35]
еще один запрос.

[0:22:35 → 0:22:37]
Теперь, пожалуйста, добавь комментарий для спикера.

[0:22:39 → 0:22:41]
Потом нейросеть не отвечает

[0:22:41 → 0:22:43]
и это получается уже

[0:22:43 → 0:22:46]
пятый элемент массива сообщений.

[0:22:46 → 0:22:48]
То есть я сохраняю себе

[0:22:48 → 0:22:49]
этот комментарий и далее

[0:22:49 → 0:22:52]
для следующих слайдов я поступаю точно так же.

[0:22:52 → 0:22:53]
Я донаращиваю

[0:22:54 → 0:22:55]
общий массив сообщений

[0:22:56 → 0:22:57]
при генерации контента

[0:22:57 → 0:22:59]
для всех слайдов всей презентации.

[0:23:00 → 0:23:02]
Получается, что у меня как бы

[0:23:02 → 0:23:04]
есть история общения с нейросетью,

[0:23:04 → 0:23:06]
где дописываются новые сообщения

[0:23:06 → 0:23:07]
и ее новые ответы

[0:23:07 → 0:23:08]
на мои сообщения.

[0:23:09 → 0:23:11]
Таким образом, если для

[0:23:11 → 0:23:13]
презентации нужно, чтобы мысль

[0:23:13 → 0:23:15]
как-то последовательно развивалась,

[0:23:15 → 0:23:17]
я сохраняю контекст предыдущих слайдов,

[0:23:17 → 0:23:19]
чтобы следующие при генерации

[0:23:19 → 0:23:20]
учитывали этот контекст.

[0:23:21 → 0:23:23]
При общении с нейросетью

[0:23:23 → 0:23:24]
у нас есть такая возможность

[0:23:24 → 0:23:27]
отправлять туда массив сообщений.

[0:23:27 → 0:23:29]
Надо просто помечать,

[0:23:29 → 0:23:30]
какой из них системный,

[0:23:30 → 0:23:31]
какой от пользователя,

[0:23:31 → 0:23:33]
а какой ответ от AI.

[0:23:34 → 0:23:36]
В общем-то, это и все.

[0:23:36 → 0:23:38]
И потом у нас остается только

[0:23:38 → 0:23:41]
собрать все эти ноды воедино.

[0:23:41 → 0:23:42]
Как это делается?

[0:23:43 → 0:23:45]
Каждая нода это функция.

[0:23:45 → 0:23:47]
Мы должны просто добавить

[0:23:47 → 0:23:48]
в наш State Graph.

[0:23:48 → 0:23:50]
State Graph прокидывается изначально

[0:23:50 → 0:23:52]
состояние, с которым мы хотим начать.

[0:23:53 → 0:23:55]
В этом состоянии можно еще и описать,

[0:23:55 → 0:23:56]
как именно обновляется каждый ключ.

[0:23:57 → 0:23:58]
Редюсеры добавить.

[0:23:59 → 0:24:00]
После чего,

[0:24:01 → 0:24:02]
получившемуся State Graph,

[0:24:02 → 0:24:04]
мы добавляем ноды.

[0:24:04 → 0:24:06]
Мы их как-то должны назвать,

[0:24:06 → 0:24:08]
как вы видели на графе,

[0:24:08 → 0:24:10]
и прокинуть функцию.

[0:24:12 → 0:24:13]
Затем

[0:24:13 → 0:24:15]
нам нужно добавить

[0:24:15 → 0:24:16]
описание, как мы будем

[0:24:16 → 0:24:18]
перемещаться между этими нодами.

[0:24:18 → 0:24:20]
От какой в какую можно пойти,

[0:24:20 → 0:24:22]
где начало и где конец.

[0:24:23 → 0:24:24]
Start это Prepare,

[0:24:24 → 0:24:26]
а Final это End.

[0:24:26 → 0:24:28]
Это означает, что работа

[0:24:29 → 0:24:30]
агента после выполнения

[0:24:30 → 0:24:33]
ноды Final будет завершена,

[0:24:33 → 0:24:34]
и он отдаст нам результат

[0:24:35 → 0:24:37]
генерации презентации.

[0:24:37 → 0:24:39]
Все, потом это дело компилируем.

[0:24:39 → 0:24:40]
В общем, работаем

[0:24:41 → 0:24:41]
с этим.

[0:24:43 → 0:24:45]
Там уже ничего сложного.

[0:24:45 → 0:24:47]
Можно посмотреть,

[0:24:47 → 0:24:49]
вот такая презентация

[0:24:49 → 0:24:51]
получилась на моем сервисе.

[0:24:51 → 0:24:53]
Можно тоже сходить

[0:24:53 → 0:24:55]
и попробовать посмотреть,

[0:24:55 → 0:24:56]
как они генерируются.

[0:24:57 → 0:24:59]
Вот такую презентацию

[0:24:59 → 0:25:01]
я сгенерировал.

[0:25:01 → 0:25:03]
Как видно, тут примерно

[0:25:03 → 0:25:05]
о том же.

[0:25:05 → 0:25:06]
В общем, тоже изображение

[0:25:06 → 0:25:07]
в едином стиле,

[0:25:07 → 0:25:10]
и на моем сайте можно

[0:25:11 → 0:25:12]
проиграть презентацию.

[0:25:13 → 0:25:14]
В общем,

[0:25:15 → 0:25:16]
это и все.

[0:25:17 → 0:25:18]
Основной вывод

[0:25:18 → 0:25:20]
это нужно пробовать,

[0:25:20 → 0:25:22]
делать это не какой-то

[0:25:22 → 0:25:23]
rocket science,

[0:25:24 → 0:25:26]
не только для ML

[0:25:26 → 0:25:28]
инженеров,

[0:25:28 → 0:25:29]
специалистов.

[0:25:29 → 0:25:31]
Сейчас я фронт-энд разработчик

[0:25:31 → 0:25:32]
в основном,

[0:25:32 → 0:25:34]
сделал вот такую вещь.

[0:25:35 → 0:25:37]
Соответственно, когда объединяются команды,

[0:25:37 → 0:25:39]
можно делать вещи более сложные

[0:25:39 → 0:25:40]
и интересные.

[0:25:40 → 0:25:43]
Это дело развивается очень интенсивно,

[0:25:44 → 0:25:46]
и всем стоит попробовать

[0:25:46 → 0:25:47]
залезть, посмотреть,

[0:25:47 → 0:25:49]
как эти вещи создаются,

[0:25:50 → 0:25:51]
может быть, создать что-то

[0:25:52 → 0:25:53]
крутое и интересное.

[0:25:53 → 0:25:56]
В любом случае, вы все,

[0:25:56 → 0:25:58]
кто это смотрит, слушает, читает,

[0:25:58 → 0:25:59]
будете

[0:26:00 → 0:26:02]
участвовать в разработке

[0:26:02 → 0:26:04]
чего-то, что связано и агентами.

[0:26:04 → 0:26:05]
Это уже неизбежно.

[0:26:06 → 0:26:08]
И я думаю, всем стоит

[0:26:08 → 0:26:10]
попробовать самостоятельно

[0:26:10 → 0:26:11]
эту историю поговорить,

[0:26:12 → 0:26:14]
чтобы, по крайней мере,

[0:26:14 → 0:26:15]
если даже вам эта идея не нравится,

[0:26:16 → 0:26:17]
просто быть знакомым

[0:26:17 → 0:26:19]
с тем, как такие вещи создаются.

[0:26:20 → 0:26:21]
Вот.

[0:26:21 → 0:26:23]
По QR-коду доступно

[0:26:23 → 0:26:25]
собственно мое приложение,

[0:26:25 → 0:26:27]
которое генерирует презентации,

[0:26:27 → 0:26:29]
но понятно, что генерация завязана

[0:26:29 → 0:26:32]
на потребление токенов,

[0:26:32 → 0:26:33]
которые стоят денег,

[0:26:33 → 0:26:35]
поэтому какое-то время

[0:26:35 → 0:26:36]
оно может быть доступно,

[0:26:36 → 0:26:38]
но потом я отключу.

[0:26:39 → 0:26:41]
На этом спасибо

[0:26:42 → 0:26:43]
и всем пока.
