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

도구 및 플러그인

이 문서는 LangChain `Tool` 클래스를 확장하여 LibreChat용 커스텀 플러그인을 만드는 방법을 설명합니다. 플러그인에서 다양한 API와 함수를 사용하는 방법과 이를 LangChain 프레임워크와 통합하는 방법을 배우게 됩니다.

이 페이지는 더 이상 사용되지 않습니다. 도구 사용에 대한 최신 정보는 Agents Guide를 참조하세요.

사용자 지정 도구를 통합하려면 Model Context Protocol 또는 OpenAPI Actions를 사용하는 것을 강력히 권장합니다.

나만의 도구/플러그인 만들기

경고

api/app/clients/tools/structured/에서 어시스턴트와 함께 사용되는 최신 도구를 참조하십시오. 향후 플러그인은 도구로 대체되어 더 이상 사용되지 않을 예정입니다.

이 프로젝트를 위한 커스텀 플러그인을 만드는 작업은 langchain/tools 모듈의 Tool 클래스를 확장하는 것을 포함합니다.

참고: 저는 plugin이라는 단어를 tool과 혼용하여 사용할 것입니다. 후자는 LangChain에 특화된 용어이며, 저희는 주로 해당 라이브러리를 따르고 있기 때문입니다.

본질적으로 LangChain 용어로 DynamicTools를 생성하는 것입니다. 자세한 내용은 **LangChainJS docs**를 참조하세요.

이 가이드는 StableDiffusionAPIWolframAlphaAPI 도구를 예시로 사용하여 나만의 커스텀 플러그인을 만드는 과정을 안내합니다.

Functions Agent(플러그인 기본 모드)를 사용할 때, 도구는 **OpenAI functions**로 변환됩니다. 어떠한 경우든 플러그인/도구는 LLM이 우리가 파싱하는 특정 형식을 생성하는 것을 기반으로 조건부로 호출됩니다.

플러그인의 가장 일반적인 구현 방식은 AI로부터 받은 자연어 입력을 기반으로 API 호출을 수행하는 것이지만, 프로그래밍적 사용 사례에는 사실상 제한이 없습니다.


핵심 요약

나만의 플러그인을 만들기 위한 핵심 요점은 다음과 같습니다:

1. 필수 모듈 가져오기: langchain/toolsTool 클래스를 포함하여 플러그인에 필요한 모듈과 플러그인이 필요로 하는 기타 모듈을 가져옵니다.

2. 플러그인 클래스 정의: Tool 클래스를 상속하는 플러그인용 클래스를 정의합니다. 생성자에서 namedescription 속성을 설정하세요. 플러그인에 자격 증명이나 기타 변수가 필요한 경우, fields 매개변수에서 설정하거나 프로세스 환경에서 이를 가져오는 메서드를 통해 설정합니다. 플러그인에 길고 상세한 지침이 필요한 경우, description_for_model 속성을 추가하고 description은 더 일반적인 내용으로 작성할 수 있습니다.

3. 도우미 메서드 정의: 필요한 경우 특정 작업을 처리하기 위해 클래스 내에 도우미 메서드를 정의하세요.

4. _call 메서드 구현: 플러그인의 주요 기능이 정의되는 _call 메서드를 구현합니다. 이 메서드는 언어 모델이 플러그인을 사용하기로 결정했을 때 호출됩니다. input 매개변수를 받아 결과를 반환해야 합니다. 오류가 발생하면 함수는 오류를 발생(throw)시키는 대신 오류를 나타내는 문자열을 반환해야 합니다. 플러그인이 LLM으로부터 여러 입력을 받아야 하는 경우, StructuredTools 섹션을 읽어보세요.

5. 플러그인을 내보내고 handleTools.js로 가져오기: 플러그인을 내보내고 handleTools.js로 가져옵니다. loadTools 함수의 toolConstructors 객체에 플러그인을 추가하세요. 플러그인에 더 고급 초기화가 필요한 경우 customConstructors 객체에 추가하십시오.

6. 플러그인을 index.js로 내보내기: tools 아래의 index.js로 플러그인을 내보냅니다. 플러그인을 index.jsmodule.exports에 추가해야 하므로, 이 파일에서 const로 선언해야 합니다.

7. manifest.json에 플러그인 추가하기: manifest.json에 플러그인을 추가하세요. "plugin" 객체의 각 필드에 대해 엄격한 형식을 따르세요. 플러그인에 인증이 필요한 경우, 해당 세부 정보를 authConfig 아래에 배열로 추가하세요. pluginKey는 생성한 Tool 클래스의 name과 일치해야 하며, authField 속성은 process.env 변수 이름과 일치해야 합니다.

사용자 지정 플러그인을 만드는 핵심은 Tool 클래스를 확장하고 _call 메서드를 구현하는 것임을 기억하세요. _call 메서드는 플러그인이 수행할 작업을 정의하는 곳입니다. 또한 클래스 내에 도우미 메서드와 속성을 정의하여 플러그인의 기능을 지원할 수도 있습니다.

참고: 이 가이드에서 언급된 모든 파일은 .\api\app\langchain\tools 폴더에서 찾을 수 있습니다.


StructuredTools

다중 입력 플러그인

LLM으로부터 단일 입력 문자열이 아닌 여러 입력을 받는 플러그인을 만들고 싶다면, 우리가 검토할 방식 대신 LangChain **StructuredTool**을 만들어야 합니다. 이에 대한 자세한 가이드는 현재 작성 중이지만, 지금은 api\app\clients\tools\structured\ 디렉터리에서 제가 어떻게 StructuredTool을 만들었는지 참고할 수 있습니다. 이 가이드는 StructuredTool을 이해하기 위한 기초이며, LangChain 도구를 먼저 더 잘 이해하기 위해 계속 읽어보는 것을 권장합니다. 위에서 링크된 블로그도 이 가이드를 읽은 후에 확인하면 도움이 될 것입니다.


Step 1: 필수 모듈 가져오기

필요한 모듈을 가져오는 것으로 시작하세요. 여기에는 langchain/toolsTool 클래스와 도구에 필요할 수 있는 기타 모듈이 포함됩니다. 예를 들면 다음과 같습니다:

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

2단계: 도구 클래스 정의하기

다음으로, Tool 클래스를 상속하는 플러그인용 클래스를 정의합니다. 이 클래스에는 super() 메서드를 호출하고 namedescription 속성을 설정하는 생성자가 있어야 합니다. 이러한 속성은 언어 모델이 언제 도구를 호출할지, 그리고 어떤 매개변수를 사용할지 결정하는 데 사용됩니다.

중요: fields 매개변수에서 자격 증명/필수 변수를 설정하거나, 프로세스 환경에서 이를 가져오는 메서드를 사용하여 설정해야 합니다.

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...`;
  }
  ...
}

선택 사항: v0.5.8부터 Functions를 사용할 때 description_for_model 속성을 통해 더 길고 자세한 지침을 추가할 수 있습니다. 이 작업을 수행할 때는 토큰을 최적화하기 위해 description 속성을 더 일반화하는 것이 좋습니다. 이 속성의 각 줄 앞에는 ChatGPT(chat.openai.com)용 프롬프트가 생성되는 방식과 동일하게 // 접두사가 붙습니다. 이 형식은 공식 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."
// ...

생성자 내에서, fields 객체나 환경 변수에 접근하기 위해 정의한 getServerURL 메서드 중 하나로부터 민감한 변수를 가져오고 있다는 점을 참고하세요.

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

필요한 모든 자격 증명은 사용자가 프론트엔드에서 제공할 때 fields를 통해 전달됩니다. 그렇지 않은 경우, 관리자는 환경 변수를 통해 모든 사용자에 대해 플러그인을 "승인"할 수 있습니다. 프론트엔드에서 전달된 모든 자격 증명은 암호화됩니다.

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

Step 3: Helper 메서드 정의

필요한 경우 클래스 내에 특정 작업을 처리하기 위한 헬퍼 메서드를 정의할 수 있습니다. 예를 들어, StableDiffusionAPI 클래스에는 다양한 작업을 처리하기 위해 replaceNewLinesWithSpaces, getMarkdownImageUrl, getServerURL과 같은 메서드가 포함되어 있습니다.

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

4단계: _call 메서드 구현

_call 메서드는 플러그인의 주요 기능이 구현되는 곳입니다. 이 메서드는 언어 모델이 플러그인을 사용하기로 결정했을 때 호출됩니다. input 매개변수를 받아 결과를 반환해야 합니다.

기본 Tool에서 LLM은 입력값으로 하나의 문자열 값을 생성합니다. 플러그인에 LLM으로부터 여러 개의 입력값이 필요한 경우, StructuredTools 섹션을 읽어보시기 바랍니다.

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

중요: _call 함수는 에이전트가 실제로 호출하게 될 함수입니다. 오류가 발생할 경우, 함수는 가능한 한 오류를 발생(throw)시키기보다는 오류를 나타내는 문자열을 반환해야 합니다. 이를 통해 오류가 LLM에 전달되어 LLM이 이를 어떻게 처리할지 결정할 수 있습니다. 만약 오류가 발생(throw)하면 에이전트의 실행이 중단됩니다.

Step 5: 플러그인을 내보내고 handleTools.js로 가져오기

이 과정은 api\app\langchain\tools에 플러그인/도구가 있는 한, 향후 어느 정도 자동화될 예정입니다.

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

handleTools.js에서 loadTools 함수의 시작 부분을 찾아 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`
  };

Tool 클래스에 더 고급 초기화가 필요한 경우, customConstructors 객체에 추가하면 됩니다.

기본 초기화는 loadToolWithAuth 함수에서 확인할 수 있으며, 대부분의 사용자 지정 플러그인은 이 방식으로 초기화해야 합니다.

다음은 다양한 초기화 방식을 가진 몇 가지 customConstructors입니다.

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

Step 6: Export your Plugin into index.js

api/app/clients/tools 아래에서 index.js를 찾으세요. 플러그인을 컴파일하려면 module.exports에 플러그인을 넣어야 하며, consts로도 선언해야 합니다:

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

7단계: manifest.json에 플러그인 추가하기

이 과정은 향후 5단계와 함께 어느 정도 자동화될 예정입니다. 단, 플러그인/도구가 api\app\langchain\tools에 있고 기본 메서드로 초기화할 수 있어야 합니다.

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

"plugin" 객체의 각 필드는 중요합니다. 이 형식을 엄격히 따르십시오. 플러그인에 인증이 필요한 경우, 여러 인증 변수가 있을 수 있으므로 authConfig 아래에 해당 세부 정보를 배열로 추가합니다. 인증이 필요하지 않은 예시는 Calculator 플러그인을 참조하십시오. 이 경우 authConfig는 빈 배열이 됩니다(배열은 항상 필수입니다).

참고: 앞서 언급했듯이, pluginKey는 생성한 Tool 클래스의 name과 일치해야 합니다. 참고: authField 속성은 process.env 변수 이름과 일치해야 합니다. 참고: authConfig 항목에는 sensitive를 포함할 수 있습니다. API 키나 비밀 값의 경우 이를 생략하거나 true로 설정하세요. URL, 사용자 이름, 배포 이름 또는 프로젝트 ID와 같이 비밀이 아닌 설정 값의 경우 sensitive: false로 설정하면 UI에서 비밀 입력 필드 대신 일반 텍스트 필드가 렌더링됩니다.

다음은 하나 이상의 자격 증명 변수를 사용하는 플러그인의 예시입니다.

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

예시: WolframAlphaAPI 도구

다음은 커스텀 툴의 또 다른 예시인 WolframAlphaAPI 툴입니다. 이 툴은 axios 모듈을 사용하여 Wolfram Alpha API에 HTTP 요청을 보냅니다.

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

이 예시에서 WolframAlphaAPI 클래스는 특정 작업을 처리하기 위해 fetchRawText, getAppId, createWolframAlphaURL과 같은 헬퍼 메서드를 가지고 있습니다. _call 메서드는 Wolfram Alpha API에 HTTP 요청을 보내고 응답을 반환합니다.

이 가이드는 어떤가요?