0Pricing
AI Engineering Academy · Lezione

Comprendere lo streaming dei token

Comprenda come l'API di streaming invii completamenti parziali man mano che vengono generati, come funziona il parametro OpenAI stream=True e quando lo streaming migliora l'esperienza utente.

Comprendere lo streaming dei token è una lezione AI Engineering Academy gratuita su CoddyKit. Questa è la lezione 1 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.

Perché lo streaming è importante per l'esperienza utente

Senza streaming, l'applicazione deve attendere che l'LLM generi la risposta completa prima di visualizzare qualsiasi contenuto: spesso da 5 a 30 secondi per le risposte lunghe. Con lo streaming, il primo token compare entro 200-500 ms dall'invio della richiesta e i token successivi arrivano man mano che vengono generati. Questo trasforma l'esperienza utente percepita: dall'attesa a un coinvolgente effetto di generazione in tempo reale, migliorando notevolmente la reattività percepita anche se il tempo totale di generazione rimane identico.

Come gli LLM generano i token

Gli LLM sono autoregressivi: generano il testo un token alla volta, e ogni nuovo token dipende da tutti i token precedenti. Quando l'API riceve una richiesta, la GPU inizia immediatamente a campionare il primo token dopo l'elaborazione del prompt. Ogni token successivo richiede all'incirca lo stesso tempo. Lo streaming invia ogni token al client non appena viene campionato, invece di memorizzare tutti i token e inviare la stringa completa alla fine.

# Conceptual model of autoregressive generation
prompt = 'The capital of France is'

# Step 1: process full prompt, predict next token
# token_1 = sample(logits) → ' Paris'

# Step 2: append token_1 to context, predict next
# token_2 = sample(logits) → '.'

# Step 3: append token_2 to context, predict next
# token_3 = sample(logits) → '<|end|>'

# Total time: time_to_process_prompt + n_tokens * time_per_token
# With streaming: first token arrives after time_to_process_prompt (TTFT)
# Without streaming: everything arrives after TTFT + n_tokens * time_per_token

TTFT e TPOT: due metriche di latenza

Lo streaming introduce due concetti distinti di latenza. TTFT (Time to First Token) è il ritardo tra l'invio della richiesta e la ricezione del primo token, determinato principalmente dal tempo di elaborazione del prompt. TPOT (Time Per Output Token) è il tempo tra token consecutivi, determinato dalle dimensioni del modello e dall'hardware. Il TTFT influisce sulla rapidità di risposta dell'interfaccia, mentre il TPOT influisce sulla fluidità dello streaming del testo. Entrambi devono essere monitorati separatamente nel sistema di osservabilità.

import time
from openai import OpenAI

client = OpenAI()

def measure_streaming_latency(prompt: str):
    t_start = time.perf_counter()
    t_first_token = None
    token_times = []

    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        stream=True,
    )
    for chunk in stream:
        if chunk.choices[0].delta.content:
            t_now = time.perf_counter()
            if t_first_token is None:
                t_first_token = t_now
                print(f'TTFT: {(t_first_token - t_start) * 1000:.0f}ms')
            else:
                token_times.append(t_now - token_times[-1] if token_times else t_now - t_first_token)
            token_times.append(t_now)
    print(f'TPOT avg: {1000 * (token_times[-1] - t_first_token) / max(len(token_times)-1, 1):.1f}ms')

Il parametro stream=True

Per abilitare lo streaming nell'SDK OpenAI, è necessario impostare stream=True nella chiamata chat.completions.create. Il tipo della risposta cambia da un oggetto ChatCompletion a un iteratore Stream[ChatCompletionChunk]. Ogni chunk contiene un delta con un frammento di stringa content oppure None quando il token è una chiamata a uno strumento o lo streaming sta terminando.

from openai import OpenAI

client = OpenAI()

# Non-streaming: wait for complete response
response = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'Explain RAG in one paragraph.'}],
)
full_text = response.choices[0].message.content

# Streaming: receive tokens incrementally
stream = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'Explain RAG in one paragraph.'}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:  # delta can be None for non-content chunks
        print(delta, end='', flush=True)
