Logo Craft Homelab Docs Контакты Telegram
Наблюдаемость AI-пайплайнов через Trace и Span Трендовые github проекты в нашем телеграм канале. Подпишись →
16 июля 2026 г.

AI-сервису нужен полный маршрут каждого запроса

У небольшого AI-приложения всё часто начинается с простой схемы: пользователь отправляет сообщение, backend вызывает модель, интерфейс показывает ответ. На этом этапе хватает обычных логов, пары метрик и ручной проверки промптов. Ситуация меняется, когда сервис получает память, RAG, несколько агентов, внешние API, фильтры безопасности и постобработку.

Итоговый ответ начинает зависеть от цепочки промежуточных решений. Запрос проходит safety-фильтр, классификатор намерения, поиск по базе знаний, выбор контекста, генерацию, проверку фактов, запись в память и финальный guard. Если результат странный, медленный или дорогой, инженеру нужно увидеть весь путь выполнения. Одной строки provider timeout или request failed уже недостаточно.

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

Логи фиксируют события, трассы показывают маршрут

Логирование остаётся полезным: оно записывает ошибки, предупреждения, входные параметры и важные бизнес-события. Но лог обычно отвечает на вопрос «что произошло в конкретной точке». Трассировка отвечает на вопрос «каким маршрутом запрос прошёл через систему».

Для AI-сервиса это критично. Один ответ может собрать данные из векторного индекса, пользовательской памяти, knowledge graph и внешнего API. Ошибка в финальном тексте иногда появляется из-за раннего этапа: неверной классификации намерения, пустой выдачи RAG, деградации провайдера или слишком жёсткого фильтра безопасности.

Модель Trace/Span раскладывает выполнение на понятную структуру:

Trace: user_message_42
├── safety_check
├── intent_detection
├── rag_retrieve
│   ├── vector_search
│   └── rerank
├── semantic_memory
├── generate_answer
├── truth_check
└── response_guard

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

Что стоит сохранять в spans

Минимальный набор данных зависит от проекта, но для большинства backend- и homelab-сервисов полезны такие поля: trace_id, span_id, parent_span_id, имя операции, тип span, время старта и завершения, статус, ошибка, параметры LLM-вызова, признаки retrieval-этапа, cache hit, retry count и rate limit.

Контент промптов и пользовательских сообщений лучше сохранять осознанно. Для внутреннего стенда может хватить полного текста, для production чаще нужны маскирование PII, усечение больших payload и отдельная политика retention. Наблюдаемость должна помогать расследованию, сохраняя приватность и секреты.

Propagation связывает сервисы в одну историю

Как только AI-приложение выходит за пределы одного процесса, важна передача контекста трассировки. Backend может вызвать сервис поиска, worker очереди, gateway к LLM-провайдерам и API истории диалогов. Без propagation каждый компонент видит собственный локальный запрос, а инженер получает набор разрозненных логов.

Простой вариант — передавать идентификаторы трассы через HTTP-заголовки или сообщения очереди:

X-Trace-Id: 7d9f...
X-Span-Id: b12a...
X-Parent-Span-Id: 4a31...
X-Causal-Depth: 3

При входящем запросе сервис извлекает контекст, создаёт дочерний span и передаёт обновлённые значения дальше. Так цепочка остаётся связной даже при нескольких микросервисах. Если сервис не получил внешний trace id, он создаёт новую трассу и становится корнем дерева.

Для FastAPI это удобно оформить middleware: на входе разобрать заголовки, положить контекст в request state или context variables, на выходе вернуть X-Trace-Id клиенту. Затем wrapper для внешних вызовов и LLM-провайдеров автоматически создаёт spans вокруг каждой операции.

Persistence нужен для расследований

In-memory трассы хороши для live-отладки, но после перезапуска они пропадают. Для реальной эксплуатации нужна запись завершённых трасс на диск или в базу, восстановление индекса при старте и аккуратная обработка незавершённых операций.

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

trace_store/
├── traces/{trace_id}.json
├── spans/{trace_id}/span_{span_id}.json
├── index.json
├── archive/
└── quarantine/

Завершённая трасса сохраняется как JSON, spans можно писать отдельно для постепенного flush. При рестарте активные трассы помечаются как interrupted, повреждённые записи отправляются в quarantine. Такой подход даёт простой retention: старые трассы архивируются, последние N дней остаются доступными для поиска и аудита.

Если нагрузка выше, хранилище можно заменить на PostgreSQL, ClickHouse, OpenSearch или OpenTelemetry-инфраструктуру. Главное — сохранить модель данных и дисциплину записи: каждый этап должен завершаться явно, ошибки должны попадать в span, пропущенные связи должны выявляться автоматической проверкой.

Аудит целостности ловит скрытые сбои

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

Для AI-пайплайна такие проверки быстро находят неприятные классы багов:

  • генерация стартовала без успешного retrieval, хотя сценарий требует контекст;
  • ответ прошёл наружу после ошибки response guard;
  • retry создал несколько несвязанных веток;
  • worker завершился, оставив активный span;
  • внешний провайдер вернул timeout, но итоговая трасса помечена успешной;
  • после рестарта часть истории потерялась или записалась повреждённым JSON.

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

Live-режим удобен при отладке агентов

Для приложений с агентами и RAG полезен live-режим. WebSocket или Server-Sent Events могут отправлять события trace_event на фронтенд: этап начался, завершился или упал. Интерфейс показывает активную стадию, завершённые шаги, ошибки и общую длительность запроса.

Такой экран быстро показывает, где запрос застрял: на retrieval, rerank, LLM-вызове, проверке фактов или сохранении памяти. Режимы работы лучше разделить заранее: live записывает и транслирует события, shadow только собирает данные, readonly открывает историю, disabled отключает инструмент.

План внедрения

Начинать стоит с узкого слоя:

  1. Ввести trace_id для каждого входящего запроса и возвращать его клиенту.
  2. Обернуть основные фазы пайплайна в spans: safety, intent, retrieval, generate, validate, persist.
  3. Добавить wrapper для внешних API и LLM-вызовов с таймингами, retry и статусами.
  4. Передавать trace context через HTTP-заголовки и сообщения очередей.
  5. Сохранять завершённые трассы в JSON или базу с понятным retention.
  6. Сделать endpoint для просмотра трассы и endpoint аудита целостности.
  7. Очистить метаданные от секретов, токенов авторизации и лишних пользовательских данных.
  8. Добавить алерты на рост latency, частые ошибки отдельных spans и незавершённые трассы.

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

Наблюдаемость AI-систем — часть эксплуатационной дисциплины. Чем больше в пайплайне памяти, агентов, retrieval и внешних сервисов, тем важнее иметь полный технический маршрут каждого запроса. Trace/Span-модель даёт такой маршрут в форме, понятной backend-разработчику и удобной для поддержки.