Tools und Plugins
Dieses Dokument zeigt Ihnen, wie Sie benutzerdefinierte Plugins für LibreChat erstellen, indem Sie die LangChain `Tool`-Klasse erweitern. Sie lernen, wie Sie verschiedene APIs und Funktionen mit Ihren Plugins verwenden und wie Sie diese in das LangChain-Framework integrieren.
Diese Seite ist veraltet. Bitte lesen Sie den Agents Guide für die aktuellsten Informationen zur Verwendung von Tools.
Es wird dringend empfohlen, das Model Context Protocol oder OpenAPI Actions für die Integration benutzerdefinierter Tools zu verwenden.
Eigene Tools/Plugins erstellen
Warnung
Bitte beziehen Sie sich auf die aktuellsten Tools, die mit Assistants in api/app/clients/tools/structured/ verwendet werden, da Plugins in naher Zukunft zugunsten von Tools eingestellt werden.
Das Erstellen benutzerdefinierter Plugins für dieses Projekt beinhaltet das Erweitern der Tool-Klasse aus dem langchain/tools-Modul.
Hinweis: Ich verwende das Wort Plugin synonym zu Tool, da Letzteres spezifisch für LangChain ist und wir uns hauptsächlich an diese Bibliothek anpassen.
Sie erstellen im Grunde genommen DynamicTools im Sinne von LangChain. Weitere Informationen finden Sie in der LangChainJS-Dokumentation.
Dieser Leitfaden führt Sie durch den Prozess der Erstellung Ihrer eigenen benutzerdefinierten Plugins, wobei die Tools StableDiffusionAPI und WolframAlphaAPI als Beispiele dienen.
Bei der Verwendung des Functions Agent (dem Standardmodus für Plugins) werden Tools in OpenAI functions konvertiert; in jedem Fall werden Plugins/Tools bedingt aufgerufen, basierend darauf, dass das LLM ein spezifisches Format generiert, das wir parsen.
Die gebräuchlichste Implementierung eines Plugins besteht darin, basierend auf der natürlichsprachlichen Eingabe der KI einen API-Aufruf zu tätigen, aber es gibt praktisch keine Grenzen für den programmatischen Anwendungsfall.
Wichtige Erkenntnisse
Hier sind die wichtigsten Punkte für die Erstellung Ihres eigenen Plugins:
1. Erforderliche Module importieren: Importieren Sie die notwendigen Module für Ihr Plugin, einschließlich der Tool-Klasse aus langchain/tools sowie aller anderen Module, die Ihr Plugin möglicherweise benötigt.
2. Define Your Plugin Class: Define a class for your plugin that extends the Tool class. Set the name and description properties in the constructor. If your plugin requires credentials or other variables, set them from the fields parameter or from a method that retrieves them from your process environment. Note: if your plugin requires long, detailed instructions, you can add a description_for_model property and make description more general.
3. Hilfsmethoden definieren: Definieren Sie bei Bedarf Hilfsmethoden innerhalb Ihrer Klasse, um spezifische Aufgaben zu erledigen.
4. Implementieren der _call-Methode: Implementieren Sie die _call-Methode, in der die Hauptfunktionalität Ihres Plugins definiert ist. Diese Methode wird aufgerufen, wenn das Sprachmodell entscheidet, Ihr Plugin zu verwenden. Sie sollte einen input-Parameter entgegennehmen und ein Ergebnis zurückgeben. Wenn ein Fehler auftritt, sollte die Funktion einen String zurückgeben, der den Fehler darstellt, anstatt einen Fehler auszulösen. Wenn Ihr Plugin mehrere Eingaben vom LLM erfordert, lesen Sie den Abschnitt StructuredTools.
5. Exportieren Sie Ihr Plugin und importieren Sie es in handleTools.js: Exportieren Sie Ihr Plugin und importieren Sie es in handleTools.js. Fügen Sie Ihr Plugin dem toolConstructors-Objekt in der loadTools-Funktion hinzu. Wenn Ihr Plugin eine fortgeschrittenere Initialisierung erfordert, fügen Sie es dem customConstructors-Objekt hinzu.
6. Exportieren Sie Ihr Plugin in die index.js: Exportieren Sie Ihr Plugin in die index.js unter tools. Fügen Sie Ihr Plugin zu den module.exports der index.js hinzu, sodass Sie es in dieser Datei auch als const deklarieren müssen.
7. Fügen Sie Ihr Plugin zu manifest.json hinzu: Fügen Sie Ihr Plugin zu manifest.json hinzu. Befolgen Sie das strikte Format für jedes der Felder des "plugin"-Objekts. Wenn Ihr Plugin eine Authentifizierung erfordert, fügen Sie diese Details unter authConfig als Array hinzu. Der pluginKey sollte mit dem Klassennamen (name) der von Ihnen erstellten Tool-Klasse übereinstimmen, und die authField-Eigenschaft muss mit dem Namen der process.env-Variable übereinstimmen.
Denken Sie daran: Der Schlüssel zur Erstellung eines benutzerdefinierten Plugins liegt darin, die Tool-Klasse zu erweitern und die _call-Methode zu implementieren. In der _call-Methode definieren Sie, was Ihr Plugin tut. Sie können in Ihrer Klasse auch Hilfsmethoden und Eigenschaften definieren, um die Funktionalität Ihres Plugins zu unterstützen.
Hinweis: Sie finden alle in dieser Anleitung erwähnten Dateien im Ordner .\api\app\langchain\tools.
StructuredTools
Multi-Input Plugins
Wenn Sie ein Plugin erstellen möchten, das von mehreren Eingaben des LLM profitiert, anstatt von einer einzelnen Eingabezeichenfolge, wie wir sie hier besprechen, müssen Sie stattdessen ein LangChain StructuredTool erstellen. Ein detaillierter Leitfaden hierfür ist in Arbeit, aber vorerst können Sie sich ansehen, wie ich StructuredTools in diesem Verzeichnis erstellt habe: api\app\clients\tools\structured\. Dieser Leitfaden ist grundlegend für das Verständnis von StructuredTools, und es wird empfohlen, dass Sie weiterlesen, um zuerst die LangChain-Tools besser zu verstehen. Der oben verlinkte Blog ist ebenfalls hilfreich, sobald Sie diesen Leitfaden durchgelesen haben.
Schritt 1: Erforderliche Module importieren
Beginnen Sie mit dem Importieren der erforderlichen Module. Dies umfasst die Tool-Klasse aus langchain/tools sowie alle anderen Module, die Ihr Tool möglicherweise benötigt. Zum Beispiel:
const { Tool } = require('langchain/tools')
// ... whatever else you needSchritt 2: Definieren Sie Ihre Tool-Klasse
Definieren Sie als Nächstes eine Klasse für Ihr Plugin, die die Tool-Klasse erweitert. Die Klasse sollte einen Konstruktor haben, der die super()-Methode aufruft und die Eigenschaften name und description festlegt. Diese Eigenschaften werden vom Sprachmodell verwendet, um zu bestimmen, wann Ihr Tool aufgerufen werden soll und mit welchen Parametern.
Wichtig: Sie sollten die Anmeldedaten/erforderlichen Variablen über den fields-Parameter oder alternativ über eine Methode festlegen, die diese aus Ihrer Prozessumgebung abruft.
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...`;
}
...
}Optional: Ab v0.5.8 können Sie bei der Verwendung von Functions längere, detailliertere Anweisungen mit der Eigenschaft description_for_model hinzufügen. Dabei wird empfohlen, die Eigenschaft description allgemeiner zu halten, um Token zu optimieren. Jede Zeile in dieser Eigenschaft wird mit // vorangestellt, um widerzuspiegeln, wie der Prompt für ChatGPT (chat.openai.com) generiert wird. Dieses Format entspricht eher dem Prompt-Engineering offizieller ChatGPT-Plugins.
// ...
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."
// ...Beachten Sie innerhalb des Konstruktors, dass wir eine sensible Variable entweder aus dem fields-Objekt oder aus der von uns definierten getServerURL-Methode abrufen, um auf eine Umgebungsvariable zuzugreifen.
this.url = fields.SD_WEBUI_URL || this.getServerURL()Alle erforderlichen Anmeldedaten werden über fields übermittelt, wenn der Benutzer sie vom Frontend aus bereitstellt; andernfalls kann der Administrator das Plugin für alle Benutzer über Umgebungsvariablen "autorisieren". Alle vom Frontend übermittelten Anmeldedaten werden verschlüsselt.
// 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;
}Schritt 3: Hilfsmethoden definieren
Sie können bei Bedarf Hilfsmethoden innerhalb Ihrer Klasse definieren, um spezifische Aufgaben zu erledigen. Zum Beispiel enthält die StableDiffusionAPI-Klasse Methoden wie replaceNewLinesWithSpaces, getMarkdownImageUrl und getServerURL, um verschiedene Aufgaben zu verarbeiten.
class StableDiffusionAPI extends Tool {
...
replaceNewLinesWithSpaces(inputString) {
return inputString.replace(/\r\n|\r|\n/g, ' ');
}
...
}Schritt 4: Implementierung der _call-Methode
Die _call-Methode ist der Ort, an dem die Hauptfunktionalität Ihres Plugins implementiert wird. Diese Methode wird aufgerufen, wenn das Sprachmodell entscheidet, Ihr Plugin zu verwenden. Sie sollte einen input-Parameter entgegennehmen und ein Ergebnis zurückgeben.
Bei einem einfachen Tool generiert das LLM einen einzelnen String-Wert als Eingabe. Wenn Ihr Plugin mehrere Eingaben vom LLM erfordert, lesen Sie den Abschnitt StructuredTools.
class StableDiffusionAPI extends Tool {
...
async _call(input) {
// Your tool's functionality goes here
...
return this.result;
}
}Wichtig: Die _call-Funktion ist das, was der Agent tatsächlich aufruft. Wenn ein Fehler auftritt, sollte die Funktion nach Möglichkeit einen String zurückgeben, der den Fehler darstellt, anstatt einen Fehler auszulösen. Dies ermöglicht es, den Fehler an das LLM weiterzuleiten, damit das LLM entscheiden kann, wie damit umzugehen ist. Wenn ein Fehler ausgelöst wird, wird die Ausführung des Agenten gestoppt.
Schritt 5: Exportieren Sie Ihr Plugin und importieren Sie es in handleTools.js
Dieser Prozess wird in Zukunft teilweise automatisiert sein, solange Sie Ihr Plugin/Tool in api\app\langchain\tools haben.
// Export
module.exports = StableDiffusionAPI/* api\app\langchain\tools\handleTools.js */
const StableDiffusionAPI = require('./StableDiffusion');
...Suchen Sie in handleTools.js den Anfang der Funktion loadTools und fügen Sie Ihr Plugin/Tool dem Objekt toolConstructors hinzu.
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`
};Wenn Ihre Tool-Klasse eine fortgeschrittenere Initialisierung erfordert, fügen Sie diese dem customConstructors-Objekt hinzu.
Die Standardinitialisierung ist in der Funktion loadToolWithAuth zu sehen, und die meisten benutzerdefinierten Plugins sollten auf diese Weise initialisiert werden.
Hier sind ein paar customConstructors, die unterschiedliche Initialisierungen aufweisen
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 }),
),
]
},
}Schritt 6: Exportieren Sie Ihr Plugin in index.js
Suchen Sie die index.js unter api/app/clients/tools. Sie müssen Ihr Plugin in die module.exports einfügen, damit es kompiliert wird; außerdem müssen Sie Ihr Plugin als consts deklarieren:
const StructuredSD = require('./structured/StableDiffusion');
const StableDiffusionAPI = require('./StableDiffusion');
...
module.exports = {
...
StableDiffusionAPI,
StructuredSD,
...
}Schritt 7: Fügen Sie Ihr Plugin zur manifest.json hinzu
Dieser Prozess wird in Zukunft zusammen mit Schritt 5 etwas automatisiert werden, solange Sie Ihr Plugin/Tool in api\app\langchain\tools haben und Ihr Plugin mit der Standardmethode initialisiert werden kann.
{
"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>."
}
]
},Jedes der Felder des "plugin"-Objekts ist wichtig. Befolgen Sie dieses Format strikt. Wenn Ihr Plugin eine Authentifizierung erfordert, fügen Sie diese Details unter authConfig als Array hinzu, da es mehrere Authentifizierungsvariablen geben kann. Siehe das Calculator-Plugin als Beispiel für eines, das keine Authentifizierung erfordert, wobei die authConfig ein leeres Array ist (ein Array ist immer erforderlich).
Hinweis: Wie bereits erwähnt, entspricht der pluginKey dem Klassennamen (name) der von Ihnen erstellten Tool-Klasse.
Hinweis: Die authField-Eigenschaft muss mit dem Namen der process.env-Variablen übereinstimmen.
Hinweis: authConfig-Einträge können sensitive enthalten. Lassen Sie es weg oder setzen Sie es auf true für API-Schlüssel und Geheimnisse. Setzen Sie sensitive: false für nicht geheime Konfigurationswerte wie URLs, Benutzernamen, Bereitstellungsnamen oder Projekt-IDs, damit die Benutzeroberfläche ein Klartextfeld anstelle eines Eingabefelds für Geheimnisse anzeigt.
Hier ist ein Beispiel für ein Plugin mit mehr als einer Anmeldeinformationsvariablen
[
{
"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
}
]
},Beispiel: WolframAlphaAPI Tool
Hier ist ein weiteres Beispiel für ein benutzerdefiniertes Tool, das WolframAlphaAPI-Tool. Dieses Tool verwendet das axios-Modul, um HTTP-Anfragen an die Wolfram Alpha API zu stellen.
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 = WolframAlphaAPIIn diesem Beispiel verfügt die WolframAlphaAPI-Klasse über Hilfsmethoden wie fetchRawText, getAppId und createWolframAlphaURL, um spezifische Aufgaben zu erledigen. Die _call-Methode führt eine HTTP-Anfrage an die Wolfram Alpha API aus und gibt die Antwort zurück.
Wie finden Sie diese Anleitung?