Привет! Не успел мир договориться, что вообще подразумевать под агентами, как в инфополе обычные агенты заменились аж сразу мультиагентными системами. Обычно большинство статей про агентов начинаются с фразы «на фреймворке X...» — ну так с фреймворком каждый может, а ты попробуй без него.

А вот мы и попробуем! Причем попробуем не самую тривиальную штуку, а замахнемся сразу на эксперимент с агентным управлением браузером.

Постановка задачи для browser-use

Мы хотим управлять браузером и этот визуал производит вау-эффект. Давайте просто порулим браузером LLM-кой:

к=
к=

Круто? Круто! А за конкретную задачу возьмем найти нам какой-нибудь товар на маркетплейсе, добавить его в корзину и отправить уведомляшку. То есть, мы дадим нашему агенту браузер, дадим ему задачу на естественном языке ̶а̶ ̶д̶а̶л̶ь̶ш̶е̶ ̶е̶%̶б̶и̶с̶ь̶ ̶с̶а̶м̶ ̶к̶а̶к̶ ̶п̶о̶с̶л̶е̶ ̶о̶н̶б̶о̶р̶д̶и̶н̶г̶а̶ и хотим просто получить ее решение, без детального объяснения промежуточных шагов.

Первично мы запускаем браузер и получаем его в свое распоряжение. Затем при заходе на страницу мы собираем самые важные теги, все это ужимаем и подаем в LLM, чтобы она нам подсказала — а где мы, собственно, находимся, и что у нас есть.

Само управление достаточно тривиальное — есть executeAction и есть обертки над действиями, и когда приходит json, то из него выбирает команда и выполняется.

Конечно, здесь очень много специфики (особенно про понимание основного контента на странице— его может быть очень много, он может быть нестандартный и его надо уметь отделять), но базово оно выглядит примерно так (я самое полезное и интересное выделил красненьким):

Рекомендашки, к слову, не мои, у нас же cold start
Рекомендашки, к слову, не мои, у нас же cold start

В коде мы разбиваем html-теги на группы, которыми мы хотим оперировать, а дальше их просто собираем.

Очень упрощенная штука для понимания содержимого страницы
Очень упрощенная штука для понимания содержимого страницы

По готовности мы просим ввести наш запрос, после чего линкуем одно с другим и просим выбрать лучшее действие из доступных.

В динамике (и да, конвертить огромные записи видео экрана в gif с макс размером 8 мб это отдельный вид искусства и убийства времени):

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

Давайте разбираться.

А почему, ктстати, не через Computer Vision ? А потому что изначально казалось, что CV сможет хорошо порешать интерфейсные задачи браузера, но жизнь оказалась суровее и более жестока к латенси и затратам, поэтому наиболее удачным подходом в индустрии на момент написания статьи признан DOM+скриншоты, то есть, комбинированный подход. Про CV у меня есть наброски статьи, но в этой статье vision-части не будет (но концепт там тот же), разберем именно DOM-based подход на практике.

И, надеюсь, я уже достаточно смог вас заинтересовать, чтобы сначала сделать достаточно большое отступление, но при желании его можно пропустить.

""" начало вступления о том, что мы собрались делать и зачем

Почему вообще возникла идея попробовать собрать агента на голой LLMке? Сейчас я много занимаюсь агентами и мы внедряем их в самые разные места: к юристам, в аналитику, БД и просто туда, где можно вытащить большой финэффект. И мне было интересно дать голой LLM порулить браузером на хоть игрушечной, но вполне реальной задаче. И да, нас ждет повышенная неэффективность, потому что кучу вещей мы специально не будем делать хорошо, потому что переложим их целиком на LLM. И да, я в курсе про A2A, фреймворки, и то что у Playwright есть MCP-коннектор. Да.

Что самое главное в агентах? На мой взгляд, помимо просто здравого смысла, в агентах главное то, подо что есть удачный англицизм — flow. У него нет такого же мощного однословного перевода на русский, но однажды я услышал выражение «понимать, как байтики едут», и это — оно. В агентах (как и в разработке софта в целом) очень важно понимать суть того, что мы делаем и как по этой сути едут байтики между сервисами и строчками кода.

Слишком жизненный мем, чтобы его сюда не приложить

В идеале нужно иметь хорошие данные и понимание как они ездят между процессами, хорошо понимать сами процессы — вот именно это я вкладываю в термин флоу

Чтобы агенты заработали — нужно очень глубоко влезть в тот процесс, который мы собрались агентизировать. Это не совсем барское дело, потому что нужно идти в поля и детально, очень атомарно разбирать вообще все, а потом много экспериментировать. Но по другому — никак.

Поэтому соберем проект, который будет управлять нашим браузером на основе команд на человеческом языке. Я такие проекты называю kamikaze coding, потому что они призваны решить одну конкретную задачу и одноразовые, а весь опыт идет в штаб.

конец отступления """

