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

Ferramentas e Plugins

Este documento mostra como criar plugins personalizados para o LibreChat estendendo a classe `Tool` do LangChain. Você aprenderá como usar diferentes APIs e funções com seus plugins e como integrá-los ao framework LangChain.

Esta página está obsoleta. Consulte o Guia de Agents para obter as informações mais atualizadas sobre o uso de ferramentas.

É altamente recomendável usar o Model Context Protocol ou OpenAPI Actions para integrar ferramentas personalizadas.

Criando suas próprias Ferramentas/Plugins

Aviso

Consulte as ferramentas mais recentes usadas com assistentes em api/app/clients/tools/structured/, já que os plugins serão descontinuados em favor das ferramentas em um futuro próximo.

Criar plugins personalizados para este projeto envolve estender a classe Tool do módulo langchain/tools.

Nota: Usarei a palavra plugin de forma intercambiável com tool, já que esta última é específica do LangChain, e estamos nos adequando principalmente à biblioteca.

Você está essencialmente criando DynamicTools na linguagem do LangChain. Veja a documentação do LangChainJS para mais informações.

Este guia irá orientá-lo pelo processo de criação de seus próprios plugins personalizados, usando as ferramentas StableDiffusionAPI e WolframAlphaAPI como exemplos.

Ao usar o Functions Agent (o modo padrão para plugins), as ferramentas são convertidas em OpenAI functions; em qualquer caso, os plugins/ferramentas são invocados condicionalmente com base no LLM gerando um formato específico que nós analisamos.

A implementação mais comum de um plugin é realizar uma chamada de API com base na entrada em linguagem natural da IA, mas praticamente não há limites para casos de uso programáticos.


Principais Pontos

Aqui estão os pontos principais para criar seu próprio plugin:

1. Importe os Módulos Necessários: Importe os módulos necessários para o seu plugin, incluindo a classe Tool de langchain/tools e quaisquer outros módulos que seu plugin possa precisar.

2. Defina sua classe de plugin: Defina uma classe para seu plugin que estenda a classe Tool. Defina as propriedades name e description no construtor. Se o seu plugin exigir credenciais ou outras variáveis, defina-as a partir do parâmetro fields ou de um método que as recupere do seu ambiente de processo. Observe que, se o seu plugin exigir instruções longas e detalhadas, você pode adicionar uma propriedade description_for_model e tornar a description mais geral.

3. Defina Métodos Auxiliares: Defina métodos auxiliares dentro da sua classe para lidar com tarefas específicas, se necessário.

4. Implemente o método _call: Implemente o método _call onde a funcionalidade principal do seu plugin é definida. Este método é chamado quando o modelo de linguagem decide usar o seu plugin. Ele deve receber um parâmetro input e retornar um resultado. Se ocorrer um erro, a função deve retornar uma string representando um erro, em vez de lançar um erro. Se o seu plugin exigir múltiplas entradas do LLM, leia a seção StructuredTools.

5. Exporte seu plugin e importe no handleTools.js: Exporte seu plugin e importe-o no handleTools.js. Adicione seu plugin ao objeto toolConstructors na função loadTools. Se o seu plugin exigir uma inicialização mais avançada, adicione-o ao objeto customConstructors.

6. Exporte seu plugin para o index.js: Exporte seu plugin para o index.js dentro de tools. Adicione seu plugin ao module.exports do index.js, portanto, você também precisa declará-lo como const neste arquivo.

7. Adicione seu plugin ao manifest.json: Adicione seu plugin ao manifest.json. Siga o formato estrito para cada um dos campos do objeto "plugin". Se o seu plugin exigir autenticação, adicione esses detalhes em authConfig como um array. O pluginKey deve corresponder ao name da classe Tool que você criou, e a propriedade authField deve corresponder ao nome da variável process.env.

Lembre-se, a chave para criar um plugin personalizado é estender a classe Tool e implementar o método _call. O método _call é onde você define o que seu plugin faz. Você também pode definir métodos auxiliares e propriedades em sua classe para dar suporte à funcionalidade do seu plugin.

Nota: Você pode encontrar todos os arquivos mencionados neste guia na pasta .\api\app\langchain\tools.


StructuredTools

Plugins de Múltiplas Entradas

Se você deseja criar um plugin que se beneficie de múltiplas entradas do LLM, em vez de uma única string de entrada como veremos, você precisa criar um StructuredTool do LangChain. Um guia detalhado sobre isso está em desenvolvimento, mas, por enquanto, você pode observar como criei StructuredTools neste diretório: api\app\clients\tools\structured\. Este guia é fundamental para entender StructuredTools, e é recomendado que você continue lendo para entender melhor as ferramentas do LangChain primeiro. O blog vinculado acima também é útil após a leitura deste guia.


Passo 1: Importar os módulos necessários

Comece importando os módulos necessários. Isso incluirá a classe Tool de langchain/tools e quaisquer outros módulos que sua ferramenta possa precisar. Por exemplo:

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

Passo 2: Defina sua Classe de Ferramenta

