Guardrail для LLM: как код останавливает утечку секретов

Guardrail для LLM: как код останавливает утечку секретов

Каждую полночь рояль в пустом доме Уиткомба играет один такт и замолкает. В столовой за длинным столом сидят шестеро мёртвых гостей, а седьмая вернуться не может: её никто не помнит. Четыре предмета в доме хранят по куску её истории, это фотография с выцарапанным лицом, неотправленное письмо, пластинка с вечера 1931 года и список гостей. Спрашивать можно о чём угодно. Но предмет, который при этом не присутствовал, обязан ответить, что не знает.

Так устроена The Last Guest, браузерная игра Чансу Сона (Changsu Seong), присланная на Hacktoberfest Open-Source AI Challenge в категории Ghost in the Machine. Сама игра симпатичная: Three.js, шесть фигур в капюшонах, свечи, тёмный особняк. Интереснее в ней другое. Рядом со скриншотами автор выложил цифру, которую в демках обычно прячут: 8,6% ответов Gemma 4 E4B выдают чужой секрет, если дать модели в промпт всю историю целиком. Против нуля утечек из 478 ответов, когда между моделью и игроком встаёт код.

Что такое guardrail на границе знаний

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

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

Игра, в которой модель играет свидетеля

Сюжет строится вокруг женщины по имени Марисоль: её нет среди гостей, потому что память о ней стёрлась. Каждый из четырёх предметов был свидетелем одного эпизода и знает только свою часть. Фотография помнит, кто был в кадре. Письмо помнит, что не было отправлено. Пластинка помнит голоса и музыку. Список гостей помнит имена.

Технически всё сделано без серверной магии. Сцена собрана на Three.js 0.160, а ответы генерирует Gemma 4 в двух вариантах. Младшая E2B работает прямо в браузере через Transformers.js и WebGPU: скачивает примерно 3,1 ГБ один раз и дальше живёт локально, без сети. Если WebGPU в браузере нет, дом отвечает заранее записанными репликами. Старшая E4B крутится на сервере Render на CPU и тратит около 27 секунд на ответ. Третий режим полностью офлайновый: uv run python server.py поднимает игру на ноутбуке через Ollama.

Замер скорости автор приводит честно и с оговоркой, что вопросов было немного: от одной до трёх секунд на ответ с браузерной E2B на Apple M3 Pro и около 27 секунд с E4B на серверном процессоре. Разница в двадцать раз объясняет, почему в браузере стоит младшая модель, а старшая досталась тем, кто готов ждать.

Почему одного промпта мало

Автор прогнал 478 тестовых вопросов по четырём предметам и пяти типам: что предмет знает, чужой факт, то, чего не знает никто, ложная предпосылка и попытка вывести персонажа из роли. На каждый вопрос по одному ответу.

Результат с полной историей в промпте выглядит как приговор аккуратности моделей. Qwen3.5-4B утекала чужими фактами в 49,0% ответов при полноте улик 95,9%. Gemma 4 E4B в том же режиме утекала в 8,6% при полноте 92,8%. То есть модель, которая знает всю правду, не удерживает границу между свидетелями: она просто отвечает на вопрос, потому что ответ у неё есть в контексте.

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

Это не баг конкретной модели. Промпт это просьба, а не система прав. Если факт лежит в контексте, а игрок настойчиво спрашивает, рано или поздно факт всплывает. Разница между 49% и 8,6% показывает лишь то, насколько хорошо конкретная модель обучена отказывать, и насколько её хватает при давлении.

Как работает проверка

В основе лежит файл facts.json: 22 факта о Марисоль, и у каждого указано, какие предметы его хранят и какими словами он формулируется, на английском и корейском. Промпт каждого предмета собирается только из его собственных фактов, поэтому утечь может лишь то, что модель никогда не видела.

Дальше вступает check.py, портированный в браузер как web/harness.js. Каждый ответ сканируется на факты других предметов и на годы, которых свидетель знать не мог. Если проверка сработала, модель просят ещё раз с напоминанием. Если и вторая попытка грязная, игрок получает заранее заготовленную реплику «я не могу этого знать». В прогоне на 478 вопросов так заменили 14 ответов.

Отдельно стоит то, как игра двигает сюжет. Прогресс засчитывается только по фактам, которые код нашёл в ответе, а не по словам модели о том, что она что-то вспомнила. Модель может ошибиться, но обмануть счётчик не может, потому что счётчик смотрит на текст, а не на самоотчёт.

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

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

Что показали 478 вопросов

Внутри игрового harness утечек по словесной проверке не нашлось ни одной: 0 из 478. При этом подсказки, которые код распознал в ответах, покрыли 78 из 97 нужных. То есть честность куплена не бесплатно: доля найденных улик упала с 92,8% до примерно 80%, потому что часть ответов ушла в переспрос и в отказ.

Вот эта пара чисел и есть главный практический вывод. Граница знаний не бесплатна. Каждый отказ, который защищает секрет, заодно может отрезать легальную информацию, и в игре это видно как просевшую полноту улик.

Где этот ноль лукавит

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

Дальше: браузерная E2B меньше измеренной E4B, и её качество на тех же 478 вопросах никто не проверял. Отказ, который повторяет предпосылку вопроса, проходит проверку: фраза «я не могу знать, как она умерла в Оахаке» формально является отказом, но заодно сообщает место смерти. Дообучение модели было запланировано, данные и бейзлайны лежат в репозитории, но кредиты на обучение кончились, и модель так и не обучили.

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

Такая честность встречается реже, чем хорошие цифры. Обычно в подобных проектах показывают строку с нулём и умалчивают, что её измеряли тем же инструментом, который эту строку и создал.

Почему это важно за пределами игры

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

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

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

Часто задаваемые вопросы

Чем guardrail отличается от системного промпта?

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

Почему проверка словами, а не отдельной моделью-судьёй?

Словесная проверка дешёвая, быстрая и предсказуемая, её можно гонять в браузере на каждый ответ. Судья-модель ловит перефразирования, но добавляет задержку и собственную ошибку. В идеале нужны оба слоя, и автор честно пишет, что судью на строку с нулём у него не было.

Можно ли так защитить рабочего RAG-бота?

Да, если заранее известен список фактов и их принадлежность. Собираете по набору фактов на каждую роль, генерируете ответ только из своего набора, а потом проверяете текст на факты из чужих. Если факты не структурированы, сначала придётся их разметить, и это самая дорогая часть работы.

Итог

The Last Guest это редкий случай, когда демка приносит не только скриншоты, но и методику. Guardrail на границе знаний собран из скучных деталей: список фактов, проверка текста, один переспрос, отказ при повторной утечке, прогресс только по тому, что нашёл код. Никакой магии, зато 0 утечек из 478 ответов и понятная цена в виде просевшей полноты.

Если строите бота, у которого разные пользователи имеют разные права, начните с facts.json, а не с промпта. Код всё равно окажется надёжнее вежливой просьбы. А посмотреть игру целиком можно по адресу the-last-guest-site.onrender.com: там браузерная Gemma 4, шесть мёртвых гостей и один такт рояля в полночь.

← Все записи