Batchowanie, routing modeli i pulpity kosztów
Kieruj proste żądania do tańszych modeli, takich jak GPT-4o-mini, a złożone do GPT-4o, grupuj niepilne żądania i zbuduj pulpit kosztów śledzący wydatki według funkcji.
Batchowanie, routing modeli i pulpity kosztów 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.
Trzy kolejne sposoby optymalizacji kosztów
Poza buforowaniem trzy dodatkowe strategie mogą znacząco zmniejszyć koszty działania LLM: przetwarzanie wsadowe (odraczanie niepilnych żądań i przesyłanie ich zbiorczo po niższej stawce API), routing modeli (kierowanie prostych zapytań do tanich modeli, a złożonych do wydajnych modeli) oraz pulpity kosztów (śledzenie wydatków dla poszczególnych funkcji w celu określenia, gdzie optymalizacje zapewnią najwyższy zwrot z inwestycji). Łącznie mogą one zmniejszyć koszty o kolejne 40–60 procent, ponad oszczędności wynikające z buforowania.
OpenAI Batch API: 50% zniżki dla zadań asynchronicznych
Batch API OpenAI przyjmuje plik JSONL zawierający maksymalnie 50 000 żądań i przetwarza je asynchronicznie w ciągu 24 godzin, po cenie stanowiącej 50 procent standardowej ceny. Funkcja ta doskonale nadaje się do zadań nieinteraktywnych: tworzenia embeddingów dużych korpusów dokumentów, generowania opisów produktów, przeprowadzania nocnych ewaluacji lub wstępnego przetwarzania danych treningowych. Kompromisem jest opóźnienie — wyniki są dostępne po kilku godzinach, a nie natychmiast.
import json
from openai import OpenAI
client = OpenAI()
# Prepare batch file
requests = [
{
'custom_id': f'req_{i}',
'method': 'POST',
'url': '/v1/chat/completions',
'body': {
'model': 'gpt-4o-mini',
'messages': [
{'role': 'user', 'content': f'Summarize: {document}'}
],
'max_tokens': 150,
}
}
for i, document in enumerate(documents_to_process)
]
# Write JSONL batch file
with open('/tmp/batch_requests.jsonl', 'w') as f:
for req in requests:
f.write(json.dumps(req) + '\n')
# Upload and submit batch
with open('/tmp/batch_requests.jsonl', 'rb') as f:
batch_file = client.files.create(file=f, purpose='batch')
batch = client.batches.create(
input_file_id=batch_file.id,
endpoint='/v1/chat/completions',
completion_window='24h',
)
print(f'Batch {batch.id} submitted, status: {batch.status}')Pobieranie wyników przetwarzania wsadowego
Po przesłaniu partii należy sprawdzać jej status aż do zakończenia (status zmienia się z in_progress na completed). Po zakończeniu należy pobrać plik wyjściowy zawierający wyniki wszystkich żądań. Każdy wiersz wyjściowy jest obiektem JSON zawierającym custom_id z żądania oraz pole response albo error — należy zawsze obsługiwać oba przypadki, ponieważ poszczególne żądania w partii mogą kończyć się niezależnie niepowodzeniem.
import time
def wait_for_batch(batch_id: str, poll_interval: int = 60) -> str:
while True:
batch = client.batches.retrieve(batch_id)
print(f'Status: {batch.status}, completed: {batch.request_counts.completed}')
if batch.status == 'completed':
return batch.output_file_id
elif batch.status == 'failed':
raise RuntimeError(f'Batch failed: {batch.errors}')
time.sleep(poll_interval)
def download_batch_results(output_file_id: str) -> list[dict]:
content = client.files.content(output_file_id)
results = []
for line in content.text.strip().split('\n'):
results.append(json.loads(line))
return results
output_file_id = wait_for_batch(batch.id)
results = download_batch_results(output_file_id)
for result in results[:3]:
print(result['custom_id'], result.get('response', {}).get('body', {}).get('choices', [{}])[0])Routing modeli: dopasowanie złożoności do rozmiaru modelu
Routing modeli przypisuje każde żądanie najtańszemu modelowi, który potrafi je dobrze obsłużyć. GPT-4o-mini kosztuje około 30 razy mniej niż GPT-4o, ale równie dobrze radzi sobie z prostą klasyfikacją, ekstrakcją i krótkimi zadaniami typu Q&A. Proste, ustrukturyzowane zadania należy kierować do małych, tanich modeli, a złożone rozumowanie, syntezę długiego kontekstu i generowanie wymagające wyczucia — do dużych, wydajnych modeli. Skierowanie choćby 60 procent ruchu do taniego modelu pozwala znacznie obniżyć koszty.
CHEAP_MODEL = 'gpt-4o-mini'
POWERFUL_MODEL = 'gpt-4o'
def classify_query_complexity(query: str) -> str:
# Heuristic-based routing (replace with ML classifier for production)
words = query.split()
has_code = any(c in query for c in ['```', 'def ', 'class ', 'SELECT ', 'function '])
is_multi_step = any(w in query.lower() for w in ['compare', 'analyze', 'explain why', 'evaluate'])
is_long = len(words) > 50
if has_code or is_multi_step or is_long:
return POWERFUL_MODEL
return CHEAP_MODEL
def routed_completion(messages: list[dict]) -> str:
user_query = messages[-1].get('content', '')
model = classify_query_complexity(user_query)
print(f'Routing to: {model}')
response = client.chat.completions.create(model=model, messages=messages)
return response.choices[0].message.contentRouting oparty na LLM dla większej dokładności
Routing heurystyczny jest szybki, ale zawodny. Dokładniejsze podejście polega na użyciu małego, taniego modelu klasyfikacyjnego, który decyduje, do którego modelu skierować żądanie. Należy dostroić mały model na przykładach prostych i złożonych zapytań z danej domeny albo użyć promptingu few-shot z wykorzystaniem samego GPT-4o-mini. Wywołanie klasyfikatora kosztuje kilkaset tokenów wejściowych — znacznie mniej niż błędne skierowanie złożonego zapytania do taniego modelu, który wygeneruje nieprawidłową odpowiedź.
CLASSIFIER_SYSTEM = '''You are a query complexity classifier.
Classify the user query as SIMPLE or COMPLEX.
SIMPLE: factual lookup, extraction, classification with clear answer.
COMPLEX: multi-step reasoning, synthesis, comparison, code generation, long-form writing.
Reply with just SIMPLE or COMPLEX.'''
def llm_classify_complexity(query: str) -> str:
response = client.chat.completions.create(
model='gpt-4o-mini', # use cheap model for routing
messages=[
{'role': 'system', 'content': CLASSIFIER_SYSTEM},
{'role': 'user', 'content': query},
],
max_tokens=10,
temperature=0,
)
label = response.choices[0].message.content.strip()
return POWERFUL_MODEL if label == 'COMPLEX' else CHEAP_MODELŚledzenie kosztu poszczególnych funkcji
Aby wiedzieć, na czym skupić działania optymalizacyjne, należy śledzić koszt poszczególnych funkcji aplikacji, a nie tylko całkowite wydatki. Każde wywołanie LLM należy opatrzyć etykietą funkcji i sumować koszty tokenów według tych etykiet. Funkcja 'search_summarization' może pochłaniać 40 procent budżetu, obsługując zaledwie 5 procent ruchu, co czyni ją priorytetowym celem optymalizacji. Funkcja 'user_onboarding' może być droga, ale obsługiwać wartościowy proces, którego nie chcą Państwo pogorszyć.
from collections import defaultdict
cost_tracker = defaultdict(lambda: {'prompt_tokens': 0, 'completion_tokens': 0, 'cost_usd': 0.0})
MODEL_PRICING = {
'gpt-4o-mini': {'input': 0.15 / 1e6, 'output': 0.60 / 1e6},
'gpt-4o': {'input': 2.50 / 1e6, 'output': 10.00 / 1e6},
}
def tracked_completion(feature: str, messages: list[dict], model: str = 'gpt-4o-mini') -> str:
response = client.chat.completions.create(model=model, messages=messages)
usage = response.usage
pricing = MODEL_PRICING.get(model, {'input': 0, 'output': 0})
cost = usage.prompt_tokens * pricing['input'] + usage.completion_tokens * pricing['output']
cost_tracker[feature]['prompt_tokens'] += usage.prompt_tokens
cost_tracker[feature]['completion_tokens'] += usage.completion_tokens
cost_tracker[feature]['cost_usd'] += cost
return response.choices[0].message.content
def print_cost_report():
print(f'{"Feature":<30} {"Prompt":<10} {"Completion":<12} {"Cost USD":<12}')
for feature, stats in sorted(cost_tracker.items(), key=lambda x: -x[1]['cost_usd']):
print(f'{feature:<30} {stats["prompt_tokens"]:<10} {stats["completion_tokens"]:<12} ${stats["cost_usd"]:.4f}')Tworzenie prostego panelu kosztów
Praktyczny panel kosztów agreguje dane o wydatkach na poziomie funkcji i udostępnia je za pomocą prostego endpointu HTTP. Należy przechowywać skumulowane koszty w Redisie, używając kluczy z dziennym podsumowaniem, aby móc śledzić wydatki w czasie. Warto dodać ten panel do wewnętrznych narzędzi deweloperskich, aby zespół mógł niemal w czasie rzeczywistym obserwować wpływ wdrażanych funkcji na koszty i wykrywać gwałtowny wzrost wydatków, zanim przełoży się on na wysoki rachunek.
from fastapi import FastAPI
import datetime
app = FastAPI()
async def record_cost(feature: str, model: str, prompt_tokens: int, completion_tokens: int):
pricing = MODEL_PRICING.get(model, {'input': 0, 'output': 0})
cost = prompt_tokens * pricing['input'] + completion_tokens * pricing['output']
today = datetime.date.today().isoformat()
key = f'cost:{today}:{feature}:{model}'
await async_r.incrbyfloat(key, cost)
await async_r.expire(key, 86400 * 30) # keep 30 days
@app.get('/dashboard/costs')
async def cost_dashboard():
today = datetime.date.today().isoformat()
pattern = f'cost:{today}:*'
costs = {}
async for key in async_r.scan_iter(match=pattern):
value = await async_r.get(key)
parts = key.split(':')
feature_model = ':'.join(parts[2:])
costs[feature_model] = float(value or 0)
return {'date': today, 'costs': costs, 'total': sum(costs.values())}Miesięczne alerty budżetowe
Należy ustawić miesięczne alerty budżetowe, aby wykrywać nieoczekiwane skoki kosztów, zanim przełożą się one na wysokie rachunki. Trzeba obliczać kroczące dzienne wydatki na podstawie modułu śledzenia kosztów, prognozować je do końca miesiąca i wysyłać alert na Slacku, gdy prognoza przekroczy ustalony próg budżetowy. Prosta prognoza — daily_spend * days_remaining — wcześnie wykrywa gwałtowny wzrost liczby żądań, nawet jeśli rzeczywiste wzorce nie są liniowe.
import datetime
import httpx
SLACK_WEBHOOK = 'https://hooks.slack.com/services/YOUR/WEBHOOK'
MONTHLY_BUDGET_USD = 500.0
async def check_budget_alert():
today = datetime.date.today()
days_in_month = 30
day_of_month = today.day
days_remaining = days_in_month - day_of_month
# Sum today's costs
today_total = sum(cost_tracker[f]['cost_usd'] for f in cost_tracker)
avg_daily = today_total # simplified: just today's spend
projected_month = avg_daily * days_in_month
if projected_month > MONTHLY_BUDGET_USD:
message = (
f'LLM Budget Alert: Projected monthly spend ${projected_month:.2f} '
f'exceeds budget ${MONTHLY_BUDGET_USD:.2f}. '
f'Today spend: ${today_total:.2f}'
)
async with httpx.AsyncClient() as client:
await client.post(SLACK_WEBHOOK, json={'text': message})Kolejka żądań do zarządzania limitami szybkości
Gdy ruch gwałtownie rośnie, żądania napotykają limity szybkości OpenAI i kończą się błędem 429 Too Many Requests. Kolejka żądań buforuje przychodzące żądania i wysyła je ze sterowaną szybkością, wyrównując szczyty ruchu. W środowisku produkcyjnym należy użyć kolejki asynchronicznej opartej na Redisie lub brokerze komunikatów, takim jak RabbitMQ, oraz zaimplementować logikę ponawiania z wykładniczym zwiększaniem opóźnienia dla tymczasowych błędów 429.
import asyncio
from asyncio import Queue
class RateLimitedLLMClient:
def __init__(self, requests_per_minute: int = 500):
self.rpm = requests_per_minute
self.queue: Queue = Queue(maxsize=1000)
self.interval = 60.0 / requests_per_minute
async def start(self):
asyncio.create_task(self._worker())
async def _worker(self):
while True:
request_fn, future = await self.queue.get()
try:
result = await request_fn()
future.set_result(result)
except Exception as e:
future.set_exception(e)
await asyncio.sleep(self.interval)
async def submit(self, request_fn) -> str:
loop = asyncio.get_event_loop()
future = loop.create_future()
await self.queue.put((request_fn, future))
return await futureKompletny stos optymalizacji kosztów
Kompletny stos optymalizacji kosztów LLM działa warstwowo: pamięć podręczna dokładnych dopasowań eliminuje wywołania dla identycznych, powtarzających się zapytań, pamięć podręczna semantyczna eliminuje wywołania dla podobnych zapytań, buforowanie prefiksów obniża koszt danych wejściowych dla wszystkich pozostałych wywołań, routing modeli kieruje proste zapytania do tanich modeli, przetwarzanie wsadowe odracza niepilne zadania, zapewniając 50-procentową zniżkę, a panele i alerty zapewniają widoczność kosztów i utrzymują je w ryzach. Należy wdrażać te rozwiązania stopniowo, zaczynając od tych, które mają największy wpływ w danej aplikacji.
# Decision framework for cost optimization priority:
#
# 1. Enable prefix caching (free, zero effort, automatic)
# 2. Add exact caching (high hit rate for FAQ/support bots)
# 3. Add model routing (simple heuristics first, ML classifier later)
# 4. Add semantic caching (complex, high ROI for paraphrase-heavy use cases)
# 5. Enable batch API (only for non-real-time pipelines)
# 6. Build cost dashboard (essential for ongoing monitoring)
#
# Typical combined result in a customer support bot:
# Before: $1,000/month
# After step 1-2: $400/month (-60%)
# After step 3-4: $200/month (-50% of remaining)
# After step 5-6: $150/month and visibleKaskadowe przełączanie awaryjne po błędzie taniego modelu
Podczas kierowania żądań do taniego modelu należy obsłużyć sytuacje, w których generuje on niezadowalającą odpowiedź. Należy zaimplementować kontrolę jakości wyniku taniego modelu — sprawdzać długość odpowiedzi, obecność wymaganych pól lub uruchamiać szybki wynik LLM-as-judge — i automatycznie przełączać się na wydajny model, jeśli jakość jest niewystarczająca. Taki mechanizm bezpieczeństwa pozwala śmielej kierować żądania do tanich modeli bez ryzyka pogorszenia jakości obsługi użytkownika.
async def routing_with_fallback(messages: list[dict], min_length: int = 50) -> str:
# Try cheap model first
cheap_response = await async_client.chat.completions.create(
model=CHEAP_MODEL, messages=messages, temperature=0.0
)
answer = cheap_response.choices[0].message.content
# Quality check: response too short indicates poor answer
if len(answer.strip()) < min_length:
print(f'Cheap model answer too short ({len(answer)} chars), escalating...')
powerful_response = await async_client.chat.completions.create(
model=POWERFUL_MODEL, messages=messages, temperature=0.0
)
return powerful_response.choices[0].message.content
return answerSzybkie sprawdzenie
Sprawdź swoją wiedzę na temat przetwarzania wsadowego, routingu modeli i paneli kosztów z tej lekcji.
Podsumowanie lekcji
W tej lekcji dowiedział się Pan / dowiedziała się Pani, że: OpenAI Batch API zapewnia 50-procentową zniżkę w przypadku asynchronicznych zadań niewymagających działania w czasie rzeczywistym, routing modeli wykorzystuje tanie modele, takie jak GPT-4o-mini, do prostych zadań, a drogie modele do złożonych, natomiast śledzenie kosztów według funkcji pokazuje, które części aplikacji zużywają największą część budżetu, dzięki czemu można skutecznie ustalać priorytety optymalizacji. W połączeniu ze strategiami buforowania z poprzednich lekcji techniki te mogą obniżyć koszty infrastruktury LLM o 60–80 procent. Kurs dotyczący buforowania LLM i optymalizacji kosztów został ukończony.
Często zadawane pytania
Czy lekcja „Batchowanie, routing modeli i pulpity kosztów” jest bezpłatna?
Tak — pełny tekst „Batchowanie, routing modeli i pulpity kosztów” 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 „Batchowanie, routing modeli i pulpity kosztów”?
Kieruj proste żądania do tańszych modeli, takich jak GPT-4o-mini, a złożone do GPT-4o, grupuj niepilne żądania i zbuduj pulpit kosztów śledzący wydatki według funkcji. Ć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 „Batchowanie, routing modeli i pulpity kosztów”?
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
- Dokładne buforowanie za pomocą Redis
- Buforowanie semantyczne z embeddingami
- Buforowanie prefiksów promptów OpenAI
- Batchowanie, routing modeli i pulpity kosztów