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: Model Specs & Save Tools per Convo/Preset #2578

Merged
merged 94 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
9515560
WIP: first pass ModelSpecs
danny-avila Apr 26, 2024
a9b4c41
refactor(onSelectEndpoint): use `getConvoSwitchLogic`
danny-avila Apr 26, 2024
d3c4155
feat: introduce iconURL, greeting, frontend fields for conversations/…
danny-avila Apr 26, 2024
578a0ba
feat: conversation.iconURL & greeting in Landing
danny-avila Apr 26, 2024
1a17665
feat: conversation.iconURL & greeting in New Chat button
danny-avila Apr 26, 2024
36ccf4c
feat: message.iconURL
danny-avila Apr 26, 2024
f4f19f4
refactor: ConversationIcon -> ConvoIconURL
danny-avila Apr 26, 2024
58152cc
WIP: add spec as a conversation field
danny-avila Apr 26, 2024
d280efc
refactor: useAppStartup, set spec on initial load for new chat, allow…
danny-avila Apr 26, 2024
840bbab
feat: handle `showIconInMenu`, `showIconInHeader`, undefined `iconURL…
danny-avila Apr 27, 2024
0271667
chore: handle undefined or empty modelSpecs
danny-avila Apr 27, 2024
f3904cb
WIP: first pass, modelSpec schema for custom config
danny-avila Apr 27, 2024
4a51326
refactor: move default filtered tools definition to ToolService
danny-avila Apr 27, 2024
79a8c1c
feat: pass modelSpecs from backend via startupConfig
danny-avila Apr 27, 2024
6f834d3
refactor: modelSpecs config, return and define list
danny-avila Apr 27, 2024
45df533
fix: react error and include iconURL in responseMessage
danny-avila Apr 28, 2024
002da92
refactor: add iconURL to responseMessage only
danny-avila Apr 28, 2024
7096b18
refactor: getIconEndpoint
danny-avila Apr 28, 2024
c375dd1
refactor: pass TSpecsConfig
danny-avila Apr 28, 2024
5cf8ba2
fix(assistants): differentiate compactAssistantSchema, correctly rese…
danny-avila Apr 28, 2024
4c457c9
refactor: assistant id prefix localStorage key
danny-avila Apr 28, 2024
48ddd2c
refactor: add more LocalStorageKeys and replace hardcoded values
danny-avila Apr 28, 2024
b537da0
feat: prioritize spec on new chat behavior: last selected modelSpec b…
danny-avila Apr 28, 2024
6582e85
feat: first pass, interface config
danny-avila Apr 28, 2024
499c818
chore: WIP, todo: add warnings based on config.modelSpecs settings.
danny-avila Apr 28, 2024
559c6d4
feat: enforce modelSpecs if configured
danny-avila Apr 29, 2024
e9c1f30
feat: show config file yaml errors
danny-avila Apr 29, 2024
748736e
chore: delete unused legacy Plugins component
danny-avila Apr 29, 2024
ee81bfc
refactor: set tools to localStorage from recoil store
danny-avila Apr 29, 2024
6e97bb7
chore: add stable recoil setter to useEffect deps
danny-avila Apr 29, 2024
2017990
refactor: save tools to conversation documents
danny-avila Apr 29, 2024
23d753c
style(MultiSelectPop): dynamic height, remove unused import
danny-avila Apr 29, 2024
19b754e
refactor(react-query): use localstorage keys and pass config to useAv…
danny-avila Apr 29, 2024
6e7b48b
feat(utils): add mapPlugins
danny-avila Apr 29, 2024
3d63197
refactor(Convo): use conversation.tools if defined, lastSelectedTools…
danny-avila Apr 29, 2024
d18a9d5
refactor: remove unused legacy code using `useSetOptions`, remove con…
danny-avila Apr 29, 2024
f5b7694
refactor(PluginStoreDialog): add exhaustive-deps which are stable rea…
danny-avila Apr 29, 2024
dddc2a9
fix(HeaderOptions): pass `popover` as true
danny-avila Apr 29, 2024
54a4f45
refactor(useSetStorage): use project enums
danny-avila Apr 29, 2024
9743287
refactor: use LocalStorageKeys enum
danny-avila Apr 29, 2024
92d1dd5
fix: prevent setConversation from setting falsy values in lastSelecte…
danny-avila Apr 29, 2024
36aadb3
refactor: use map for availableTools state and available Plugins query
danny-avila Apr 29, 2024
a75053a
refactor(updateLastSelectedModel): organize logic better and add note…
danny-avila Apr 29, 2024
1935d74
fix(setAgentOption): prevent reseting last model to secondary model f…
danny-avila Apr 29, 2024
78113b0
refactor(buildDefaultConvo): use enum
danny-avila Apr 29, 2024
6a2461b
refactor: remove `useSetStorage` and consolidate areas where conversa…
danny-avila Apr 29, 2024
658a1a4
fix: conversations retain tools on refresh
danny-avila Apr 29, 2024
218515f
fix(gptPlugins): prevent nullish tools from being saved
danny-avila Apr 29, 2024
5b848ce
chore: delete useServerStream
danny-avila Apr 29, 2024
7dd8263
refactor: move initial plugins logic to useAppStartup
danny-avila Apr 29, 2024
fd7351c
refactor(MultiSelectDropDown): add more pass-in className props
danny-avila Apr 29, 2024
90c93aa
feat: use tools in presets
danny-avila Apr 29, 2024
62ebe2c
chore: delete unused usePresetOptions
danny-avila Apr 29, 2024
7eba8c8
refactor: new agentOptions default handling
danny-avila Apr 29, 2024
5c62be4
chore: note
danny-avila Apr 29, 2024
a5f1109
feat: add label and custom instructions to agents
danny-avila Apr 29, 2024
1060d3f
chore: remove 'disabled with tools' message
danny-avila Apr 29, 2024
98e79ca
style: move plugins to 2nd column in parameters
danny-avila Apr 29, 2024
c9292c8
fix: TPreset type for agentOptions
danny-avila Apr 29, 2024
966b600
fix: interface controls
danny-avila Apr 29, 2024
248be11
refactor: add interfaceConfig, use Separator within Switcher
danny-avila Apr 29, 2024
c237473
refactor: hide Assistants panel if interface.parameters are disabled
danny-avila Apr 30, 2024
df391e5
fix(Header): only modelSpecs if list is greater than 0
danny-avila Apr 30, 2024
0c0b1bc
refactor: separate MessageIcon logic from useMessageHelpers for bette…
danny-avila Apr 30, 2024
e52aaf1
fix(AppService): don't use reserved keyword 'interface'
danny-avila Apr 30, 2024
17dbb23
feat: set existing Icon for custom endpoints through iconURL
danny-avila Apr 30, 2024
d24c0d3
fix(ci): tests passing for App Service
danny-avila Apr 30, 2024
7868909
docs: refactor custom_config.md for readability and better organizati…
danny-avila Apr 30, 2024
4a23d72
docs: interface section and re-organize docs
danny-avila Apr 30, 2024
d56fb3f
docs: update modelSpecs info
danny-avila Apr 30, 2024
5efa422
chore: remove unused files
danny-avila Apr 30, 2024
056f73e
chore: remove unused files
danny-avila Apr 30, 2024
2c01472
chore: move useSetIndexOptions
danny-avila Apr 30, 2024
f8e71a2
chore: remove unused file
danny-avila Apr 30, 2024
939428f
chore: move useConversation(s)
danny-avila Apr 30, 2024
e1d6346
chore: move useDefaultConvo
danny-avila Apr 30, 2024
bd62598
chore: move useNavigateToConvo
danny-avila Apr 30, 2024
e55927c
refactor: use plugin install hook so it can be used elsewhere
danny-avila Apr 30, 2024
5ea4fee
chore: import order
danny-avila Apr 30, 2024
c6d8a8e
update docs
danny-avila Apr 30, 2024
f1d2755
refactor(OpenAI/Plugins): allow modelLabel as an initial value for ch…
danny-avila Apr 30, 2024
445aa41
chore: remove unused EndpointOptionsPopover and hide 'Save as Preset'…
danny-avila Apr 30, 2024
84f1b70
feat(loadDefaultInterface): issue warnings based on values
danny-avila Apr 30, 2024
1c389f3
feat: changelog for custom config file
danny-avila Apr 30, 2024
69d2007
docs: add additional changelog note
danny-avila Apr 30, 2024
207d08e
fix: prevent unavailable tool selection from preset and update availa…
danny-avila Apr 30, 2024
23d7547
feat: add `filteredTools` option in custom config
danny-avila Apr 30, 2024
7d225f4
chore: changelog
danny-avila Apr 30, 2024
a632f23
fix(MessageIcon): always overwrite conversation.iconURL in messageSet…
danny-avila Apr 30, 2024
2d7c517
fix(ModelSpecsMenu): icon edge cases
danny-avila Apr 30, 2024
501f356
fix(NewChat): dynamic icon
danny-avila Apr 30, 2024
523f4ac
fix(PluginsClient): always include endpoint in responseMessage
danny-avila Apr 30, 2024
e46f4c1
fix: always include endpoint and iconURL in responseMessage across di…
danny-avila May 1, 2024
a9860c8
feat: interchangeable keys for modelSpec enforcing
danny-avila May 1, 2024
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
3 changes: 3 additions & 0 deletions api/app/clients/AnthropicClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,9 @@ class AnthropicClient extends BaseClient {
promptPrefix: this.options.promptPrefix,
modelLabel: this.options.modelLabel,
resendFiles: this.options.resendFiles,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
...this.modelOptions,
};
}
Expand Down
15 changes: 14 additions & 1 deletion api/app/clients/BaseClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ class BaseClient {
sender: this.sender,
text: addSpaceIfNeeded(generation) + completion,
promptTokens,
iconURL: this.options.iconURL,
endpoint: this.options.endpoint,
...(this.metadata ?? {}),
};

Expand Down Expand Up @@ -525,8 +527,19 @@ class BaseClient {
return _messages;
}

/**
* Save a message to the database.
* @param {TMessage} message
* @param {Partial<TConversation>} endpointOptions
* @param {string | null} user
*/
async saveMessageToDatabase(message, endpointOptions, user = null) {
await saveMessage({ ...message, endpoint: this.options.endpoint, user, unfinished: false });
await saveMessage({
...message,
endpoint: this.options.endpoint,
unfinished: false,
user,
});
await saveConvo(user, {
conversationId: message.conversationId,
endpoint: this.options.endpoint,
Expand Down
3 changes: 3 additions & 0 deletions api/app/clients/GoogleClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,9 @@ class GoogleClient extends BaseClient {
return {
promptPrefix: this.options.promptPrefix,
modelLabel: this.options.modelLabel,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
...this.modelOptions,
};
}
Expand Down
3 changes: 3 additions & 0 deletions api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,9 @@ class OpenAIClient extends BaseClient {
promptPrefix: this.options.promptPrefix,
resendFiles: this.options.resendFiles,
imageDetail: this.options.imageDetail,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
...this.modelOptions,
};
}
Expand Down
10 changes: 9 additions & 1 deletion api/app/clients/PluginsClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ class PluginsClient extends OpenAIClient {
return {
chatGptLabel: this.options.chatGptLabel,
promptPrefix: this.options.promptPrefix,
tools: this.options.tools,
...this.modelOptions,
agentOptions: this.agentOptions,
iconURL: this.options.iconURL,
greeting: this.options.greeting,
spec: this.options.spec,
};
}

Expand Down Expand Up @@ -144,9 +148,11 @@ class PluginsClient extends OpenAIClient {
signal,
pastMessages,
tools: this.tools,
currentDateString: this.currentDateString,
verbose: this.options.debug,
returnIntermediateSteps: true,
customName: this.options.chatGptLabel,
currentDateString: this.currentDateString,
customInstructions: this.options.promptPrefix,
callbackManager: CallbackManager.fromHandlers({
async handleAgentAction(action, runId) {
handleAction(action, runId, onAgentAction);
Expand Down Expand Up @@ -304,6 +310,8 @@ class PluginsClient extends OpenAIClient {
}

const responseMessage = {
endpoint: EModelEndpoint.gptPlugins,
iconURL: this.options.iconURL,
messageId: responseMessageId,
conversationId,
parentMessageId: userMessage.messageId,
Expand Down
8 changes: 8 additions & 0 deletions api/app/clients/agents/CustomAgent/initializeCustomAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,18 @@ const initializeCustomAgent = async ({
tools,
model,
pastMessages,
customName,
customInstructions,
currentDateString,
...rest
}) => {
let prompt = CustomAgent.createPrompt(tools, { currentDateString, model: model.modelName });
if (customName) {
prompt = `You are "${customName}".\n${prompt}`;
}
if (customInstructions) {
prompt = `${prompt}\n${customInstructions}`;
}

const chatPrompt = ChatPromptTemplate.fromMessages([
new SystemMessagePromptTemplate(prompt),
Expand Down
10 changes: 9 additions & 1 deletion api/app/clients/agents/Functions/initializeFunctionsAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const initializeFunctionsAgent = async ({
tools,
model,
pastMessages,
customName,
customInstructions,
currentDateString,
...rest
}) => {
Expand All @@ -24,7 +26,13 @@ const initializeFunctionsAgent = async ({
returnMessages: true,
});

const prefix = addToolDescriptions(`Current Date: ${currentDateString}\n${PREFIX}`, tools);
let prefix = addToolDescriptions(`Current Date: ${currentDateString}\n${PREFIX}`, tools);
if (customName) {
prefix = `You are "${customName}".\n${prefix}`;
}
if (customInstructions) {
prefix = `${prefix}\n${customInstructions}`;
}

return await initializeAgentExecutorWithOptions(tools, model, {
agentType: 'openai-functions',
Expand Down
2 changes: 2 additions & 0 deletions api/models/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
async saveMessage({
user,
endpoint,
iconURL,
messageId,
newMessageId,
conversationId,
Expand All @@ -35,6 +36,7 @@ module.exports = {

const update = {
user,
iconURL,
endpoint,
messageId: newMessageId || messageId,
conversationId,
Expand Down
6 changes: 6 additions & 0 deletions api/models/Preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ module.exports = {
try {
const setter = { $set: {} };
const update = { presetId, ...preset };
if (preset.tools && Array.isArray(preset.tools)) {
update.tools =
preset.tools
.map((tool) => tool?.pluginKey ?? tool)
.filter((toolName) => typeof toolName === 'string') ?? [];
}
if (newPresetId) {
update.presetId = newPresetId;
}
Expand Down
11 changes: 11 additions & 0 deletions api/models/schema/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ const conversationPreset = {
type: String,
},
stop: { type: [{ type: String }], default: undefined },
/* UI Components */
iconURL: {
type: String,
},
greeting: {
type: String,
},
spec: {
type: String,
},
tools: { type: [{ type: String }], default: undefined },
};

const agentOptions = {
Expand Down
4 changes: 4 additions & 0 deletions api/models/schema/messageSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ const messageSchema = mongoose.Schema(
thread_id: {
type: String,
},
/* frontend components */
iconURL: {
type: String,
},
},
{ timestamps: true },
);
Expand Down
8 changes: 7 additions & 1 deletion api/server/controllers/PluginController.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const getAvailablePluginsController = async (req, res) => {
return;
}

/** @type {{ filteredTools: string[] }} */
const { filteredTools = [] } = req.app.locals;

const pluginManifest = await fs.readFile(req.app.locals.paths.pluginManifest, 'utf8');

const jsonData = JSON.parse(pluginManifest);
Expand All @@ -67,7 +70,10 @@ const getAvailablePluginsController = async (req, res) => {
return plugin;
}
});
const plugins = await addOpenAPISpecs(authenticatedPlugins);

let plugins = await addOpenAPISpecs(authenticatedPlugins);
plugins = plugins.filter((plugin) => !filteredTools.includes(plugin.pluginKey));

await cache.set(CacheKeys.PLUGINS, plugins);
res.status(200).json(plugins);
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions api/server/middleware/abortMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const createAbortController = (req, res, getAbortData) => {
...responseData,
conversationId,
finish_reason: 'incomplete',
endpoint: endpointOption.endpoint,
iconURL: endpointOption.iconURL,
model: endpointOption.modelOptions.model,
unfinished: false,
error: false,
Expand Down
27 changes: 27 additions & 0 deletions api/server/middleware/buildEndpointOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const anthropic = require('~/server/services/Endpoints/anthropic');
const openAI = require('~/server/services/Endpoints/openAI');
const custom = require('~/server/services/Endpoints/custom');
const google = require('~/server/services/Endpoints/google');
const enforceModelSpec = require('./enforceModelSpec');
const { handleError } = require('~/server/utils');

const buildFunction = {
[EModelEndpoint.openAI]: openAI.buildOptions,
Expand All @@ -21,6 +23,31 @@ const buildFunction = {
async function buildEndpointOption(req, res, next) {
const { endpoint, endpointType } = req.body;
const parsedBody = parseConvo({ endpoint, endpointType, conversation: req.body });

if (req.app.locals.modelSpecs?.list && req.app.locals.modelSpecs?.enforce) {
/** @type {{ list: TModelSpec[] }}*/
const { list } = req.app.locals.modelSpecs;
const { spec } = parsedBody;

if (!spec) {
return handleError(res, { text: 'No model spec selected' });
}

const currentModelSpec = list.find((s) => s.name === spec);
if (!currentModelSpec) {
return handleError(res, { text: 'Invalid model spec' });
}

if (endpoint !== currentModelSpec.preset.endpoint) {
return handleError(res, { text: 'Model spec mismatch' });
}

const isValidModelSpec = enforceModelSpec(currentModelSpec, parsedBody);
if (!isValidModelSpec) {
return handleError(res, { text: 'Model spec mismatch' });
}
}

req.body.endpointOption = buildFunction[endpointType ?? endpoint](
endpoint,
parsedBody,
Expand Down
47 changes: 47 additions & 0 deletions api/server/middleware/enforceModelSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const interchangeableKeys = new Map([
['chatGptLabel', ['modelLabel']],
['modelLabel', ['chatGptLabel']],
]);

/**
* Middleware to enforce the model spec for a conversation
* @param {TModelSpec} modelSpec - The model spec to enforce
* @param {TConversation} parsedBody - The parsed body of the conversation
* @returns {boolean} - Whether the model spec is enforced
*/
const enforceModelSpec = (modelSpec, parsedBody) => {
for (const [key, value] of Object.entries(modelSpec.preset)) {
if (key === 'endpoint') {
continue;
}

if (!checkMatch(key, value, parsedBody)) {
return false;
}
}
return true;
};

/**
* Checks if there is a match for the given key and value in the parsed body
* or any of its interchangeable keys.
* @param {string} key
* @param {any} value
* @param {TConversation} parsedBody
* @returns {boolean}
*/
const checkMatch = (key, value, parsedBody) => {
if (parsedBody[key] === value) {
return true;
}

if (interchangeableKeys.has(key)) {
return interchangeableKeys
.get(key)
.some((interchangeableKey) => parsedBody[interchangeableKey] === value);
}

return false;
};

module.exports = enforceModelSpec;
4 changes: 3 additions & 1 deletion api/server/routes/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ router.get('/', async function (req, res) {
};

try {
/** @type {TStartupConfig} */
const payload = {
appTitle: process.env.APP_TITLE || 'LibreChat',
socialLogins: req.app.locals.socialLogins ?? defaultSocialLogins,
Expand Down Expand Up @@ -44,7 +45,8 @@ router.get('/', async function (req, res) {
isEnabled(process.env.SHOW_BIRTHDAY_ICON) ||
process.env.SHOW_BIRTHDAY_ICON === '',
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
interface: req.app.locals.interface,
interface: req.app.locals.interfaceConfig,
modelSpecs: req.app.locals.modelSpecs,
};

if (typeof process.env.CUSTOM_FOOTER === 'string') {
Expand Down
Loading
Loading