📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Самый ЦЕННЫЙ НАВЫК для создания умных ИИ агентов: Context Engineering в n8n

Несерьезный айтишник33:58

Transcription

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

Итак, я импортировал сюда шаблоны из предыдущего видео с помощью кнопки "import from file". У меня здесь есть скачанный из моего Telegram-бота N1 шаблон. И последнее видео было про мегарак. Я забрал все файлы, которые у меня здесь, импортировал их в N8N и прошёлся вот здесь везде, проставлял нужные айдишники на нужный workflow. Вы можете использовать "from list". Здесь будут ссылки на другие workflow, но просто у меня уже настолько много здесь workflow, что мне проще указывать айдишники, которые находятся просто в урле. К сожалению, урл здесь не будет видно, но я думаю, что вы догадаетесь, где находится ID.

Если вы не смотрели предыдущие два видео, где мы создавали раксистему с гибридным поиском или ранкингом, то ничего страшного, вы всё равно поймёте суть этого видео. Просто поставьте на паузу, запишите это на посмотреть и сделайте это позже. Дайте мне полминутки, я для контекста расскажу, что здесь происходит. У нас есть Telegram-бот, который помогает пользователям находить какие-то заведения с едой на Бали. То есть пользователь, например, пишет, где позавтракать в определённом районе, и мой агент отвечает, куда сходить. На основе рекомендаций не из интернета, а из нашей базы данных.

Я вот это всё лишнее удаляю. Также я, как обычно, удалю вот эту вот часть, потому что она мне будет не нужна. Удалю вот это вот "merch". Здесь я соединю это. Так, я буду использовать "chat trigger" просто потому, что мне так удобнее экран демонстрировать. Вы можете ничего не трогать и продолжать общаться с Telegram-ботом. И, соответственно, эта нода мне тоже не нужна, и мне нужно внести несколько исправлений. То есть, давайте я это запущу, напишу что-нибудь. Это у нас, соответственно, сразу же сломалось, потому что у нас нет здесь текста. Перетаскиваю сюда "chat input". Далее захожу вот сюда. Здесь у нас тоже текст, но у нас текста больше нет, поэтому здесь будет "chat input". И вот здесь вот в памяти мне тоже нужно заменить "chat ID" на "session ID". Вот теперь всё должно быть о'кей.

Давайте я задам вопрос: "Где позавтракать?". Нам возвращается одно максимально подходящее заведение на основе нашей базы данных. Она у нас на основе Google Диска. Здесь загружено всего несколько заведений, поэтому именно качество ответов давайте пока не анализировать. Суть вообще не в этом. И казалось бы, всё здесь работает, но давайте добавим всего лишь одно требование, чтобы всё это сломать. Возвращать одно заведение. Но если есть ещё, то добавлять, что "может подсказать ещё". Для того, чтобы подсказать ещё, логично, что нам нужно получить не одно заведение вот здесь, а несколько заведений. Только таким образом мы будем знать, что в базе вообще есть подходящие варианты. Давайте просто на бум представим, что пользователь больше чем пять заведений не будет открывать. То есть он будет просить, просить, просить, и в какой-то момент он устанет просить, и пусть это будет число пять. Вот при таких водных мы получим вот здесь не один элемент, а пять элементов. И нам нужно внести какие-то изменения в промт для того, чтобы рассказать, как с этим работать. Поэтому я добавил новые условия: "Если в данных больше трёх заведений, верни первые три и скажи, что есть ещё".

Давайте проверим, как это работает. Я сброшу сессию и напишу снова: "Где позавтракать?". Здесь мы получаем три заведения, и он нам пишет, что "если хочешь, могу рассказать ещё". Там есть вариант. То есть, по сути, если я сейчас попрошу ещё, он должен добавить два варианта, которых я не видел здесь, но которые он получил из гибридного поиска. Давайте это сделаем. Напишем "Ещё". И мы видим, что он снова сходил в гибридный поиск, запросил снова заведение и снова нам что-то ответил. То есть мы ожидали, что он нам отправит ещё два заведения, которые не отправил вот здесь, но он снова идёт в гибридный поиск и снова что-то там ищет. Ну, очевидная проблема. Промт. Давайте вернёмся в промт и попробуем что-то с ним сделать. "Если пользователь просит ещё в какой-либо форме без нового вызова, верни следующие три заведения. Если меньше, то верни все". И давайте, кстати, вот здесь "places" тоже заменим на "hybridsearch", чтобы у нас как-то всё одинаково было.

Итак, давайте попробуем задать те же вопросы. Просим "Ещё". Нового запроса в "hybrid search" не было, но и у нас возвращаются заведения, которых вообще нет в базе данных. То есть GPT сгаллюцинировал и на основе своих данных дал ответ. Ну, причина, наверное, опять в том же. Промт. Ну, а если промт хороший, то, наверное, нужно просто поменять модель на подороже. А если это не поможет, наверное, нужно покрутить параметры у модели. Я не буду тратить ваше время, ничего из перечисленного не поможет. Давайте посмотрим, почему это происходит.

