Avec "stream": true, POST /chat/completions renvoie la réponse token par token sous forme d'événements server-sent, au même format qu'OpenAI. Vos utilisateurs voient le texte apparaître immédiatement au lieu d'attendre la réponse complète.
Activer le streaming
Ajoutez stream: true au corps de la requête ; rien d'autre ne change. Avec curl, passez -N pour désactiver la mise en tampon de la sortie.
curl -N https://promptix.tn/api/v1/chat/completions \ -H "Authorization: Bearer $PROMPTIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-6-luna", "stream": true, "messages": [{ "role": "user", "content": "Write a short poem about Djerba." }] }'Format des événements
La réponse a le type Content-Type: text/event-stream. Chaque événement tient sur une ligne qui commence par data: , suivie d'un objet JSON chat.completion.chunk, puis d'une ligne vide. Le flux se termine par la chaîne littérale data: [DONE].
data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-6-luna","provider":"OpenAI","choices":[{"index":0,"delta":{"role":"assistant","content":"Palm"},"finish_reason":null}]} data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-6-luna","provider":"OpenAI","choices":[{"index":0,"delta":{"content":" trees lean"},"finish_reason":null}]} data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-6-luna","provider":"OpenAI","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}]} data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-6-luna","provider":"OpenAI","choices":[],"usage":{"prompt_tokens":15,"completion_tokens":48,"total_tokens":63}} data: [DONE]choices[0].delta.contentcontient le nouveau texte. Le premier chunk porte aussidelta.role.- Le chunk dont
finish_reasonn'est pas nul marque la fin de la réponse (stop,length,tool_calls). - Promptix demande toujours l'usage : le dernier chunk avant
[DONE]contient un objetusageavec le nombre de tokens. Son tableauchoicespeut être vide, prévoyez-le. - Tous les chunks ont le même
id. Conservez-le pour consulter le coût de la requête. - Les appels d'outils arrivent par fragments dans
delta.tool_calls, comme chez OpenAI.
Lire le flux vous-même
Les SDK OpenAI analysent les événements pour vous. Sans SDK, lisez le corps ligne par ligne, gardez les lignes incomplètes dans un tampon et arrêtez-vous à [DONE].
JavaScript
// Plain fetch (Node 18+, Deno, Bun, edge runtimes): no SDK requiredconst res = await fetch('https://promptix.tn/api/v1/chat/completions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PROMPTIX_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'openai/gpt-6-luna', stream: true, messages: [{ role: 'user', content: 'Write a short poem about Djerba.' }], }),}); // Errors raised before the stream starts (401, 402, 400) come back as plain JSONif (!res.ok) { const body = await res.json(); throw new Error(typeof body.error === 'string' ? body.error : body.error?.message);} const reader = res.body.getReader();const decoder = new TextDecoder();let buffer = ''; outer: while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() ?? ''; // keep the incomplete line for the next read for (const line of lines) { if (!line.startsWith('data: ')) continue; const data = line.slice(6); if (data === '[DONE]') break outer; const chunk = JSON.parse(data); if (chunk.error) throw new Error(chunk.error.message); process.stdout.write(chunk.choices[0]?.delta?.content ?? ''); }}Python
import jsonimport osimport requests with requests.post( "https://promptix.tn/api/v1/chat/completions", headers={"Authorization": f"Bearer {os.environ['PROMPTIX_API_KEY']}"}, json={ "model": "openai/gpt-6-luna", "stream": True, "messages": [{"role": "user", "content": "Write a short poem about Djerba."}], }, stream=True, timeout=300,) as res: res.raise_for_status() for line in res.iter_lines(decode_unicode=True): if not line or not line.startswith("data: "): continue data = line[len("data: "):] if data == "[DONE]": break chunk = json.loads(data) if "error" in chunk: raise RuntimeError(chunk["error"]["message"]) if chunk["choices"]: print(chunk["choices"][0]["delta"].get("content") or "", end="", flush=True)Erreurs pendant le streaming
Les erreurs d'authentification, de validation, de solde et de plafond de clé surviennent avant le début du streaming : elles sont renvoyées sous forme de réponse JSON classique avec le code correspondant (400, 401, 402). Voir Erreurs et limites.
Une fois le flux commencé, le statut HTTP est déjà 200. Si le fournisseur échoue à ce moment-là, Promptix envoie un dernier événement contenant un objet error et ferme le flux sans [DONE] :
data: {"error":{"message":"Streaming failed","type":"stream_error"}}Considérez un flux terminé sans [DONE] comme incomplet. Les SDK OpenAI pour Python et Node lèvent une APIError lorsqu'ils reçoivent cet événement.
Facturation et déconnexions
Une requête en streaming coûte autant qu'une requête classique : vous payez les tokens d'entrée et de sortie indiqués dans le chunk usage final.
Fermer la connexion n'annule pas la génération
Si votre client se déconnecte en cours de flux, le modèle continue de générer jusqu'à la fin et les tokens produits sont facturés. Utilisez max_tokens pour borner le coût des longues réponses.
Les flux sont servis avec Content-Type: text/event-stream et Cache-Control: no-cache, et envoyés token par token. Si vous placez un proxy (Nginx, un CDN) devant votre propre backend, désactivez-y la mise en tampon des réponses, sinon les tokens arriveront par paquets. Une requête, streaming compris, peut durer jusqu'à 300 secondes.