Вернемся к задаче.

Декомпозиция задачи

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

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

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

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

При заходе на страницу мы собираем список всех ссылок и передаём их вместе с исходным запросом в LLM. Модель выбирает лучшее действие (next best action) — ссылку, наиболее близкую к нашему запросу. В идеале стоило бы предварительно отфильтровать кандидатов (регулярка + BM25 + top-K), но в базовом эксперименте вываливаем всё «как есть», а потом после перехода оцениваем насколько мы приблизилсь к цели.

Пробуем!

Ищем женскую одежду со стартовой страницы
Ищем женскую одежду со стартовой страницы

Вот наш flow этой игрушечной задачи: разбираем, а что пользователь от запроса хочет вообще (если запрос общий, то ищем категорию, если конкретный, то продукт). Затем достаем все ссылки со страницы, фильтруем точно неподходящие (реклама, копирайты и так далее — все это легко, потому что ссылки категорий и товаров зафиксированы), а затем кидаем в LLM с просьбой подсказать, что из списка нам подходит к запросу больше всего, после чего через Playwright делаем по ней клик.

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

И мы видим, что по умолчанию в DOM нету всех-всех ссылок, а нам надо их получить. Делаем клик по большой синей кнопке «Каталог» (для простоты примера мы прицепимся к ней по классу, что ок в частном случае, но в идеале фолбеком ее тоже надо автоматом искать: это хоть и решаемый, но все же отдельный вид искусства), а затем видим, что мы получили только самый верхний уровень, а дальнейшая вложенность отдается тоже по запросу. Это ок, ибо создает сильно меньше нагрузки на сервис (так как не все до туда доходят вообще, а данных ррраз и передали меньше).

Мы в меню ограничимся верхнеуровневой категорий (без дальнешего проваливания в подкатегории) и наш изначальный «flow» чуть перестраивается: на главной мы ищем ссылки, но допромпчиваем ллмку так, чтобы она умела сказать «из этого списка ничего не подходит». А если ничего не подошло, то эмулируем клик по главному меню, ждем обновленный DOM, а затем повторяем изначальную операцию.

На верхнеуровневой категории что-то, да должно подойти.

И, о чудо, оно работает.

Да, пока у нас нет никакого агента, мы просто учимся предсказывать следующее лучшее действие нашей LLMкой, проходя весь его путь «в полях» — по шагам. Пояснение к гифке: мы пишем «стиралку», в итоге получаем переход в «бытовую технику», где мы еще раз повторяем запрос «стиралку» и мы попадаем в «Стиральные машины».

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

Состояние среды (стейт-машина)

И тут мы приходим к пониманию такой вещи, как «состояние». Состояние — это полный снимок среды, в которой мы находимся, и которую нужно оценивать относительно поставленной задачи.

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

И наша текущая простейшая стейт-машина состоит из трех пунктов с двумя подпунктами:

  1. Найти нужный товар

    1.1) Найти подходящую категорию

    1.2) Найти в ней товар

  2. Добавить его в корзину

  3. Послать уведомление об успехе

Если на пальцах, то наше состояние это на каждом шаге спрашивать «а где я нахожусь?» и «что было до этого?», а затем на основе этого планировщик будет принимать решение о следующем шаге.

Примерно так: «я нахожусь на главной, а ищу газонокосилки, значит иду воот в такую категорию.» И так — каждый раз, один раз — атомарно. В идеале, надо уметь откатываться по состояниям, не впадать в зацикливание и многое другое. Руками писать такое сложновато, но очень улучшает понимание всего подкапотного.

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

