Библиотека · Надёжность: мониторинг, сбои, резервные копии

MLOps для indie — мониторинг, drift, retraining без DevOps команды

Инженер65 минОбновлено: октябрь 2026
88 из 105 в библиотеке

Время: ~25 мин теории + 40 мин практики


Суть урока

MLOps звучит как что-то из корпоративного Netflix с сотней инженеров. На деле — это ответ на один вопрос: "мой AI работает так же хорошо, как неделю назад?" Для indie-разработчика MLOps — это три вещи: следить за стоимостью, следить за качеством, и не сломать то что работает.

🎨 Образ: Ты открыл кофейню. Один раз настроил рецепт эспрессо — и всё. Но через месяц поставщик сменил зерно, давление в машине поплыло, бариста стал чуть иначе темперовать. Кофе "примерно такой же". Клиенты не жалуются открыто — просто перестают приходить. MLOps — это дегустация собственного кофе каждый день.


Ключевые концепции

  • Prompt drift — промпт деградирует со временем без изменений в коде
  • LangSmith — трейсинг и мониторинг вызовов Claude
  • Promptfoo — тестирование промптов на регрессии
  • Cost monitoring — бюджетные алерты и аномалии расходов
  • Версионирование промптов — Git для промптов, не только для кода
  • Baseline metrics — метрики качества для сравнения со временем

Теория

Что такое prompt drift и почему он происходит

Ты написал промпт три месяца назад. Код не менялся. Но вдруг начинаешь замечать: ответы стали длиннее, или менее конкретными, или немного другого тона. Ты ничего не трогал — что случилось?

Причины prompt drift:

  1. Модель сменилась — в коде стоит алиас вместо конкретной версии, или старую модель убрали из API и ты перешёл на новую. Поведение меняется незначительно или заметно. Закрепляй версию модели в коде и следи за списком моделей и выводов из API: Актуальное сейчас.

  2. Изменился контекст — твои пользователи стали писать иначе, вопросы изменились, появились новые паттерны которые промпт не учитывал.

  3. Цепочка агентов сдвинулась — один агент стал выдавать немного другой формат → следующий агент перестал его правильно парсить.

  4. Системный промпт устарел — ты описал контекст который уже не актуален.

🎨 Образ: Навигатор настроен на прошлогодние карты. Дорога та же, но новый торговый центр перекрыл привычный маршрут. GPS уверенно ведёт — а ты стоишь перед забором.

LangSmith: трейсинг вызовов Claude

LangSmith (от LangChain) — это observability платформа для AI. Записывает каждый вызов: входной промпт, ответ, latency, стоимость, метки.

Зачем нужен:

  • Видеть что реально происходит внутри цепочки агентов
  • Сравнивать ответы "сейчас vs неделю назад"
  • Находить дорогие вызовы которые можно оптимизировать
  • Дебажить когда что-то сломалось в production

Интеграция с Anthropic SDK:

python
import anthropic
from langsmith import traceable
from langsmith.wrappers import wrap_anthropic

# wrap_anthropic записывает токены и стоимость каждого вызова
client = wrap_anthropic(anthropic.Anthropic())

@traceable(name="content-generator")
def generate_content(topic: str, style: str) -> str:
    response = client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=1000,
        messages=[{
            "role": "user",
            "content": f"Напиши контент на тему '{topic}' в стиле {style}"
        }]
    )
    return "".join(b.text for b in response.content if b.type == "text")

# Все вызовы автоматически попадают в LangSmith dashboard
result = generate_content("email marketing", "дружелюбный")

Что видишь в дашборде:

  • Каждый вызов с полным промптом и ответом
  • Latency percentiles (p50, p90, p99)
  • Стоимость по каждому эндпоинту
  • Аномалии (вызов занял в 10 раз дольше обычного)

Promptfoo: тестирование промптов

Promptfoo — это CLI-инструмент для запуска regression-тестов на промптах. Работает как unit-tests, только для AI. В марте 2026 Promptfoo купила OpenAI; по заявлению компании, проект остаётся с открытым исходным кодом, но следи за его развитием.

bash
npm install -g promptfoo

Конфигурация теста (promptfooconfig.yaml):