На самом деле я подключил "Smit" просто для того, чтобы посмотреть более подробно трейсинг того, что происходило в EA-агенте. Вам в рамках этого видео ничего об этом сервисе знать не нужно, но если вы хотите какое-то отдельное видео про инструменты, которые есть вокруг N8N, напишите об этом в комментариях. Если действительно будет какой-то спрос, я потрачу время и запишу об этом видео. Ну так вот, видим мы здесь вызовы нашего workflow. И вот первый наш вызов, соответственно, привёл к тому, что в ChatGPT попали данные из инструмента "hybrid search". Вот здесь вот он был вызван. И нам "hybrid search" вернул несколько мест, и в конечном итоге это всё попало в GPT. И он нам дал ответ на основе этих данных. То есть он же не сразу заволлюционировал, пошёл искать что-то в интернете. Нет, он понял, что у нас есть инструмент "hybrid search". Он вернул какие-то параметры, и на основе этих параметров он дал вполне себе адекватный ответ.

Теперь мы переходим в следующий вызов - это запрос "Ещё". И вот здесь вот мы видим, что у нас есть "system message", наш вопрос, ответ от AI, наш вопрос, и всё. То есть мы понимаем, что у GPT, когда мы вызвали его второй раз, вот здесь вообще нет контекста от гибридного поиска, который мы вызывали в первый раз. То есть он просто не попадает в память. И действительно, это так и есть. Если мы посмотрим вот здесь вот "Simple Memory", что он нам отвечает, да? Он нам отвечает диалог между AI агентом и пользователем. Но здесь нет вызова никаких тулов. Ну, наверное, можно просто зайти в N8N, нажать здесь "Add Option" и здесь есть "вернуть промежуточные шаги". О'кей, давайте мы это сделаем. Сохраним и попробуем сделать всё то же самое. Просим "Ещё". Но, к сожалению, это не помогает. Давайте убедимся, что всё ещё вот здесь вот при запросе "Ещё" у нас нет никакого контекста. Итак, у нас есть "system prompt", "human". Ответ AI, "human". И снова нет ничего, что связано с вызовом тула, который мы вызываем в первый раз. Вот здесь вот гибридный поиск. Казалось бы, суперпростая задача, но мы уже потеряли контекст, который так необходим нашему EA-агенту.

Это приводит нас к простому выводу, что мы должны самостоятельно научиться управлять памятью этого EA-агента. Я бы здесь мог просто вызвать ноду кода, запустить это ещё раз. И вот благодаря тому, что я нажал вот эту галочку, у нас здесь появились промежуточные шаги, и я мог бы как-нибудь это всё вытащить вот отсюда. То есть здесь есть вот "observation", то есть это результат вызова нашей тулы "hybrid search". И вот здесь пересобрать всю эту память и сохранить её заново. Но тогда вы бы мне сказали: "Ань, да пойдём просто код писать, что нам этот N8N костли какие-то здесь клепаем". И будете правы, потому что N8N - это всё-таки no-code или low-code платформа, и хотелось бы коды писать минимально и использовать те возможности, которые у нас есть здесь. Именно поэтому в этом видео я буду использовать стандартные средства N8N для того, чтобы управлять памятью и решить конкретно эту задачу.

Но смысл этого видео, на самом деле, заключается не в том, чтобы научить вас управлять памятью в N8N. Я хочу донести мысль, что вам нужно научиться управлять контекстом. То есть, если вы, например, сейчас пойдёте и напишите в поиске "топ дизайн паттернов при создании агентов", вы получите какие-то паттерны и сможете с помощью них решать задачи. И это всё, на самом деле, про управление контекстом. То есть цепочки промптов, RAG, RAG, мультиагентные системы и так далее - это всё способ управлять контекстом. Даже на протяжении этого плейлиста мы уже научились реализовывать несколько паттернов. То есть даже с помощью того же RAG мы достаём только нужные данные и управляем контекстом агента. Я хочу при решении этой задачи представить, что мы не знаем никаких паттернов и просто руководствоваться здравым смыслом, помня несколько принципов у себя в голове. Мы будем стараться изолировать контекст, то есть решать одну задачу одним EA-агентом и стараться уменьшать контекст. Если для кого-то меньше галлюцинаций - это не проблема, да, мы можем просто взять модель подороже. Ну а вот сделать систему подешевле - это достаточно привлекательно для ваших клиентов. Это будет даже вашим конкурентным преимуществом, что вы можете собрать систему на дешёвых моделях, и она будет работать так же, как бы работала на дорогих моделях.

Я тут немножко отвлёкся, поэтому уже успел стемнеть, и я включил свет. Прежде чем мы будем что-то делать в N8N, я предлагаю на схеме верхнеуровнево понять вообще, какие запросы у нас могут быть. Понятно, что могут быть запросы вообще не по теме. Нам нужно какой-то построить гард, который будет отстреливать, что он помогает только с поиском заведений. Но это вообще отдельная история, она никак не повлияет на ход этого видео. Поэтому у нас будет только несколько запросов. Если это новый запрос, да, мы должны очистить всю старую память. Нам не нужны места из другого запроса. То есть, если я искал пиццу, а начал искать пельмени, мне не нужны места с пиццей. Поэтому я должен очистить память, достать новые данные и положить их в память. Только потом обратиться к последнему агенту, который с этой памятью работает. Далее, запрос может быть обновлённый. То есть, если, например, я искал пиццу, а потом я докинул район, то, соответственно, нужно помнить, да, какой-то контекст и добавить туда район и достать новые данные, положить их в память и снова пойти в отвечающего агента. То есть мы не должны очищать память агента, потому что он должен помнить, что вообще до этого было. Ну и последний тип - это когда он просто просит ещё заведения, как раз ту логику, которую мы хотим реализовать. И нам вообще не нужно ничего чистить, ничего доставать из памяти. Нам нужно просто обратиться к отвечающему агенту, и он будет в памяти держать уже все заведения и, соответственно, доставать те заведения, которые не успел ещё порекомендовать ранее.

