AI Engineering Academy · Lezione

Batching, routing dei modelli e dashboard dei costi

Indirizzi le richieste semplici verso modelli più economici come GPT-4o-mini e quelle complesse verso GPT-4o, raggruppi le richieste non urgenti in batch e crei una dashboard dei costi che monitori la spesa per funzionalità.

Lezione 4 di 413 passaggi

Batching, routing dei modelli e dashboard dei costi è una lezione AI Engineering Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Engineering Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Engineering Academy include 4 lezioni in totale.

Altri tre strumenti per ottimizzare i costi

Dopo il caching, tre strategie aggiuntive riducono drasticamente i costi operativi degli LLM: il batching (rinviare le richieste non urgenti e inviarle in blocco a una tariffa API inferiore), il model routing (indirizzare le query semplici verso modelli economici e quelle complesse verso modelli più potenti) e i dashboard dei costi (monitorare la spesa per funzionalità per individuare gli ambiti in cui le ottimizzazioni offrono il ROI più elevato). Insieme, queste strategie possono ridurre i costi di un ulteriore 40-60 percento oltre ai risparmi ottenuti con il caching.

OpenAI Batch API: 50% di sconto per i carichi di lavoro asincroni

La Batch API di OpenAI accetta un file JSONL contenente fino a 50.000 richieste ed esegue l'elaborazione in modo asincrono entro 24 ore al 50 percento del prezzo standard. È ideale per i carichi di lavoro non interattivi: creare gli embedding di grandi raccolte di documenti, generare descrizioni di prodotti, eseguire valutazioni notturne o pre-elaborare dati di addestramento. Il compromesso è la latenza: i risultati sono disponibili dopo alcune ore, non immediatamente.

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}')

Eseguire il polling dei risultati del batch

Dopo aver inviato un batch, controllarne lo stato finché non viene completato (lo stato passa da in_progress a completed). Al termine, scaricare il file di output contenente i risultati di tutte le richieste. Ogni riga dell'output è un oggetto JSON con il campo custom_id della richiesta e un campo response oppure error: gestire sempre entrambi, poiché le singole richieste all'interno di un batch possono fallire indipendentemente.

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])

Model routing: associare la complessità alle dimensioni del modello

Il routing dei modelli assegna ogni richiesta al modello meno costoso in grado di gestirla adeguatamente. GPT-4o-mini costa circa 30 volte meno di GPT-4o, ma gestisce altrettanto bene le attività semplici di classificazione, estrazione e domande e risposte brevi. Indirizzi le attività semplici e strutturate verso modelli piccoli ed economici e il ragionamento complesso, la sintesi di contesti estesi e la generazione con sfumature verso modelli grandi e potenti. Anche indirizzare il 60 percento del traffico verso un modello economico consente di risparmiare notevolmente.

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.content

Routing basato su LLM per una maggiore accuratezza

Il routing basato su euristiche è rapido, ma poco robusto. Un approccio più accurato utilizza un modello di classificazione piccolo ed economico per decidere a quale modello indirizzare la richiesta. Esegua il fine-tuning di un modello piccolo su esempi di query semplici e complesse nel proprio dominio oppure utilizzi il prompting few-shot con GPT-4o-mini stesso. La chiamata al classificatore costa poche centinaia di token di input, molto meno del costo di indirizzare per errore una query complessa a un modello economico che produce una risposta errata.

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

Monitoraggio dei costi per funzionalità

Per sapere su cosa concentrare gli sforzi di ottimizzazione, deve monitorare il costo per funzionalità dell'applicazione, non solo la spesa totale. Racchiuda ogni chiamata LLM in un tag della funzionalità e accumuli i costi dei token per tag. 'search_summarization' potrebbe consumare il 40 percento del budget servendo solo il 5 percento del traffico, diventando così un obiettivo prioritario per l'ottimizzazione. 'user_onboarding' potrebbe essere costoso, ma servire un flusso di grande valore che non desidera compromettere.

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}')

Creazione di una semplice dashboard dei costi

Una dashboard pratica dei costi aggrega i dati di spesa a livello di funzionalità e li espone tramite un semplice endpoint HTTP. Memorizzi i costi cumulativi in Redis utilizzando chiavi per i riepiloghi giornalieri, in modo da poter analizzare l'andamento della spesa nel tempo. Aggiunga questa dashboard agli strumenti interni per sviluppatori, così il team potrà vedere quasi in tempo reale l'impatto sui costi dei rilasci delle funzionalità e individuare una spesa fuori controllo prima che si trasformi in una fattura elevata.

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())}

