Outils et plugins
Ce document vous montre comment créer des plugins personnalisés pour LibreChat en étendant la classe `Tool` de LangChain. Vous apprendrez à utiliser différentes API et fonctions avec vos plugins, ainsi qu'à les intégrer au framework LangChain.
Cette page est obsolète. Veuillez vous référer au Guide des Agents pour obtenir les informations les plus récentes sur l'utilisation des outils.
Il est fortement recommandé d'utiliser le Model Context Protocol ou les OpenAPI Actions pour intégrer des outils personnalisés.
Créer vos propres Outils/Plugins
Avertissement
Veuillez vous référer aux outils les plus récents utilisés avec les assistants dans api/app/clients/tools/structured/ car les plugins seront prochainement obsolètes au profit des outils.
La création de plugins personnalisés pour ce projet implique l'extension de la classe Tool du module langchain/tools.
Note : J'utiliserai le mot plugin de manière interchangeable avec tool, car ce dernier est spécifique à LangChain, et nous nous conformons principalement à la bibliothèque.
Vous créez essentiellement des DynamicTools dans le langage de LangChain. Consultez la documentation de LangChainJS pour plus d'informations.
Ce guide vous accompagnera tout au long du processus de création de vos propres plugins personnalisés, en utilisant les outils StableDiffusionAPI et WolframAlphaAPI comme exemples.
Lors de l'utilisation de l'agent Functions (le mode par défaut pour les plugins), les outils sont convertis en OpenAI functions ; dans tous les cas, les plugins/outils sont invoqués de manière conditionnelle en fonction du format spécifique généré par le LLM que nous analysons.
L'implémentation la plus courante d'un plugin consiste à effectuer un appel API basé sur l'entrée en langage naturel de l'IA, mais il n'y a pratiquement aucune limite aux cas d'utilisation programmatiques.
Points clés
Voici les points clés pour créer votre propre plugin :
1. Importer les modules requis : Importez les modules nécessaires pour votre plugin, y compris la classe Tool depuis langchain/tools et tout autre module dont votre plugin pourrait avoir besoin.
2. Définissez votre classe de plugin : Définissez une classe pour votre plugin qui étend la classe Tool. Définissez les propriétés name et description dans le constructeur. Si votre plugin nécessite des identifiants ou d'autres variables, définissez-les à partir du paramètre fields ou d'une méthode qui les récupère depuis votre environnement de processus. Notez que si votre plugin nécessite des instructions longues et détaillées, vous pouvez ajouter une propriété description_for_model et rendre la description plus générale.
3. Définir des méthodes d'assistance : Définissez des méthodes d'assistance au sein de votre classe pour gérer des tâches spécifiques si nécessaire.
4. Implémenter la méthode _call : Implémentez la méthode _call où la fonctionnalité principale de votre plugin est définie. Cette méthode est appelée lorsque le modèle de langage décide d'utiliser votre plugin. Elle doit prendre un paramètre input et retourner un résultat. Si une erreur survient, la fonction doit retourner une chaîne de caractères représentant une erreur, plutôt que de lever une erreur. Si votre plugin nécessite plusieurs entrées de la part du LLM, lisez la section StructuredTools.
5. Exportez votre plugin et importez-le dans handleTools.js : Exportez votre plugin et importez-le dans handleTools.js. Ajoutez votre plugin à l'objet toolConstructors dans la fonction loadTools. Si votre plugin nécessite une initialisation plus avancée, ajoutez-le à l'objet customConstructors.
6. Exportez votre plugin dans index.js : Exportez votre plugin dans index.js sous tools. Ajoutez votre plugin au module.exports du fichier index.js, vous devez donc également le déclarer en tant que const dans ce fichier.
7. Ajoutez votre plugin au fichier manifest.json : Ajoutez votre plugin au fichier manifest.json. Respectez le format strict pour chacun des champs de l'objet "plugin". Si votre plugin nécessite une authentification, ajoutez ces détails sous authConfig sous forme de tableau. Le pluginKey doit correspondre au name de la classe Tool que vous avez créée, et la propriété authField doit correspondre au nom de la variable process.env.
N'oubliez pas que la clé pour créer un plugin personnalisé est d'étendre la classe Tool et d'implémenter la méthode _call. La méthode _call est l'endroit où vous définissez ce que fait votre plugin. Vous pouvez également définir des méthodes et des propriétés d'assistance dans votre classe pour prendre en charge les fonctionnalités de votre plugin.
Note : Vous pouvez trouver tous les fichiers mentionnés dans ce guide dans le dossier .\api\app\langchain\tools.
StructuredTools
Plugins à entrées multiples
Si vous souhaitez créer un plugin qui bénéficierait d'entrées multiples provenant du LLM, au lieu d'une chaîne d'entrée unique comme nous allons l'examiner, vous devez créer un StructuredTool LangChain à la place. Un guide détaillé à ce sujet est en cours de rédaction, mais pour le moment, vous pouvez consulter la manière dont j'ai créé des StructuredTools dans ce répertoire : api\app\clients\tools\structured\. Ce guide est fondamental pour comprendre les StructuredTools, et il est recommandé de continuer la lecture pour mieux comprendre les outils LangChain en premier lieu. Le blog lié ci-dessus est également utile une fois que vous aurez lu ce guide.
Étape 1 : Importer les modules requis
Commencez par importer les modules nécessaires. Cela inclura la classe Tool depuis langchain/tools ainsi que tout autre module dont votre outil pourrait avoir besoin. Par exemple :
const { Tool } = require('langchain/tools')
// ... whatever else you needÉtape 2 : Définir votre classe d'outil
Ensuite, définissez une classe pour votre plugin qui étend la classe Tool. La classe doit avoir un constructeur qui appelle la méthode super() et définit les propriétés name et description. Ces propriétés seront utilisées par le modèle de langage pour déterminer quand appeler votre outil et avec quels paramètres.
Important : vous devez définir les identifiants/variables nécessaires à partir du paramètre fields, ou alternativement à partir d'une méthode qui les récupère depuis votre process environment.
class StableDiffusionAPI extends Tool {
constructor(fields) {
super();
this.name = 'stable-diffusion';
this.url = fields.SD_WEBUI_URL || this.getServerURL(); // <--- important!
this.description = `You can generate images with 'stable-diffusion'. This tool is exclusively for visual content...`;
}
...
}Optionnel : À partir de la version v0.5.8, lors de l'utilisation de Functions, vous pouvez ajouter des instructions plus longues et détaillées avec la propriété description_for_model. Dans ce cas, il est recommandé de rendre la propriété description plus généralisée afin d'optimiser les jetons (tokens). Chaque ligne de cette propriété est préfixée par // pour refléter la manière dont le prompt est généré pour ChatGPT (chat.openai.com). Ce format s'aligne plus étroitement sur l'ingénierie de prompt des plugins officiels de ChatGPT.
// ...
this.description_for_model = `// Generate images and visuals using text with 'stable-diffusion'.
// Guidelines:
// - ALWAYS use {{"prompt": "7+ detailed keywords", "negative_prompt": "7+ detailed keywords"}} structure for queries.
// - Visually describe the moods, details, structures, styles, and/or proportions of the image. Remember, the focus is on visual attributes.
// - Craft your input by "showing" and not "telling" the imagery. Think in terms of what you'd want to see in a photograph or a painting.
// - Here's an example for generating a realistic portrait photo of a man:
// "prompt":"photo of a man in black clothes, half body, high detailed skin, coastline, overcast weather, wind, waves, 8k uhd, dslr, soft lighting, high quality, film grain, Fujifilm XT3"
// "negative_prompt":"semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, out of frame, low quality, ugly, mutation, deformed"
// - Generate images only once per human query unless explicitly requested by the user`
this.description =
"You can generate images using text with 'stable-diffusion'. This tool is exclusively for visual content."
// ...Dans le constructeur, notez que nous récupérons une variable sensible soit depuis l'objet fields, soit depuis la méthode getServerURL que nous définissons pour accéder à une variable d'environnement.
this.url = fields.SD_WEBUI_URL || this.getServerURL()Toutes les informations d'identification nécessaires sont transmises via fields lorsque l'utilisateur les fournit depuis l'interface ; sinon, l'administrateur peut « autoriser » le plugin pour tous les utilisateurs via des variables d'environnement. Toutes les informations d'identification transmises depuis l'interface sont chiffrées.
// It's recommended you follow this convention when accessing environment variables.
getServerURL() {
const url = process.env.SD_WEBUI_URL || '';
if (!url) {
throw new Error('Missing SD_WEBUI_URL environment variable.');
}
return url;
}Étape 3 : Définir les méthodes d'assistance
Vous pouvez définir des méthodes d'assistance au sein de votre classe pour gérer des tâches spécifiques si nécessaire. Par exemple, la classe StableDiffusionAPI inclut des méthodes telles que replaceNewLinesWithSpaces, getMarkdownImageUrl et getServerURL pour gérer diverses tâches.
class StableDiffusionAPI extends Tool {
...
replaceNewLinesWithSpaces(inputString) {
return inputString.replace(/\r\n|\r|\n/g, ' ');
}
...
}Étape 4 : Implémenter la méthode _call
La méthode _call est l'endroit où la fonctionnalité principale de votre plugin est implémentée. Cette méthode est appelée lorsque le modèle de langage décide d'utiliser votre plugin. Elle doit prendre un paramètre input et retourner un résultat.
Dans un Tool de base, le LLM générera une valeur de chaîne unique comme entrée. Si votre plugin nécessite plusieurs entrées de la part du LLM, lisez la section StructuredTools.
class StableDiffusionAPI extends Tool {
...
async _call(input) {
// Your tool's functionality goes here
...
return this.result;
}
}Important : La fonction _call est ce que l'agent appellera réellement. Lorsqu'une erreur survient, la fonction doit, dans la mesure du possible, retourner une chaîne de caractères représentant une erreur plutôt que de lever une erreur. Cela permet à l'erreur d'être transmise au LLM, qui peut alors décider de la manière de la gérer. Si une erreur est levée, l'exécution de l'agent s'arrêtera.
Étape 5 : Exporter votre plugin et l'importer dans handleTools.js
Ce processus sera quelque peu automatisé à l'avenir, tant que vous avez votre plugin/outil dans api\app\langchain\tools
// Export
module.exports = StableDiffusionAPI/* api\app\langchain\tools\handleTools.js */
const StableDiffusionAPI = require('./StableDiffusion');
...Dans handleTools.js, trouvez le début de la fonction loadTools et ajoutez votre plugin/outil à l'objet toolConstructors.
const loadTools = async ({ user, model, tools = [], options = {} }) => {
const toolConstructors = {
calculator: Calculator,
google: GoogleSearchAPI,
wolfram: WolframAlphaAPI,
'dall-e': OpenAICreateImage,
'stable-diffusion': StableDiffusionAPI // <----- Newly Added. Note: the key is the 'name' provided in the class.
// We will now refer to this name as the `pluginKey`
};Si votre classe Tool nécessite une initialisation plus avancée, vous devez l'ajouter à l'objet customConstructors.
L'initialisation par défaut peut être observée dans la fonction loadToolWithAuth, et la plupart des plugins personnalisés devraient être initialisés de cette manière.
Voici quelques customConstructors, qui ont des initialisations variées
const customConstructors = {
browser: async () => {
let openAIApiKey = process.env.OPENAI_API_KEY
if (!openAIApiKey) {
openAIApiKey = await getUserPluginAuthValue(user, 'OPENAI_API_KEY')
}
return new WebBrowser({ model, embeddings: new OpenAIEmbeddings({ openAIApiKey }) })
},
// ...
plugins: async () => {
return [
new HttpRequestTool(),
await AIPluginTool.fromPluginUrl(
'https://www.klarna.com/.well-known/ai-plugin.json',
new ChatOpenAI({ openAIApiKey: options.openAIApiKey, temperature: 0 }),
),
]
},
}Étape 6 : Exporter votre plugin dans index.js
Trouvez le fichier index.js sous api/app/clients/tools. Vous devez ajouter votre plugin dans le module.exports, pour qu'il soit compilé, vous devrez également déclarer votre plugin en tant que consts :
const StructuredSD = require('./structured/StableDiffusion');
const StableDiffusionAPI = require('./StableDiffusion');
...
module.exports = {
...
StableDiffusionAPI,
StructuredSD,
...
}Étape 7 : Ajoutez votre plugin au manifest.json
Ce processus sera quelque peu automatisé à l'avenir, tout comme l'étape 5, tant que vous avez votre plugin/outil dans api\app\langchain\tools et que votre plugin peut être initialisé avec la méthode par défaut.
{
"name": "Calculator",
"pluginKey": "calculator",
"description": "Perform simple and complex mathematical calculations.",
"icon": "https://i.imgur.com/RHsSG5h.png",
"isAuthRequired": "false",
"authConfig": []
},
{
"name": "Stable Diffusion",
"pluginKey": "stable-diffusion",
"description": "Generate photo-realistic images given any text input.",
"icon": "https://i.imgur.com/Yr466dp.png",
"authConfig": [
{
"authField": "SD_WEBUI_URL",
"label": "Your Stable Diffusion WebUI API URL",
"description": "You need to provide the URL of your Stable Diffusion WebUI API. For instructions on how to obtain this, see <a href='url'>Our Docs</a>."
}
]
},Chacun des champs de l'objet "plugin" est important. Suivez strictement ce format. Si votre plugin nécessite une authentification, vous ajouterez ces détails sous authConfig en tant que tableau, car il peut y avoir plusieurs variables d'authentification. Consultez le plugin Calculator pour un exemple qui ne nécessite pas d'authentification, où le authConfig est un tableau vide (un tableau est toujours requis).
Note : comme mentionné précédemment, la pluginKey correspond au name de la classe de l'outil (Tool) que vous avez créée.
Note : la propriété authField doit correspondre au nom de la variable process.env.
Note : les entrées authConfig peuvent inclure sensitive. Omettez-le ou réglez-le sur true pour les clés API et les secrets. Réglez sensitive: false pour les valeurs de configuration non secrètes telles que les URLs, les noms d'utilisateur, les noms de déploiement ou les IDs de projet afin que l'interface utilisateur affiche un champ de texte brut au lieu d'une saisie masquée.
Voici un exemple de plugin avec plus d'une variable d'identification
[
{
"name": "Google",
"pluginKey": "google",
"description": "Use Google Search to find information about the weather, news, sports, and more.",
"icon": "https://i.imgur.com/SMmVkNB.png",
"authConfig": [
{
"authField": "GOOGLE_CSE_ID",
"label": "Google CSE ID",
"description": "This is your Google Custom Search Engine ID. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>.",
"sensitive": false
},
{
"authField": "GOOGLE_SEARCH_API_KEY",
"label": "Google API Key",
"description": "This is your Google Custom Search API Key. For instructions on how to obtain this, see <a href='https://github.com/danny-avila/LibreChat/blob/main/docs/features/plugins/google_search.md'>Our Docs</a>.",
"sensitive": true
}
]
},Exemple : Outil WolframAlphaAPI
Voici un autre exemple d'outil personnalisé, l'outil WolframAlphaAPI. Cet outil utilise le module axios pour effectuer des requêtes HTTP vers l'API Wolfram Alpha.
const axios = require('axios')
const { Tool } = require('langchain/tools')
class WolframAlphaAPI extends Tool {
constructor(fields) {
super()
this.name = 'wolfram'
this.apiKey = fields.WOLFRAM_APP_ID || this.getAppId()
this.description = `Access computation, math, curated knowledge & real-time data through wolframAlpha...`
}
async fetchRawText(url) {
try {
const response = await axios.get(url, { responseType: 'text' })
return response.data
} catch (error) {
console.error(`Error fetching raw text: ${error}`)
throw error
}
}
getAppId() {
const appId = process.env.WOLFRAM_APP_ID || ''
if (!appId) {
throw new Error('Missing WOLFRAM_APP_ID environment variable.')
}
return appId
}
createWolframAlphaURL(query) {
const formattedQuery = query.replaceAll(/`/g, '').replaceAll(/\n/g, ' ')
const baseURL = 'https://www.wolframalpha.com/api/v1/llm-api'
const encodedQuery = encodeURIComponent(formattedQuery)
const appId = this.apiKey || this.getAppId()
const url = `${baseURL}?input=${encodedQuery}&appid=${appId}`
return url
}
async _call(input) {
try {
const url = this.createWolframAlphaURL(input)
const response = await this.fetchRawText(url)
return response
} catch (error) {
if (error.response && error.response.data) {
console.log('Error data:', error.response.data)
return error.response.data
} else {
console.log(`Error querying Wolfram Alpha`, error.message)
return 'There was an error querying Wolfram Alpha.'
}
}
}
}
module.exports = WolframAlphaAPIDans cet exemple, la classe WolframAlphaAPI possède des méthodes d'assistance telles que fetchRawText, getAppId et createWolframAlphaURL pour gérer des tâches spécifiques. La méthode _call effectue une requête HTTP vers l'API Wolfram Alpha et renvoie la réponse.
Que pensez-vous de ce guide ?