AI Engineering Academy · Урок

Оповещения о задержке, расходах и ухудшении качества

Определите пороги оповещений для задержки p99, стоимости запроса и автоматических оценок качества, а при ухудшении конвейера LLM направляйте уведомления в Slack или PagerDuty.

Урок 4 из 413 шагов

«Оповещения о задержке, расходах и ухудшении качества» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.

Зачем нужны оповещения в системах LLM

Приложения LLM могут выходить из строя незаметно и постепенно. Изменение подсказки может увеличить среднюю задержку на 30%, улучшение извлечения данных — немного снизить качество ответа, а резкий рост использования — увеличить ежедневные затраты в 5 раз сверх бюджета. Без упреждающих оповещений Вы обнаружите эти проблемы только после жалоб пользователей или получения ежемесячного счёта. Оповещения превращают реактивное тушение пожаров в упреждающее управление.

Три категории оповещений

Оповещения в приложениях LLM делятся на три категории. Оповещения о задержке срабатывают, когда время ответа превышает порог, допустимый для удобства пользователей (например, p99 > 10 секунд). Оповещения о затратах срабатывают, когда стоимость запроса или ежедневные расходы превышают бюджетные пороги, предотвращая неожиданные счета. Оповещения о качестве срабатывают, когда автоматические метрики качества (оценки LLM в роли судьи, уровень удовлетворённости пользователей, оценки достоверности) опускаются ниже приемлемого минимума. Для каждой категории нужны свои инструменты измерения и каналы оповещения.

from dataclasses import dataclass

@dataclass
class AlertThresholds:
    # Latency (milliseconds)
    p50_latency_ms: int = 2000    # median should be under 2s
    p99_latency_ms: int = 10000   # 99th percentile under 10s
    # Cost (USD)
    max_cost_per_request: float = 0.05  # alert if one request costs > 5 cents
    max_daily_spend: float = 50.00      # alert if daily spend exceeds $50
    # Quality (0.0 to 1.0 scale)
    min_quality_score: float = 0.75     # alert if rolling avg drops below 75%
    min_faithfulness: float = 0.80      # alert if RAGAS faithfulness drops below 80%

DEFAULT_THRESHOLDS = AlertThresholds()

Сбор метрик задержки

Чтобы настраивать оповещения о задержке, сначала необходимо единообразно её собирать. Отдельно измеряйте три компонента задержки: время до первого токена (TTFT, критически важное для потокового взаимодействия), общее время ответа и задержку каждого этапа извлечения данных и генерации. Сохраняйте эти значения как временные ряды (по одной точке данных на запрос), чтобы вычислять процентили и скользящие средние за последние N минут или часов.

import time
from collections import deque
from statistics import quantiles

class LatencyTracker:
    def __init__(self, window_size=100):
        self.window = deque(maxlen=window_size)  # rolling window of latencies

    def record(self, latency_ms: float):
        self.window.append(latency_ms)

    def p50(self) -> float:
        if not self.window:
            return 0
        return quantiles(self.window, n=100)[49]

    def p99(self) -> float:
        if not self.window:
            return 0
        return quantiles(self.window, n=100)[98]

    def check_alert(self, thresholds: AlertThresholds) -> list[str]:
        alerts = []
        if self.p99() > thresholds.p99_latency_ms:
            alerts.append(f'p99 latency {self.p99():.0f}ms exceeds {thresholds.p99_latency_ms}ms')
        if self.p50() > thresholds.p50_latency_ms:
            alerts.append(f'p50 latency {self.p50():.0f}ms exceeds {thresholds.p50_latency_ms}ms')
        return alerts

latency_tracker = LatencyTracker()

Отслеживание затрат и оповещения о них

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

from datetime import datetime, date
import threading

class CostTracker:
    def __init__(self):
        self._lock = threading.Lock()
        self._daily_spend = {}  # date -> total USD
        self._per_request = []  # list of (timestamp, cost)

    def record(self, cost_usd: float) -> list[str]:
        today = date.today().isoformat()
        alerts = []
        
        with self._lock:
            # Track daily spend
            self._daily_spend[today] = self._daily_spend.get(today, 0) + cost_usd
            self._per_request.append((datetime.now(), cost_usd))
        
        # Alert on expensive single requests
        if cost_usd > DEFAULT_THRESHOLDS.max_cost_per_request:
            alerts.append(f'Expensive request: ${cost_usd:.4f} (threshold: ${DEFAULT_THRESHOLDS.max_cost_per_request})')
        
        # Alert on daily budget exceeded
        daily_total = self._daily_spend[today]
        if daily_total > DEFAULT_THRESHOLDS.max_daily_spend:
            alerts.append(f'Daily budget exceeded: ${daily_total:.2f} > ${DEFAULT_THRESHOLDS.max_daily_spend}')
        
        return alerts

cost_tracker = CostTracker()

Оповещения по оценкам качества

