Настроить SLA между агентами

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить SLA между агентами

1. Цель задачи

Научиться проектировать и реализовывать надёжное взаимодействие между агентами в мультиагентной системе. Разработать механизмы таймаутов, повторных попыток (retry) и breaker|circuit breaker для вызова агента-исполнителя, обеспечив корректный fallback при его недоступности.

Ключевой результат Работающая цепочка вызовов, в которой при недоступности агента Б (или превышении SLA) автоматически срабатывает model|запасной сценарий, а система не «зависает» и логирует все сбои.


2. Исходные данные

Что нужноОткуда взять
Прототип мультиагентной системы (агент А → агент Б)Собственный учебный проект или код из предыдущих заданий
Агент-исполнитель (агент Б), работающий через HTTP/gRPCЗапущенный сервис с эндпоинтом (можно mock)
Набор тестовых запросовСгенерировать вручную (5–10 запросов разных типов)
Инструмент для создания задержек/отказовchaos-mesh, toxiproxy или Python-декоратор time.sleep / raise
Инструмент для мониторинга состояния вызововPrometheus-метрики или просто stdout-логи с метками времени

Если нет реального инструмента — симулируем:

  1. Написать простой HTTP-сервер на FastAPI, имитирующий агента Б.
  2. Добавить эндпоинт /call с параметром delay и error_probability (например, ?delay=0.5&error_prob=0.3).
  3. Агент А обращается к агенту Б через aiohttp или httpx.
  4. Для breaker|circuit breaker использовать библиотеку pybreaker или написать простой класс самому.

3. Технологический стек

КомпонентИнструментыНазначение
Агент А (оркестратор)Python 3.11+, FastAPI / Flask, asyncioОрганизация вызовов, логика таймаутов/ретраев
Агент Б (исполнитель)FastAPI (или просто HTTP-сервер)Обработка запросов, имитация сбоев
Клиент HTTP (async)httpx / aiohttpАсинхронные вызовы с таймаутами
Circuit breakerpybreaker или самописныйКонтроль частоты отказов
Мониторинг и логиstructlog + Prometheus-клиент pythonФиксация каждого вызова, времени, статуса
Тестированиеpytest + pytest-asyncioПроверка сценариев падений
Chaos engineeringtoxiproxy или декораторыВнесение задержек и ошибок

4. Этапы выполнения

Этап 1: Создание тестового окружения (1 час)

Действия

  1. Запустить два независимых FastAPI-приложения:
    • Агент А (порт 8001) — будет вызывать агента Б.
    • Агент Б (порт 8002) — имеет эндпоинт /execute.
  2. На агенте Б добавить query-параметры:
    • delay — задержка в секундах (float).
    • error — вероятность ошибки (0.0–1.0).
    @app.get("/execute")
    async def execute(delay: float = 0.0, error: float = 0.0):
        if random.random() < error:
            raise HTTPException(503, "Simulated failure")
        await asyncio.sleep(delay)
        return {"status": "ok", "agent": "B"}
    
  3. Проверить взаимную доступность: curl localhost:8001/health, curl localhost:8002/health.

Ожидаемый результат этапа Два работающих HTTP-сервиса, готовых к вызовам.


Этап 2: Базовая интеграция с реализацией таймаута (1 час)

Действия

  1. На агенте А написать функцию вызова агента Б через httpx.AsyncClient с таймаутом.
  2. Реализовать таймаут на уровне клиента (например, timeout=httpx.Timeout(5.0, connect=2.0)).
  3. Добавить логирование каждого вызова: время отправки, время ответа, статус, длительность.
  4. Написать простой эндпоинт /chain на агенте А, который вызывает Б и возвращает результат или ошибку таймаута.
  5. Тест: отправить запрос с delay=10 (больше таймаута) — агент А должен вернуть 504 или кастомный ответ.
async with httpx.AsyncClient(timeout=httpx.Timeout(5.0)) as client:
    try:
        resp = await client.get("http://agent-b:8002/execute", params={"delay": delay})
        resp.raise_for_status()
        return resp.json()
    except httpx.TimeoutException:
        log.warning("Timeout calling agent B")
        return fallback_response()

Ожидаемый результат этапа При превышении времени ожидания агент А возвращает fallback-ответ, а не виснет.


Этап 3: Добавление ретраев (1 час)

Действия

  1. Реализовать стратегию повторных попыток с экспоненциальной задержкой (exponential backoff).
  2. Использовать tenacity либо написать свой декоратор:
    from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
    
    @retry(stop=stop_after_attempt(3), 
           wait=wait_exponential(multiplier=1, min=1, max=10),
           retry=retry_if_exception_type((httpx.TimeoutException, httpx.HTTPStatusError)))
    async def call_agent_b(delay):
        ...
    
  3. Учесть: ретраи только для ошибок, которые могут быть временными (таймауты, 5xx). На 4xx ретраи не делать.
  4. Добавить логирование каждой попытки (номер, задержка перед ретраем).
  5. Тест: установить error=0.8 — проверить, что агент А делает 3 попытки, затем возвращает fallback.

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


Этап 4: Реализация Circuit Breaker (1.5–2 часа)

Действия

  1. Внедрить паттерн Circuit Breaker с использованием библиотеки pybreaker:
    import pybreaker
    breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=30)
    
  2. Обернуть вызов агента Б через breaker.call(call_agent_b, ...).
  3. Настроить три состояния:
    • Closed — нормальная работа, считаем ошибки.
    • Open — при превышении fail_max ошибок за период (за 60 сек) — немедленный отказ, вызов fallback.
    • Half-Open — после reset_timeout пускаем один тестовый запрос, если успех → Closed, если ошибка → Open.
  4. Настроить логирование переключения состояний (метрика: circuit_breaker_state).
  5. Написать endpoint /status на агенте А, показывающий текущее состояние breaker.
  6. Тесты:
    • Отправить 6 запросов с ошибкой — breaker должен открыться, 7-й запрос сразу пойти в fallback.
    • После reset_timeout отправить успешный запрос — breaker должен закрыться.

