Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
812b34e
chore: remove unused redis file
danny-avila Apr 11, 2025
ed690ec
chore: bump keyv dependencies, and update related imports
danny-avila Apr 11, 2025
e76b9d3
refactor: Implement IoRedis client for rate limiting across middlewar…
danny-avila Apr 11, 2025
2a4e112
fix: Set max listeners to expected amount
danny-avila Apr 11, 2025
4e58abb
WIP: memory improvements
danny-avila Apr 11, 2025
6a9a27d
refactor: Simplify getAbortData assignment in createAbortController
danny-avila Apr 11, 2025
c8f2355
refactor: Update getAbortData to use WeakRef for content management
danny-avila Apr 12, 2025
9aa93d4
WIP: memory improvements in agent chat requests
danny-avila Apr 12, 2025
7ee7d63
refactor: Enhance memory management with finalization registry and cl…
danny-avila Apr 12, 2025
37d9f4a
refactor: Simplify domainParser calls by removing unnecessary request…
danny-avila Apr 12, 2025
65fa01d
refactor: Update parameter types for action tools and agent loading f…
danny-avila Apr 12, 2025
7b6940a
refactor: Simplify domainParser tests by removing unnecessary request…
danny-avila Apr 12, 2025
3c69bc4
refactor: Simplify domainParser call by removing unnecessary request …
danny-avila Apr 12, 2025
a89c981
refactor: Enhance client disposal by nullifying additional properties…
danny-avila Apr 12, 2025
30dbebc
refactor: Improve title generation by adding abort controller and tim…
danny-avila Apr 12, 2025
9c74f13
refactor: Update checkIdleConnections to skip current user when check…
danny-avila Apr 12, 2025
31d90e2
refactor: Update createMCPTool to derive userId from config and handl…
danny-avila Apr 12, 2025
ec20b7a
refactor: Introduce createTokenCounter function and update tokenCount…
danny-avila Apr 12, 2025
58dbae4
refactor: Update getMCPManager to accept userId parameter for improve…
danny-avila Apr 12, 2025
ed62ff9
refactor: Extract logToolError function for improved error handling i…
danny-avila Apr 12, 2025
3890348
refactor: Update disposeClient to clear handlerRegistry and graphRunn…
danny-avila Apr 12, 2025
ef1c93a
refactor: Extract createHandleNewToken function to streamline token h…
danny-avila Apr 12, 2025
accf8df
chore: bump @librechat/agents
danny-avila Apr 12, 2025
c5721ac
refactor: Improve timeout handling in addTitle function for better er…
danny-avila Apr 12, 2025
10d14de
refactor: Introduce createFetch instead of using class method
danny-avila Apr 12, 2025
30e0453
refactor: Enhance client disposal and request data handling in AskCon…
danny-avila Apr 12, 2025
129770c
refactor: Update import statements for AnthropicClient and OpenAIClie…
danny-avila Apr 12, 2025
86ab0f5
refactor: Use WeakRef for response handling in SplitStreamHandler to …
danny-avila Apr 12, 2025
09b3a86
refactor: Simplify client disposal and rename getReqData to processRe…
danny-avila Apr 12, 2025
e033904
refactor: Improve logging structure and parameter handling in OpenAIC…
danny-avila Apr 12, 2025
da09074
refactor: Remove unused GraphEvents and improve stream event handling…
danny-avila Apr 12, 2025
bd862ed
refactor: Simplify client initialization in AskController and EditCon…
danny-avila Apr 12, 2025
9872cb3
refactor: Remove unused mock functions and implement in-memory store …
danny-avila Apr 12, 2025
64cb541
chore: Update dependencies in package-lock.json to latest versions
danny-avila Apr 12, 2025
bbf086a
refactor: Await token usage recording in OpenAIClient to ensure prope…
danny-avila Apr 12, 2025
ce40ffa
refactor: Remove handleAbort route from multiple endpoints and enhanc…
danny-avila Apr 12, 2025
ad1bfb5
refactor: Enhance abort controller logic by managing abortKey more ef…
danny-avila Apr 12, 2025
ee339fa
refactor: Add newConversation handling in useEventHandlers for improv…
danny-avila Apr 12, 2025
266efac
fix: dropparams
danny-avila Apr 12, 2025
e190d01
refactor: Use optional chaining for safer access to request propertie…
danny-avila Apr 12, 2025
83b091b
refactor: Move client disposal and request data processing logic to c…
danny-avila Apr 12, 2025
d14927c
refactor: Remove aborted request check from addTitle function for cle…
danny-avila Apr 12, 2025
062bff0
feat: Add Grok 3 model pricing and update tests for new models
danny-avila Apr 12, 2025
08dd8f5
chore: Remove trace warnings and inspect flags from backend start scr…
danny-avila Apr 12, 2025
87149a8
refactor: Replace user identifier handling with userId for consistenc…
danny-avila Apr 12, 2025
1c8e222
refactor: Enhance client disposal logic to prevent memory leaks by cl…
danny-avila Apr 12, 2025
6ebe531
chore: Update @librechat/agents to version 2.4.14 in package.json and…
danny-avila Apr 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions api/app/clients/AnthropicClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {
getResponseSender,
validateVisionModel,
} = require('librechat-data-provider');
const { SplitStreamHandler: _Handler, GraphEvents } = require('@librechat/agents');
const { SplitStreamHandler: _Handler } = require('@librechat/agents');
const {
truncateText,
formatMessage,
Expand All @@ -26,10 +26,11 @@ const {
const { getModelMaxTokens, getModelMaxOutputTokens, matchModelName } = require('~/utils');
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
const { createFetch, createStreamEventHandlers } = require('./generators');
const Tokenizer = require('~/server/services/Tokenizer');
const { logger, sendEvent } = require('~/config');
const { sleep } = require('~/server/utils');
const BaseClient = require('./BaseClient');
const { logger } = require('~/config');

const HUMAN_PROMPT = '\n\nHuman:';
const AI_PROMPT = '\n\nAssistant:';
Expand Down Expand Up @@ -184,7 +185,10 @@ class AnthropicClient extends BaseClient {
getClient(requestOptions) {
/** @type {Anthropic.ClientOptions} */
const options = {
fetch: this.fetch,
fetch: createFetch({
directEndpoint: this.options.directEndpoint,
reverseProxyUrl: this.options.reverseProxyUrl,
}),
apiKey: this.apiKey,
};

Expand Down Expand Up @@ -795,14 +799,11 @@ class AnthropicClient extends BaseClient {
}

logger.debug('[AnthropicClient]', { ...requestOptions });
const handlers = createStreamEventHandlers(this.options.res);
this.streamHandler = new SplitStreamHandler({
accumulate: true,
runId: this.responseMessageId,
handlers: {
[GraphEvents.ON_RUN_STEP]: (event) => sendEvent(this.options.res, event),
[GraphEvents.ON_MESSAGE_DELTA]: (event) => sendEvent(this.options.res, event),
[GraphEvents.ON_REASONING_DELTA]: (event) => sendEvent(this.options.res, event),
},
handlers,
});

let intermediateReply = this.streamHandler.tokens;
Expand Down
50 changes: 33 additions & 17 deletions api/app/clients/BaseClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,10 @@ class BaseClient {
month: 'long',
day: 'numeric',
});
this.fetch = this.fetch.bind(this);
/** @type {boolean} */
this.skipSaveConvo = false;
/** @type {boolean} */
this.skipSaveUserMessage = false;
/** @type {ClientDatabaseSavePromise} */
this.userMessagePromise;
/** @type {ClientDatabaseSavePromise} */
this.responsePromise;
/** @type {string} */
this.user;
/** @type {string} */
Expand Down Expand Up @@ -564,6 +559,8 @@ class BaseClient {
}

async sendMessage(message, opts = {}) {
/** @type {Promise<TMessage>} */
let userMessagePromise;
const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } =
await this.handleStartMethods(message, opts);

Expand Down Expand Up @@ -625,11 +622,11 @@ class BaseClient {
}

if (!isEdited && !this.skipSaveUserMessage) {
this.userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user);
userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user);
this.savedMessageIds.add(userMessage.messageId);
if (typeof opts?.getReqData === 'function') {
opts.getReqData({
userMessagePromise: this.userMessagePromise,
userMessagePromise,
});
}
}
Expand All @@ -655,7 +652,9 @@ class BaseClient {

/** @type {string|string[]|undefined} */
const completion = await this.sendCompletion(payload, opts);
this.abortController.requestCompleted = true;
if (this.abortController) {
this.abortController.requestCompleted = true;
}

/** @type {TMessage} */
const responseMessage = {
Expand Down Expand Up @@ -703,7 +702,13 @@ class BaseClient {
if (usage != null && Number(usage[this.outputTokensKey]) > 0) {
responseMessage.tokenCount = usage[this.outputTokensKey];
completionTokens = responseMessage.tokenCount;
await this.updateUserMessageTokenCount({ usage, tokenCountMap, userMessage, opts });
await this.updateUserMessageTokenCount({
usage,
tokenCountMap,
userMessage,
userMessagePromise,
opts,
});
} else {
responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage);
completionTokens = responseMessage.tokenCount;
Expand All @@ -712,8 +717,8 @@ class BaseClient {
await this.recordTokenUsage({ promptTokens, completionTokens, usage });
}

if (this.userMessagePromise) {
await this.userMessagePromise;
if (userMessagePromise) {
await userMessagePromise;
}

if (this.artifactPromises) {
Expand All @@ -728,7 +733,11 @@ class BaseClient {
}
}

this.responsePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user);
responseMessage.databasePromise = this.saveMessageToDatabase(
responseMessage,
saveOptions,
user,
);
this.savedMessageIds.add(responseMessage.messageId);
delete responseMessage.tokenCount;
return responseMessage;
Expand All @@ -749,9 +758,16 @@ class BaseClient {
* @param {StreamUsage} params.usage
* @param {Record<string, number>} params.tokenCountMap
* @param {TMessage} params.userMessage
* @param {Promise<TMessage>} params.userMessagePromise
* @param {object} params.opts
*/
async updateUserMessageTokenCount({ usage, tokenCountMap, userMessage, opts }) {
async updateUserMessageTokenCount({
usage,
tokenCountMap,
userMessage,
userMessagePromise,
opts,
}) {
/** @type {boolean} */
const shouldUpdateCount =
this.calculateCurrentTokenCount != null &&
Expand Down Expand Up @@ -787,7 +803,7 @@ class BaseClient {
Note: we update the user message to be sure it gets the calculated token count;
though `AskController` saves the user message, EditController does not
*/
await this.userMessagePromise;
await userMessagePromise;
await this.updateMessageInDatabase({
messageId: userMessage.messageId,
tokenCount: userMessageTokenCount,
Expand Down Expand Up @@ -853,7 +869,7 @@ class BaseClient {
}

const savedMessage = await saveMessage(
this.options.req,
this.options?.req,
{
...message,
endpoint: this.options.endpoint,
Expand All @@ -877,7 +893,7 @@ class BaseClient {
const existingConvo =
this.fetchedConvo === true
? null
: await getConvo(this.options.req?.user?.id, message.conversationId);
: await getConvo(this.options?.req?.user?.id, message.conversationId);

const unsetFields = {};
const exceptions = new Set(['spec', 'iconURL']);
Expand All @@ -897,7 +913,7 @@ class BaseClient {
}
}

const conversation = await saveConvo(this.options.req, fieldsToKeep, {
const conversation = await saveConvo(this.options?.req, fieldsToKeep, {
context: 'api/app/clients/BaseClient.js - saveMessageToDatabase #saveConvo',
unsetFields,
});
Expand Down
4 changes: 2 additions & 2 deletions api/app/clients/ChatGPTClient.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Keyv = require('keyv');
const { Keyv } = require('keyv');
const crypto = require('crypto');
const { CohereClient } = require('cohere-ai');
const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source');
Expand Down Expand Up @@ -339,7 +339,7 @@ class ChatGPTClient extends BaseClient {
opts.body = JSON.stringify(modelOptions);

if (modelOptions.stream) {
// eslint-disable-next-line no-async-promise-executor

return new Promise(async (resolve, reject) => {
try {
let done = false;
Expand Down
31 changes: 17 additions & 14 deletions api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const OpenAI = require('openai');
const { OllamaClient } = require('./OllamaClient');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { SplitStreamHandler, GraphEvents } = require('@librechat/agents');
const { SplitStreamHandler } = require('@librechat/agents');
const {
Constants,
ImageDetail,
Expand Down Expand Up @@ -32,17 +32,18 @@ const {
createContextHandlers,
} = require('./prompts');
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
const { createFetch, createStreamEventHandlers } = require('./generators');
const { addSpaceIfNeeded, isEnabled, sleep } = require('~/server/utils');
const Tokenizer = require('~/server/services/Tokenizer');
const { spendTokens } = require('~/models/spendTokens');
const { handleOpenAIErrors } = require('./tools/util');
const { createLLM, RunManager } = require('./llm');
const { logger, sendEvent } = require('~/config');
const ChatGPTClient = require('./ChatGPTClient');
const { summaryBuffer } = require('./memory');
const { runTitleChain } = require('./chains');
const { tokenSplit } = require('./document');
const BaseClient = require('./BaseClient');
const { logger } = require('~/config');

class OpenAIClient extends BaseClient {
constructor(apiKey, options = {}) {
Expand Down Expand Up @@ -609,7 +610,7 @@ class OpenAIClient extends BaseClient {
return result.trim();
}

logger.debug('[OpenAIClient] sendCompletion: result', result);
logger.debug('[OpenAIClient] sendCompletion: result', { ...result });

if (this.isChatCompletion) {
reply = result.choices[0].message.content;
Expand Down Expand Up @@ -818,7 +819,7 @@ ${convo}

const completionTokens = this.getTokenCount(title);

this.recordTokenUsage({ promptTokens, completionTokens, context: 'title' });
await this.recordTokenUsage({ promptTokens, completionTokens, context: 'title' });
} catch (e) {
logger.error(
'[OpenAIClient] There was an issue generating the title with the completion method',
Expand Down Expand Up @@ -1245,7 +1246,10 @@ ${convo}
let chatCompletion;
/** @type {OpenAI} */
const openai = new OpenAI({
fetch: this.fetch,
fetch: createFetch({
directEndpoint: this.options.directEndpoint,
reverseProxyUrl: this.options.reverseProxyUrl,
}),
apiKey: this.apiKey,
...opts,
});
Expand Down Expand Up @@ -1275,12 +1279,13 @@ ${convo}
}

if (this.options.addParams && typeof this.options.addParams === 'object') {
const addParams = { ...this.options.addParams };
modelOptions = {
...modelOptions,
...this.options.addParams,
...addParams,
};
logger.debug('[OpenAIClient] chatCompletion: added params', {
addParams: this.options.addParams,
addParams: addParams,
modelOptions,
});
}
Expand Down Expand Up @@ -1309,11 +1314,12 @@ ${convo}
}

if (this.options.dropParams && Array.isArray(this.options.dropParams)) {
this.options.dropParams.forEach((param) => {
const dropParams = [...this.options.dropParams];
dropParams.forEach((param) => {
delete modelOptions[param];
});
logger.debug('[OpenAIClient] chatCompletion: dropped params', {
dropParams: this.options.dropParams,
dropParams: dropParams,
modelOptions,
});
}
Expand Down Expand Up @@ -1355,15 +1361,12 @@ ${convo}
delete modelOptions.reasoning_effort;
}

const handlers = createStreamEventHandlers(this.options.res);
this.streamHandler = new SplitStreamHandler({
reasoningKey,
accumulate: true,
runId: this.responseMessageId,
handlers: {
[GraphEvents.ON_RUN_STEP]: (event) => sendEvent(this.options.res, event),
[GraphEvents.ON_MESSAGE_DELTA]: (event) => sendEvent(this.options.res, event),
[GraphEvents.ON_REASONING_DELTA]: (event) => sendEvent(this.options.res, event),
},
handlers,
});

intermediateReply = this.streamHandler.tokens;
Expand Down
10 changes: 6 additions & 4 deletions api/app/clients/PluginsClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,14 @@ class PluginsClient extends OpenAIClient {
await this.recordTokenUsage(responseMessage);
}

this.responsePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user);
const databasePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user);
delete responseMessage.tokenCount;
return { ...responseMessage, ...result };
return { ...responseMessage, ...result, databasePromise };
}

async sendMessage(message, opts = {}) {
/** @type {Promise<TMessage>} */
let userMessagePromise;
/** @type {{ filteredTools: string[], includedTools: string[] }} */
const { filteredTools = [], includedTools = [] } = this.options.req.app.locals;

Expand Down Expand Up @@ -327,10 +329,10 @@ class PluginsClient extends OpenAIClient {
}

if (!this.skipSaveUserMessage) {
this.userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user);
userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user);
if (typeof opts?.getReqData === 'function') {
opts.getReqData({
userMessagePromise: this.userMessagePromise,
userMessagePromise,
});
}
}
Expand Down
Loading