Lotes, roteamento de modelos e painéis de custos
Encaminhe solicitações simples para modelos mais baratos, como GPT-4o-mini, e as complexas para GPT-4o; agrupe solicitações não urgentes e crie um painel de custos que acompanhe os gastos por funcionalidade.
Lotes, roteamento de modelos e painéis de custos é uma aula grátis de AI Engineering Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Engineering Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Engineering Academy inclui 4 aulas no total.
Mais três estratégias para otimização de custos
Depois do armazenamento em cache, três estratégias adicionais reduzem drasticamente os custos operacionais dos LLMs: processamento em lotes (adiar solicitações não urgentes e enviá-las em massa a uma tarifa menor da API), roteamento de modelos (encaminhar consultas simples para modelos baratos e consultas complexas para modelos mais potentes) e painéis de custos (acompanhar os gastos por recurso para identificar onde as otimizações têm o maior ROI). Juntas, essas estratégias podem reduzir os custos em mais 40–60 por cento, além da economia obtida com o armazenamento em cache.
API Batch da OpenAI: 50% de desconto para cargas de trabalho assíncronas
A API Batch da OpenAI aceita um arquivo JSONL contendo até 50.000 solicitações e as processa de forma assíncrona em até 24 horas, por 50 por cento do preço padrão. Ela é ideal para cargas de trabalho não interativas: gerar embeddings de grandes coleções de documentos, criar descrições de produtos, executar avaliações noturnas ou pré-processar dados de treinamento. A desvantagem é a latência — os resultados ficam disponíveis horas depois, e não imediatamente.
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}')Consultando os resultados do lote
Depois de enviar um lote, consulte o status até que ele seja concluído (o status muda de in_progress para completed). Quando terminar, baixe o arquivo de saída que contém os resultados de todas as solicitações. Cada linha da saída é um objeto JSON com o custom_id da solicitação e um campo response ou error — trate sempre os dois casos, pois solicitações individuais dentro de um lote podem falhar de forma independente.
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])Roteamento de modelos: associe a complexidade ao tamanho do modelo
O roteamento de modelos atribui cada solicitação ao modelo mais barato capaz de processá-la bem. O GPT-4o-mini custa cerca de 30 vezes menos que o GPT-4o, mas lida igualmente bem com tarefas simples de classificação, extração e perguntas e respostas curtas. Encaminhe tarefas simples e estruturadas para modelos pequenos e baratos, e raciocínio complexo, síntese de contextos longos e geração refinada para modelos grandes e potentes. Mesmo encaminhar 60 por cento do tráfego para um modelo barato gera uma economia significativa.
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.contentRoteamento baseado em LLM para maior precisão
O roteamento heurístico é rápido, mas frágil. Uma abordagem mais precisa usa um modelo de classificação pequeno e barato para decidir para qual modelo encaminhar a solicitação. Ajuste um modelo pequeno com exemplos de consultas simples e complexas do seu domínio ou use prompting com poucos exemplos usando o próprio GPT-4o-mini. A chamada do classificador custa algumas centenas de tokens de entrada — muito menos do que encaminhar incorretamente uma consulta complexa para um modelo barato que produz uma resposta errada.
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_MODELAcompanhamento do custo por funcionalidade
Para saber onde concentrar os esforços de otimização, é necessário acompanhar o custo por funcionalidade da aplicação, não apenas o gasto total. Envolva cada chamada de LLM com uma etiqueta de funcionalidade e acumule os custos de tokens por etiqueta. A funcionalidade 'resumo_de_pesquisa' pode consumir 40 por cento do seu orçamento, embora atenda apenas 5 por cento do tráfego, tornando-se um alvo prioritário de otimização. A funcionalidade 'integração_de_usuários' pode ser cara, mas atender a um fluxo de alto valor que você não deseja prejudicar.
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}')Criação de um painel de custos simples
Um painel de custos prático agrega dados de gastos por funcionalidade e os disponibiliza por meio de um endpoint HTTP simples. Armazene os custos acumulados no Redis usando chaves de consolidação diária para acompanhar a evolução dos gastos ao longo do tempo. Adicione esse painel às ferramentas internas de desenvolvimento para que a equipe possa ver o impacto das versões das funcionalidades nos custos quase em tempo real e detectar gastos descontrolados antes que se transformem em uma conta alta.
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())}Alertas de orçamento mensal
Configure alertas de orçamento mensal para detectar picos inesperados de custos antes que se transformem em contas altas. Calcule um gasto diário móvel a partir do seu rastreador de custos, projete-o até o fim do mês e envie um alerta no Slack quando a projeção ultrapassar o limite do orçamento. Uma projeção simples — gasto_diário * dias_restantes — detecta solicitações descontroladas rapidamente, mesmo quando os padrões reais não são lineares.
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})Fila de solicitações para gerenciar limites de taxa
Quando o tráfego aumenta, as solicitações atingem os limites de taxa da OpenAI e falham com 429 Too Many Requests. Uma fila de solicitações armazena temporariamente as solicitações recebidas e as envia em uma taxa controlada, suavizando os picos de tráfego. Use uma fila assíncrona apoiada pelo Redis ou por um intermediário de mensagens como o RabbitMQ em produção e implemente uma lógica de nova tentativa com espera progressiva para erros 429 temporários.
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 futureReunindo tudo: conjunto de otimização de custos
Um conjunto completo de otimização de custos de LLM opera em camadas: o cache exato elimina chamadas para consultas idênticas repetidas, o cache semântico elimina chamadas para consultas semelhantes, o cache de prefixos reduz o custo de entrada de todas as chamadas restantes, o roteamento de modelos usa modelos baratos para consultas simples, o processamento em lotes adia trabalhos não urgentes com 50 por cento de desconto, e painéis e alertas mantêm os custos visíveis e sob controle. Implemente esses recursos gradualmente, na ordem de impacto para a sua aplicação específica.
# 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 visibleFallback em cascata quando um modelo barato falha
Ao encaminhar uma solicitação para um modelo barato, é necessário lidar com os casos em que ele produz uma resposta insatisfatória. Implemente uma verificação de qualidade na saída do modelo barato — verifique o tamanho da resposta, a presença dos campos obrigatórios ou execute uma avaliação rápida por um LLM — e use automaticamente o modelo potente como fallback se a qualidade for insuficiente. Essa rede de segurança permite encaminhar solicitações de forma agressiva para modelos baratos sem correr o risco de prejudicar a experiência do usuário.
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 answerVerificação rápida
Teste sua compreensão sobre processamento em lotes, roteamento de modelos e painéis de custos nesta lição.
Recapitulação da lição
Nesta lição, você aprendeu que a API de lotes da OpenAI oferece 50 por cento de desconto para cargas de trabalho assíncronas que não precisam de tempo real; o roteamento de modelos usa modelos baratos, como o GPT-4o-mini, para tarefas simples e modelos caros para tarefas complexas; e o acompanhamento de custos por funcionalidade revela quais partes da sua aplicação consomem mais orçamento, permitindo priorizar otimizações com eficiência. Combinadas com as estratégias de cache das lições anteriores, essas técnicas podem reduzir os custos de infraestrutura de LLM em 60 a 80 por cento. Você concluiu o curso de cache e otimização de custos de LLM.
Perguntas Frequentes
A aula “Lotes, roteamento de modelos e painéis de custos” é grátis?
Sim — o texto completo de “Lotes, roteamento de modelos e painéis de custos” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Engineering Academy, atualize para CoddyKit PRO. O curso de AI Engineering Academy inclui 4 aulas no total.
O que vou aprender em “Lotes, roteamento de modelos e painéis de custos”?
Encaminhe solicitações simples para modelos mais baratos, como GPT-4o-mini, e as complexas para GPT-4o; agrupe solicitações não urgentes e crie um painel de custos que acompanhe os gastos por funcion… Você pratica AI Engineering Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar AI Engineering Academy?
Nenhuma experiência prévia é necessária. AI Engineering Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “Lotes, roteamento de modelos e painéis de custos”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de AI Engineering Academy?
Sim. Cada aula de AI Engineering Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Armazenamento exato em cache com Redis
- Armazenamento semântico em cache com embeddings
- Armazenamento em cache de prefixos de prompts da OpenAI
- Lotes, roteamento de modelos e painéis de custos