Fleeexdocs

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_tokens plus grand réserve davantage, donc coupe plus tard, au prix de fonds immobilisés plus longtemps. Voir max_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.