diff --git a/api/server/services/AppService.js b/api/server/services/AppService.js index d194d31a6b..09827c9244 100644 --- a/api/server/services/AppService.js +++ b/api/server/services/AppService.js @@ -12,6 +12,7 @@ const { agentsConfigSetup } = require('./start/agents'); const { initializeRoles } = require('~/models/Role'); const { getMCPManager } = require('~/config'); const paths = require('~/config/paths'); +const { loadTokenRatesConfig } = require('./Config/loadTokenRatesConfig'); /** * @@ -21,9 +22,13 @@ const paths = require('~/config/paths'); */ const AppService = async (app) => { await initializeRoles(); - /** @type {TCustomConfig}*/ + /** @type {TCustomConfig} */ const config = (await loadCustomConfig()) ?? {}; const configDefaults = getConfigDefaults(); + const tokenRatesConfig = loadTokenRatesConfig(config, configDefaults); + // + // // Set the global token rates configuration so that it can be used by the tx.js functions. + // setTokenRatesConfig(tokenRatesConfig); const filteredTools = config.filteredTools; const includedTools = config.includedTools; diff --git a/api/server/services/Config/loadTokenRatesConfig.js b/api/server/services/Config/loadTokenRatesConfig.js new file mode 100644 index 0000000000..cc7d03b8a0 --- /dev/null +++ b/api/server/services/Config/loadTokenRatesConfig.js @@ -0,0 +1,27 @@ +const { removeNullishValues } = require('librechat-data-provider'); +const { logger } = require('~/config'); + +/** + * Loads custom token rates from the user's YAML config, merging with default token rates if available. + * + * @param {TCustomConfig | undefined} config - The loaded custom configuration. + * @param {TConfigDefaults} [configDefaults] - Optional default configuration values. + * @returns {TCustomConfig['tokenRates']} - The final token rates configuration. + */ +function loadTokenRatesConfig(config, configDefaults) { + const userTokenRates = removeNullishValues(config?.tokenRates ?? {}); + + if (!configDefaults?.tokenRates) { + logger.info(`User tokenRates configuration:\n${JSON.stringify(userTokenRates, null, 2)}`); + return userTokenRates; + } + + /** @type {TCustomConfig['tokenRates']} */ + const defaultTokenRates = removeNullishValues(configDefaults.tokenRates); + const merged = { ...defaultTokenRates, ...userTokenRates }; + + logger.info(`Merged tokenRates configuration:\n${JSON.stringify(merged, null, 2)}`); + return merged; +} + +module.exports = { loadTokenRatesConfig }; \ No newline at end of file diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 56a560f2ee..5dbf8f90ee 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -505,12 +505,56 @@ export type TStartupConfig = { helpAndFaqURL: string; customFooter?: string; modelSpecs?: TSpecsConfig; + tokenRates?: TTokenRates; sharedLinksEnabled: boolean; publicSharedLinksEnabled: boolean; analyticsGtmId?: string; instanceProjectId: string; }; +// Token cost schema type +export type TTokenCost = { + prompt?: number; + completion?: number; + cache?: { + write?: number; + read?: number; + }; +}; + +// Endpoint token rates schema type +export type TEndpointTokenRates = Record; + +// Token rates schema type +export type TTokenRates = { + openAI?: TEndpointTokenRates; + google?: TEndpointTokenRates; + anthropic?: TEndpointTokenRates; + bedrock?: TEndpointTokenRates; + custom?: TEndpointTokenRates; +}; + +const tokenCostSchema = z.object({ + prompt: z.number().optional(), // e.g. 1.5 => $1.50 / 1M tokens + completion: z.number().optional(), // e.g. 2.0 => $2.00 / 1M tokens + cache: z + .object({ + write: z.number().optional(), + read: z.number().optional(), + }) + .optional(), +}); + +const endpointTokenRatesSchema = z.record(z.string(), tokenCostSchema); + +const tokenRatesSchema = z.object({ + openAI: endpointTokenRatesSchema.optional(), + google: endpointTokenRatesSchema.optional(), + anthropic: endpointTokenRatesSchema.optional(), + bedrock: endpointTokenRatesSchema.optional(), + custom: endpointTokenRatesSchema.optional(), +}); + export const configSchema = z.object({ version: z.string(), cache: z.boolean().default(true), @@ -542,6 +586,7 @@ export const configSchema = z.object({ rateLimits: rateLimitSchema.optional(), fileConfig: fileConfigSchema.optional(), modelSpecs: specsConfigSchema.optional(), + tokenRates: tokenRatesSchema.optional(), endpoints: z .object({ all: baseEndpointSchema.optional(),