Забудьте про мониторинг: как создать самоисцеляющийся микросервис, который сам фиксит свои баги
Пошаговое руководство по развертыванию автономного сервиса на стеке Ray Serve + OpenAPI + Агентский LLM. Рабочие промпты, конфигурации YAML и код для production-окружения 2026 года.
1. Архитектурный обзор: от спецификации к самоисцелению
Традиционный подход (OpenAPI → ручная реализация → деплой → мониторинг → реакция инженера) умирает. Вместо этого мы строим пайплайн, где LLM-агент становится частью runtime-окружения:
- OpenAPI-спецификация — единственный источник истины о контрактах.
- Генерация кода — Llama 3.1/Mistral Large создаёт сервис с обработкой ошибок на основе спецификации.
- Ray Serve — развёртывание с pipeline-параллелизмом и автоскейлингом.
- Агент самоисцеления — парсит логи, детектирует аномалии, генерирует патч и применяет его без downtime.
- A/B тестирование стратегий — сравнение Instant Rollback vs In-Place Fix по метрике MTTR.
"Ключевой инсайт 2025 года: самоисцеление ≠ автономное исправление. Это цикл Detect → Diagnose → Patch → Verify с человеческим в loop только для стратегических решений. MTTR сокращается с часов до минут, но false-positive rate должен оставаться <0.1%."
2. Установка окружения: Ray, BentoML и зависимости
Используем Python 3.11+ и изолируем окружение через uv (альтернатива pipenv/poetry):
Файл pyproject.toml
[project]
name = "self-healing-service"
version = "0.1.0"
dependencies = [
"ray[serve]>=2.40.0",
"bentoml>=1.2.0",
"fastapi>=0.110.0",
"pydantic>=2.0",
"openapi-spec-validator>=0.7.0",
"httpx>=0.27.0",
"prometheus-client>=0.20.0",
"langchain>=0.2.0", # для LLM-агента
"ollama>=0.3.0", # локальный Llama 3.1
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Установка и проверка
# Инициализация
uv venv .venv
source .venv/bin/activate # Linux/Mac
# или .venv\Scripts\activate # Windows
# Установка
uv pip install -e .
# Проверка Ray
ray start --head --port=6379
ray status
# Запуск BentoML (для сборки контейнеров)
bentoml --version
* Для production используйте Docker-образ с предустановленным Ray и BentoML.
Настройте ray start --head --dashboard-host=0.0.0.0 для доступа к Dashboard.
3. OpenAPI спецификация: контракты как код
Спецификация должна быть валидной и содержать примеры ошибок. Ray Serve умеет автоматически генерировать FastAPI-сервер из OpenAPI, но нам нужна кастомизация.
Файл api/spec.yaml
openapi: 3.1.0
info:
title: Self-Healing Payment Service
version: 1.0.0
description: |
Пример сервиса с самоисцелением. Обрабатывает платежи и генерирует
искусственные ошибки для демонстрации агента.
paths:
/process:
post:
summary: Обработка платежа
operationId: process_payment
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentRequest'
responses:
'200':
description: Успех
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentResponse'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalError'
components:
schemas:
PaymentRequest:
type: object
required: [amount, currency]
properties:
amount:
type: number
example: 100.50
currency:
type: string
example: "USD"
fail_with:
type: string
enum: [none, division, timeout]
default: none
description: |
Искусственная ошибка для тестирования агента:
- division: ZeroDivisionError
- timeout: asyncio.TimeoutError
PaymentResponse:
type: object
properties:
transaction_id:
type: string
status:
type: string
enum: [completed, failed]
responses:
BadRequest:
description: Некорректные данные
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: "Invalid currency"
InternalError:
description: Внутренняя ошибка сервера
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: "Division by zero"
traceback:
type: string
example: "Traceback (most recent call last): ..."
* Поле fail_with — это "дырявый" контракт для инъекции ошибок. В production его не будет, но агент должен уметь обрабатывать такие исключения.
4. Генерация кода через LLM: промпты, которые работают
Не генерируйте весь сервис целиком. Разбейте на микро-задачи.
Пример промпта для генерации обработчика ZeroDivisionError:
Промпт для Claude 3.5 Sonnet / Llama 3.1 (через Ollama)
Ты — senior Python-разработчик. Напиши обработчик исключения для FastAPI эндпоинта.
Контекст:
- Эндпоинт: POST /process
- Вход: JSON с полями amount (float) и fail_with (str)
- Логика:
if fail_with == "division":
result = 100 / 0 # искусственная ошибка
else:
result = amount * 0.9 # комиссия 10%
Требования:
1. Перехвати ZeroDivisionError и верни 500 с JSON: {"error": "Division by zero", "traceback": "..."}.
2. traceback должен содержать только последний фрейм (используй traceback.extract_tb()).
3. Добавь structured logging в JSON-формате: {"event": "error", "error_type": "...", "path": "/process"}.
4. Код должен соответствовать PEP8, использовать type hints.
Пример вывода:
{
"error": "Division by zero",
"traceback": "ZeroDivisionError: division by zero"
}
Не пиши полный сервис, только функцию-обработчик.
Результат (сгенерированный LLM, проверенный вручную):
Генерируемый код: service/error_handler.py
import traceback
from typing import Any, Dict
from fastapi import Request, HTTPException
import logging
logger = logging.getLogger("self_healing")
async def handle_division_error(request: Request, exc: ZeroDivisionError) -> Dict[str, Any]:
"""Обработчик ZeroDivisionError с структурированным логом."""
tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
# Берём только последний фрейм
last_frame = tb_lines[-1] if tb_lines else str(exc)
logger.error(
"division_error",
extra={
"event": "error",
"error_type": "ZeroDivisionError",
"path": request.url.path,
"traceback": last_frame.strip(),
}
)
return {
"error": "Division by zero",
"traceback": last_frame.strip(),
}
* Вставьте этот обработчик в FastAPI-приложение через app.exception_handler(ZeroDivisionError).
LLM может сгенерировать и другие обработчики (TimeoutError, ValidationError) по аналогии.
5. Развертывание на Ray Serve: конфигурация автоскейлинга
Ray Serve позволяет настраивать pipeline-параллелизм и автоскейлинг на основе метрик. Вот рабочий конфиг для GPU-сервиса:
Файл serve.yaml
applications:
- name: payment_service
import_path: service.app:app # FastAPI(app) в service/app.py
runtime_env:
working_dir: .
pip:
- -r requirements.txt
deployments:
- name: payment_deployment
num_replicas: 2 # начальное число реплик
max_concurrent_queries: 100
ray_actor_options:
num_gpus: 0.5 # каждая реплика использует 0.5 GPU
autoscaling_config:
target_utilization_fraction: 0.7 # скейлим при 70% загрузке GPU
min_replicas: 1
max_replicas: 10
initial_replicas: 2
health_check_period_s: 10
health_check_timeout_s: 30
Запуск сервиса
# Запуск кластера Ray (если не запущен)
ray start --head --port=6379 --dashboard-host=0.0.0.0
# Деплой через Ray Serve CLI
serve deploy serve.yaml
# Проверка статуса
serve status
# Доступ через Ray Dashboard: http://localhost:8265
Важно: Ray Serve не поддерживает hot-reload для Python-файлов.
При изменении кода делайте serve deploy serve.yaml --force — это перезапустит реплики без поранения запросов (если настроено graceful shutdown).
6. Агент самоисцеления: детекция и патчинг в runtime
Агент работает как фоновый Ray-актор, который каждые 30 секунд:
- Парсит логи из stdout/stderr Ray-реплик (или отправляет их в Loki/Prometheus).
- Детектирует аномалии: рост 5xx ошибок >5% за 5 минут или повторяющиеся traceback.
- Генерирует промпт для LLM с контекстом ошибки и исходным кодом.
- Применяет патч через Git-хук или прямо в файловую систему (если контейнер read-write).
- Перезапускает реплику для применения изменений.
Промпт для агента (агент/repair_prompt.txt)
Ты — senior инженер, специализирующийся на исправлении багов в Python-сервисах.
Контекст ошибки:
- Сервис: Self-Healing Payment Service
- Эндпоинт: POST /process
- Ошибка: ZeroDivisionError: division by zero
- Traceback (последний фрейм):
File "service/app.py", line 42, in process_payment
result = 100 / 0
Исходный код эндпоинта (фрагмент):
```python
@router.post("/process")
async def process_payment(request: PaymentRequest):
if request.fail_with == "division":
result = 100 / 0 # ← ОШИБКА ЗДЕСЬ
else:
result = request.amount * 0.9
return {"transaction_id": uuid4(), "status": "completed"}
```
Задача:
1. Предложи минимальный патч, который:
- Исправляет ошибку (деление на ноль).
- Сохраняет логику искусственной ошибки для тестов (fail_with == "division").
- Добавляет валидацию: если amount == 0, возвращай 400.
2. Верни только diff в формате unified:
--- a/service/app.py
+++ b/service/app.py
@@ -40,7 +40,11 @@
async def process_payment(request: PaymentRequest):
if request.fail_with == "division":
- result = 100 / 0
+ if request.amount == 0:
+ raise HTTPException(status_code=400, detail="Amount cannot be zero")
+ result = 100 / request.amount # делим на amount, а не на 0
else:
result = request.amount * 0.9
return {"transaction_id": uuid4(), "status": "completed"}
3. Если патч неясен, верни "UNCERTAIN: need more context".
Агент на Python (агент/self_healing_agent.py)
import asyncio
import httpx
import os
from ray import serve
from typing import List, Dict
@serve.deployment(
num_replicas=1,
ray_actor_options={"num_cpus": 0.5}
)
class SelfHealingAgent:
def __init__(self):
self.ollama_url = os.getenv("OLLAMA_URL", "http://localhost:11434")
self.model = "llama3.1:70b" # или mistral-large
self.service_code_path = "/app/service" # путь внутри контейнера
async def detect_anomalies(self, logs: List[Dict]) -> List[Dict]:
"""Детектор аномалий на основе простых правил."""
anomalies = []
error_counter = {}
for log in logs:
if log.get("level") == "ERROR" and "ZeroDivisionError" in log.get("message", ""):
key = f"{log['path']}:{log['error_type']}"
error_counter[key] = error_counter.get(key, 0) + 1
for key, count in error_counter.items():
if count >= 5: # более 5 одинаковых ошибок за интервал
path, error_type = key.split(":")
anomalies.append({
"path": path,
"error_type": error_type,
"count": count,
"sample_traceback": log["traceback"]
})
return anomalies
async def generate_patch(self, anomaly: Dict) -> str:
"""Запрос к LLM для генерации патча."""
async with httpx.AsyncClient() as client:
# Читаем исходный код
file_path = f"{self.service_code_path}/app.py"
with open(file_path, "r") as f:
source_code = f.read()
prompt = f"""... (используем промпт из repair_prompt.txt, подставляя anomaly и source_code) ..."""
response = await client.post(
f"{self.ollama_url}/api/generate",
json={
"model": self.model,
"prompt": prompt,
"stream": False
},
timeout=30.0
)
return response.json()["response"]
async def apply_patch(self, patch: str, anomaly: Dict):
"""Применение патча через git или прямое редактирование."""
# Вариант 1: через git (предпочтительно)
# git apply patch.diff && git commit -m "auto-fix"
# Вариант 2: прямое редактирование (если контейнер read-write)
# with open(file_path, "w") as f: f.write(patched_code)
# После применения перезапускаем реплику Ray Serve
serve.get_app_handle("payment_service").options(
stream=True
).deploy() # или ray restart для контейнера
async def run_cycle(self):
"""Основной цикл агента (вызывается по расписанию)."""
logs = await self.collect_logs() # реализация зависит от системы логов
anomalies = await self.detect_anomalies(logs)
for anomaly in anomalies:
patch = await self.generate_patch(anomaly)
if "UNCERTAIN" not in patch:
await self.apply_patch(patch, anomaly)
await self.log_action(f"Applied patch for {anomaly['path']}")
else:
await self.escalate_to_human(anomaly) # отправка в Telegram/Slack
* Агент должен работать в том же кластере Ray, но в отдельном deployment. Настройте квоту ресурсов, чтобы он не мешал основному сервису.
7. A/B тестирование стратегий восстановления
Сравниваем две стратегии на production-трафике (5% трафика на эксперимент):
| Стратегия | Механизм | MTTR (цель) | Риски |
|---|---|---|---|
| Instant Rollback | Откат на последний стабильный коммит (git revert) | < 2 мин | Потеря новых фич, возможен регресс |
| In-Place Fix | Применение патча LLM без отката | < 5 мин | False-positive патчи могут усугубить проблему |
Эксперимент через Prometheus + Grafana
# prometheus/rules.yml
groups:
- name: self_healing
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.path }}"
description: "Error rate > 5% for 2 minutes"
- alert: SelfHealingAction
expr: self_healing_patches_applied_total > 0
for: 1m
labels:
strategy: "{{ $labels.strategy }}" # rollback или fix
annotations:
summary: "Self-healing action taken: {{ $labels.strategy }}"
В Grafana создайте дашборд с метриками:
- MTTR: среднее время от детекции до восстановления (по стратегиям).
- Success Rate After Fix: % успешных запросов в течение 10 минут после патча.
- False Positive Rate: % патчей, которые привели к новым ошибкам.
8. Production-ready чек-лист
Перед запуском в production проверьте:
Код и конфигурация
- OpenAPI-спецификация валидна (openapi-spec-validator)
-
Все зависимости зафиксированы в
requirements.txt(черезuv pip freeze) - Патчи агента проходят code review (или есть approve-flow для production)
- Конфиг Ray Serve: лимиты CPU/GPU, автоскейлинг, health checks
Мониторинг и безопасность
- Структурированные логи в JSON (для парсинга агентом)
- Метрики Prometheus: error rate, latency, patches_applied
- LLM-агент работает с read-only контейнером (или через PR)
- Альтернатива: BentoML для изоляции модели и сервера
Типичные ошибки при внедрении
- False-positive патчи: LLM применяет изменения, которые ломают другие эндпоинты. Решение: тестируйте патчи в staging-окружении перед применением.
- Циклические исправления: Агент постоянно применяет один и тот же патч. Добавьте версионирование кода (commit hash) и проверку, менялся ли файл после последнего патча.
- Ресурсоёмкость LLM: Запросы к Llama 3.1 70B требуют 2xGPU. Используйте кэширование промптов и ограничьте частоту детекции (раз в 30 сек).
- Ray Serve memory leaks: При частых редеплоях реплик может расти потребление памяти. Настройте
max_replicas_per_nodeи мониторьтеray_memory_usage_bytes.
Итоги: что пошло не так в 2025 и как исправить в 2026
Основные проблемы самоисцеляющихся систем в 2025 году:
- LLM-галлюцинации в патчах — 15% автоматических исправлений приводили к регрессам. Решение: few-shot промпты с примерами успешных патчей и валидация через unit-тесты (генерируемые тем же LLM).
- Задержка детекции — классические метрики (error rate) реагируют с задержкой. Решение: streaming-анализ логов (например, через Apache Flink) и детекция аномалий в реальном времени.
- Отсутствие rollback-стратегии — агент часто применял "интересные" но опасные патчи. Решение: A/B тестирование стратегий на 5% трафика с автоматическим выбором по метрике MTTR.
К 2026 году стек Ray Serve + OpenAPI + Агентский LLM становится стандартом для критичных сервисов. Ключевой принцип: агент не должен быть автократом. Каждый патч проходит через pipeline (детекция → генерация → тестирование → применение) с человеческим approve для стратегических сервисов.
Полный рабочий пример (включая Dockerfile для BentoML, unit-тесты и скрипты развёртывания) доступен в репозитории: github.com/example/self-healing-2026