Torna al blog

Ottimizzazione dei costiGuida

Taglia la cronologia della chat per ridurre i costi: finestra scorrevole o riassunto

Limita ciò che reinvii a ogni turno: mantieni gli ultimi messaggi o un breve riassunto. Aritmetica dei token per una chat di 50 turni e un helper Python.

Di CheapTokens TeamPubblicato il 6 min di lettura

Copertina: mantieni economica una chat lunga, con l'helper trim_history
In questa pagina
  1. Cosa invia davvero il turno 50
  2. Opzione 1: una finestra scorrevole
  3. Opzione 2: un riassunto dei vecchi turni
  4. Quanto costano le tre configurazioni
  5. Quale scegliere

Il tuo bot di supporto chatta da 50 turni e ogni nuovo messaggio costa più del precedente. L'API non ricorda nulla, quindi il tuo codice rinvia l'intera conversazione ogni volta. La soluzione è limitare ciò che invii: mantieni il system prompt e gli ultimi turni (una finestra scorrevole), oppure sostituisci i vecchi turni con un breve riassunto. Qui sotto trovi l'aritmetica per una chat di 50 turni e un helper che puoi copiare.

La guida alla chat multi-round di DeepSeek (api-docs.deepseek.com, verificata il 1 novembre 2026) lo dice chiaramente: il server non registra il contesto delle richieste precedenti, quindi il client invia l'intera lista messages a ogni chiamata. Il formato compatibile OpenAI su CheapTokens funziona allo stesso modo. Se è una novità per te, inizia da perché una chat lunga costa di più e come funziona l'array messages.

Cosa invia davvero il turno 50#

Supponi di gestire un bot di supporto con un system prompt di 400 token. In un turno tipico l'utente scrive circa 60 token e il bot risponde con circa 190. Un turno completo aggiunge 250 token alla cronologia. Sono ipotesi: sostituiscile con i numeri del campo usage delle tue risposte.

Al turno 50 la richiesta trasporta il system prompt, 49 turni precedenti e il nuovo messaggio: 400 + 49 × 250 + 60 = 12,710 token di input, per ottenere una risposta di 190 token. Somma l'intera chat. Il system prompt e il nuovo messaggio vengono inviati ogni volta: 50 × 460 = 23,000. La cronologia ripetuta aggiunge 250 × 1,225 = 306,250. In totale: 23,000 + 306,250 = 329,250 token di input, contro 50 × 190 = 9,500 token di output.

La cronologia in ogni richiesta cresce di un turno, quindi il totale cresce con il quadrato della lunghezza della chat. Passa da 50 a 100 turni e la parte di cronologia ripetuta quadruplica all'incirca.

Provalo tu stessoProva: se inviassi solo il system prompt, gli ultimi 4 turni e il nuovo messaggio, quanti token di input trasporterebbe il turno 50?Mostra risposta

1.460 (400 + 4 × 250 + 60), contro 12.710 con la cronologia completa. Stessa domanda, stessa lunghezza della risposta, una frazione dell'input.

Opzione 1: una finestra scorrevole#

L'idea: invia sempre il system prompt, scarta tutto tranne gli ultimi N turni. Conservi la cronologia completa nel tuo storage e tagli solo la copia che invii.

python
def trim_history(messages, keep_pairs=4):
    """System prompt + the last keep_pairs turns + the new user message."""
    system = [m for m in messages if m["role"] == "system"]
    rest = [m for m in messages if m["role"] != "system"]
    return system + rest[-(keep_pairs * 2 + 1):]

Chiamalo dopo aver aggiunto il nuovo messaggio dell'utente. Con keep_pairs=4 sono 4 coppie utente/assistant più il nuovo messaggio, 9 messaggi, e la slice inizia sempre su un messaggio utente. Ecco come si inserisce in una chiamata reale:

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)
MODEL = "deepseek-ai/DeepSeek-V4-Flash-0731"

history = [{"role": "system", "content": "You are a concise support assistant."}]

def ask(text):
    history.append({"role": "user", "content": text})
    resp = client.chat.completions.create(
        model=MODEL,
        messages=trim_history(history),
        max_tokens=500,
    )
    answer = resp.choices[0].message.content
    history.append({"role": "assistant", "content": answer})
    print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
    return answer

La riga print è quella utile: guarda il primo numero. Senza il taglio sale a ogni turno. Con la finestra smette di crescere una volta che la chat supera il turno 4.

Helper Python trim_history: system prompt, ultimi 4 turni, nuovo messaggio
L'helper di taglio di questo articolo

In numeri: dal turno 5 in poi, ogni richiesta è di 400 + 4 × 250 + 60 = 1,460 token di input. I primi quattro turni crescono ancora normalmente e sommano a 3.340. L'intera chat: 3,340 + 46 × 1,460 = 70,500 token di input, circa un quinto dei 329.250 che invii con la cronologia completa. L'output resta 50 × 190 = 9,500.

Richiesta del turno 50: cronologia completa contro una finestra di 4 turni
Parti in token della richiesta del turno 50, secondo le ipotesi sopra

Nel codice, l'intera modifica è un argomento nella chiamata:

Confronta

La modifica in una riga

Prima

python
messages=history

Dopo

python
messages=trim_history(history)

