Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🤖 feat: OpenAI Assistants v2 (initial support) #2781

Merged
merged 10 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ ASSISTANTS_API_KEY=user_provided
# ASSISTANTS_BASE_URL=
# ASSISTANTS_MODELS=gpt-4o,gpt-3.5-turbo-0125,gpt-3.5-turbo-16k-0613,gpt-3.5-turbo-16k,gpt-3.5-turbo,gpt-4,gpt-4-0314,gpt-4-32k-0314,gpt-4-0613,gpt-3.5-turbo-0613,gpt-3.5-turbo-1106,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview

#==========================#
# Azure Assistants API #
#==========================#

# Note: You should map your credentials with custom variables according to your Azure OpenAI Configuration
# The models for Azure Assistants are also determined by your Azure OpenAI configuration.

# More info, including how to enable use of Assistants with Azure here:
# https://www.librechat.ai/docs/configuration/librechat_yaml/ai_endpoints/azure#using-assistants-with-azure

#============#
# OpenRouter #
#============#
Expand Down
9 changes: 8 additions & 1 deletion api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,8 @@ class OpenAIClient extends BaseClient {
* In case of failure, it will return the default title, "New Chat".
*/
async titleConvo({ text, conversationId, responseText = '' }) {
this.conversationId = conversationId;

if (this.options.attachments) {
delete this.options.attachments;
}
Expand Down Expand Up @@ -838,13 +840,17 @@ ${convo}

try {
let useChatCompletion = true;

if (this.options.reverseProxyUrl === CohereConstants.API_URL) {
useChatCompletion = false;
}

title = (
await this.sendPayload(instructionsPayload, { modelOptions, useChatCompletion })
).replaceAll('"', '');

const completionTokens = this.getTokenCount(title);

this.recordTokenUsage({ promptTokens, completionTokens, context: 'title' });
} catch (e) {
logger.error(
Expand All @@ -868,6 +874,7 @@ ${convo}
context: 'title',
tokenBuffer: 150,
});

title = await runTitleChain({ llm, text, convo, signal: this.abortController.signal });
} catch (e) {
if (e?.message?.toLowerCase()?.includes('abort')) {
Expand Down Expand Up @@ -1005,9 +1012,9 @@ ${convo}
await spendTokens(
{
context,
user: this.user,
model: this.modelOptions.model,
conversationId: this.conversationId,
user: this.user ?? this.options.req.user?.id,
endpointTokenConfig: this.options.endpointTokenConfig,
},
{ promptTokens, completionTokens },
Expand Down
1 change: 1 addition & 0 deletions api/app/clients/specs/OpenAIClient.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ describe('OpenAIClient', () => {

const defaultOptions = {
// debug: true,
req: {},
openaiApiKey: 'new-api-key',
modelOptions: {
model,
Expand Down
18 changes: 17 additions & 1 deletion api/models/Action.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,24 @@ const deleteAction = async (searchParams, session = null) => {
return await Action.findOneAndDelete(searchParams, options).lean();
};

/**
* Deletes actions by params, within a transaction session if provided.
*
* @param {Object} searchParams - The search parameters to find the actions to delete.
* @param {string} searchParams.action_id - The ID of the action(s) to delete.
* @param {string} searchParams.user - The user ID of the action's author.
* @param {mongoose.ClientSession} [session] - The transaction session to use (optional).
* @returns {Promise<Number>} A promise that resolves to the number of deleted action documents.
*/
const deleteActions = async (searchParams, session = null) => {
const options = session ? { session } : {};
const result = await Action.deleteMany(searchParams, options);
return result.deletedCount;
};

module.exports = {
updateAction,
getActions,
updateAction,
deleteAction,
deleteActions,
};
13 changes: 13 additions & 0 deletions api/models/Assistant.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,21 @@ const getAssistants = async (searchParams) => {
return await Assistant.find(searchParams).lean();
};

/**
* Deletes an assistant based on the provided ID.
*
* @param {Object} searchParams - The search parameters to find the assistant to delete.
* @param {string} searchParams.assistant_id - The ID of the assistant to delete.
* @param {string} searchParams.user - The user ID of the assistant's author.
* @returns {Promise<void>} Resolves when the assistant has been successfully deleted.
*/
const deleteAssistant = async (searchParams) => {
return await Assistant.findOneAndDelete(searchParams);
};

module.exports = {
updateAssistant,
deleteAssistant,
getAssistants,
getAssistant,
};
2 changes: 1 addition & 1 deletion api/models/plugins/mongoMeili.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const createMeiliMongooseModel = function ({ index, attributesToIndex }) {
function (results, value, key) {
return { ...results, [key]: 1 };
},
{ _id: 1 },
{ _id: 1, __v: 1 },
),
).lean();

Expand Down
2 changes: 1 addition & 1 deletion api/models/spendTokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const spendTokens = async (txData, tokenUsage) => {
});
}

if (!completionTokens) {
if (!completionTokens && isNaN(completionTokens)) {
logger.debug('[spendTokens] !completionTokens', { prompt, completion });
return;
}
Expand Down
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"nodejs-gpt": "^1.37.4",
"nodemailer": "^6.9.4",
"ollama": "^0.5.0",
"openai": "4.36.0",
"openai": "^4.47.1",
"openai-chat-tokens": "^0.2.8",
"openid-client": "^5.4.2",
"passport": "^0.6.0",
Expand Down
20 changes: 19 additions & 1 deletion api/server/controllers/EndpointController.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,28 @@ async function endpointController(req, res) {
/** @type {TEndpointsConfig} */
const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
if (mergedConfig[EModelEndpoint.assistants] && req.app.locals?.[EModelEndpoint.assistants]) {
const { disableBuilder, retrievalModels, capabilities, ..._rest } =
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
req.app.locals[EModelEndpoint.assistants];

mergedConfig[EModelEndpoint.assistants] = {
...mergedConfig[EModelEndpoint.assistants],
version,
retrievalModels,
disableBuilder,
capabilities,
};
}

if (
mergedConfig[EModelEndpoint.azureAssistants] &&
req.app.locals?.[EModelEndpoint.azureAssistants]
) {
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
req.app.locals[EModelEndpoint.azureAssistants];

mergedConfig[EModelEndpoint.azureAssistants] = {
...mergedConfig[EModelEndpoint.azureAssistants],
version,
retrievalModels,
disableBuilder,
capabilities,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
const { v4 } = require('uuid');
const express = require('express');
const {
Constants,
RunStatus,
CacheKeys,
FileSources,
ContentTypes,
EModelEndpoint,
ViolationTypes,
ImageVisionTool,
checkOpenAIStorage,
AssistantStreamEvents,
} = require('librechat-data-provider');
const {
Expand All @@ -21,44 +20,36 @@ const {
} = require('~/server/services/Threads');
const { sendResponse, sendMessage, sleep, isEnabled, countTokens } = require('~/server/utils');
const { runAssistant, createOnTextProgress } = require('~/server/services/AssistantService');
const { addTitle, initializeClient } = require('~/server/services/Endpoints/assistants');
const { formatMessage, createVisionPrompt } = require('~/app/clients/prompts');
const { createRun, StreamRunManager } = require('~/server/services/Runs');
const { addTitle } = require('~/server/services/Endpoints/assistants');
const { getTransactions } = require('~/models/Transaction');
const checkBalance = require('~/models/checkBalance');
const { getConvo } = require('~/models/Conversation');
const getLogStores = require('~/cache/getLogStores');
const { getModelMaxTokens } = require('~/utils');
const { getOpenAIClient } = require('./helpers');
const { logger } = require('~/config');

const router = express.Router();
const {
setHeaders,
handleAbort,
validateModel,
handleAbortError,
// validateEndpoint,
buildEndpointOption,
} = require('~/server/middleware');

router.post('/abort', handleAbort());
const { handleAbortError } = require('~/server/middleware');

const ten_minutes = 1000 * 60 * 10;

/**
* @route POST /
* @desc Chat with an assistant
* @access Public
* @param {express.Request} req - The request object, containing the request data.
* @param {express.Response} res - The response object, used to send back a response.
* @param {Express.Request} req - The request object, containing the request data.
* @param {Express.Response} res - The response object, used to send back a response.
* @returns {void}
*/
router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res) => {
const chatV1 = async (req, res) => {
logger.debug('[/assistants/chat/] req.body', req.body);

const {
text,
model,
endpoint,
files = [],
promptPrefix,
assistant_id,
Expand All @@ -70,7 +61,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
} = req.body;

/** @type {Partial<TAssistantEndpoint>} */
const assistantsConfig = req.app.locals?.[EModelEndpoint.assistants];
const assistantsConfig = req.app.locals?.[endpoint];

if (assistantsConfig) {
const { supportedIds, excludedIds } = assistantsConfig;
Expand Down Expand Up @@ -138,7 +129,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
user: req.user.id,
shouldSaveMessage: false,
messageId: responseMessageId,
endpoint: EModelEndpoint.assistants,
endpoint,
};

if (error.message === 'Run cancelled') {
Expand All @@ -149,7 +140,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
logger.debug('[/assistants/chat/] Request aborted on close');
} else if (/Files.*are invalid/.test(error.message)) {
const errorMessage = `Files are invalid, or may not have uploaded yet.${
req.app.locals?.[EModelEndpoint.azureOpenAI].assistants
endpoint === EModelEndpoint.azureAssistants
? ' If using Azure OpenAI, files are only available in the region of the assistant\'s model at the time of upload.'
: ''
}`;
Expand Down Expand Up @@ -205,6 +196,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
const runMessages = await checkMessageGaps({
openai,
run_id,
endpoint,
thread_id,
conversationId,
latestMessageId: responseMessageId,
Expand Down Expand Up @@ -311,8 +303,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
});
};

/** @type {{ openai: OpenAIClient }} */
const { openai: _openai, client } = await initializeClient({
const { openai: _openai, client } = await getOpenAIClient({
req,
res,
endpointOption: req.body.endpointOption,
Expand Down Expand Up @@ -370,10 +361,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res

/** @type {MongoFile[]} */
const attachments = await req.body.endpointOption.attachments;
if (
attachments &&
attachments.every((attachment) => attachment.source === FileSources.openai)
) {
if (attachments && attachments.every((attachment) => checkOpenAIStorage(attachment.source))) {
return;
}

Expand Down Expand Up @@ -431,7 +419,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res

if (processedFiles) {
for (const file of processedFiles) {
if (file.source !== FileSources.openai) {
if (!checkOpenAIStorage(file.source)) {
attachedFileIds.delete(file.file_id);
const index = file_ids.indexOf(file.file_id);
if (index > -1) {
Expand Down Expand Up @@ -467,6 +455,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
assistant_id,
thread_id,
model: assistant_id,
endpoint,
};

previousMessages.push(requestMessage);
Expand All @@ -476,7 +465,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res

conversation = {
conversationId,
endpoint: EModelEndpoint.assistants,
endpoint,
promptPrefix: promptPrefix,
instructions: instructions,
assistant_id,
Expand Down Expand Up @@ -513,7 +502,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
let response;

const processRun = async (retry = false) => {
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
if (endpoint === EModelEndpoint.azureAssistants) {
body.model = openai._options.model;
openai.attachedFileIds = attachedFileIds;
openai.visionPromise = visionPromise;
Expand Down Expand Up @@ -603,6 +592,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
assistant_id,
thread_id,
model: assistant_id,
endpoint,
};

sendMessage(res, {
Expand Down Expand Up @@ -655,6 +645,6 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
} catch (error) {
await handleError(error);
}
});
};

module.exports = router;
module.exports = chatV1;
Loading
Loading