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 API AI transmet les événements envoyés par le serveur au client mais traite les premiers morceaux comme 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é, délai d'expiration de la passerelle 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 agrégée du SDK, 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 indique 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 se concilient pas toujours parfaitement à des fins financières.
Fait : Le streaming anthropique utilise des événements envoyés par le serveur tels que message_start, content_block_delta, message_delta et message_stop. Ses informations d'utilisation message_delta sont cumulatives, une passerelle ne doit donc pas additionner chaque delta d'utilisation.
Fait : Les API de streaming de type Gemini et 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 :
accepté: 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.réglé: la passerelle a converti l'utilisation en coût du locataire et en consommation de quota.concilié: les données ultérieures sur l'utilisation ou les coûts 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 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": nul,
"model": "id-modèle-fournisseur",
"state": "accepté",
"stream": vrai,
"input_tokens": nul,
"output_tokens_billed": nul,
"output_tokens_delivered_estimate": 0,
"provider_usage_source": nul,
"billing_status": "en attente_réconciliation",
"client_abort_at": nul,
"provider_completed_at": nul,
"setled_at": nul,
"classe_erreur": nul
La séparation importante est output_tokens_billed et 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 nombres 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
Une API compatible OpenAI indépendante du fournisseur 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.
Diffusion compatible OpenAI
Pour les routes OpenAI, 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": vrai,
"stream_options": {
"include_usage" : vrai
}
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 les coûts 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.
Streaming anthropique
L'utilisation cumulative d'Anthropic nécessite une règle différente. Si une passerelle voit trois événements message_delta avec des nombres de jetons de sortie de 10, 25 et 40, le nombre de sorties est de 40 et non de 75.
let lastUsage = null;
pour wait (événement const de anthropicStream) {
if (event.type === "message_delta" && event.usage) {
if (latestUsage && event.usage.output_tokens < lastUsage.output_tokens) {
émet("cumulative_usage_regressed", requestId);
}
lastUsage = event.usage;
}
forwardToClient (événement);
}
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.
Diffusion de style Gemini et 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 = wait model.generateContentStream(request);
pour wait (morceau const de streamingResult.stream) {
forwardChunk(morceau);
countDeliveredBytesOrText(morceau);
}
const agrégé = attendre streamingResult.response ;
SettFromAggregatedUsage(aggregated);
Recommandation : évitez de créer toute la comptabilité à partir de morceaux visibles si le SDK fournit 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
De nombreuses passerelles perdent de l'argent ou facturent trop cher leurs clients lors de déconnexions de 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 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 ending_reconciliation plutôt que de prétendre qu'elle est exacte.
downstream.on("close", async() => {
si (!providerCompleted) {
ledger.markClientAborted(requestId);
attendre en amont.abort().catch(() => {
ledger.emit("upstream_cancel_failed", requestId);
});
}
});
Recommandation : exposez des étiquettes de facturation transparentes telles que final, provider_reconciled, estimated, waived ou ending_reconciliation. C'est plus défendable que d'afficher chaque appel diffusé comme étant immédiatement exact.
Application des quotas pendant un flux
La 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 le résultat délivré 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 faire appel à 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 comptables
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: flux terminé sans utilisation faisant autorité.cumulative_usage_regressed: le nombre cumulé de jetons a été déplacé vers l'arrière.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: le rapport du fournisseur a 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 l'analyse de l'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 continu
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 des 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 correctement réglée.
- Déconnexion forcée du client : fermeture en aval 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 utilisation finale : la ligne devient une estimation ou un rapprochement en attente.
- É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 pratique de mise en œuvre
- Créez la ligne du grand livre d'utilisation avant de distribuer la demande en amont.
- Stocker 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 sur l'utilisation finale du fournisseur lorsque cela est pris en charge, par exemple
stream_options.include_usagecompatible avec OpenAI. - Pour les fournisseurs cumulatifs, stockez la dernière valeur d'utilisation au lieu de additionner les événements.
- Conservez les objets de réponse agrégés lorsque les SDK les fournissent.
- Suivez les résultats fournis séparément de l'utilisation facturée par le fournisseur.
- Lors de la déconnexion, annulez en amont conformément à la stratégie de routage et marquez
client_aborted. - Utilisez des statuts de facturation transparents : final, estimé, en attente de rapprochement, rapprochement du fournisseur ou abandon.
- Émettre des événements d'observabilité spécifiques à la comptabilité.
- Rapprochez-vous ultérieurement des 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 montrer :
- 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 le résultat fourni 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 de 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 les réponses partielles sans deviner.