├── .difyignore ├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ └── plugin-publish.yml ├── .gitignore ├── .vscode └── launch.json ├── FAQ.md ├── GUIDE.md ├── PRIVACY.md ├── README.md ├── README_CN.md ├── _assets ├── contact.jpg ├── csv.png ├── icon.svg ├── sql_execute.png └── text.png ├── endpoints ├── sql_execute.py └── sql_execute.yaml ├── main.py ├── manifest.yaml ├── provider ├── database.py ├── database.yaml └── endpoint.yaml ├── requirements.txt ├── test.db └── tools ├── csv_query.py ├── csv_query.yaml ├── sql_execute.py ├── sql_execute.yaml ├── table_schema.py ├── table_schema.yaml ├── text2sql.py └── text2sql.yaml /.difyignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | .Python 8 | build/ 9 | develop-eggs/ 10 | dist/ 11 | downloads/ 12 | eggs/ 13 | .eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | wheels/ 20 | share/python-wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .nox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | *.py,cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | cover/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | db.sqlite3-journal 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | .pybuilder/ 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | # For a library or package, you might want to ignore these files since the code is 84 | # intended to run in multiple environments; otherwise, check them in: 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | Pipfile.lock 93 | 94 | # UV 95 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 96 | # This is especially recommended for binary packages to ensure reproducibility, and is more 97 | # commonly ignored for libraries. 98 | uv.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 113 | .pdm.toml 114 | .pdm-python 115 | .pdm-build/ 116 | 117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 118 | __pypackages__/ 119 | 120 | # Celery stuff 121 | celerybeat-schedule 122 | celerybeat.pid 123 | 124 | # SageMath parsed files 125 | *.sage.py 126 | 127 | # Environments 128 | .env 129 | .venv 130 | env/ 131 | venv/ 132 | ENV/ 133 | env.bak/ 134 | venv.bak/ 135 | 136 | # Spyder project settings 137 | .spyderproject 138 | .spyproject 139 | 140 | # Rope project settings 141 | .ropeproject 142 | 143 | # mkdocs documentation 144 | /site 145 | 146 | # mypy 147 | .mypy_cache/ 148 | .dmypy.json 149 | dmypy.json 150 | 151 | # Pyre type checker 152 | .pyre/ 153 | 154 | # pytype static type analyzer 155 | .pytype/ 156 | 157 | # Cython debug symbols 158 | cython_debug/ 159 | 160 | # PyCharm 161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 163 | # and can be added to the global gitignore or merged into this file. For a more nuclear 164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 165 | .idea/ 166 | .vscode/ 167 | .git/ 168 | test.db 169 | csv.db 170 | .DS_Store 171 | _assets/contact.jpg -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | INSTALL_METHOD=remote 2 | REMOTE_INSTALL_HOST=debug-plugin.dify.dev 3 | REMOTE_INSTALL_PORT=5003 4 | REMOTE_INSTALL_KEY=********-****-****-****-************ 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug 报告 2 | description: 创建一个 bug 报告以帮助我们改进 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ### ⚠️ 重要提示 9 | 在提交 issue 之前,请先查看 [FAQ](https://github.com/hjlarry/dify-plugin-database/blob/main/FAQ.md) 文档,很多常见问题在那里都能找到解答。 10 | 特别是以下常见问题: 11 | - No module named 'MySQLdb' 错误 12 | - 数据库密码包含 @ 符号 13 | - text2sql 工具生成的 SQL 语句问题 14 | - PluginDaemonBadRequestError 错误 15 | - 数据库连接问题 16 | - Oracle 连接问题 17 | 18 | - type: checkboxes 19 | attributes: 20 | label: 在提交之前,请确认 21 | options: 22 | - label: 我已经查看了 FAQ 文档,但没有找到解决方案 23 | required: true 24 | 25 | - type: textarea 26 | attributes: 27 | label: 问题描述 28 | description: 请清晰简洁地描述你遇到的问题 29 | validations: 30 | required: true 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: FAQ 文档 4 | url: https://github.com/hjlarry/dify-plugin-database/blob/main/FAQ.md 5 | about: 在创建 issue 之前,请先查看我们的 FAQ 文档 -------------------------------------------------------------------------------- /.github/workflows/plugin-publish.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/auto-pr.yml 2 | name: Auto Create PR on Main Push 3 | 4 | on: 5 | push: 6 | branches: [ main ] # Trigger on push to main 7 | 8 | jobs: 9 | create_pr: # Renamed job for clarity 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Print working directory # Kept for debugging 16 | run: | 17 | pwd 18 | ls -la 19 | 20 | - name: Download CLI tool 21 | run: | 22 | # Create bin directory in runner temp 23 | mkdir -p $RUNNER_TEMP/bin 24 | cd $RUNNER_TEMP/bin 25 | 26 | # Download CLI tool 27 | wget https://github.com/langgenius/dify-plugin-daemon/releases/download/0.0.6/dify-plugin-linux-amd64 28 | chmod +x dify-plugin-linux-amd64 29 | 30 | # Show download location and file 31 | echo "CLI tool location:" 32 | pwd 33 | ls -la dify-plugin-linux-amd64 34 | 35 | - name: Get basic info from manifest # Changed step name and content 36 | id: get_basic_info 37 | run: | 38 | PLUGIN_NAME=$(grep "^name:" manifest.yaml | cut -d' ' -f2) 39 | echo "Plugin name: $PLUGIN_NAME" 40 | echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT 41 | 42 | VERSION=$(grep "^version:" manifest.yaml | cut -d' ' -f2) 43 | echo "Plugin version: $VERSION" 44 | echo "version=$VERSION" >> $GITHUB_OUTPUT 45 | 46 | # If the author's name is not your github username, you can change the author here 47 | AUTHOR=$(grep "^author:" manifest.yaml | cut -d' ' -f2) 48 | echo "Plugin author: $AUTHOR" 49 | echo "author=$AUTHOR" >> $GITHUB_OUTPUT 50 | 51 | - name: Package Plugin 52 | id: package 53 | run: | 54 | # Use the downloaded CLI tool to package 55 | cd $GITHUB_WORKSPACE 56 | # Use variables for package name 57 | PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg" 58 | # Use CLI from runner temp 59 | $RUNNER_TEMP/bin/dify-plugin-linux-amd64 plugin package . -o "$PACKAGE_NAME" 60 | 61 | # Show packaging result 62 | echo "Package result:" 63 | ls -la "$PACKAGE_NAME" 64 | echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT 65 | 66 | # Show full file path and directory structure (kept for debugging) 67 | echo "\\nFull file path:" 68 | pwd 69 | echo "\\nDirectory structure:" 70 | tree || ls -R 71 | 72 | - name: Checkout target repo 73 | uses: actions/checkout@v3 74 | with: 75 | # Use author variable for repository 76 | repository: ${{steps.get_basic_info.outputs.author}}/dify-plugins 77 | path: dify-plugins 78 | token: ${{ secrets.PLUGIN_ACTION }} 79 | fetch-depth: 1 # Fetch only the last commit to speed up checkout 80 | persist-credentials: true # Persist credentials for subsequent git operations 81 | 82 | - name: Prepare and create PR 83 | run: | 84 | # Debug info (kept) 85 | echo "Debug: Current directory $(pwd)" 86 | # Use variable for package name 87 | PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg" 88 | echo "Debug: Package name: $PACKAGE_NAME" 89 | ls -la 90 | 91 | # Move the packaged file to the target directory using variables 92 | mkdir -p dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }} 93 | mv "$PACKAGE_NAME" dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }}/ 94 | 95 | # Enter the target repository directory 96 | cd dify-plugins 97 | 98 | # Configure git 99 | git config user.name "GitHub Actions" 100 | git config user.email "actions@github.com" 101 | 102 | # Ensure we are on the latest main branch 103 | git fetch origin main 104 | git checkout main 105 | git pull origin main 106 | 107 | # Create and switch to a new branch using variables and new naming convention 108 | BRANCH_NAME="bump-${{ steps.get_basic_info.outputs.plugin_name }}-plugin-${{ steps.get_basic_info.outputs.version }}" 109 | git checkout -b "$BRANCH_NAME" 110 | 111 | # Add and commit changes (using git add .) 112 | git add . 113 | git status # for debugging 114 | # Use variables in commit message 115 | git commit -m "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}" 116 | 117 | # Push to remote (use force just in case the branch existed before from a failed run) 118 | git push -u origin "$BRANCH_NAME" --force 119 | 120 | # Confirm branch has been pushed and wait for sync (GitHub API might need a moment) 121 | git branch -a 122 | echo "Waiting for branch to sync..." 123 | sleep 10 # Wait 10 seconds for branch sync 124 | 125 | - name: Create PR via GitHub API 126 | env: 127 | GH_TOKEN: ${{ secrets.PLUGIN_ACTION }} # Use the provided token for authentication 128 | run: | 129 | gh pr create \ 130 | --repo langgenius/dify-plugins \ 131 | --head "${{ steps.get_basic_info.outputs.author }}:${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}" \ 132 | --base main \ 133 | --title "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}" \ 134 | --body "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin package to version ${{ steps.get_basic_info.outputs.version }} 135 | 136 | Changes: 137 | - Updated plugin package file" || echo "PR already exists or creation skipped." # Handle cases where PR already exists 138 | 139 | - name: Print environment info # Kept for debugging 140 | run: | 141 | echo "GITHUB_WORKSPACE: $GITHUB_WORKSPACE" 142 | echo "Current directory contents:" 143 | ls -R -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | .DS_Store 171 | csv.db -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Module", 6 | "type": "debugpy", 7 | "request": "launch", 8 | "module": "main", 9 | "python": "${workspaceFolder}/.venv/bin/python", 10 | "cwd": "${workspaceFolder}", 11 | "console": "integratedTerminal", 12 | "justMyCode": false, 13 | "env": { 14 | "GEVENT_SUPPORT": "True" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## 1. 错误提示No module named 'MySQLdb' 4 | 你是应该是用`mysql+pymysql://`去连接,而不是`mysql://` 5 | 6 | ## 2. 密码中有 `@` 怎么办 7 | `@` 是URL中的保留字符,需要使用 `%40` 代替,例如 `123%40456` 8 | 9 | ## 3. `text2sql`工具生成的sql语句无法运行 10 | 主要是模型的指令遵循能力不强,生成的sql格式不准确,建议换模型。或者针对它生成的sql语句,加一个代码节点进行修改,目前常见的模型生成sql语句错误有前面多了`sql`字符串,生成的sql都有`limit 5`,双引号格式问题等。 11 | 12 | ## 4. 安装本地插件或github版本报错 `PluginDaemonBadRequestError` 13 | 找环境变量设置 `FORCE_VERIFYING_SIGNATURE=false` 14 | 15 | ## 5. 如何安装离线版本 16 | 参考 https://github.com/junjiem/dify-plugin-repackaging 17 | 18 | ## 6. 数据库连接不上 19 | 一般是数据库地址不对,肯定不能填`localhost`,因为插件都是运行在容器内的,localhost也会指向容器内部,你要解决的是容器内如何连接你的宿主机或者你宿主机所在的局域网,询问大模型怎么解决这个问题 20 | 21 | ## 7. `sql_execute` 工具输出为json,但下个节点只能选择text和files 22 | dify每个节点对接收的变量类型有限制,比如`直接回复`这个节点只能接收text和files类型的变量,可以在其前面加个`code`节点之类的转换一下 23 | 24 | ## 8. oracle连接问题 25 | 参考 https://docs.sqlalchemy.org/en/20/dialects/oracle.html#module-sqlalchemy.dialects.oracle.oracledb 26 | 27 | ## 9. SQL Server连接问题 28 | 连接不上时,可以在数据库连接配置选项中添加tds参数尝试,例如 {"connect_args": {"tds_version": "7.0"}} 29 | 不同的sqlserver版本对应不同的tds版本,具体列表可查看 https://www.freetds.org/userguide/ChoosingTdsProtocol.html#tab.Protocol.by.Product 30 | 感谢群友Alpha提供的解决方案 31 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | ## User Guide of how to develop a Dify Plugin 2 | 3 | Hi there, looks like you have already created a Plugin, now let's get you started with the development! 4 | 5 | ### Choose a Plugin type you want to develop 6 | 7 | Before start, you need some basic knowledge about the Plugin types, Plugin supports to extend the following abilities in Dify: 8 | - **Tool**: Tool Providers like Google Search, Stable Diffusion, etc. it can be used to perform a specific task. 9 | - **Model**: Model Providers like OpenAI, Anthropic, etc. you can use their models to enhance the AI capabilities. 10 | - **Endpoint**: Like Service API in Dify and Ingress in Kubernetes, you can extend a http service as an endpoint and control its logics using your own code. 11 | 12 | Based on the ability you want to extend, we have divided the Plugin into three types: **Tool**, **Model**, and **Extension**. 13 | 14 | - **Tool**: It's a tool provider, but not only limited to tools, you can implement an endpoint there, for example, you need both `Sending Message` and `Receiving Message` if you are building a Discord Bot, **Tool** and **Endpoint** are both required. 15 | - **Model**: Just a model provider, extending others is not allowed. 16 | - **Extension**: Other times, you may only need a simple http service to extend the functionalities, **Extension** is the right choice for you. 17 | 18 | I believe you have chosen the right type for your Plugin while creating it, if not, you can change it later by modifying the `manifest.yaml` file. 19 | 20 | ### Manifest 21 | 22 | Now you can edit the `manifest.yaml` file to describe your Plugin, here is the basic structure of it: 23 | 24 | - version(version, required):Plugin's version 25 | - type(type, required):Plugin's type, currently only supports `plugin`, future support `bundle` 26 | - author(string, required):Author, it's the organization name in Marketplace and should also equals to the owner of the repository 27 | - label(label, required):Multi-language name 28 | - created_at(RFC3339, required):Creation time, Marketplace requires that the creation time must be less than the current time 29 | - icon(asset, required):Icon path 30 | - resource (object):Resources to be applied 31 | - memory (int64):Maximum memory usage, mainly related to resource application on SaaS for serverless, unit bytes 32 | - permission(object):Permission application 33 | - tool(object):Reverse call tool permission 34 | - enabled (bool) 35 | - model(object):Reverse call model permission 36 | - enabled(bool) 37 | - llm(bool) 38 | - text_embedding(bool) 39 | - rerank(bool) 40 | - tts(bool) 41 | - speech2text(bool) 42 | - moderation(bool) 43 | - node(object):Reverse call node permission 44 | - enabled(bool) 45 | - endpoint(object):Allow to register endpoint permission 46 | - enabled(bool) 47 | - app(object):Reverse call app permission 48 | - enabled(bool) 49 | - storage(object):Apply for persistent storage permission 50 | - enabled(bool) 51 | - size(int64):Maximum allowed persistent memory, unit bytes 52 | - plugins(object, required):Plugin extension specific ability yaml file list, absolute path in the plugin package, if you need to extend the model, you need to define a file like openai.yaml, and fill in the path here, and the file on the path must exist, otherwise the packaging will fail. 53 | - Format 54 | - tools(list[string]): Extended tool suppliers, as for the detailed format, please refer to [Tool Guide](https://docs.dify.ai/docs/plugins/standard/tool_provider) 55 | - models(list[string]):Extended model suppliers, as for the detailed format, please refer to [Model Guide](https://docs.dify.ai/docs/plugins/standard/model_provider) 56 | - endpoints(list[string]):Extended Endpoints suppliers, as for the detailed format, please refer to [Endpoint Guide](https://docs.dify.ai/docs/plugins/standard/endpoint_group) 57 | - Restrictions 58 | - Not allowed to extend both tools and models 59 | - Not allowed to have no extension 60 | - Not allowed to extend both models and endpoints 61 | - Currently only supports up to one supplier of each type of extension 62 | - meta(object) 63 | - version(version, required):manifest format version, initial version 0.0.1 64 | - arch(list[string], required):Supported architectures, currently only supports amd64 arm64 65 | - runner(object, required):Runtime configuration 66 | - language(string):Currently only supports python 67 | - version(string):Language version, currently only supports 3.12 68 | - entrypoint(string):Program entry, in python it should be main 69 | 70 | ### Install Dependencies 71 | 72 | - First of all, you need a Python 3.10+ environment, as our SDK requires that. 73 | - Then, install the dependencies: 74 | ```bash 75 | pip install -r requirements.txt 76 | ``` 77 | - If you want to add more dependencies, you can add them to the `requirements.txt` file, once you have set the runner to python in the `manifest.yaml` file, `requirements.txt` will be automatically generated and used for packaging and deployment. 78 | 79 | ### Implement the Plugin 80 | 81 | Now you can start to implement your Plugin, by following these examples, you can quickly understand how to implement your own Plugin: 82 | 83 | - [OpenAI](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/openai): best practice for model provider 84 | - [Google Search](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/google): a simple example for tool provider 85 | - [Neko](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko): a funny example for endpoint group 86 | 87 | ### Test and Debug the Plugin 88 | 89 | You may already noticed that a `.env.example` file in the root directory of your Plugin, just copy it to `.env` and fill in the corresponding values, there are some environment variables you need to set if you want to debug your Plugin locally. 90 | 91 | - `INSTALL_METHOD`: Set this to `remote`, your plugin will connect to a Dify instance through the network. 92 | - `REMOTE_INSTALL_HOST`: The host of your Dify instance, you can use our SaaS instance `https://debug.dify.ai`, or self-hosted Dify instance. 93 | - `REMOTE_INSTALL_PORT`: The port of your Dify instance, default is 5003 94 | - `REMOTE_INSTALL_KEY`: You should get your debugging key from the Dify instance you used, at the right top of the plugin management page, you can see a button with a `debug` icon, click it and you will get the key. 95 | 96 | Run the following command to start your Plugin: 97 | 98 | ```bash 99 | python -m main 100 | ``` 101 | 102 | Refresh the page of your Dify instance, you should be able to see your Plugin in the list now, but it will be marked as `debugging`, you can use it normally, but not recommended for production. 103 | 104 | ### Package the Plugin 105 | 106 | After all, just package your Plugin by running the following command: 107 | 108 | ```bash 109 | dify-plugin plugin package ./ROOT_DIRECTORY_OF_YOUR_PLUGIN 110 | ``` 111 | 112 | you will get a `plugin.difypkg` file, that's all, you can submit it to the Marketplace now, look forward to your Plugin being listed! 113 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | This tool is designed with privacy in mind and does not collect any user data. We are committed to maintaining your privacy and ensuring your data remains secure. 4 | 5 | ## Data Collection 6 | 7 | - **No Personal Information**: We do not collect, store, or process any personal information. 8 | - **No Usage Data**: We do not track or monitor how you use the tool. 9 | - **No Analytics**: We do not implement any analytics or tracking mechanisms. 10 | 11 | ## Third-Party Services 12 | 13 | This tool does not integrate with or utilize any third-party services that might collect user data. 14 | 15 | ## Changes to Privacy Policy 16 | 17 | If there are any changes to our privacy practices, we will update this document accordingly. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## database 2 | 3 | **Author:** hjlarry 4 | **Version:** 0.0.5 5 | **Type:** tool 6 | **Repo:** [https://github.com/hjlarry/dify-plugin-database](https://github.com/hjlarry/dify-plugin-database) 7 | **Feature Request:** [issues](https://github.com/hjlarry/dify-plugin-database/issues) 8 | **中文文档** [README_CN.md](https://github.com/hjlarry/dify-plugin-database/blob/main/README_CN.md) 9 | 10 | ### Be Carefull! 11 | **The sql_execute tool can run any SQL query; for enhanced security, always use a read-only database account.** 12 | 13 | ### Description 14 | 15 | A database tool make it easy to query data from existing databases. 16 | 17 | You can get different format of data, like `json`, `csv`, `yaml`, `xlsx`, `html`, `md` etc. Also support use a url to get those data. 18 | 19 | ### Usage 20 | 21 | #### 1. Input a databaseURI for Authorization. Now support `mysql`, `postgresql`, `sqlite`, `sqlserver`, `oracle`, example format: 22 | ```shell 23 | mysql+pymysql://root:123456@localhost:3306/test 24 | postgresql+psycopg2://postgres:123456@localhost:5432/test 25 | sqlite:///test.db 26 | mssql+pymssql://:@/?charset=utf8 27 | oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=[&key=value&key=value...]] 28 | ``` 29 | 30 | > **Note:**: this plugin always run in a docker, so the `localhost` always means docker internal network, try `host.docker.internal` instead. 31 | 32 | #### 2. Use the `SQL Execute` tool to query data from the database. 33 | ![sql](./_assets/sql_execute.png) 34 | The `OUTPUT FORMAT` is used to specify the format of the output data. If you don't specify it, the default format is `json` and will output in the `json` variable of workflow node. `md` will output in the `text` variable, other format will create file and output in the `files` variable. 35 | 36 | If you input the `DB URI` field, it will overwrite the default authorization uri, so this will be useful if you want to use different databases in the same workflow. 37 | 38 | #### 3. Use the `Text to SQL` tool can transform user input to a valid sql query. 39 | ![text](./_assets/text.png) 40 | 41 | This tool will use the default prompt [here](https://github.com/hjlarry/dify-plugin-database/blob/d6dd3695840e8eb5d673611784af148b1789da97/tools/text2sql.py#L9) to generate a sql query. If you specify the `TABLES` field, it will only get those tables' schema into the LLM context. 42 | 43 | #### 4. Use the `Get Table Schema` tool can get the schema of tables. 44 | 45 | If the `Text to SQL` tool can't generate a helpful sql query, you can use this tool to get the schema of tables, then use the schema orginze with your own prompt or other information to a LLM node to generate a helpful sql query. 46 | 47 | #### 5. The `CSV Query` tool can execute a sql query from a csv file. 48 | ![csv](./_assets/csv.png) 49 | The table name is always `csv`, the column name is the csv file's first line. It support output `json` and `md`. 50 | 51 | #### 6. Use the `endpoint` tool to get the data from a url request. 52 | 53 | example url request format: 54 | ```shell 55 | curl -X POST 'https://daemon-plugin.dify.dev/o3wvwZfYFLU5iGopr5CxYmGaM5mWV7xf/sql' -H 'Content-Type: application/json' -d '{"query":"select * from test", "format": "md"}' 56 | ``` 57 | 58 | 59 | ### Changelog 60 | 61 | #### 0.0.6 62 | 1. support get more table info of `get table schema` tool 63 | 2. support special `schema` of `get table schema` tool 64 | 65 | #### 0.0.5 66 | 1. support `with` in sql query 67 | 2. fix `text2sql` generate double quotes sql string 68 | 3. fix `too many clients already` 69 | 4. add connect option of sqlalchemy 70 | 5. change the `db_uri` of authorization to an optional field 71 | 6. add a `Get Table Schema` to directly response the schema of tables 72 | 7. add a `CSV Query` tool to execute a sql query from a csv file 73 | 8. fix `oracle` authorization can't run `select 1` 74 | 75 | #### 0.0.4 76 | 1. support `sqlserver`, `oracle` connection 77 | 2. change `db_url` to a llm format, so that user can use a environment variable of workflow to set the database uri 78 | 3. fix in a agent app, `sql_execute` tool only response the first result 79 | 4. migrate the table schema info of the `text2sql` tool to a user prompt, to prevent system prompt too long then response nothing 80 | 81 | #### 0.0.3 82 | 1. add `cryptography` to requirements.txt to support mysql 8.1 sha256 link 83 | 2. remove database uri setting of the endpoint 84 | 3. add a `db_uri` to support link to multiple databases 85 | 4. change the `output` of sql_execute tool to a form format 86 | 5. change the `tables` of text2sql tool to a llm format 87 | 6. fix sql query being converted to lowercase [issue](https://github.com/hjlarry/dify-plugin-database/issues/2) 88 | 89 | 90 | ### Contact 91 | ![1](_assets/contact.jpg) -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | ## 数据库 2 | 3 | **作者:** hjlarry 4 | **版本:** 0.0.6 5 | **类型:** 工具 6 | **仓库:** [https://github.com/hjlarry/dify-plugin-database](https://github.com/hjlarry/dify-plugin-database) 7 | **功能请求:** [issues](https://github.com/hjlarry/dify-plugin-database/issues) 8 | **FAQ:** [FAQ](FAQ.md) 9 | 10 | ### 请注意! 11 | **`sql_execute` 工具可以运行任何 SQL 查询;为了增强安全性,请始终使用只读数据库账户。** 12 | 13 | ### 描述 14 | 15 | 一个数据库工具,可以轻松地从现有数据库中查询数据。 16 | 17 | 您可以获取不同格式的数据,例如 `json`、`csv`、`yaml`、`xlsx`、`html`、`md` 等。还支持使用 URL 获取这些数据。 18 | 19 | ### 用法 20 | 21 | #### 1. 输入用于授权的 databaseURI。目前支持 `mysql`、`postgresql`、`sqlite`、`sqlserver`、`oracle`,示例格式如下: 22 | ```shell 23 | mysql+pymysql://root:123456@localhost:3306/test 24 | postgresql+psycopg2://postgres:123456@localhost:5432/test 25 | sqlite:///test.db 26 | mssql+pymssql://:@/?charset=utf8 27 | oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=[&key=value&key=value...]] 28 | ``` 29 | 30 | > **注意:** 此插件总是在 Docker 中运行,因此 `localhost` 始终指 Docker 内部网络,请尝试使用 `host.docker.internal` 代替。 31 | 32 | #### 2. 使用 `SQL 执行` (SQL Execute) 工具从数据库查询数据。 33 | ![sql](./_assets/sql_execute.png) 34 | `输出格式` (OUTPUT FORMAT) 用于指定输出数据的格式。如果未指定,默认格式为 `json`,并将输出在工作流节点的 `json` 变量中。`md` 格式将输出在 `text` 变量中,其他格式将创建文件并输出在 `files` 变量中。 35 | 36 | 如果您输入了 `数据库 URI` (DB URI) 字段,它将覆盖授权时填写的 URI,因此这在您想在同一工作流中使用不同数据库时非常有用。 37 | 38 | #### 3. 使用 `文本转 SQL` (Text to SQL) 工具可以将用户输入转换为有效的 SQL 查询。 39 | ![text](./_assets/text.png) 40 | 41 | 该工具将使用[此处](https://github.com/hjlarry/dify-plugin-database/blob/d6dd3695840e8eb5d673611784af148b1789da97/tools/text2sql.py#L9)的默认提示词(prompt)来生成 SQL 查询。如果您指定了 `表` (TABLES) 字段,它将仅将这些表的结构(schema)获取到 LLM 上下文中。 42 | 43 | #### 4. 使用 `获取表模式` (Get Table Schema) 工具可以获取表的结构(schema)。 44 | 45 | 如果 `文本转 SQL` 工具无法生成有用的 SQL 查询,您可以使用此工具获取表的结构(在text字段中返回),然后将该结构与您自己的提示词或其他信息结合,输入到 LLM 节点以生成有用的 SQL 查询。 46 | 47 | #### 5. `CSV 查询` (CSV Query) 工具可以对 CSV 文件执行 SQL 查询。 48 | ![csv](./_assets/csv.png) 49 | 表名始终为 `csv`,列名是 CSV 文件的第一行。它支持输出 `json` 和 `md` 格式。 50 | 51 | #### 6. 使用 `端点` (endpoint) 工具通过 URL 请求获取数据。 52 | 53 | URL 请求格式示例: 54 | ```shell 55 | curl -X POST 'https://daemon-plugin.dify.dev/o3wvwZfYFLU5iGopr5CxYmGaM5mWV7xf/sql' -H 'Content-Type: application/json' -d '{"query":"select * from test", "format": "md"}' 56 | ``` 57 | 58 | ### 更新日志 59 | 60 | #### 0.0.6 61 | 1. 支持在 `get table schema` 工具中获取更多信息,例如表和字段的注释、外键关联索引等 62 | 2. support special `schema` of `get table schema` tool 63 | 64 | #### 0.0.5 65 | 1. 支持 SQL 查询中的 `with` 语句 66 | 2. 修复 `text2sql` 生成带双引号的 SQL 字符串的问题 67 | 3. 修复 `too many clients already` (客户端连接过多) 的问题 68 | 4. 添加 SQLAlchemy 的连接选项 69 | 5. 将授权中的 `db_uri` 更改为可选字段 70 | 6. 添加 `获取表模式` (Get Table Schema) 工具以直接响应表的模式 71 | 7. 添加 `CSV 查询` (CSV Query) 工具以对 CSV 文件执行 SQL 查询 72 | 73 | #### 0.0.4 74 | 1. 支持 `sqlserver`、`oracle` 连接 75 | 2. 将 `db_url` 更改为 LLM 格式,以便用户可以使用工作流的环境变量来设置数据库 URI 76 | 3. 修复在 Agent 应用中,`sql_execute` 工具仅响应第一个结果的问题 77 | 4. 将 `text2sql` 工具的表模式信息迁移到用户提示词中,以防止系统提示词过长导致无响应 78 | 79 | #### 0.0.3 80 | 1. 在 requirements.txt 中添加 `cryptography` 以支持 MySQL 8.1 的 sha256 连接 81 | 2. 移除端点(endpoint)的数据库 URI 设置 82 | 3. 添加 `db_uri` 以支持连接到多个数据库 83 | 4. 将 `sql_execute` 工具的 `output` 更改为表单(form)格式 84 | 5. 将 `text2sql` 工具的 `tables` 更改为 LLM 格式 85 | 6. 修复 SQL 查询被转换为小写的问题 [issue](https://github.com/hjlarry/dify-plugin-database/issues/2) 86 | 87 | 88 | ### 加群 89 | ![1](_assets/contact.jpg) -------------------------------------------------------------------------------- /_assets/contact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjlarry/dify-plugin-database/e7ab8c35676f5bcb075b15085af89d06d5df722d/_assets/contact.jpg -------------------------------------------------------------------------------- /_assets/csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjlarry/dify-plugin-database/e7ab8c35676f5bcb075b15085af89d06d5df722d/_assets/csv.png -------------------------------------------------------------------------------- /_assets/icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | -------------------------------------------------------------------------------- /_assets/sql_execute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjlarry/dify-plugin-database/e7ab8c35676f5bcb075b15085af89d06d5df722d/_assets/sql_execute.png -------------------------------------------------------------------------------- /_assets/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjlarry/dify-plugin-database/e7ab8c35676f5bcb075b15085af89d06d5df722d/_assets/text.png -------------------------------------------------------------------------------- /endpoints/sql_execute.py: -------------------------------------------------------------------------------- 1 | import json 2 | from collections.abc import Mapping 3 | 4 | from flask import Request, Response 5 | from dify_plugin import Endpoint 6 | from dify_plugin.entities.tool import ToolInvokeMessage 7 | 8 | 9 | class SQLEndpoint(Endpoint): 10 | def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response: 11 | 12 | result = self.session.tool.invoke_builtin_tool( 13 | provider="hjlarry/database/database", 14 | tool_name="sql_execute", 15 | parameters=r.json, 16 | ) 17 | 18 | for message in result: 19 | if message.type == ToolInvokeMessage.MessageType.JSON: 20 | content = message.message.to_dict().get("json_object") 21 | return Response( 22 | json.dumps(content), 23 | status=200, 24 | content_type="application/json", 25 | ) 26 | elif message.type == ToolInvokeMessage.MessageType.BLOB: 27 | headers = { 28 | "Content-Type": message.meta.get( 29 | "mime_type", "application/octet-stream" 30 | ), 31 | "Content-Disposition": f'attachment; filename="{message.meta.get("filename")}"', 32 | } 33 | 34 | return Response(message.message.blob, status=200, headers=headers) 35 | else: 36 | # ToolInvokeMessage.MessageType.TEXT 37 | return Response(message.message.text, status=200) 38 | -------------------------------------------------------------------------------- /endpoints/sql_execute.yaml: -------------------------------------------------------------------------------- 1 | path: "/sql" 2 | method: "POST" 3 | extra: 4 | python: 5 | source: "endpoints/sql_execute.py" -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from dify_plugin import Plugin, DifyPluginEnv 2 | 3 | plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120)) 4 | 5 | if __name__ == "__main__": 6 | plugin.run() 7 | -------------------------------------------------------------------------------- /manifest.yaml: -------------------------------------------------------------------------------- 1 | version: 0.0.6 2 | type: plugin 3 | author: hjlarry 4 | name: database 5 | label: 6 | en_US: database 7 | ja_JP: database 8 | zh_Hans: database 9 | pt_BR: database 10 | description: 11 | en_US: This tool is used to execute SQL and text2sql in an existing database. 12 | zh_Hans: 此工具可以用来连接现有数据库,并执行SQL语句和自然语言转SQL。 13 | icon: icon.svg 14 | resource: 15 | memory: 268435456 16 | permission: 17 | tool: 18 | enabled: true 19 | model: 20 | enabled: true 21 | llm: true 22 | endpoint: 23 | enabled: true 24 | plugins: 25 | tools: 26 | - provider/database.yaml 27 | endpoints: 28 | - provider/endpoint.yaml 29 | meta: 30 | version: 0.0.1 31 | arch: 32 | - amd64 33 | - arm64 34 | runner: 35 | language: python 36 | version: "3.12" 37 | entrypoint: main 38 | created_at: 2024-12-24T14:34:18.7972424+08:00 39 | verified: false 40 | -------------------------------------------------------------------------------- /provider/database.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from dify_plugin import ToolProvider 4 | from dify_plugin.errors.tool import ToolProviderCredentialValidationError 5 | from tools.sql_execute import SQLExecuteTool 6 | 7 | 8 | class DatabaseProvider(ToolProvider): 9 | def _validate_credentials(self, credentials: dict[str, Any]) -> None: 10 | if not credentials.get("db_uri"): 11 | return 12 | query = "SELECT 1 FROM DUAL" if "oracle" in credentials.get("db_uri") else "SELECT 1" 13 | try: 14 | for _ in SQLExecuteTool.from_credentials(credentials).invoke( 15 | tool_parameters={"query": query} 16 | ): 17 | pass 18 | except Exception as e: 19 | raise ToolProviderCredentialValidationError(str(e)) 20 | -------------------------------------------------------------------------------- /provider/database.yaml: -------------------------------------------------------------------------------- 1 | identity: 2 | author: hjlarry 3 | name: database 4 | label: 5 | en_US: Database 6 | description: 7 | en_US: This tool is used to execute SQL in an existing database. 8 | zh_Hans: 此工具用于在已存在的数据库中执行 SQL 查询。 9 | icon: icon.svg 10 | tools: 11 | - tools/sql_execute.yaml 12 | - tools/text2sql.yaml 13 | - tools/table_schema.yaml 14 | - tools/csv_query.yaml 15 | extra: 16 | python: 17 | source: provider/database.py 18 | credentials_for_provider: 19 | db_uri: 20 | help: 21 | en_US: For example `mysql+pymysql://:@:/` 22 | zh_Hans: 例如 `mysql+pymysql://:@:/` 23 | label: 24 | en_US: Database URI 25 | zh_Hans: 数据库 URI 26 | placeholder: 27 | en_US: Please enter the database URI 28 | zh_Hans: 请输入数据库 URI 29 | required: false 30 | type: secret-input 31 | -------------------------------------------------------------------------------- /provider/endpoint.yaml: -------------------------------------------------------------------------------- 1 | settings: [] 2 | endpoints: 3 | - endpoints/sql_execute.yaml 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dify_plugin>=0.1.0,<0.2.0 2 | records[all]~=0.6.0 3 | pymysql~=1.1.1 4 | pymssql~=2.3.2 5 | oracledb~=2.2.1 6 | psycopg2-binary~=2.9.10 7 | cryptography~=44.0.2 8 | pandas~=2.2.3 -------------------------------------------------------------------------------- /test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjlarry/dify-plugin-database/e7ab8c35676f5bcb075b15085af89d06d5df722d/test.db -------------------------------------------------------------------------------- /tools/csv_query.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Generator 2 | from typing import Any 3 | import io 4 | 5 | import pandas as pd 6 | from sqlalchemy import create_engine 7 | from dify_plugin import Tool 8 | from dify_plugin.entities.tool import ToolInvokeMessage 9 | 10 | 11 | class CSVQueryTool(Tool): 12 | def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]: 13 | file = tool_parameters.get("file") 14 | if file.extension != ".csv": 15 | raise ValueError("Only CSV files are supported.") 16 | query = tool_parameters.get("query") 17 | df = pd.read_csv(io.BytesIO(file.blob)) 18 | engine = create_engine("sqlite:///csv.db") 19 | df.to_sql("csv", engine, index=False, if_exists='replace') 20 | format = tool_parameters.get("format") 21 | 22 | try: 23 | result_df = pd.read_sql_query(query, engine) 24 | 25 | if format == "json": 26 | json_data = result_df.to_dict(orient='records') 27 | yield self.create_json_message({"result": json_data}) 28 | else: 29 | markdown_table = ( 30 | "| " + " | ".join(result_df.columns) + " |\n" + 31 | "| " + " | ".join(["---"] * len(result_df.columns)) + " |\n" + 32 | "\n".join("| " + " | ".join(str(x) for x in row) + " |" 33 | for row in result_df.values) 34 | ) 35 | yield self.create_text_message(markdown_table) 36 | finally: 37 | engine.dispose() 38 | -------------------------------------------------------------------------------- /tools/csv_query.yaml: -------------------------------------------------------------------------------- 1 | identity: 2 | name: csv_query 3 | author: hjlarry 4 | label: 5 | en_US: CSV Query 6 | description: 7 | human: 8 | en_US: run sql query on csv file. 9 | zh_Hans: 运行在 CSV 文件上的 SQL 查询。 10 | llm: run sql query on csv file. 11 | parameters: 12 | - name: query 13 | type: string 14 | required: true 15 | label: 16 | en_US: Query string 17 | zh_Hans: 查询语句 18 | human_description: 19 | en_US: The sql query. The table name is `csv`. 20 | zh_Hans: SQL 查询语句。 表名为 `csv`。 21 | llm_description: The sql query. The table name is `csv`. 22 | form: llm 23 | - name: file 24 | type: file 25 | required: true 26 | label: 27 | en_US: csv file 28 | zh_Hans: csv 文件 29 | human_description: 30 | en_US: The csv file. 31 | zh_Hans: csv 文件。 32 | llm_description: The csv file. 33 | form: llm 34 | - name: format 35 | type: select 36 | required: false 37 | label: 38 | en_US: Output format 39 | zh_Hans: 输出格式 40 | human_description: 41 | en_US: Choose the output format. 42 | zh_Hans: 选择输出格式。 43 | form: form 44 | default: md 45 | options: 46 | - value: json 47 | label: 48 | en_US: JSON 49 | zh_Hans: JSON 50 | - value: md 51 | label: 52 | en_US: Markdown 53 | zh_Hans: Markdown 54 | extra: 55 | python: 56 | source: tools/csv_query.py 57 | -------------------------------------------------------------------------------- /tools/sql_execute.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Generator 2 | from typing import Any 3 | import re 4 | import json 5 | 6 | import records 7 | from sqlalchemy import text 8 | from dify_plugin import Tool 9 | from dify_plugin.entities.tool import ToolInvokeMessage 10 | 11 | 12 | class SQLExecuteTool(Tool): 13 | def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]: 14 | db_uri = tool_parameters.get("db_uri") or self.runtime.credentials.get("db_uri") 15 | if not db_uri: 16 | raise ValueError("Database URI is not provided.") 17 | query = tool_parameters.get("query").strip() 18 | format = tool_parameters.get("format", "json") 19 | config_options = tool_parameters.get("config_options") or "{}" 20 | try: 21 | config_options = json.loads(config_options) 22 | except json.JSONDecodeError: 23 | raise ValueError("Invalid JSON format for Connect Config") 24 | db = records.Database(db_uri, **config_options) 25 | 26 | try: 27 | if re.match(r'^\s*(SELECT|WITH)\s+', query, re.IGNORECASE): 28 | rows = db.query(query) 29 | if format == "json": 30 | result = rows.as_dict() 31 | yield self.create_json_message({"result": result}) 32 | elif format == "md": 33 | result = str(rows.dataset) 34 | yield self.create_text_message(result) 35 | elif format == "csv": 36 | result = rows.export("csv").encode() 37 | yield self.create_blob_message( 38 | result, meta={"mime_type": "text/csv", "filename": "result.csv"} 39 | ) 40 | elif format == "yaml": 41 | result = rows.export("yaml").encode() 42 | yield self.create_blob_message( 43 | result, 44 | meta={"mime_type": "text/yaml", "filename": "result.yaml"}, 45 | ) 46 | elif format == "xlsx": 47 | result = rows.export("xlsx") 48 | yield self.create_blob_message( 49 | result, 50 | meta={ 51 | "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 52 | "filename": "result.xlsx", 53 | }, 54 | ) 55 | elif format == "html": 56 | result = rows.export("html").encode() 57 | yield self.create_blob_message( 58 | result, 59 | meta={"mime_type": "text/html", "filename": "result.html"}, 60 | ) 61 | else: 62 | raise ValueError(f"Unsupported format: {format}") 63 | else: 64 | with db.get_connection() as conn: 65 | trans = conn._conn.begin() 66 | try: 67 | result = conn._conn.execute(text(query)) 68 | affected_rows = result.rowcount 69 | trans.commit() 70 | yield self.create_text_message( 71 | f"Query executed successfully. Affected rows: {affected_rows}" 72 | ) 73 | except Exception as e: 74 | trans.rollback() 75 | yield self.create_text_message(f"Error: {str(e)}") 76 | finally: 77 | db.close() 78 | -------------------------------------------------------------------------------- /tools/sql_execute.yaml: -------------------------------------------------------------------------------- 1 | identity: 2 | name: sql_execute 3 | author: hjlarry 4 | label: 5 | en_US: SQL Execute 6 | description: 7 | human: 8 | en_US: This tool is used to execute SQL in an existing database. 9 | zh_Hans: 此工具用于在已存在的数据库中执行 SQL 查询。 10 | llm: This tool is used to execute SQL in an existing database. 11 | parameters: 12 | - name: query 13 | type: string 14 | required: true 15 | label: 16 | en_US: SQL Query 17 | zh_Hans: SQL 查询语句 18 | human_description: 19 | en_US: The SQL query string. 20 | zh_Hans: SQL 查询语句。 21 | llm_description: The SQL query string. 22 | form: llm 23 | - name: db_uri 24 | type: string 25 | required: false 26 | label: 27 | en_US: DB URI 28 | human_description: 29 | en_US: Optional, Filling in this field will overwrite the database connection entered during authorization. 30 | zh_Hans: 选填,填写后将覆盖授权时填写的数据库连接。 31 | form: llm 32 | - name: format 33 | type: select 34 | required: false 35 | label: 36 | en_US: Output format 37 | zh_Hans: 输出格式 38 | human_description: 39 | en_US: Choose the output format. 40 | zh_Hans: 选择输出格式。 41 | form: form 42 | default: json 43 | options: 44 | - value: json 45 | label: 46 | en_US: JSON 47 | zh_Hans: JSON 48 | - value: csv 49 | label: 50 | en_US: CSV 51 | zh_Hans: CSV 52 | - value: yaml 53 | label: 54 | en_US: YAML 55 | zh_Hans: YAML 56 | - value: md 57 | label: 58 | en_US: Markdown 59 | zh_Hans: Markdown 60 | - value: xlsx 61 | label: 62 | en_US: Excel 63 | zh_Hans: Excel 64 | - value: html 65 | label: 66 | en_US: HTML 67 | zh_Hans: HTML 68 | - name: config_options 69 | type: string 70 | required: false 71 | form: form 72 | label: 73 | en_US: DB Config Options 74 | zh_Hans: 数据库连接配置选项 75 | human_description: 76 | en_US: | 77 | Optional. For example {"connect_args": {"ssl": {"ca": "/path/to/ca.pem"}}} 78 | More connection options can be found at https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine 79 | zh_Hans: | 80 | 选填项。例如 {"connect_args": {"ssl": {"ca": "/path/to/ca.pem"}}} 81 | 更多连接选项可参考 https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine 82 | extra: 83 | python: 84 | source: tools/sql_execute.py 85 | -------------------------------------------------------------------------------- /tools/table_schema.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Generator 2 | from typing import Any 3 | import json 4 | 5 | from sqlalchemy import create_engine, inspect 6 | from dify_plugin import Tool 7 | from dify_plugin.entities.tool import ToolInvokeMessage 8 | 9 | 10 | class QueryTool(Tool): 11 | def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]: 12 | db_uri = tool_parameters.get("db_uri") or self.runtime.credentials.get("db_uri") 13 | if not db_uri: 14 | raise ValueError("Database URI is not provided.") 15 | config_options = tool_parameters.get("config_options") or "{}" 16 | try: 17 | config_options = json.loads(config_options) 18 | except json.JSONDecodeError: 19 | raise ValueError("Invalid JSON format for Connect Config") 20 | engine = create_engine(db_uri, **config_options) 21 | inspector = inspect(engine) 22 | 23 | tables = tool_parameters.get("tables") 24 | schema = tool_parameters.get("schema") 25 | if not schema: 26 | # sometimes the schema is empty string, it must be None 27 | schema = None 28 | tables = tables.split(",") if tables else inspector.get_table_names(schema=schema) 29 | 30 | schema_info = {} 31 | with engine.connect() as _: 32 | for table_name in tables: 33 | # Basic table info 34 | table_info = { 35 | "table_name": table_name, 36 | "columns": [], 37 | "primary_keys": inspector.get_pk_constraint(table_name, schema=schema).get('constrained_columns', []), 38 | "foreign_keys": [], 39 | "indexes": [] 40 | } 41 | 42 | # Get table comment 43 | try: 44 | table_info["comment"] = inspector.get_table_comment(table_name, schema=schema).get('text', '') 45 | except NotImplementedError: 46 | table_info["comment"] = "" 47 | 48 | # Get foreign keys 49 | try: 50 | for fk in inspector.get_foreign_keys(table_name, schema=schema): 51 | table_info["foreign_keys"].append({ 52 | "referred_table": fk['referred_table'], 53 | "referred_columns": fk['referred_columns'], 54 | "constrained_columns": fk['constrained_columns'] 55 | }) 56 | except NotImplementedError: 57 | pass 58 | 59 | # Get indexes 60 | try: 61 | for idx in inspector.get_indexes(table_name, schema=schema): 62 | table_info["indexes"].append({ 63 | "name": idx['name'], 64 | "columns": idx['column_names'], 65 | "unique": idx['unique'] 66 | }) 67 | except NotImplementedError: 68 | pass 69 | 70 | # Get columns 71 | try: 72 | columns = inspector.get_columns(table_name, schema=schema) 73 | table_info["columns"] = [ 74 | { 75 | "name": col["name"], 76 | "type": str(col["type"]), 77 | "nullable": col.get("nullable", True), 78 | "default": col.get("default"), 79 | "comment": col.get("comment", ""), 80 | } 81 | for col in columns 82 | ] 83 | 84 | schema_info[table_name] = table_info 85 | except Exception as e: 86 | schema_info[table_name] = f"Error getting schema: {str(e)}" 87 | yield self.create_text_message(json.dumps(schema_info, ensure_ascii=False)) 88 | -------------------------------------------------------------------------------- /tools/table_schema.yaml: -------------------------------------------------------------------------------- 1 | identity: 2 | name: table_schema 3 | author: hjlarry 4 | label: 5 | en_US: Get Table Schema 6 | description: 7 | human: 8 | en_US: get the table schema from the database. 9 | zh_Hans: 从数据库中获取表结构。 10 | llm: get the table schema from the database. 11 | parameters: 12 | - name: tables 13 | type: string 14 | required: false 15 | label: 16 | en_US: Tables 17 | zh_Hans: 表 18 | human_description: 19 | en_US: Specify which tables schema you want to get. If not specified, all tables will be used. Multi tables use comma to separate. 20 | zh_Hans: 指定想要哪些表的结构,如不指定,则使用所有表。多张表用半角逗号隔开。 21 | llm_description: Specify which tables schema you want to get. If not specified, all tables will be used. Multi tables use comma to separate. 22 | form: llm 23 | - name: db_uri 24 | type: secret-input 25 | required: false 26 | label: 27 | en_US: DB URI 28 | human_description: 29 | en_US: Optional, Filling in this field will overwrite the database connection entered during authorization. 30 | zh_Hans: 选填,填写后将覆盖授权时填写的数据库连接。 31 | form: llm 32 | - name: config_options 33 | type: string 34 | required: false 35 | form: form 36 | label: 37 | en_US: DB Config Options 38 | zh_Hans: 数据库连接配置选项 39 | human_description: 40 | en_US: | 41 | Optional. For example {"connect_args": {"ssl": {"ca": "/path/to/ca.pem"}}} 42 | More connection options can be found at https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine 43 | zh_Hans: | 44 | 选填项。例如 {"connect_args": {"ssl": {"ca": "/path/to/ca.pem"}}} 45 | 更多连接选项可参考 https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine 46 | - name: schema 47 | type: string 48 | required: false 49 | form: form 50 | label: 51 | en_US: Schema 52 | human_description: 53 | en_US: Schema name. If `schema` is left empty, the database's default schema(like the `public` of postgresql) is used, else the named schema is searched 54 | zh_Hans: 数据库schema名称。如果 `schema` 留空,则使用数据库的默认schema(例如postgresql中的public),否则使用指定的schema。 55 | extra: 56 | python: 57 | source: tools/table_schema.py 58 | -------------------------------------------------------------------------------- /tools/text2sql.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Generator 2 | from typing import Any 3 | import json 4 | 5 | from sqlalchemy import create_engine, inspect 6 | from dify_plugin import Tool 7 | from dify_plugin.entities.tool import ToolInvokeMessage 8 | from dify_plugin.entities.model.message import SystemPromptMessage, UserPromptMessage 9 | 10 | SYSTEM_PROMPT_TEMPLATE = """ 11 | You are a {dialect} expert. Your task is to generate an executable {dialect} query based on the user's question. 12 | 13 | Requirements: 14 | 1. Generate a complete, executable {dialect} query that can be run directly 15 | 2. Query only necessary columns 16 | 3. Don't wrap column names in double quotes (") as delimited identifiers 17 | 4. Unless specified, limit results to 5 rows 18 | 5. Use date('now') for current date references 19 | 6. The response format should not include special characters like ```, \n, \", etc. 20 | 21 | Query Guidelines: 22 | - Ensure the query matches the exact {dialect} syntax 23 | - Only use columns that exist in the provided tables 24 | - Add appropriate table joins with correct join conditions 25 | - Include WHERE clauses to filter data as needed 26 | - Add ORDER BY when sorting is beneficial 27 | - Use appropriate data type casting 28 | 29 | Common Pitfalls to Avoid: 30 | - NULL handling in NOT IN clauses 31 | - UNION vs UNION ALL usage 32 | - Exclusive range conditions 33 | - Data type mismatches 34 | - Missing or incorrect quotes around identifiers 35 | - Wrong function arguments 36 | - Incorrect join conditions 37 | """ 38 | 39 | USER_PROMPT_TEMPLATE = """ 40 | Context and Tables: 41 | {table_info} 42 | 43 | Examples: 44 | User input: How many employees are there 45 | Your response: SELECT COUNT(*) FROM "Employee" 46 | 47 | User input: How many tracks are there in the album with ID 5? 48 | Your response: SELECT COUNT(*) FROM Track WHERE AlbumId = 5; 49 | 50 | User input: Which albums are from the year 2000? 51 | Your response: SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000'; 52 | 53 | User input: List all tracks in the 'Rock' genre. 54 | Your response: SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock'); 55 | 56 | 57 | Now, the user input is : {query} 58 | """ 59 | 60 | 61 | class QueryTool(Tool): 62 | def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]: 63 | db_uri = tool_parameters.get("db_uri") or self.runtime.credentials.get("db_uri") 64 | if not db_uri: 65 | raise ValueError("Database URI is not provided.") 66 | config_options = tool_parameters.get("config_options") or "{}" 67 | try: 68 | config_options = json.loads(config_options) 69 | except json.JSONDecodeError: 70 | raise ValueError("Invalid JSON format for Connect Config") 71 | engine = create_engine(db_uri, **config_options) 72 | inspector = inspect(engine) 73 | dialect = engine.dialect.name 74 | 75 | tables = tool_parameters.get("tables") 76 | tables = tables.split(",") if tables else inspector.get_table_names() 77 | 78 | schema_info = {} 79 | with engine.connect() as _: 80 | 81 | for table_name in tables: 82 | try: 83 | columns = inspector.get_columns(table_name) 84 | schema_info[table_name] = [ 85 | { 86 | "name": col["name"], 87 | "type": str(col["type"]), 88 | "nullable": col.get("nullable", True), 89 | "default": col.get("default"), 90 | "primary_key": col.get("primary_key", False), 91 | } 92 | for col in columns 93 | ] 94 | except Exception as e: 95 | schema_info[table_name] = f"Error getting schema: {str(e)}" 96 | prompt_messages = [ 97 | SystemPromptMessage(content=SYSTEM_PROMPT_TEMPLATE.format(dialect=dialect)), 98 | UserPromptMessage( 99 | content=USER_PROMPT_TEMPLATE.format( 100 | table_info=schema_info, query=tool_parameters.get("query") 101 | ) 102 | ), 103 | ] 104 | 105 | response = self.session.model.llm.invoke( 106 | model_config=tool_parameters.get("model"), 107 | prompt_messages=prompt_messages, 108 | stream=False, 109 | ) 110 | yield self.create_text_message(response.message.content) 111 | -------------------------------------------------------------------------------- /tools/text2sql.yaml: -------------------------------------------------------------------------------- 1 | identity: 2 | name: text2sql 3 | author: hjlarry 4 | label: 5 | en_US: Text to SQL 6 | description: 7 | human: 8 | en_US: supply to LLM the context of the database and the user query to get a sql string. 9 | zh_Hans: 提供数据库上下文和用户的问题,来得到一个 SQL 语句。 10 | llm: supply to LLM the context of the database and the user query to get a sql string. 11 | parameters: 12 | - name: query 13 | type: string 14 | required: true 15 | label: 16 | en_US: Query string 17 | zh_Hans: 查询语句 18 | human_description: 19 | en_US: The user query. 20 | zh_Hans: 用户的问题。 21 | llm_description: The user query. 22 | form: llm 23 | - name: tables 24 | type: string 25 | required: false 26 | label: 27 | en_US: Tables 28 | zh_Hans: 表 29 | human_description: 30 | en_US: Specify which tables to provide to LLM as context. If not specified, all tables will be used. Multi tables use comma to separate. 31 | zh_Hans: 指定哪些表用于提供给LLM作为上下文,如不指定,则使用所有表。多张表用半角逗号隔开。 32 | llm_description: Specify which tables schema do you want to get as model context. Multi tables use comma to separate. Leave it blank if you want to get all tables schema. 33 | form: llm 34 | - name: model 35 | type: model-selector 36 | scope: llm 37 | required: true 38 | label: 39 | en_US: Model 40 | zh_Hans: 模型 41 | human_description: 42 | en_US: Choose the model. 43 | zh_Hans: 选择模型。 44 | form: form 45 | - name: db_uri 46 | type: secret-input 47 | required: false 48 | label: 49 | en_US: DB URI 50 | human_description: 51 | en_US: Optional, Filling in this field will overwrite the database connection entered during authorization. 52 | zh_Hans: 选填,填写后将覆盖授权时填写的数据库连接。 53 | form: llm 54 | - name: config_options 55 | type: string 56 | required: false 57 | form: form 58 | label: 59 | en_US: DB Config Options 60 | zh_Hans: 数据库连接配置选项 61 | human_description: 62 | en_US: | 63 | Optional. For example {"connect_args": {"ssl": {"ca": "/path/to/ca.pem"}}} 64 | More connection options can be found at https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine 65 | zh_Hans: | 66 | 选填项。例如 {"connect_args": {"ssl": {"ca": "/path/to/ca.pem"}}} 67 | 更多连接选项可参考 https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine 68 | extra: 69 | python: 70 | source: tools/text2sql.py 71 | --------------------------------------------------------------------------------