ObservabilityOpenTelemetryLLM APIMonitoringProduction Engineering

Наблюдаемость LLM API с OpenTelemetry (руководство 2026)

1 мин чтения

62% продакшн-сбоев LLM остаются незамеченными HTTP-мониторингом более 48 часов. Статус 200 OK означает, что сервер ответил — а не что ответ был правильным, извлечённый контекст свежим или агент не зациклился через 47 избыточных вызовов инструментов. Ваш дашборд зелёный, пока галлюцинации доходят до платящих клиентов, а регресс шаблона промпта отравляет каждый ответ после деплоя во вторник.

Это руководство строит трёхслойный стек наблюдаемости — базовый OTel, семантические конвенции GenAI, виды спанов OpenInference — превращающий каждый пользовательский запрос в криминалистическое дерево трассировки. Один вызов функции регистрирует его. Хвостовая выборка сохраняет каждую трассировку сбоя без взрыва вашего бюджета на хранение.

Почему ваш мониторинговый дашборд слеп к сбоям LLM

Почему стандартный APM проваливается для LLM-приложений

HTTP 200 не означает «правильный ответ». Он означает, что сервер ответил. Проект OpenTelemetry предоставляет формат передачи и инфраструктуру коллектора — но LLM-приложениям нужны семантические конвенции поверх этого фундамента. Стандартный APM проваливается, потому что LLM-приложения проваливаются способами, которые HTTP-статусы не могут выразить:

  • Галлюцинация. Модель вернула уверенный, хорошо отформатированный ответ. Каждый факт в нём неверен. HTTP-статус: 200.
  • Молчаливый отказ. Модель должна была ответить. Она отказалась — вежливо, в идеальном JSON. HTTP-статус: 200.
  • Скачок затрат. Один запрос сгенерировал 32,000 токенов мышления, потому что для простой задачи классификации усилие рассуждения было установлено на «high». HTTP-статус: 200. Ни один дашборд не показывает число токенов мышления.

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

Трёхслойная архитектура

Это не три варианта. Вам нужны все три.

Слой 1: базовый OpenTelemetry. Формат передачи (OTLP), распространение контекста (W3C trace context), конвейер коллектора. Это субстрат — каждый бэкенд наблюдаемости говорит на OTLP. Каждый микросервис излучает спаны OTel. Без этого слоя вы заперты в проприетарном формате вендора. С ним вы можете менять бэкенды без переинструментирования ни одной строки.

Слой 2: семантические конвенции OTel-GenAI. Стандартизированные атрибуты спанов для LLM-операций: gen_ai.system (какой провайдер), gen_ai.request.model (какая версия модели), gen_ai.usage.input_tokens и gen_ai.usage.output_tokens (потребление токенов), gen_ai.operation.name (чат против эмбеддингов против выполнения инструментов). Без этого слоя все ваши LLM-спаны выглядят одинаково — вы не отличите завершение чата от вызова эмбеддингов.

Слой 3: виды спанов OpenInference. Четырнадцать LLM-осведомлённых типов спанов, которые конвенции GenAI ещё не перечисляют: LLM, CHAIN, RETRIEVER, TOOL, EMBEDDING, AGENT, RERANKER, GUARDRAIL, EVALUATOR, CONVERSATION, VECTOR_DB и другие. Без этого слоя трассировка вашего RAG-конвейера — плоский список HTTP-вызовов. С ним вы видите EMBEDDING —RETRIEVER —RERANKER —LLM как отдельные этапы — и точно знаете, какой этап добавил всплеск задержки в 800 мс.

Дерево трассировки как минимальная единица понимания

Изолированная строка лога не может диагностировать проблему LLM. Вопрос никогда не «что вернул этот вызов API?» Он — «какова была полная причинная цепочка: запрос пользователя — классификация намерения — извлечённые чанки — оценки реранкера — финальный промпт — ответ LLM — оценка?» Дерево трассировки захватывает эту цепочку. Одна трассировка = полная криминалистическая запись одного взаимодействия пользователя.

Почему наблюдаемость обязательна

Затраты без видимости

Немониторинговый агентный цикл сжёг $5,000 за выходные на одном развёртывании, которое я исследовал. Агент попал в цикл вызова инструментов в пятницу вечером — search_kb("return policy") вернул «нет результатов», поэтому агент вызвал search_kb("return policy EU"), затем search_kb("return policy Europe"), затем ещё 44 вариации — каждая полный вызов LLM API с контекстом. Никто не заметил до понедельничного биллинг-предупреждения.

Атрибуция затрат на спан — gen_ai.cost.input, gen_ai.cost.output, gen_ai.cost.total, прикреплённые к каждому LLM-спану, — ловит это за минуты, а не дни. Установите предупреждение: если любая отдельная трассировка превышает $2.00 накопленных затрат API, триггерите уведомление. Стоимость инфраструктуры предупреждений меньше одного выходного инцидента.

Атрибуция затрат — слой обнаружения. За слой предотвращения — стратегии кэширования, выбор уровней моделей и пакетную обработку — смотрите наше руководство по стратегиям оптимизации затрат.

Качество без видимости

Миграция модели с GPT-4o на GPT-5.5 выглядела чистой на HTTP-дашбордах. Задержка улучшилась на 15%. Частота ошибок не изменилась. Чего дашборд не показал: новая модель обрабатывала структурированный вывод слегка иначе — null появился в трёх полях, которые никогда раньше не были null. Формат был валидным JSON. Потребляющая его бизнес-логика молча сломалась.

Прикреплённые к спанам оценки захватывают это за пять минут. Ваша оценочная рубрика прогоняется по продакшн-трассировкам непрерывно. Любое падение верности, соблюдения контекста или соответствия формата триггерит предупреждение — до того как пользователи заметят, до накопления тикетов поддержки, до удара по квартальным метрикам качества. За CI/CD-конвейер, запускающий эти оценки перед деплоем, смотрите наше руководство по тестированию и оценке.

Соответствие без видимости

Аудиторы SOC 2 Type II спрашивают: «Покажите полную запись вызовов API для пользователя X за дату Y — какие данные были отправлены, какая модель их обработала, что было возвращено?» Если ваши вызовы LLM API не производят структурированные трассировки с правильной политикой хранения, ответ: «мы не можем». Это не замечание. Это оговорка в аудиторском заключении — гораздо более дорогое слово в отчёте аудита.

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

Как настроить наблюдаемость LLM

Шаг 1: регистрация в один вызов

Один вызов функции в вашем стартовом модуле. Вот и всё.

from fi_instrumentation import register, ProjectType, SemanticConvention

trace_provider = register(
    project_name="checkout_assistant",
    project_type=ProjectType.OBSERVE,
    semantic_convention=SemanticConvention.OPENINFERENCE,
    metadata={"git_sha": "abc123", "environment": "production"},
    batch=True,
)

from openinference.instrumentation.openai import OpenAIInstrumentor
from openinference.instrumentation.langchain import LangChainInstrumentor

OpenAIInstrumentor().instrument(tracer_provider=trace_provider)
LangChainInstrumentor().instrument(tracer_provider=trace_provider)

Параметр semantic_convention — ключевое архитектурное решение здесь. Установите его в OPENINFERENCE, OTEL_GENAI или OPENLLMETRY — ваш инструментирующий код не меняется. Меняется только именование атрибутов на излучаемых спанах. Это важно при смене бэкенда наблюдаемости: Datadog ожидает одну конвенцию, Langfuse другую, SigNoz третью. Один переключатель конфигурации. Без изменений кода.

Покрытие: 50+ Python-фреймворков, 39 TypeScript-пакетов, 24 Java-модуля, C#. OpenAI, Anthropic, LangChain, LlamaIndex, Haystack, DSPy — всё автоматически инструментируется.

