Перейти к содержимому
24 янв. 2026 г.·8 мин чтения

Аудит-логи для LLM: что хранить банку и госсектору

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

Аудит-логи для LLM: что хранить банку и госсектору

Почему без журналов начинается хаос

Проблема редко начинается с самой модели. Хаос начинается в тот момент, когда ответ уже сломал процесс, а команда не может быстро восстановить цепочку событий. Для банка или госоргана это особенно болезненно: один странный ответ затрагивает клиента, оператора, внутреннюю проверку и отчетность для службы безопасности.

Без нормальных аудит-логов для LLM люди быстро переходят на память и догадки. Один инженер говорит, что сбой заметили в 10:05, аналитик уверен, что жалоба пришла позже, а владелец продукта смотрит в другой дашборд и называет третье время. Уже на этом этапе теряется час, хотя сначала нужен один простой факт: кто первым увидел проблему и когда это произошло.

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

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

Один лог события должен сразу отвечать на несколько вопросов:

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

Представьте обычный случай. В банковском чате бот внезапно начал отвечать слишком длинно и раскрыл внутренний шаблон. Если нет истории изменений, команда спорит, виноват новый системный промпт или вырос token limit. Если нет маршрута, все смотрят на модель A, хотя ответ пришел от модели B через другого провайдера. Если нет времени первого сигнала, трудно связать инцидент с конкретным релизом.

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

Из чего собрать одно событие

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

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

Дальше нужны идентификаторы. У одного события обычно есть request_id, а часто еще session_id и ID пользователя или сервиса. Эти поля связывают вызов LLM с экраном оператора, пакетной задачей или внутренним микросервисом. Если user_id нельзя хранить в открытом виде, оставьте стабильный псевдоним или хэш. Так вы увидите цепочку действий без раскрытия личности.

Что должно быть в записи

Минимальный состав записи обычно такой:

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

Отдельный блок нужен для маршрута обработки. Если команда использует шлюз вроде AI Router, одного названия модели мало. Один и тот же OpenAI-совместимый вызов может уйти к разным провайдерам по разным правилам. Поэтому в журнале стоит хранить не только model_name, но и provider, route_id и версию политики маршрутизации. Иначе вы увидите "gpt-4.1", но не поймете, почему вчера задержка была 900 мс, а сегодня 4 секунды.

Финал события тоже важен. Успех без метрик мало что дает. Лог должен показывать HTTP-статус или внутренний статус, код ошибки, длительность по шагам, токены и цену. Если запрос упал после ретрая, это тоже стоит отмечать. Тогда при разборе видно, проблема была в модели, провайдере, лимите доступа или в самом приложении.

Хорошая запись читается за 20 секунд и дает ответ на три вопроса: кто вызвал модель, по какому маршруту пошел запрос и почему результат оказался именно таким.

Что писать, а что сразу маскировать

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

Рабочее правило простое: все, что нужно для поиска, сверки и расследования, храните; все, что раскрывает человека напрямую, маскируйте в момент записи. Не через день и не отдельной задачей, а сразу в том же потоке.

Что оставлять в записи

Хорошая запись отделяет текст запроса от служебных метаданных. Метаданные нужны почти всегда: время, ID запроса, сервис, модель, версия промпта, маршрут, статус ответа, число токенов, код ошибки, кто вызвал API и какая политика сработала.

Для чувствительных полей лучше хранить не исходное значение, а безопасную форму:

  • маску для визуальной сверки, например 4403****1298;
  • хэш для точного поиска совпадений;
  • внутренний ID клиента вместо ФИО и контактов;
  • признак того, что в запросе нашли PII;
  • класс данных записи: публичные, внутренние, персональные, банковская тайна.

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

Что скрывать сразу

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

Если пользователь написал: "Мой ИИН 123456789012, телефон 87771234567, перевыпустите карту 4403...", в журнале лучше оставить смысл и контекст, но скрыть прямые идентификаторы. Например: "Запрос на перевыпуск карты; найден ИИН, телефон и номер карты". Этого уже достаточно, чтобы понять тип операции и ход инцидента.

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

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

Как выбрать сроки хранения

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

Банку и госсектору редко нужен вечный архив сырых payload. Если в prompt попали персональные данные, служебные сведения или кусок клиентского договора, длинное хранение само становится проблемой. Срок лучше привязать к трем вещам: риску инцидента, сроку рассмотрения жалоб и длительности внутренней проверки.

