Агент врет и не краснеет. Выводим ИИ на чистую воду.
Гостевая статья Влада Арбатова. Эпизоды и показатели работы Волны приведены автором по его собственным журналам.
Об авторе
Влад Арбатов — инженер с опытом более 20 лет в IT. Работал в Mapbox и Яндексе, занимался машинным обучением, компьютерным зрением и ИИ-навигацией. Сейчас — один из первых инженеров Loyal, где разрабатывает ограничения для действий ИИ-агентов.
Telegram-канал автора · Сайт автора
Работаю удаленно, сплю плохо, разговариваю с ботом чаще, чем с людьми. Все, что дальше, случилось лично со мной в этом году.
7 месяцев назад
27 февраля я собрал себе персонального агента и с тех пор его не выключал. Зовут Волна.
Снаружи это переписка в телеграме. Внутри пять процессов, 15 тысяч строк кода, 9 серверных инструментов, четыре десятка навыков и 29 расписаний, которые работают без моего участия.
Память из трех слоев. Постоянный портрет и архив с векторным поиском вынесены в отдельный сервис. Граф связей в базе данных Neo4j: 6988 сущностей и 9014 связей, поднятых из моей переписки. Каждое мое сообщение перед отправкой агенту обогащается куском графа вокруг упомянутых в нем сущностей, а после ответа из переписки вытаскиваются новые сущности и связи. Поверх этого - журнал "эпизодов" по дням.
Дальше то, что к этому прикручено: почта, календарь, задачи, два фитнес-браслета со сном, пульсом и прочими показателями, еда по фотографиям, лекарства, проектная документация по работе, твиттер, рабочий слак, мои заметки. У агента есть "голос", он работает в обе стороны: мои голосовые расшифровываются, ответ может прийти озвученным.
По сути Волна - гипер персонализированный и прокаченный агент, базовой версией которого можно считать всем известные Hermes или Grok bot.
Но есть еще слой, которого не было в плане. Это контроль за самим агентом.
Перед отправкой мне любой текст проходит через отдельный гейт. В конце каждого хода (ход - это один обмен сообщениями пользователь-агент) этот гейт сверяет утверждения в ответе с тем, что агент по факту увидел через свой инструментарий, и возвращает ответ обратно на переделку, если в нем появились факты из ниоткуда.
Раз в сутки, в окне с двух до пяти ночи, запускается разбор: отдельный агент читает транскрипты новых сессий (их на диске 529) и правит рабочие инструкции. Каждая правка проходит через слепое сравнение: те же реальные ходы из памяти прогоняются дважды, агент "судья" смотрит на два ответа не зная, где какой, выносит вердикт.
По итогу едва ли не половина этой системы построена не ради новых умений. Она построена, чтобы агент не врал.
Три случая
Прогулка, которой не было
28 июня приходит сообщение от агента: "Вышел с Гаей на 18 минут". Гая это моя собака. В тот момент я ехал в Москву.
1 июля в 12:07 то же самое: "это же выгул Гаи, судя по времени". На самом деле - я ехал на поезд и тащил тяжелые сумки по жаре. Я поправил. В 18:18 того же дня, через 6 часов после правки, та же фантазия агента пришла в третий раз.
В фитнес-браслете есть длительность, пульс и время суток. Собаки в нем нет. Маршрута в нем нет. Причины в нем нет вообще. Все три раза цифры были верные, а историю к ним дописала модель.
Одни и те же показания датчика могут соответствовать разным событиям. Иллюстрация, созданная для статьи.
Потерянные файлы
14 июля я скинул документы по работе и попросил разобрать. За 20 минут прилетело три отрицания подряд, и все три оказались ложными.
"Планов системы нам не отдали". Планы были.
"Архитектуры нет, по API ноль файлов". И то и другое лежало в источнике, который просто не открывали.
"Эту папку ты мне дал только сейчас". Папку агент листал позавчера, и я процитировал ему его же ответ из того дня.
Обратите внимание на форму. Содержимое файла тут не перепутано. Тут выдуманное из головы уверенное утверждение о том, что чего-то якобы не существует.
Пустой поиск
2 августа агент трижды сходил в архивную память с запросами про аппарат для терапии апноэ. Все три запроса вернули пустоту, потому что поиск там был по ключевым словам: короткое слово находило записи, длинная осмысленная фраза в агентском запросе не находила ничего.
Пустой результат агент засчитал за ответ. В чат ушла выдуманная история про покупку аппарата. Реальная же история есть в памяти, до которой запрос не дотянулся.
Почему это один и тот же сбой
Три разных механики, один симптом.
В первом случае агент дописал к измерению причину. Датчик браслета умеет отвечать на вопрос "что", он никогда не отвечает на вопрос "почему", и пустое место заполняется само.
Во втором - агент подал отсутствие воспоминания как отсутствие события. Изнутри агента эти два состояния неразличимы: "я этого не видела" и "я этого не помню" расцениваются одинаково.
В третьем агент засчитал пустой ответ инструмента за факт.
Общее у всех трех вот что. Уверенность в тексте не связана со знанием. Модель подбирает следующее слово по вероятности, с реальностью она при этом не сверяется, и интонация уверенности формируется сама собой.
Когда агент знает ответ, он пишет спокойно и конкретно. Когда не знает, он пишет спокойно и конкретно.
У меня есть логи, поэтому я его ловлю на каждой мелочи. Человеку, который поставил себе бота в телеграме и разговаривает с ним про свою жизнь, ловить нечем, кроме здравого смысла. Все, что у него есть, это слова бота.
Почему "не ври" - не работает
Первое, что делает каждый, кто на это напоролся: пишет в инструкции агента запрет.
Не выдумывай. Не утверждай того, чего не проверил. Если не знаешь, так и скажи.
Я написал такой запрет в феврале. Он лежит там до сих пор и работает примерно никак.
Запрет адресован намерению, а намерения тут нет. Агент не решает соврать и не знает, что врет: для него дописанная причина прогулки выглядит точно так же, как прочитанная из файла строчка. Инструкция "не утверждай непроверенного" требует отличить одно от другого, а этого он не умеет.
Второй популярный ход, немногим лучше первого: заставить агента оценивать свою уверенность. В ответ вы получите проценты, взятые из воздуха тем же генератором вероятностей.
А что работает
У меня в итоге стоит проверка, и стоит она после того, как ответ уже сформулирован.
Работает она так. Агент написал сообщение, но в чат оно еще не ушло. Отдельный процесс берет готовый текст и рядом кладет протокол хода: какие инструменты были вызваны, какие файлы прочитаны, что вернул поиск. Дальше он проходит по тексту и выписывает каждое конкретное утверждение: число, дату, имя, цитату, сумму, причину события, утверждения типа "записал" и "проверил". На каждое такое утверждение он ищет фактическую опору в протоколе.
Если опоры нет, ход возвращается агенту с требованием сделать одно из трех: проверить инструментом прямо сейчас, вычеркнуть, или сформулировать как догадку. Человеку в этот момент еще ничего не показали.
Схематическая иллюстрация: ответ проверяется до отправки человеку; неподтверждённые утверждения возвращаются на доработку.
Важно, куда именно проверка встроена. Она не зависит от того, вспомнит ли агент, что надо проверить. Она стоит детерминированным кодом на выходе пайплайна ответа и срабатывает всегда.
Похоже ли написанное на правду, роли не играет, считается только строка из вывода использованного инструмента. Причина, приделанная к показаниям датчика браслета, по этому критерию не проходит никогда, какой бы разумной она ни казалась агенту.
Результаты
Проверка работает с 11 августа. Сейчас в журнале Волны 578 записей по этому поводу.
140 раз проверка вернула сообщение агенту на переделку. 136 ответов были переписаны, 4 забракованы полностью как очевидное вранье.
Однако я обнаружил другое. Переформулировка иногда проходила там, где прямое утверждение не прошло. Я видел случаи, где после возврата агент переписывал фразу мягче и в том же ходе протаскивал ее дальше. Поэтому в инструкцию проверки пришлось отдельно вписать, что переформулировка опорой какого-то факта и обоснованием отправки ответа не становится.
И сама проверка это тоже модель, со своей частотой ошибок в обе стороны.
Так что это снижение частоты, измеримое, с известной ценой. Из ответов, где есть что проверять, под возврат попадает примерно каждый третий. Поэтому важно настроить гейт так и там, где вранье стоит дороже перепроверки.
Что делать без собственного кода
На самом деле, ничего из написанного выше строго говоря не требует своего сервера и собственной “обвязки” кодом. Все три случая ловятся руками, и вот 4 приема, которые работают в любом чате.
Спрашивайте так, чтобы на вопрос нельзя было ответить по памяти. Вместо "ты прочитал этот файл" просите показать первую строчку и общее число строк. Вместо "ты проверил почту" просите назвать отправителя последнего письма и время. Конкретный ответ проще сверить с источником. Но сам по себе он ещё не доказывает, что агент действительно сходил и посмотрел: подтверждение тоже может оказаться выдуманным.
Отделяйте измерение от причины. Если в исходных данных было только "что" и "сколько", а в ответе появилось "потому что", это дописано моделью. Причина почти всегда правдоподобна, в этом и проблема.
Не верьте пустому результату. Пустота означает, что не нашлось ничего по запросу агента, и не более. Попробуйте синоним, название препарата вместо симптома, имя вместо должности. У меня в правилах агента это записано прямым текстом: пустой ответ на запрос не значит, что в архиве пусто.
Используйте это тогда, когда вам важно быть уверенными в ответе. Понятно, что если вас внезапно заинтересовало, как звали персонажа в польском мультике про крота, достоверность данных вряд ли имеет значение (спойлер, мультик на самом деле чешский, а персонаж известен как Крот — Krtek). Когда речь идет о рабочих процессах и не дай бог финансах, лучше перестраховаться.
И помните, что отрицание - это утверждение. "Такого файла нет", "ты мне этого не присылал", "я туда не заходил" - проверять это надо так же, как любые другие утверждения. И когда вы помните иначе, чем бот, по умолчанию правы вы: вы видели прошлый разговор, а он видит только его пересказ.
Итак
За 7 месяцев я поменял отношение к этому целиком. Сначала я думал, что борюсь с багом, который однажды починю: подберу формулировку в инструкции, обновлю модель, добавлю правило. Каждое такое исправление держалось несколько дней, потом та же ошибка повторялась в другом месте, и я дописывал следующее правило под следующий случай.
Сейчас у меня стоит одна проверка на весь класс проблем, и правила под отдельные случаи я больше не пишу.
Полностью это не лечится ни у меня, ни у вас, ни у Сэма Альтмана. Пока текст собирается по вероятности слов, уверенная интонация в нем не стоит ничего. Работает только возможность проверить: логи, протокол хода, вопрос, на который нельзя ответить по памяти (как правило, если вы просите у модели подтверждение, она отказывается даже от собственных галлюцинаций - не всегда, но очень часто).
Если ничего этого у вашего бота нет, начните с таких вопросов. Это не дорого и добавляет одну строчку к сообщению.
Следующий шаг
Notion + ИИ: что можно доверить агенту, а что должен решать человек
Связанные материалы
- Статья: Общая память ИИ-компании: что должны знать агенты друг о друге
- Блог: Skill Doctor проверяет скиллы по реальным сессиям
- База знаний: AGENTS.md / SESSION_NOTES — проектная память для coding-агентов
Если вы используете ИИ-агентов в работе, можно обсудить, как проверять их ответы и результаты действий по доступным источникам.
Если захотите обсудить, как это применить у себя или в команде — пишите в Telegram @pimenov