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

Herramientas y Plugins

Este documento le muestra cómo crear plugins personalizados para LibreChat extendiendo la clase `Tool` de LangChain. Aprenderá a utilizar diferentes APIs y funciones con sus plugins, y cómo integrarlos con el framework de LangChain.

Esta página está obsoleta. Por favor, consulte la Guía de Agentes para obtener la información más actualizada sobre el uso de herramientas.

Se recomienda encarecidamente utilizar el Model Context Protocol o OpenAPI Actions para integrar herramientas personalizadas

Creando tus propias Herramientas/Plugins

Advertencia

Consulte las herramientas más recientes utilizadas con asistentes en api/app/clients/tools/structured/ ya que los plugins quedarán obsoletos en favor de las herramientas en un futuro próximo.

Crear plugins personalizados para este proyecto implica extender la clase Tool del módulo langchain/tools.

Nota: Usaré la palabra plugin indistintamente con tool, ya que esta última es específica de LangChain y principalmente nos estamos ajustando a la biblioteca.

Básicamente estás creando DynamicTools en el lenguaje de LangChain. Consulta la documentación de LangChainJS para obtener más información.

Esta guía le llevará a través del proceso de creación de sus propios plugins personalizados, utilizando las herramientas StableDiffusionAPI y WolframAlphaAPI como ejemplos.

Al utilizar el Functions Agent (el modo predeterminado para plugins), las herramientas se convierten en OpenAI functions; en cualquier caso, los plugins/herramientas se invocan de forma condicional basándose en que el LLM genere un formato específico que nosotros analizamos.

La implementación más común de un plugin es realizar una llamada a la API basada en la entrada de lenguaje natural de la IA, pero prácticamente no hay límite en los casos de uso programáticos.


Puntos clave

Aquí tienes los puntos clave para crear tu propio plugin:

1. Importar los módulos necesarios: Importe los módulos necesarios para su plugin, incluyendo la clase Tool de langchain/tools y cualquier otro módulo que su plugin pueda necesitar.

2. Define su clase de plugin: Defina una clase para su plugin que extienda la clase Tool. Establezca las propiedades name y description en el constructor. Si su plugin requiere credenciales u otras variables, establézcalas desde el parámetro fields o desde un método que las recupere de su entorno de proceso. Tenga en cuenta que, si su plugin requiere instrucciones largas y detalladas, puede añadir una propiedad description_for_model y hacer que description sea más general.

3. Definir métodos auxiliares: Defina métodos auxiliares dentro de su clase para manejar tareas específicas si es necesario.

4. Implementar el método _call: Implemente el método _call donde se define la funcionalidad principal de su plugin. Este método se invoca cuando el modelo de lenguaje decide utilizar su plugin. Debe aceptar un parámetro input y devolver un resultado. Si ocurre un error, la función debe devolver una cadena que represente el error, en lugar de lanzar una excepción. Si su plugin requiere múltiples entradas del LLM, lea la sección StructuredTools.

5. Exporte su plugin e impórtelo en handleTools.js: Exporte su plugin e impórtelo en handleTools.js. Añada su plugin al objeto toolConstructors en la función loadTools. Si su plugin requiere una inicialización más avanzada, añádalo al objeto customConstructors.

6. Exporte su plugin en index.js: Exporte su plugin en index.js dentro de tools. Añada su plugin al module.exports del archivo index.js, por lo que también deberá declararlo como const en este archivo.

7. Añada su plugin a manifest.json: Añada su plugin a manifest.json. Siga el formato estricto para cada uno de los campos del objeto "plugin". Si su plugin requiere autenticación, añada esos detalles bajo authConfig como una matriz. El pluginKey debe coincidir con el name de la clase Tool que creó, y la propiedad authField debe coincidir con el nombre de la variable process.env.

Recuerda, la clave para crear un plugin personalizado es extender la clase Tool e implementar el método _call. El método _call es donde defines lo que hace tu plugin. También puedes definir métodos auxiliares y propiedades en tu clase para respaldar la funcionalidad de tu plugin.

Nota: Puedes encontrar todos los archivos mencionados en esta guía en la carpeta .\api\app\langchain\tools.


StructuredTools

Plugins de entrada múltiple

Si desea crear un plugin que se beneficie de múltiples entradas del LLM, en lugar de una cadena de entrada única como revisaremos, necesita crear un StructuredTool de LangChain en su lugar. Una guía detallada para esto está en progreso, pero por ahora, puede observar cómo he creado StructuredTools en este directorio: api\app\clients\tools\structured\. Esta guía es fundamental para comprender las StructuredTools, y se recomienda que continúe leyendo para entender mejor las herramientas de LangChain primero. El blog enlazado arriba también es útil una vez que haya leído esta guía.


Paso 1: Importar los módulos requeridos

Comience importando los módulos necesarios. Esto incluirá la clase Tool de langchain/tools y cualquier otro módulo que su herramienta pueda necesitar. Por ejemplo:

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

Paso 2: Defina su clase de herramienta