И вот эта часть - это, по сути, какой-то роутер, который мы должны реализовать с помощью LLM. То есть, по сути, он должен находиться между запросом и конечным исполнением этого запроса. То есть где-то вот здесь. Далее у него должна быть та же память, что и у агента. Давайте просто вот эти части пока вынесем сюда. Они будут общие. То есть мы сейчас не обращаем внимания на то, что здесь можно там докрутить температуру или поменять вообще модельку. Будем использовать одну и ту же модельку. Сейчас это опять же роль особо не играет. Здесь мы будем использовать LLM chain, да, то есть "basic LLM chain". Здесь мы добавляем "system prompt", то есть саму инструкцию, как будет работать этот роутер.

Итак, я добавил небольшой промт, то есть добавил ему роль, что он является роутером в этой системе. Его задача - это определять тип запроса. Здесь я писал, каким образом мы будем определять этот запрос. То есть, если это обновлённый запрос, то это "updated query". Если это тот же запрос, то это "same query". Если это новый запрос, то есть ни один из предыдущих пунктов не подошёл, то это "new query". И привёл примеры использования "same query", "updated query". А "new query" методом исключения получается, поэтому никакого примера здесь не нужно. Также я хотел бы определённый формат. На самом деле, кто не знает, вот эта галочка, она тоже не делает никакой магии, она просто в "system" добавляет больше инструкций. Это можно написать самостоятельно, но просто N8N предоставляет удобный интерфейс для того, чтобы это сделать. Поэтому я перехожу в "output parser", выбираю "structure output parser". И вот здесь выбираю "json schema", то есть я хочу описать именно схему. Я хочу получать объект с типом "type". И у этого типа "type" есть только три опции: это "same query", "updated query" и "new query". Ну, если вдруг что-то не так пошло с форматом, соответственно, я его хочу поправить. И всё это пока будем делать с помощью одной модели, то есть вот этой. Поэтому я сюда эти линии проведу.

Далее нам нужно этому агенту предоставить вообще какую-то память. То есть сейчас он не понимает, у него просто пришёл запрос, но нет никакого контекста. Поэтому вот здесь мы будем определять контекст самостоятельно. По сути, мы должны здесь разместить текущее сообщение, то есть то, что мы получили из "chat trigger", и историю сообщений. А история сообщений по сути находится вот в этой ноде памяти "Simple Memory". Так, давайте я вот это удалю, чтобы это нам не мешало. И вот здесь мы будем получать данные из "Simple Memory", то есть вот отсюда. Для этого будем использовать "memory manager". И здесь есть "get many messages". По сути, всё, что нам нужно сделать - это подсоединить к этой же памяти вот эту вот ноду, и она вернёт эти данные в роутер. Давайте что-нибудь сюда напишем. Так, мы не можем написать, потому что у нас есть проблемы вот здесь. О'кей, давайте сюда пока что-нибудь напишем. 1, 2, 3, это неважно. Возвращаемся вот сюда. И здесь теперь определим, что сюда приходит. Итак, что у нас здесь есть? У нас есть "chat Memory Manager" и у нас здесь есть "messages". То есть, по сути, здесь мы можем написать "история сообщений" и вот сюда перетащить эту историю сообщений. Ещё мы сюда должны поместить, соответственно, текущее сообщение. И оно у нас приходит из "chat message receive", то есть вот это вот "chat input". Ну и по сути теперь агент обладает достаточным контекстом для того, чтобы определить тип запроса.

Итак, я сбросил сессию. Давайте я вот это вот отсоединю пока и запущу ещё раз каким-нибудь запросом. "Где позавтракать?". Итак, у нас произошла какая-то ошибка. Скорее всего, потому что мы вот эту линию отсоединили, и теперь одной памятью пользуются агенты, которые не связаны. Короче говоря, ладно, давайте я просто ещё раз задам этот вопрос и посмотрим, что здесь вышло на выходе. И вышло здесь "new query". Мы все остальные "query" проверим позже, когда у нас будет какой-то контекст, потому что мы сейчас в контексте не продолжаем сохранять ответы от EA-агента, то есть у нас нет никакого диалога. Поэтому пока продолжим с тем, что есть.

Здесь мы должны разместить ноду "switch", то есть мы должны по какому-то пути направить запрос в зависимости от его типа. Мы вот сюда перетаскиваем тип. И в каждой опции я просто буду сюда вставлять другой "query". Например, здесь это "new query". И в "rename output" буду называть это "new query", чтобы у нас не были там нули, единички просто-напросто. По такому же принципу я сейчас добавлю все типы, которые у нас описаны в предыдущей ноде. Я это сделал. То есть у нас "new query" есть, "updated query" и "same query". В общем, всё то, что есть вот здесь.

