/
Вопросы и ответы
/
Промт-инжиниринг
/

Как мониторить LLM в продакшне

Как мониторить LLM в продакшне

9 часов назад

Никита Вихров

Ответы

0

Как мониторить LLM в продакшне

Без мониторинга не знаешь: сколько стоит каждый запрос, где модель отвечает плохо, когда деградирует качество. Узнаёшь постфактум — от пользователей или из счёта.


Что мониторить

Четыре метрики которые важны с первого дня в продакшне:

from anthropic import Anthropic
from dataclasses import dataclass, field
from datetime import datetime
import time
import json

client = Anthropic()


@dataclass
class LLMCallMetrics:
    request_id: str
    timestamp: datetime
    model: str
    input_tokens: int
    output_tokens: int
    latency_ms: int
    cost_usd: float
    success: bool
    error_type: str | None = None
    user_id: str | None = None
    feature: str | None = None  # какая фича вызвала запрос


# Стоимость токенов (проверяй актуальные цены на сайте Anthropic)
TOKEN_COSTS = {
    "claude-opus-4-5":   {"input": 15.0,  "output": 75.0},   # $ за 1M токенов
    "claude-sonnet-4-5": {"input": 3.0,   "output": 15.0},
    "claude-haiku-4-5":  {"input": 0.25,  "output": 1.25},
}


def calculate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
    costs = TOKEN_COSTS.get(model, TOKEN_COSTS["claude-opus-4-5"])
    return (input_tokens * costs["input"] + output_tokens * costs["output"]) / 1_000_000

Обёртка с автоматическим логированием

import logging
import uuid

logger = logging.getLogger("llm_monitor")


def monitored_llm_call(
    messages: list,
    model: str = "claude-opus-4-5",
    system: str = None,
    max_tokens: int = 1024,
    user_id: str = None,
    feature: str = None,
) -> tuple[str, LLMCallMetrics]:

    request_id = str(uuid.uuid4())
    start_time = time.time()

    try:
        kwargs = {
            "model": model,
            "max_tokens": max_tokens,
            "messages": messages,
        }
        if system:
            kwargs["system"] = system

        response = client.messages.create(**kwargs)

        latency_ms = int((time.time() - start_time) * 1000)
        cost = calculate_cost(model, response.usage.input_tokens, response.usage.output_tokens)

        metrics = LLMCallMetrics(
            request_id=request_id,
            timestamp=datetime.now(),
            model=model,
            input_tokens=response.usage.input_tokens,
            output_tokens=response.usage.output_tokens,
            latency_ms=latency_ms,
            cost_usd=cost,
            success=True,
            user_id=user_id,
            feature=feature,
        )

        logger.info("llm_call", extra={"metrics": metrics.__dict__})
        return response.content[0].text, metrics

    except Exception as e:
        latency_ms = int((time.time() - start_time) * 1000)

        metrics = LLMCallMetrics(
            request_id=request_id,
            timestamp=datetime.now(),
            model=model,
            input_tokens=0,
            output_tokens=0,
            latency_ms=latency_ms,
            cost_usd=0,
            success=False,
            error_type=type(e).__name__,
            user_id=user_id,
            feature=feature,
        )

        logger.error("llm_call_failed", extra={"metrics": metrics.__dict__, "error": str(e)})
        raise

Агрегация метрик

from collections import defaultdict
from statistics import mean, median


class MetricsAggregator:
    def __init__(self):
        self.calls: list[LLMCallMetrics] = []

    def add(self, metrics: LLMCallMetrics):
        self.calls.append(metrics)

    def report(self, last_n_hours: int = 24) -> dict:
        cutoff = datetime.now().timestamp() - last_n_hours * 3600
        recent = [c for c in self.calls if c.timestamp.timestamp() > cutoff]

        if not recent:
            return {"message": "Нет данных"}

        successful = [c for c in recent if c.success]
        failed = [c for c in recent if not c.success]

        # Группируем по фичам
        by_feature = defaultdict(list)
        for call in recent:
            by_feature[call.feature or "unknown"].append(call)

        return {
            "total_calls": len(recent),
            "success_rate": len(successful) / len(recent),
            "total_cost_usd": sum(c.cost_usd for c in recent),
            "avg_latency_ms": mean(c.latency_ms for c in recent),
            "p95_latency_ms": sorted(c.latency_ms for c in recent)[int(len(recent) * 0.95)],
            "total_input_tokens": sum(c.input_tokens for c in recent),
            "total_output_tokens": sum(c.output_tokens for c in recent),
            "errors": {e: sum(1 for c in failed if c.error_type == e)
                      for e in set(c.error_type for c in failed)},
            "by_feature": {
                feature: {
                    "calls": len(calls),
                    "cost": sum(c.cost_usd for c in calls),
                    "avg_latency_ms": mean(c.latency_ms for c in calls),
                }
                for feature, calls in by_feature.items()
            }
        }


aggregator = MetricsAggregator()

# Использование
text, metrics = monitored_llm_call(
    messages=[{"role": "user", "content": "Привет"}],
    user_id="user_123",
    feature="chat_support"
)
aggregator.add(metrics)

print(json.dumps(aggregator.report(), indent=2))

Алерты на аномалии

def check_anomalies(aggregator: MetricsAggregator) -> list[str]:
    report = aggregator.report(last_n_hours=1)
    alerts = []

    # Высокая стоимость
    if report["total_cost_usd"] > 10:
        alerts.append(f"⚠️ Высокая стоимость за час: ${report['total_cost_usd']:.2f}")

    # Низкий success rate
    if report["success_rate"] < 0.95:
        alerts.append(f"❌ Success rate упал: {report['success_rate']:.1%}")

    # Высокая латентность
    if report["p95_latency_ms"] > 10_000:
        alerts.append(f"🐌 p95 латентность: {report['p95_latency_ms']}ms")

    return alerts


# Запускай раз в 5 минут через cron или celery beat
alerts = check_anomalies(aggregator)
for alert in alerts:
    send_to_slack(alert)  # или email, pagerduty

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

9 часов назад

Никита Вихров

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845