Comptabilisation des jetons de streaming dans une passerelle AI API : utilisation finale, annulations et réponses partielles
Le streaming améliore la latence perçue, mais il peut perturber l'analyse de l'utilisation et la facturation de l'IA si la passerelle ne transmet que des octets. Voici un modèle de machine à états pratique pour capturer l'utilisation finale, les flux interrompus, les erreurs du fournisseur et les réponses partielles.
Les réponses LLM en streaming sont faciles à proxy et difficiles à facturer correctement. Si une passerelle AI API transmet les événements envoyés par le serveur au client mais traite les premiers fragments comme un enregistrement d'utilisation, les analyses des locataires dériveront. La dérive apparaît généralement dans des litiges tels que : "l'utilisateur n'a vu que la moitié de la réponse", "le fournisseur a facturé plus que ce que notre tableau de bord indique", "le quota a été libéré trop tôt" ou "un délai d'attente a produit des jetons mais aucune ligne de facture."
Le problème fondamental est que les appels diffusés en streaming ne constituent pas un seul événement. Il s'agit d'une séquence : demande acceptée, flux amont ouvert, octets livrés, utilisation finale signalée, fournisseur arrêté, client déconnecté, passerelle expirée et facturation réglée. Une passerelle fiable doit modéliser ces états explicitement au lieu de supposer qu'une réponse HTTP terminée est le seul chemin réussi.
Le mode échec : le streaming masque la frontière comptable
Les complétions non diffusées renvoient généralement un objet de réponse avec des métadonnées d'utilisation. Une passerelle peut normaliser cette utilisation, écrire une ligne dans le grand livre, mettre à jour le quota et émettre des analyses en un seul passage.
Le streaming modifie les limites. L'expérience utilisateur est incrémentielle, mais la vérité sur la facturation peut arriver à la fin, lors d'un événement final spécifique au fournisseur, dans un delta cumulé, via une réponse SDK agrégée ou plus tard via les API de reporting du fournisseur. Si le client se déconnecte avant l'événement d'utilisation final, la passerelle peut n'avoir fourni qu'une partie de la réponse alors que le fournisseur a quand même généré et facturé davantage de jetons.
Fait : OpenAI documente que les appelants en streaming qui souhaitent des données d'utilisation doivent définir stream_options avec include_usage. OpenAI fournit également des paramètres d'utilisation et de coût au niveau de l'organisation, tout en notant que l'utilisation et les coûts ne peuvent pas toujours être parfaitement conciliés à des fins financières.
Fait : Anthropic le streaming utilise des événements envoyés par le serveur tels que message_start, content_block_delta, message_delta et message_stop. Ses message_delta informations d'utilisation sont cumulatives, une passerelle ne doit donc pas additionner chaque delta d'utilisation.
Fait : Gemini et les API de streaming de style Vertex peuvent exposer des fragments incrémentiels, tandis que les SDK peuvent également fournir un objet de réponse agrégé. Pour les passerelles, ce chemin agrégé peut constituer une meilleure source d'utilisation complète que les morceaux visibles seuls.
Utilisez une machine à états de flux, pas un indicateur de réussite booléen
Une requête diffusée en streaming doit avoir un enregistrement d'utilisation durable avant le début de l'appel en amont. Cet enregistrement devrait passer par des états explicites. Un minimum pratique est :
accepted: la passerelle a authentifié la clé, attribué le locataire et créé une ligne de grand livre ouverte.first_byte_sent: au moins un événement de sortie a atteint le client en aval.provider_completed: le fournisseur en amont a émis un signal d'arrêt normal ou un objet de réponse terminé.client_aborted: le socket en aval s'est fermé avant l'achèvement normal de la passerelle.provider_error: le fournisseur en amont a renvoyé une erreur après le début du flux ou avant l'arrivée de l'utilisation finale.gateway_timeout: la passerelle a appliqué son budget de latence et a mis fin à la requête.settled: la passerelle a converti l'utilisation en coût du locataire et en consommation de quota.reconciled: les données ultérieures d'utilisation ou de coût du fournisseur ont confirmé ou ajusté la ligne.
Ce modèle évite un bug d'analyse courant : marquer chaque flux qui produit du texte comme "réussi et exact". Un flux peut être à la fois utile à l'utilisateur, incomplet de la part du fournisseur, estimé pour la facturation et en attente de rapprochement.
Champs du grand livre recommandés
Gardez la ligne d'heure de la requête petite mais explicite :
{
"request_id": "gw_req_...",
"tenant_id": "tenant_123",
"api_key_id": "key_456",
"provider": "openai|anthropic|gemini|...",
"provider_request_id": null,
"model": "provider-model-id",
"state": "accepted",
"stream": true,
"input_tokens": null,
"output_tokens_billed": null,
"output_tokens_delivered_estimate": 0,
"provider_usage_source": null,
"billing_status": "pending_reconciliation",
"client_abort_at": null,
"provider_completed_at": null,
"settled_at": null,
"error_class": null
}
La séparation importante est output_tokens_billed contre output_tokens_delivered_estimate. Les utilisateurs se soucient de ce qui atteint leur application. Les finances se soucient de ce que le fournisseur a facturé. Ces chiffres peuvent différer après des déconnexions, des flux d'appels d'outils, des jetons de raisonnement cachés, des jetons mis en cache, des arrêts de sécurité ou des délais d'attente de passerelle.
Règles de capture spécifiques au fournisseur
Un OpenAI compatible avec le fournisseur API est utile pour les développeurs d'applications, mais l'adaptateur de passerelle nécessite toujours des règles de comptabilité spécifiques au fournisseur.
OpenAIdiffusion compatible
Pour OpenAI routes, exposez une option de passerelle qui permet la création de rapports d'utilisation en amont lorsqu'elle est prise en charge. Un modèle courant consiste à accepter une valeur par défaut au niveau de la passerelle telle que :
{
"stream": true,
"stream_options": {
"include_usage": true
}
}
Si l'appelant en aval l'omet, la passerelle peut décider de l'injecter pour les routes où cela est compatible. Documentez ce comportement, car certains clients s'attendent à une compatibilité filaire exacte et certains modèles ou composants en amont peuvent ne pas prendre en charge l'utilisation finale de la même manière.
Recommandation : ne réglez pas le coût du locataire à partir des premières tranches. Gardez la ligne du grand livre ouverte jusqu'à ce que l'événement d'utilisation final soit capturé, que la réponse du fournisseur se termine sans utilisation ou que le flux entre dans un chemin d'erreur ou d'annulation.
Anthropic diffusion en continu
L'utilisation cumulée deAnthropic nécessite une règle différente. Si une passerelle voit trois message_delta événements avec des nombres de jetons de sortie de 10, 25 et 40, le nombre de sorties est de 40 et non de 75.
let latestUsage = null;
for await (const event of anthropicStream) {
if (event.type === "message_delta" && event.usage) {
if (latestUsage && event.usage.output_tokens < latestUsage.output_tokens) {
emit("cumulative_usage_regressed", requestId);
}
latestUsage = event.usage;
}
forwardToClient(event);
}
settleFromLatestCumulativeUsage(latestUsage);
Recommandation : enregistrez la dernière valeur d'utilisation cumulée et émettez un événement d'observabilité si elle régresse. Une régression peut indiquer des bugs de l'analyseur, des événements en double, des changements de fournisseur ou des flux mixtes.
Gemini et streaming de style Vertex
Gemini prend en charge les morceaux de streaming pour réduire la latence perçue. Dans les SDK de style Vertex, le streaming peut exposer à la fois un flux asynchrone et un objet de réponse agrégé. Une passerelle doit conserver ce chemin agrégé lorsqu'il est disponible.
const streamingResult = await model.generateContentStream(request);
for await (const chunk of streamingResult.stream) {
forwardChunk(chunk);
countDeliveredBytesOrText(chunk);
}
const aggregated = await streamingResult.response;
settleFromAggregatedUsage(aggregated);
Recommandation : évitez de créer toute la comptabilité à partir de morceaux visibles si le SDK donne un enregistrement de réponse complet. Les morceaux sont destinés à la latence. L'objet final est souvent meilleur pour la facturation et l'analyse.
Gérer les déconnexions des clients comme des événements comptables de premier ordre
Les déconnexions de clients sont des situations dans lesquelles de nombreuses passerelles perdent de l'argent ou surchargent leurs clients. Un onglet de navigateur se ferme, un réseau mobile est interrompu ou une application annule une demande. La passerelle remarque que le socket en aval est fermé, mais le fournisseur en amont peut toujours générer.
La passerelle doit faire un choix de politique explicite :
- Annuler immédiatement en amont : réduit le gaspillage de génération et les coûts du fournisseur, mais peut interrompre les flux de travail où le backend a toujours besoin du résultat après la déconnexion de l'interface utilisateur.
- Continuer en amont en arrière-plan : peut préserver le travail des consommateurs côté serveur, mais l'utilisateur peut ne pas voir tous les jetons générés et facturés.
- Comportement dépendant de l'itinéraire : annulez le chat interactif, continuez pour les flux de travail de type tâche et rendez le paramètre visible aux locataires.
Une méthode par défaut pratique pour le streaming interactif consiste à annuler en amont lorsque le client en aval se déconnecte, puis à marquer la ligne du grand livre comme client_aborted. Si l'utilisation finale arrive lors de l'annulation, réglez à partir de cette utilisation faisant autorité. Sinon, marquez la ligne estimated ou pending_reconciliation plutôt que de prétendre qu'elle est exacte.
downstream.on("close", async () => {
if (!providerCompleted) {
ledger.markClientAborted(requestId);
await upstream.abort().catch(() => {
ledger.emit("upstream_cancel_failed", requestId);
});
}
});
Recommandation : exposez des étiquettes de facturation transparentes telles que final, provider_reconciled, estimated, waived ou pending_reconciliation. C'est plus défendable que d'afficher chaque appel diffusé comme étant immédiatement exact.
Application des quotas pendant un flux
Une facturation précise dépend généralement de l'utilisation finale du fournisseur, mais l'application des quotas ne peut pas toujours attendre la fin. Un locataire disposant d'un budget important ne devrait pas être autorisé à diffuser indéfiniment, car l'utilisation exacte n'est pas disponible en cours de vol.
Utilisez deux mécanismes ensemble :
- Réservation en amont : réservez un maximum estimé en fonction du modèle, du nombre maximal de jetons demandés, de la politique du locataire et du solde actuel.
- Vérifications de la pression de streaming : estime la sortie fournie pendant le flux et s'arrête si la requête franchit une limite de sécurité configurée.
Il s'agit d'un mécanisme de contrôle, pas de la facture finale. Les fournisseurs peuvent compter les jetons mis en cache, les jetons de raisonnement, les jetons multimodaux ou les jetons cachés différemment de l'estimateur d'une passerelle.
Compromis : les estimations en temps réel aident à respecter les budgets, mais elles peuvent différer des jetons facturés par le fournisseur. Le règlement final doit utiliser l'utilisation d'un fournisseur faisant autorité lorsqu'il est disponible, et le rapprochement doit ajuster les estimations ultérieurement.
Événements d'observabilité qui détectent les bugs de comptabilité
Les échecs de facturation du streaming sont plus faciles à déboguer lorsque la passerelle émet des événements ciblés au lieu de uniquement des journaux de requêtes génériques. Ajoutez des événements tels que :
final_usage_missing: le flux s'est terminé sans utilisation faisant autorité.cumulative_usage_regressed: le nombre cumulé de jetons a reculé.stream_ended_without_stop_event: aucun marqueur d'arrêt normal du fournisseur n'a été observé.aborted_after_provider_completion: le fournisseur a terminé, mais le client en aval s'est fermé avant que la passerelle ait terminé le transfert.settled_from_estimate: le grand livre des locataires a utilisé une estimation car l'utilisation finale n'était pas disponible.reconciliation_adjusted_usage: les rapports du fournisseur ont modifié la ligne ultérieurement.
Fait : Les conventions sémantiques d'OpenTelemetry GenAI recommandent d'utiliser les informations d'utilisation renvoyées par le fournisseur pour diffuser les réponses lorsqu'elles sont disponibles, et mettent en garde contre la communication des métriques d'utilisation si le nombre de jetons ne peut pas être obtenu de manière efficace ou précise.
Pour les analyses d'utilisation de l'IA, cela signifie que les tableaux de bord doivent prendre en charge les niveaux de confiance. Un graphique qui mélange des valeurs finales, estimées et rapprochées sans étiquettes peut sembler clair mais induire en erreur les équipes financières et d'assistance.
Tests de conformité pour la comptabilité en streaming
Ne comptez pas sur des tests manuels avec une invite de discussion Happy Path. Chaque adaptateur de fournisseur doit disposer de tests de conformité pour les cas de rupture des registres :
- Flux normal : l'utilisation finale arrive, événement d'arrêt observé, le grand livre est réglé comme
final. - Flux d'appels d'outils : les deltas d'appels d'outils sont transmis, l'utilisation est capturée, les métadonnées structurées ne corrompent pas le comptage des jetons.
- Arrêt de sécurité ou de refus : le fournisseur s'arrête plus tôt, l'utilisation est toujours réglée correctement.
- Déconnexion forcée du client : en aval se ferme après une sortie partielle ; en amont est annulé ou poursuivi conformément à la politique.
- En amont 5xx après une sortie partielle : la passerelle enregistre la livraison partielle et ne marque pas la demande comme un succès net.
- Délai d'expiration de la passerelle avant l'utilisation finale : ligne devient une estimation ou est en attente de rapprochement.
- Événement final manquant : l'adaptateur émet
final_usage_missinget évite les étiquettes de facturation exactes.
Ces tests doivent vérifier les transitions d'état, les champs du grand livre, les événements d'observabilité émis et le comportement en aval. La compatibilité des flux octet par octet ne suffit pas ; les effets secondaires comptables font partie du contrat.
Liste de contrôle de mise en œuvre pratique
- Créez la ligne du grand livre d'utilisation avant de distribuer la requête en amont.
- Stockez les identifiants du locataire, de la clé, de l'utilisateur, du modèle, de l'itinéraire, du fournisseur et de la demande au moment de la demande.
- Activer les rapports d'utilisation finale du fournisseur lorsque cela est pris en charge, par exemple OpenAI-compatible
stream_options.include_usage. - Pour les fournisseurs cumulatifs, stockez la dernière valeur d'utilisation au lieu de additionner les événements.
- Préserver les objets de réponse agrégés lorsque les SDK les fournissent.
- Suivez le résultat fourni séparément de l'utilisation facturée par le fournisseur.
- Lors de la déconnexion, annulez en amont conformément à la politique de routage et marquez
client_aborted. - Utilisez des statuts de facturation transparents : final, estimé, en attente de rapprochement, fournisseur rapproché ou abandonné.
- Émettre des événements d'observabilité spécifiques à la comptabilité.
- Réconciliez plus tard avec les rapports d'utilisation ou de coûts du fournisseur lorsqu'ils sont disponibles, tout en préservant l'attribution des locataires au moment de la demande.
Que montrer aux locataires
Les locataires n'ont pas besoin de tous les événements internes, mais ils ont besoin d'étiquettes honnêtes. Un tableau d'utilisation utile pourrait afficher :
- Statut : final, estimé ou rapproché.
- Résultat de la demande : terminée, client abandonné, erreur du fournisseur ou délai d'expiration de la passerelle.
- Résultat fourni : texte ou octets approximatifs envoyés au client.
- Jetons facturés : utilisation normalisée par le fournisseur utilisée pour le coût.
- Ajustement : tout delta de rapprochement ultérieur.
Cette conception réduit l'ambiguïté du support. Si un utilisateur n'a vu qu'une partie d'une réponse, le tableau de bord peut expliquer si le fournisseur a déjà terminé, si la passerelle a annulé en amont et si les frais sont définitifs ou estimés.
Recommandations versus prédictions
Recommandations : traitez les requêtes diffusées en continu comme des machines à états, attendez l'utilisation finale faisant autorité avant le règlement exact, séparez la sortie livrée de l'utilisation facturée et étiquetez honnêtement les lignes estimées. Les adaptateurs de fournisseur doivent coder la sémantique d'utilisation spécifique au fournisseur plutôt que d'aplatir chaque flux dans un proxy d'octet générique.
Prédiction : la comptabilité en streaming deviendra plus importante à mesure que les modèles exposeront davantage de travail caché : jetons de raisonnement, remises sur les jetons mis en cache, traitement multimodal, traces d'utilisation des outils et arrêts de sécurité. Les passerelles qui séparent déjà l'utilisation facturée par le fournisseur de la sortie visible par le client s'adapteront plus facilement que les passerelles qui ne comptent que le texte diffusé en streaming.
Conclusion exploitable
Si votre passerelle prend en charge le streaming, auditez un chemin aujourd'hui : forcez la déconnexion d'un client après les premiers morceaux et inspectez la ligne du grand livre. S'il indique « succès » avec un nombre exact de jetons, vos analyses mentent probablement.
La solution n'est pas d'abandonner le streaming. Conservez une expérience utilisateur rapide, mais rendez les états comptables explicites de l'achèvement du flux, de l'annulation, des erreurs du fournisseur, de l'utilisation finale manquante et du rapprochement. Cela donne aux équipes produit un résultat réactif, des coûts défendables aux équipes financières et aux équipes d'assistance suffisamment de preuves pour expliquer des réponses partielles sans deviner.