Skip to main content
LibreChat is joining ClickHouse to power the open-source Agentic Data Stack 🎉 Learn more
LibreChat

Strumenti e Plugin

Questo documento ti mostra come creare plugin personalizzati per LibreChat estendendo la classe `Tool` di LangChain. Imparerai come utilizzare diverse API e funzioni con i tuoi plugin e come integrarli con il framework LangChain.

Questa pagina è obsoleta. Si prega di fare riferimento alla Guida agli Agent per le informazioni più aggiornate sull'utilizzo degli strumenti.

È altamente consigliato utilizzare il Model Context Protocol o le OpenAPI Actions per integrare strumenti personalizzati.

Creare i propri Strumenti/Plugin

Attenzione

Si prega di fare riferimento agli strumenti più recenti utilizzati con gli assistenti in api/app/clients/tools/structured/ poiché i plugin saranno deprecati a favore degli strumenti nel prossimo futuro.

La creazione di plugin personalizzati per questo progetto comporta l'estensione della classe Tool dal modulo langchain/tools.

Nota: Userò il termine plugin in modo intercambiabile con tool, poiché quest'ultimo è specifico per LangChain e ci stiamo conformando principalmente alla libreria.

Stai essenzialmente creando dei DynamicTools nel linguaggio di LangChain. Consulta la documentazione di LangChainJS per maggiori informazioni.

Questa guida ti accompagnerà attraverso il processo di creazione dei tuoi plugin personalizzati, utilizzando come esempi gli strumenti StableDiffusionAPI e WolframAlphaAPI.

Quando si utilizza il Functions Agent (la modalità predefinita per i plugin), gli strumenti vengono convertiti in OpenAI functions; in ogni caso, i plugin/strumenti vengono invocati in modo condizionale in base al fatto che l'LLM generi un formato specifico che noi analizziamo.

L'implementazione più comune di un plugin consiste nell'effettuare una chiamata API basata sull'input in linguaggio naturale dell'AI, ma non c'è praticamente alcun limite ai casi d'uso programmatici.


Punti chiave

Ecco i punti chiave per creare il tuo plugin:

1. Importare i moduli richiesti: Importa i moduli necessari per il tuo plugin, inclusa la classe Tool da langchain/tools e qualsiasi altro modulo di cui il tuo plugin potrebbe aver bisogno.

2. Definisci la tua classe plugin: Definisci una classe per il tuo plugin che estenda la classe Tool. Imposta le proprietà name e description nel costruttore. Se il tuo plugin richiede credenziali o altre variabili, impostale dal parametro fields o da un metodo che le recupera dall'ambiente di processo. Nota: se il tuo plugin richiede istruzioni lunghe e dettagliate, puoi aggiungere una proprietà description_for_model e rendere description più generica.

3. Definire i metodi di supporto: Definire i metodi di supporto all'interno della classe per gestire attività specifiche, se necessario.

4. Implementa il metodo _call: Implementa il metodo _call dove viene definita la funzionalità principale del tuo plugin. Questo metodo viene richiamato quando il modello linguistico decide di utilizzare il tuo plugin. Dovrebbe accettare un parametro input e restituire un risultato. Se si verifica un errore, la funzione dovrebbe restituire una stringa che rappresenta l'errore, invece di generare un'eccezione. Se il tuo plugin richiede input multipli dall'LLM, leggi la sezione StructuredTools.

5. Esporta il tuo plugin e importalo in handleTools.js: Esporta il tuo plugin e importalo in handleTools.js. Aggiungi il tuo plugin all'oggetto toolConstructors nella funzione loadTools. Se il tuo plugin richiede un'inizializzazione più avanzata, aggiungilo all'oggetto customConstructors.

6. Esporta il tuo plugin in index.js: Esporta il tuo plugin in index.js sotto tools. Aggiungi il tuo plugin al module.exports del file index.js, quindi dovrai anche dichiararlo come const in questo file.

7. Aggiungi il tuo plugin a manifest.json: Aggiungi il tuo plugin a manifest.json. Segui il formato rigoroso per ciascuno dei campi dell'oggetto "plugin". Se il tuo plugin richiede l'autenticazione, aggiungi tali dettagli sotto authConfig come array. Il pluginKey deve corrispondere al name della classe Tool che hai creato, e la proprietà authField deve corrispondere al nome della variabile process.env.

Ricorda, la chiave per creare un plugin personalizzato è estendere la classe Tool e implementare il metodo _call. Il metodo _call è dove definisci cosa fa il tuo plugin. Puoi anche definire metodi di supporto e proprietà nella tua classe per supportare le funzionalità del tuo plugin.

Nota: Puoi trovare tutti i file menzionati in questa guida nella cartella .\api\app\langchain\tools.


StructuredTools

Plugin Multi-Input

Se desideri creare un plugin che tragga vantaggio da input multipli da parte dell'LLM, invece di una singola stringa di input come vedremo, devi creare uno StructuredTool di LangChain. Una guida dettagliata a riguardo è in fase di elaborazione, ma per ora puoi osservare come ho creato gli StructuredTool in questa directory: api\app\clients\tools\structured\. Questa guida è fondamentale per comprendere gli StructuredTool, ed è consigliato continuare a leggere per comprendere meglio innanzitutto gli strumenti di LangChain. Anche il blog linkato sopra è utile una volta terminata la lettura di questa guida.


Passaggio 1: Importare i moduli richiesti

Inizia importando i moduli necessari. Questo includerà la classe Tool da langchain/tools e qualsiasi altro modulo di cui il tuo strumento potrebbe aver bisogno. Per esempio:

const { Tool } = require('langchain/tools')
// ... whatever else you need

Passaggio 2: Definisci la tua classe Tool

Successivamente, definisci una classe per il tuo plugin che estenda la classe Tool. La classe dovrebbe avere un costruttore che richiami il metodo super() e imposti le proprietà name e description. Queste proprietà verranno utilizzate dal modello linguistico per determinare quando richiamare il tuo strumento e con quali parametri.

Importante: dovresti impostare le credenziali/variabili necessarie dal parametro fields, o in alternativa da un metodo che le recupera dal tuo 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...`;
  }
  ...
}

Opzionale: A partire dalla v0.5.8, quando si utilizzano le Functions, è possibile aggiungere istruzioni più lunghe e dettagliate con la proprietà description_for_model. In tal caso, si consiglia di rendere la proprietà description più generalizzata per ottimizzare i token. Ogni riga in questa proprietà è preceduta da // per rispecchiare il modo in cui il prompt viene generato per ChatGPT (chat.openai.com). Questo formato si allinea più strettamente al prompt engineering dei plugin ufficiali di 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."
// ...

All'interno del costruttore, nota che stiamo recuperando una variabile sensibile dall'oggetto fields o dal metodo getServerURL che definiamo per accedere a una variabile d'ambiente.

this.url = fields.SD_WEBUI_URL || this.getServerURL()

Tutte le credenziali necessarie vengono passate tramite fields quando l'utente le fornisce dal frontend; in alternativa, l'amministratore può "autorizzare" il plugin per tutti gli utenti tramite variabili d'ambiente. Tutte le credenziali passate dal frontend sono crittografate.

// 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;
  }

Passaggio 3: Definire i metodi di supporto

Puoi definire metodi di supporto all'interno della tua classe per gestire attività specifiche, se necessario. Ad esempio, la classe StableDiffusionAPI include metodi come replaceNewLinesWithSpaces, getMarkdownImageUrl e getServerURL per gestire varie attività.

class StableDiffusionAPI extends Tool {
  ...
  replaceNewLinesWithSpaces(inputString) {
    return inputString.replace(/\r\n|\r|\n/g, ' ');
  }
  ...
}

Passaggio 4: Implementare il metodo _call

Il metodo _call è dove viene implementata la funzionalità principale del tuo plugin. Questo metodo viene chiamato quando il modello linguistico decide di utilizzare il tuo plugin. Dovrebbe accettare un parametro input e restituire un risultato.

In un Tool di base, l'LLM genererà un singolo valore di stringa come input. Se il tuo plugin richiede più input dall'LLM, leggi la sezione StructuredTools.

class StableDiffusionAPI extends Tool {
  ...
  async _call(input) {
    // Your tool's functionality goes here
    ...
    return this.result;
  }
}

Importante: La funzione _call è ciò che l'agente chiamerà effettivamente. Quando si verifica un errore, la funzione dovrebbe, quando possibile, restituire una stringa che rappresenti l'errore, invece di generare un errore (throwing an error). Ciò consente di passare l'errore al LLM, che potrà decidere come gestirlo. Se viene generato un errore, l'esecuzione dell'agente si interromperà.

Passaggio 5: Esporta il tuo plugin e importalo in handleTools.js

Questo processo sarà in qualche modo automatizzato in futuro, a condizione che il tuo plugin/tool si trovi in api\app\langchain\tools

// Export
module.exports = StableDiffusionAPI
/* api\app\langchain\tools\handleTools.js */
const StableDiffusionAPI = require('./StableDiffusion');
...

In handleTools.js, trova l'inizio della funzione loadTools e aggiungi il tuo plugin/tool all'oggetto 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`
  };

