Le streaming affiche la réponse à mesure qu’elle arrive et réduit le temps d’attente perçu. L’appel au fournisseur reste dans une route serveur afin de protéger la clé et d’appliquer les limites.
Installer
~~~bash npm install ai @ai-sdk/openai ~~~
Configurez OPENAI_API_KEY et un modèle actif dans les variables serveur. Ne préfixez jamais la clé avec NEXT_PUBLIC_.
Créer la route
~~~typescript // app/api/chat/route.ts import { openai } from '@ai-sdk/openai'; import { streamText, convertToModelMessages, type UIMessage } from 'ai';
export const maxDuration = 30;
export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json();
if (!Array.isArray(messages) || messages.length > 40) { return Response.json({ error: 'Conversation invalide' }, { status: 422 }); }
const result = streamText({ model: openai(process.env.OPENAI_MODEL!), system: 'Réponds clairement en français.', messages: await convertToModelMessages(messages), maxOutputTokens: 600, });
return result.toUIMessageStreamResponse(); } ~~~
Les noms précis des helpers dépendent de la version majeure du SDK. Verrouillez les dépendances après validation et suivez la documentation de cette version.
Créer l’interface
~~~tsx 'use client';
import { useChat } from '@ai-sdk/react'; import { useState } from 'react';
export function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage, status, stop, error } = useChat();
return ( <section aria-labelledby="chat-title"> <h1 id="chat-title">Assistant</h1> <div aria-live="polite"> {messages.map(message => ( <article key={message.id}> <strong>{message.role === 'user' ? 'Vous' : 'Assistant'}</strong> {message.parts.map((part, index) => part.type === 'text' ? <p key={index}>{part.text}</p> : null )} </article> ))} </div> <form onSubmit={event => { event.preventDefault(); if (!input.trim()) return; sendMessage({ text: input }); setInput(''); }}> <label htmlFor="message">Message</label> <textarea id="message" value={input} onChange={e => setInput(e.target.value)} /> <button disabled={status !== 'ready'}>Envoyer</button> {status === 'streaming' && <button type="button" onClick={stop}>Arrêter</button>} </form> {error && <p role="alert">La réponse n’a pas pu être générée.</p>} </section> ); } ~~~
Production
Authentifiez la route, limitez corps, messages, tokens et requêtes par utilisateur. Propagez l’annulation au fournisseur, filtrez les logs et affichez un état accessible. Les outils demandent une validation serveur et une confirmation pour les écritures.
FAQ
Le streaming coûte-t-il moins cher ?
Non. Il change la livraison, pas le nombre de tokens générés.
Pourquoi la réponse s’interrompt-elle sur l’hébergeur ?
Vérifiez maxDuration, limites de fonction, proxy et que tous les intermédiaires acceptent le streaming.