print()  # newline at end

Accumulo della risposta completa

In molti flussi applicativi è necessario sia trasmettere i token all'interfaccia per garantire la reattività sia accumulare il testo completo della risposta per elaborazioni successive, come registrazione, memorizzazione nella cache o ulteriori passaggi della pipeline. Il procedimento è semplice: iteri sullo stream, stampi o restituisca ogni chunk al client e, contemporaneamente, concateni il contenuto in un'unica stringa completa.

def stream_and_accumulate(prompt: str) -> str:
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        stream=True,
    )

    full_text = ''
    finish_reason = None

    for chunk in stream:
        choice = chunk.choices[0]
        delta = choice.delta.content
        if delta:
            print(delta, end='', flush=True)  # real-time display
            full_text += delta               # accumulate
        if choice.finish_reason:
            finish_reason = choice.finish_reason

    print()  # newline
    print(f'Finished: {finish_reason}, total chars: {len(full_text)}')
    return full_text

Streaming con statistiche di utilizzo

Per impostazione predefinita, la risposta in streaming non include le statistiche sull'utilizzo dei token (token del prompt e token del completamento). Per includerle, passi stream_options={'include_usage': True}. I dati sull'utilizzo arrivano in un chunk finale dopo la fine dello stream del contenuto. Questo è importante per monitorare i costi e i limiti di frequenza nelle applicazioni in produzione.

stream = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'What is a vector database?'}],
    stream=True,
    stream_options={'include_usage': True},  # include token counts
)

full_text = ''
usage = None

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        full_text += chunk.choices[0].delta.content
    if chunk.usage:  # arrives in the final chunk
        usage = chunk.usage

if usage:
    print(f'Prompt tokens: {usage.prompt_tokens}')
    print(f'Completion tokens: {usage.completion_tokens}')
    print(f'Total tokens: {usage.total_tokens}')

Quando non utilizzare lo streaming

Lo streaming non è sempre la scelta giusta. Eviti lo streaming quando: (1) ha bisogno della risposta completa prima di poter eseguire qualsiasi operazione su di essa, ad esempio per analizzare JSON o rilevare chiamate a strumenti; (2) la risposta è molto breve (meno di 30 token), quindi il sovraccarico dello streaming aggiunge più ritardo di quanto ne faccia risparmiare; oppure (3) sta eseguendo l'elaborazione in batch di molte richieste e la velocità complessiva è più importante della latenza della singola risposta. In questi casi, le chiamate standard senza streaming sono più semplici e altrettanto veloci.

Streaming con le API Anthropic e Gemini

Lo streaming è disponibile nelle API di tutti i principali provider di LLM, non solo in OpenAI. Il procedimento è simile, ma le interfacce degli SDK differiscono leggermente. L'SDK Python di Anthropic utilizza client.messages.stream() come context manager, mentre Gemini utilizza generate_content(stream=True). Quando crea applicazioni indipendenti dal provider, astragga l'interfaccia di streaming dietro una funzione generatore comune.

import anthropic

ant_client = anthropic.Anthropic(api_key='YOUR_KEY')

# Anthropic streaming
with ant_client.messages.stream(
    model='claude-sonnet-4-5',
    max_tokens=1024,
    messages=[{'role': 'user', 'content': 'Explain hybrid search briefly.'}],
) as stream:
    for text in stream.text_stream:
        print(text, end='', flush=True)

# Final message with usage stats
final_msg = stream.get_final_message()
print(f'\nInput tokens: {final_msg.usage.input_tokens}')
print(f'Output tokens: {final_msg.usage.output_tokens}')

Interfaccia di streaming basata su un generatore

Un modello architetturale ordinato racchiude lo streaming in una funzione generatore Python che restituisce stringhe di token. In questo modo la logica dello streaming viene disaccoppiata da quella di consumo: i chiamanti possono iterare sul generatore, scrivere su un file, inoltrare i dati a un WebSocket oppure accumularli in una stringa, senza che il codice dello streaming sappia come verrà utilizzato il suo output. Questa è la base della maggior parte delle API di streaming in produzione.

from typing import Generator