Se la tua classe Tool richiede un'inizializzazione più avanzata, dovresti aggiungerla all'oggetto customConstructors.

L'inizializzazione predefinita può essere visualizzata nella funzione loadToolWithAuth e la maggior parte dei plugin personalizzati dovrebbe essere inizializzata in questo modo.

Ecco alcuni customConstructors, che hanno inizializzazioni variabili

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 }),
      ),
    ]
  },
}

Passaggio 6: Esporta il tuo Plugin in index.js

Trova il file index.js sotto api/app/clients/tools. Devi inserire il tuo plugin in module.exports; per farlo compilare, dovrai anche dichiarare il tuo plugin come consts:

const StructuredSD = require('./structured/StableDiffusion');
const StableDiffusionAPI = require('./StableDiffusion');
...
module.exports = {
  ...
  StableDiffusionAPI,
  StructuredSD,
  ...
}

Passaggio 7: Aggiungi il tuo plugin a manifest.json

Questo processo sarà in qualche modo automatizzato in futuro insieme al passaggio 5, a condizione che tu abbia il tuo plugin/tool in api\app\langchain\tools e che il tuo plugin possa essere inizializzato con il metodo predefinito

  {
    "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>."
      }
    ]
  },

Ognuno dei campi dell'oggetto "plugin" è importante. Segui rigorosamente questo formato. Se il tuo plugin richiede l'autenticazione, aggiungerai tali dettagli sotto authConfig come un array, poiché potrebbero esserci più variabili di autenticazione. Vedi il plugin Calculator per un esempio di uno che non richiede autenticazione, dove l'authConfig è un array vuoto (un array è sempre richiesto).

Nota: come menzionato in precedenza, il pluginKey corrisponde al name della classe dello strumento (Tool) che hai creato. Nota: la proprietà authField deve corrispondere al nome della variabile process.env. Nota: le voci di authConfig possono includere sensitive. Omettilo o impostalo su true per chiavi API e segreti. Imposta sensitive: false per valori di configurazione non segreti come URL, nomi utente, nomi di deployment o ID progetto, in modo che l'interfaccia utente visualizzi un campo di testo semplice invece di un input per segreti.

Ecco un esempio di plugin con più di una variabile di credenziali

  [
  {
    "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
      }
    ]
  },

Esempio: WolframAlphaAPI Tool

Ecco un altro esempio di strumento personalizzato, lo strumento WolframAlphaAPI. Questo strumento utilizza il modulo axios per effettuare richieste HTTP all'API di 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 = WolframAlphaAPI

In questo esempio, la classe WolframAlphaAPI dispone di metodi di supporto come fetchRawText, getAppId e createWolframAlphaURL per gestire attività specifiche. Il metodo _call effettua una richiesta HTTP all'API di Wolfram Alpha e restituisce la risposta.

Com’è questa guida?