yaml
prompts:
  - "Проанализируй этот отзыв и определи настроение: {{review}}"

providers:
  - anthropic:messages:claude-haiku-4-5

tests:
  - vars:
      review: "Отличный продукт, очень доволен!"
    assert:
      - type: contains
        value: "позитив"
      - type: not-contains
        value: "негатив"
  
  - vars:
      review: "Ужасное качество, больше не куплю"
    assert:
      - type: contains
        value: "негатив"
      - type: llm-rubric
        value: "Ответ должен идентифицировать негативное настроение"
  
  - vars:
      review: "Нормально, ничего особенного"
    assert:
      - type: contains-any
        value: ["нейтрал", "смешанный", "неопределённый"]
bash
# Запустить тесты
promptfoo eval

# Сравнить две версии промпта
promptfoo eval --prompts v1-prompt.txt v2-prompt.txt

CI/CD интеграция:

yaml
# .github/workflows/prompt-tests.yml
name: Prompt Regression Tests

on: [push, pull_request]

jobs:
  test-prompts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: npm install -g promptfoo
      - run: promptfoo eval --no-progress-bar
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

Теперь при каждом пуше прогоняются тесты. Если промпт регрессировал, promptfoo завершается с ошибкой и CI падает.

Cost monitoring: деньги под контролем

Для indie деньги важнее всего. Один баг в промпте может многократно увеличить дневные расходы.

Встроенный мониторинг стоимости:

python
import anthropic
from datetime import datetime, timezone
import json
import os

class CostTracker:
    def __init__(self, daily_budget_usd: float = 20.0):
        self.client = anthropic.Anthropic()
        self.daily_budget = daily_budget_usd
        self.today_cost = 0.0
        self.log_file = "cost_log.jsonl"
    
    # Цены за 1 млн токенов на октябрь 2026. Они меняются: актуальные на странице
    # «Актуальное сейчас» (../actual.html). Другие модели добавь по той же схеме.
    COSTS = {
        "claude-sonnet-5-5": {"input": 2.0, "output": 10.0},
        "claude-haiku-4-5": {"input": 1.0, "output": 5.0},
    }
    
    def track_call(self, model: str, input_tokens: int, output_tokens: int, endpoint: str):
        rates = self.COSTS.get(model, self.COSTS["claude-sonnet-5-5"])
        cost = (input_tokens / 1_000_000 * rates["input"] + 
                output_tokens / 1_000_000 * rates["output"])
        
        self.today_cost += cost
        
        entry = {
            "ts": datetime.now(timezone.utc).isoformat(),
            "model": model,
            "endpoint": endpoint,
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "cost_usd": round(cost, 6)
        }
        
        with open(self.log_file, "a") as f:
            f.write(json.dumps(entry) + "\n")
        
        # Алерт при превышении 80% бюджета
        if self.today_cost > self.daily_budget * 0.8:
            self.send_alert(f"⚠️ Потрачено ${self.today_cost:.2f} из ${self.daily_budget} бюджета")
        
        return cost
    
    def send_alert(self, message: str):
        # Telegram уведомление
        import requests
        requests.post(
            f"https://api.telegram.org/bot{os.environ['TELEGRAM_BOT_TOKEN']}/sendMessage",
            json={"chat_id": os.environ['TELEGRAM_CHAT_ID'], "text": message}
        )

tracker = CostTracker(daily_budget_usd=20.0)

Аномалии расходов:

python
def detect_cost_anomaly(log_file: str, window_days: int = 7):
    """Сравнивает сегодняшние расходы со средним за неделю."""
    import statistics
    
    with open(log_file) as f:
        entries = [json.loads(line) for line in f]
    
    today = datetime.now(timezone.utc).date()
    daily_costs = {}
    
    for entry in entries:
        date = datetime.fromisoformat(entry['ts']).date()
        daily_costs[date] = daily_costs.get(date, 0) + entry['cost_usd']
    
    recent = [cost for date, cost in daily_costs.items() 
              if (today - date).days <= window_days and date != today]
    
    if len(recent) < 3:
        return False
    
    avg = statistics.mean(recent)
    std = statistics.stdev(recent)
    today_cost = daily_costs.get(today, 0)
    
    # Аномалия: сегодня дороже среднего больше чем на 2 стандартных отклонения
    if today_cost > avg + 2 * std:
        return f"🚨 Аномалия! Сегодня: ${today_cost:.2f}, среднее: ${avg:.2f}"
    
    return False