Оповещения о качестве — самая сложная категория, поскольку качество нельзя измерить только по инфраструктурным метрикам: требуется смысловая оценка. Наиболее масштабируемый подход — автоматическая оценка выборки: для случайной выборки рабочих запросов (5–10%) асинхронно запускайте оценщика, где LLM выступает в роли судьи, сохраняйте оценки и вычисляйте скользящее среднее. Оповещайте команду, когда скользящее среднее опускается ниже минимально допустимого качества.

import random
from openai import OpenAI

client = OpenAI()

def evaluate_response_quality(question: str, answer: str) -> float:
    judge_prompt = f'''Rate the quality of this AI assistant response on a scale from 0 to 1.

Question: {question}
Answer: {answer}

Return only a JSON object: {{"score": 0.85, "reason": "brief reason"}}

Scoring guide:
1.0 = Perfect, accurate, helpful
0.75 = Good, minor issues
0.5 = Acceptable but incomplete
0.25 = Poor, major gaps
0.0 = Completely wrong or harmful'''
    
    response = client.chat.completions.create(
        model='gpt-4o-mini',  # cheaper model for evaluation
        messages=[{'role': 'user', 'content': judge_prompt}],
        response_format={'type': 'json_object'}
    )
    import json
    result = json.loads(response.choices[0].message.content)
    return float(result['score'])

def maybe_evaluate(question: str, answer: str, sample_rate=0.1):
    if random.random() < sample_rate:
        score = evaluate_response_quality(question, answer)
        quality_tracker.record(score)
        return score
    return None

Скользящие средние для обнаружения тенденций

Один плохой ответ не свидетельствует о системной проблеме. Используйте скользящие средние за определённое временное окно (например, за последний час или по последним 100 запросам), чтобы выявлять тенденции. Если скользящее среднее пересекает порог и остаётся за ним более 10 минут, это указывает на реальную проблему, тогда как кратковременный всплеск может быть шумом. Экспоненциально взвешенные скользящие средние (EWMA) быстрее реагируют на недавние изменения, чем обычные скользящие средние.

class RollingQualityMonitor:
    def __init__(self, window=50, alert_threshold=0.75, ewma_alpha=0.1):
        self.scores = []
        self.window = window
        self.alert_threshold = alert_threshold
        self.alpha = ewma_alpha  # EWMA decay factor
        self.ewma_score = None

    def record(self, score: float):
        self.scores.append(score)
        if len(self.scores) > self.window:
            self.scores.pop(0)
        
        # Update exponentially weighted moving average
        if self.ewma_score is None:
            self.ewma_score = score
        else:
            self.ewma_score = self.alpha * score + (1 - self.alpha) * self.ewma_score

    def should_alert(self) -> bool:
        if len(self.scores) < 10:  # not enough data yet
            return False
        return self.ewma_score < self.alert_threshold

    def summary(self) -> dict:
        if not self.scores:
            return {}
        return {
            'rolling_avg': sum(self.scores) / len(self.scores),
            'ewma': self.ewma_score,
            'sample_count': len(self.scores),
            'alert': self.should_alert()
        }

quality_tracker = RollingQualityMonitor()

Маршрутизация оповещений: Slack и PagerDuty

Оповещения полезны только тогда, когда доходят до нужного человека по нужному каналу. Маршрутизируйте их по степени серьёзности: оповещения об ухудшении качества и превышении бюджета направляйте в канал Slack, который команда отслеживает в рабочее время. Оповещения о задержке выше критических порогов (например, p99 > 30 секунд) и внезапных скачках затрат (например, в 10 раз выше нормы за 5 минут) направляйте в PagerDuty для немедленной передачи дежурному специалисту.

import requests

def send_slack_alert(message: str, severity: str, webhook_url: str):
    color = {'critical': '#ff0000', 'warning': '#ff9900', 'info': '#36a64f'}[severity]
    payload = {
        'attachments': [{
            'color': color,
            'title': f'LLM Alert [{severity.upper()}]',
            'text': message,
            'footer': 'AI Pipeline Monitor'
        }]
    }
    requests.post(webhook_url, json=payload)

def send_pagerduty_alert(title: str, details: str, routing_key: str):
    payload = {
        'routing_key': routing_key,
        'event_action': 'trigger',
        'payload': {
            'summary': title,
            'severity': 'critical',
            'source': 'ai-pipeline-monitor',
            'custom_details': {'details': details}
        }
    }
    requests.post('https://events.pagerduty.com/v2/enqueue', json=payload)

def route_alert(alert_text: str, severity: str):
    send_slack_alert(alert_text, severity, SLACK_WEBHOOK_URL)
    if severity == 'critical':
        send_pagerduty_alert(alert_text, alert_text, PAGERDUTY_ROUTING_KEY)

Цикл проверки оповещений

Логика оповещений должна выполняться в рамках запланированного цикла, а не непосредственно при обработке запросов. Асинхронное выполнение проверок предотвращает добавление задержки к рабочим запросам. Для большинства задач оповещения достаточно фонового потока или запланированной задачи, запускаемой каждые 60 секунд. Собирайте метрики на пути обработки запроса, а пороги проверяйте в фоновом цикле.

import threading
import time

