/ Дневник курса / Урок 13 /

Трассировка ИИ-агента: сквозной идентификатор, словарь телеметрии и границы наблюдаемости

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

  • observability
  • tracing
  • opentelemetry
  • production
  • agentops

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

1. Наблюдаемость: инструмент отладки или слой архитектуры

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

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

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

2. Сквозной идентификатор: как трейс переживает границу сервиса

Внутри процесса трассировка тривиальна, всё ломается на границах. Склеивает куски единый идентификатор из стандарта W3C Trace Context: HTTP-заголовок traceparent, который каждый участник цепочки обязан принять, использовать и передать дальше. Стандарт не привязан ни к вендору, ни к языку.

00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│  │                                │                │
│  │                                │                └ trace-flags
│  │                                └ parent-id, 16 hex
│  └ trace-id, 32 hex
└ версия

  UI ──► FastAPI ──► оркестратор ──► ретривер ──► внешний API
       один trace-id на всю цепочку, parent-id меняется на узле
Рисунок — формат заголовка версии 00: идентификатор трейса из 32 шестнадцатеричных символов, идентификатор вызывающего узла из 16 и байт флагов, младший бит которого — рекомендация sampled; trace-id остаётся неизменным на всём пути UI → FastAPI → оркестратор → ретривер → внешний API, parent-id переписывается на каждом переходе. Сам пример — канонический из текста спецификации, а не трейс из чьего-то прода.

Дальше три детали, которые теряются в пересказах. Третье поле спецификация называет parent-id и лишь оговаривает, что «в некоторых системах трассировки это известно как span-id»: интерфейсы подписывают его как Parent Span ID, но искать в документации придётся по первому имени. Флаг sampled — рекомендация вызывающей стороны, а не приказ; принимающий сервис под своей нагрузкой вправе досэмплировать по-своему. Версия ff запрещена, а заголовок со всеми нулями в любом из идентификаторов вендор обязан игнорировать — то есть «трейс есть, но пустой» иногда означает корректное поведение по стандарту, а не баг бэкенда. Рядом живёт заголовок tracestate для вендорских пар ключ-значение, которым жёсткий формат traceparent тесен.

Со зрелостью стандарта всё спокойно: Level 1 — Recommendation от 23 ноября 2021 (редакционное обновление рекомендации 2020 года), то есть завершённый документ. Level 2 добавляет флаг случайного trace-id и рекомендации по генерации идентификаторов, но с 28 марта 2024 висит в статусе Candidate Recommendation Draft и с тех пор не двигался. Базовый формат стабилен с 2021 года — это аргумент за него, а не против.

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

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

Бесплатно это работает только в синхронном HTTP. В событийной архитектуре заголовка нет, traceparent кладут в метаданные сообщения и разбирают на стороне подписчика руками — про этот шов подробнее в разборе мультиагентных систем в продакшене.

3. Чем описан шаг агента: три словаря и ошибка, которую легко унаследовать

Транспорт спора не вызывает: OpenTelemetry в мае 2026 стал Graduated-проектом CNCF, спаны едут по OTLP — общему протоколу передачи телеметрии. Спорят про словарь: какими атрибутами описан шаг агента. Вокруг этого вопроса кочует неточность, которую стоит показать целиком.

Атрибуты input.value, output.value, llm.token_count.prompt и ключ openinference.span.kind регулярно подписывают как конвенцию OTel GenAI. Это словарь OpenInference, открытой спецификации Arize поверх OpenTelemetry: она добавляет к общему транспорту понятия промпта, вызова инструмента и извлечённого документа. У официального стандарта префикс другой: gen_ai.input.messages, gen_ai.usage.input_tokens, gen_ai.operation.name. Ошибка не косметическая — по неверному имени атрибута запрос к телеметрии просто не вернёт ничего.

Словарь Кто ведёт Как выглядит Статус
gen_ai.* OpenTelemetry (CNCF) gen_ai.input.messages, gen_ai.usage.input_tokens Development, с мая 2026 в отдельном репозитории
openinference.* Arize input.value, openinference.span.kind, llm.token_count.* Apache-2.0, 30+ инструментаций, активен
traceloop.* OpenLLMetry traceloop.entity.input, traceloop.span.kind живой третий вариант

Ни один не вытеснил остальные, и рынок решил это не победой одного, а мультисловарным приёмом: LangSmith публикует таблицы маппинга сразу для всех трёх плюс собственный неймспейс, Langfuse для одного поля перечисляет gen_ai.prompt, input.value и вариант MLflow. Официальные конвенции при этом растут — GenAI-часть выделена в репозиторий semantic-conventions-genai, появились спаны plan, retrieval и memory, есть отдельный документ для Model Context Protocol, — но статус остаётся Development и на уровне документов, и на уровне каждого спана.

