Streaming Risposte LLM — UX Real-Time in App Web Senza Far Impazzire il Server
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Intelligenza Artificiale & Software

Streaming Risposte LLM — UX Real-Time in App Web Senza Far Impazzire il Server

[2026-07-28] Author: Ing. Calogero Bono
> condividi
Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith Demo gratis · senza carta

Il tuo utente ha scritto una domanda. Preme invio. Poi aspetta. Il server elabora, il modello genera la risposta parola per parola, ma lui vede solo una rotellina di caricamento per 10 secondi. Poi, tutto in una volta, compare il testo completo. Sembra magia, ma l'esperienza è quella di un'app lenta. Il problema? Stai inviando la risposta completa invece di farla arrivare in streaming, pezzo per pezzo. Noi, di Meteora Web, lo vediamo spesso nei progetti che ci arrivano: sviluppatori che integrano LLM in app web dimenticano che l'utente percepisce la velocità del primo token, non del tempo totale. E in un'app web reale, ogni secondo di attesa è un abbandono potenziale.

Questa guida è per chi ha già familiarità con LangChain e LLM, ma vuole portare la UX a un livello professionale. Implementeremo lo streaming con FastAPI e Server-Sent Events (SSE), discuteremo costi, errori e ottimizzazioni. Se arrivi da zero, ti consigliamo di partire dalla nostra Pillar Guide su LangChain e LLM.

Perché lo streaming migliora l'esperienza utente nelle app web con LLM?

L'utente non vuole sapere quanto ci mette il modello a generare 500 token. Vuole vedere che sta succedendo qualcosa. Lo streaming abbassa la latenza percepita: il primo token arriva in millisecondi invece che in secondi, e il testo compare progressivamente. Studi di UX mostrano che un'app che mostra i risultati in tempo reale viene percepita come 3-4 volte più veloce, anche se il tempo totale è lo stesso.

Sponsored Protocol

Ma non è solo questione di percezione. In applicazioni interattive (chat, assistenti, editor collaborativi), lo streaming permette all'utente di leggere e reagire mentre il modello sta ancora generando. Può fermare la generazione se vede un errore, o fornire feedback immediato. Inoltre, riduce il carico di memoria lato server: invece di tenere in ram l'intera risposta prima di inviarla, la invii a pezzi.

Esempio concreto: Un cliente e-commerce aveva un assistente AI per consigli di vendita. Con risposta completa, l'utente aspettava in media 8 secondi. Con streaming, il primo consiglio appariva dopo 1.5 secondi. Il tasso di completamento delle interazioni è salito del 40%.

Come funziona tecnicamente lo streaming di un LLM?

Quando chiami un modello (ChatGPT, Claude, Llama via API), il provider espone un parametro stream=True. Invece di restituire un unico JSON alla fine, invia una sequenza di chunk data: {...} (SSE) o eventi WebSocket. Ogni chunk contiene un frammento di testo (un token, più token, o un delta). Il frontend li accumula e aggiorna la UI in tempo reale.

Con LangChain, lo streaming è nativo. Basta usare stream() sulla catena invece di invoke().

Come implementare lo streaming di risposte LLM con LangChain e FastAPI?

Partiamo da un esempio completo. Useremo FastAPI con SSE (Server-Sent Events) perché è più semplice dei WebSocket per streaming unidirezionale server->client. Il frontend riceve i chunk con l'API EventSource.

Sponsored Protocol

# server.py
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
import asyncio

app = FastAPI()

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, streaming=True)

async def generate_stream(prompt: str):
    # LangChain stream() restituisce un iteratore asincrono
    async for chunk in llm.astream([HumanMessage(content=prompt)]):
        # chunk è un AIMessageChunk con attribute content
        yield f"data: {chunk.content}\n\n"

@app.get("/chat/stream")
async def chat_stream(prompt: str, request: Request):
    return StreamingResponse(
        generate_stream(prompt),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",  # essenziale per nginx
        }
    )
// frontend.js
const eventSource = new EventSource(`/chat/stream?prompt=${encodeURIComponent(prompt)}`);
let responseText = "";
eventSource.onmessage = (event) => {
    responseText += event.data;
    document.getElementById("output").innerText = responseText;
};
eventSource.onerror = (err) => {
    console.error("Stream error", err);
    eventSource.close();
};

Attenzione: se usi un reverse proxy come Nginx, devi disabilitare il buffering con l'header X-Accel-Buffering: no. Altrimenti Nginx accumula i chunk prima di inviarli al client, annullando lo streaming.

Sponsored Protocol

