Как вы логируете все вызовы LLM для аудита?
Краткий тезис
Логирование всех вызовов LLM — это обязательная практика для аудита, отладки и соответствия требованиям (compliance). Каждый запрос и ответ сохраняются в структурированном формате (JSON) в специализированное хранилище (ClickHouse), с разными уровнями детализации и политикой ретеншна. Критически важны маскирование персональных данных]] (PII) и обеспечение неизменности журналов для юридической доказательности.
1. Зачем логировать вызовы LLM: пять целей
Аудит — не единственная причина. Логи необходимы для:
| Цель | Описание |
|---|---|
| Compliance (соответствие) | Доказательство перед регулятором (GDPR, HIPAA, SOX), что система обрабатывала данные в соответствии с политиками. |
| Отладка (debugging) | Восстановление цепочки вызовов при некорректном ответе или сбое. |
| Мониторинг производительности | Замер latency, токенов, ошибок — основа для алертов и оптимизации. |
| Анализ злоупотреблений | Выявление аномального использования (высокий объём, повторяющиеся вредоносные промпты). |
| Метрики качества | Сравнение ответов разных моделей, A/B-тестирование. |
Без полного лога невозможно доказать, что система работала корректно (или выявить, что пошло не так).
2. Структура лога: минимальный набор полей в JSON
Каждый лог — это строка JSON. Обязательные поля:
{
"event_id": "uuid-v4",
"timestamp": "2025-02-18T10:30:00.123Z",
"user_id": "hashed_user_abc123",
"session_id": "session_xyz456",
"request_id": "req_789",
"model": "gpt-4o",
"prompt": "<текст запроса после маскировки>",
"response": "<текст ответа после маскировки>",
"prompt_tokens": 245,
"completion_tokens": 120,
"total_tokens": 365,
"latency_ms": 1560,
"temperature": 0.7,
"max_tokens": 2000,
"response_status": "success",
"pii_masked": true,
"version": "1.2"
}
Термин «маскировка PII» (Personally Identifiable Information) — замена реальных имён, адресов, номеров телефонов на плейсхолдеры (например, [PII_REDACTED]) до записи в лог.
Дополнительные поля для аудита: hash всего объекта (SHA256) для проверки целостности, signature (приватный ключ сервера) для аутентификации записи.
3. Хранилище: почему ClickHouse и как организовать данные
ClickHouse — колоночная СУБД, оптимизированная для аналитических запросов с высокой скоростью записи. Он идеально подходит для логов, потому что:
- Вставка тысяч строк в секунду (OLAP).
- Быстрая фильтрация по времени, модели, пользователю.
- Нативная поддержка TTL (Time-To-Live) для автоматического удаления старых записей.
Пример схемы таблицы:
CREATE TABLE llm_logs (
event_id UUID,
timestamp DateTime64(3),
user_id String,
session_id String,
request_id String,
model String,
prompt Mediumtext,
response Mediumtext,
prompt_tokens UInt32,
completion_tokens UInt32,
latency_ms UInt32,
response_status String,
pii_masked Bool,
event_hash String
) ENGINE = MergeTree()
ORDER BY (timestamp, model)
TTL timestamp + INTERVAL 90 DAY DELETE;
Альтернативы PostgreSQL (если запись <10k/s и нужны транзакции), Elasticsearch (для полнотекстового поиска по промптам), облачные решения вроде S3 + Athena (дёшево, но медленно при частых запросах).
4. Уровни логирования: DEBUG, INFO, ERROR
Типичная трехуровневая модель:
| Уровень | Что включается | Когда использовать | Размер записи |
|---|---|---|---|
| DEBUG | Полный промпт, полный ответ, трейсинг внутренних шагов (RAG-агенты, цепочки мыслей) | Разработка, поиск багов, A/B-тесты | Большой (до сотен KB) |
| INFO | Безопасный минимум (все поля после маскировки), метрики, статус | Продакшн — для аудита и мониторинга | Средний (~1-2 KB) |
| ERROR | Только упавшие вызовы: код ошибки, stack trace, исходный промпт (без маскировки в защищённом хранилище) | Аварийный мониторинг, RCA | Большой, но редко |
В продакшне обычно используют INFO для 99.9% запросов, включая усечённые данные. DEBUG — только для выбранных пользователей (по session_id) или в отдельной низкошумящей таблице с коротким TTL.
5. Безопасность: маскировка PII и контроль доступа
Перед записью лога необходимо удалить или заменить PII. Процесс:
- Промпт и ответ проходят через детектор PII (spaCy, Presidio, Microsoft Presidio или собственный regex).
- Найденные сущности маскируются:
{ "email": "[EMAIL_REDACTED]", "phone": "[PHONE_REDACTED]" }. - Исходный (незамаскированный) лог сохраняется только в системе SIEM с ограниченным доступом и audit trail (журналом доступа к журналу).
- Доступ к таблицам логов — по ролям (например,
auditor,developer) и с обязательным аудитом запросов.
Дополнительная защита: шифрование на уровне хранения (TDE) и при передаче (TLS). Для юридической значимости — WORM (Write Once Read Many) хранилище, которое нельзя изменить постфактум.
6. Политика ретеншна: 30–90 дней
Срок хранения зависит от требований регулятора и бизнеса:
| Тип лога | Ретеншн | Причина |
|---|---|---|
| DEBUG (полный) | 7–30 дней | Удобство отладки, быстрое удаление из-за объёма |
| INFO (маскированный) | 30–90 дней | Аудит, анализ трендов |
| ERROR (с исходными данными) | 30–90 дней | RCA, может быть продлён по инциденту |
| Агрегированные метрики (средняя latency, токены) | 1 год + | Долгосрочная ёмкостная оценка |
Реализуется TTL на уровне базы или внешний скрипт архивации (cold storage в S3 Glacier). Важно: после удаления логов из горячего хранилища они должны быть восстановимы в течение срока аудита (если требуется).
7. Аудит и цепочки proof: неизменяемость и верификация
Чтобы лог был принят как доказательство, он должен быть неизменяемым:
- Каждая запись содержит хэш (SHA-256 всех полей) и цифровую подпись (HMAC с секретным ключом).
- Хэш предыдущего блока записей включается в следующий (аналог блокчейна для логов).
- Периодически (каждый час) создаётся snapshot — сжатый файл с агрегированным хэшем, который отправляется в WORM-хранилище.
Пример проверки целостности (Python):
import hashlib, hmac
def verify_log_entry(entry: dict, secret: bytes) -> bool:
expected_hash = entry.pop('event_hash', None)
if not expected_hash:
return False
data = json.dumps(entry, sort_keys=True, ensure_ascii=False).encode()
real_hash = hashlib.sha256(data).hexdigest()
return hmac.compare_digest(expected_hash, real_hash)
8. Инструменты и подходы к сбору
В промышленных системах логирование внедряется через стандартизированный middleware (пример для FastAPI):
from fastapi import Request, Response
import time, uuid, json, hashlib
async def llm_logging_middleware(request: Request, call_next):
start = time.monotonic()
body = await request.body()
response = await call_next(request)
latency = time.monotonic() - start
log_entry = {
"event_id": str(uuid.uuid4()),
"timestamp": datetime.utcnow().isoformat() + "Z",
"user_id": request.headers.get("X-User-ID", "anonymous"),
"model": request.headers.get("X-Model", "unknown"),
"prompt": mask_pii(body.decode()),
"response": mask_pii(response.body.decode()),
"prompt_tokens": count_tokens(body),
"completion_tokens": count_tokens(response.body),
"latency_ms": int(latency * 1000),
"response_status": "success" if response.status_code < 400 else "error"
}
log_entry["event_hash"] = hashlib.sha256(
json.dumps(log_entry, sort_keys=True).encode()
).hexdigest()
# Асинхронная отправка в ClickHouse через буфер (Kafka/Redis)
await log_queue.put(log_entry)
return response
Стек OpenTelemetry (трейсинг), Kafka (буфер), Logstash (трансформация), ClickHouse (хранение), Grafana (визуализация). Альтернатива — MLflow (для экспериментов) или LangSmith (коммерческое решение для LLM).
9. Стоимость и масштабирование
Логирование всех вызовов может генерировать терабайты в день при высокой нагрузке. Оптимизация:
- Сэмплирование — писать 100% только для ошибок и случайных 1% успешных вызовов (если не требуется полный аудит).
- Партиционирование по дням и моделям — ускорение очистки по TTL.
- Сжатие в ClickHouse (ZSTD) — уменьшение объёма в 3–5 раз.
- Агрегация — вместо хранения каждого вызова хранить минутные агрегаты (средняя latency, p95, количество токенов, количество ошибок), а для полного аудита — только для запросов, помеченных флагом
audit_required=true.
10. Пет-проект для закрепления
Задача Разработать систему логирования вызовов LLM с аудитом и дашбордом.
Инструменты Python (FastAPI или Flask), ClickHouse (или SQLite для теста), Docker, Grafana (опционально). Детектор PII — библиотека presidio-analyzer.
Шаги:
- Напишите middleware, которое перехватывает вызов к LLM (можно заменить на заглушку mock_llm.py).
- Реализуйте маскировку email, телефона, номера карты.
- Создайте модель ClickHouse (или таблицу SQLite) по схеме из раздела 3.
- Сделайте API для записи лога (
POST /log) и для чтения логов по времени (GET /logs?from=2025-01-01). - Добавьте проверку целостности с помощью HMAC.
- Настройте TTL (например, 7 дней). Напишите тест, проверяющий, что запись удаляется после TTL.
- Постройте простой дашборд в Streamlit: количество запросов в час, средняя latency, процент ошибок.
Ожидаемый результат
- Работающий endpoint, который принимает и сохраняет JSON-лог.
- Проверка, что PII заменено на [REDACTED].
- Возможность запросить лог за период и убедиться в его целостности.
- Отчёт с графиками, который помогает выявить аномалии.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 72 (мониторинг LLM-приложений) | Метрики и алерты, в логировании — источник данных для мониторинга. |
| 68 (безопасность RAG-систем) | Маскировка PII и контроль доступа напрямую связаны с безопасностью. |
| 74 (уменьшение latency) | Логирование latency помогает выявлять узкие места. |
| 66 (выбор LLM для production) | Разные модели логируются, данные используются для сравнения. |
| 47 (оффлайн-оценка RAG) | Логи — основа для сбора данных для оффлайн-метрик. |
| 55 (fine-tuning с логированием) | Логи вызовов нужны для сбора датасета для дообучения. |
Навигация
- Предыдущий: 72
- Следующий: 74
- Индекс: 00. Индекс разборов