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 / InfluxDB | 14-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 — нулевая дополнительная задержка. Хвостовая выборка держит счёт за хранение под контролем, сохраняя каждую важную трассировку. Начните с одной модели в одном сервисе. Инструментируйте её. Смотрите дерево трассировки в течение дня. Вы найдёте что-то, о чём не знали, что происходит, — каждый находит в первый день с настоящей наблюдаемостью.