mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +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>
204 lines
5.6 KiB
JavaScript
204 lines
5.6 KiB
JavaScript
const mongoose = require('mongoose');
|
|
|
|
jest.mock('./Transaction', () => ({
|
|
Transaction: {
|
|
create: jest.fn(),
|
|
createStructured: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
jest.mock('./Balance', () => ({
|
|
findOne: jest.fn(),
|
|
findOneAndUpdate: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('~/config', () => ({
|
|
logger: {
|
|
debug: jest.fn(),
|
|
error: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
// New config module
|
|
const { getBalanceConfig } = require('~/server/services/Config');
|
|
jest.mock('~/server/services/Config');
|
|
|
|
// Import after mocking
|
|
const { spendTokens, spendStructuredTokens } = require('./spendTokens');
|
|
const { Transaction } = require('./Transaction');
|
|
const Balance = require('./Balance');
|
|
|
|
describe('spendTokens', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
getBalanceConfig.mockResolvedValue({ enabled: true });
|
|
});
|
|
|
|
it('should create transactions for both prompt and completion tokens', async () => {
|
|
const txData = {
|
|
user: new mongoose.Types.ObjectId(),
|
|
conversationId: 'test-convo',
|
|
model: 'gpt-3.5-turbo',
|
|
context: 'test',
|
|
};
|
|
const tokenUsage = {
|
|
promptTokens: 100,
|
|
completionTokens: 50,
|
|
};
|
|
|
|
Transaction.create.mockResolvedValueOnce({ tokenType: 'prompt', rawAmount: -100 });
|
|
Transaction.create.mockResolvedValueOnce({ tokenType: 'completion', rawAmount: -50 });
|
|
Balance.findOne.mockResolvedValue({ tokenCredits: 10000 });
|
|
Balance.findOneAndUpdate.mockResolvedValue({ tokenCredits: 9850 });
|
|
|
|
await spendTokens(txData, tokenUsage);
|
|
|
|
expect(Transaction.create).toHaveBeenCalledTimes(2);
|
|
expect(Transaction.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
tokenType: 'prompt',
|
|
rawAmount: -100,
|
|
}),
|
|
);
|
|
expect(Transaction.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
tokenType: 'completion',
|
|
rawAmount: -50,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should handle zero completion tokens', async () => {
|
|
const txData = {
|
|
user: new mongoose.Types.ObjectId(),
|
|
conversationId: 'test-convo',
|
|
model: 'gpt-3.5-turbo',
|
|
context: 'test',
|
|
};
|
|
const tokenUsage = {
|
|
promptTokens: 100,
|
|
completionTokens: 0,
|
|
};
|
|
|
|
Transaction.create.mockResolvedValueOnce({ tokenType: 'prompt', rawAmount: -100 });
|
|
Transaction.create.mockResolvedValueOnce({ tokenType: 'completion', rawAmount: -0 });
|
|
Balance.findOne.mockResolvedValue({ tokenCredits: 10000 });
|
|
Balance.findOneAndUpdate.mockResolvedValue({ tokenCredits: 9850 });
|
|
|
|
await spendTokens(txData, tokenUsage);
|
|
|
|
expect(Transaction.create).toHaveBeenCalledTimes(2);
|
|
expect(Transaction.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
tokenType: 'prompt',
|
|
rawAmount: -100,
|
|
}),
|
|
);
|
|
expect(Transaction.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
tokenType: 'completion',
|
|
rawAmount: -0,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should handle undefined token counts', async () => {
|
|
const txData = {
|
|
user: new mongoose.Types.ObjectId(),
|
|
conversationId: 'test-convo',
|
|
model: 'gpt-3.5-turbo',
|
|
context: 'test',
|
|
};
|
|
const tokenUsage = {};
|
|
|
|
await spendTokens(txData, tokenUsage);
|
|
|
|
expect(Transaction.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not update balance when the balance feature is disabled', async () => {
|
|
// Override configuration: disable balance updates.
|
|
getBalanceConfig.mockResolvedValue({ enabled: false });
|
|
const txData = {
|
|
user: new mongoose.Types.ObjectId(),
|
|
conversationId: 'test-convo',
|
|
model: 'gpt-3.5-turbo',
|
|
context: 'test',
|
|
};
|
|
const tokenUsage = {
|
|
promptTokens: 100,
|
|
completionTokens: 50,
|
|
};
|
|
|
|
Transaction.create.mockResolvedValueOnce({ tokenType: 'prompt', rawAmount: -100 });
|
|
Transaction.create.mockResolvedValueOnce({ tokenType: 'completion', rawAmount: -50 });
|
|
|
|
await spendTokens(txData, tokenUsage);
|
|
|
|
expect(Transaction.create).toHaveBeenCalledTimes(2);
|
|
// When balance updates are disabled, Balance methods should not be called.
|
|
expect(Balance.findOne).not.toHaveBeenCalled();
|
|
expect(Balance.findOneAndUpdate).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should create structured transactions for both prompt and completion tokens', async () => {
|
|
const txData = {
|
|
user: new mongoose.Types.ObjectId(),
|
|
conversationId: 'test-convo',
|
|
model: 'claude-3-5-sonnet',
|
|
context: 'test',
|
|
};
|
|
const tokenUsage = {
|
|
promptTokens: {
|
|
input: 10,
|
|
write: 100,
|
|
read: 5,
|
|
},
|
|
completionTokens: 50,
|
|
};
|
|
|
|
Transaction.createStructured.mockResolvedValueOnce({
|
|
rate: 3.75,
|
|
user: txData.user.toString(),
|
|
balance: 9570,
|
|
prompt: -430,
|
|
});
|
|
Transaction.create.mockResolvedValueOnce({
|
|
rate: 15,
|
|
user: txData.user.toString(),
|
|
balance: 8820,
|
|
completion: -750,
|
|
});
|
|
|
|
const result = await spendStructuredTokens(txData, tokenUsage);
|
|
|
|
expect(Transaction.createStructured).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
tokenType: 'prompt',
|
|
inputTokens: -10,
|
|
writeTokens: -100,
|
|
readTokens: -5,
|
|
}),
|
|
);
|
|
expect(Transaction.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
tokenType: 'completion',
|
|
rawAmount: -50,
|
|
}),
|
|
);
|
|
expect(result).toEqual({
|
|
prompt: expect.objectContaining({
|
|
rate: 3.75,
|
|
user: txData.user.toString(),
|
|
balance: 9570,
|
|
prompt: -430,
|
|
}),
|
|
completion: expect.objectContaining({
|
|
rate: 15,
|
|
user: txData.user.toString(),
|
|
balance: 8820,
|
|
completion: -750,
|
|
}),
|
|
});
|
|
});
|
|
});
|