Косвенный голос за OpenInference подаёт академия: бенчмарк TRAIL собирал трейсы «через OpenTelemetry, а конкретно — через openinference standard», то есть надстройку вендора взяли рабочим стандартом для агентских трейсов. Отсюда простое требование к проекту: словарь фиксируют один раз на систему, потому что от него зависит каждый последующий запрос к телеметрии — и алерт, и дашборд, и разбор инцидента.

4. Что искать в трейсе: режимы отказа под кодом 200

Классический мониторинг отвечает на вопрос «сервис жив?». У агента ответ почти всегда «да» и почти всегда бесполезен: HTTP 200, латентность в норме, результат негодный или разорительный. Поэтому режимы отказа держат отдельным списком, где у каждого прописана сигнатура в телеметрии.

Trace (root)
└── LLM Span ──────────►  llm.token_count.prompt / .completion
    ├── Tool Span ─────►  много TOOL в одном трейсе = петля
    ├── Retriever Span ►  пустой результат + уверенный ответ = голодание RAG
    └── Guardrail Span ►  фильтр сработал, наружу ушёл пустой ответ
Рисунок — дерево спанов одного вызова агента: тип шага лежит в openinference.span.kind (значения LLM, RETRIEVER, TOOL, GUARDRAIL, EVALUATOR и ещё пять), и именно он превращает отладку в запрос к телеметрии — вместо чтения логов считаем спаны нужного типа внутри трейса.

Матрица сбоев, у которых в мониторинге один и тот же вид:

  • Семантический сбой — 200 OK за две секунды, а модель согласилась выдать закрытые данные: сработала инъекция.
  • Голодание RAG — запрос к базе успешен, ретривер вернул пустой список, ответ сгенерирован на весах модели, то есть выдуман.
  • Бесконечная петля — 200 «в процессе», агент долбит один инструмент, счёт за токены растёт с каждой итерацией.
  • Отказ безопасности — 200 OK, ограждения на выходе отсекли ответ, клиент получил пустоту вместо результата.

Алерты обычно ставят на три сигнатуры. Бесконечные петли — нет условия выхода, агент уходит на десятки итераций одного инструмента. Число «двадцать и больше» встречается часто, но это рабочая эвристика, а не измеренная граница: калибровать придётся на своём сценарии. Держит петли пара «жёсткий max_iterations в оркестраторе плюс порог на число TOOL-спанов внутри трейса». Порча памяти — в долговременный контекст записан невалидный JSON, и дальше система считает испорченное состояние своим; тут спасают Pydantic-схемы на каждом узле графа, поставленные щитом до записи, а не проверкой в конце. Каскадный коллапс — один неверный шаг отравил контекст, вся генерация ниже идёт от ложной предпосылки; отсекается проверкой обоснованности перед сборкой финального ответа.

5. Сколько верить оценке «80% сбоев приходится на три сигнатуры»

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

Академическая картина устроена иначе. MAST, каталог отказов мультиагентных систем, раскладывает их поведение на 14 режимов в трёх категориях, меряет базовую частоту отказа в 41–86,7% на семи открытых фреймворках и прямо отрицает универсальность: профиль отказов отражает архитектуру конкретной системы. TRAIL, бенчмарк разбора трейсов, ближе к теме — 148 размеченных человеком трейсов, 1987 спанов в формате OpenInference, из них 575 с ошибкой хотя бы одного типа; таксономия там из трёх областей: рассуждение; планирование и координация; исполнение. Пропорции «80 на 20» не даёт ни та работа, ни другая.

Зато TRAIL даёт факт, который ломает лёгкий вывод «соберите телеметрию — дальше разберётесь». Лучшая модель на задаче разбора трейсов набирает 11%. Телеметрия себя не читает: каталог сигнатур, пороги и алерты проектируются руками и заранее, а человек из контура разбора инцидентов пока не уходит.

6. Персональные данные в трейсах: три рубежа

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

приложение ──► инструментация ──► приёмник трейсов ──► хранилище
     │                │                    │
  DLP-слой       OPENINFERENCE_HIDE_*   серверное маскирование
  Presidio,      → "__REDACTED__"       (в Langfuse — платный
  10–20 мс                               EE-ключ)
Рисунок — три рубежа обрезки персональных данных: собственный слой очистки перед вызовом модели (гибрид Presidio, около 10–20 мс), переменные OPENINFERENCE_HIDE_* на уровне инструментации, подменяющие значение константой "__REDACTED__", и серверное маскирование на стороне приёмника, которое у Langfuse входит в платную редакцию.