def alert_evaluation_loop(interval_seconds=60):
    while True:
        try:
            # Check latency
            latency_alerts = latency_tracker.check_alert(DEFAULT_THRESHOLDS)
            for alert in latency_alerts:
                route_alert(f'LATENCY: {alert}', 'warning')
            
            # Check quality
            quality_summary = quality_tracker.summary()
            if quality_summary.get('alert'):
                msg = f'QUALITY DEGRADATION: Rolling avg {quality_summary["ewma"]:.2f} below threshold {DEFAULT_THRESHOLDS.min_quality_score}'
                route_alert(msg, 'warning')
            
            print(f'Alert check complete. Quality: {quality_summary.get("ewma", "N/A")}')
        except Exception as e:
            print(f'Alert evaluation error: {e}')
        
        time.sleep(interval_seconds)

# Start in background thread
alert_thread = threading.Thread(target=alert_evaluation_loop, daemon=True)
alert_thread.start()

Утомление от оповещений и настройка порогов

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

class AlertDebouncer:
    def __init__(self, required_consecutive_fires=3):
        self.required = required_consecutive_fires
        self.fire_counts = {}  # alert_name -> consecutive fires
        self.already_firing = set()  # alert_names currently active

    def should_fire(self, alert_name: str, condition: bool) -> bool:
        if condition:
            self.fire_counts[alert_name] = self.fire_counts.get(alert_name, 0) + 1
            if self.fire_counts[alert_name] >= self.required and alert_name not in self.already_firing:
                self.already_firing.add(alert_name)
                return True  # fire the alert (first time condition sustained)
        else:
            if self.fire_counts.get(alert_name, 0) > 0:
                self.fire_counts[alert_name] = 0
                self.already_firing.discard(alert_name)  # condition cleared
        return False  # either not sustained or already firing (no duplicate alert)

debouncer = AlertDebouncer(required_consecutive_fires=3)

Контроль аномалий затрат статистическими методами

Простые пороговые оповещения не обнаруживают сложные аномалии затрат, например постепенное увеличение расходов на 50% за два дня. Используйте статистическое обнаружение аномалий: вычисляйте скользящее среднее и стандартное отклонение почасовых затрат и отправляйте оповещение, когда стоимость текущего часа превышает среднее более чем на N стандартных отклонений (оповещение по Z-оценке). Такой подход автоматически адаптируется к естественным особенностям использования, например к различию трафика в будни и выходные.

import statistics

def compute_z_score(recent_value: float, historical_values: list[float]) -> float:
    if len(historical_values) < 5:
        return 0  # not enough data
    mean = statistics.mean(historical_values)
    stdev = statistics.stdev(historical_values)
    if stdev == 0:
        return 0
    return (recent_value - mean) / stdev

def check_cost_anomaly(current_hour_cost: float, historical_hourly_costs: list[float]) -> str | None:
    z = compute_z_score(current_hour_cost, historical_hourly_costs)
    if z > 3.0:  # more than 3 standard deviations above mean
        mean = statistics.mean(historical_hourly_costs)
        return f'Cost anomaly: ${current_hour_cost:.2f} this hour (normal: ${mean:.2f}, z={z:.1f})'
    return None

Создание операционной панели

Оповещения — это реактивный уровень, а рабочая операционная панель в реальном времени — упреждающий. Создайте простую панель, отображающую метрики в реальном времени: текущую задержку p50/p99, количество запросов в минуту, текущую оценку качества (скользящее EWMA), накопленные за день затраты по сравнению с бюджетом и пять самых дорогих запросов за последний час. Это даёт команде возможность мгновенно оценивать состояние системы, не дожидаясь срабатывания оповещения.

Быстрая проверка

Проверьте своё понимание оповещений по метрикам приложений LLM по материалам этого урока.

Итоги урока

В этом уроке Вы узнали: три категории оповещений охватывают системы LLM — задержка, стоимость и качество — и для каждой требуется своя инструментализация; скользящие средние и EWMA лучше выявляют постепенное ухудшение качества, чем проверки пороговых значений для отдельных запросов; а дебаунсинг оповещений предотвращает усталость от оповещений, требуя, чтобы условия сохранялись в течение нескольких циклов оценки, прежде чем сработает оповещение. Далее мы подробно рассмотрим атаки с внедрением промптов и безопасность ИИ.

Можно начать бесплатно

Изучай Python с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
30
Уроки
120

Часто задаваемые вопросы

Урок «Оповещения о задержке, расходах и ухудшении качества» бесплатный?

Да — полный текст урока «Оповещения о задержке, расходах и ухудшении качества» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.

Чему я научусь в уроке «Оповещения о задержке, расходах и ухудшении качества»?

Определите пороги оповещений для задержки p99, стоимости запроса и автоматических оценок качества, а при ухудшении конвейера LLM направляйте уведомления в Slack или PagerDuty. Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Engineering Academy?

Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Оповещения о задержке, расходах и ухудшении качества»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Engineering Academy?

Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Почему приложения на LLM сложно отлаживать
  2. Трассировка с LangSmith
  3. Langfuse для наблюдаемости, независимой от модели
  4. Оповещения о задержке, расходах и ухудшении качества
← Назад к AI Engineering Academy