1. Зачем вообще нужны метрики и SLO в ChatGPT App
Представьте два состояния команды.
В первом все живут по принципу «кажется, работает». Пока пользователи не жалуются в поддержку и не пишут гневные твиты — всё окей. Время от времени кто‑то заходит на логи, листает «портянку» из строк, кивает и закрывает вкладку.
Во втором у команды есть несколько простых панелей:
- p95 латентности основного MCP‑инструмента.
- error‑rate по MCP и по checkout.
- доступность вебхуков.
- конверсия в оплату по воронке.
И есть 3–5 SLO: «95% вызовов инструмента recommend_gifts — быстрее 2 секунд», «доля ошибок MCP‑tools < 1%», «доступность checkout ≥ 99.5%», «конверсия от виджета до успешной оплаты ≥ 10%».
Во втором варианте вы:
- быстро понимаете, что с приложением что‑то не так, даже до жалоб;
- можете измерить эффект любого изменения (новый алгоритм рекомендаций, миграция SDK, новый тариф провайдера);
- и к моменту, когда вы дойдёте до модуля про Store и ревью, сможете честно сказать: «у нас есть критерии качества, и мы знаем, насколько им соответствуем».
Мы будем говорить об очень очевидных вещах: что такое p95, чем он лучше среднего, как считать error‑rate и availability, какие метрики важны для commerce‑части с вебхуками и как из всего этого собрать простые, но полезные SLO. Разбирать всё это будем на примере нашего учебного приложения GiftGenius — commerce‑сценария с MCP‑инструментом рекомендаций, checkout’ом и вебхуками оплаты.
2. Базовые метрики для ChatGPT App: что именно мерять
Латентность: p50/p95/p99 вместо «среднего по больнице»
Латентность — это время от начала операции до её логического конца. В нашем стеке таких операций несколько:
- вызов MCP‑инструмента recommend_gifts (подбор подарков);
- вызов инструмента, создающего checkout intent в ACP;
- обработка вебхука об успешной оплате.
Важно понимать, что общую задержку, которую видит пользователь в ChatGPT, мы контролируем лишь частично. Есть вклад платформы (инференс модели, сетевые задержки OpenAI), а есть наш вклад — инструменты, бэкенд, БД, платёжка. Для SLI (Service Level Indicator — измеримый показатель качества сервиса) по латентности обычно меряют как раз наш кусок: от входа запроса в App/MCP до готового ответа нашего сервера.
Среднее время ответа здесь обманчиво. Если 90% запросов приходят за 100 мс, а 10% — за 5 секунд, среднее будет около полсекунды, и график вроде бы «зелёный». Но каждый десятый пользователь видит фриз в 5 секунд — UX ощутимо страдает.
Поэтому принято использовать перцентили: p50 (медиана), p95, p99. p95 — это такая граница, ниже которой укладываются 95% запросов. Как подчёркивают SRE‑шные гайды, перцентили «не дают выбросам спрятаться в среднем значении» и делают видимой жизнь тех самых несчастных 5–10% пользователей, которые всё время попадают в хвост распределения.
Проще всего думать так: p50 говорит, как живёт «средний» пользователь, а p95/p99 — насколько больно приходится терпеливым людям, у которых всё время «что‑то тормозит».
Error‑rate: доля неуспешных запросов
Error‑rate — это отношение количества неуспешных запросов к общему числу запросов за период. Основным источником данных обычно являются либо структурированные логи, либо метрики с label’ами status="success" | "error".
В нашем стеке есть несколько естественных error‑rate:
- на уровне MCP‑tools: доля вызовов recommend_gifts, закончившихся ошибкой (исключение, HTTP 5xx внешнего API, timeout);
- на уровне ACP/checkout: доля неуспешных попыток оформления заказа (ошибка API, недоступность платёжки);
- на уровне webhook‑обработчика: доля вебхуков, по которым обработка завершилась неуспешно.
Хитрый момент: в ChatGPT App бывают «тихие» ошибки. Например, MCP‑инструмент вернул ошибку, модель извинилась и продолжила диалог, не пробрасывая техническую ошибку в заголовок. Формально ChatGPT показывает пользователю человеческий ответ, но с точки зрения надёжности ваш стэк дал сбой. Поэтому для error‑rate важно считать именно инженерные статусы, а не «оконечный UX‑ответ GPT».
Availability: доступность сервисов
Availability — это процент запросов, успешно обслуженных за период. Концептуально это тот же error‑rate, только с обратным знаком: «сколько успешных», а не «сколько неуспешных». Классический пример: availability = successful_requests / total_requests * 100%.
Применительно к нашему стеку:
- доступность MCP‑сервера: сколько JSON‑RPC вызовов от ChatGPT завершилось корректным ответом за последний час;
- доступность checkout‑API: какая доля обращений к /api/checkout закончилась HTTP 2xx;
- доступность webhook‑эндпоинта: какая доля входящих вебхуков от платёжки получила от нас корректный ответ (обычно HTTP 200).
Если в документации платёжного провайдера им обещана, скажем, доступность 99.9%, а у вас — 97%, проблема, скорее всего, не у них.
Commerce‑метрики: конверсия и воронка
GiftGenius — commerce‑сценарий. Тут важна не только техничка (ответы быстрые, ошибок мало), но и бизнес‑результат — насколько часто рекомендации превращаются в оплаченные заказы.
Здесь в игру вступает конверсионная воронка:
- пользователи, у которых показан виджет (view);
- пользователи, которые выбрали подарок (selection);
- пользователи, которые нажали на «оформить заказ» (checkout started);
- пользователи, у которых статус заказа стал «оплачен» (paid).
Из этой воронки можно определить конверсию «от виджета до оплаты», и это тоже SLI — измеримый показатель качества сервиса. Например, «конверсия успешных оплат / пользователи, увидевшие виджет за сутки, = 7%». Если внезапно latency вырос, или checkout иногда падает по таймауту, воронка начнёт сужаться в неожиданных местах.
Если вы используете Instant Checkout
Все описанные commerce‑метрики так же применимы и к Instant Checkout на базе Agentic Commerce Protocol. В этом случае вместо «своего» /api/checkout у вас есть стандартизированный Agentic Checkout API: ChatGPT вызывает ваши REST‑эндпоинты POST /checkout_sessions, POST /checkout_sessions/{id}, POST /checkout_sessions/{id}/complete (а опционально — cancel и GET), каждый раз получая от вас «истинное» состояние корзины и чекаута.
Для метрик это ничего не меняет: p95 латентности, error-rate и availability вы будете считать не по произвольному /api/checkout, а по этим стандартным endpoint’ам. SLO вида «p95 латентности checkout < 3 секунд, error-rate < 2%» просто применяются к checkout_sessions‑вызовам, а не к самодельному API.
Отдельный мир: метрики вебхуков
Вебхуки — это асинхронные события (например, payment_succeeded от платёжки), которые двигают заказ по жизненному циклу. Если вебхуки обрабатываются плохо, приложение может красиво рекомендовать подарки, но заказы будут «зависать» со статусом «ожидание оплаты» или в каком‑то неопределённом состоянии.
Если вы интегрируетесь не напрямую со Stripe/платёжкой, а через Instant Checkout, роль «вебхуков платёжки» берут на себя order events из Agentic Checkout: ваш бэкенд шлёт в OpenAI события вроде order.created и order.updated на выделенный webhook‑URL. Это ровно тот же класс сущностей: асинхронные события, от которых зависит финальный статус заказа, просто адресатом является не ваш фронтенд, а ChatGPT.
Соответственно, те же самые метрики — success rate, latency, error-rate — вы будете считать уже не по Stripe‑webhook’ам, а по своим order‑event вебхукам в сторону OpenAI. В SLO можно прямо написать «не менее 99% order.* событий доставляются и успешно обрабатываются за 7 дней, p95 задержки обработки < 500 мс» — и это будет корректным SLI/SLO именно для Instant Checkout.
Для вебхуков обычно смотрят:
- webhook success rate — доля вебхуков, по которым бизнес‑логика завершилась успешно (включая ретраи);
- webhook latency — время от входа вебхука до окончания обработки;
- webhook error rate — доля случаев, когда обработка вебхука закончилась ошибкой (валидация не прошла, БД недоступна, таймаут и так далее).
Например, можно задать SLO: «не менее 99% вебхуков обрабатываются успешно за 7 дней» и «p95 задержки обработки вебхука < 500 мс».
3. Как технически измерять метрики в GiftGenius
Переходим от теории к коду. Нам нужно понять, куда встраивать измерения, чтобы потом агрегировать данные в p95, error‑rate и другие метрики.
Чтобы не тащить в лекцию конкретный Prometheus или Datadog, будем считать, что у нас есть простая функция logMetric или логирование JSON‑событий. Уже поверх этого любая система наблюдаемости соберёт нужные графики.
Измеряем латентность MCP‑инструмента
Допустим, у нас есть MCP‑сервер GiftGenius на TypeScript, и инструмент recommend_gifts. Оборачиваем бизнес‑логику таймером:
// mcp/tools/recommendGifts.ts
import { logMetric } from "../observability/metrics"; // условный helper
export async function recommendGiftsTool(input: RecommendInput) {
const startedAt = performance.now(); // время начала
try {
const result = await recommendGifts(input); // бизнес-логика
const duration = performance.now() - startedAt;
logMetric("tool_latency_ms", duration, {
tool: "recommend_gifts",
status: "success",
});
return result;
} catch (error) {
const duration = performance.now() - startedAt;
logMetric("tool_latency_ms", duration, {
tool: "recommend_gifts",
status: "error",
error_type: "exception",
});
throw error;
}
}
Здесь logMetric может просто писать JSON‑лог в stdout:
// observability/metrics.ts
export function logMetric(
name: string,
value: number,
labels: Record<string, string | number>
) {
// В реальности тут будет клиент Prometheus/DataDog
console.log(
JSON.stringify({
type: "metric",
name,
value,
labels,
timestamp: new Date().toISOString(),
})
);
}
С таким подходом вы получаете поток событий tool_latency_ms, где уже по label’ам tool и status можно считать p95 только по успешным вызовам или, наоборот, смотреть, как долго живут запросы, заканчивающиеся ошибкой.
Подсчитываем error‑rate
Похожим образом можно логировать отдельную метрику ошибок:
// внутри того же обработчика инструмента
logMetric("tool_error_total", 1, {
tool: "recommend_gifts",
error_type: "external_api_timeout",
});
А для успешных запросов:
logMetric("tool_success_total", 1, {
tool: "recommend_gifts",
});
Дальше в системе метрик вы строите error_rate = tool_error_total / (tool_error_total + tool_success_total) за период. Это уже агрегируется на стороне метрик‑системы; в приложении важно лишь аккуратно выбрасывать события.
Если хочется совсем минимализма, можно даже обойтись логами без отдельной error_total. Тогда error‑rate считается по полю status.
Метрики checkout/ACP
Для checkout‑эндпоинта на Next.js логика та же: оборачиваем обработчик таймером и считаем статусы.
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from "next/server";
import { logMetric } from "@/observability/metrics";
export async function POST(req: NextRequest) {
const startedAt = performance.now();
try {
const body = await req.json();
const result = await createCheckoutSession(body); // вызов ACP/Stripe
const duration = performance.now() - startedAt;
logMetric("checkout_latency_ms", duration, { status: "success" });
logMetric("checkout_total", 1, { status: "success" });
return NextResponse.json(result, { status: 200 });
} catch (error) {
const duration = performance.now() - startedAt;
logMetric("checkout_latency_ms", duration, { status: "error" });
logMetric("checkout_total", 1, { status: "error" });
return NextResponse.json(
{ error: "Checkout failed" },
{ status: 500 }
);
}
}
Теперь уже можно смотреть p95 по checkout_latency_ms и error‑rate по checkout_total. На основе этих SLI легко задать SLO вида «p95 < 3 секунд, error‑rate < 2%».
Метрики вебхуков
И, конечно, вебхуки. Мы уже обсудили, какие метрики нам важны (см. раздел 2), теперь посмотрим, как именно их снимать в коде. Там особенно важно не только время, но и факт успешной обработки: иначе пользователь заплатил, а заказ так и не перешёл в «paid».
// app/api/webhooks/payment/route.ts
import { NextRequest, NextResponse } from "next/server";
import { logMetric } from "@/observability/metrics";
export async function POST(req: NextRequest) {
const startedAt = performance.now();
try {
const payload = await req.text(); // сырые данные
const sig = req.headers.get("stripe-signature") || "";
const event = verifyStripeSignature(payload, sig); // валидация
await handlePaymentEvent(event); // обновляем заказ
const duration = performance.now() - startedAt;
logMetric("webhook_latency_ms", duration, {
type: event.type,
status: "success",
});
logMetric("webhook_total", 1, {
type: event.type,
status: "success",
});
return new NextResponse("ok", { status: 200 });
} catch (error) {
const duration = performance.now() - startedAt;
logMetric("webhook_latency_ms", duration, {
type: "unknown",
status: "error",
});
logMetric("webhook_total", 1, {
type: "unknown",
status: "error",
});
return new NextResponse("error", { status: 500 });
}
}
На базе этих событий можно задать SLI:
- webhook success rate = success / (success + error);
- p95 webhook_latency_ms для payment_succeeded.
И затем прописать для них SLO, например «99% вебхуков успешно обрабатываются за 7 дней, p95 < 500 мс».
4. Процентильная статистика: чуть глубже
Мы уже несколько раз опирались на p50/p95/p99, давайте теперь чуть формальнее посмотрим, что именно под ними понимается. В реальной жизни перцентили будет считать система метрик, но полезно понимать, что там вообще происходит.
Перцентиль pX — это значение, ниже которого лежит X процентов измерений. Если вы отсортируете массив латентностей по возрастанию, p95 будет где‑то в «хвосте», ближе к максимальным значениям. В коде (например, на Node, если вдруг вам захотелось локально посчитать p95 для набора значений) это может выглядеть примерно так:
// простая функция расчёта перцентиля
export function percentile(values: number[], p: number): number {
if (values.length === 0) return 0;
const sorted = [...values].sort((a, b) => a - b);
const index = Math.ceil((p / 100) * sorted.length) - 1;
return sorted[Math.max(0, Math.min(index, sorted.length - 1))];
}
Такую функцию можно использовать в тестах или в простом скрипте, который поиграет с данными и покажет, насколько p95 отличается от среднего. В проде же этот функционал возьмут на себя Prometheus, Datadog, New Relic и другие взрослые ребята.
5. SLI, SLO и SLA: человеческим языком
Три буквы, которые кажутся страшными, но на деле очень простые.
SLI — Service Level Indicator
SLI — это конкретный измеряемый показатель, формула. Например:
- p95(latency) инструмента recommend_gifts за последние 24 часа;
- error‑rate MCP‑инструментов за последние 7 дней;
- конверсия виджет → успешная оплата за неделю.
SLI — это не цель и не обещание, это просто «термометр».
SLO — Service Level Objective
SLO — это цель для SLI, по сути условие «здоровья» сервиса. Например:
- «p95(latency recommend_gifts) < 2 секунд за 7‑дневное окно»;
- «error‑rate всех MCP‑tools < 1% за 30‑дневное окно»;
- «доступность checkout API ≥ 99.5% за квартал»;
- «конверсия от виджета до успешной оплаты ≥ 15% за месяц».
Хорошая практика — сначала измерить текущий уровень и поставить SLO чуть строже, чем то, что уже умеете выдерживать, иначе это превратится либо в «миссию невыполнимую», либо в «и так норм».
SLA — Service Level Agreement
SLA — это уже не внутренняя цель, а внешний договор с пользователями или партнёрами. В SLA прописывают обязательства (например, «99.9% uptime») и последствия нарушения (компенсация, штрафы, продление подписки и так далее). SLO часто строже SLA, чтобы было пространство на «ошибочный бюджет».
В рамках учебного GiftGenius вам SLA, скорее всего, не нужен, но важно понимать лесенку:
SLI → SLO → SLA
метрика → цель → внешнее обещание
Error budget (бюджет ошибок)
Error budget — это, упрощённо, допустимый объём «неидеальности». Если у вас SLO «доступность 99.9% за 30 дней», то 0.1% — это бюджет ошибок, который можно «проесть» на:
- плановые релизы с даунтаймом;
- эксперименты;
- непредвиденные инциденты.
Когда бюджет «сгорел» (например, по факту за месяц доступность вышла 99.5%), пора тормозить новые фичи и чинить стабильность.
6. Особые метрики для GiftGenius: commerce + webhooks
Соберём всё вместе и посмотрим на GiftGenius как на цельный продукт.
Воронка и конверсия
Представим простой путь пользователя:
flowchart TD A[Открыл App с виджетом GiftGenius] --> B[Получил рекомендации] B --> C[Выбрал подарок] C --> D[Нажал 'перейти к оплате'] D --> E[Успешный платеж]
Для каждого шага мы можем логировать событие:
logMetric("funnel_step_total", 1, {
step: "widget_view",
});
logMetric("funnel_step_total", 1, {
step: "gift_selected",
});
logMetric("funnel_step_total", 1, {
step: "checkout_started",
});
logMetric("funnel_step_total", 1, {
step: "checkout_paid",
});
Дальше, зная количество событий на шаге widget_view и checkout_paid, мы можем посчитать конверсию: paid / view * 100%. И поставить SLO: «конверсия ≥ 10%». Если внезапно конверсия упала до 3%, а технические SLI (latency/error‑rate) выглядят нормально, возможно, дело в UX, инструкции для модели или в качестве продуктового фида, но не в инфраструктуре.
Метрики вебхуков как часть конверсии
Мы уже говорили про технические метрики вебхуков и их реализацию. Теперь важно связать их с конверсией: для commerce‑сценариев вебхуки критичны. Пользователь видит «оплата прошла», но финальный статус заказа зависит от того, как быстро ваш backend примет и обработает payment_succeeded.
SLI по вебхукам:
- webhook success rate;
- webhook latency p95;
- грубая доступность webhook_endpoint_availability.
Если success rate по вебхукам падает, вы буквально теряете заказы.
7. Простые алерты поверх SLO
Полноценная система алертинга — тема ближе к операционным модулям, но уже сейчас можно наметить базу.
Идея в том, чтобы слать алерты не на каждую отдельную ошибку, а именно на нарушение SLO. Это то, что часто называют symptom‑based alerts — алерты, завязанные на симптомы деградации сервиса: вас интересует не «произошла ошибка», а «сервис начал системно работать хуже, чем обещано».
Типичные примеры:
- error‑rate MCP‑tools за последние 5 минут > 2% при SLO < 1%;
- p95(latency recommend_gifts) за 10 минут > 3 секунд при SLO 2 секунды;
- за последние 15 минут нет ни одного успешного checkout_paid — подозрение на падение интеграции;
- webhook success rate за 10 минут < 95%.
Текст алерта должен быть человеческим, а не «Alert: metric 123 > 456». Пример «уведомления в Slack» для GiftGenius:
[ALERT][GiftGenius] High error rate on MCP tools:
error_rate = 3.2% (>1% SLO) for last 5 minutes.
Impact: part of users cannot receive gift recommendations.
Actions: check MCP logs and external gift API health.
Такие сообщения связаны с SLO и помогают on‑call разработчику быстро понять масштаб бедствия.
8. Мини‑практика: придумываем SLO для GiftGenius
Попробуем сформулировать набор SLO для нашего приложения. Это можно сделать даже на бумаге, без развёрнутой метрик‑системы.
Пример «старта»:
- Для основного MCP‑инструмента recommend_gifts:
- SLI: p95(latency) успешных вызовов за последние 7 дней.
- SLO: p95(latency) < 2 секунд.
- Для ошибок MCP‑инструментов:
- SLI: error‑rate = errors / (errors + successes) за 30 дней.
- SLO: error‑rate < 1%.
- Для checkout API:
- SLI: availability = доля HTTP 2xx от всех запросов.
- SLO: availability ≥ 99.5% за месяц.
- Для вебхуков оплаты:
- SLI: webhook success rate.
- SLO: не менее 99% успешной обработки webhooks за 7 дней; p95 задержки обработки < 500 мс.
- Для бизнес‑результата:
- SLI: конверсия виджет → оплаченный заказ за месяц.
- SLO: конверсия ≥ 10–15% (в зависимости от ниши).
Даже такой небольшой набор уже даёт «каркас» для принятия решений: если вы выкатываете новую версию модели, меняете промпт или алгоритм рекомендаций и видите, что p95 и error‑rate остались в пределах SLO, а конверсия выросла — вероятно, эксперимент удался.
Если вместо собственного checkout‑эндпоинта вы используете Instant Checkout, то «checkout API» это фактически вызовы Agentic Checkout (/checkout_sessions create/update/complete), а «вебхуки оплаты» — ваши order events (order.created, order.updated) в сторону OpenAI. Метрики и формулировки SLO остаются теми же, меняется только конкретный протокол.
9. Типичные ошибки при работе с метриками и SLO
Ошибка №1: измерять только среднее время ответа.
Среднее значение удобно, но опасно. Оно легко маскирует хвост из медленных запросов. В ChatGPT App это критично: даже если большинство пользователей всё получают быстро, регулярные 5–10‑секундные лаги для небольшого процента людей будут ощущаться как «приложение всё время тормозит». Поэтому для латентности всегда используйте хотя бы p50 и p95, а не только mean.
Ошибка №2: путать SLI, SLO и SLA в голове и в документации.
Иногда разработчики пишут «наш SLA — p95 < 2 секунд», при том что это вообще нигде не зафиксировано для внешних пользователей и не сопровождается никакими обязательствами. На самом деле это SLO. SLA — это уже договор с клиентами, где есть последствия нарушений. Если всё смешать, будет непонятно, что вы реально кому обещаете.
Ошибка №3: отсутствие связи между алертами и SLO.
Классическая ловушка — настроить алерты «на каждую ошибку», на каждый таймаут, на любое отклонение метрики на 0.01. В результате on‑call инженер живёт в аду нотификаций и перестаёт на них смотреть. Гораздо полезнее строить алерты на нарушении SLO: если error‑rate вырос существенно сверх целевого уровня или p95 «вывалился» за границы, вот тогда уже есть смысл будить людей.
Ошибка №4: игнорирование метрик вебхуков и асинхронных частей.
Отдельно вынесенная, но уже знакомая нам проблема — игнорирование метрик вебхуков и асинхронных частей. Многие команды следят только за HTTP‑ответами основных API, но не смотрят на вебхуки и фоновую обработку. В commerce‑сценариях именно там прячутся самые неприятные баги: оплата прошла, вебхук не обработался, заказ «застрял». Без метрик success rate и latency по вебхукам можно долго думать, что «всё работает», пока цифры в бухгалтерии и в базе не начнут сильно расходиться.
Ошибка №5: нереалистичные SLO или их полное отсутствие.
Иногда SLO формулируют в стиле «100% uptime» или «никаких ошибок вообще». На практике это невыполнимо и демотивирует команду. Вторая крайность — не формулировать SLO вообще и жить в режиме «вроде норм». Золотая середина — начать с измерения текущих SLI, поставить чуть более строгую, но достижимую цель и постепенно ужесточать её по мере взросления системы.
Ошибка №6: метрики только ради галочки, без привязки к продукту.
Бывает и так: в системе метрик полно графиков, p95, p99, десятки дашбордов, но никто не может ответить на простой вопрос: «Если эта линия подскочит — что пострадает? Пользователь? Деньги? Репутация?» Особенно в LLM‑продуктах важно связать технические показатели (latency, error‑rate) с бизнес‑метриками (конверсия, успешные оплаты). Иначе наблюдаемость превращается в красивый, но бесполезный телевизор.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