Трейс ИИ-агента можно открыть в обычном "дереве" и найти медленный вызов или спан с ошибкой. Но по дереву трудно восстановить логику запуска: сколько раз агент обращался к модели, какие инструменты вызывал, где повторил шаг и какой ответ в итоге увидел пользователь.

В Proto Observability Platform уже были дерево, граф, флейм-граф и другие представления трейса. Они продолжали решать инфраструктурные задачи, но ответы о поведении агента приходилось собирать вручную из десятков спанов, транспортных вызовов и повторяющихся тел сообщений. Мы увидели это сначала на OpenTelemetry Demo 3.0, затем при отладке собственного «AI-аналитика» в связке с нашим MCP-сервером.

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

Новый взгляд на тот же трейс

Первые данные для нового представления мы получили на OpenTelemetry Demo 3.0. Сначала там нужно было получить единый трейс через chatbot, агента, MCP, модель и сервисы магазина. Когда контекст перестал обрываться на MCP-вызове, в одном дереве оказались операции модели, вызовы инструментов и обычные HTTP-спаны. Как мы восстанавливали этот сквозной трейс, подробно описано в предыдущей статье. На собранных данных мы и начали проектировать специализированное GenAI-представление данных трейса.

Сквозной трейс OpenTelemetry Demo 3.0, с которого началось проектирование GenAI-представления
Сквозной трейс OpenTelemetry Demo 3.0, с которого началось проектирование GenAI-представления

Дополнительно появился второй источник данных: собственный «AI-аналитик» Proto Observability Platform. Мы разрабатывали его вместе с нашим MCP-сервером и тестировали связку на реальных запросах к телеметрии. Для этой статьи «AI-аналитик» важен не как отдельный продуктовый функционал, а как прикладная агентная система, на которой визуализация должна была помогать отлаживать взаимодействие модели, инструментов и транспорта.

Собственный «AI-аналитик», который мы тестировали в связке с нашим MCP-сервером
Собственный «AI-аналитик», который мы тестировали в связке с нашим MCP-сервером

На одном из его трейсов цепочка ошибки была видна целиком: tool_error на стороне proto-ai-chatbackend_error в proto-mcp, ошибка запроса к базе и итоговый HTTP 500.

Каскад ошибок от вызова инструмента до MCP и запроса к базе
Каскад ошибок от вызова инструмента до MCP и запроса к базе

Затем мы открыли детали ИИ-спанов и получили другую проблему. Атрибуты gen_ai.input.messagesgen_ai.output.messagesgen_ai.tool.definitions и traceloop.entity.* содержали строки длиной в несколько килобайт. На тестовом стенде отдельные тела сообщений доходили почти до 9 КБ. Одни и те же данные могли повторяться сразу в трёх семействах атрибутов.

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

Первая идея была прямолинейной: выбрать GenAI-спаны и вывести их карточками в хронологическом порядке. Такая версия уже была лучше килобайтных JSON, но на живом трейсе быстро проявились ограничения:

  • каждый вызов модели снова присылал всю историю диалога;

  • HTTP- и RPC-спаны либо засоряли последовательность, либо полностью исчезали из GenAI-представления;

  • список карточек показывал детали, но не форму повторяющегося цикла агента;

  • пользовательский разговор терялся среди внутренних рассуждений и результатов инструментов.

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

Четыре вопроса к одному трейсу

В итоговой версии есть четыре режима:

Представление

На какой вопрос отвечает

Что намеренно скрывает

Лента

Что происходило по порядку и где ушло время?

Повторяющийся контекст и лишние уровни транспорта

Чат

Что спросил и получил пользователь?

Рассуждения, инструменты, транспорт и техническое время

Граф вызовов

В каком порядке выполнялись вызовы и какой из них завершился ошибкой?

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

Граф шагов

Какие шаги повторялись и где возникли ошибки?

Детали отдельных вызовов: одинаковые операции объединены по шагам

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

Лента: отделить работу агента от транспорта

Лента стала основным представлением. Шаги расположены сверху вниз, слева указано смещение по времени от начала трейса, а вложенность передаётся отступом.

Лента прогона «AI-аналитика»: доступные и вызванные MCP-инструменты
Лента прогона «AI-аналитика»: доступные и вызванные MCP-инструменты