Шаг 2: обогащение спанов

Спан без user_id, session_id и prompt_version — сирота. Вы видите, что произошло, но не кому или с какой конфигурацией.

from contextlib import contextmanager

@contextmanager
def using_attributes(**kwargs):
    # Attach attributes to the current span; all child spans inherit them
    with tracer.start_as_current_span("user-interaction") as span:
        for key, value in kwargs.items():
            span.set_attribute(key, value)
        yield span

with using_attributes(
    session_id="sess_a1b2c3",
    user_id="user_42",
    metadata={
        "prompt_template": "checkout_v3.2",
        "ab_bucket": "treatment",
        "feature_flag": "new_upsell_logic"
    }
):
    response = client.chat.completions.create(...)

Минимальный набор атрибутов для каждой трассировки: session.id, user.id, prompt.version, feature.id, tenant.id. Без них ваши трассировочные данные не могут ответить на вопрос «промпт checkout_v3.2 вызвал регресс или смена версии модели?» — первый вопрос, который вы зададите во время инцидента.

Шаг 3: eval-as-span-атрибут

Оценки, живущие в отдельной базе данных и требующие ручного соединения с ID трассировок, — оценки, которые никто не смотрит. EvalTag исправляет это: объявляйте оценщиков при регистрации, и их оценки записываются прямо на исходный спан как атрибуты gen_ai.evaluation.<rubric>.score — с нулевой добавленной задержкой запроса.

register(
    project_name="checkout_assistant",
    evaluators=[
        "GROUNDEDNESS",         # Are claims supported by retrieved context?
        "CONTEXT_ADHERENCE",    # Is the answer using the provided context?
        "PROMPT_INJECTION",     # Is there an injection attempt in the input?
        "TASK_COMPLETION",      # Did the model complete the requested task?
    ],
)

Оценщик выполняется асинхронно — пользователь получает ответ, не дожидаясь оценки. Оценка появляется на спане в течение секунд. Ваш дашборд обновляется. Если GROUNDEDNESS падает ниже 0.7 за 5-минутное окно, срабатывает ваше предупреждение. Без отдельного конвейера оценки. Без ручной корреляции. Одно дерево трассировки, один источник истины.

Шаг 4: хвостовая выборка

Выборка от головы — «сохранять 10% всех трассировок случайно» — дефолт в большинстве APM-настроек. Для LLM-приложений это катастрофически неверно. Сбои редки. Выбросы затрат редки. Низкокачественные выходы редки. Равномерная 10% случайная выборка выбрасывает 90% трассировок, которые действительно важны.

Хвостовая выборка переворачивает это: коллектор видит полную трассировку до решения, сохранять ли её. Правило удержания:

  • Сохранять 100% трассировок с ошибками (5xx, таймаут, лимит запросов)
  • Сохранять 100% трассировок с любой оценкой ниже порога
  • Сохранять 100% трассировок с затратами выше p95
  • Сохранять 1–10% чистых, быстрых, корректных трассировок

Затраты на хранение остаются под контролем. Трассировки, которые действительно нужны для отладки, остаются доступными.

Шаг 5: трёхъярусное удержание

Не платите цены ClickHouse за данные регуляторного соответствия, к которым обращаетесь раз в год.

УровеньХранилищеСрок храненияСодержимое
ГорячийClickHouse / InfluxDB14-30 днейВсе сохранённые трассировки — живые дашборды и алерты
ТёплыйColumnar (S3/Parquet)90 днейПолные трассировки — соответствие и ретроспективная отладка
ХолодныйОбъектное хранилище (S3 Glacier)1-7 летСжатые трассировки — регуляторное хранение

Горячий ярус — для операций. Тёплый — для отладки инцидента прошлого квартала. Холодный — для аудиторов. Каждый ярус стоит примерно на порядок меньше верхнего.