Если у нас "same query", мы просто можем продолжить общение с EA-агентом, потому что ничего не нужно складывать в памяти. А вот если у нас "new query", "updated query", здесь немножко сложнее. Поэтому, если у нас "new query", что мы должны сделать? Мы должны очистить память этого EA-агента. То есть в нём не должно быть тех мест, которые не связаны с текущим запросом. То есть, если я до этого искал пиццу, а теперь ищу мясо, мне не нужно иметь в памяти EA-агента те места, которые были связаны с пиццей. Поэтому что я делаю? Я нажимаю здесь "+" и добавляю ещё один "memory manager". И здесь выбираю "удалить сообщения". Здесь я выбираю "удалить все сообщения", потому что мне не нужно удалять какие-то последние несколько. И соединяю это с памятью, которой есть у AI агента. Давайте это всё подвинем. Это, кстати, нода нам тоже не нужна.

Далее нам нужно каким-то образом достать из гибридного поиска места и сложить их в память агента. То есть то, что как будто бы мы ожидали от него получить, когда тестировали это в самом начале. Поэтому мы не будем с помощью этого EA-агента доставать что-то из гибридного поиска. Мы это можем отсоединить, просто поставить этот пока сюда. И теперь нам нужен кто-то, кто будет создавать "query", который создавал вот этот вот AI agent. Нам здесь достаточно просто использовать "basic LLM chain" с определённой инструкцией. Я это подвину тоже пока вот сюда, куда-нибудь далеко. И здесь буду использовать "Basic LLM chain". Здесь мы будем использовать ту же модельку. Мы просто в этом видео используем одни и те же модели. Это никакой роли не играет. Итак, по сути, когда мы пошли по пути нового "query", нам всё, что нужно - это определить на основании запроса пользователя запрос в базу данных. Здесь мы тоже должны описать это "system prompt". Здесь я описал роль по тому же принципу, что мы делали в роутере. То есть он является создателем запроса для базы данных в системе, которая помогает пользователям найти заведение. Кстати говоря, вот здесь вот у меня была опечатка. Давайте я её тоже исправлю. То есть "в системе". Ну, просто чтобы красиво было. И заодно вот здесь переименую в "Query creator".

Итак, давайте вернёмся ещё раз сюда. То есть задача сформулировать поисковый запрос для баз данных с гибридным поиском на основе текущего сообщения и истории сообщений с пользователя. Вот это пока я оставил на будущее, потому что мы эту ноду будем использовать и в "updated query". То есть история сообщения нам тоже будет нужна. Здесь я добавил в инструкцию те вещи, которые я заметил, пока мы тестировали. То есть он постоянно добавлял "на Бали", хотя это было в другом системпромпте. Этот агент не знает про это, поэтому, наверное, это вообще не имеет значения. Просто не будем включать локацию в запрос, да, этого будет достаточно. То есть, если кто-то будет писать в запросе там "на Бали", "в Чингугу" и так далее, он всё равно это будет просто игнорировать. За это будет отвечать другая нода. Она у нас как раз-таки называется "get location". Это отдельный workflow с "Basic LLM chain" тоже.

Итак, теперь нам здесь вот тоже нужно определить ту же историю, которая была вот здесь вот в роутере. То есть мы можем это просто скопировать. Здесь у нас пока не будет никакой истории сообщения. Это будет пустой массив. Давайте проверим эту цепочку, когда история сообщений уже очищена. То есть на основе текущего сообщения мы будем получать какой-то определённый "query" запрос. И нам нужно, конечно же, определённый формат получить. Поэтому я нажимаю "output parser". Здесь выбираю тот же "output". Здесь мы также выбираем "json schema". Итак, я хочу получить "query" с типом "string", то есть это какая-то строка. И здесь описал ещё немного правил, то есть это какие-то ключевые слова без вопросительных слов и склонений, просто чтобы какой-то адекватный результат получить на этом этапе. Итак, мы также будем использовать "auto fix format", если вдруг что-то пойдёт не так. Ну и подсоединяем это всё к той же модельке от OpenAI. Я понимаю, что здесь уже очень много линий получается. Это всё, конечно же, можно причесать, можно какие-то части вносить в отдельные workflow, но суть этого видео вообще не в этом. Я надеюсь, что вы поймёте, что я хочу донести. Давайте это подвину чуть-чуть ниже, потому что у нас всё растёт и растёт наш flow.

Теперь, когда мы получили "query", мы можем обратиться в гибридный поиск. То есть он у нас вот здесь вот находится, но это "tool", а нам нужно теперь это использовать как отдельную ноду. Поэтому мы будем вызывать отдельный workflow. И здесь нам нужен "subworkflow", то есть "execute subworkflow". Здесь я из списка могу выбрать тот "subworkflow", который у нас есть. Как я говорил, я это делаю по ID, но сейчас я это сделаю просто из списка. Вы тоже можете вот по названию выбрать конкретный. Просто у меня уже этих гибридных поисков как минимум два в разных папках лежит. Итак, у нас здесь те же поля, то есть здесь вот должна быть "get location". Мы, кстати, её сейчас тоже добавим. И вот сюда мы должны вставить "query", который до этого формировал AI agent, да? А теперь мы должны это получить вот из этой ноды. Ну и в конечном итоге это всё уедет вот сюда. Вот это вот нам нужно отсоединить, переместить это вот сюда. По сути, здесь что должно произойти? У нас должен быть вот такой вот процесс. То есть после этой ноды мы должны запросить и "get location", и "query creator", да, и у них отдельная логика работы. И потом мы должны всё это смерджить в одно. То есть вот это и вот это мы должны смерджить и в конечном итоге потом уже пойти и запросить всё в гибридном поиске. Как-то вот так это должно происходить. Ну, здесь нам нужно поменять опцию. То есть мы должны всё это скомбинировать и просто будем использовать все возможные комбинации. Так как у нас два инпута, нам это в целом подходит.

