articles/primakov.txt
2025-07-07 16:27:01 +03:00

2036 lines
55 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Тайм коды со слайдами
**слайд** обо мне [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]
и всем пока.