Продакшен-паттерны трассировок для конкретных нагрузок

Топология трассировки RAG

Плоская трассировка RAG-конвейера бесполезна. Вам нужно видеть каждый этап как отдельный спан:

EMBEDDING span [model: text-embedding-3-small, tokens: 450, latency: 32ms]
  → RETRIEVER span [vector_db: pgvector, top_k: 20, index: hnsw, latency: 8ms]
    → RERANKER span [model: bge-reranker-large, candidates: 20→5, latency: 45ms]
      → LLM span [model: gpt-4o, input_tokens: 2840, output_tokens: 380, latency: 1.2s]

Когда качество извлечения падает, вы смотрите на спан RETRIEVER — оценки подобия низкие? Проверьте, не дрейфовала ли модель эмбеддингов. Когда качество генерации падает, но извлечение выглядит нормально, вы смотрите на спан LLM — извлечённые чанки подаются в правильном порядке? Системный промпт невредим? Топология говорит, где смотреть, а не просто что что-то не так. Полную архитектуру RAG-конвейера за этой моделью трассировки смотрите в нашем руководстве по продакшн-RAG.

Топология трассировки агента

6-узловой агент LangGraph, трассированный плоско, — кошмар отладки. Вы видите 100 спанов. Не знаете, какой узел какой инструмент запустил, какой инструмент отказал или где начался цикл.

Правильная топология:

Root: AGENT span [session_id, user_id, task]
  —Reasoning span: "plan to answer user's question about order status"
    —TOOL span: lookup_order(order_id="ORD-12345") [latency: 180ms, status: success]
  —Reasoning span: "order found, now check shipping"
    —TOOL span: track_shipment(tracking_id="ZYX-987") [latency: 340ms, status: success]
  —LLM span: synthesis [model: claude-sonnet-4, input_tokens: 1520, output_tokens: 210]

Для LangGraph конкретно добавляйте langgraph.node.name, langgraph.node.type и события условных рёбер на каждый спан. Без них, когда ваш 6-узловой агент застрянет в цикле, вы не сможете сказать, какой узел — проблема. С ними трассировка отображается как топологический граф — и зацикленный узел визуально очевиден. За паттерны мультиагентной оркестрации, порождающие эти трассировки, смотрите наше руководство по мультиагентной архитектуре.

Атрибуция затрат на спан

Каждый LLM-спан несёт gen_ai.cost.input, gen_ai.cost.output, gen_ai.cost.cache_read и gen_ai.cost.total. На уровне шлюза поддерживайте иерархические бюджеты: организация — команда — пользователь — сессия. Генерируйте ежемесячные разбивки затрат по командам, моделям и сценариям использования автоматически, из трассировочных данных. Без ручной сверки счетов. Без «строка ИИ — чёрный ящик». За настройку троттлинга запросов на пользователя и endpoint для предотвращения раздувания бюджета разбежавшимися агентными циклами смотрите документацию по лимитам запросов.

Ошибки наблюдаемости, дорого обходящиеся в продакшне

Инструментирование только вендорским SDK

Вы инструментировали нативным SDK Datadog, потому что это был самый быстрый путь к дашборду. Через шесть месяцев команда хочет оценить Langfuse для LLM-специфичной трассировки. Каждое место вызова требует переинструментирования.

Исправление: инструментируйте с OTel. Это слой абстракции. Меняйте бэкенды изменением конфигурации экспортёра, а не кода инструментирования. Вендорные SDK — выходные цели, а не фреймворки инструментирования.

Равномерная случайная выборка

Ваша частота выборки — 10%. Вы случайно отбрасываете 90% трассировок — включая ту, где пользователю выставили двойной счёт из-за зацикленного агента, ту, где попытка инъекции промпта почти удалась, и ту, где один запрос потребил $18 токенов мышления.

Исправление: хвостовая выборка. Коллектор видит полную трассировку, затем решает. Сбои, выбросы затрат и низкокачественные выходы: сохранять 100%. Чистые трассировки: сохранять небольшой процент для базового сравнения.