Так, я предлагаю запустить этого Франкенштейна, потом уже чуть-чуть причесать, чтобы это всё как-то аккуратнее выглядело. Здесь мы получаем ошибку. Это логично, потому что у нас здесь нет "chat input". Мы должны использовать вот это вот. То есть "chat input" по хардкорду. Здесь мы тоже сломались. Это тоже логично, потому что у нас есть теперь другие параметры. Итак, "query" мы должны перетащить вот сюда. "Match count" мы должны получать пять, как мы и получали в этой ноде. И здесь мы можем просто скопировать вот этот вот фильтр, чтобы не писать его ещё раз, и вставить его вот сюда. Но нам нужно кое-что поменять, да? А нет, даже ничего менять не нужно, потому что и здесь, и здесь это "output".

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

Итак, что мы должны делать дальше? После того, как мы получили заведение, нам было бы неплохо проверить, что у нас массив не пустой. То есть мы будем использовать "if". И вот этот вот массив перетащим вот сюда. Здесь выберем "массив" и из "not empty", то есть он не пустой. Если он не пустой, да, давайте так и назовём "not empty". Если он не пустой, то, соответственно, мы должны его положить в память. То есть мы будем использовать опять "memory manager". И здесь будем использовать "insert messages". И какие же "messages" мы будем вставлять? То есть давайте вот это вот запустим, чтобы что-то прошло дальше. И будем вставлять мы вот эти вот "messages". То есть мы добавляем здесь "message", выбираем "AI", потому что это AI, и перемещаем сюда "places", да? То есть вот таким вот образом. Мы попробуем пока больше ничего не добавлять. То есть мы можем здесь добавить контекст, что это вызвано с помощью какой-нибудь "tool", да, то есть как это было ранее в агенте. Просто он это не сохранял, но мы попробуем так и посмотрим, как это будет работать. Напишем "add places to agent memory". В целом, всё логично. Здесь мы подсоединяем это к той же памяти. Ну и можем продолжить работать с нашим агентом.

А вот если ничего не найдено в базе данных, здесь надо, конечно, прорабатывать UX. То есть можно, например, автосообщение какое-то отправить, то есть "больше ничего не найдено", и даже не тратить токены на LLM. Но здесь просто несколько сценариев есть. И, например, если человек добавил какой-то контекст, то мы должны ему отправить не "в этом районе ничего нет", да, например, а если он первый раз отправляет сообщение, то можно просто написать, что "ничего не найдено". В общем, я пока предлагаю об этом не думать. Ничего дополнительного мы в память не добавим и сразу обратимся к AI агенту.

Давайте попробуем запустить это ещё раз. Я сбрасываю сессию и спрашиваю: "Где поесть?".

пиццу. По сути, он теперь нам нужен ответить на основе тех данных, которые у нас есть вот здесь.

Итак, у нас возникла какая-то проблема в том, что он не может что-то смпить. О'кей, я догадываюсь, что вот здесь вот мы сохраняем массив, а нужно сохранять строку. Я добавил сюда JSON Stringify, то есть мы превратим массив в строку JSON. Я думаю, что теперь проблемы не будет.

Давайте сбросим сессию. Я вот это опять копирую. И мы получаем заведение додо. И если мы посмотрим, да, то вот здесь вот здесь не очень удобно смотреть. Давайте я посмотрю LMIT. Итак, вот у нас последнее это сообщение. И вот что у нас здесь есть. У нас есть System Prompt, потом есть вот этот вот самый AI, который мы ожидали ещё в самом начале, что он тоже будет складываться в память. Здесь он есть. Далее мы видим запрос пользователя, где поесть пиццу, и он на основе вот этих вот данных что-то отвечает. Но пока что это работает точно так же, как работало и ранее. Просто у нас теперь позиция вот этого вот массива, да, она не в конце как отдельная тула, а вот здесь вот отдельным сообщением от самого яагента. Но мы уже очень близки к сути, и вы скоро поймёте, зачем мы вообще всё это делаем.

