Оповещения о задержке, расходах и ухудшении качества
Определите пороги оповещений для задержки p99, стоимости запроса и автоматических оценок качества, а при ухудшении конвейера LLM направляйте уведомления в Slack или PagerDuty.
«Оповещения о задержке, расходах и ухудшении качества» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Почему приложения на LLM сложно отлаживать
- Трассировка с LangSmith
- Langfuse для наблюдаемости, независимой от модели
- Оповещения о задержке, расходах и ухудшении качества