AI Engineering Academy · Lekcja

Dostawcy zapasowi i circuit breakers

Zbuduj kaskadę dostawców, która automatycznie przełącza się z OpenAI na Anthropic, a następnie na model lokalny, gdy główny dostawca działa wolno lub jest niedostępny, korzystając ze wzorca circuit breaker.

Lekcja 3 z 413 kroki

Dostawcy zapasowi i circuit breakers to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Ryzyko związane z jednym dostawcą

Poleganie na jednym dostawcy LLM tworzy pojedynczy punkt awarii. OpenAI doświadczało awarii, których usunięcie zajmowało od kilku minut do kilku godzin. Jeśli cała aplikacja zależy od dostępności GPT-4o, każdy incydent po stronie dostawcy natychmiast przekłada się na przestój widoczny dla użytkowników. Strategia dostawcy zapasowego utrzymuje ciągłość działania usługi, kierując ruch do alternatywnych dostawców, gdy główny dostawca ulegnie awarii.

Definiowanie kaskady dostawców

Kaskada dostawców to uporządkowana lista dostawców i modeli wypróbowywanych kolejno. Gdy główny dostawca ulegnie awarii lub przekroczy limit czasu, system automatycznie próbuje użyć następnego. Typowa kaskada może wyglądać tak: OpenAI GPT-4o → Anthropic Claude 3.5 Sonnet → lokalnie wdrożony model Llama. Każdy poziom jest rozwiązaniem zapasowym, a model lokalny pełni funkcję ostatecznego zabezpieczenia, które nie może przestać działać.

from dataclasses import dataclass
from typing import Optional

@dataclass
class Provider:
    name: str
    base_url: Optional[str]
    api_key_env: str
    model: str
    priority: int  # lower = higher priority

CASCADE = [
    Provider('openai',    None,                              'OPENAI_API_KEY',    'gpt-4o',              1),
    Provider('anthropic', 'https://api.anthropic.com/v1',   'ANTHROPIC_API_KEY', 'claude-3-5-sonnet',   2),
    Provider('local',     'http://localhost:8000/v1',        'LOCAL_KEY',         'llama-3.1-8b-inst',   3),
]

Implementowanie pętli przełączania awaryjnego

Zaimplementuj pętlę przełączania awaryjnego jako prostą konstrukcję try/except iterującą po kaskadzie. Przechwytuj błędy przejściowe (przekroczenia limitu czasu, błędy 500 i 503) i przechodź do następnego dostawcy. Nie przechwytuj błędów uwierzytelniania (401) ani błędów nieprawidłowych żądań (400) — są to błędy programistyczne, które powinny zostać natychmiast ujawnione, zamiast powodować przełączenie na innego dostawcę.

import openai
import os

TRANSIENT_ERRORS = (openai.APITimeoutError, openai.InternalServerError, openai.APIConnectionError)

async def call_with_fallback(messages: list, **kwargs) -> str:
    for provider in CASCADE:
        try:
            client = openai.AsyncOpenAI(
                api_key=os.environ[provider.api_key_env],
                base_url=provider.base_url
            )
            resp = await client.chat.completions.create(
                model=provider.model,
                messages=messages,
                timeout=10.0,
                **kwargs
            )
            return resp.choices[0].message.content
        except TRANSIENT_ERRORS as e:
            print(f'Provider {provider.name} failed: {e}, trying next...')
    raise RuntimeError('All providers failed')

Czym jest bezpiecznik obwodu

Bezpiecznik obwodu zapobiega zasypywaniu niesprawnej usługi żądaniami podczas awarii. Jego nazwa pochodzi od elektrycznych bezpieczników obwodów; ma trzy stany: Closed (żądania przechodzą normalnie), Open (żądania są natychmiast odrzucane) oraz Half-Open (pojedyncze żądanie testowe jest przepuszczane, aby sprawdzić, czy usługa odzyskała sprawność). Chroni to zarówno usługę zależną, jak i własną aplikację podczas incydentów.

# Circuit breaker state machine:
#
# CLOSED --> (failure_count >= threshold) --> OPEN
#    ^                                          |
#    |     (test_request succeeds)              | (timeout expires)
#    +------------ HALF_OPEN <-----------------+
#
# In OPEN state: immediately return fallback/error
# In HALF_OPEN: allow one request through to test recovery
# In CLOSED: normal operation, count failures

Implementowanie bezpiecznika obwodu

Oto minimalna implementacja bezpiecznika obwodu. Śledź liczbę awarii oraz czas otwarcia obwodu. Gdy liczba awarii przekroczy próg, otwórz obwód. Po konfigurowalnym czasie resetowania zezwól na jedno żądanie sondy. Jeśli sonda zakończy się powodzeniem, zamknij obwód. Jeśli się nie powiedzie, pozostaw obwód otwarty i zresetuj limit czasu.

import time
from enum import Enum

class State(Enum):
    CLOSED = 'closed'
    OPEN = 'open'
    HALF_OPEN = 'half_open'

class CircuitBreaker:
    def __init__(self, failure_threshold=5, reset_timeout=60):
        self.state = State.CLOSED
        self.failure_count = 0
        self.failure_threshold = failure_threshold
        self.reset_timeout = reset_timeout
        self.opened_at = None

    def record_success(self):
        self.failure_count = 0
        self.state = State.CLOSED

    def record_failure(self):
        self.failure_count += 1
        if self.failure_count >= self.failure_threshold:
            self.state = State.OPEN
            self.opened_at = time.time()

    def can_attempt(self) -> bool:
        if self.state == State.CLOSED:
            return True
        if self.state == State.OPEN:
            if time.time() - self.opened_at > self.reset_timeout:
                self.state = State.HALF_OPEN
                return True  # allow one probe
            return False
        return True  # HALF_OPEN: allow probe

Integracja bezpieczników obwodu z dostawcami

Utrzymuj jeden bezpiecznik obwodu dla każdego dostawcy. Przed wywołaniem dostawcy sprawdź, czy jego bezpiecznik obwodu zezwala na próbę. Po każdym wywołaniu zarejestruj powodzenie lub niepowodzenie. Gdy obwód dostawcy zostanie otwarty, pętla przełączania awaryjnego naturalnie go pomija i próbuje użyć następnego dostawcy w kaskadzie, bez oczekiwania na przekroczenie limitu czasu przy każdym wywołaniu.

breakers = {p.name: CircuitBreaker(failure_threshold=5, reset_timeout=60) for p in CASCADE}

async def call_with_circuit_breaker(messages: list) -> str:
    for provider in CASCADE:
        breaker = breakers[provider.name]
        if not breaker.can_attempt():
            continue  # skip this provider, circuit is open
        try:
            result = await call_provider(provider, messages)
            breaker.record_success()
            return result
        except TRANSIENT_ERRORS as e:
            breaker.record_failure()
            print(f'{provider.name} failed ({breaker.failure_count}/{breaker.failure_threshold})')
    raise RuntimeError('All providers exhausted')

Wykrywanie powolnych wywołań jako awarii

Dostawca, który odpowiada po 30 sekundach, z perspektywy użytkownika jest niemal tak samo problematyczny jak dostawca całkowicie niedostępny. Skonfiguruj rygorystyczny limit czasu dla każdego wywołania dostawcy i traktuj wyjątki przekroczenia limitu czasu jako awarie w bezpieczniku obwodu. Limit czasu wynoszący 10 sekund oznacza, że przełączenie awaryjne nastąpi wystarczająco szybko, aby użytkownik zobaczył jedynie krótkie opóźnienie, a nie zawieszony ekran.

async def call_provider(provider: Provider, messages: list) -> str:
    client = openai.AsyncOpenAI(
        api_key=os.environ[provider.api_key_env],
        base_url=provider.base_url
    )
    try:
        resp = await asyncio.wait_for(
            client.chat.completions.create(model=provider.model, messages=messages),
            timeout=10.0  # fail fast, let circuit breaker count it
        )
        return resp.choices[0].message.content
    except asyncio.TimeoutError:
        raise openai.APITimeoutError('Provider timed out')

Panel kondycji dostawców

Udostępnij endpoint /health/providers pokazujący bieżący stan bezpiecznika obwodu dla każdego dostawcy, w tym liczbę awarii, stan (closed/open/half-open) oraz czas do resetu. Ułatwia to szybkie sprawdzenie, którzy dostawcy działają prawidłowo podczas incydentu, i pomaga zdecydować, czy ręcznie wymusić reset, czy zaczekać na automatyczne przywrócenie działania.

from fastapi import FastAPI

app = FastAPI()

@app.get('/health/providers')
def provider_health():
    return {
        name: {
            'state': cb.state.value,
            'failure_count': cb.failure_count,
            'seconds_until_reset': (
                max(0, cb.reset_timeout - (time.time() - cb.opened_at))
                if cb.state == State.OPEN else None
            )
        }
        for name, cb in breakers.items()
    }

Ujednolicanie danych wyjściowych dostawców

Różni dostawcy mają różne formaty odpowiedzi, filtry bezpieczeństwa i możliwości. Po przełączeniu z GPT-4o na Claude model może odrzucić niektóre żądania, na które GPT-4o udzieliłby odpowiedzi. Utrzymuj opakowania promptów właściwe dla dostawcy, które dostosowują prompty do konwencji każdego dostawcy. Testuj każdego dostawcę zapasowego niezależnie, aby upewnić się, że generuje akceptowalne dane wyjściowe dla danego zastosowania.

def adapt_messages_for_provider(provider: Provider, messages: list) -> list:
    if provider.name == 'anthropic':
        # Claude prefers explicit task descriptions
        system = next((m['content'] for m in messages if m['role'] == 'system'), '')
        if 'JSON' not in system:
            messages = [{'role': 'system', 'content': system + ' Respond in JSON.'}] + [
                m for m in messages if m['role'] != 'system'
            ]
    return messages

Testowanie działania przełączania awaryjnego

Napisz test, który wymusza awarię głównego dostawcy (podając nieprawidłowy klucz API lub używając mocka zgłaszającego błędy), a następnie sprawdza, czy nastąpiło przełączenie awaryjne i czy zwrócono prawidłową odpowiedź. Przetestuj również, czy bezpiecznik obwodu poprawnie otwiera się po skonfigurowanej liczbie awarii oraz czy odzyskuje działanie po upływie limitu czasu resetowania. Logika przełączania awaryjnego, która nigdy nie była testowana, jest zawodna podczas rzeczywistej awarii.

import pytest
from unittest.mock import AsyncMock, patch

@pytest.mark.asyncio
async def test_fallback_on_primary_timeout():
    # Primary provider times out
    with patch('your_module.call_provider', side_effect=[
        openai.APITimeoutError('Timeout'),  # primary fails
        'Claude response'                   # fallback succeeds
    ]):
        result = await call_with_circuit_breaker([{'role': 'user', 'content': 'Hello'}])
    assert result == 'Claude response'

Kwestie kosztów kaskady dostawców

Dostawcy awaryjni często mają inne ceny niż dostawca główny. Anthropic Claude może kosztować więcej lub mniej niż OpenAI GPT-4o, zależnie od poziomu modelu. Należy śledzić, który dostawca obsłużył każde żądanie, i osobno obliczać przypisanie kosztów. Jeśli dostawca awaryjny jest stale droższy, warto sprawdzić, czy dostawca główny nie ma zbyt małej dostępnej przepustowości oraz czy przejście na wyższy poziom limitu zapytań nie byłoby bardziej opłacalne niż częste korzystanie z trybu awaryjnego.

# Approximate costs per 1M tokens (2026):
PROVIDER_COSTS = {
    'openai/gpt-4o':          {'input': 2.50, 'output': 10.00},
    'anthropic/claude-3.5-sonnet': {'input': 3.00, 'output': 15.00},
    'openai/gpt-4o-mini':     {'input': 0.15, 'output': 0.60},
    'local/llama-3.1-8b':     {'input': 0.00, 'output': 0.00},  # infra cost only
}

# If fallback adds $0.50/day and a Tier 2 upgrade costs $100/month:
# Tier 2 pays off if you use fallback > 200 requests/day

Szybkie sprawdzenie

Sprawdź swoją wiedzę na temat wyłączników obwodu i dostawców awaryjnych.

Podsumowanie lekcji

W tej lekcji poznano: kaskady dostawców, które definiują uporządkowaną sekwencję awaryjną od głównego do zapasowych dostawców LLM; wyłączniki obwodu, które zapobiegają przeciążaniu niesprawnego dostawcy, szybko przerywając wywołania po przekroczeniu progu błędów; oraz limity czasu dla poszczególnych dostawców, które sprawiają, że wolne wywołania szybko uruchamiają tryb awaryjny, zamiast blokować użytkowników. W następnej części ustawimy budżety czasu i zaimplementujemy kontrolowane obniżanie jakości.

Bezpłatny start

Ucz się Python dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
30
Lekcje
120

Często zadawane pytania

Czy lekcja „Dostawcy zapasowi i circuit breakers” jest bezpłatna?

Tak — pełny tekst „Dostawcy zapasowi i circuit breakers” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Dostawcy zapasowi i circuit breakers”?

Zbuduj kaskadę dostawców, która automatycznie przełącza się z OpenAI na Anthropic, a następnie na model lokalny, gdy główny dostawca działa wolno lub jest niedostępny, korzystając ze wzorca circuit b… Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Dostawcy zapasowi i circuit breakers”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Pomiar opóźnienia LLM: TTFT i TPOT
  2. Równoważenie obciążenia i strategie wielu kluczy
  3. Dostawcy zapasowi i circuit breakers
  4. Budżety czasu oczekiwania i kontrolowana degradacja
← Powrót do AI Engineering Academy