Узнайте, как опыт бэкенд-разработки с платежными системами помогает строить надежный шлюз для LLM. Практические примеры на Python ждут вас.
Представьте: вы отправляете один и тот же промпт в Claude Haiku и Gemini 2.5 Flash. Flash дешевле за токен, но тратит 28 токенов на рассуждения, а Haiku — всего 4. В результате Flash обходится в 8.6 раза дороже за запрос. Я заметил это только потому, что инструментировал каждый вызов: токены, стоимость, задержка — всё в Postgres. Этот инстинкт — ключевой. За два с половиной года создания кросс-граничных платежей в реальном времени в NPCI я привык, что округление — это инцидент, а падение смежника — испорченная суббота. В этом году я строил шлюз для LLM — и использовал те же инструменты. Это не совпадение.
AI-инфраструктура выглядит новой областью. На деле это та же системная работа, которую бэкенд-инженеры делали всегда. Модельный API — это внешняя зависимость: медленная, иногда недоступная, с лимитами и оплатой за вызов. Вы уже интегрировали такую зависимость — платежный процессор, KYC-провайдер, банк-партнер. Модель — не магия. Это дорогой и нестабильный внешний сервис, а сложные части — надежность, контроль затрат, отказоустойчивость — мы уже решили.
Шлюзу нужен circuit breaker для каждого провайдера. В NPCI я строил именно это — на платежной платформе, где каждый партнер был внешним сервисом в другой стране, со своим SLA и определением «доступен». Мои отказоустойчивые процессоры на Go оборачивали партнеров в circuit breaker и rate limiter по одной причине: когда партнер начинал падать, худшее — продолжать долбить его, пока пулы воркеров забиваются зависшими вызовами. Поэтому вы отключаетесь, быстро падаете и пускаете тонкий поток трафика для проверки восстановления. CLOSED, OPEN, HALF_OPEN. Те же три состояния для Anthropic и Gemini — изменилось только имя: «банк-партнер» стал «провайдером модели».
import time
import threading
from enum import Enum
class CircuitState(Enum):
CLOSED = 'closed' # Нормальная работа
OPEN = 'open' # Провайдер недоступен
HALF_OPEN = 'half_open' # Проверка восстановления
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=30):
self.state = CircuitState.CLOSED
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = None
self.lock = threading.Lock()
def call(self, func, *args, **kwargs):
with self.lock:
if self.state == CircuitState.OPEN:
# Проверяем, не прошло ли время для восстановления
if time.time() - self.last_failure_time >= self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
else:
raise Exception("Circuit breaker OPEN — запрос отклонён")
try:
result = func(*args, **kwargs)
with self.lock:
# Успешный вызов в HALF_OPEN восстанавливает цепь
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.CLOSED
self.failure_count = 0
return result
except Exception as e:
with self.lock:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
raise e
Шлюз должен учитывать каждый вызов в Postgres. Это журнал аудита, а платежи работают на журналах. В 20+ сервисах на Go я завязал всё на идемпотентность, чтобы повторный запрос не удваивал счёт. Лог стоимости ключится по request_id по той же причине. Деньги всегда фиксированной точности, никогда не float — поэтому cost_usd — NUMERIC, а не float. Вы не аппроксимируете деньги, и не аппроксимируете затраты.
import psycopg2
from datetime import datetime
class CostLogger:
def __init__(self, dsn):
self.conn = psycopg2.connect(dsn)
self.cur = self.conn.cursor()
self.cur.execute("""
CREATE TABLE IF NOT EXISTS llm_calls (
request_id TEXT PRIMARY KEY,
model TEXT NOT NULL,
prompt_tokens INTEGER,
completion_tokens INTEGER,
cost_usd NUMERIC(10, 6) NOT NULL, -- Фиксированная точность
created_at TIMESTAMP DEFAULT NOW()
)
""")
self.conn.commit()
def log_call(self, request_id, model, prompt_tokens, completion_tokens, cost_usd):
# Идемпотентная вставка: повторный вызов не дублирует запись
self.cur.execute("""
INSERT INTO llm_calls (request_id, model, prompt_tokens, completion_tokens, cost_usd)
VALUES (%s, %s, %s, %s, %s)
ON CONFLICT (request_id) DO NOTHING
""", (request_id, model, prompt_tokens, completion_tokens, cost_usd))
self.conn.commit()
Различие между транзиентными и устойчивыми сбоями — мышечная память. В платежах неосторожный повтор — это двойное списание, поэтому один таймаут означает идемпотентный повтор, никогда не слепую переотправку. Устойчивый сбой — прекращаем отправлять. Я писал это различие для финансовых рабочих процессов годами. Переписывая для LLM-вызова, цена ошибки упала с чьих-то денег до потраченного токена — но форма проблемы не изменилась ни на дюйм.
import time
import random
from enum import Enum
class RetryStrategy:
def __init__(self, max_retries=3, base_delay=1.0, max_delay=10.0):
self.max_retries = max_retries
self.base_delay = base_delay
self.max_delay = max_delay
def execute(self, func, *args, **kwargs):
last_exception = None
for attempt in range(self.max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
# Транзиентные ошибки: таймаут, 429, 503
if self._is_transient(e):
delay = min(self.base_delay * (2 ** attempt) + random.uniform(0, 1), self.max_delay)
time.sleep(delay)
else:
# Устойчивая ошибка: не повторяем
raise e
raise last_exception
def _is_transient(self, e):
# Пример: проверка по коду ошибки
return hasattr(e, 'status_code') and e.status_code in (429, 503, 504)
Не буду притворяться, что всё было знакомо. Несколько вещей действительно новы, и все они связаны с самой моделью. Токен-экономика — реальная дисциплина: сюрприз с 8.6× не существует ни в одной базе данных, за которую я когда-либо выставлял счёт. Модель недетерминирована так, как недетерминировано хранилище: один и тот же вход может дать разный выход, поэтому вы не можете проверять точные строки, и «тестирование» становится оценками и распределениями вместо равенства. И модель, тихо тратящая ваш бюджет на собственные рассуждения, не имеет аналога в платежном коммутаторе. Это новая поверхность — но это несколько недель нового на годах фундамента, а не новая карьера.
Если вы бэкенд-инженер и сомневаетесь, переносится ли ваш опыт распределённых систем на AI: он не просто переносится — он дефицитен. Любой может вызвать LLM API за полдня. Гораздо меньше людей могут сделать этот вызов надёжным, когда провайдер деградирует, дешёвым, когда математика токенов идёт против вас, и наблюдаемым, когда кто-то просит объяснить счёт. Это не AI-экспертиза. Это работа, которую вы уже делали, только с моделью на конце.
Шлюз доступен по адресу https://llm-gateway-python.onrender.com, а код на GitHub: https://github.com/Yogesh23012001/llm-gateway-python. Скопируйте circuit breaker и cost logger в свой проект — и вы уже на шаг впереди.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →