Streaming
Diffusez les tokens au fil de leur génération, exactement comme avec le SDK OpenAI.
Le streaming fonctionne exactement comme dans le SDK OpenAI : passez stream: true et
parcourez l'itérable asynchrone. Le SDK ne change rien à la forme du flux.
const stream = await client.chat.completions.create({
model: "nova-lite",
messages: [{ role: "user", content: "Écris un haïku." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Chaque chunk est un chat.completion.chunk OpenAI standard. Le type de retour est
entièrement typé : stream: true donne un flux, stream: false (ou l'absence de champ)
donne une complétion unique, avec les mêmes surcharges qu'en amont.
La facturation des flux
La longueur de la sortie n'est pas connue avant la fin du flux : Fleeex réserve donc un plafond au départ et règle le coût réel à la fin. Le portefeuille ne peut jamais passer en négatif. Côté SDK, il n'y a rien à faire : vous parcourez le flux, c'est tout.
Pour savoir ce que le flux a réellement coûté, ajoutez
stream_options: { include_usage: true }. Voir
Usage en streaming.
Les erreurs arrivent avant le premier chunk
Chaque refus est décidé avant qu'un seul octet SSE ne soit écrit : il se manifeste donc au
moment du await sur l'appel create, jamais en cours de parcours. Cela couvre le 402,
un 400 sur un paramètre non pris en charge, les 403 et la
limite de flux ci-dessous.
import { PaymentRequiredError } from "@fleeex/sdk";
try {
const stream = await client.chat.completions.create({
model: "nova-lite",
messages,
stream: true,
});
for await (const chunk of stream) {
/* … */
}
} catch (err) {
if (err instanceof PaymentRequiredError) {
if (err.topupUrl) redirect(err.topupUrl);
} else {
throw err;
}
}Une fois les en-têtes engagés, la réponse ne peut plus devenir une erreur HTTP : une
panne du fournisseur en cours de flux met fin au flux, et vous êtes facturé de ce qui a
été livré. Traitez donc un flux qui s'arrête sans finish_reason comme une réponse
partielle, et non comme un succès.
Voir Solde et paiements pour le parcours de paiement complet.
Un flux peut être coupé, et il le dit
Deux plafonds peuvent interrompre un flux. Les deux se présentent de la même façon sur le
protocole, finish_reason: "length", et les deux se règlent honnêtement : vous êtes
facturé de ce qui a été livré, et le reste de la réservation est libéré.
- Le plafond de coût. La longueur de la sortie n'est pas connue d'avance : si le coût
courant dépassait la réservation, le flux s'arrête au lieu de laisser le portefeuille
passer en négatif. Un
max_completion_tokensplus grand réserve davantage, donc coupe plus tard, au prix de fonds immobilisés plus longtemps. Voirmax_completion_tokens. - Un plafond d'horloge, pour un flux qui s'égrène indéfiniment.
Ainsi finish_reason: "length" signifie que la réponse a été tronquée, par votre
max_completion_tokens ou par l'un de ces deux plafonds. Jamais que le modèle a terminé
de lui-même. Les trois cas ne se distinguent pas, alors traitez ce signal comme « il
restait des choses à dire ». Si vous devez savoir ce qu'a consommé un flux coupé,
demandez le chunk d'usage.
Un utilisateur, plusieurs flux
Les flux simultanés sont plafonnés par utilisateur final, pour qu'un seul utilisateur ne
prenne pas toutes les places. Au-delà du plafond, create échoue avec un 429
CONCURRENT_STREAM_LIMIT porteur d'un
en-tête Retry-After.
Le refus intervient avant toute réservation et tout appel au fournisseur : il ne coûte donc rien, et il se lève dès qu'un des flux de cet utilisateur se termine. Le remède est de réduire le nombre de flux ouverts, pas la cadence des requêtes.