From 56c73daf133dff4431441e3ed97425124f1ecc9d Mon Sep 17 00:00:00 2001 From: Vitaly <bikevit2008@gmail.com> Date: Sat, 25 Jan 2025 02:11:16 +0300 Subject: [PATCH 1/6] Relative WS path for comfyui By default now we can use links like abc.com/ but abc.com/foo/bar/comfy is not valid --- api/core/tools/provider/builtin/comfyui/comfyui.py | 3 ++- .../tools/provider/builtin/comfyui/tools/comfyui_client.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/core/tools/provider/builtin/comfyui/comfyui.py b/api/core/tools/provider/builtin/comfyui/comfyui.py index a8127dd23f1553..d2bb12d9a9fdd9 100644 --- a/api/core/tools/provider/builtin/comfyui/comfyui.py +++ b/api/core/tools/provider/builtin/comfyui/comfyui.py @@ -14,7 +14,8 @@ def _validate_credentials(self, credentials: dict[str, Any]) -> None: ws_protocol = "ws" if base_url.scheme == "https": ws_protocol = "wss" - ws_address = f"{ws_protocol}://{base_url.authority}/ws?clientId=test123" + base_path = str(base_url.path).rstrip('/') + ws_address = f"{ws_protocol}://{base_url.authority}{base_path}/ws?clientId=test123" try: ws.connect(ws_address) diff --git a/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py b/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py index 2bf10ce8ff2632..65dc1737798c2e 100644 --- a/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py +++ b/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py @@ -43,7 +43,8 @@ def open_websocket_connection(self) -> tuple[WebSocket, str]: ws_protocol = "ws" if self.base_url.scheme == "https": ws_protocol = "wss" - ws_address = f"{ws_protocol}://{self.base_url.authority}/ws?clientId={client_id}" + ws_url = self.base_url.with_scheme(ws_protocol).join(URL('ws')) + ws_address = str(ws_url.with_query({'clientId': client_id})) ws.connect(ws_address) return ws, client_id From f3915b695f74379c7f2e5d19ea1a9f4c0a6beace Mon Sep 17 00:00:00 2001 From: Vitaly <bikevit2008@gmail.com> Date: Sat, 25 Jan 2025 03:16:48 +0300 Subject: [PATCH 2/6] build from source for api in docker compose --- docker/docker-compose.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index a11ec261f3ac0d..37b74d16073a83 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -393,7 +393,9 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:0.15.2 + build: + dockerfile: ../api/Dockerfile + context: ../api restart: always environment: # Use the shared environment variables. @@ -416,7 +418,9 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.15.2 + build: + dockerfile: ../api/Dockerfile + context: ../api restart: always environment: # Use the shared environment variables. From f37c8720391b85c7640d7423f338a6e2e44f311e Mon Sep 17 00:00:00 2001 From: Vitaly <bikevit2008@gmail.com> Date: Tue, 28 Jan 2025 05:04:01 +0300 Subject: [PATCH 3/6] Linted --- api/core/tools/provider/builtin/comfyui/comfyui.py | 2 +- .../tools/provider/builtin/comfyui/tools/comfyui_client.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/core/tools/provider/builtin/comfyui/comfyui.py b/api/core/tools/provider/builtin/comfyui/comfyui.py index d2bb12d9a9fdd9..fac17db28afe4e 100644 --- a/api/core/tools/provider/builtin/comfyui/comfyui.py +++ b/api/core/tools/provider/builtin/comfyui/comfyui.py @@ -14,7 +14,7 @@ def _validate_credentials(self, credentials: dict[str, Any]) -> None: ws_protocol = "ws" if base_url.scheme == "https": ws_protocol = "wss" - base_path = str(base_url.path).rstrip('/') + base_path = str(base_url.path).rstrip("/") ws_address = f"{ws_protocol}://{base_url.authority}{base_path}/ws?clientId=test123" try: diff --git a/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py b/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py index 65dc1737798c2e..6cf9110d1f71b9 100644 --- a/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py +++ b/api/core/tools/provider/builtin/comfyui/tools/comfyui_client.py @@ -43,8 +43,8 @@ def open_websocket_connection(self) -> tuple[WebSocket, str]: ws_protocol = "ws" if self.base_url.scheme == "https": ws_protocol = "wss" - ws_url = self.base_url.with_scheme(ws_protocol).join(URL('ws')) - ws_address = str(ws_url.with_query({'clientId': client_id})) + ws_url = self.base_url.with_scheme(ws_protocol).join(URL("ws")) + ws_address = str(ws_url.with_query({"clientId": client_id})) ws.connect(ws_address) return ws, client_id From f96d84cf5b83d918c5685338045d117bf6ca220e Mon Sep 17 00:00:00 2001 From: Vitaly <bikevit2008@gmail.com> Date: Tue, 28 Jan 2025 16:51:16 +0300 Subject: [PATCH 4/6] Base64 encode tool --- api/constants/__init__.py | 33 +++------ .../tools/provider/builtin/_positions.yaml | 6 ++ .../builtin/url_to_base64/__init__.py | 3 + .../builtin/url_to_base64/_assets/icon.svg | 9 +++ .../tools/url_to_base64_converter.py | 73 +++++++++++++++++++ .../tools/url_to_base64_converter.yaml | 66 +++++++++++++++++ .../builtin/url_to_base64/url_to_base64.py | 22 ++++++ .../builtin/url_to_base64/url_to_base64.yaml | 16 ++++ .../url_to_base64/url_to_base64_tool.py | 45 ++++++++++++ 9 files changed, 252 insertions(+), 21 deletions(-) create mode 100644 api/core/tools/provider/builtin/_positions.yaml create mode 100644 api/core/tools/provider/builtin/url_to_base64/__init__.py create mode 100644 api/core/tools/provider/builtin/url_to_base64/_assets/icon.svg create mode 100644 api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py create mode 100644 api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.yaml create mode 100644 api/core/tools/provider/builtin/url_to_base64/url_to_base64.py create mode 100644 api/core/tools/provider/builtin/url_to_base64/url_to_base64.yaml create mode 100644 api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py diff --git a/api/constants/__init__.py b/api/constants/__init__.py index 4500ef4306fc2a..19bd11e03f695d 100644 --- a/api/constants/__init__.py +++ b/api/constants/__init__.py @@ -1,24 +1,15 @@ -from configs import dify_config +# File types and extensions +AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'm4a', 'aac', "ogg"] +DOCUMENT_EXTENSIONS = ['txt', 'pdf', 'doc', 'docx', 'csv', 'xls', 'xlsx'] +IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] +VIDEO_EXTENSIONS = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'] -HIDDEN_VALUE = "[__HIDDEN__]" -UUID_NIL = "00000000-0000-0000-0000-000000000000" - -IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "webp", "gif", "svg"] -IMAGE_EXTENSIONS.extend([ext.upper() for ext in IMAGE_EXTENSIONS]) - -VIDEO_EXTENSIONS = ["mp4", "mov", "mpeg", "mpga"] -VIDEO_EXTENSIONS.extend([ext.upper() for ext in VIDEO_EXTENSIONS]) +# File identifiers +DIFY_FILE_IDENTIFIER = '__dify__file__' -AUDIO_EXTENSIONS = ["mp3", "m4a", "wav", "webm", "amr"] -AUDIO_EXTENSIONS.extend([ext.upper() for ext in AUDIO_EXTENSIONS]) +# File transfer methods +LOCAL_FILE_TRANSFER = 'local_file' +REMOTE_URL_TRANSFER = 'remote_url' - -if dify_config.ETL_TYPE == "Unstructured": - DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls"] - DOCUMENT_EXTENSIONS.extend(("docx", "csv", "eml", "msg", "pptx", "xml", "epub")) - if dify_config.UNSTRUCTURED_API_URL: - DOCUMENT_EXTENSIONS.append("ppt") - DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) -else: - DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "docx", "csv"] - DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) +HIDDEN_VALUE = "[__HIDDEN__]" +UUID_NIL = "00000000-0000-0000-0000-000000000000" diff --git a/api/core/tools/provider/builtin/_positions.yaml b/api/core/tools/provider/builtin/_positions.yaml new file mode 100644 index 00000000000000..23cab8b6731b38 --- /dev/null +++ b/api/core/tools/provider/builtin/_positions.yaml @@ -0,0 +1,6 @@ +google: 1 +dalle: 2 +stable_diffusion: 3 +serpapi: 4 +wolfram: 5 +url_to_base64: 6 diff --git a/api/core/tools/provider/builtin/url_to_base64/__init__.py b/api/core/tools/provider/builtin/url_to_base64/__init__.py new file mode 100644 index 00000000000000..9fb37b71eb75f2 --- /dev/null +++ b/api/core/tools/provider/builtin/url_to_base64/__init__.py @@ -0,0 +1,3 @@ +from .url_to_base64 import URLToBase64Provider + +__all__ = ['URLToBase64Provider'] diff --git a/api/core/tools/provider/builtin/url_to_base64/_assets/icon.svg b/api/core/tools/provider/builtin/url_to_base64/_assets/icon.svg new file mode 100644 index 00000000000000..3901c1c7f0119e --- /dev/null +++ b/api/core/tools/provider/builtin/url_to_base64/_assets/icon.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"> + <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <rect fill="#4A90E2" x="0" y="0" width="32" height="32" rx="6"/> + <text font-family="Arial-BoldMT, Arial" font-size="12" font-weight="bold" fill="#FFFFFF"> + <tspan x="4" y="20">B64</tspan> + </text> + </g> +</svg> diff --git a/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py b/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py new file mode 100644 index 00000000000000..f8095ac6a1c087 --- /dev/null +++ b/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py @@ -0,0 +1,73 @@ +from typing import Any, Dict, List, Union +import base64 +import requests +import json +import logging + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool +from core.tools.tool_file_manager import ToolFileManager +from extensions.ext_storage import storage +from core.file.models import File +from core.file.file_manager import download + +logger = logging.getLogger(__name__) + +class URLToBase64Converter(BuiltinTool): + def _invoke(self, + user_id: str, + tool_parameters: Dict[str, Any], + ) -> ToolInvokeMessage: + """ + Конвертирует файл в base64 строку. + Поддерживает как внешние URL, так и локальные файлы Dify. + """ + logger.info(f"Received parameters: {tool_parameters}") + + # Получаем источник файла + file_source = tool_parameters.get('file_source') + if not file_source: + return self.create_text_message('Please specify file source (url or local)') + + try: + # Обработка внешнего URL + if file_source == 'url': + url = tool_parameters.get('url') + if not url: + return self.create_text_message('Please provide a URL') + + response = requests.get(url) + response.raise_for_status() + content = response.content + + # Обработка локального файла Dify + elif file_source == 'local': + file = tool_parameters.get('file') + logger.info(f"File data: {file}") + + if not file: + return self.create_text_message('Please provide a file') + + if not isinstance(file, File): + logger.error(f"Invalid file type: {type(file)}") + return self.create_text_message('Invalid file type. Expected Dify File object') + + # Загружаем содержимое файла + content = download(file) + if not content: + logger.error("Failed to download file content") + return self.create_text_message('Failed to download file content') + + logger.info(f"Successfully loaded file: {len(content)} bytes") + + else: + return self.create_text_message('Invalid file source. Use "url" or "local"') + + # Конвертируем содержимое в base64 + base64_content = base64.b64encode(content).decode('utf-8') + logger.info(f"Successfully converted file to base64: {len(base64_content)} characters") + return self.create_text_message(base64_content) + + except Exception as e: + logger.exception("Error processing file") + return self.create_text_message(f'Error processing file: {str(e)}') diff --git a/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.yaml b/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.yaml new file mode 100644 index 00000000000000..09c3b37781184c --- /dev/null +++ b/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.yaml @@ -0,0 +1,66 @@ +identity: + name: url_to_base64 + author: vitalijsatilov + label: + en_US: URL to Base64 + zh_Hans: URL转Base64 + ru_RU: URL в Base64 + +description: + human: + en_US: Convert URL or local file to base64 string + zh_Hans: 将URL或本地文件转换为base64字符串 + ru_RU: Конвертировать URL или локальный файл в строку base64 + llm: Convert a file from URL or local storage to base64 string format + +parameters: + - name: file_source + type: string + required: true + label: + en_US: File Source + zh_Hans: 文件来源 + ru_RU: Источник файла + human_description: + en_US: Source of the file (url or local) + zh_Hans: 文件来源(url或本地) + ru_RU: Источник файла (url или локальный) + llm_description: Specify the source of the file. Use "url" for external URLs or "local" for files from Dify storage. + options: + - value: url + label: + en_US: URL + zh_Hans: URL + ru_RU: URL + - value: local + label: + en_US: Local File + zh_Hans: 本地文件 + ru_RU: Локальный файл + form: llm + - name: url + type: string + required: false + label: + en_US: URL + zh_Hans: URL + ru_RU: URL + human_description: + en_US: URL of the file to convert (required if file_source is "url") + zh_Hans: 要转换的文件的URL(当file_source为"url"时必填) + ru_RU: URL файла для конвертации (обязательно если file_source - "url") + llm_description: URL of the file to convert to base64. Only required when file_source is "url". + form: llm + - name: file + type: file + required: false + label: + en_US: File + zh_Hans: 文件 + ru_RU: Файл + human_description: + en_US: Local file to convert (required if file_source is "local") + zh_Hans: 要转换的本地文件(当file_source为"local"时必填) + ru_RU: Локальный файл для конвертации (обязательно если file_source - "local") + llm_description: Local file to convert to base64. Only required when file_source is "local". + form: llm diff --git a/api/core/tools/provider/builtin/url_to_base64/url_to_base64.py b/api/core/tools/provider/builtin/url_to_base64/url_to_base64.py new file mode 100644 index 00000000000000..d048f2cb225574 --- /dev/null +++ b/api/core/tools/provider/builtin/url_to_base64/url_to_base64.py @@ -0,0 +1,22 @@ +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController +from core.tools.provider.builtin.url_to_base64.tools.url_to_base64_converter import URLToBase64Converter + +class URLToBase64Provider(BuiltinToolProviderController): + @property + def need_credentials(self) -> bool: + """ + Whether the provider needs credentials + """ + return False + + def _tools(self) -> list: + return [ + URLToBase64Converter(), + ] + + def _validate_credentials(self, credentials: dict) -> bool: + """ + Validate the credentials. + Our tool doesn't require any credentials, so we always return True. + """ + return True diff --git a/api/core/tools/provider/builtin/url_to_base64/url_to_base64.yaml b/api/core/tools/provider/builtin/url_to_base64/url_to_base64.yaml new file mode 100644 index 00000000000000..aa7bf17c7b745b --- /dev/null +++ b/api/core/tools/provider/builtin/url_to_base64/url_to_base64.yaml @@ -0,0 +1,16 @@ +identity: + author: bikevit2008 + name: url_to_base64 + label: + en_US: URL to Base64 + ru_RU: URL в Base64 + zh_Hans: URL 转 Base64 + description: + en_US: A tool for converting files from URL to base64 string format + ru_RU: Инструмент для конвертации файлов из URL в формат base64 + zh_Hans: 将 URL 文件转换为 base64 字符串的工具 + icon: icon.svg + tags: + - utilities + +credentials_for_provider: {} diff --git a/api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py b/api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py new file mode 100644 index 00000000000000..ecb2b251c7d9db --- /dev/null +++ b/api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py @@ -0,0 +1,45 @@ +from typing import Any, Dict, List, Union +import base64 +import requests + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + +class URLToBase64Tool(BuiltinTool): + def _invoke(self, + user_id: str, + tool_parameters: Dict[str, Any], + ) -> Union[ToolInvokeMessage, List[ToolInvokeMessage]]: + """ + Конвертирует файл из URL в base64 строку + """ + # Получаем URL из параметров + url = tool_parameters.get('url', '') + if not url: + return self.create_text_message('Пожалуйста, укажите URL') + + try: + # Загружаем файл по URL + response = requests.get(url) + response.raise_for_status() # Проверяем на ошибки + + # Конвертируем содержимое в base64 + base64_content = base64.b64encode(response.content).decode('utf-8') + + # Возвращаем результат + return self.create_text_message(base64_content) + + except Exception as e: + return self.create_text_message(f'Ошибка при обработке файла: {str(e)}') + + def get_runtime_parameters(self) -> List[Dict]: + """ + Определяем параметры инструмента + """ + return [{ + "name": "url", + "type": "string", + "required": True, + "label": "URL файла", + "description": "URL файла, который нужно конвертировать в base64" + }] From 7b1d1126d954f655da337e8407bb1a46f6828624 Mon Sep 17 00:00:00 2001 From: Vitaly <bikevit2008@gmail.com> Date: Wed, 29 Jan 2025 01:35:24 +0300 Subject: [PATCH 5/6] Fixed mime type -> extension file by HTTP request --- api/constants/__init__.py | 30 +++++++++++++++++++++++++---- api/core/tools/tool_file_manager.py | 27 ++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/api/constants/__init__.py b/api/constants/__init__.py index 19bd11e03f695d..229c7400fb4444 100644 --- a/api/constants/__init__.py +++ b/api/constants/__init__.py @@ -1,9 +1,21 @@ +from configs import dify_config + # File types and extensions -AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'm4a', 'aac', "ogg"] -DOCUMENT_EXTENSIONS = ['txt', 'pdf', 'doc', 'docx', 'csv', 'xls', 'xlsx'] -IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] -VIDEO_EXTENSIONS = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'] +AUDIO_EXTENSIONS = ["mp3", "m4a", "wav", "webm", "amr", "ogg"] +AUDIO_EXTENSIONS.extend([ext.upper() for ext in AUDIO_EXTENSIONS]) + +IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "webp", "gif", "svg"] +IMAGE_EXTENSIONS.extend([ext.upper() for ext in IMAGE_EXTENSIONS]) +VIDEO_EXTENSIONS = ["mp4", "mov", "mpeg", "mpga"] +VIDEO_EXTENSIONS.extend([ext.upper() for ext in VIDEO_EXTENSIONS]) + +MIME_TO_EXTENSION = { + 'audio/wav': '.wav', + 'audio/x-wav': '.wav', + 'audio/wave': '.wav', + 'audio/x-pn-wav': '.wav', +} # File identifiers DIFY_FILE_IDENTIFIER = '__dify__file__' @@ -11,5 +23,15 @@ LOCAL_FILE_TRANSFER = 'local_file' REMOTE_URL_TRANSFER = 'remote_url' +if dify_config.ETL_TYPE == "Unstructured": + DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls"] + DOCUMENT_EXTENSIONS.extend(("docx", "csv", "eml", "msg", "pptx", "xml", "epub")) + if dify_config.UNSTRUCTURED_API_URL: + DOCUMENT_EXTENSIONS.append("ppt") + DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) +else: + DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "docx", "csv"] + DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) + HIDDEN_VALUE = "[__HIDDEN__]" UUID_NIL = "00000000-0000-0000-0000-000000000000" diff --git a/api/core/tools/tool_file_manager.py b/api/core/tools/tool_file_manager.py index 2aaca6d82e36b1..92a28c6bfe6600 100644 --- a/api/core/tools/tool_file_manager.py +++ b/api/core/tools/tool_file_manager.py @@ -16,6 +16,7 @@ from extensions.ext_storage import storage from models.model import MessageFile from models.tools import ToolFile +from constants import MIME_TO_EXTENSION logger = logging.getLogger(__name__) @@ -64,7 +65,18 @@ def create_file_by_raw( file_binary: bytes, mimetype: str, ) -> ToolFile: - extension = guess_extension(mimetype) or ".bin" + logger.info(f"Creating file with mimetype: {mimetype}") + + # Проверяем наше сопоставление MIME-типов + extension = MIME_TO_EXTENSION.get(mimetype) + if not extension: + # Если нет в нашем сопоставлении, пробуем стандартный способ + extension = guess_extension(mimetype) + if not extension: + logger.warning(f"Could not determine extension for mimetype {mimetype}, using .bin") + extension = ".bin" + + logger.info(f"Using extension: {extension}") unique_name = uuid4().hex filename = f"{unique_name}{extension}" filepath = f"tools/{tenant_id}/{filename}" @@ -102,7 +114,18 @@ def create_file_by_url( raise ValueError(f"timeout when downloading file from {file_url}") mimetype = guess_type(file_url)[0] or "octet/stream" - extension = guess_extension(mimetype) or ".bin" + logger.info(f"Creating file from URL with mimetype: {mimetype}") + + # Проверяем наше сопоставление MIME-типов + extension = MIME_TO_EXTENSION.get(mimetype) + if not extension: + # Если нет в нашем сопоставлении, пробуем стандартный способ + extension = guess_extension(mimetype) + if not extension: + logger.warning(f"Could not determine extension for mimetype {mimetype}, using .bin") + extension = ".bin" + + logger.info(f"Using extension: {extension}") unique_name = uuid4().hex filename = f"{unique_name}{extension}" filepath = f"tools/{tenant_id}/{filename}" From f88d7f658148199e9e7d0ec891db21b286e78c5f Mon Sep 17 00:00:00 2001 From: Vitaly <bikevit2008@gmail.com> Date: Wed, 29 Jan 2025 01:41:44 +0300 Subject: [PATCH 6/6] Linted success --- api/constants/__init__.py | 14 ++-- .../builtin/url_to_base64/__init__.py | 2 +- .../tools/url_to_base64_converter.py | 66 +++++++++---------- .../builtin/url_to_base64/url_to_base64.py | 5 +- .../url_to_base64/url_to_base64_tool.py | 46 +++++++------ api/core/tools/tool_file_manager.py | 10 +-- 6 files changed, 74 insertions(+), 69 deletions(-) diff --git a/api/constants/__init__.py b/api/constants/__init__.py index 229c7400fb4444..2872d00de92ac9 100644 --- a/api/constants/__init__.py +++ b/api/constants/__init__.py @@ -11,17 +11,17 @@ VIDEO_EXTENSIONS.extend([ext.upper() for ext in VIDEO_EXTENSIONS]) MIME_TO_EXTENSION = { - 'audio/wav': '.wav', - 'audio/x-wav': '.wav', - 'audio/wave': '.wav', - 'audio/x-pn-wav': '.wav', + "audio/wav": ".wav", + "audio/x-wav": ".wav", + "audio/wave": ".wav", + "audio/x-pn-wav": ".wav", } # File identifiers -DIFY_FILE_IDENTIFIER = '__dify__file__' +DIFY_FILE_IDENTIFIER = "__dify__file__" # File transfer methods -LOCAL_FILE_TRANSFER = 'local_file' -REMOTE_URL_TRANSFER = 'remote_url' +LOCAL_FILE_TRANSFER = "local_file" +REMOTE_URL_TRANSFER = "remote_url" if dify_config.ETL_TYPE == "Unstructured": DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls"] diff --git a/api/core/tools/provider/builtin/url_to_base64/__init__.py b/api/core/tools/provider/builtin/url_to_base64/__init__.py index 9fb37b71eb75f2..6a8f5685e91dc4 100644 --- a/api/core/tools/provider/builtin/url_to_base64/__init__.py +++ b/api/core/tools/provider/builtin/url_to_base64/__init__.py @@ -1,3 +1,3 @@ from .url_to_base64 import URLToBase64Provider -__all__ = ['URLToBase64Provider'] +__all__ = ["URLToBase64Provider"] diff --git a/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py b/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py index f8095ac6a1c087..df6abd2c24b145 100644 --- a/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py +++ b/api/core/tools/provider/builtin/url_to_base64/tools/url_to_base64_converter.py @@ -1,73 +1,73 @@ -from typing import Any, Dict, List, Union import base64 -import requests -import json import logging +from typing import Any + +import requests +from core.file.file_manager import download +from core.file.models import File from core.tools.entities.tool_entities import ToolInvokeMessage from core.tools.tool.builtin_tool import BuiltinTool -from core.tools.tool_file_manager import ToolFileManager -from extensions.ext_storage import storage -from core.file.models import File -from core.file.file_manager import download logger = logging.getLogger(__name__) + class URLToBase64Converter(BuiltinTool): - def _invoke(self, - user_id: str, - tool_parameters: Dict[str, Any], - ) -> ToolInvokeMessage: + def _invoke( + self, + user_id: str, + tool_parameters: dict[str, Any], + ) -> ToolInvokeMessage: """ Конвертирует файл в base64 строку. Поддерживает как внешние URL, так и локальные файлы Dify. """ logger.info(f"Received parameters: {tool_parameters}") - + # Получаем источник файла - file_source = tool_parameters.get('file_source') + file_source = tool_parameters.get("file_source") if not file_source: - return self.create_text_message('Please specify file source (url or local)') + return self.create_text_message("Please specify file source (url or local)") try: # Обработка внешнего URL - if file_source == 'url': - url = tool_parameters.get('url') + if file_source == "url": + url = tool_parameters.get("url") if not url: - return self.create_text_message('Please provide a URL') - + return self.create_text_message("Please provide a URL") + response = requests.get(url) response.raise_for_status() content = response.content - + # Обработка локального файла Dify - elif file_source == 'local': - file = tool_parameters.get('file') + elif file_source == "local": + file = tool_parameters.get("file") logger.info(f"File data: {file}") - + if not file: - return self.create_text_message('Please provide a file') - + return self.create_text_message("Please provide a file") + if not isinstance(file, File): logger.error(f"Invalid file type: {type(file)}") - return self.create_text_message('Invalid file type. Expected Dify File object') - + return self.create_text_message("Invalid file type. Expected Dify File object") + # Загружаем содержимое файла content = download(file) if not content: logger.error("Failed to download file content") - return self.create_text_message('Failed to download file content') - + return self.create_text_message("Failed to download file content") + logger.info(f"Successfully loaded file: {len(content)} bytes") - + else: return self.create_text_message('Invalid file source. Use "url" or "local"') - + # Конвертируем содержимое в base64 - base64_content = base64.b64encode(content).decode('utf-8') + base64_content = base64.b64encode(content).decode("utf-8") logger.info(f"Successfully converted file to base64: {len(base64_content)} characters") return self.create_text_message(base64_content) - + except Exception as e: logger.exception("Error processing file") - return self.create_text_message(f'Error processing file: {str(e)}') + return self.create_text_message(f"Error processing file: {str(e)}") diff --git a/api/core/tools/provider/builtin/url_to_base64/url_to_base64.py b/api/core/tools/provider/builtin/url_to_base64/url_to_base64.py index d048f2cb225574..bc4d5ece873c26 100644 --- a/api/core/tools/provider/builtin/url_to_base64/url_to_base64.py +++ b/api/core/tools/provider/builtin/url_to_base64/url_to_base64.py @@ -1,5 +1,6 @@ -from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController from core.tools.provider.builtin.url_to_base64.tools.url_to_base64_converter import URLToBase64Converter +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController + class URLToBase64Provider(BuiltinToolProviderController): @property @@ -13,7 +14,7 @@ def _tools(self) -> list: return [ URLToBase64Converter(), ] - + def _validate_credentials(self, credentials: dict) -> bool: """ Validate the credentials. diff --git a/api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py b/api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py index ecb2b251c7d9db..9bfc359593185d 100644 --- a/api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py +++ b/api/core/tools/provider/builtin/url_to_base64/url_to_base64_tool.py @@ -1,45 +1,49 @@ -from typing import Any, Dict, List, Union import base64 +from typing import Any, Union + import requests from core.tools.entities.tool_entities import ToolInvokeMessage from core.tools.tool.builtin_tool import BuiltinTool + class URLToBase64Tool(BuiltinTool): - def _invoke(self, - user_id: str, - tool_parameters: Dict[str, Any], - ) -> Union[ToolInvokeMessage, List[ToolInvokeMessage]]: + def _invoke( + self, + user_id: str, + tool_parameters: dict[str, Any], + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: """ Конвертирует файл из URL в base64 строку """ - # Получаем URL из параметров - url = tool_parameters.get('url', '') + url = tool_parameters.get("url", "") if not url: - return self.create_text_message('Пожалуйста, укажите URL') + return self.create_text_message("Пожалуйста, укажите URL") try: # Загружаем файл по URL response = requests.get(url) response.raise_for_status() # Проверяем на ошибки - + # Конвертируем содержимое в base64 - base64_content = base64.b64encode(response.content).decode('utf-8') - + base64_content = base64.b64encode(response.content).decode("utf-8") + # Возвращаем результат return self.create_text_message(base64_content) - + except Exception as e: - return self.create_text_message(f'Ошибка при обработке файла: {str(e)}') + return self.create_text_message(f"Ошибка при обработке файла: {str(e)}") - def get_runtime_parameters(self) -> List[Dict]: + def get_runtime_parameters(self) -> list[dict]: """ Определяем параметры инструмента """ - return [{ - "name": "url", - "type": "string", - "required": True, - "label": "URL файла", - "description": "URL файла, который нужно конвертировать в base64" - }] + return [ + { + "name": "url", + "type": "string", + "required": True, + "label": "URL файла", + "description": "URL файла, который нужно конвертировать в base64", + } + ] diff --git a/api/core/tools/tool_file_manager.py b/api/core/tools/tool_file_manager.py index 92a28c6bfe6600..3c649d8f8c5722 100644 --- a/api/core/tools/tool_file_manager.py +++ b/api/core/tools/tool_file_manager.py @@ -11,12 +11,12 @@ import httpx from configs import dify_config +from constants import MIME_TO_EXTENSION from core.helper import ssrf_proxy from extensions.ext_database import db from extensions.ext_storage import storage from models.model import MessageFile from models.tools import ToolFile -from constants import MIME_TO_EXTENSION logger = logging.getLogger(__name__) @@ -66,7 +66,7 @@ def create_file_by_raw( mimetype: str, ) -> ToolFile: logger.info(f"Creating file with mimetype: {mimetype}") - + # Проверяем наше сопоставление MIME-типов extension = MIME_TO_EXTENSION.get(mimetype) if not extension: @@ -75,7 +75,7 @@ def create_file_by_raw( if not extension: logger.warning(f"Could not determine extension for mimetype {mimetype}, using .bin") extension = ".bin" - + logger.info(f"Using extension: {extension}") unique_name = uuid4().hex filename = f"{unique_name}{extension}" @@ -115,7 +115,7 @@ def create_file_by_url( mimetype = guess_type(file_url)[0] or "octet/stream" logger.info(f"Creating file from URL with mimetype: {mimetype}") - + # Проверяем наше сопоставление MIME-типов extension = MIME_TO_EXTENSION.get(mimetype) if not extension: @@ -124,7 +124,7 @@ def create_file_by_url( if not extension: logger.warning(f"Could not determine extension for mimetype {mimetype}, using .bin") extension = ".bin" - + logger.info(f"Using extension: {extension}") unique_name = uuid4().hex filename = f"{unique_name}{extension}"