От категории к товару

Что мы уже умеем: мы научились пробиваться к любой нужной категории — ок, а что на счет поиска нужного товара? А здесь срабатывает самый главный плюс LLM-based софта — у нас уже все есть! Чисто технически, с товаром мы решаем немного другую задачу нежели с категорией, но по факту процесс тот же — мы просто подаем в ллм немного другое условие, что мы ищем товар, а не категорию. И оно работает!

Добавление в корзину — это тоже поиск кнопки и ее нажатие, а это у нас уже тоже есть. На самом деле, конечно же, есть не все, не в нашем базовом сценарии все может быть сложно, потому что у товаров бывают разные предложения — выбор цвета/размера и другие характеристики, иногда добавление в корзину работает через ajax (который не перезагружает DOM, а сейчас мы завязаны на него). Но если мы будем делать промышленное решение, то эта потребность вырезается в отдельную задачу и доводится до идеального состояния. Ну, а уведомлялка в тг — совсем базовая вещь, на ней останавливаться не будем.

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

Как же утомительно подготавливать большие гифки
Как же утомительно подготавливать большие гифки

Промпт на анализ в итоге свелся к вот такому:

Важная часть про память

Но чтобы агент стал автономным (а иначе зачем он нам нужен?) и персонализируемым, нужна память.

  • Краткосрочная — служебный срез текущего состояния, обновляется почти на каждом шаге

  • Долгосрочная — Долгосрочная — знания, которые можно переиспользовать и которые влияют на выбор действий; обращение к ней обычно на старте, в финале задачи или в ключевых точках

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

Например, вот наша история поисков и хождений. Как пример — мы можем подсуммаризовывать весь лог краткосрочной памяти во что-то полезное долгосрочное.

Получается, вся наша магия сводится к следующему: получить запрос, сходить в память, сходить в стейт, найти лучшее следующее действие, выполнить его и так по кругу, пока мы не выполним финальное условие.

Ну, вот и весь агент!

Выводы и фреймворки

Но.

Но пописав какие-то небольшие (и не самые сложные) куски агентного поведения (а мы даже не дошли до антизацикливаний и хотя бы минимального джентльменского набора для прода), половив нудных багов, я окончательно для себя зафиксировал вот какие выводы:

1) Если уже есть работающий проект с понятным flow, и в нем есть недетерминированные части, то подключать голую LLM вместо фреймворка туда может быть разумно

2) Если проект новый или к проекту точно-точно надо подключать агентов, а код проекта так себе, то без фреймворка будет очень много monkey job в агентной обвязке. Потому что фреймворки это готовые решение для прода, где есть множество оптимизацией, обработка ошибок/retries, многопоточность, логи, классная реализация разных агентных подходов, да и множество функционала, который написан явно лучше, чем если делать его самому.

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

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

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

Мой любимый для завершения слайд из корпоративной презы про агентов:

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

Спасибо!

P.S.: мне нравится писать всякое разное, но большие статьи отнимают очень много времени, поэтому я совсем недавно завел себе канальчик в тг Agentic World, в котором хочу делиться более короткими заметочками про ии, агентов, продукты и людей. И буду очень рад вашей подписке ?

Agentic World. Если вы уже использовали агентов в проде, то символизм в виде выхлопа спереди  должен напомнить что-то до боли знакомое
Agentic World. Если вы уже использовали агентов в проде, то символизм в виде выхлопа спереди должен напомнить что-то до боли знакомое

Мои другие статьи:

Бенчмарк качества распознавания речи (ASR) в телефонии: как мы сравниваемся с Whisper, GigaAM и T-One

Переизобретая аналитику будущего: как и почему LLM-агенты меняют анализ продуктов, но все не так просто

Как я автоматизировал мониторинг цен своей корзины на маркетплейсах и при чем тут LLM

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


  1. holgw
    04.09.2025 07:40

    Исходники выкладывать не планируете? С ними было бы гораздо интереснее.


  1. evgeniy_kudinov
    04.09.2025 07:40

    Да, задумка и реализация интересная. Нет ли риска, что что-то зловредное сделает, например, с личного кабинета банка переведет деньги куда-нибудь?


  1. delfer
    04.09.2025 07:40

    Nanobrowser