На практике удобно разводить сроки так:

  • полные запросы и ответы хранить коротко, например 30-90 дней;
  • маскированные копии держать дольше, если команда разбирает ошибки без доступа к чувствительным данным;
  • метаданные события хранить заметно дольше, часто 1-3 года, если этого требует внутренний контроль;
  • обезличенные сводки по нагрузке и лимитам оставлять еще дольше, если в них нет текста запросов.

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

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

Правило удаления стоит хранить рядом со схемой события. У каждого поля должен быть свой статус: не писать вовсе, писать в маске, хранить 30 дней, хранить год. Когда это собрано в одном месте, команда не спорит на каждой проверке, почему текст ответа удалили, а маршрут запроса оставили.

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

Кому давать доступ

Не меняйте SDK и код
Смените base_url и сохраните текущие SDK код и промпты

Доступ к журналам нельзя раздавать по принципу "кому может пригодиться". В банке и госсекторе это обычно заканчивается лишними просмотрами, спором о том, кто что видел, и слабым разбором инцидента.

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

ИБ нужен доступ шире, но тоже не ко всему без повода. Команда ИБ проверяет аномалии, массовые выгрузки, обход rate limit, попытки отправить PII в промпт. Для этого полезны полные цепочки событий, IP, сервисный аккаунт, метки контента и история изменений прав.

Комплаенс обычно смотрит не сами диалоги, а соблюдение правил: срок хранения, факт маскирования, кто запросил раскрытие и кто одобрил просмотр. Разработке редко нужен сырой текст. Ей чаще хватает обезличенных фрагментов, трассировки и служебных полей.

На практике разделение выглядит так:

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

Полный текст запроса и ответа лучше открывать только по заявке. Нужен понятный маршрут: кто создал запрос, кто одобрил, на какой срок выдали доступ и зачем он нужен. Если инцидент связан с клиентскими данными, второй уровень одобрения обычно оправдан.

Каждый просмотр и каждый экспорт нужно писать в отдельный журнал. Не в тот же поток, где лежат события LLM, а в журнал действий с логами. Тогда команда быстро видит, кто открыл запись, кто выгрузил файл и кто пытался искать по чувствительным полям.

Временный доступ не стоит оставлять "до конца недели". Инцидент разобрали - доступ закрыли сразу. Автоистечение через несколько часов надежнее ручных обещаний.

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

Как настроить журнал по шагам

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

Дальше полезно пройти путь одного запроса от начала до конца и отметить, где он меняется. Например, сервис добавляет системный промпт, шлюз выбирает модель, а потом внешний инструмент возвращает данные. В банке это обычная ситуация: клиент пишет в чат, backend дополняет контекстом из CRM, а LLM уходит через единый API-шлюз к другой модели, если первая недоступна.

Минимальный порядок

  1. Зафиксируйте все точки входа и все точки изменения запроса. Сюда входят ретраи, fallback на другую модель и вызовы tools.
  2. Согласуйте обязательный состав события. Обычно хватает request_id, времени, пользователя или сервиса, канала, модели, провайдера, версии промпта, статуса ответа, задержки, токенов и признака вызова инструмента.
  3. Включите маскирование до записи в журнал. Не сохраняйте сырые данные с надеждой почистить их позже.
  4. Разделите сроки хранения. Полный след события можно держать меньше, а обезличенные метаданные дольше, если они нужны для расследований и отчетности.
  5. Назначьте роли доступа. Разработчик видит технические поля, служба ИБ видит журнал инцидента, а сырые фрагменты доступны только узкому кругу по заявке.

На шаге с полями не пытайтесь записывать все подряд. Лишний шум мешает не меньше, чем пустые логи. Если команда использует OpenAI-совместимый шлюз, полезно логировать еще и маршрут: какая модель выбрана, был ли перевод на другого провайдера, где сработали rate limits и включалось ли маскирование.

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

Пример инцидента без лишней теории

Маршрутизируйте модели через один вход
Подключайте более 500 моделей и десятки провайдеров через один эндпоинт

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

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

В карточке события команда сразу видит:

  • какую модель вызвали;
  • через какого провайдера или маршрут прошел запрос;
  • какую версию системного промпта использовал сервис;
  • какой контекст пришел в модель после маскирования;
  • какой ответ ушел клиенту и сколько занял вызов.

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

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

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

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