Нет топологии LangGraph в трассировках агента

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

Исправление: langgraph.node.name и langgraph.node.type на каждом спане. События условных рёбер. Ваш просмотрщик трассировок должен отображать агента как граф, а не список.

Пропуск спанов, излучаемых шлюзом

Вы тщательно трассируете код приложения. Но вы обращаетесь к LLM через единую API-платформу — и спаны шлюза (задержка на стороне провайдера, решение о маршрутизации, попадание/промах кэша, триггер резерва) невидимы вашему трассировщику приложения. Когда задержка скачет, вы не можете сказать, ваш ли это код, шлюз или провайдер.

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

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

FAQ

Нужны ли все три слоя (базовый OTel + GenAI + OpenInference)?

Да. Базовый OTel предотвращает привязку к вендору. Семантические конвенции GenAI стандартизируют специфичные для модели атрибуты (числа токенов, идентичность модели), чтобы ваши дашборды не ломались при смене провайдеров. Виды спанов OpenInference дают LLM-осведомлённую топологию — без них каждый спан «вызов API», и вы не можете отличить извлечение от генерации от выполнения инструментов.

Каковы накладные расходы на производительность?

Создание спанов и установка атрибутов: менее 1% влияния на задержку. Оценщики (EvalTag): нулевое влияние на задержку для пользователя — они выполняются асинхронно после отправки ответа. Хвостовая выборка: выполняется в коллекторе, не в процессе вашего приложения. Полные накладные расходы ничтожны по сравнению с задержкой 200 мс–10 с самих вызовов LLM API. Для сокращения входных затрат на повторяющихся трассировках стратегии кэширования промптов естественно сочетаются с атрибуцией затрат на уровне спана.

Самохостинг или SaaS для наблюдаемости?

Самохостинг: SigNoz (OTel-нативный, GenAI-дашборды) + ClickHouse + Grafana. Хорошо, если вы уже запускаете OTel-инфраструктуру. SaaS: Langfuse Cloud (трассировка в первую очередь, настроенный ClickHouse), Datadog LLM Observability. Хорошо, если нужен дашборд за 10 минут. Абстракция OTel означает, что можно начать с SaaS и мигрировать на самохостинг без переинструментирования.

Как удалять PII из LLM-трассировок?

Удаляйте в коллекторе — не в коде приложения. Регулярные выражения для номеров кредитных карт, SSN и адресов электронной почты. NER-классификаторы для имён и физических адресов. Пользовательские правила для API-ключей и токенов доступа. Принцип: сырые секреты никогда не пересекают границу вашей сети. Они удаляются в процессоре коллектора до экспорта в любой внешний бэкенд.

Какой самый простой путь к единой наблюдаемости LLM?

Одна точка интеграции. Когда каждый вызов модели — GPT, Claude, Gemini, DeepSeek — проходит через один API-endpoint, вы настраиваете экспорт OTel один раз. Спаны шлюза (задержка на стороне провайдера, решения о маршрутизации, частоты попаданий в кэш, триггеры резерва) приходят предварительно отформатированными вместе со спанами приложения. Без сшивания трассировок из трёх разных SDK провайдеров. Без гадания, в вашем ли коде скачок задержки, в шлюзе или провайдере, — потому что все три в одном дереве трассировки. Начните с одного API-ключа для всех моделей и увидите единые трассировки на платформе TokSpan.

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

Паттерн регистрации — один вызов функции. Прикрепление eval-as-span — нулевая дополнительная задержка. Хвостовая выборка держит счёт за хранение под контролем, сохраняя каждую важную трассировку. Начните с одной модели в одном сервисе. Инструментируйте её. Смотрите дерево трассировки в течение дня. Вы найдёте что-то, о чём не знали, что происходит, — каждый находит в первый день с настоящей наблюдаемостью.