A continuación, defina una clase para su plugin que extienda la clase Tool. La clase debe tener un constructor que llame al método super() y establezca las propiedades name y description. Estas propiedades serán utilizadas por el modelo de lenguaje para determinar cuándo llamar a su herramienta y con qué parámetros.

Importante: debes configurar las credenciales/variables necesarias desde el parámetro fields, o alternativamente desde un método que las obtenga de tu 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...`;
  }
  ...
}

Opcional: A partir de la v0.5.8, al usar Functions, puede añadir instrucciones más largas y detalladas con la propiedad description_for_model. Al hacerlo, se recomienda que haga la propiedad description más generalizada para optimizar los tokens. Cada línea en esta propiedad tiene el prefijo // para reflejar cómo se genera el prompt para ChatGPT (chat.openai.com). Este formato se alinea más estrechamente con la ingeniería de prompts de los plugins oficiales 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."
// ...

Dentro del constructor, tenga en cuenta que estamos obteniendo una variable sensible ya sea del objeto fields o del método getServerURL que definimos para acceder a una variable de entorno.

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

Cualquier credencial necesaria se pasa a través de fields cuando el usuario la proporciona desde el frontend; de lo contrario, el administrador puede "autorizar" el plugin para todos los usuarios a través de variables de entorno. Todas las credenciales enviadas desde el frontend están cifradas.

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

Paso 3: Definir métodos auxiliares

Puede definir métodos auxiliares dentro de su clase para manejar tareas específicas si es necesario. Por ejemplo, la clase StableDiffusionAPI incluye métodos como replaceNewLinesWithSpaces, getMarkdownImageUrl y getServerURL para manejar diversas tareas.

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

Paso 4: Implementar el método _call

El método _call es donde se implementa la funcionalidad principal de su plugin. Este método se invoca cuando el modelo de lenguaje decide utilizar su plugin. Debe recibir un parámetro input y devolver un resultado.

En una Tool básica, el LLM generará un valor de cadena como entrada. Si tu plugin requiere múltiples entradas del LLM, lee la sección StructuredTools.

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

Importante: La función _call es la que el agente realmente llamará. Cuando ocurra un error, la función debería, siempre que sea posible, devolver una cadena que represente un error en lugar de lanzar un error. Esto permite que el error se pase al LLM y que el LLM decida cómo manejarlo. Si se lanza un error, la ejecución del agente se detendrá.

Paso 5: Exporte su Plugin e impórtelo en handleTools.js

Este proceso estará algo automatizado en el futuro, siempre y cuando tengas tu plugin/herramienta en api\app\langchain\tools

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

En handleTools.js, busca el inicio de la función loadTools y añade tu plugin/herramienta al objeto 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 tu clase Tool requiere una inicialización más avanzada, deberás añadirla al objeto customConstructors.

La inicialización predeterminada se puede ver en la función loadToolWithAuth, y la mayoría de los plugins personalizados deberían inicializarse de esta manera.

Aquí hay algunos customConstructors, que tienen diferentes inicializaciones

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

Paso 6: Exporta tu Plugin en index.js

Busque el archivo index.js en api/app/clients/tools. Debe incluir su plugin en module.exports para que se compile; también deberá declarar su plugin como consts:

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

Paso 7: Agregue su Plugin a manifest.json

Este proceso estará algo automatizado en el futuro junto con el paso 5, siempre y cuando tengas tu plugin/herramienta en api\app\langchain\tools y tu plugin pueda inicializarse con el método predeterminado.

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

Cada uno de los campos del objeto "plugin" es importante. Siga este formato estrictamente. Si su plugin requiere autenticación, deberá añadir esos detalles bajo authConfig como un array, ya que podría haber múltiples variables de autenticación. Consulte el plugin Calculator para ver un ejemplo de uno que no requiere autenticación, donde el authConfig es un array vacío (siempre se requiere un array).

Nota: como se mencionó anteriormente, pluginKey coincide con el name de la clase de la herramienta (Tool class) que creó. Nota: la propiedad authField debe coincidir con el nombre de la variable en process.env. Nota: las entradas de authConfig pueden incluir sensitive. Omítalo o establézcalo en true para claves de API y secretos. Establezca sensitive: false para valores de configuración que no sean secretos, como URLs, nombres de usuario, nombres de despliegue o IDs de proyecto, para que la interfaz de usuario renderice un campo de texto plano en lugar de una entrada de secreto.

Aquí hay un ejemplo de un plugin con más de una variable de credencial

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

Ejemplo: Herramienta WolframAlphaAPI

Aquí hay otro ejemplo de una herramienta personalizada, la herramienta WolframAlphaAPI. Esta herramienta utiliza el módulo axios para realizar solicitudes HTTP a la API de 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

En este ejemplo, la clase WolframAlphaAPI tiene métodos auxiliares como fetchRawText, getAppId y createWolframAlphaURL para manejar tareas específicas. El método _call realiza una solicitud HTTP a la API de Wolfram Alpha y devuelve la respuesta.

¿Qué te parece esta guía?