Il problema: una finestra dimentica. Se l'utente chiede al turno 40 "qual era il mio numero d'ordine?" e il numero era nel turno 3, il modello non l'ha mai visto. Due soluzioni economiche. Estrai i fatti stabili (nome, numero d'ordine, piano) dalla chat nel tuo codice e aggiungili al system prompt, dove la finestra non li taglia mai. Oppure aggiungi un riassunto, che è l'opzione successiva.

Opzione 2: un riassunto dei vecchi turni#

Invece di scartare i vecchi turni, li comprimi. Ogni pochi turni, invii al modello i turni appena usciti dalla finestra, più il riassunto corrente, e chiedi un nuovo riassunto breve. Poi inserisci quel riassunto nel messaggio di sistema.

python
def summarize(summary, old_messages):
    transcript = "\n".join(f"{m['role']}: {m['content']}" for m in old_messages)
    prompt = (
        f"Summary so far:\n{summary}\n\nNew turns:\n{transcript}\n\n"
        "Rewrite the summary in under 200 words. "
        "Keep names, numbers, decisions and open questions."
    )
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        max_tokens=400,
    )
    return resp.choices[0].message.content

def build_messages(history, summary, keep_pairs=4):
    system = history[0]["content"]
    if summary:
        system += "\n\nEarlier in this chat: " + summary
    recent = history[1:][-(keep_pairs * 2 + 1):]
    return [{"role": "system", "content": system}] + recent

Il riassunto non è gratis: è una chiamata extra al modello. Supponi un riassunto di circa 300 token, aggiornato ogni 5 turni (9 volte in 50 turni), con ogni aggiornamento che legge circa 1.600 token (il vecchio riassunto più cinque turni più l'istruzione) e ne scrive 300. Per stare sul sicuro, conta il riassunto in ogni richiesta dal turno 5 in poi (in realtà compare dopo il primo aggiornamento, quindi è una leggera sovrastima): 400 + 300 + 4 × 250 + 60 = 1,760 token di input.

Totali per la chat: richieste principali 3,340 + 46 × 1,760 = 84,300 token di input, chiamate di riassunto 9 × 1,600 = 14,400 di input e 9 × 300 = 2,700 di output. Complessivamente 84,300 + 14,400 = 98,700 token di input e 9,500 + 2,700 = 12,200 token di output.

Il riassunto costa un po' più della sola finestra e in cambio il modello sa ancora cosa è successo nel turno 3.

Quanto costano le tre configurazioni#

Ecco la stessa chat di 50 turni su DeepSeek-V4-Flash, prezzata dal vivo dai totali sopra:

Una chat di 50 turni su DeepSeek-V4-Flash
  • Cronologia completa (329.250 in, 9.500 out)$0.0059
  • Finestra più riassunto (98.700 in, 12.200 out)$0.0027
  • Finestra scorrevole, 4 turni (70.500 in, 9.500 out)$0.002

I prezzi provengono dal listino live, quindi i numeri si aggiornano quando cambia la tariffa. L'ordine no: la finestra invia il minimo, il riassunto sta nel mezzo, la cronologia completa è la più costosa.

Per una chat come questa, il modello CheapTokens da cui partire è DeepSeek-V4-Flash. Ha una finestra di contesto di 380K (una chat di 50 turni ci sta anche senza taglio), fino a 32.768 token di output per risposta, un prezzo fisso a qualsiasi ora del giorno e, in totale, contando insieme input e output, è più economico di OpenRouter. L'id del modello è deepseek-ai/DeepSeek-V4-Flash-0731. Se vuoi confrontare i modelli sulla configurazione a finestra (1.410 token di input per richiesta in media, 190 di output, 50 richieste), ecco la tabella live:

Modello Per richiesta 50 richieste OpenRouter, 50 richieste
GLM-5.3-Flash $0.000036 $0.0018 $0.0032
DeepSeek-V4-Flash $0.00004 $0.002 $0.0063
MiniMax-M2.7 $0.000099 $0.005 $0.0594

1410 token di input e 190 token di output per richiesta. 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.

GLM-5.3-Flash ha un contesto di 390K e MiniMax-M2.7 di 180K, sufficiente per una chat con taglio. I prezzi attuali sono sulla pagina dei prezzi, e la documentazione mostra come inviare la prima richiesta.

Quale scegliere#

La finestra è la scelta predefinita: tre righe di codice, nessuna chiamata extra, nessuna modalità di errore aggiuntiva. Usala per bot di supporto, tutor e role-play, dove gli ultimi turni portano la conversazione e il system prompt porta le regole. Aggiungi il riassunto quando gli utenti fanno riferimento a cose dette molto tempo prima, come in un assistente personale con un'unica conversazione senza fine. Mantieni la finestra abbastanza piccola perché il modello veda ancora il contesto immediato, e abbastanza grande da contenere un intero scambio domanda-risposta. Quanto ci sta in tutto è un limite a parte, trattato in cos'è una finestra di contesto.

Verifica rapida

L'utente chiede al turno 40 un numero d'ordine fornito nel turno 3. Usi una finestra di 4 turni senza riassunto. Cosa succede?

Mostra risposta

Risposta: Il modello non può vederlo, perché il turno 3 è stato tagliato dalla richiesta

L'API è stateless. Il modello vede solo ciò che è nella richiesta, quindi i fatti che ti servono ancora devono restare nel system prompt o in un riassunto.