Usage en streaming
stream_options.include_usage, la forme exacte du chunk d'usage final sur le protocole, et pourquoi ses chiffres sont ceux qui vous ont été facturés.
Sans cela, un appelant en streaming n'a aucun moyen de savoir ce qu'il a consommé.
Ajoutez stream_options: { include_usage: true } et le flux se termine par un chunk
supplémentaire porteur du décompte des tokens.
const stream = await client.chat.completions.create({
model: "nova-lite",
messages: [{ role: "user", content: "Écris un haïku." }],
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of stream) {
// Le chunk d'usage final a un tableau choices VIDE : protégez l'index.
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
if (chunk.usage) {
console.log("\nfacturé :", chunk.usage.total_tokens, "tokens");
}
}Le protocole, exactement
La forme est celle d'OpenAI, et c'est important : le chunk à choices vide est la seule
forme de chunk qu'un client ne rencontre qu'à la toute fin d'un flux.
- Chaque chunk porteur d'un choix porte aussi
usage: null. Le champ est toujours déclaré, doncchunk.usagevautnull, jamaisundefined. - Après le chunk de raison d'arrêt, un chunk supplémentaire avec un tableau
choicesvide et unusagerenseigné (prompt_tokens,completion_tokens,total_tokens). - Puis
data: [DONE].
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":21,"total_tokens":35}}
data: [DONE]Sans l'option, le protocole ne porte aucune clé usage, à l'octet près comme un flux
antérieur à l'existence de l'option. Et stream_options sans stream: true est un
400 : c'est le seul
paramètre honoré qui n'existe que dans le protocole de streaming.
Les chiffres sont ceux qui vous ont été facturés
Ils sont relus depuis l'événement d'usage enregistré, les décomptes sur lesquels la facturation a été réglée, jamais une nouvelle estimation. Cela fige l'ordre : le règlement d'abord, le chunk ensuite. Un chunk écrit avant le règlement pourrait annoncer un chiffre que les comptes ne confirment jamais, sans que vous ayez le moyen de le savoir.
Deux conséquences à intégrer dans votre conception :
- Pas de facturation enregistrée, pas de chunk d'usage. Si rien n'était facturable, si la
réservation avait disparu au moment du règlement, ou si le règlement a échoué, le flux
se termine par le chunk d'arrêt et
[DONE], sans rien d'autre. Y publier les chiffres bruts du fournisseur reviendrait à vous annoncer des tokens que personne ne vous a facturés. - Un flux coupé annonce ce qui lui a été facturé. Si le flux est arrêté avant terme, parce
que le plafond réservé ou le plafond d'horloge a été atteint,
finish_reasonvautlengthet les décomptes sont ceux inscrits sur l'événement, pas des chiffres du fournisseur qui ne sont jamais arrivés.
Ce chunk est au mieux-effort, comme en amont : OpenAI documente la même réserve, « si le flux est interrompu ou annulé, il se peut que vous ne receviez pas le chunk d'usage final ». Traitez-le donc comme la réponse pratique, pas comme l'autorité.
Il n'y a pas d'argent sur le protocole
usage n'a pas de champ de coût, et n'en aura pas. C'est l'objet que chaque SDK
désérialise dans un modèle typé généré, et un décodeur strict échoue sur une propriété
inconnue : c'est précisément la garantie de compatibilité que cette fonctionnalité doit
tenir. Le flux est aussi structurellement le mauvais endroit pour un montant, puisque ce
chunk peut légitimement ne jamais arriver, alors qu'une facturation fait autorité.
Les tokens annoncés ici sont ceux qui ont été facturés. Le montant, lui, vit là où il est
auditable : getBalance() et
getUsageSummary().