Первый рубеж — собственный слой DLP (data loss prevention: перехват чувствительного текста до того, как он уйдёт наружу). У каждого метода очистки своя цена. Регулярные выражения дают меньше миллисекунды и около 60% полноты, но разваливаются на нестандартном написании. NER (named entity recognition — модель, размечающая в тексте имена, адреса и номера) — 15–30 мс и около 85%, с ложными срабатываниями на брендах. Гибридный Presidio — 10–20 мс и около 92%. Локальный BERT — свыше 150 мс и около 98%, что режет пропускную способность. Под российские сущности вроде номера паспорта в Presidio дописывают свой распознаватель с регулярным выражением.

Второй рубеж дешевле остальных, и о нём чаще всего не знают. Инструментация OpenInference умеет не писать чувствительное с самого начала: OPENINFERENCE_HIDE_INPUTS, OPENINFERENCE_HIDE_INPUT_TEXT, OPENINFERENCE_HIDE_EMBEDDINGS_TEXT и ещё несколько переключателей. Важна деталь замены: скрытое поле не пустеет, а получает "__REDACTED__" — потребитель трейса отличает «скрыто намеренно» от «потерялось по дороге».

Третий рубеж — маскирование на стороне приёмника, и здесь бесплатный self-hosting подводит. Ядро Langfuse под MIT и без ограничений по масштабу, но Server-Side Data Masking и журнал аудита идут по корпоративному ключу вместе с ролевой моделью на уровне проекта и политиками хранения. Практическое следствие для регулируемого контура: в бесплатной установке резать персональные данные приходится раньше — на первых двух рубежах.

7. Куда складывать трейсы: что изменилось за год

Ось выбора осталась прежней — где физически живут данные. А вот аргумент про совместимость сдулся: LangSmith принимает OTLP от любого приложения на своём эндпоинте, документирует маппинг трёх внешних словарей и поддерживает веерную отправку через OpenTelemetry Collector, когда один поток спанов уходит одновременно к нему и в Datadog, Honeycomb или Grafana; для собственной установки эндпоинт просто подменяется. Преимущество Langfuse по независимости от фреймворка сузилось до лицензии и места хранения.

Обратная поправка касается Langfuse: формула «бесплатный self-hosting с полным функционалом» неточна ровно в тех пунктах, которые нужны регулируемому контуру, — маскирование и аудит платные. Сравнение LangSmith и Langfuse целиком разобрано раньше; эти две строки в таблице теперь читаются иначе.

8. Промпт в реестре против префиксного кэша

Версионирование промптов на стороне системы трейсов даёт горячую замену без пересборки образа и ревью изменений отдельно от кода. Плата — сетевой поход за промптом на каждый запрос, 50–150 мс. Лечится локальным кэшем с временем жизни порядка пяти минут.

Конфликт возникает уровнем ниже, там, где работает кэширование промпта. Инференс-движки хэшируют статический префикс и переиспользуют KV-кэш, снижая время до первого токена; любая динамика в начале — обращение по имени клиента, дата, идентификатор сессии — меняет хэш и обнуляет кэш на каждом запросе. Отсюда правило компоновки: статическая часть идёт первой и выравнивается по границе блока (в vLLM блок по умолчанию 16 токенов), переменные уезжают в конец контекста.

Рядом лежит вторая ловушка того же слоя. Внезапно подросший промпт — добавили пример на полторы тысячи токенов — при пиковом батчинге выедает свободные блоки памяти под кэш, а выпадение из бакетов по длине запускает рекомпиляцию вычислительных графов со спайками латентности в 5–10 секунд. Поэтому замер размера токенизированного промпта ставят шагом в CI, рядом с прогоном по эталонному набору.

9. Семантический кэш: экономия измерена, порог — нет

Семантический кэш отдаёт готовый ответ, когда новый запрос близок к прошлому по смыслу, а не совпадает буквально: запрос кодируется энкодером, сосед ищется по косинусной близости. Экономия измерена: кэш поверх Redis срезает до 68,8% обращений к API при доле попаданий 61,6–68,8%. SCALM — схема, которая строит политику кэширования на смысловых связях в логах диалогов, — поднимает долю попаданий примерно в полтора раза и экономит 77% токенов против базовой реализации. Порядок величины — «две трети повторов можно не отправлять в модель», а не «минус девяносто процентов счёта».

Обратная сторона того же замера по Redis-кэшу: доля семантически верных попаданий превышает 97%, то есть около 3% ответов из кэша не о том. На бенчмарке это статистика, на банковском трафике — ответ про кредитную карту на вопрос про дебетовую. Это и есть дрейф кэша, только с числом.

