LibreChat/packages/api/src/agents/usage.ts
Danny Avila 8b159079f5
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
🪙 feat: Add messageId to Transactions (#11987)
* feat: Add messageId to transactions

* chore: field order

* feat: Enhance token usage tracking by adding messageId parameter

- Updated `recordTokenUsage` method in BaseClient to accept a new `messageId` parameter for improved tracking.
- Propagated `messageId` in the AgentClient when recording usage.
- Added tests to ensure `messageId` is correctly passed and handled in various scenarios, including propagation across multiple usage entries.

* chore: Correct field order in createGeminiImageTool function

- Moved the conversationId field to the correct position in the object being passed to the recordTokenUsage method, ensuring proper parameter alignment for improved functionality.

* refactor: Update OpenAIChatCompletionController and createResponse to use responseId instead of requestId

- Replaced instances of requestId with responseId in the OpenAIChatCompletionController for improved clarity in logging and tracking.
- Updated createResponse to include responseId in the requestBody, ensuring consistency across the handling of message identifiers.

* test: Add messageId to agent client tests

- Included messageId in the agent client tests to ensure proper handling and propagation of message identifiers during transaction recording.
- This update enhances the test coverage for scenarios involving messageId, aligning with recent changes in the tracking of message identifiers.

* fix: Update OpenAIChatCompletionController to use requestId for context

- Changed the context object in OpenAIChatCompletionController to use `requestId` instead of `responseId` for improved clarity and consistency in handling request identifiers.

* chore: field order
2026-02-27 23:50:13 -05:00

150 lines
3.8 KiB
TypeScript

import { logger } from '@librechat/data-schemas';
import type { TCustomConfig, TTransactionsConfig } from 'librechat-data-provider';
import type { UsageMetadata } from '../stream/interfaces/IJobStore';
import type { EndpointTokenConfig } from '../types/tokens';
interface TokenUsage {
promptTokens?: number;
completionTokens?: number;
}
interface StructuredPromptTokens {
input?: number;
write?: number;
read?: number;
}
interface StructuredTokenUsage {
promptTokens?: StructuredPromptTokens;
completionTokens?: number;
}
interface TxMetadata {
user: string;
model?: string;
context: string;
messageId?: string;
conversationId: string;
balance?: Partial<TCustomConfig['balance']> | null;
transactions?: Partial<TTransactionsConfig>;
endpointTokenConfig?: EndpointTokenConfig;
}
type SpendTokensFn = (txData: TxMetadata, tokenUsage: TokenUsage) => Promise<unknown>;
type SpendStructuredTokensFn = (
txData: TxMetadata,
tokenUsage: StructuredTokenUsage,
) => Promise<unknown>;
export interface RecordUsageDeps {
spendTokens: SpendTokensFn;
spendStructuredTokens: SpendStructuredTokensFn;
}
export interface RecordUsageParams {
user: string;
conversationId: string;
collectedUsage: UsageMetadata[];
model?: string;
context?: string;
messageId?: string;
balance?: Partial<TCustomConfig['balance']> | null;
transactions?: Partial<TTransactionsConfig>;
endpointTokenConfig?: EndpointTokenConfig;
}
export interface RecordUsageResult {
input_tokens: number;
output_tokens: number;
}
/**
* Records token usage for collected LLM calls and spends tokens against balance.
* This handles both sequential execution (tool calls) and parallel execution (multiple agents).
*/
export async function recordCollectedUsage(
deps: RecordUsageDeps,
params: RecordUsageParams,
): Promise<RecordUsageResult | undefined> {
const {
user,
model,
balance,
messageId,
transactions,
conversationId,
collectedUsage,
endpointTokenConfig,
context = 'message',
} = params;
const { spendTokens, spendStructuredTokens } = deps;
if (!collectedUsage || !collectedUsage.length) {
return;
}
const firstUsage = collectedUsage[0];
const input_tokens =
(firstUsage?.input_tokens || 0) +
(Number(firstUsage?.input_token_details?.cache_creation) ||
Number(firstUsage?.cache_creation_input_tokens) ||
0) +
(Number(firstUsage?.input_token_details?.cache_read) ||
Number(firstUsage?.cache_read_input_tokens) ||
0);
let total_output_tokens = 0;
for (const usage of collectedUsage) {
if (!usage) {
continue;
}
const cache_creation =
Number(usage.input_token_details?.cache_creation) ||
Number(usage.cache_creation_input_tokens) ||
0;
const cache_read =
Number(usage.input_token_details?.cache_read) || Number(usage.cache_read_input_tokens) || 0;
total_output_tokens += Number(usage.output_tokens) || 0;
const txMetadata: TxMetadata = {
user,
context,
balance,
messageId,
transactions,
conversationId,
endpointTokenConfig,
model: usage.model ?? model,
};
if (cache_creation > 0 || cache_read > 0) {
spendStructuredTokens(txMetadata, {
promptTokens: {
input: usage.input_tokens,
write: cache_creation,
read: cache_read,
},
completionTokens: usage.output_tokens,
}).catch((err) => {
logger.error('[packages/api #recordCollectedUsage] Error spending structured tokens', err);
});
continue;
}
spendTokens(txMetadata, {
promptTokens: usage.input_tokens,
completionTokens: usage.output_tokens,
}).catch((err) => {
logger.error('[packages/api #recordCollectedUsage] Error spending tokens', err);
});
}
return {
input_tokens,
output_tokens: total_output_tokens,
};
}