Torna al blog

Modelli e confrontiGuida

Confronta due LLM in modo equo sul tuo task: scambio dei lati e log dei costi

Esegui un testa a testa tra due modelli sui tuoi ticket: impostazioni uguali, ogni coppia giudicata in entrambi gli ordini e un log calls.jsonl che dà il costo dell'esecuzione.

Di CheapTokens TeamPubblicato il 8 min di lettura

Copertina: confronta due modelli in modo equo, con un flusso in tre passi: stesso prompt, scambia i lati, registra ogni chiamata
In questa pagina
  1. Cosa fa un torneo pubblico che puoi prendere in prestito
  2. Il task: redigere risposte a 30 ticket di supporto
  3. Scambia i lati: giudica ogni coppia due volte
  4. Registra ogni chiamata, poi calcola il costo dell'esecuzione
  5. Prima di lanciare l'esecuzione completa
  6. Leggere il risultato senza ingannarti

Hai eseguito tre prompt con due modelli e il secondo ha vinto. Prima di fidarti, controlla quale dei due era elencato per secondo. Un confronto equo richiede tre cose: impostazioni identiche per entrambi i modelli, ogni coppia giudicata in entrambi gli ordini e un log dei token per chiamata, così sai quanto è costata l'intera esecuzione. Qui sotto c'è un piccolo script Python che fa tutte e tre le cose sul tuo task.

Cosa fa un torneo pubblico che puoi prendere in prestito#

Il Clocktower Bench di Dwarkesh Patel è un progetto di ricerca non ufficiale in cui i modelli linguistici giocano l'uno contro l'altro al gioco di deduzione sociale Blood on the Clocktower. Il gioco non è il punto qui; lo è l'harness. Secondo la pagina About del progetto (consultata l'8 novembre 2026), fa alcune cose che qualsiasi confronto testa a testa dovrebbe fare:

  • un modello occupa tutti i posti buoni e un altro tutti i posti cattivi, e ogni mano viene giocata due volte con i lati scambiati, così un posto fortunato non decide il risultato;
  • tutti i modelli ricevono le stesse impostazioni: lo stesso sforzo di ragionamento, un tetto di 8.000 token per turno e un limite di 150 parole per il discorso;
  • lo Storyteller è scriptato, quindi nessun modello arbitra la partita;
  • ogni chiamata API viene scritta in calls.jsonl con i suoi token e il costo. La pagina dice che una singola partita costa da circa $2 a $50, a seconda dei modelli (sulle API OpenAI e Anthropic).

L'harness è costruito per i modelli OpenAI e Anthropic, e il tuo task probabilmente non è un gioco da tavolo. Ma le tre abitudini si trasferiscono: impostazioni uguali, lati scambiati e un log dei costi. Il resto di questa guida le applica a qualcosa di ordinario.

Ogni ticket va a entrambi i modelli; il giudice vede la coppia due volte, in ordini opposti
Il ciclo di scambio dei lati per un ticket

Il task: redigere risposte a 30 ticket di supporto#

Supponi di gestire un piccolo help desk e di voler sapere se DeepSeek-V4-Flash o GLM-5.3-Flash scrive prime bozze migliori. Hai un file tickets.jsonl con 30 ticket reali, un oggetto JSON per riga: {"id": 1, "text": "..."}.

L'API è compatibile con OpenAI, quindi il pacchetto ufficiale openai funziona con CheapTokens su https://api.cheaptokens.dev/v1. Usa gli id completi dei modelli dalla documentazione: deepseek-ai/DeepSeek-V4-Flash-0731 e zai-org/GLM-5.3-Flash. Entrambi i modelli ricevono anche un terzo modello come giudice, MiniMaxAI/MiniMax-M2.7, così nessuno dei due concorrenti valuta il proprio lavoro.

Inizia dalla parte che rende possibile il log: una funzione attraverso cui passa ogni chiamata.

python
import json, os, re
from openai import OpenAI

client = OpenAI(base_url="https://api.cheaptokens.dev/v1",
                api_key=os.environ["CHEAPTOKENS_API_KEY"])

CONTESTANTS = {"deepseek": "deepseek-ai/DeepSeek-V4-Flash-0731",
               "glm": "zai-org/GLM-5.3-Flash"}
JUDGE = "MiniMaxAI/MiniMax-M2.7"
SETTINGS = {"temperature": 0.2, "max_tokens": 800}   # same for both

