RussiaAPI

Python и надёжность API

Python httpx async для AI API: timeout, отмена и ограниченный retry

Python httpx async для AI API удобен, когда backend одновременно обслуживает несколько запросов, но «async» не отменяет сетевые сбои. Без общего deadline, ограниченных повторов и ясной обработки отмены один медленный upstream-вызов способен занять воркер, создать дубль асинхронной задачи или незаметно увеличить расход. Ниже — практический контракт для server-side клиента RussiaAPI: сначала проверить текущий каталог и собственный ключ, затем измерить один сценарий и только потом расширять параллельность.

Опубликовано 28 августа 2026 · 11 минут чтения · Ключевой запрос: Python httpx async timeout retry AI API

Граница сервиса. RussiaAPI — независимый сторонний API gateway, а не официальный сервис OpenAI, Anthropic, Google, DeepSeek, Node.js, Python или производителя модели. Совместимый формат означает только проверяемый контракт запроса. Он не гарантирует одинаковые модели, цены, доступность, правила или функции. Используйте на сервере только собственный RUSSIAAPI_API_KEY; не передавайте внешние ключи, cookie, пароли, коды подтверждения или лишние персональные данные.

Определите контракт до настройки retry

Сначала опишите для каждой операции допустимое время ожидания, результат, который считается успехом, и то, можно ли безопасно повторить запрос. Чтение каталога моделей обычно идемпотентно, но создание видео-задачи или действие с внешним эффектом требует внутреннего operation ID. Статус 200 не всегда завершает бизнес-задачу: ответ может не пройти валидацию или пользователь может отменить ожидание. Поэтому измеряйте конечный результат отдельно от HTTP-кода.

Не переносите значения timeout из чужой статьи в production. Они зависят от вашего интерфейса, очереди, модели, streaming и политики продукта. Начните с небольшого synthetic smoke test, который включает успешный ответ, медленный ответ, timeout, 429, 5xx, невалидный JSON и отмену. Текущие model ID и доступ для проекта нужно сверять в каталоге, а не предполагать по названию модели.

Разделите connect, read и общий deadline

В httpx есть разные фазы ожидания: соединение, запись, чтение и пул. Для пользователя важен общий срок операции, поэтому одному per-request timeout недостаточно. Внешний asyncio.timeout ограничивает весь путь, включая ожидание свободного соединения и ваш retry. Когда budget исчерпан, отмените работу и верните понятный контролируемый результат; не оставляйте фоновые coroutine, которые потом неожиданно завершат старый запрос.

Отмена не доказывает, что сервер не начал обработку. Для операции с побочным эффектом не создавайте новую задачу сразу после client timeout. Сначала запросите состояние по безопасному внутреннему идентификатору, если ваш сценарий его поддерживает. Для обычного text completion повтор допустим только после проверки политики и отсутствия побочного эффекта. Подробнее о защите от дублей — в материале об идемпотентных повторах.

Повторяйте только временные и понятные ошибки

Retry — не средство исправлять любую ошибку. Временными могут быть часть 429, сетевые сбои и отдельные 5xx, но 400 и 422 обычно означают ошибку тела запроса, а 401 и 403 — ключ, base URL, модель или права. Повтор с тем же неверным JSON тратит время и может скрыть дефект в приложении. Разделяйте классы ошибок в коде, а правила их диагностики держите рядом с контрактным тестом.

Используйте экспоненциальный backoff с небольшим случайным jitter, максимальным числом попыток и общим deadline. Не запускайте бесконечный цикл и не создавайте новую сессию на каждую попытку. При 429 уменьшайте собственную конкуренцию и уважайте фактический ответ; лимит зависит от проекта, тарифа и текущих условий, поэтому не публикуйте фиксированные пороги как обещание. Связанный разбор есть в статье о 429 и backoff.

Минимальный server-side пример

Пример ниже показывает один общий deadline, общий AsyncClient и ограниченный retry. Ключ берётся только из runtime-переменной; его нельзя передавать браузеру, помещать в notebook, коллекцию запросов или лог. Название модели также передаётся как конфигурация приложения: перед запуском подтвердите его в текущем каталоге RussiaAPI. Код иллюстрирует текстовый endpoint и не обещает одинаковую поддержку всех параметров у всех моделей.

import asyncio, httpx, os, random

client = httpx.AsyncClient(limits=httpx.Limits(max_connections=20, max_keepalive_connections=10))

