mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 01:10:14 +01:00
⌚ feat: Add Current Datetime to Assistants (v1/v2) (#4952)
* Feature: Added ability to send current date and time to v1 and v2 assistants * remove date_feature.patch * fix: rename append_today_date to append_current_datetime * feat: Refactor time handling in chatV1 and chatV2, add date and time utility functions * fix: Add warning log and response for missing run values in abortRun middleware --------- Co-authored-by: Max Sanna <max@maxsanna.com>
This commit is contained in:
parent
b5c9144127
commit
1dbe6ee75d
38 changed files with 378 additions and 67 deletions
|
|
@ -1,5 +1,6 @@
|
|||
const { v4 } = require('uuid');
|
||||
const {
|
||||
Time,
|
||||
Constants,
|
||||
RunStatus,
|
||||
CacheKeys,
|
||||
|
|
@ -24,6 +25,7 @@ const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
|
|||
const { formatMessage, createVisionPrompt } = require('~/app/clients/prompts');
|
||||
const { createRun, StreamRunManager } = require('~/server/services/Runs');
|
||||
const { addTitle } = require('~/server/services/Endpoints/assistants');
|
||||
const { createRunBody } = require('~/server/services/createRunBody');
|
||||
const { getTransactions } = require('~/models/Transaction');
|
||||
const checkBalance = require('~/models/checkBalance');
|
||||
const { getConvo } = require('~/models/Conversation');
|
||||
|
|
@ -32,8 +34,6 @@ const { getModelMaxTokens } = require('~/utils');
|
|||
const { getOpenAIClient } = require('./helpers');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const ten_minutes = 1000 * 60 * 10;
|
||||
|
||||
/**
|
||||
* @route POST /
|
||||
* @desc Chat with an assistant
|
||||
|
|
@ -59,6 +59,7 @@ const chatV1 = async (req, res) => {
|
|||
messageId: _messageId,
|
||||
conversationId: convoId,
|
||||
parentMessageId: _parentId = Constants.NO_PARENT,
|
||||
clientTimestamp,
|
||||
} = req.body;
|
||||
|
||||
/** @type {OpenAIClient} */
|
||||
|
|
@ -304,24 +305,14 @@ const chatV1 = async (req, res) => {
|
|||
};
|
||||
|
||||
/** @type {CreateRunBody | undefined} */
|
||||
const body = {
|
||||
const body = createRunBody({
|
||||
assistant_id,
|
||||
model,
|
||||
};
|
||||
|
||||
if (promptPrefix) {
|
||||
body.additional_instructions = promptPrefix;
|
||||
}
|
||||
|
||||
if (typeof endpointOption.artifactsPrompt === 'string' && endpointOption.artifactsPrompt) {
|
||||
body.additional_instructions = `${body.additional_instructions ?? ''}\n${
|
||||
endpointOption.artifactsPrompt
|
||||
}`.trim();
|
||||
}
|
||||
|
||||
if (instructions) {
|
||||
body.instructions = instructions;
|
||||
}
|
||||
promptPrefix,
|
||||
instructions,
|
||||
endpointOption,
|
||||
clientTimestamp,
|
||||
});
|
||||
|
||||
const getRequestFileIds = async () => {
|
||||
let thread_file_ids = [];
|
||||
|
|
@ -518,7 +509,7 @@ const chatV1 = async (req, res) => {
|
|||
});
|
||||
|
||||
run_id = run.id;
|
||||
await cache.set(cacheKey, `${thread_id}:${run_id}`, ten_minutes);
|
||||
await cache.set(cacheKey, `${thread_id}:${run_id}`, Time.TEN_MINUTES);
|
||||
sendInitialResponse();
|
||||
|
||||
// todo: retry logic
|
||||
|
|
@ -529,7 +520,7 @@ const chatV1 = async (req, res) => {
|
|||
/** @type {{[AssistantStreamEvents.ThreadRunCreated]: (event: ThreadRunCreated) => Promise<void>}} */
|
||||
const handlers = {
|
||||
[AssistantStreamEvents.ThreadRunCreated]: async (event) => {
|
||||
await cache.set(cacheKey, `${thread_id}:${event.data.id}`, ten_minutes);
|
||||
await cache.set(cacheKey, `${thread_id}:${event.data.id}`, Time.TEN_MINUTES);
|
||||
run_id = event.data.id;
|
||||
sendInitialResponse();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ const { createErrorHandler } = require('~/server/controllers/assistants/errors')
|
|||
const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
|
||||
const { createRun, StreamRunManager } = require('~/server/services/Runs');
|
||||
const { addTitle } = require('~/server/services/Endpoints/assistants');
|
||||
const { createRunBody } = require('~/server/services/createRunBody');
|
||||
const { getTransactions } = require('~/models/Transaction');
|
||||
const checkBalance = require('~/models/checkBalance');
|
||||
const { getConvo } = require('~/models/Conversation');
|
||||
|
|
@ -31,8 +32,6 @@ const { getModelMaxTokens } = require('~/utils');
|
|||
const { getOpenAIClient } = require('./helpers');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const ten_minutes = 1000 * 60 * 10;
|
||||
|
||||
/**
|
||||
* @route POST /
|
||||
* @desc Chat with an assistant
|
||||
|
|
@ -58,6 +57,7 @@ const chatV2 = async (req, res) => {
|
|||
messageId: _messageId,
|
||||
conversationId: convoId,
|
||||
parentMessageId: _parentId = Constants.NO_PARENT,
|
||||
clientTimestamp,
|
||||
} = req.body;
|
||||
|
||||
/** @type {OpenAIClient} */
|
||||
|
|
@ -186,22 +186,14 @@ const chatV2 = async (req, res) => {
|
|||
};
|
||||
|
||||
/** @type {CreateRunBody | undefined} */
|
||||
const body = {
|
||||
const body = createRunBody({
|
||||
assistant_id,
|
||||
model,
|
||||
};
|
||||
|
||||
if (promptPrefix) {
|
||||
body.additional_instructions = promptPrefix;
|
||||
}
|
||||
|
||||
if (typeof endpointOption.artifactsPrompt === 'string' && endpointOption.artifactsPrompt) {
|
||||
body.additional_instructions = `${body.additional_instructions ?? ''}\n${endpointOption.artifactsPrompt}`.trim();
|
||||
}
|
||||
|
||||
if (instructions) {
|
||||
body.instructions = instructions;
|
||||
}
|
||||
promptPrefix,
|
||||
instructions,
|
||||
endpointOption,
|
||||
clientTimestamp,
|
||||
});
|
||||
|
||||
const getRequestFileIds = async () => {
|
||||
let thread_file_ids = [];
|
||||
|
|
@ -361,7 +353,7 @@ const chatV2 = async (req, res) => {
|
|||
});
|
||||
|
||||
run_id = run.id;
|
||||
await cache.set(cacheKey, `${thread_id}:${run_id}`, ten_minutes);
|
||||
await cache.set(cacheKey, `${thread_id}:${run_id}`, Time.TEN_MINUTES);
|
||||
sendInitialResponse();
|
||||
|
||||
// todo: retry logic
|
||||
|
|
@ -372,7 +364,7 @@ const chatV2 = async (req, res) => {
|
|||
/** @type {{[AssistantStreamEvents.ThreadRunCreated]: (event: ThreadRunCreated) => Promise<void>}} */
|
||||
const handlers = {
|
||||
[AssistantStreamEvents.ThreadRunCreated]: async (event) => {
|
||||
await cache.set(cacheKey, `${thread_id}:${event.data.id}`, ten_minutes);
|
||||
await cache.set(cacheKey, `${thread_id}:${event.data.id}`, Time.TEN_MINUTES);
|
||||
run_id = event.data.id;
|
||||
sendInitialResponse();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,8 +19,15 @@ const createAssistant = async (req, res) => {
|
|||
try {
|
||||
const { openai } = await getOpenAIClient({ req, res });
|
||||
|
||||
const { tools = [], endpoint, conversation_starters, ...assistantData } = req.body;
|
||||
const {
|
||||
tools = [],
|
||||
endpoint,
|
||||
conversation_starters,
|
||||
append_current_datetime,
|
||||
...assistantData
|
||||
} = req.body;
|
||||
delete assistantData.conversation_starters;
|
||||
delete assistantData.append_current_datetime;
|
||||
|
||||
assistantData.tools = tools
|
||||
.map((tool) => {
|
||||
|
|
@ -49,6 +56,9 @@ const createAssistant = async (req, res) => {
|
|||
if (conversation_starters) {
|
||||
createData.conversation_starters = conversation_starters;
|
||||
}
|
||||
if (append_current_datetime !== undefined) {
|
||||
createData.append_current_datetime = append_current_datetime;
|
||||
}
|
||||
|
||||
const document = await updateAssistantDoc({ assistant_id: assistant.id }, createData);
|
||||
|
||||
|
|
@ -60,6 +70,10 @@ const createAssistant = async (req, res) => {
|
|||
assistant.conversation_starters = document.conversation_starters;
|
||||
}
|
||||
|
||||
if (append_current_datetime !== undefined) {
|
||||
assistant.append_current_datetime = append_current_datetime;
|
||||
}
|
||||
|
||||
logger.debug('/assistants/', assistant);
|
||||
res.status(201).json(assistant);
|
||||
} catch (error) {
|
||||
|
|
@ -102,7 +116,12 @@ const patchAssistant = async (req, res) => {
|
|||
await validateAuthor({ req, openai });
|
||||
|
||||
const assistant_id = req.params.id;
|
||||
const { endpoint: _e, conversation_starters, ...updateData } = req.body;
|
||||
const {
|
||||
endpoint: _e,
|
||||
conversation_starters,
|
||||
append_current_datetime,
|
||||
...updateData
|
||||
} = req.body;
|
||||
updateData.tools = (updateData.tools ?? [])
|
||||
.map((tool) => {
|
||||
if (typeof tool !== 'string') {
|
||||
|
|
@ -127,6 +146,11 @@ const patchAssistant = async (req, res) => {
|
|||
updatedAssistant.conversation_starters = conversationStartersUpdate.conversation_starters;
|
||||
}
|
||||
|
||||
if (append_current_datetime !== undefined) {
|
||||
await updateAssistantDoc({ assistant_id }, { append_current_datetime });
|
||||
updatedAssistant.append_current_datetime = append_current_datetime;
|
||||
}
|
||||
|
||||
res.json(updatedAssistant);
|
||||
} catch (error) {
|
||||
logger.error('[/assistants/:id] Error updating assistant', error);
|
||||
|
|
@ -219,6 +243,7 @@ const getAssistantDocuments = async (req, res) => {
|
|||
conversation_starters: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
append_current_datetime: 1,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,15 @@ const createAssistant = async (req, res) => {
|
|||
/** @type {{ openai: OpenAIClient }} */
|
||||
const { openai } = await getOpenAIClient({ req, res });
|
||||
|
||||
const { tools = [], endpoint, conversation_starters, ...assistantData } = req.body;
|
||||
const {
|
||||
tools = [],
|
||||
endpoint,
|
||||
conversation_starters,
|
||||
append_current_datetime,
|
||||
...assistantData
|
||||
} = req.body;
|
||||
delete assistantData.conversation_starters;
|
||||
delete assistantData.append_current_datetime;
|
||||
|
||||
assistantData.tools = tools
|
||||
.map((tool) => {
|
||||
|
|
@ -46,6 +53,9 @@ const createAssistant = async (req, res) => {
|
|||
if (conversation_starters) {
|
||||
createData.conversation_starters = conversation_starters;
|
||||
}
|
||||
if (append_current_datetime !== undefined) {
|
||||
createData.append_current_datetime = append_current_datetime;
|
||||
}
|
||||
|
||||
const document = await updateAssistantDoc({ assistant_id: assistant.id }, createData);
|
||||
|
||||
|
|
@ -56,6 +66,9 @@ const createAssistant = async (req, res) => {
|
|||
if (document.conversation_starters) {
|
||||
assistant.conversation_starters = document.conversation_starters;
|
||||
}
|
||||
if (append_current_datetime !== undefined) {
|
||||
assistant.append_current_datetime = append_current_datetime;
|
||||
}
|
||||
|
||||
logger.debug('/assistants/', assistant);
|
||||
res.status(201).json(assistant);
|
||||
|
|
@ -89,6 +102,14 @@ const updateAssistant = async ({ req, openai, assistant_id, updateData }) => {
|
|||
delete updateData.conversation_starters;
|
||||
}
|
||||
|
||||
if (updateData?.append_current_datetime !== undefined) {
|
||||
await updateAssistantDoc(
|
||||
{ assistant_id: assistant_id },
|
||||
{ append_current_datetime: updateData.append_current_datetime },
|
||||
);
|
||||
delete updateData.append_current_datetime;
|
||||
}
|
||||
|
||||
let hasFileSearch = false;
|
||||
for (const tool of updateData.tools ?? []) {
|
||||
let actualTool = typeof tool === 'string' ? req.app.locals.availableTools[tool] : tool;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ async function abortRun(req, res) {
|
|||
const cacheKey = `${req.user.id}:${conversationId}`;
|
||||
const cache = getLogStores(CacheKeys.ABORT_KEYS);
|
||||
const runValues = await cache.get(cacheKey);
|
||||
if (!runValues) {
|
||||
logger.warn('[abortRun] Run not found in cache', { cacheKey });
|
||||
return res.status(204).send({ message: 'Run not found' });
|
||||
}
|
||||
const [thread_id, run_id] = runValues.split(':');
|
||||
|
||||
if (!run_id) {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ async function buildEndpointOption(req, res, next) {
|
|||
const builder = isAgents ? (...args) => endpointFn(req, ...args) : endpointFn;
|
||||
|
||||
// TODO: use object params
|
||||
req.body.endpointOption = builder(endpoint, parsedBody, endpointType);
|
||||
req.body.endpointOption = await builder(endpoint, parsedBody, endpointType);
|
||||
|
||||
// TODO: use `getModelsConfig` only when necessary
|
||||
const modelsConfig = await getModelsConfig(req);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
const { removeNullishValues } = require('librechat-data-provider');
|
||||
const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
|
||||
const { getAssistant } = require('~/models/Assistant');
|
||||
|
||||
const buildOptions = (endpoint, parsedBody) => {
|
||||
const buildOptions = async (endpoint, parsedBody) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { promptPrefix, assistant_id, iconURL, greeting, spec, artifacts, ...modelOptions } =
|
||||
parsedBody;
|
||||
|
|
@ -15,6 +16,21 @@ const buildOptions = (endpoint, parsedBody) => {
|
|||
modelOptions,
|
||||
});
|
||||
|
||||
if (assistant_id) {
|
||||
const assistantDoc = await getAssistant({ assistant_id });
|
||||
|
||||
if (assistantDoc) {
|
||||
// Create a clean assistant object with only the needed properties
|
||||
endpointOption.assistant = {
|
||||
append_current_datetime: assistantDoc.append_current_datetime,
|
||||
assistant_id: assistantDoc.assistant_id,
|
||||
conversation_starters: assistantDoc.conversation_starters,
|
||||
createdAt: assistantDoc.createdAt,
|
||||
updatedAt: assistantDoc.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof artifacts === 'string') {
|
||||
endpointOption.artifactsPrompt = generateArtifactsPrompt({ endpoint, artifacts });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
const { removeNullishValues } = require('librechat-data-provider');
|
||||
const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
|
||||
const { getAssistant } = require('~/models/Assistant');
|
||||
|
||||
const buildOptions = (endpoint, parsedBody) => {
|
||||
const buildOptions = async (endpoint, parsedBody) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { promptPrefix, assistant_id, iconURL, greeting, spec, artifacts, ...modelOptions } =
|
||||
parsedBody;
|
||||
|
|
@ -15,6 +16,19 @@ const buildOptions = (endpoint, parsedBody) => {
|
|||
modelOptions,
|
||||
});
|
||||
|
||||
if (assistant_id) {
|
||||
const assistantDoc = await getAssistant({ assistant_id });
|
||||
if (assistantDoc) {
|
||||
endpointOption.assistant = {
|
||||
append_current_datetime: assistantDoc.append_current_datetime,
|
||||
assistant_id: assistantDoc.assistant_id,
|
||||
conversation_starters: assistantDoc.conversation_starters,
|
||||
createdAt: assistantDoc.createdAt,
|
||||
updatedAt: assistantDoc.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof artifacts === 'string') {
|
||||
endpointOption.artifactsPrompt = generateArtifactsPrompt({ endpoint, artifacts });
|
||||
}
|
||||
|
|
|
|||
78
api/server/services/createRunBody.js
Normal file
78
api/server/services/createRunBody.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Obtains the date string in 'YYYY-MM-DD' format.
|
||||
*
|
||||
* @param {string} [clientTimestamp] - Optional ISO timestamp string. If provided, uses this timestamp;
|
||||
* otherwise, uses the current date.
|
||||
* @returns {string} - The date string in 'YYYY-MM-DD' format.
|
||||
*/
|
||||
function getDateStr(clientTimestamp) {
|
||||
return clientTimestamp ? clientTimestamp.split('T')[0] : new Date().toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the time string in 'HH:MM:SS' format.
|
||||
*
|
||||
* @param {string} [clientTimestamp] - Optional ISO timestamp string. If provided, uses this timestamp;
|
||||
* otherwise, uses the current time.
|
||||
* @returns {string} - The time string in 'HH:MM:SS' format.
|
||||
*/
|
||||
function getTimeStr(clientTimestamp) {
|
||||
return clientTimestamp
|
||||
? clientTimestamp.split('T')[1].split('.')[0]
|
||||
: new Date().toTimeString().split(' ')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the body object for a run request.
|
||||
*
|
||||
* @param {Object} options - The options for creating the run body.
|
||||
* @param {string} options.assistant_id - The assistant ID.
|
||||
* @param {string} options.model - The model name.
|
||||
* @param {string} [options.promptPrefix] - The prompt prefix to include.
|
||||
* @param {string} [options.instructions] - The instructions to include.
|
||||
* @param {Object} [options.endpointOption={}] - The endpoint options.
|
||||
* @param {string} [options.clientTimestamp] - Client timestamp in ISO format.
|
||||
*
|
||||
* @returns {Object} - The constructed body object for the run request.
|
||||
*/
|
||||
const createRunBody = ({
|
||||
assistant_id,
|
||||
model,
|
||||
promptPrefix,
|
||||
instructions,
|
||||
endpointOption = {},
|
||||
clientTimestamp,
|
||||
}) => {
|
||||
const body = {
|
||||
assistant_id,
|
||||
model,
|
||||
};
|
||||
|
||||
let systemInstructions = '';
|
||||
|
||||
if (endpointOption.assistant?.append_current_datetime) {
|
||||
const dateStr = getDateStr(clientTimestamp);
|
||||
const timeStr = getTimeStr(clientTimestamp);
|
||||
systemInstructions = `Current date and time: ${dateStr} ${timeStr}\n`;
|
||||
}
|
||||
|
||||
if (promptPrefix) {
|
||||
systemInstructions += promptPrefix;
|
||||
}
|
||||
|
||||
if (typeof endpointOption?.artifactsPrompt === 'string' && endpointOption.artifactsPrompt) {
|
||||
systemInstructions += `\n${endpointOption.artifactsPrompt}`;
|
||||
}
|
||||
|
||||
if (systemInstructions.trim()) {
|
||||
body.additional_instructions = systemInstructions.trim();
|
||||
}
|
||||
|
||||
if (instructions) {
|
||||
body.instructions = instructions;
|
||||
}
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
module.exports = { createRunBody, getDateStr, getTimeStr };
|
||||
Loading…
Add table
Add a link
Reference in a new issue