Budżety czasu oczekiwania i kontrolowana degradacja
Ustaw rygorystyczne budżety timeoutów na każdej warstwie potoku i zaimplementuj kontrolowaną degradację, która zwraca odpowiedzi z pamięci podręcznej lub uproszczone odpowiedzi, gdy LLM przekroczy budżet.
Budżety czasu oczekiwania i kontrolowana degradacja to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 4 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.
Czym jest budżet czasu?
Budżet czasu to maksymalny łączny czas przeznaczony na ukończenie żądania we wszystkich etapach potoku. Zamiast ustawiać arbitralny limit czasu dla każdego wywołania API z osobna, definiuje się budżet od początku do końca operacji widocznej dla użytkownika i rozdziela go między pobieranie danych, generowanie przez LLM oraz przetwarzanie końcowe. Dzięki temu odpowiedź zawsze zostanie udzielona w akceptowalnym czasie, nawet jeśli niektóre etapy działają wolno.
Rozdzielanie budżetu między etapy potoku
Typowy potok czatu RAG składa się z trzech etapów: pobierania danych, generowania przez LLM i formatowania odpowiedzi. Każdemu z nich należy przydzielić odcinek czasu na podstawie typowego czasu wykonania oraz tego, jak długie opóźnienie użytkownicy są skłonni zaakceptować. Pozostały zapas to bufor kontrolowanego obniżania jakości — jeśli dowolny etap wykorzysta cały przydział, w kolejnych etapach zaczyna się upraszczać działanie, aby zmieścić się w ogólnym budżecie.
# Total user-facing SLA: 8000ms
BUDGET_TOTAL_MS = 8000
BUDGET_STAGES = {
'retrieval': 1500, # vector search + rerank
'llm_call': 5500, # token streaming
'formatting': 500, # post-processing
'slack': 500, # buffer for overhead
}
assert sum(BUDGET_STAGES.values()) == BUDGET_TOTAL_MSŚledzenie wykorzystania budżetu
Należy używać obiektu BudgetTracker, który rejestruje czas rozpoczęcia i sprawdza pozostały budżet przy każdym przejściu między etapami. Przed rozpoczęciem etapu trzeba zweryfikować, czy pozostało wystarczająco dużo czasu. Dzięki temu kolejne etapy mogą się dostosować — jeśli pobieranie danych zajmie 1200 ms z przydzielonych 1500 ms, pozostanie tylko 300 ms zapasu, co powinno uruchomić prostszy prompt dla LLM lub pominięcie etapu ponownego szeregowania.
import time
class BudgetTracker:
def __init__(self, total_ms: float):
self.start = time.perf_counter()
self.total_ms = total_ms
def elapsed_ms(self) -> float:
return (time.perf_counter() - self.start) * 1000
def remaining_ms(self) -> float:
return self.total_ms - self.elapsed_ms()
def check(self, stage: str, required_ms: float = 0) -> bool:
remaining = self.remaining_ms()
if remaining < required_ms:
print(f'Budget exhausted before {stage}: {remaining:.0f}ms left, need {required_ms}ms')
return False
return TrueDefinicja kontrolowanego obniżania jakości
Kontrolowane obniżanie jakości oznacza dostarczenie odpowiedzi o niższej jakości, ale nadal użytecznej, gdy pełny potok nie może zakończyć działania w ramach budżetu, zamiast zwracania błędu. Przykłady to: zwrócenie odpowiedzi z pamięci podręcznej, pominięcie ponownego szeregowania, skrócenie okna kontekstu, użycie szybszego, ale mniej dokładnego modelu lub zwrócenie wcześniej przygotowanego komunikatu awaryjnego. Celem jest zawsze przekazanie użytkownikowi jakiejś odpowiedzi, a nie pozostawienie go bez odpowiedzi.
# Degradation ladder for a RAG chat endpoint:
# Level 0 (normal): retrieve 10 chunks + rerank + GPT-4o -- 8000ms budget
# Level 1 (fast): retrieve 5 chunks + skip rerank + GPT-4o -- 5000ms budget
# Level 2 (minimal): retrieve 3 chunks + GPT-4o-mini -- 3000ms budget
# Level 3 (cached): return semantic cache hit -- 100ms
# Level 4 (sorry): return static 'Try again in a moment' -- 1msImplementowanie drabiny obniżania jakości
W każdym punkcie decyzyjnym potoku należy sprawdzić pozostały budżet i wybrać odpowiedni poziom jakości. Poniższy kod wybiera głębokość pobierania danych i model na podstawie pozostałego budżetu. Dzięki temu przy normalnym obciążeniu użytkownicy otrzymują najwyższą jakość, a w okresach dużych opóźnień nadal dostają użyteczną odpowiedź zamiast błędu przekroczenia limitu czasu.
async def smart_rag_query(question: str, budget_ms: float = 8000) -> str:
tracker = BudgetTracker(budget_ms)
# Retrieval stage
if tracker.remaining_ms() > 5000:
chunks = await retrieve_and_rerank(question, top_k=10)
elif tracker.remaining_ms() > 3000:
chunks = await retrieve(question, top_k=5) # skip rerank
elif tracker.remaining_ms() > 1500:
chunks = await retrieve(question, top_k=3) # minimal retrieval
else:
return await get_cached_or_static(question)
# LLM stage
if tracker.remaining_ms() > 4000:
model = 'gpt-4o'
else:
model = 'gpt-4o-mini' # faster fallback
timeout = tracker.remaining_ms() / 1000 - 0.5
return await generate_answer(question, chunks, model, timeout)Ustawianie limitów czasu na poziomie wywołania API
Zawsze należy ustawiać jawne limity czasu dla każdego zewnętrznego wywołania API. OpenAI Python SDK przyjmuje parametr timeout w sekundach. Należy ustawić go nieco poniżej pozostałego budżetu, aby pozostał czas na obsłużenie wyjątku i ewentualne kontrolowane obniżenie jakości przed upływem ostatecznego terminu odpowiedzi. Nigdy nie należy polegać na domyślnym limicie czasu SDK — może być zbyt długi dla żądań obsługujących użytkowników.
async def generate_answer(question: str, chunks: list, model: str, timeout_sec: float) -> str:
context = '\n\n'.join(chunks)
prompt = f'Answer using this context:\n{context}\n\nQuestion: {question}'
try:
resp = await client.chat.completions.create(
model=model,
messages=[{'role': 'user', 'content': prompt}],
max_tokens=500,
timeout=max(timeout_sec, 1.0) # minimum 1 second
)
return resp.choices[0].message.content
except openai.APITimeoutError:
return 'I was unable to generate a response in time. Please try again.'Zwracanie częściowych odpowiedzi strumieniowych
W przypadku przesyłania strumieniowego można zwracać częściowe odpowiedzi wygenerowane przed upływem budżetu. Gdy w trakcie strumienia wystąpi przekroczenie limitu czasu, należy przestać odczytywać nowe tokeny, dodać wielokropek lub krótki prompt kontynuacji i zamknąć strumień. Użytkownik zobaczy odpowiedź, która kończy się w uporządkowany sposób, zamiast pustego błędu. Jest to możliwe tylko w przypadku przesyłania strumieniowego — wywołania bez strumieniowania są operacjami typu „wszystko albo nic”.
async def stream_with_budget(question: str, budget_ms: float):
tracker = BudgetTracker(budget_ms)
collected = []
stream = await client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': question}],
stream=True
)
async for chunk in stream:
if tracker.remaining_ms() < 200: # 200ms safety margin
collected.append(' [response truncated]')
break
delta = chunk.choices[0].delta.content or ''
collected.append(delta)
yield delta
# Ensure stream is closed even if budget exceeded
await stream.close()Pamięć semantyczna jako warstwa obniżania jakości
Pamięć semantyczna jest doskonałą warstwą obniżania jakości, ponieważ zapewnia niemal zerowe opóźnienie. Przed wywołaniem LLM należy przeszukać pamięć semantyczną pod kątem podobnych wcześniejszych pytań. Jeśli zostanie znalezione dopasowanie o wysokim podobieństwie (powyżej podobieństwa cosinusowego 0,92), należy natychmiast zwrócić zapisaną odpowiedź. Przyspiesza to odpowiedzi i zapewnia natychmiastowy tryb awaryjny, gdy LLM działa wolno lub jest niedostępny.
async def query_with_cache_fallback(question: str, budget_ms: float = 8000) -> str:
# Try semantic cache first (fast)
cached = await semantic_cache.lookup(question, threshold=0.92)
if cached:
return cached.response
tracker = BudgetTracker(budget_ms)
# Try full pipeline
if tracker.remaining_ms() > 3000:
try:
return await smart_rag_query(question, tracker.remaining_ms())
except Exception:
pass # fall through to static response
# Last resort
return 'I am experiencing high load right now. Please try again in a moment.'Rejestrowanie zdarzeń obniżenia jakości
Za każdym razem, gdy potok przechodzi na niższy poziom jakości, należy zarejestrować to jako zdarzenie strukturalne. Należy uwzględnić osiągnięty poziom obniżenia jakości, budżet pozostały na każdym etapie oraz końcowe opóźnienie. Analiza tych logów pokazuje, jak często uruchamiany jest każdy poziom obniżenia jakości, co pomaga dostrajać budżety, identyfikować etapy stale przekraczające budżet oraz uzasadniać inwestycje w infrastrukturę.
import structlog
log = structlog.get_logger()
def log_degradation(level: int, stage: str, remaining_ms: float, total_ms: float):
log.warning(
'pipeline_degradation',
degradation_level=level,
triggered_at_stage=stage,
remaining_budget_ms=round(remaining_ms),
total_budget_ms=total_ms,
budget_consumed_pct=round((total_ms - remaining_ms) / total_ms * 100)
)Kształtowanie oczekiwań użytkownika za pomocą sygnałów interfejsu
Podczas dostarczania odpowiedzi o obniżonej jakości należy zasygnalizować użytkownikowi, że może ona być gorsza niż zwykle. W interfejsie czatu można wyświetlić subtelny wskaźnik, taki jak „Tryb szybkiej odpowiedzi — niektóre szczegóły mogą być ograniczone”. W przypadku API ekstrakcji należy uwzględnić w odpowiedzi JSON pole degraded: true, aby odbiorcy korzystający z danych mogli inaczej obsłużyć wyniki o obniżonej jakości. Przejrzystość pomaga zachować zaufanie użytkowników nawet podczas awarii.
from pydantic import BaseModel
from typing import Optional
class ChatResponse(BaseModel):
content: str
degraded: bool = False
degradation_level: Optional[int] = None # 0=full, 1=fast, 2=minimal, 3=cached
latency_ms: int
# API response when degraded:
# {
# 'content': 'Here is a brief answer...',
# 'degraded': true,
# 'degradation_level': 2,
# 'latency_ms': 2800
# }Dostrajanie przydziałów budżetu w czasie
Początkowe przydziały budżetu są jedynie szacunkami. Po tygodniu działania na produkcji należy przeanalizować rozkład czasu spędzanego w każdym etapie, korzystając z danych śledzenia. Jeśli pobieranie danych stale zajmuje 800 ms zamiast zaplanowanych 1500 ms, należy przekazać ten zapas etapowi LLM, umożliwiając wygenerowanie większej liczby tokenów lub użycie większego okna kontekstu. Dostrajanie budżetu jest stałym zadaniem operacyjnym, a nie jednorazową konfiguracją.
# Budget tuning based on production p95 data:
ACTUAL_P95 = {
'retrieval': 780, # vs budget 1500ms -> 720ms headroom
'llm_call': 4200, # vs budget 5500ms -> 1300ms headroom
'formatting': 120, # vs budget 500ms -> 380ms headroom
}
TOTAL_HEADROOM = sum(
BUDGET_STAGES[k] - ACTUAL_P95[k] for k in ACTUAL_P95
)
print(f'Total headroom: {TOTAL_HEADROOM}ms')
# Reallocate headroom to allow longer LLM responsesSzybkie sprawdzenie
Sprawdź swoją wiedzę na temat budżetów czasu i kontrolowanego obniżania jakości.
Podsumowanie lekcji
W tej lekcji poznano: budżety czasu, które rozdzielają czas między etapy potoku, aby odpowiedź zawsze mieściła się w akceptowalnym terminie; drabiny obniżania jakości, które po wyczerpaniu czasu dostarczają odpowiedzi o stopniowo niższej jakości zamiast błędów; oraz rejestrowanie zdarzeń obniżenia jakości, które pomaga identyfikować i usuwać chroniczne wąskie gardła. W następnej części omówimy wzorzec LLM-as-judge do automatycznej oceny jakości.
Często zadawane pytania
Czy lekcja „Budżety czasu oczekiwania i kontrolowana degradacja” jest bezpłatna?
Tak — pełny tekst „Budżety czasu oczekiwania i kontrolowana degradacja” 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 „Budżety czasu oczekiwania i kontrolowana degradacja”?
Ustaw rygorystyczne budżety timeoutów na każdej warstwie potoku i zaimplementuj kontrolowaną degradację, która zwraca odpowiedzi z pamięci podręcznej lub uproszczone odpowiedzi, gdy LLM przekroczy bu… Ć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 4 z 4.
Ile czasu zajmuje lekcja „Budżety czasu oczekiwania i kontrolowana degradacja”?
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
- Pomiar opóźnienia LLM: TTFT i TPOT
- Równoważenie obciążenia i strategie wielu kluczy
- Dostawcy zapasowi i circuit breakers
- Budżety czasu oczekiwania i kontrolowana degradacja