Итак, что у нас уже есть? На самом деле у нас уже есть обработка same querery. То есть если я сейчас попрошу ещё, у нас уже всё должно работать. И потом мы уже доделаем updated query как отдельную логику. Давайте проверим. То есть я отвечаю на его просьбу. Давай ещё. И в прошлый раз он начинал искать данные из интернета, потому что у него не было никаких других данных. И вот смотрите, насколько важен контекст и неважен промпт в нашем случае. Что здесь вообще данные, которые позволяют гаволюционировать направо и налево, потому что здесь есть какие-то инструменты, которых вообще у Яагента нет. Но тем не менее он не стал отвечать ничего из интернета. Он сказал, что больше пицц у него нет. А всё почему? Потому что у него в контексте, вот это наш новый запрос, у него в контексте есть эта история. То есть он не пытается найти контекст, пофантазировать, где взять этот контекст. Он понимает, что есть системпром, есть вот этот контекст. Далее есть запрос пользователя, ответ на этот вопрос, и он просит ещё. И GPT уже не нужно думать, где брать этот контекст. Он перестаёт галлюцинировать даже с плохим промптом и отвечает нам, что пицц вот в этом массиве больше нет.

Итак, мы возвращаемся в найтены. Хоть у нас не готова вот эта вот дорожка, которая с Updated query, я думаю, что мы можем теперь проверить новый quyery. То есть мы не будем сбрасывать сессию, и я попрошу найти мне места с мясом, где поесть мясо. По сути, это должно определиться как новый запрос. Мы должны очистить память. То есть от этого контекста, который сейчас отправляется про пиццерии, у Иагента больше не должно быть. Давайте это проверим.

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

Итак, что нам осталось сделать? Ну, во-первых, давайте убедимся, что в этом контексте нет тех заведений, которые были до этого. То есть память у нас очистилась, я вернусь в Lмиmit. Итак, вот он наш запрос. И вот здесь мы видим, что здесь VI и совсем другие заведения и больше ничего нет. Это, собственно, то, что мы хотели получить. У нас теперь в памяти хранятся актуальные данные. Это как раз-таки ещё и соответствует принципу уменьшения контекста. То есть мы могли бы хранить и предыдущий контекст, но зачем это делать, если это не нужно для решения задачи я агентом.

Итак, мы переходим к линии Updated Query. Нам всё также нужно будет получать, всё также мрджить и что-то искать. Поэтому вот эту часть, на самом деле, мы должны переиспользовать. Давайте я вот это вот уберу куда-то наверх. Вот здесь вот как-то немножечко причешу это всё. Ладно, я думаю, что это выглядит приемлемо. Просто эти линии добавляют немножко когнитивной нагрузки ко всему этому. Но ничего страшного, зато работает. Это всё можно, конечно, причесать. Просто если я сейчас это буду делать, я увлекусь и мы не закончим это видео. Да, можно просто вот так куда-нибудь вниз пока их утащить, чтобы как-то побольше пространства появилось.

Итак, если мы идём по линии Updated Query, то, соответственно, вот здесь в Qy creatory должна появиться история сообщения, иначе он не сможет сварить query на основе контекста, который был до этого. Поэтому мы должны либо отсюда из getem получить query, но тогда нам придётся вот здесь строить логику, что это либо пустой массив, если у нас вот здесь вот равно newquery, да, то есть если мы вот по этой линии пошли. Поэтому я предлагаю это всё объединить и просто ещё раз дёрнуть агентскую память для того, чтобы получить оттуда историю. Поэтому я здесь пишу опять чат Memory Manager и здесь выбираю get many messages. И по сути всё, что нам нужно сделать - это получить эту память и отдать её в quyer creator. Memory мы должны соединить с той же мемории, что и все остальные, собственно, у нас ноды. Ну и название можно взять отсюда. По сути, это можно было просто скопировать, на самом деле, вот эту ноду.

Итак, это название я вставляю вот сюда. Ну и фактически вот эту ноду можно сделать точкой вхождения вообще и того, и того сценария, потому что когда мы очистим память, должны продолжить по тому же сценарию. То есть сделать это вот так. И вот эту линию подсоединить вот сюда. Кстати говоря, у нас с серия будет проблема, потому что вот здесь вот у нас история сообщения есть, а здесь мы просто текст сообщения отсылаем туда. И опять же здесь нет контекста, поэтому эту ноду тоже придётся переделать, но мы это сделаем в конце, поэтому эта роли сейчас не играет. То есть мы должны очистить память, в конечном итоге всё равно её получить и вот здесь вот историю сообщений достать из вот этой Get Agent Memory 1. То есть актуальная история сообщений, даже если она была очищена.

Итак, давайте запустим этот процесс, то есть опять напишем что-нибудь новое. Пусть это будет тот же запрос, что был до этого. Теперь вот здесь вот мы должны подтянуть данные, которые есть из Get Memory 1. То есть вот она. И по сути всё, кроме локации должно у нас работать. Давайте пробовать это запускать. Где поесть мясо? Это должно уйти в новый запрос. Оно ушло в новый запрос. Отсюда должен вернуться пустой массив. Он вернулся. И агент нам отвечает три места, которые у него есть, и говорит, что может рассказать ещё. Давайте попросим ещё. Как мы видим, мы пошли в same query. Он продолжил список. Здесь у нас есть додо и 1918. Вот пока все местные места, которые могу посоветовать из баз данных. Я хочу обратить ваше внимание, что качество ответов сейчас не надо анализировать. У нас очень маленькая база данных. Мы вообще никак не фильтруем по скору. То есть, если у вас из пяти элементов задать хоть какой-то вопрос и вообще никак не фильтровать по скору, то в любом случае здесь какие-то топ- пять заведений должны были оказаться.

Итак, мы протестировали new query и same query. И если мы сейчас пойдём в updated query, то есть добавим, например, а если только в chгуu, то мы по сути должны оказаться вот здесь. Но вот эта getне сейчас не сработает, потому что нет никакого контекста. Но давайте всё равно попробуем, чтобы хотя бы понять, что в эту линию мы уходим.

Итак, мы ушли в same query, то есть сработало всё правильно. Даже вот эта нода на самом деле отработала, потому что задался вопрос про район именно сейчас. То есть, если бы он задавался ранее, а я бы уточнил что-то ещё, то вот тогда бы контекст был потерян. Я надеюсь, что вы ещё не поплыли и понимаете, о чём я говорю. По сути, вот это вот всё, что нам нужно было сделать для того, чтобы решить эту задачу, если говорить очень верхнеуровнево, да. Единственное, что мне нужно, конечно, исправить getтонерия, но это сейчас опять же никакой вообще роли не играет. Нам нужно просто создать точно такой же quyery creator и описать его для того, чтобы он на основе истории получал район. Я это уже три раза сказал. Извиняюсь, что повторяюсь. Я это сделаю в готовом шаблоне это будет, если вы вдруг его будете забирать из моего Telegram-бота. Просто сейчас мне не хочется тратить на это время, так как у меня ещё есть несколько важных вещей, которые я хочу вам рассказать.

Если вы помните, я вам говорил о принципе, что нам нужно изолировать контекст и пытаться его уменьшить. И вот здесь вот пока мы этот принцип нарушаем. То есть роутеру на самом деле вообще не нужно знать ничего о PlayS. У нас здесь они перечислены от этого я агента. Ему не интересно, какие там места есть. Ему интересен диалог. То есть о чём мы сейчас говорим с я агентом. На самом деле не очень правильно использовать вот эту же память для вот этого роутера, потому что ему намного меньше нужно контекста для того, чтобы решить эту задачу. Поэтому давайте мы вот эту вот память просто продублируем, положим её вот сюда. Эту мы назовём как всё неудобно. Давайте вот здесь это сделаем. Здесь есть rename. Пусть это будет agent memory. А вот здесь мы будем хранить только диалоги. То есть dialogs memory. Ключ мы тот же самый использовать не можем, потому что, ну вот session ID он уже есть, он используется для другой памяти. Поэтому мы здесь просто добавим диалог, какая-то приписка плюс session ID. То есть это будет уникальный ключ просто для другой памяти. Вот этот агент должен обращаться вот к этой памяти, да? То есть ему не нужна память вот этого агента.

Далее вот этот вот Clear agent Memory, она должна очищать агентскую память, но при этом она должна очищать и вот эту память тоже, потому что нам эти диалоги уже не будут нужны. Поэтому мы вот это вот всё сейчас опять подвинем вот сюда и добавим здесь ещё очистку другой памяти. Вот это мы сейчас удалим, соединим. Опять это соединим. И вот здесь мы просто проведём линию вот сюда. То есть она будет очищать именно диалоги. И здесь будет не Clear Agent Memory, а Clear Dialogs Memory. Я представляю, на самом деле, какое количество людей уже немножечко поплыла. Но, ребятушки, можно всё это пересмотреть несколько раз и понять, о чём я говорю. Чем больше я, конечно, таскаю эти линии, тем больше у меня греется нот, потому что здесь перерисовки на этом канвесе происходят.

Итак, мы идём дальше. И на самом деле вот здесь нам тоже не нужна агентская память. Для того, чтобы создать новый quyery, нам нужно просто иметь историю диалогов. Поэтому это я удаляю и подсоединяю это вот сюда. То есть Dialog с Memory. Здесь мы не GET agent memory, а get Dialogs memory.

Итак, получается, что агентская память у нас очищается вот здесь. Далее мы сохраняем в агентскую память места, то есть она нужна агенту. И сам агент подключён тоже именно к этой памяти. Как будто бы всё оклось нам в эту память диалогов что-то начать складывать. То есть мы её очищаем, мы её получаем, но ничего туда не складываем. Поэтому после того, как ягент нам что-то ответил, мы должны опять же сохранить что-то в память. И мы должны в память сложить ответ от е агента и сам запрос. То есть сначала мы должны сложить запрос чат. Ага, вот он. И здесь мы выбираем usеer. То есть мы складываем чат от юзера. Далее мы добавляем add messages и выбираем AI, то есть это то, что нам ответил AI агент, который вот этот вот. И сюда мы перетаскиваем output. То есть у нас получается идёт история usеer, потом AI, потом опять usеer, потом опять AI. В целом, всё правильно. И мы вот это подсоединяем к памяти, которая которая диалог с Memory. По моему субъективному мнению, мне кажется, это уже должно начать работать. Выглядит всё вполне адекватно.