Порог 0.94, который ходит по презентациям, честнее подавать как рабочую константу конкретного контура. Литература вообще уводит вопрос из плоскости подбора числа: единый порог похожести задаёт жёсткий компромисс, где осторожное значение теряет безопасные переиспользования, а агрессивное отдаёт ответы не по адресу, и оптимальная офлайн-политика при этом NP-трудна — «настроить один раз правильно» невозможно в принципе. Современный ответ переносит работу с порога на границу: пограничные попадания верифицируют асинхронно, судья вне критического пути решает, годится ли кэшированный ответ, одобренные пары уезжают в динамический слой. Латентность запроса не страдает, доля выверенных ответов растёт вплоть до 3,9 раза. Зрелость приёма честно отражает статус управляемого сервиса Redis LangCache — preview. Ещё один ходовой приём, внешнего подтверждения которому не нашлось: сбрасывать секторы кэша по тегам при обновлении базы знаний, иначе кэш продолжает отдавать ответы по документам, которых уже нет.

Итог

  • Сквозной идентификатор — обязательство каждого узла цепочки: traceparent принимают, используют и передают дальше, а в шине сообщений прокидывают руками.
  • Словарь атрибутов выбирают один раз на систему. Их три (gen_ai.*, openinference.*, traceloop.*), официальный до сих пор в статусе Development, а перепутанный префикс превращает алерт в пустой запрос.
  • Режимы отказа живут под кодом 200 и ловятся по типам спанов: метрики транспорта их не видят.
  • Долю «80% сбоев на три сигнатуры» проверяйте на своих трейсах: MAST даёт 14 режимов и частоту отказа 41–86,7% с архитектурной зависимостью профиля.
  • Телеметрия себя не читает — 11% у лучшей модели на разборе трейсов. Каталог сигнатур и пороги проектируются заранее и руками.
  • Персональные данные режут до записи в трейс: серверного маскирования в бесплатной self-hosted установке Langfuse нет.

FAQ

Что такое traceparent и зачем он агенту

traceparent — HTTP-заголовок стандарта W3C Trace Context, который переносит идентификатор трейса и идентификатор вызывающего узла через границы сервисов. Агенту он даёт три вещи: поиск всей траектории по одному идентификатору при разборе жалобы, атрибуцию стоимости запроса по сумме токенов на узлах и локализацию сбоя до конкретного шага. Без него трейс рассыпается на несвязанные куски: фронтенд знает свой запрос, оркестратор — свой, ретривер — свой.

Чем OpenInference отличается от OTel GenAI semantic conventions

Это два разных словаря атрибутов поверх одного транспорта. OpenInference — спецификация Arize с префиксами вида input.value, output.value, llm.token_count.prompt и обязательным openinference.span.kind; официальные конвенции OpenTelemetry используют префикс gen_ai.* и на середину 2026 года остаются в статусе Development. Оба живы, ни один не вытеснил другой, и крупные приёмники трейсов маппят оба плюс третий словарь traceloop.*.

Как в трейсе увидеть, что агент зациклился

По числу спанов типа TOOL внутри одного трейса. Рост их количества сверх обычного для сценария — сигнатура петли: агент повторно вызывает один инструмент без условия выхода, а счёт за токены растёт с каждой итерацией при полностью зелёном мониторинге. Лечится парой «жёсткий лимит итераций в оркестраторе + алерт на превышение порога числа TOOL-спанов»; сам порог придётся откалибровать на своих трейсах, потому что легитимные длинные задачи тоже дают много вызовов.

Можно ли отдать разбор трейсов языковой модели

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

Где маскировать персональные данные перед отправкой в трейс

Раньше, чем кажется. Рубежа три: собственный слой перехвата чувствительных данных перед вызовом модели (гибридный Presidio даёт около 92% полноты при 10–20 мс), переменные OPENINFERENCE_HIDE_* на уровне инструментации с заменой значения на "__REDACTED__" и серверное маскирование на стороне приёмника. Третий рубеж в Langfuse входит в платную редакцию вместе с журналом аудита, поэтому в бесплатной self-hosted установке работают первые два.

Насколько можно доверять порогу похожести 0.94 в семантическом кэше

Как стартовой точке конкретного контура, а не как константе. По измерениям кэш срезает до 68,8% обращений к API при доле попаданий 61,6–68,8% и примерно 3% семантически неверных ответов. Единый порог задаёт жёсткий компромисс между потерянными переиспользованиями и неверными ответами, а оптимальная офлайн-политика формально NP-трудна, поэтому пограничные попадания разумнее верифицировать асинхронно, вне критического пути запроса.

Источники

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