AI & MCP
Streaming UI con l'AI SDK: route handler, useChat e parti
Lo streaming con l'AI SDK è un route handler che restituisce parti e un componente che le legge: il testo compare mentre arriva, la tool call come parte.
In sintesi
- Il modello e la chiave restano nel route handler; il browser riceve solo uno stream di parti.
- useChat espone messages e sendMessage: ogni messaggio ha un array parts ordinato.
- Una tool call è una parte chiamata tool-
, non testo da interpretare.
Lo streaming con l'AI SDK è un route handler che restituisce uno stream di parti e un componente che legge message.parts: il testo compare mentre arriva, e una chiamata a un tool compare come parte separata. Non serve gestire a mano il protocollo.
Tre file, un percorso
app/api/chat/route.ts: riceve i messaggi, chiama il modello, restituisce lo stream.app/page.tsx: usauseChate disegna le parti di ogni messaggio..env.local: tiene la chiave del provider, che non esce mai dal server.
import {
streamText,
convertToModelMessages,
createUIMessageStreamResponse,
toUIMessageStream,
type UIMessage,
} from 'ai';
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: 'anthropic/claude-sonnet-5.5',
messages: await convertToModelMessages(messages),
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}
Il route handler non aspetta la risposta completa: restituisce un oggetto stream e il framework lo invia al browser mentre il modello scrive. convertToModelMessages toglie dai messaggi della UI i campi che il modello non deve vedere, come identificativi e tempi.
La chiave resta sul server
Il modello si sceglie nel route handler, non nel componente. Una chiave in una variabile NEXT_PUBLIC_ finisce nel bundle e chiunque la legge dal browser. Se il modello deve cambiare per utente o per piano, la decisione si prende sul server dopo aver letto la sessione: il client manda i messaggi, non il nome del modello.
Le parti sono l'interfaccia
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat();
return (
{messages.map((m) =>
m.parts.map((part, i) => {
if (part.type === 'text') return {part.text};
if (part.type === 'tool-search_components')
return {JSON.stringify(part, null, 2)};
return null;
}),
)}
{
e.preventDefault();
sendMessage({ text: input });
setInput('');
}}
>
setInput(e.target.value)} />
);
}
message.parts è un array ordinato: contiene testo, ragionamento e tool call nello stesso ordine in cui il modello li ha prodotti. Una tool call si chiama tool- più il nome che hai dato al tool, quindi tool-search_components. Non c'è HTML da interpretare e non c'è un formato da indovinare.
Multi-step: alzare stopWhen
Con il default isStepCount(1) la generazione si ferma alla prima tool call: il risultato viene eseguito, ma il modello non lo usa per rispondere. Per un agente che cerca, legge e poi scrive serve stopWhen: isStepCount(5), con un tetto esplicito. Il numero è una scelta di prodotto: ogni passo in più costa un prompt intero.
Cosa non fa lo streaming
- Non nasconde la latenza: il primo token arriva quando arriva; lo streaming cambia l'attesa, non il tempo.
- Non mette in sicurezza il route handler: autenticazione, rate limit e quota restano da scrivere.
- Non valida l'output del tool: se il server restituisce testo con istruzioni, il client lo mostra.
La limitazione
Il pacchetto ai 7.0.159 occupa 8,4 MB scompattati in 714 file, richiede Node 22 e zod come peer dependency: è una dipendenza che pesa, non un hook da due righe. La major 7 ha cambiato l'API rispetto alle versioni precedenti, quindi un tutorial vecchio produce errori di tipo invece di una UI: blocca la versione nel lockfile e leggi la guida della major che usi.
Dove continuare
Lo streaming lato server senza SDK è in streaming e Suspense in Next.js, la differenza fra route handler e server action in route handler o server action, il rendering sospeso in Suspense in React. Le parti pronte per una chat sono in 111.985 componenti e 1.556 blocchi; un esempio con licenza dichiarata a monte è chatgpt prompt input di Jahed.
Domande frequenti
Devo usare Next.js per lo streaming con l'AI SDK?
No. Serve un endpoint che restituisca uno stream e un client che lo legga; la guida ufficiale usa Next.js App Router perché è il percorso più breve. Su Vite, Express o un altro framework cambi il trasporto, non il modello di dati.
Dove metto la chiave del provider?
In una variabile d'ambiente letta solo dal server, come AI_GATEWAY_API_KEY in .env.local. Una chiave con prefisso pubblico finisce nel bundle e chiunque la può leggere dal browser.
Come mostro una tool call invece del testo?
Leggendo message.parts e trattando la parte il cui type è tool- più il nome del tool, per esempio tool-search_components. La parte porta input e risultato, quindi puoi mostrarla come JSON senza interpretare stringhe.
Perché il modello non risponde dopo la tool call?
Perché stopWhen vale isStepCount(1) per default: la generazione finisce alla prima chiamata. Alza il limite, per esempio a 5, se vuoi che il modello usi il risultato del tool per chiudere la risposta.
Lo streaming riduce i token o il costo?
No. I token sono gli stessi, cambia solo quando l'utente li vede. Il costo si riduce tagliando contesto e passi, non cambiando il modo in cui il testo arriva al browser.