Ожидаемый результат этапа При частых сбоях вызовы блокируются на уровне breaker, защищая систему от каскадных отказов.


Этап 5: Интеграция SLA и финальное тестирование (1 час)

Действия

  1. Определить SLA для вызова агента Б:
  2. Реализовать метрики в Prometheus-формате:
    • agent_a_requests_total (counter, labels: status=ok/timeout/error)
    • agent_a_request_duration_seconds (histogram)
    • agent_a_circuit_breaker_state (gauge, 0=closed,1=half,2=open)
  3. Написать автотест (pytest), который проверяет все сценарии:
    • Нормальный вызов (без задержек) — успех.
    • Медленный вызов (delay=4) — таймаут и fallback.
    • С ошибкой (error=0.5) — 3 ретрая, потом fallback.
    • Частые ошибки (5 подряд) — circuit breaker открывается.
    • После паузы (30 сек) — проверка восстановления.
  4. Задокументировать поведение агента А в README.

Ожидаемый результат этапа Полная реализация SLA-механизма с метриками и автоматизированным тестом.


5. Критерии приемки (Definition of Done)

  • Агент А корректно обрабатывает таймауты: при превышении лимита возвращается fallback-ответ.
  • Реализованы ретраи с экспоненциальной задержкой (максимум 3 попытки).
  • Circuit breaker переходит в Open после 5 последовательных ошибок.
  • Fallback-сценарий выполняется при недоступности агента Б (Open breaker или таймаут после всех ретраев).
  • Логирование каждого вызова включает: ID запроса, время начала/конца, статус, количество ретраев.
  • Метрики (Prometheus) экспортируются, дашборд (Grafana) опционален.
  • Автотест покрывает все 4 сценария (успех, таймаут, ошибка, circuit breaker).
  • Код задокументирован (docstrings, README с описанием SLA и конфигурации).
  • Время ответа агента А при падении агента Б не превышает 2 секунд (быстрый fallback).

6. Ожидаемый результат

Основной артефакт Репозиторий с кодом двух агентов (A и B), содержащий:

  • agent_a/FastAPI приложение с интеграцией таймаутов, ретраев, circuit breaker.
  • agent_b/FastAPI приложение-заглушка с управляемыми сбоями.
  • tests/pytest-тесты всех сценариев.
  • requirements.txt или pyproject.toml.
  • README.md с описанием SLA, инструкцией по запуску и примером запроса.

Дополнительно (по желанию):

  • Docker-compose для запуска всего стенда.
  • Prometheus + Grafana конфиги для визуализации метрик.
  • Простая нагрузочная проба (locust/siege) для проверки поведения под нагрузкой.

7. Возможные сложности и их решение

СложностьРешение
Агент А «зависает» при синхронном вызовеИспользовать asyncio и неблокирующие HTTP-клиенты (httpx, aiohttp)
Некорректное поведение circuit breaker при параллельных запросахИспользовать потокобезопасный breaker (pybreaker по умолчанию thread-safe) или явно блокировать через asyncio.Lock
Ретраи не отличают временные ошибки от постоянныхФильтровать исключения: только TimeoutException и 5xx; 4xx не ретраить
Сложно отлаживать состояние breakerВыводить лог при каждом изменении состояния, добавить endpoint /breaker-status
Разные таймауты на connect, read и totalИспользовать httpx.Timeout с разными параметрами; документировать каждый
Метрики не сходятся с реальным поведениемВешать счётчики непосредственно перед вызовом и после (до исключения)

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Создание тестового окружения1 ч
Этап 2: Базовая интеграция + таймаут1 ч
Этап 3: Ретраи1 ч
Этап 4: Circuit Breaker1.5–2 ч
Этап 5: Интеграция + тесты1 ч
Итого5.5–6 ч

Примечание: если некоторые инструменты уже знакомы, время можно сократить на 30–40%.


9. Связанные вопросы из базы знаний

ВопросТема
12Как работает circuit breaker в микросервисах
45Экспоненциальный backoff vs линейный retry
67Паттерн Timeout в распределённых системах
89Мониторинг SLA: метрики и алерты
112Fallback-стратегии при недоступности сервиса
134Chaos engineering для тестирования отказоустойчивости
156Проектирование API с учётом SLA
189Логирование и трейсинг в мультиагентных системах
210Prometheus histogram для задержек запросов
245Graceful degradation: как снижать качество, а не ломаться

10. Чек-лист самопроверки

  • Я настроил таймаут на HTTP-клиенте и убедился, что при задержке > SLA возвращается fallback.
  • Я реализовал ретраи с ограничением по числу попыток и экспоненциальной задержкой.
  • Я добавил circuit breaker и проверил его работу: после N ошибок вызовы идут в fallback без попытки.
  • Я написал хотя бы 3 автотеста, покрывающих разные сценарии (успех, сбой, broken circuit).
  • Я задокументировал SLA и параметры конфигурации в README.
  • Я проверил, что время ответа агента А при недоступности Б не превышает 2 секунд.
  • Я настроил логирование состояния breaker и количества ретраев.
  • Я убедился, что метрики (экспортируются в Prometheus) корректно отражают число запросов и их статусы.