Главная ошибка тех, кто строит агентов на чужих данных
Привет. Меня зовут Тимур, я работаю над приложением Флайди и последние месяцы много копаюсь в том, как устроен виральный контент про отказ от привычек. Здесь — история про систему, которую мы собрали, чтобы перестать угадывать, и про то, как она четыре дня подряд выдавал чушь
Я построил локальную мультиагентную систему, которая сама находит аккаунты конкурентов в нише, собирает их короткие видео, находит статистические выбросы и разбирает, почему они выстрелили. В первый же боевой прогон она отрапортовала 37 «залетевших» роликов. Из них настоящими оказались 13. Остальные 24 были фантомом — следствием одного числа, которое соцсеть отдаёт вместо реальных лайков. Ошибка была не в модели и не в промптах: агенты добросовестно разбирали ролики, которые вообще не были хитами. Ниже — как устроена система, почему статистика подвела там, где её никто не проверял, и правило живого окна, к которому мы пришли, когда платформа начала гасить нам доступ каждые сорок пять минут
Семь агентов, но ни одного «роя»
Мультиагентность у нас устроена скучно — и в этом весь смысл. Никакого чата агентов между собой: есть детерминированный оркестратор, который гоняет фазы по порядку, а языковая модель работает внутри отдельных шагов, где нужна именно интерпретация
Разделение получилось такое. Scout ищет новые аккаунты по хэштегам и оценивает их релевантность по десятибалльной шкале. Harvester собирает публикации — это чистый код, без модели, потому что здесь нужна аккуратность, а не сообразительность. Outliers считает выбросы: голая арифметика, отношение метрики ролика к медиане последних двадцати роликов этого же аккаунта. Analyst разбирает каждый хит — формат, крючок, аудитория, переносимость. Learner перерабатывает мои оценки идей в уроки. Strategist синтезирует идеи для съёмки. Reporter собирает отчёт
Ключевое решение — граница между математикой и моделью. Всё, что можно посчитать, считается кодом, и модель об этом даже не спрашивают. Модель отвечает только там, где вопрос принципиально не арифметический: почему это зашло, на кого это работает, что из этого переносится на другую тему
Практический вывод: если фаза детерминирована, не отдавайте её агенту. Каждый лишний вызов модели — это не только деньги, но и лишняя точка, где система может ошибиться
Три лайка и семьдесят три комментария
Метрика соврала в самом неожиданном месте — в знаменателе. Выброс мы считаем как отношение лайков ролика к медиане лайков аккаунта. Медиана устойчива к выбросам, это её главное свойство, и именно поэтому её выбрали. Устойчивость не спасла
Случай из практики. Разбираю отчёт первого боевого прогона и вижу у одного аккаунта двадцать восемь суперхитов из двадцати восьми роликов. Так не бывает) суперхит — это десятикратное превышение медианы, все ролики сразу такими быть не могут по определению. Лезу в базу, смотрю распределение лайков: 350 577, 113 382, 86 850 — и россыпь ровно троек. Медиана аккаунта — 3. Открываю один такой ролик: три лайка и семьдесят три комментария. У нормальных роликов этого аккаунта соотношение около ста двадцати лайков на комментарий. Три лайка при семидесяти трёх комментариях физически невозможны — это заглушка, которую платформа отдаёт, когда автор скрыл лайки
Двадцать шесть роликов из ста восьмидесяти оказались с такой заглушкой. Они утащили медиану на дно, и любой обычный ролик автоматически стал «суперхитом» с превышением в сотни раз. Хуже того, языковая модель добросовестно разобрала эти фантомы и записала выводы в базу, а синтез идей потом две недели питался бы этими разборами
Что с этим делать: заглушку нельзя считать нулём или пропуском по умолчанию. Мы завели явную настройку — какое значение метрики считать «данных нет», — и выкинули такие ролики и из медианы, и из скоринга. После пересчёта медиана того же аккаунта поднялась с 3 до 7 743, а суперхитов осталось три вместо двадцати восьми
Платформа даёт сорок пять минут, и это надо принять
Дальше выяснилось, что сбор живёт ровно столько, сколько ему разрешает платформа, и договориться нельзя. Наша сессия стабильно умирала примерно через сорок пять минут работы — на четвёртом-пятом аккаунте, независимо от того, какие паузы мы ставили между запросами. Мы удвоили задержки: запрос раз в 16–50 секунд, между аккаунтами две-шесть минут. Умерло на том же месте
Сначала мы чинили симптом — растягивали паузы. Потом посмотрели, с какого адреса система вообще выходит в сеть, и обнаружили, что трафик идёт через датацентровый IP немецкого хостинга. С таких адресов не бывает обычных людей, только автоматизация, и платформа гасит там не отдельный запрос, а сессию целиком. Паузами это не лечится в принципе
Вывод оказался архитектурным, а не тактическим. Раз окно короткое и его длину задаём не мы, надо перестать проектировать пайплайн так, будто времени сколько угодно. Изначально у нас было красиво по фазам: сначала собрать всё, потом посчитать всё, потом догрузить комментарии ко всему. На бумаге чисто. На практике комментарии стояли в очереди последними и не собрались вообще — три ролика из ста восьмидесяти. А комментарии — это главный источник сигнала о том, кто именно смотрит ролик и почему
Правило живого окна — каждую единицу работы доводим до конца внутри того окна доступа, которое нам дали, а не раскладываем по красивым фазам.
Когда внешняя система сама решает, сколько вы проживёте, порядок операций перестаёт быть вопросом вкуса. Всё, что стоит в очереди последним, не выполнится никогда — не потому что упало, а потому что до него не дошло. Поэтому мы перестали делить прогон на «собрать всё» и «обработать всё».
Как применять:
- Определить единицу, которая имеет ценность сама по себе. У нас это один аккаунт.
- Довести её до конца сразу: собрать, посчитать, забрать дополнительные данные — пока доступ жив.
- Нарезать работу на партии по размеру окна и вести очередь: первым идёт тот, кого дольше всех не трогали.
Что с этим делать: посмотрите, что в вашем пайплайне стоит последним в очереди. Если внешний доступ конечен, именно эта часть у вас не работает — и вы, скорее всего, об этом ещё не знаете.
Почему идеи получаются, а не растекаются
Самая частая проблема с генерацией контента моделью — универсальная вода: «расскажите свою историю», «будьте искренними». Такие идеи невозможно снять, потому что в них нет ни кадра, ни первой фразы.
Мы упёрлись в это на второй итерации и решили жёстко, на уровне кода, а не уговоров в промпте. Каждая идея обязана ссылаться на конкретный разобранный ролик, и если модель возвращает идею без валидной ссылки, код её просто выбрасывает — до того, как я её увижу. Плюс инструкции агентов вынесены в отдельные текстовые файлы, а не зашиты в код: докрутка поведения системы — это правка текста, а не программирование. В команде Флайди это оказалось важнее, чем кажется: тон и стоп-слова правит тот, кто отвечает за контент, без разработчика.
Второй уровень — обучение на моих оценках. Я ставлю идеям «в точку», «вода» или «не наш бренд», отдельный агент перерабатывает эти вердикты в компактную базу уроков, и стратег читает её перед каждым синтезом. Честно скажу, что здесь система пока голодает: из сорока шести идей я оценил две, и в последнем прогоне обучающий агент прямо написал, что перерабатывать нечего. Петля обратной связи работает ровно настолько, насколько её кормят.
На практике: если генеративная часть выдаёт воду, не переписывайте промпт бесконечно — добавьте проверку, которая механически отсекает результат без опоры на факт.
Что в итоге
Главный урок этой истории не про агентов, а про доверие к цифрам. Мультиагентная система устроена так, что каждый следующий шаг верит предыдущему: аналитик верит детектору выбросов, стратег верит аналитику, отчёт верит всем. Одно испорченное число в самом низу — и наверху получается уверенный, красиво аргументированный, полностью ложный вывод. Причём система не выглядит сломанной: она бодро рапортует о рекордных результатах.
Поэтому первое, что стоит закладывать в такую систему, — не память и не оркестрацию, а проверку правдоподобия входных данных. Три лайка при семидесяти трёх комментариях должны вызывать ошибку, а не медиану.