def stream_completion(
    messages: list[dict],
    model: str = 'gpt-4o-mini',
    **kwargs,
) -> Generator[str, None, None]:
    stream = client.chat.completions.create(
        model=model,
        messages=messages,
        stream=True,
        **kwargs,
    )
    for chunk in stream:
        delta = chunk.choices[0].delta.content
        if delta:
            yield delta

# Usage: pipe to stdout
for token in stream_completion([{'role': 'user', 'content': 'Hello!'}]):
    print(token, end='', flush=True)

# Usage: accumulate
full = ''.join(stream_completion([{'role': 'user', 'content': 'Hello!'}]))

Streaming nelle applicazioni da terminale e CLI

Nelle applicazioni da terminale, l'output in streaming appare proprio come una digitazione: ogni carattere viene visualizzato immediatamente non appena viene generato. Il requisito fondamentale è utilizzare flush=True in ogni chiamata a print. Senza il flush, Python memorizza l'output in un buffer fino al carattere di nuova riga, vanificando lo scopo dello streaming. Può anche utilizzare sys.stdout.write(token) seguito da sys.stdout.flush() per avere un controllo maggiore sulla formattazione dell'output.

import sys

def stream_to_terminal(messages: list[dict]):
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        stream=True,
    )
    token_count = 0
    for chunk in stream:
        delta = chunk.choices[0].delta.content
        if delta:
            sys.stdout.write(delta)  # no newline added
            sys.stdout.flush()       # MUST flush or output buffers
            token_count += 1
    print()  # final newline
    print(f'({token_count} tokens generated)')

Streaming e recupero dagli errori

Lo streaming complica la gestione degli errori perché un problema può verificarsi a metà dello stream, dopo che alcuni token sono già stati inviati al client. Il procedimento consigliato consiste nell'inserire l'iterazione sullo stream in un blocco try/except e, in caso di errore, inviare al client un indicatore di errore oppure chiudere lo stream correttamente. Implementi sempre un timeout per l'intero stream, così da gestire i casi in cui il server inizi lo streaming ma poi si interrompa durante la generazione.

import signal

def stream_with_timeout(messages, timeout_seconds=30):
    def timeout_handler(signum, frame):
        raise TimeoutError('LLM stream timed out')

    signal.signal(signal.SIGALRM, timeout_handler)
    signal.alarm(timeout_seconds)

    try:
        stream = client.chat.completions.create(
            model='gpt-4o-mini',
            messages=messages,
            stream=True,
        )
        for chunk in stream:
            delta = chunk.choices[0].delta.content
            if delta:
                yield delta
    except TimeoutError:
        yield '\n[Response timed out]'
    except Exception as e:
        yield f'\n[Error: {str(e)}]'
    finally:
        signal.alarm(0)  # cancel timeout

Verifica rapida

Verifichi la sua comprensione dello streaming dei token degli LLM trattato in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che lo streaming invia ogni token generato al client non appena viene campionato, migliorando notevolmente la reattività percepita; TTFT e TPOT sono le due metriche fondamentali di latenza da monitorare separatamente; e stream=True modifica la risposta dell'SDK OpenAI trasformandola in un iteratore di chunk da consumare con un ciclo for. Incapsuli gli stream in funzioni generatore per ottenere un'interfaccia pulita e riutilizzabile. Nel prossimo argomento implementeremo lo streaming asincrono con l'SDK Python.

Domande Frequenti

La lezione «Comprendere lo streaming dei token» è gratuita?

Sì — il testo completo di «Comprendere lo streaming dei token» è 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 «Comprendere lo streaming dei token»?

Comprenda come l'API di streaming invii completamenti parziali man mano che vengono generati, come funziona il parametro OpenAI stream=True e quando lo streaming migliora l'esperienza utente. 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 1 di 4.

Quanto tempo richiede la lezione «Comprendere lo streaming dei token»?

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. Comprendere lo streaming dei token
  2. Consumare gli stream con l'SDK Python
  3. Streaming in FastAPI con Server-Sent Events
  4. Gestire le chiamate agli strumenti nelle risposte in streaming
← Torna a AI Engineering Academy