async def chat_once(messages):
    headers = {"Authorization": f"Bearer {os.environ['RUSSIAAPI_API_KEY']}", "content-type": "application/json"}
    payload = {"model": os.environ['RUSSIAAPI_MODEL'], "messages": messages}
    async with asyncio.timeout(20):
        for attempt in range(3):
            response = await client.post("https://russiaapi.com/v1/chat/completions", headers=headers, json=payload, timeout=httpx.Timeout(12.0))
            if response.status_code not in (429, 502, 503, 504):
                response.raise_for_status()
                return response.json()
            if attempt == 2:
                response.raise_for_status()
            await asyncio.sleep(min(2 ** attempt, 4) + random.random() / 4)

В production добавьте аутентификацию своего пользователя, лимит входного текста, валидацию структуры ответа, telemetry без содержимого prompt и закрытие клиента при остановке приложения. Не записывайте Authorization, тело запроса или полный ответ в exception message. Для корректного JSON проверьте типы и обязательные поля до отправки; ошибки 400/422 разобраны в руководстве по JSON и schema.

Пул соединений и конкуренция

Один AsyncClient с явными limits обычно лучше, чем новый клиент в каждой функции: он повторно использует соединения и делает нагрузку измеримой. Но большой пул не делает upstream бесконечно быстрым. Ограничьте параллельность отдельным semaphore или очередью на уровне сценария, чтобы защитить приложение от всплеска задач. Ваша очередь должна уметь отменять устаревшую работу, а не просто ждать до бесконечности.

Собирайте безопасные метрики: длительность, HTTP-класс статуса, число попыток, ожидание в очереди, результат серверной валидации и внутренний request ID. Не используйте prompt, email, ключ или произвольный user ID как label. Так команда увидит, когда повысить budget, уменьшить concurrency или остановить rollout, не превращая логи в хранилище чувствительных данных. Практика маскирования — в статье о безопасных логах.

Тестируйте отказ так же, как успех

В тесте замокайте transport, а не живой production endpoint: проверьте timeout, отмену до первой попытки, 429, одну временную 5xx, постоянную 4xx, пустой ответ и некорректный JSON. Для каждого случая зафиксируйте, сколько попыток произошло, какой код увидел вызывающий сервис и не был ли повторён побочный эффект. Отдельно убедитесь, что строка, похожая на bearer token, не выходит в лог или сообщение пользователю.

После тестов выкатывайте изменение постепенно: малый процент трафика, ограниченный набор моделей и наблюдение за p95 задержки, 429 и долей валидных результатов. Fallback не должен обходить доступ или неожиданно отправлять данные в неутверждённый маршрут. Разрешайте fallback лишь на текущие доступные модели после своего теста; статья о fallback помогает оформить такую политику.

Чек-лист перед релизом

  1. Для операции определены общий deadline, terminal success и допустимость повтора.
  2. AsyncClient и limits создаются на уровне приложения, ключ остаётся только на сервере.
  3. Retry ограничен по классам ошибок, числу попыток и общему времени.
  4. Создание задач защищено operation ID и проверкой статуса после timeout.
  5. Логи и метрики не содержат ключей, заголовков, prompt или персональных данных.

Этот подход не гарантирует постоянную доступность или скорость модели. Он даёт воспроизводимый клиентский контракт, который команда может проверить на своём проекте и актуальном каталоге RussiaAPI.

Проверьте сценарий в RussiaAPI

Создайте собственный тестовый ключ в консоли, сверьте текущий каталог моделей и начните с обезличенного server-side smoke test. Расширяйте доступ и нагрузку только после измеримой проверки.

Открыть консоль RussiaAPI · Документы · Каталог моделей

FAQ

Нужно ли повторять 400 или 422?

Обычно нет. Эти статусы указывают на форму запроса, типы или схему; повтор с теми же данными редко что-то меняет. Сначала проверьте JSON и конфигурацию, а затем отправьте новый валидный запрос.

Можно ли хранить ключ в клиентском Python-коде?

Нет. Ключ RussiaAPI должен существовать только в защищённой server-side переменной или secret manager. Не помещайте его в мобильное приложение, браузер, репозиторий, логи или чат.

Что делать после timeout при создании асинхронной задачи?

Не создавайте задачу повторно вслепую. Используйте внутренний operation ID, проверьте уже созданный статус в своей системе и повторяйте только когда политика операции это допускает.

Читайте также