Gestione della concorrenza e del timeout

In produzione, ogni utente apre una connessione SSE. Il server deve gestire molte connessioni simultanee. FastAPI con async def usa un event loop singolo, quindi ogni stream è cooperativo. Se un LLM impiega molto, blocca altre richieste? No, perché async for rilascia il controllo ad ogni chunk. Tuttavia, è meglio limitare le connessioni con un semaforo o un pool di worker.

from asyncio import Semaphore

sem = Semaphore(10)  # massimo 10 stream concorrenti

async def generate_stream(prompt: str):
    async with sem:
        async for chunk in llm.astream(...):
            yield f"data: {chunk.content}\n\n"

Quali sono i costi impliciti dello streaming di risposte?

Veniamo dalla contabilità e lo sappiamo bene: ogni token costa. Lo streaming non cambia il numero di token consumati, ma impatta la latenza e l'infrastruttura.

  • Costo API: identico a una richiesta normale. Paghi per token in input + output, indipendentemente dallo streaming.
  • Costo banda: leggermente superiore per via degli header SSE aggiuntivi, ma trascurabile.
  • Costo server: ogni connessione SSE aperta consuma risorse. Se hai 1000 utenti simultanei, devi dimensionare il server o usare servizi serverless (es. Cloudflare Workers, Vercel Edge) che supportano streaming. Noi abbiamo ottimizzato un server Linux con sysctl per aumentare il numero massimo di connessioni aperte.
  • Costo di abbandono: se l'utente chiude la pagina, la connessione si interrompe. Ma il server potrebbe continuare a generare token in background se non gestisci l'evento disconnect. FastAPI lancia asyncio.CancelledError quando il client si disconnette: devi catturarlo e fermare lo stream.
async def generate_stream(prompt: str, request: Request):
    try:
        async for chunk in llm.astream(...):
            await request.is_disconnected()  # controlla ogni chunk
            yield f"data: {chunk.content}\n\n"
    except asyncio.CancelledError:
        # client disconnesso, interrompi generazione
        pass

Come gestire errori e interruzioni durante lo streaming di LLM?

Lo streaming è fragile: il modello può restituire un errore a metà, la connessione di rete cade, l'API rate-limit scatta. L'utente non deve vedere un messaggio di errore brutale.

Sponsored Protocol

Strategia: invia un evento speciale per segnalare la fine o l'errore.

async def generate_stream(prompt: str):
    try:
        async for chunk in llm.astream(...):
            yield f"data: {chunk.content}\n\n"
        yield "data: [DONE]\n\n"
    except Exception as e:
        yield f"event: error\ndata: {str(e)}\n\n"

Sul frontend, ascolta l'evento error e mostra un messaggio amichevole con possibilità di riprovare.

Quale protocollo scegliere: SSE o WebSocket?

Dipende dal caso d'uso.

Sponsored Protocol

  • SSE: comunicazione unidirezionale server->client, nativo su HTTP, supportato da tutti i browser, automaticamente riconnette in caso di caduta. Perfetto per chat e streaming di testo.
  • WebSocket: bidirezionale, più complesso, utile se il client deve anche inviare dati durante lo streaming (es. feedback interattivo, interruzione).

Per il 90% delle app web con LLM, SSE è la scelta giusta. Noi abbiamo costruito una piattaforma proprietaria per gestire la presenza social di più clienti: abbiamo usato SSE per le notifiche in tempo reale e WebSocket solo per la chat interattiva. Sempre: scegli il più semplice che funziona.

Cosa fare adesso

  1. Testa lo streaming sul tuo progetto esistente: Cambia invoke con astream in LangChain. Misura il time-to-first-token prima e dopo.
  2. Aggiungi gestione disconnessione: Controlla request.is_disconnected() e cattura CancelledError. Non sprecare token.
  3. Configura il reverse proxy: Se usi Nginx, inserisci proxy_set_header X-Accel-Buffering no; nel blocco location.
  4. Monitora i costi: Traccia il numero di token generati per sessione. Se lo streaming porta a interazioni più lunghe, potresti avere più consumo. Valuta di introdurre un limite di token massimo per risposta.
  5. Testa con utenti reali: La percezione è tutto. Fai un A/B test tra streaming e risposta completa. Guarda il tasso di completamento e i feedback.
> condividi
Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // WEB AGENCY

Costruiamo la presenza digitale che la tua azienda merita.

Siti web, social, pubblicità online, e-commerce e hosting performante: ingegnerizzati con metodo da ingegneri informatici a Sciacca, per tutta Italia.

> MW_JOURNAL

> READ_ALL()