Давайте запустим это с тем же запросом, то есть где поесть мясо. Ну и да, к сожалению, последней нода теперь идёт сохранение в память, мы не получаем ответа. Но здесь, по сути, должна быть следующей нодой - это отправка в Telegram, да? Либо я сейчас могу просто ноду кода какую-нибудь воткнуть или Edit field и достать вот отсюда нужный мне field, то есть output, и в конечном итоге его вернуть. Но я это сейчас делать не буду, потому что у меня и так есть output. Я могу его смотреть. И вот он нам говорит, что есть несколько мест, где можно заценить мясо. Давайте я попробую спросить Авчангу. Мы пошли по пути Updated query. И вот давайте здесь посмотрим, что у нас на каком этапе происходит. То есть мы получили диалоги, здесь где поесть мясо. Дальше и яй что-то отвечает. И у нас вообще здесь нет никакого контекста про places. Мы тратим сильно меньше токенов на input. Далее мы видим, что вот здесь вот мы складываем места в меory самого агента. Вот они, places. И в конечном итоге в агента улетают эти плейсы. И вот здесь вот в логах мы видим, что последний агент он как раз-таки уже с этими местами. Ну или можно там в L sмиmit это посмотреть в более удобном виде, но и так понятно, что они здесь есть.

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

И на самом деле можем пойти вот сюда и что-то здесь поменять. То есть у нас вот здесь есть гибридный поиск. Давайте сохраним эту же концепцию. То есть мы будем, когда сохранять в память, вот здесь добавлять, что это search. Пусть это будет так. И вот здесь вот что у нас есть. У нас здесь есть информация, что если пользователь просит ещё в какой-либо форме без нового вызова Search, вот это нам вообще не нужно, верни следующие три заведения. Ну и вот это вот на самом деле является причиной, почему он галлюционировал именно с текстом, да? То есть мы говорим, что если в данных больше трёх заведений, то скажи, что есть ещё. В данных действительно больше трёх заведений, поэтому он говорит, что есть ещё. Ну и в конечном итоге, когда уже он не нашёл ничего, он не стал писать это, потому что додумал, что писать это уже неуместно. Опять же, это урок не про промты, и промты - это в целом такая очень тоненькая тема. Я, кстати говоря, недавно писал пост в Телеграме по поводу промтов, именно те советы, которые я могу давать. Я, скорее всего, никогда не буду записывать видео про промты. Нужно конкретно на ваших задачах и данных тестировать. То есть я не из тех людей, которые там будут говорить вам, что нужно писать промт определённой длины, нужно определённо всё настроить. Как мы видим, это вообще никакой роли не играет. Если у вас неправильный контекст, вы получите в любом случае. И в обратную сторону тоже работает. Как мы видели, у нас был хороший контекст и очень плохой промпт, но всё равно мы получали результат лучше, чем в предыдущий раз, и получали намного меньше галлюцинаций. То есть вот эта вот приписка ещё - это всего лишь ошибка нашего промта вот здесь. И всё это сейчас работает на 4.1 min. То есть, если мы поставим сюда модель помощнее, то и результаты у нас будут лучше. Даже на очень плохих промтах мы будем получать, скорее всего, хорошее качество.

Я хочу обратить ваше внимание, что пока мы создавали этот контекст и держали принципы эти в голове, мы уже сделали несколько зон ответственности. На самом деле каждую из них можно дорабатывать. Например, вот эта зона отвечает за намерение пользователя, то есть понимает, что он делает прямо сейчас. Например, ещё до мы можем построить гард, который валидирует входные параметры. Например, если он ищет в стране, где мы не работаем, либо он задаёт какой-то вопрос, который вообще не связан с поиском заведений. Мы можем сразу же делать автоотбивку с помощью какого-то шаблонного сообщения Telegram и вообще не тратить токены на все дальнейшие ноды. Также, если, например, у нас вот здесь вот вырастет количество параметров для поиска, то есть, например, это сейчас просто э, которой я ещё поправлю, и строка сама quy, то далее это может быть ценовая политика, может быть сначала страна, потом район, может быть ещё что-то, ещё что-то. То есть вот эта штука отвечает за формирование query, которое пойдёт в конечном итоге в гибридный поиск. В самом гибридном поиске мы тоже можем, например, вот здесь мы просто сортируем документы и ничего с этим не делаем, но мы можем определить, какой скор от Кохир является допустимым, и не возвращать вообще неподходящее заведение, и таким образом ещё уменьшить контекст, ещё меньше мусора передавать дальше, ещё меньше будет галлюцинации и ещё меньше мы будем денег сжигать на каждый запрос. Если, например, у нас в базе данных нет подходящих заведений, зачем их вообще отдавать дальше по цепочке?

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

Я предполагаю, что это видео, скорее всего, не будет популярным, потому что я здесь не даю какую-то волшебную пилюлю, готовый Night шаблон, готовый промт, какие-то идеальные параметры. Ну а если это видео помогло вам, давайте мы поможем добраться до той аудитории, которому оно тоже поможет. Для этого нужно написать комментарий, поставить лайк, ну и, конечно же, подписаться. Ну и на самом деле, я бы хотел, чтобы это было не совсем формальностью, то есть действительно есть что обсудить. Вы можете предложить какие-то свои варианты улучшения этой схемы или поделиться каким-то своим опытом. Я буду рад это всё почитать. И думаю, что другие участники, которые будут проходить мимо этого видео, тоже будут рады узнать что-то новое. Ну и в конечном итоге это всё окажется в Telegram-боте. По ссылке в описании можете перейти и забрать этот шаблон. Ну, на этом у меня всё. До встречи в следующих видео.