def call(model, messages, role, ticket_id, **settings):
    r = client.chat.completions.create(model=model, messages=messages, **settings)
    with open("calls.jsonl", "a", encoding="utf-8") as f:
        f.write(json.dumps({
            "role": role, "ticket": ticket_id, "model": model,
            "input_tokens": r.usage.prompt_tokens,
            "output_tokens": r.usage.completion_tokens,
            "finish": r.choices[0].finish_reason}) + "\n")
    return r.choices[0].message.content or ""

def draft(model, ticket):
    msgs = [{"role": "system", "content": "You answer support tickets. "
             "Reply in under 80 words, politely, with no promises about refunds."},
            {"role": "user", "content": ticket["text"]}]
    return call(model, msgs, "draft", ticket["id"], **SETTINGS)

I campi in calls.jsonl sono quelli scelti da questo script; i numeri di utilizzo provengono dall'oggetto usage della risposta API. Entrambi i modelli ricevono lo stesso system prompt, lo stesso temperature e lo stesso max_tokens. Cambiane uno per un modello e stai confrontando impostazioni, non modelli.

Scambia i lati: giudica ogni coppia due volte#

Ora il giudice. Un modello giudice può propendere per la risposta in una particolare posizione, quindi un singolo verdetto mescola qualità e posizione. Il trucco del Clocktower si adatta bene: mostra ogni coppia in entrambi i ruoli. Una risposta vince un ticket solo se il giudice la sceglie in entrambi gli ordini. Se il verdetto segue la posizione invece della risposta, il ticket è un pareggio.

python
def judge(ticket, first, second):
    prompt = (f"Ticket:\n{ticket['text']}\n\nReply A:\n{first}\n\nReply B:\n{second}\n\n"
              "Which reply is better for the customer? Finish with a last line: Verdict: A or Verdict: B.")
    out = call(JUDGE, [{"role": "user", "content": prompt}], "judge",
               ticket["id"], temperature=0, max_tokens=1000)
    letters = re.findall(r"Verdict:\s*([AB])\b", out)
    return letters[-1] if letters else None

tickets = [json.loads(l) for l in open("tickets.jsonl", encoding="utf-8")]
wins = {"deepseek": 0, "glm": 0, "tie": 0}

for t in tickets:
    a = draft(CONTESTANTS["deepseek"], t)
    b = draft(CONTESTANTS["glm"], t)
    v1 = judge(t, a, b)   # DeepSeek's reply shown as A
    v2 = judge(t, b, a)   # GLM's reply shown as A
    pick1 = {"A": "deepseek", "B": "glm"}.get(v1)
    pick2 = {"A": "glm", "B": "deepseek"}.get(v2)
    wins[pick1 if pick1 and pick1 == pick2 else "tie"] += 1

print(wins)

Due dettagli sono facili da trascurare. Il giudice gira a temperature=0 per mantenere i verdetti più stabili; non tutti i modelli rispettano questa impostazione, quindi riesegui alcune coppie e confronta i verdetti prima di fidarti. Il verdetto viene letto solo da una riga finale Verdict:, e i limiti sono generosi (800 per le bozze, 1000 per il giudice) così un modello che ragiona prima di rispondere non viene interrotto a metà pensiero; la stima sotto usa dimensioni tipiche, non i limiti. Una risposta non interpretabile dà None, che finisce in un pareggio invece di far crashare l'esecuzione.

Provalo tu stessoProvalo: il giudice ha detto "A" in entrambi gli ordini per il ticket 7. Chi vince quel ticket?Mostra risposta

Nessuno. Nel primo ordine A era la risposta di DeepSeek, nel secondo ordine A era la risposta di GLM, quindi il giudice ha preferito la posizione, non un modello. Il codice lo conta come pareggio. Una pila di pareggi del genere è di per sé un risultato: i due modelli sono vicini sul tuo task, oppure il tuo prompt del giudice è troppo vago.

L'ordine è solo un modo per inclinare un test. Anche impostazioni, prompt e logging possono inclinarlo, quindi controlla l'intera configurazione, non solo chi va per primo. Ecco lo stesso esperimento prima e dopo.

Confronta

Cosa cambia quando smetti di confrontare in modo casuale

Casuale

Un prompt per modello, valori predefiniti lasciati attivi, la coppia mostrata in un solo ordine e il conto letto dalla dashboard a fine mese. Il vincitore potrebbe essere il modello che per caso era elencato per primo.

Equo

Un system prompt condiviso e gli stessi temperature e max_tokens, ogni coppia giudicata in entrambi gli ordini e una riga in calls.jsonl per ogni chiamata. Il vincitore è chi vince da entrambi i posti.

Registra ogni chiamata, poi calcola il costo dell'esecuzione#

Il log è ciò che trasforma il confronto in un budget. Dopo l'esecuzione, somma i token per modello:

