mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 19:30:15 +01:00
refactor: consolidate tool loading logic into a new tools module for startup logic
This commit is contained in:
parent
1bdfb143eb
commit
84c78f5812
2 changed files with 134 additions and 147 deletions
|
|
@ -1,11 +1,7 @@
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const { sleep } = require('@librechat/agents');
|
const { sleep } = require('@librechat/agents');
|
||||||
const { getToolkitKey } = require('@librechat/api');
|
const { getToolkitKey } = require('@librechat/api');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { zodToJsonSchema } = require('zod-to-json-schema');
|
const { tool: toolFn, DynamicStructuredTool } = require('@langchain/core/tools');
|
||||||
const { Calculator } = require('@langchain/community/tools/calculator');
|
|
||||||
const { tool: toolFn, Tool, DynamicStructuredTool } = require('@langchain/core/tools');
|
|
||||||
const {
|
const {
|
||||||
Tools,
|
Tools,
|
||||||
Constants,
|
Constants,
|
||||||
|
|
@ -26,153 +22,14 @@ const {
|
||||||
loadActionSets,
|
loadActionSets,
|
||||||
domainParser,
|
domainParser,
|
||||||
} = require('./ActionService');
|
} = require('./ActionService');
|
||||||
const {
|
|
||||||
createOpenAIImageTools,
|
|
||||||
createYouTubeTools,
|
|
||||||
manifestToolMap,
|
|
||||||
toolkits,
|
|
||||||
} = require('~/app/clients/tools');
|
|
||||||
const { getEndpointsConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
|
const { getEndpointsConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||||
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
|
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
|
||||||
|
const { manifestToolMap, toolkits } = require('~/app/clients/tools/manifest');
|
||||||
const { createOnSearchResults } = require('~/server/services/Tools/search');
|
const { createOnSearchResults } = require('~/server/services/Tools/search');
|
||||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||||
const { recordUsage } = require('~/server/services/Threads');
|
const { recordUsage } = require('~/server/services/Threads');
|
||||||
const { loadTools } = require('~/app/clients/tools/util');
|
const { loadTools } = require('~/app/clients/tools/util');
|
||||||
const { redactMessage } = require('~/config/parsers');
|
const { redactMessage } = require('~/config/parsers');
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads and formats tools from the specified tool directory.
|
|
||||||
*
|
|
||||||
* The directory is scanned for JavaScript files, excluding any files in the filter set.
|
|
||||||
* For each file, it attempts to load the file as a module and instantiate a class, if it's a subclass of `StructuredTool`.
|
|
||||||
* Each tool instance is then formatted to be compatible with the OpenAI Assistant.
|
|
||||||
* Additionally, instances of LangChain Tools are included in the result.
|
|
||||||
*
|
|
||||||
* @param {object} params - The parameters for the function.
|
|
||||||
* @param {string} params.directory - The directory path where the tools are located.
|
|
||||||
* @param {string} [params.fileStrategy] - The file storage strategy.
|
|
||||||
* @param {string} [params.imageOutputType] - The image output type configuration.
|
|
||||||
* @param {Array<string>} [params.adminFilter=[]] - Array of admin-defined tool keys to exclude from loading.
|
|
||||||
* @param {Array<string>} [params.adminIncluded=[]] - Array of admin-defined tool keys to include from loading.
|
|
||||||
* @returns {Record<string, FunctionTool>} An object mapping each tool's plugin key to its instance.
|
|
||||||
*/
|
|
||||||
function loadAndFormatTools({
|
|
||||||
directory,
|
|
||||||
fileStrategy,
|
|
||||||
imageOutputType,
|
|
||||||
adminFilter = [],
|
|
||||||
adminIncluded = [],
|
|
||||||
}) {
|
|
||||||
const filter = new Set([...adminFilter]);
|
|
||||||
const included = new Set(adminIncluded);
|
|
||||||
const tools = [];
|
|
||||||
/* Structured Tools Directory */
|
|
||||||
const files = fs.readdirSync(directory);
|
|
||||||
|
|
||||||
if (included.size > 0 && adminFilter.length > 0) {
|
|
||||||
logger.warn(
|
|
||||||
'Both `includedTools` and `filteredTools` are defined; `filteredTools` will be ignored.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = path.join(directory, file);
|
|
||||||
if (!file.endsWith('.js') || (filter.has(file) && included.size === 0)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ToolClass = null;
|
|
||||||
try {
|
|
||||||
ToolClass = require(filePath);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[loadAndFormatTools] Error loading tool from ${filePath}:`, error);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ToolClass || !(ToolClass.prototype instanceof Tool)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let toolInstance = null;
|
|
||||||
try {
|
|
||||||
toolInstance = new ToolClass({ override: true });
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(
|
|
||||||
`[loadAndFormatTools] Error initializing \`${file}\` tool; if it requires authentication, is the \`override\` field configured?`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!toolInstance) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.has(toolInstance.name) && included.size === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (included.size > 0 && !included.has(file) && !included.has(toolInstance.name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedTool = formatToOpenAIAssistantTool(toolInstance);
|
|
||||||
tools.push(formattedTool);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Basic Tools & Toolkits; schema: { input: string } */
|
|
||||||
const openAIImageTools = createOpenAIImageTools({
|
|
||||||
override: true,
|
|
||||||
imageOutputType,
|
|
||||||
fileStrategy,
|
|
||||||
});
|
|
||||||
const basicToolInstances = [
|
|
||||||
new Calculator(),
|
|
||||||
...openAIImageTools,
|
|
||||||
...createYouTubeTools({ override: true }),
|
|
||||||
];
|
|
||||||
for (const toolInstance of basicToolInstances) {
|
|
||||||
const formattedTool = formatToOpenAIAssistantTool(toolInstance);
|
|
||||||
let toolName = formattedTool[Tools.function].name;
|
|
||||||
toolName = getToolkitKey({ toolkits, toolName }) ?? toolName;
|
|
||||||
if (filter.has(toolName) && included.size === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (included.size > 0 && !included.has(toolName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tools.push(formattedTool);
|
|
||||||
}
|
|
||||||
|
|
||||||
tools.push(ImageVisionTool);
|
|
||||||
|
|
||||||
return tools.reduce((map, tool) => {
|
|
||||||
map[tool.function.name] = tool;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a `StructuredTool` instance into a format that is compatible
|
|
||||||
* with OpenAI's ChatCompletionFunctions. It uses the `zodToJsonSchema`
|
|
||||||
* function to convert the schema of the `StructuredTool` into a JSON
|
|
||||||
* schema, which is then used as the parameters for the OpenAI function.
|
|
||||||
*
|
|
||||||
* @param {StructuredTool} tool - The StructuredTool to format.
|
|
||||||
* @returns {FunctionTool} The OpenAI Assistant Tool.
|
|
||||||
*/
|
|
||||||
function formatToOpenAIAssistantTool(tool) {
|
|
||||||
return {
|
|
||||||
type: Tools.function,
|
|
||||||
[Tools.function]: {
|
|
||||||
name: tool.name,
|
|
||||||
description: tool.description,
|
|
||||||
parameters: zodToJsonSchema(tool.schema),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the required actions by calling the appropriate tools and returning the outputs.
|
* Processes the required actions by calling the appropriate tools and returning the outputs.
|
||||||
* @param {OpenAIClient} client - OpenAI or StreamRunManager Client.
|
* @param {OpenAIClient} client - OpenAI or StreamRunManager Client.
|
||||||
|
|
@ -737,7 +594,5 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getToolkitKey,
|
getToolkitKey,
|
||||||
loadAgentTools,
|
loadAgentTools,
|
||||||
loadAndFormatTools,
|
|
||||||
processRequiredActions,
|
processRequiredActions,
|
||||||
formatToOpenAIAssistantTool,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
132
api/server/services/start/tools.js
Normal file
132
api/server/services/start/tools.js
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { Tool } = require('@langchain/core/tools');
|
||||||
|
const { logger } = require('@librechat/data-schemas');
|
||||||
|
const { zodToJsonSchema } = require('zod-to-json-schema');
|
||||||
|
const { Tools, ImageVisionTool } = require('librechat-data-provider');
|
||||||
|
const { Calculator } = require('@langchain/community/tools/calculator');
|
||||||
|
const { getToolkitKey, oaiToolkit, ytToolkit } = require('@librechat/api');
|
||||||
|
const { toolkits } = require('~/app/clients/tools/manifest');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and formats tools from the specified tool directory.
|
||||||
|
*
|
||||||
|
* The directory is scanned for JavaScript files, excluding any files in the filter set.
|
||||||
|
* For each file, it attempts to load the file as a module and instantiate a class, if it's a subclass of `StructuredTool`.
|
||||||
|
* Each tool instance is then formatted to be compatible with the OpenAI Assistant.
|
||||||
|
* Additionally, instances of LangChain Tools are included in the result.
|
||||||
|
*
|
||||||
|
* @param {object} params - The parameters for the function.
|
||||||
|
* @param {string} params.directory - The directory path where the tools are located.
|
||||||
|
* @param {Array<string>} [params.adminFilter=[]] - Array of admin-defined tool keys to exclude from loading.
|
||||||
|
* @param {Array<string>} [params.adminIncluded=[]] - Array of admin-defined tool keys to include from loading.
|
||||||
|
* @returns {Record<string, FunctionTool>} An object mapping each tool's plugin key to its instance.
|
||||||
|
*/
|
||||||
|
function loadAndFormatTools({ directory, adminFilter = [], adminIncluded = [] }) {
|
||||||
|
const filter = new Set([...adminFilter]);
|
||||||
|
const included = new Set(adminIncluded);
|
||||||
|
const tools = [];
|
||||||
|
/* Structured Tools Directory */
|
||||||
|
const files = fs.readdirSync(directory);
|
||||||
|
|
||||||
|
if (included.size > 0 && adminFilter.length > 0) {
|
||||||
|
logger.warn(
|
||||||
|
'Both `includedTools` and `filteredTools` are defined; `filteredTools` will be ignored.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(directory, file);
|
||||||
|
if (!file.endsWith('.js') || (filter.has(file) && included.size === 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ToolClass = null;
|
||||||
|
try {
|
||||||
|
ToolClass = require(filePath);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[loadAndFormatTools] Error loading tool from ${filePath}:`, error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ToolClass || !(ToolClass.prototype instanceof Tool)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let toolInstance = null;
|
||||||
|
try {
|
||||||
|
toolInstance = new ToolClass({ override: true });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`[loadAndFormatTools] Error initializing \`${file}\` tool; if it requires authentication, is the \`override\` field configured?`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toolInstance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.has(toolInstance.name) && included.size === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (included.size > 0 && !included.has(file) && !included.has(toolInstance.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedTool = formatToOpenAIAssistantTool(toolInstance);
|
||||||
|
tools.push(formattedTool);
|
||||||
|
}
|
||||||
|
|
||||||
|
const basicToolInstances = [
|
||||||
|
new Calculator(),
|
||||||
|
...Object.values(oaiToolkit),
|
||||||
|
...Object.values(ytToolkit),
|
||||||
|
];
|
||||||
|
for (const toolInstance of basicToolInstances) {
|
||||||
|
const formattedTool = formatToOpenAIAssistantTool(toolInstance);
|
||||||
|
let toolName = formattedTool[Tools.function].name;
|
||||||
|
toolName = getToolkitKey({ toolkits, toolName }) ?? toolName;
|
||||||
|
if (filter.has(toolName) && included.size === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (included.size > 0 && !included.has(toolName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tools.push(formattedTool);
|
||||||
|
}
|
||||||
|
|
||||||
|
tools.push(ImageVisionTool);
|
||||||
|
|
||||||
|
return tools.reduce((map, tool) => {
|
||||||
|
map[tool.function.name] = tool;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a `StructuredTool` instance into a format that is compatible
|
||||||
|
* with OpenAI's ChatCompletionFunctions. It uses the `zodToJsonSchema`
|
||||||
|
* function to convert the schema of the `StructuredTool` into a JSON
|
||||||
|
* schema, which is then used as the parameters for the OpenAI function.
|
||||||
|
*
|
||||||
|
* @param {StructuredTool} tool - The StructuredTool to format.
|
||||||
|
* @returns {FunctionTool} The OpenAI Assistant Tool.
|
||||||
|
*/
|
||||||
|
function formatToOpenAIAssistantTool(tool) {
|
||||||
|
return {
|
||||||
|
type: Tools.function,
|
||||||
|
[Tools.function]: {
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
parameters: zodToJsonSchema(tool.schema),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
loadAndFormatTools,
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue