도구(Tool) 플러그인 개발

도구(Tool) 플러그인 개발

Tool은 Chatflow, Workflow, Agent 애플리케이션이 호출할 수 있는 서드파티 서비스예요. 온라인 검색이나 이미지 생성 같은 능력으로 Dify 앱을 확장해 줍니다.

출처: 공식문서

이 가이드에서 *도구 플러그인(tool plugin)*은 도구 프로바이더 파일, 기능 코드, 지원 구조를 포함한 완전한 프로젝트를 뜻해요. 도구 프로바이더는 여러 도구를 묶을 수 있고, 각 도구는 서로 다른 능력을 제공해요.

- Tool Provider
    - Tool A
    - Tool B

이 가이드는 Google Search를 예시로 도구 플러그인 개발을 처음부터 끝까지 따라가요.

사전 준비

  • Dify 플러그인 스캐폴딩 툴
  • Python 환경 (3.12 버전)

스캐폴딩 툴 설정은 개발 도구 초기화를 참고하세요.

새 프로젝트 만들기

스캐폴딩 커맨드라인 툴을 실행해 새 Dify 플러그인 프로젝트를 만들어요.

./dify-plugin-darwin-arm64 plugin init

바이너리 파일 이름을 dify로 바꾸고 /usr/local/bin 경로에 복사했다면 아래 명령으로 새 플러그인 프로젝트를 만들 수 있어요.

dify plugin init

📝 아래 예시들은 dify를 명령어로 씁니다. 문제가 생기면 dify를 커맨드라인 툴의 경로로 바꿔 사용하세요.

플러그인 유형과 템플릿 고르기

스캐폴딩 툴의 각 템플릿은 완전한 코드 프로젝트예요. 이 예시에서는 Tool 플러그인을 선택합니다.

💡 플러그인 개발에 이미 익숙해서 템플릿이 필요 없다면, General Specifications 가이드로 아무 플러그인 유형이나 만들 수 있어요.

플러그인 권한 설정

플러그인은 Dify 플랫폼에서 읽을 권한도 필요해요. 이 예시 플러그인에는 다음 권한을 부여합니다.

  • Tools
  • Apps
  • 영속 스토리지 활성화, 기본 스토리지 크기 사용
  • Endpoints 등록 허용

💡 터미널에서 화살표 키로 권한을 선택하고 Tab 키로 부여해요.

모든 권한 항목을 확인한 뒤 Enter를 눌러 플러그인 생성을 완료해요. 시스템이 플러그인 프로젝트 코드를 자동으로 생성해 줍니다.

도구 플러그인 개발

1. 도구 프로바이더 파일 만들기

도구 프로바이더 파일은 플러그인의 기본 설정 역할을 하는 YAML 파일로, 도구가 필요한 인증 정보를 제공해요.

플러그인 템플릿 프로젝트의 /provider 디렉토리로 가서 YAML 파일 이름을 google.yaml로 바꿔요. 이 파일이 도구 프로바이더를 설명합니다: 설치될 때 표시되는 이름, 아이콘, 작성자 등 세부 정보죠.

예시 코드:

identity: # Basic information about the tool provider
    author: Your-name # Author
    name: google # Unique name; must not duplicate another provider's name
    label: # Label shown in the frontend
        en_US: Google # English label
        zh_Hans: Google # Chinese label
    description: # Description shown in the frontend
        en_US: Google # English description
        zh_Hans: Google # Chinese description
    icon: icon.svg # Tool icon; must be placed in the _assets folder
    tags: # Tags shown in the frontend
        - search

파일 경로가 /tools 디렉토리에 있고, 전체 경로는 이렇게 돼 있는지 확인하세요.

plugins:
    tools:
        - 'google.yaml'

google.yaml은 플러그인 프로젝트 안에서 절대 경로로 참조되어야 해요. 이 예시에서는 프로젝트 루트 디렉토리에 있습니다. YAML 파일에서 identity는 도구 프로바이더의 기본 정보(작성자, 이름, 라벨, 설명, 아이콘)를 담아요.

  • 아이콘은 프로젝트 루트의 _assets 폴더에 있는 첨부 리소스여야 해요.
  • 태그는 사용자가 플러그인을 카테고리별로 찾는 데 도움을 줘요. 현재 지원되는 태그는 전부 이래요.
class ToolLabelEnum(Enum):
  SEARCH = 'search'
  IMAGE = 'image'
  VIDEOS = 'videos'
  WEATHER = 'weather'
  FINANCE = 'finance'
  DESIGN = 'design'
  TRAVEL = 'travel'
  SOCIAL = 'social'
  NEWS = 'news'
  MEDICAL = 'medical'
  PRODUCTIVITY = 'productivity'
  EDUCATION = 'education'
  BUSINESS = 'business'
  ENTERTAINMENT = 'entertainment'
  UTILITIES = 'utilities'
  OTHER = 'other'

2. 서드파티 서비스 자격증명 추가

편의를 위해 이 예시는 서드파티 서비스 SerpApi가 제공하는 Google Search API를 사용해요. SerpApi는 API 키가 필요하므로 YAML 파일에 credentials_for_provider 필드를 추가합니다.

전체 코드:

identity:
    author: Dify
    name: google
    label:
        en_US: Google
        zh_Hans: Google
        pt_BR: Google
    description:
        en_US: Google
        zh_Hans: GoogleSearch
        pt_BR: Google
    icon: icon.svg
    tags:
        - search
credentials_for_provider: # Add the credentials_for_provider field
    serpapi_api_key:
        type: secret-input
        required: true
        label:
            en_US: SerpApi API key
            zh_Hans: SerpApi API key
        placeholder:
            en_US: Please input your SerpApi API key
            zh_Hans: Please enter your SerpApi API key
        help:
            en_US: Get your SerpApi API key from SerpApi
            zh_Hans: Get your SerpApi API key from SerpApi
        url: https://serpapi.com/manage-api-key
tools:
    - tools/google_search.yaml
extra:
    python:
        source: google.py
  • credentials_for_provider의 하위 구조는 General Specifications의 요건을 충족해야 해요.
  • 프로바이더가 포함하는 도구를 지정해요. 이 예시는 tools/google_search.yaml 파일 하나만 포함합니다.
  • 기본 정보뿐 아니라 프로바이더는 코드 로직도 필요하므로, 구현 파일을 지정해요. 이 예시는 google.py를 쓰지만, 지금은 구현을 비워두고 먼저 google_search 도구 코드부터 작성할게요.

3. 도구 YAML 파일 채우기

도구 플러그인은 여러 도구를 담을 수 있고, 각 도구는 기본 정보·파라미터·출력을 담은 각자의 YAML 파일로 설명돼요.

GoogleSearch 도구를 계속해서, /tools 폴더에 새 google_search.yaml 파일을 만들어요.

identity:
    name: google_search
    author: Dify
    label:
        en_US: GoogleSearch
        zh_Hans: Google Search
        pt_BR: GoogleSearch
description:
    human:
        en_US: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
        zh_Hans: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
        pt_BR: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
    llm: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
parameters:
    - name: query
      type: string
      required: true
      label:
          en_US: Query string
          zh_Hans: Query string
          pt_BR: Query string
      human_description:
          en_US: used for searching
          zh_Hans: used for searching web content
          pt_BR: used for searching
      llm_description: key words for searching
      form: llm
extra:
    python:
        source: tools/google_search.py
  • identity: 도구의 기본 정보(이름, 작성자, 라벨, 설명).
  • parameters: 파라미터 목록.
    • name (필수): 파라미터 이름. 도구의 파라미터들 사이에서 고유해야 해요.

    • type (필수): 파라미터 유형. 값은 세 그룹으로 나뉘어요.

      • 평범한 값: string, number, boolean, any.
      • 선택형: options로 만든 고정 드롭다운용 select, 설정 시점에 플러그인이 옵션을 공급하는 드롭다운용 dynamic-select, 그리고 checkbox.
      • 선택기와 업로드:
        • 민감 값용 secret-input — 암호화된 입력 박스로 렌더링돼요
        • 업로드용 filefiles
        • 워크스페이스에서 앱이나 모델을 고르는 app-selectormodel-selector
        • 날짜 하나용 date — 날짜 선택기로 렌더링되고 YYYY-MM-DD 문자열로 전달돼요
        • 시작·끝 날짜용 date-range — 범위 선택기로 렌더링되고 start·end 날짜 문자열을 담은 객체로 전달돼요

      그 외에 arrayobject 두 값은 플러그인이 선언한 파라미터가 아니라 MCP 서버 도구용으로 존재하고, system-files는 지원 중단됐어요.

    • label (필수): 파라미터 라벨, 프론트엔드에 표시돼요.

    • form (필수): 폼 유형, llm 또는 form.

      • Agent 앱에서 llm은 LLM이 파라미터를 직접 추론한다는 뜻이고, form은 도구를 쓰기 전에 파라미터를 미리 설정할 수 있다는 뜻이에요.
      • Workflow 앱에서는 llm·form 파라미터 모두 프론트엔드에서 채워지지만, llm 파라미터는 도구 노드의 입력 변수로 쓰여요.
    • required (선택): 파라미터가 필수인지.

      • llm 모드에서 필수 파라미터는 Agent가 추론해야 해요.
      • form 모드에서 필수 파라미터는 대화 시작 전에 프론트엔드에서 채워야 해요.
    • options (선택): 파라미터 옵션.

      • llm 모드에서 Dify는 모든 옵션을 LLM에 넘기고, LLM이 그것에 기반해 추론할 수 있어요.
      • form 모드에서 typeselect면 프론트엔드가 옵션을 표시해요.
    • multiple (선택): select·dynamic-select 파라미터에서 true로 설정하면 사용자가 여러 옵션을 고를 수 있어요. 값은 옵션 값들의 목록으로 전달되고, default가 있으면 그때도 목록이어야 해요.

      - name: formats
        type: select
        multiple: true
        form: form
        label:
          en_US: Formats
        options:
          - label:
              en_US: Markdown
            value: markdown
          - label:
              en_US: Links
            value: links
        default:
          - markdown
      
    • default (선택): 기본값.

    • min (선택): 최솟값. 파라미터 유형이 number일 때 적용돼요.

    • max (선택): 최댓값. 파라미터 유형이 number일 때 적용돼요.

    • human_description (선택): 프론트엔드에 표시되는 설명. 다중 언어 지원.

    • placeholder (선택): 입력 필드의 힌트 텍스트. 폼 유형이 form이고 파라미터 유형이 string·number·secret-input일 때 적용돼요. 다중 언어 지원.

    • llm_description (선택): LLM에 전달되는 설명. LLM이 파라미터를 이해할 수 있도록 최대한 상세히 쓰세요.

4. 도구 코드 작성

도구 설정이 갖춰졌으면 도구 로직을 구현하는 코드를 써요. /tools 디렉토리에 google_search.py를 만들고 다음 내용을 넣어요.

from collections.abc import Generator
from typing import Any

import requests

from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage

SERP_API_URL = "https://serpapi.com/search"

class GoogleSearchTool(Tool):
    def _parse_response(self, response: dict) -> dict:
        result = {}
        if "knowledge_graph" in response:
            result["title"] = response["knowledge_graph"].get("title", "")
            result["description"] = response["knowledge_graph"].get("description", "")
        if "organic_results" in response:
            result["organic_results"] = [
                {
                    "title": item.get("title", ""),
                    "link": item.get("link", ""),
                    "snippet": item.get("snippet", ""),
                }
                for item in response["organic_results"]
            ]
        return result

    def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
        params = {
            "api_key": self.runtime.credentials["serpapi_api_key"],
            "q": tool_parameters["query"],
            "engine": "google",
            "google_domain": "google.com",
            "gl": "us",
            "hl": "en",
        }

        response = requests.get(url=SERP_API_URL, params=params, timeout=5)
        response.raise_for_status()
        valuable_res = self._parse_response(response.json())

        yield self.create_json_message(valuable_res)

이 코드는 serpapi에 요청을 보내고, self.create_json_message로 형식화된 JSON 데이터를 돌려줘요. 반환 데이터 유형에 대해 더 알고 싶다면 원격 디버깅Persistent Storage KV를 참고하세요.

5. 도구 프로바이더 코드 완성

마지막으로 프로바이더의 자격증명 검증 로직을 구현해요. 검증이 실패하면 코드가 ToolProviderCredentialValidationError 예외를 던지고, 성공하면 google_search 도구 서비스가 올바르게 요청돼요.

/provider 디렉토리에 google.py 파일을 만들고 다음 내용을 넣어요.

from typing import Any

from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
from tools.google_search import GoogleSearchTool

class GoogleProvider(ToolProvider):
    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
        try:
            for _ in GoogleSearchTool.from_credentials(credentials).invoke(
                tool_parameters={"query": "test", "result_type": "link"},
            ):
                pass
        except Exception as e:
            raise ToolProviderCredentialValidationError(str(e))

플러그인 디버깅

개발 후 플러그인이 제대로 동작하는지 테스트해요. Dify는 원격 디버깅을 제공해서 테스트 환경에서 플러그인 기능을 빠르게 확인할 수 있어요.

Plugin Management 페이지로 가서 원격 서버 주소와 디버그 키를 받아요.

플러그인 프로젝트로 돌아가 .env.example 파일을 복사해 .env로 이름 바꾸고, 원격 서버 주소와 디버그 키를 채워 넣어요.

.env 파일:

INSTALL_METHOD=remote
REMOTE_INSTALL_URL=debug.dify.ai:5003
REMOTE_INSTALL_KEY=********-****-****-****-************

python -m main을 실행해 플러그인을 시작해요. Plugins 페이지에서 워크스페이스에 설치된 플러그인을 볼 수 있고, 다른 팀원도 접근할 수 있어요.

플러그인 패키징 (선택)

플러그인이 올바르게 동작하면 다음 명령으로 패키징하고 이름을 붙여요. 현재 폴더에 google.difypkg 파일 — 최종 플러그인 패키지가 생깁니다.

# Replace ./google with the actual path of the plugin project

dify plugin package ./google

축하해요 — 도구 플러그인을 개발하고, 디버깅하고, 패키징까지 마쳤어요!

플러그인 게시 (선택)

플러그인을 Dify Marketplace에 게시하려면 Marketplace 준비 및 검증 요건을 충족해야 해요. 검토 후 패키지는 메인 브랜치에 병합되고 Marketplace 파이프라인을 통해 릴리스돼요.

전체 과정은 게시 개요를 참고하세요.

더 알아보기 (Learn more)