From 35d8ef50f497c6f9625f2a471e3f1db0ea88144e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 16 Jul 2025 17:58:15 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=99=20fix:=20Use=20Fallback=20Token=20?= =?UTF-8?q?Transaction=20if=20No=20Collected=20Usage=20(#8503)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/clients/BaseClient.js | 13 +++++-- api/server/controllers/agents/client.js | 48 +++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index 3815f159be..19f7073fe2 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -108,12 +108,15 @@ class BaseClient { /** * Abstract method to record token usage. Subclasses must implement this method. * If a correction to the token usage is needed, the method should return an object with the corrected token counts. + * Should only be used if `recordCollectedUsage` was not used instead. + * @param {string} [model] * @param {number} promptTokens * @param {number} completionTokens * @returns {Promise} */ - async recordTokenUsage({ promptTokens, completionTokens }) { + async recordTokenUsage({ model, promptTokens, completionTokens }) { logger.debug('[BaseClient] `recordTokenUsage` not implemented.', { + model, promptTokens, completionTokens, }); @@ -741,9 +744,13 @@ class BaseClient { } else { responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage); completionTokens = responseMessage.tokenCount; + await this.recordTokenUsage({ + usage, + promptTokens, + completionTokens, + model: responseMessage.model, + }); } - - await this.recordTokenUsage({ promptTokens, completionTokens, usage }); } if (userMessagePromise) { diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index c02e147110..97cf83ee9b 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -1135,8 +1135,52 @@ class AgentClient extends BaseClient { } } - /** Silent method, as `recordCollectedUsage` is used instead */ - async recordTokenUsage() {} + /** + * @param {object} params + * @param {number} params.promptTokens + * @param {number} params.completionTokens + * @param {OpenAIUsageMetadata} [params.usage] + * @param {string} [params.model] + * @param {string} [params.context='message'] + * @returns {Promise} + */ + async recordTokenUsage({ model, promptTokens, completionTokens, usage, context = 'message' }) { + try { + await spendTokens( + { + model, + context, + conversationId: this.conversationId, + user: this.user ?? this.options.req.user?.id, + endpointTokenConfig: this.options.endpointTokenConfig, + }, + { promptTokens, completionTokens }, + ); + + if ( + usage && + typeof usage === 'object' && + 'reasoning_tokens' in usage && + typeof usage.reasoning_tokens === 'number' + ) { + await spendTokens( + { + model, + context: 'reasoning', + conversationId: this.conversationId, + user: this.user ?? this.options.req.user?.id, + endpointTokenConfig: this.options.endpointTokenConfig, + }, + { completionTokens: usage.reasoning_tokens }, + ); + } + } catch (error) { + logger.error( + '[api/server/controllers/agents/client.js #recordTokenUsage] Error recording token usage', + error, + ); + } + } getEncoding() { return 'o200k_base';