Avvisi sul budget mensile

Imposti avvisi sul budget mensile per individuare aumenti imprevisti dei costi prima che si trasformino in fatture elevate. Calcoli una spesa giornaliera progressiva a partire dal sistema di monitoraggio dei costi, la proietti fino alla fine del mese e invii un avviso su Slack quando la proiezione supera la soglia del budget. Una semplice proiezione — daily_spend * days_remaining — individua tempestivamente le richieste fuori controllo, anche se gli andamenti effettivi non sono lineari.

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})

Coda delle richieste per la gestione dei limiti di frequenza

Quando il traffico aumenta improvvisamente, le richieste raggiungono i limiti di frequenza di OpenAI e falliscono con 429 Too Many Requests. Una coda delle richieste memorizza temporaneamente le richieste in arrivo e le invia a una velocità controllata, attenuando i picchi di traffico. In produzione, utilizzi una coda asincrona basata su Redis o su un message broker come RabbitMQ e implementi una logica di ritentativo con backoff esponenziale per gli errori 429 temporanei.

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 future

Combinare tutto: stack per l'ottimizzazione dei costi

Uno stack completo per l'ottimizzazione dei costi degli LLM opera su più livelli: la cache esatta elimina le chiamate per le query identiche ripetute, la cache semantica elimina le chiamate per le query simili, il caching del prefisso riduce il costo dell'input per tutte le chiamate rimanenti, il routing dei modelli utilizza modelli economici per le query semplici, il batching rimanda le attività non urgenti ottenendo uno sconto del 50 percento e dashboard e avvisi mantengono i costi visibili e sotto controllo. Li implementi gradualmente, in ordine di impatto per la propria applicazione.

# 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 visible

Fallback a cascata in caso di errore del modello economico

Quando indirizza una richiesta a un modello economico, deve gestire i casi in cui questo produca una risposta insoddisfacente. Implementi un controllo della qualità dell'output del modello economico: verifichi la lunghezza della risposta e la presenza dei campi obbligatori oppure esegua rapidamente una valutazione LLM-as-judge, quindi passi automaticamente al modello potente se la qualità è insufficiente. Questa rete di sicurezza consente di indirizzare con decisione le richieste verso modelli economici senza rischiare di compromettere l'esperienza utente.

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 answer

Verifica rapida

Verifichi la propria comprensione del batching, del routing dei modelli e delle dashboard dei costi presentati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: OpenAI Batch API offre uno sconto del 50 percento per i carichi di lavoro asincroni non in tempo reale, il routing dei modelli utilizza modelli economici come GPT-4o-mini per le attività semplici e modelli costosi per quelle complesse, mentre il monitoraggio dei costi per funzionalità mostra quali parti dell'applicazione consumano la maggior parte del budget, consentendo di stabilire efficacemente le priorità di ottimizzazione. Insieme alle strategie di caching delle lezioni precedenti, queste tecniche possono ridurre del 60-80 percento i costi dell'infrastruttura LLM. Ha completato il corso sul caching degli LLM e sull'ottimizzazione dei costi.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Batching, routing dei modelli e dashboard dei costi» è gratuita?

Sì — il testo completo di «Batching, routing dei modelli e dashboard dei costi» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Engineering Academy, passa a CoddyKit PRO. Il corso AI Engineering Academy include 4 lezioni in totale.

Cosa imparerò in «Batching, routing dei modelli e dashboard dei costi»?

Indirizzi le richieste semplici verso modelli più economici come GPT-4o-mini e quelle complesse verso GPT-4o, raggruppi le richieste non urgenti in batch e crei una dashboard dei costi che monitori l… Eserciti AI Engineering Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AI Engineering Academy?

Non è richiesta alcuna esperienza precedente. AI Engineering Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Batching, routing dei modelli e dashboard dei costi»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AI Engineering Academy?

Sì. Ogni lezione AI Engineering Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Caching esatto con Redis
  2. Caching semantico con gli embedding
  3. Caching dei prefissi dei prompt con OpenAI
  4. Batching, routing dei modelli e dashboard dei costi
← Torna a AI Engineering Academy