mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00

* 🚀 feat: Add automatic refill settings to balance schema * 🚀 feat: Refactor balance feature to use global interface configuration * 🚀 feat: Implement auto-refill functionality for balance management * 🚀 feat: Enhance auto-refill logic and configuration for balance management * 🚀 chore: Bump version to 0.7.74 in package.json and package-lock.json * 🚀 chore: Bump version to 0.0.5 in package.json and package-lock.json * 🚀 docs: Update comment for balance settings in librechat.example.yaml * chore: space in `.env.example` * 🚀 feat: Implement balance configuration loading and refactor related components * 🚀 test: Refactor tests to use custom config for balance feature * 🚀 fix: Update balance response handling in Transaction.js to use Balance model * 🚀 test: Update AppService tests to include balance configuration in mock setup * 🚀 test: Enhance AppService tests with complete balance configuration scenarios * 🚀 refactor: Rename balanceConfig to balance and update related tests for clarity * 🚀 refactor: Remove loadDefaultBalance and update balance handling in AppService * 🚀 test: Update AppService tests to reflect new balance structure and defaults * 🚀 test: Mock getCustomConfig in BaseClient tests to control balance configuration * 🚀 test: Add get method to mockCache in OpenAIClient tests for improved cache handling * 🚀 test: Mock getCustomConfig in OpenAIClient tests to control balance configuration * 🚀 test: Remove mock for getCustomConfig in OpenAIClient tests to streamline configuration handling * 🚀 fix: Update balance configuration reference in config.js for consistency * refactor: Add getBalanceConfig function to retrieve balance configuration * chore: Comment out example balance settings in librechat.example.yaml * refactor: Replace getCustomConfig with getBalanceConfig for balance handling * fix: tests * refactor: Replace getBalanceConfig call with balance from request locals * refactor: Update balance handling to use environment variables for configuration * refactor: Replace getBalanceConfig calls with balance from request locals * refactor: Simplify balance configuration logic in getBalanceConfig --------- Co-authored-by: Danny Avila <danny@librechat.ai>
189 lines
6 KiB
JavaScript
189 lines
6 KiB
JavaScript
const mongoose = require('mongoose');
|
|
const { transactionSchema } = require('@librechat/data-schemas');
|
|
const { getBalanceConfig } = require('~/server/services/Config');
|
|
const { getMultiplier, getCacheMultiplier } = require('./tx');
|
|
const { logger } = require('~/config');
|
|
const Balance = require('./Balance');
|
|
const cancelRate = 1.15;
|
|
|
|
/** Method to calculate and set the tokenValue for a transaction */
|
|
transactionSchema.methods.calculateTokenValue = function () {
|
|
if (!this.valueKey || !this.tokenType) {
|
|
this.tokenValue = this.rawAmount;
|
|
}
|
|
const { valueKey, tokenType, model, endpointTokenConfig } = this;
|
|
const multiplier = Math.abs(getMultiplier({ valueKey, tokenType, model, endpointTokenConfig }));
|
|
this.rate = multiplier;
|
|
this.tokenValue = this.rawAmount * multiplier;
|
|
if (this.context && this.tokenType === 'completion' && this.context === 'incomplete') {
|
|
this.tokenValue = Math.ceil(this.tokenValue * cancelRate);
|
|
this.rate *= cancelRate;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Static method to create a transaction and update the balance
|
|
* @param {txData} txData - Transaction data.
|
|
*/
|
|
transactionSchema.statics.create = async function (txData) {
|
|
const Transaction = this;
|
|
if (txData.rawAmount != null && isNaN(txData.rawAmount)) {
|
|
return;
|
|
}
|
|
|
|
const transaction = new Transaction(txData);
|
|
transaction.endpointTokenConfig = txData.endpointTokenConfig;
|
|
transaction.calculateTokenValue();
|
|
|
|
await transaction.save();
|
|
|
|
const balance = await getBalanceConfig();
|
|
if (!balance?.enabled) {
|
|
return;
|
|
}
|
|
|
|
let balanceResponse = await Balance.findOne({ user: transaction.user }).lean();
|
|
let incrementValue = transaction.tokenValue;
|
|
|
|
if (balanceResponse && balanceResponse.tokenCredits + incrementValue < 0) {
|
|
incrementValue = -balanceResponse.tokenCredits;
|
|
}
|
|
|
|
balanceResponse = await Balance.findOneAndUpdate(
|
|
{ user: transaction.user },
|
|
{ $inc: { tokenCredits: incrementValue } },
|
|
{ upsert: true, new: true },
|
|
).lean();
|
|
|
|
return {
|
|
rate: transaction.rate,
|
|
user: transaction.user.toString(),
|
|
balance: balanceResponse.tokenCredits,
|
|
[transaction.tokenType]: incrementValue,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Static method to create a structured transaction and update the balance
|
|
* @param {txData} txData - Transaction data.
|
|
*/
|
|
transactionSchema.statics.createStructured = async function (txData) {
|
|
const Transaction = this;
|
|
|
|
const transaction = new Transaction({
|
|
...txData,
|
|
endpointTokenConfig: txData.endpointTokenConfig,
|
|
});
|
|
|
|
transaction.calculateStructuredTokenValue();
|
|
|
|
await transaction.save();
|
|
|
|
const balance = await getBalanceConfig();
|
|
if (!balance?.enabled) {
|
|
return;
|
|
}
|
|
|
|
let balanceResponse = await Balance.findOne({ user: transaction.user }).lean();
|
|
let incrementValue = transaction.tokenValue;
|
|
|
|
if (balanceResponse && balanceResponse.tokenCredits + incrementValue < 0) {
|
|
incrementValue = -balanceResponse.tokenCredits;
|
|
}
|
|
|
|
balanceResponse = await Balance.findOneAndUpdate(
|
|
{ user: transaction.user },
|
|
{ $inc: { tokenCredits: incrementValue } },
|
|
{ upsert: true, new: true },
|
|
).lean();
|
|
|
|
return {
|
|
rate: transaction.rate,
|
|
user: transaction.user.toString(),
|
|
balance: balanceResponse.tokenCredits,
|
|
[transaction.tokenType]: incrementValue,
|
|
};
|
|
};
|
|
|
|
/** Method to calculate token value for structured tokens */
|
|
transactionSchema.methods.calculateStructuredTokenValue = function () {
|
|
if (!this.tokenType) {
|
|
this.tokenValue = this.rawAmount;
|
|
return;
|
|
}
|
|
|
|
const { model, endpointTokenConfig } = this;
|
|
|
|
if (this.tokenType === 'prompt') {
|
|
const inputMultiplier = getMultiplier({ tokenType: 'prompt', model, endpointTokenConfig });
|
|
const writeMultiplier =
|
|
getCacheMultiplier({ cacheType: 'write', model, endpointTokenConfig }) ?? inputMultiplier;
|
|
const readMultiplier =
|
|
getCacheMultiplier({ cacheType: 'read', model, endpointTokenConfig }) ?? inputMultiplier;
|
|
|
|
this.rateDetail = {
|
|
input: inputMultiplier,
|
|
write: writeMultiplier,
|
|
read: readMultiplier,
|
|
};
|
|
|
|
const totalPromptTokens =
|
|
Math.abs(this.inputTokens || 0) +
|
|
Math.abs(this.writeTokens || 0) +
|
|
Math.abs(this.readTokens || 0);
|
|
|
|
if (totalPromptTokens > 0) {
|
|
this.rate =
|
|
(Math.abs(inputMultiplier * (this.inputTokens || 0)) +
|
|
Math.abs(writeMultiplier * (this.writeTokens || 0)) +
|
|
Math.abs(readMultiplier * (this.readTokens || 0))) /
|
|
totalPromptTokens;
|
|
} else {
|
|
this.rate = Math.abs(inputMultiplier); // Default to input rate if no tokens
|
|
}
|
|
|
|
this.tokenValue = -(
|
|
Math.abs(this.inputTokens || 0) * inputMultiplier +
|
|
Math.abs(this.writeTokens || 0) * writeMultiplier +
|
|
Math.abs(this.readTokens || 0) * readMultiplier
|
|
);
|
|
|
|
this.rawAmount = -totalPromptTokens;
|
|
} else if (this.tokenType === 'completion') {
|
|
const multiplier = getMultiplier({ tokenType: this.tokenType, model, endpointTokenConfig });
|
|
this.rate = Math.abs(multiplier);
|
|
this.tokenValue = -Math.abs(this.rawAmount) * multiplier;
|
|
this.rawAmount = -Math.abs(this.rawAmount);
|
|
}
|
|
|
|
if (this.context && this.tokenType === 'completion' && this.context === 'incomplete') {
|
|
this.tokenValue = Math.ceil(this.tokenValue * cancelRate);
|
|
this.rate *= cancelRate;
|
|
if (this.rateDetail) {
|
|
this.rateDetail = Object.fromEntries(
|
|
Object.entries(this.rateDetail).map(([k, v]) => [k, v * cancelRate]),
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
const Transaction = mongoose.model('Transaction', transactionSchema);
|
|
|
|
/**
|
|
* Queries and retrieves transactions based on a given filter.
|
|
* @async
|
|
* @function getTransactions
|
|
* @param {Object} filter - MongoDB filter object to apply when querying transactions.
|
|
* @returns {Promise<Array>} A promise that resolves to an array of matched transactions.
|
|
* @throws {Error} Throws an error if querying the database fails.
|
|
*/
|
|
async function getTransactions(filter) {
|
|
try {
|
|
return await Transaction.find(filter).lean();
|
|
} catch (error) {
|
|
logger.error('Error querying transactions:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
module.exports = { Transaction, getTransactions };
|