Em seguida, defina uma classe para o seu plugin que estenda a classe Tool. A classe deve ter um construtor que chame o método super() e defina as propriedades name e description. Essas propriedades serão usadas pelo modelo de linguagem para determinar quando chamar sua ferramenta e com quais parâmetros.

Importante: você deve definir as credenciais/variáveis necessárias a partir do parâmetro fields ou, alternativamente, a partir de um método que as obtenha do seu 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 da v0.5.8, ao usar Functions, você pode adicionar instruções mais longas e detalhadas com a propriedade description_for_model. Ao fazer isso, recomenda-se tornar a propriedade description mais generalizada para otimizar tokens. Cada linha nesta propriedade é prefixada com // para espelhar como o prompt é gerado para o ChatGPT (chat.openai.com). Este formato se alinha mais estreitamente à engenharia de prompt dos plugins oficiais do 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 do construtor, observe que estamos obtendo uma variável sensível a partir do objeto fields ou do método getServerURL que definimos para acessar uma variável de ambiente.

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

Quaisquer credenciais necessárias são passadas através de fields quando o usuário as fornece pelo frontend; caso contrário, o administrador pode "autorizar" o plugin para todos os usuários através de variáveis de ambiente. Todas as credenciais passadas pelo frontend são criptografadas.

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

Passo 3: Definir métodos auxiliares

Você pode definir métodos auxiliares dentro da sua classe para lidar com tarefas específicas, se necessário. Por exemplo, a classe StableDiffusionAPI inclui métodos como replaceNewLinesWithSpaces, getMarkdownImageUrl e getServerURL para lidar com várias tarefas.

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

Passo 4: Implementar o método _call

O método _call é onde a funcionalidade principal do seu plugin é implementada. Este método é chamado quando o modelo de linguagem decide usar o seu plugin. Ele deve receber um parâmetro input e retornar um resultado.

Em uma Tool básica, o LLM gerará um valor de string como entrada. Se o seu plugin exigir múltiplas entradas do LLM, leia a seção StructuredTools.

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

Importante: A função _call é o que o agente irá realmente chamar. Quando ocorrer um erro, a função deve, sempre que possível, retornar uma string representando um erro, em vez de lançar um erro. Isso permite que o erro seja passado para o LLM e o LLM possa decidir como lidar com ele. Se um erro for lançado, a execução do agente será interrompida.

Passo 5: Exporte seu Plugin e importe no handleTools.js

Este processo será um tanto automatizado no futuro, desde que você tenha seu plugin/ferramenta em api\app\langchain\tools

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

Em handleTools.js, encontre o início da função loadTools e adicione seu plugin/ferramenta ao 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`
  };

Se a sua classe Tool exigir uma inicialização mais avançada, você deve adicioná-la ao objeto customConstructors.

A inicialização padrão pode ser vista na função loadToolWithAuth, e a maioria dos plugins personalizados deve ser inicializada desta forma.

Aqui estão alguns customConstructors, que possuem inicializações variadas

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

Passo 6: Exporte seu Plugin para index.js

Encontre o index.js em api/app/clients/tools. Você precisa colocar seu plugin no module.exports, para fazê-lo compilar, você também precisará declarar seu plugin como consts:

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

Passo 7: Adicione seu Plugin ao manifest.json

Este processo será um pouco automatizado no futuro, juntamente com o passo 5, desde que você tenha seu plugin/ferramenta em api\app\langchain\tools e seu plugin possa ser inicializado com o método padrão

  {
    "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 um dos campos do objeto "plugin" é importante. Siga este formato rigorosamente. Se o seu plugin exigir autenticação, você adicionará esses detalhes em authConfig como um array, já que pode haver múltiplas variáveis de autenticação. Veja o plugin Calculator para um exemplo de um que não requer autenticação, onde o authConfig é um array vazio (um array é sempre necessário).

Nota: como mencionado anteriormente, a pluginKey corresponde ao name da classe da ferramenta (Tool class) que você criou. Nota: a propriedade authField deve corresponder ao nome da variável em process.env. Nota: as entradas em authConfig podem incluir sensitive. Omita-o ou defina-o como true para chaves de API e segredos. Defina sensitive: false para valores de configuração que não sejam secretos, como URLs, nomes de usuário, nomes de implantação ou IDs de projeto, para que a interface renderize um campo de texto simples em vez de um campo de entrada de segredo.

Aqui está um exemplo de um plugin com mais de uma variável 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
      }
    ]
  },

Exemplo: Ferramenta WolframAlphaAPI

Aqui está outro exemplo de uma ferramenta personalizada, a ferramenta WolframAlphaAPI. Esta ferramenta utiliza o módulo axios para realizar requisições HTTP para a API do 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

Neste exemplo, a classe WolframAlphaAPI possui métodos auxiliares como fetchRawText, getAppId e createWolframAlphaURL para lidar com tarefas específicas. O método _call faz uma requisição HTTP para a API do Wolfram Alpha e retorna a resposta.

Como está este guia?