Где команды чаще ошибаются

Когда инцидент уже случился, поздно выяснять, что в журнале есть только текст запроса, а служебного следа нет. Для банка или госоргана это частая проблема: видно, что пользователь отправил, но неясно, какой сервис принял запрос, какой маршрут сработал, какая модель ответила и кто потом прочитал результат. Такие аудит-логи почти бесполезны.

Первая ошибка проста: команда хранит только prompt и ответ. Без request_id, времени, среды, версии шаблона, имени модели, параметров вызова и статуса маскирования персональных данных разбор быстро упирается в догадки. Если клиент пожаловался на странный ответ в 14:07, журнал должен позволить собрать цепочку за минуту, а не по кускам из пяти систем.

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

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

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

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

Обычно хватает пяти правил:

  • хранить не только текст, но и технический контекст вызова;
  • разделять тест, пилот и прод по разным журналам;
  • давать доступ по роли и по задаче, а не всем подряд;
  • записывать каждую смену модели, шаблона и настроек;
  • замораживать удаление записей, если идет проверка.

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

Короткая проверка перед запуском

Дайте ИБ факты
Аудит-логи и лимиты на уровне ключа помогают быстрее разбирать инциденты

Перед запуском проверьте не схему на бумаге, а один живой запрос: от входа в API до записи в журнал и поиска этой записи по ID. Если команда не может пройти этот путь руками за несколько минут, при инциденте журнал только добавит шума.

Для банков и госсектора этого минимума обычно достаточно:

  • у каждого запроса есть свой ID, точное время и понятная привязка к сервису, пользователю или системной роли;
  • запись показывает, какая модель ответила, через какого провайдера прошел вызов и какая версия промпта сработала;
  • система маскирует PII до записи в журнал, а не после;
  • каждая роль видит только свои поля;
  • дежурная команда может быстро поднять нужную запись по request_id, времени, клиентскому ключу или номеру операции.

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

Хороший тест очень простой. Возьмите один запрос с ошибкой, один успешный запрос и один запрос с персональными данными. Потом попросите три роли - разработчика, ИБ и аудит - найти их в журнале и ответить на два вопроса: что произошло и кто имеет право видеть детали. Если ответы собираются быстро и без ручной расшифровки, систему можно выпускать спокойнее.

Что сделать дальше

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

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

Стоит заранее закрепить несколько правил:

  • кто читает полные логи;
  • кто видит только метаданные;
  • сколько хранить сырые события и сколько агрегаты;
  • кто одобряет выгрузку для расследования;
  • кто отвечает за удаление по сроку хранения.

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

Если аудит-логи идут через единый шлюз, проверьте его руками. Для AI Router имеет смысл посмотреть, пишет ли шлюз события в одном формате, включает ли маскирование PII до записи, поддерживает ли хранение данных внутри страны и задает ли rate limits на уровне ключа доступа. Это не детали. По ним потом видно, кто сделал запрос, что именно ушло в модель и почему инцидент не остановили раньше.

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

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

Что должно быть в одном событии аудит-лога для LLM?

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

Нужно ли хранить полный prompt и полный ответ?

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

Как правильно маскировать PII в логах?

Маскируйте чувствительные данные в момент записи, а не позже. Номер карты, ИИН, телефон и ФИО лучше сразу заменить на маску, хэш или внутренний ID, чтобы команда могла искать событие без доступа к чужим данным.

Сколько хранить логи LLM?

Обычно полный текст запросов и ответов держат коротко, например 30–90 дней. Метаданные события можно хранить дольше, часто 1–3 года, если это требует внутренний контроль и если там нет лишних персональных данных.

Кому нужен доступ к полным аудит-логам?

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

Зачем отдельно писать модель, провайдера и маршрут?

Потому что одно имя модели не объясняет, что реально произошло. Если запрос идет через шлюз вроде AI Router, вызов может уйти к другому провайдеру или по другому правилу, и без маршрута вы не поймете, где выросла задержка или где сломался ответ.

Что делать с удалением логов, если уже идет инцидент?

Сразу ставьте заморозку удаления по request_id, периоду, пользователю или сервису. Иначе обычный TTL сотрет часть следа, и команда снова начнет спорить по памяти вместо разбора по фактам.

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

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

Как быстро проверить журнал перед запуском?

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

С чего начать внедрение аудит-логов для LLM?

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