python
from collections import defaultdict

totals = defaultdict(lambda: [0, 0, 0])
for line in open("calls.jsonl", encoding="utf-8"):
    c = json.loads(line)
    s = totals[c["model"]]
    s[0] += c["input_tokens"]; s[1] += c["output_tokens"]; s[2] += 1

for model, (tin, tout, n) in totals.items():
    print(model, n, "calls", tin, "input", tout, "output")

Otterrai una riga per modello con numero di chiamate, token di input e token di output. Moltiplica per i due prezzi del modello (input e output sono fatturati separatamente) o, più semplicemente, incolla i totali in un indicatore di costo live.

Puoi anche stimare prima di eseguire. Supponi che ogni ticket più il system prompt sia circa 300 token di input, una bozza circa 250 token di output, e che il giudice veda circa 800 token di input (ticket più entrambe le risposte più la domanda) e risponda con circa 200 token (la riga del verdetto più un po' di spazio per pensare):

  • bozze: 30 ticket × 2 modelli = 60 chiamate, 30 × 300 = 9000 token di input e 30 × 250 = 7500 token di output per concorrente;
  • verdetti: 30 ticket × 2 ordini = 60 chiamate, 60 × 800 = 48000 token di input e 60 × 200 = 12000 token di output sul giudice.

Ai prezzi attuali, le 30 bozze di un concorrente costano $0.00089 su DeepSeek-V4-Flash e $0.00048 su GLM-5.3-Flash. I 60 verdetti su MiniMax-M2.7 costano $0.0042. Somma i tre e hai il prezzo dell'intero confronto prima di inviare una singola richiesta. Lo stesso carico di redazione su tutti e tre i modelli, con input e output contati insieme:

Modello Costo OpenRouter
GLM-5.3-Flash $0.00048 $0.00092
DeepSeek-V4-Flash $0.00089 $0.0017
MiniMax-M2.7 $0.0016 $0.0189

9000 token di input e 7500 token di output in totale. I prezzi per 1M di token sono attuali e cambiano insieme alle nostre tariffe; la colonna OpenRouter mostra i prezzi di riferimento per lo stesso carico di lavoro.

La tabella mostra anche il riferimento OpenRouter, così puoi vedere come si confronta il totale. Le ipotesi sui token sono tue da modificare: se i tuoi ticket sono lunghi, l'input del giudice cresce più in fretta, perché porta entrambe le risposte due volte. Se il tuo giudice pensa ad alta voce prima di rispondere, cresce anche il suo numero di output, e il tuo log lo mostrerà. Per l'abitudine di contare i token prima di una richiesta, vedi conta i token prima di inviare una richiesta.

Vale la pena rubare lo stesso limite che usa Clocktower. Imposta 8.000 token per turno, e hai già max_tokens. Mettilo in SETTINGS e una risposta fuori controllo non può moltiplicare silenziosamente la tua stima.

Prima di lanciare l'esecuzione completa#

Lista di controllo

Pre-flight per un confronto equo

Quest'ultimo punto conta. Esegui tre ticket, apri calls.jsonl e leggilo. Controlla che finish dica stop invece di length; se una bozza è stata troncata da max_tokens, un modello potrebbe perdere solo perché la sua risposta è stata troncata. Lo stesso vale per le chiamate del giudice: un length lì diventa un pareggio silenzioso, quindi conta anche i verdetti None. Se il tuo task ha un'unica risposta corretta, potresti non aver bisogno affatto di un giudice: testa il tuo prompt su 20 casi con valutazione exact-match.

Leggere il risultato senza ingannarti#

Guarda i tre numeri in wins. Trenta ticket sono un campione piccolo: una divisione come 12 a 9 con 9 pareggi ti dice che i modelli sono vicini, non che uno è migliore. Il log aiuta qui. Se un modello vince di poco ma usa molto più output, la vera domanda è il prezzo del task, non il numero di vittorie; prezzo per 1M token versus costo per task affronta quel confronto.

Se il risultato è vicino, scegli il modello più economico per il task, oppure aggiungi altri ticket e riesegui. Entrambe le cose sono economiche da provare perché l'intera macchina è già un ciclo. Quando vuoi ripetere l'esercizio su un altro modello, i tre modelli sopra stanno dietro un'unica API e un'unica chiave: la configurazione nella documentazione è la stessa per ciascuno, e la pagina dei prezzi elenca quanto costa ciascuno in questo momento. La scoperta di Dwarkesh Patel sui modelli OpenAI versus Anthropic (rilevamento versus inganno) è il suo risultato per il suo gioco; il tuo verrà dai tuoi ticket.