Здесь пришлось определить, что считать шагом. OpenTelemetry-трейс содержит операции агента и транспорт под ними. Например, операция инструмента длилась 88 мс, из которых 81 мс занял запрос к currencyservice. Если вывести оба спана соседними карточками, транспорт начинает выглядеть как ещё одно решение агента. Если удалить HTTP-спан, исчезает ответ на вопрос, где именно прошли эти 81 мс.

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

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

Граф шагов: увидеть цикл, а не список

Лента хорошо показывает последовательность, но длинный прогон всё равно приходится читать сверху вниз. Для ответа на вопрос «какой сценарий выполнял агент?» мы добавили граф шагов.

Граф шагов «AI-аналитика» со схлопнутыми повторами и ошибкой инструмента
Граф шагов «AI-аналитика» со схлопнутыми повторами и ошибкой инструмента

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

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

  • вложенность показывает, внутри какой операции началась ветка;

  • последовательность показывает, что выполнялось следом.

Если последовательное ребро возвращается к уже пройденному узлу, оно рисуется пунктиром. Так становится виден цикл model > tools > model, который в обычном дереве размазан по нескольким веткам.

Граф вызовов: одинаковые шаги могут закончиться по-разному

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

Граф вызовов, где повторные вызовы и ошибки остаются отдельными узлами
Граф вызовов, где повторные вызовы и ошибки остаются отдельными узлами

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

Чат: не каждая реплика модели была ответом пользователю

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

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

Но и восстановленная стенограмма не равна пользовательскому чату. В ней остаются системный промпт, промежуточные ходы и рассуждения модели, вызовы инструментов и технические ответы. Для режима «Чат» мы оставляем вопросы пользователя и финальные ответы модели, а внутреннюю работу сохраняем в ленте.

Диалог с «AI-аналитиком» без внутренних операций агента и MCP
Диалог с «AI-аналитиком» без внутренних операций агента и MCP

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

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

Вернёмся к «AI-аналитику», интерфейс которого показан в начале статьи. Именно ошибки инструментов первыми доказали ценность OpenTelemetry при разработке нашего агента в связке с нашим MCP-сервером. В обычном дереве было видно, какой вызов не сработал. Но при построении специального GenAI-представления мы столкнулись с более тонким случаем: операция агента могла выглядеть успешной, хотя её транспорт или результат уже содержал ошибку.

На трейсе «AI-аналитика» инструмент search_traces вернул структурированный результат с ok: false и backend_error. Ниже лежала настоящая причина: запрос к базе завершился ILLEGAL_AGGREGATION, а MCP-сервер вернул HTTP 500. Для агентного фреймворка это мог быть обычный результат инструмента, а не ошибка. Если смотреть только на статус внешнего execute_tool, сбой легко потерять.

Ошибка : результат инструмента, причина в базе и метаданные MCP
Ошибка : результат инструмента, причина в базе и метаданные MCP

Первая версия GenAI-представления смотрела только на статус операции агента и показывала полностью зелёный прогон. Затем мы стали учитывать транспорт, но ошибка всё ещё могла исчезнуть вместе со спаном, который визуализация скрыла при агрегации.

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

GenAI-данные в трейсах устроены по-разному

Мы хотели, чтобы представление работало не только с «идеальным» набором OpenTelemetry Semantic Conventions. На практике встретились полные стандартные атрибуты, обёртки фреймворков и частично инструментированные спаны, где нужные признаки распределены между именем операции и нестандартными полями.

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

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

Что в итоге дала такая декомпозиция

Один и тот же трейс теперь можно рассмотреть с четырёх дополнительных сторон:

  • Лента показывает исполнение и время.

  • Граф шагов показывает алгоритм.

  • Граф вызовов показывает конкретный запуск.

  • Чат показывает пользовательский результат.

Самым полезным решением оказалось не выбрать «лучшую» визуализацию, а запретить каждой из них отвечать на чужой вопрос. Чат не показывает транспорт. Граф шагов не притворяется журналом конкретных вызовов. Лента не пытается одним экраном показать общий цикл. Сырые атрибуты остаются рядом как уровень проверки, но больше не служат основным интерфейсом.

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