W3C Trace Context

Стандарт, по которому один идентификатор запроса переживает переход между сервисами. Без него трейс агента рассыпается на несвязанные куски: фронт знает свой запрос, бэкенд — свой, RAG — свой, и собрать из них одну историю нельзя.

Суть

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

W3C Trace Context решает это одним HTTP-заголовком traceparent, который каждый участник цепочки обязан принять, использовать и передать дальше. Это стандарт консорциума W3C — он не привязан ни к вендору наблюдаемости, ни к языку.

Как работает

Заголовок состоит из четырёх полей через дефис:

00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│  │                                │                │
│  Trace ID — един для всей цепочки │                Trace Flags
Версия                              parent-id        (сэмплирование)
  • Trace ID не меняется от первого до последнего узла — по нему собирается вся история.
  • parent-id (так поле названо в спецификации; в инструментах его обычно показывают как Parent Span ID) указывает на вызывающий узел, из этого строится дерево.
  • Trace Flags говорят, сэмплировать этот трейс или пропустить. Флаг sampled — рекомендация вызывающей стороны, а не приказ: принимающая может решить иначе.

Рядом со стандартом живёт заголовок tracestate — для вендор-специфичных данных, которые не помещаются в жёсткий формат traceparent.

Принимающая сторона извлекает контекст из заголовка и восстанавливает его для всех дочерних задач, включая асинхронные:

carrier = {"traceparent": request.headers.get("traceparent", "")}
context = TraceContextTextMapPropagator().extract(carrier=carrier)

После восстановления контекста каждый порождённый спан автоматически привязывается к общему дереву — даже если исполняется в другом потоке или корутине.

Зачем это агенту

У агента шаги не выстроены в линию: один запрос порождает ветвление на планирование, вызовы инструментов, обращения к ретриверу, иногда — делегирование чужому агенту. Сквозной Trace ID даёт три вещи, которых иначе нет:

  • Поиск по инциденту. Пользователь жалуется на ответ — по одному идентификатору поднимается вся траектория, а не разрозненные записи.
  • Атрибуция стоимости. Токены и латентность каждого узла суммируются по трейсу, и цену конкретного взаимодействия наконец можно посчитать.
  • Локализация сбоя. Видно не «система вернула ошибку», а на каком именно узле ветка ушла не туда.

В событийно-ориентированных архитектурах именно это становится главной болью: протащить Trace ID через шину сообщений сложнее, чем через синхронный HTTP-вызов, и без дисциплины контекст теряется (см. Event Driven Agents).

Связано с

  • OpenInference — что именно писать внутрь спанов, когда контекст уже проброшен
  • Agent Observability — трейсинг шагов как основа наблюдаемости агента
  • AgentOps — стандарт как обязательный пункт продакшен-чеклиста
  • Agent Failure Modes — без сквозного трейса часть режимов отказа неотличима друг от друга
  • Event Driven Agents — где проброс контекста ломается чаще всего

Открытые вопросы

  • как надёжно пробрасывать traceparent через Kafka или RabbitMQ, чтобы не терять контекст на асинхронных переходах
  • какую долю трейсов сэмплировать в проде, чтобы не утонуть в объёме, но поймать редкие сбои