Версионирование промптов как кода

Промпты — это код. Он должен быть в Git с историей изменений.

Структура репозитория:

Код
prompts/
  v1/
    system-prompt.md     # Версия 1.0
    user-template.md
  v2/
    system-prompt.md     # Версия 2.0 — изменили тон
    user-template.md
  current -> v2/         # Симлинк на текущую версию
  CHANGELOG.md           # Что изменилось и почему

CHANGELOG.md для промптов:

markdown
## v2.0 — 2026-04-15

### Изменения
- Убрал инструкцию "будь кратким" — ответы были слишком короткими для деловых писем
- Добавил примеры форматирования
- Изменил тон с формального на "дружелюбно-профессиональный"

### Метрики до/после (promptfoo)
- Средняя длина ответа: 150 → 280 слов ✅
- Тест "содержит приветствие": 60% → 95% ✅
- Тест "нет слова 'Здравствуйте'": 100% (всё равно) ✅

### Откат
git checkout v1/ если v2 окажется хуже

Практика

Шаг 1: Установить инструменты

bash
# Promptfoo для тестирования промптов
npm install -g promptfoo

# LangSmith (для трейсинга)
pip install langsmith

# Переменные окружения
export LANGSMITH_TRACING="true"   # без этого трейсинг не включается
export LANGSMITH_API_KEY="ls__xxx"
export LANGSMITH_PROJECT="my-ai-app"

Шаг 2: Написать первые тесты промпта

Создай файл promptfooconfig.yaml для своего основного промпта. Придумай 5-10 тестовых случаев — включая граничные: пустой ввод, очень длинный текст, текст на разных языках, потенциально вредоносный запрос.

bash
promptfoo eval
# Смотри что прошло, что упало

Шаг 3: Настроить cost tracking

python
# cost_tracker.py
# Скопируй код из урока, замени TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID
# Добавь вызов tracker.track_call() после каждого messages.create()

Шаг 4: Базовый мониторинг

Запускай раз в день:

bash
# Итог за день
python -c "
import json
from datetime import datetime, timezone

today = datetime.now(timezone.utc).date().isoformat()
total = 0
calls = 0

with open('cost_log.jsonl') as f:
    for line in f:
        e = json.loads(line)
        if e['ts'].startswith(today):
            total += e['cost_usd']
            calls += 1

print(f'Сегодня: {calls} вызовов, \${total:.4f}')
"

Шаг 5: Задание

  1. Возьми любой промпт из своего проекта
  2. Напиши 10 тестов в promptfoo
  3. Запусти тесты — зафиксируй baseline (сколько прошло)
  4. Внеси небольшое изменение в промпт
  5. Запусти снова — улучшилось или ухудшилось?
  6. Закоммить обе версии в Git с описанием изменений

Инструменты и ресурсы

  • LangSmith: smith.langchain.com (есть бесплатный тариф, лимиты смотри на сайте)
  • Promptfoo: promptfoo.dev — npm install -g promptfoo
  • Helicone — альтернатива LangSmith: helicone.ai (в марте 2026 компанию купила Mintlify, сервис работает в режиме поддержки без новых функций; для нового проекта лучше выбрать другой инструмент)
  • Braintrust — ещё одна платформа оценки и мониторинга: braintrust.dev
  • Weights & Biases — enterprise MLOps: wandb.ai

Ключевые выводы

MLOps для indie — это не DevOps, это дисциплина. Три правила: тестируй промпты перед деплоем (Promptfoo), следи за расходами с алертами (cost tracker), логируй всё в LangSmith чтобы было куда смотреть когда что-то пойдёт не так. Prompt drift — тихий убийца качества. Без тестов ты его не заметишь, пока клиенты не начнут уходить.


Следующий урок

→ Production Observability — что мониторить когда агент в проде

Отметка хранится только в этом браузере и никуда не отправляется. Мой прогресс