Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/enhance the multi-modal support #8818

Merged
merged 37 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0eb53a2
feat(api): Enhance multi modal support.
laipz8200 Sep 1, 2024
b39ede9
feat(podcast_generator): add new podcast generation tools
laipz8200 Oct 12, 2024
bfdbbe3
fix(workflow): handle special values for process data consistently
laipz8200 Oct 14, 2024
1bad1da
refactor(prompt): improve handling of variable templates in advanced …
laipz8200 Oct 14, 2024
9305cb9
refactor(core): simplify role handling and improve usability
laipz8200 Oct 15, 2024
a6513c6
fix(memory): filter non-image file types in prompt message content
laipz8200 Oct 15, 2024
fb64397
refactor(core): improve type annotations and file handling consistency
laipz8200 Oct 15, 2024
5da907a
refactor(http_executor): improve HTTP request logging format
laipz8200 Oct 16, 2024
c9f4622
refactor(workflow): improve handling of outputs and inputs
laipz8200 Oct 16, 2024
bca8163
refactor(workflow): rename and restructure list filter components
laipz8200 Oct 16, 2024
849602f
refactor(workflow/nodes): unify filtering and ordering under a single…
laipz8200 Oct 16, 2024
7fdb1d7
refactor(api): enhance file streaming response handling
laipz8200 Oct 16, 2024
d6ca3b6
fix(file-serving): adjust content disposition header for images
laipz8200 Oct 16, 2024
587751b
refactor(tool_node): simplify tool_file_id assignment
laipz8200 Oct 16, 2024
6dbbad7
feat(podcast_audio_generator): improve audio generation workflow
laipz8200 Oct 17, 2024
7f156d9
fix(podcast_audio_generator): remove explicit format specification fo…
laipz8200 Oct 17, 2024
6854491
fix(podcast_audio_generator): specify audio format for file loading
laipz8200 Oct 17, 2024
740d2c8
fix(audio-generation): simplify audio response handling
laipz8200 Oct 17, 2024
849a2ac
feat(file-download): add inline or attachment option for file previews
laipz8200 Oct 17, 2024
900f5f4
fix(tool_files): correct typo in default argument for as_attachment
laipz8200 Oct 17, 2024
e04fcee
fix(tool_files): adjust argument order for as_attachment in parser
laipz8200 Oct 17, 2024
0c2c4c3
refactor(api): update file preview handling and support for audio files
laipz8200 Oct 18, 2024
df1d607
chore(dependencies): update openai package to version 1.52.0
laipz8200 Oct 18, 2024
05bcb6c
chore(dependencies): update openai version constraint
laipz8200 Oct 18, 2024
6ff72e6
chore(dependencies): update pydub version constraint and regenerate l…
laipz8200 Oct 18, 2024
1c1ff05
refactor(tests): improve unit test setup for prompt and document extr…
laipz8200 Oct 20, 2024
33d7eb9
refactor(constants): adjust document extension handling
laipz8200 Oct 20, 2024
69c3267
refactor(document_extractor): update module structure
laipz8200 Oct 20, 2024
8e49ad5
refactor(http_request): streamline HTTP request handling
laipz8200 Oct 20, 2024
2c799a2
refactor(http_request_node): reorganize imports for better readability
laipz8200 Oct 20, 2024
f1d2215
refactor(workflow): reorganize BaseNode import paths
laipz8200 Oct 20, 2024
40b97b0
refactor(workflow): consolidate BaseNodeData and related entities
laipz8200 Oct 20, 2024
d707c5e
delete(api): remove unused BaseIterationState entity
laipz8200 Oct 20, 2024
5cbc375
refactor(workflow): consolidate LLM-related imports and update events
laipz8200 Oct 20, 2024
650581e
refactor(nodes): restructure imports and organization
laipz8200 Oct 20, 2024
1233a5e
refactor(workflow): simplify node type mapping and initialization
laipz8200 Oct 20, 2024
fe47ecd
refactor(models): relocate and update enums usage
laipz8200 Oct 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(http_request): streamline HTTP request handling
- Renamed `HttpExecutor` to `Executor` for consistency.
- Unified `Response` handling by moving common logic to a single `Response` class to avoid redundancy.
- Simplified imports and file structure for clearer organization.
- Updated tests and references to reflect changes in class and module names.
  • Loading branch information
laipz8200 committed Oct 21, 2024
commit 8e49ad5a085a4ebf14a9764e8a555b61e0213a68
2 changes: 1 addition & 1 deletion api/core/workflow/nodes/http_request/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .entities import BodyData, HttpRequestNodeAuthorization, HttpRequestNodeBody, HttpRequestNodeData
from .http_request_node import HttpRequestNode
from .node import HttpRequestNode

__all__ = ["HttpRequestNodeData", "HttpRequestNodeAuthorization", "HttpRequestNodeBody", "BodyData", "HttpRequestNode"]
57 changes: 57 additions & 0 deletions api/core/workflow/nodes/http_request/entities.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
from collections.abc import Sequence
from typing import Literal, Optional

import httpx
from pydantic import BaseModel, Field, ValidationInfo, field_validator

from configs import dify_config
from core.workflow.entities.base_node_data_entities import BaseNodeData

NON_FILE_CONTENT_TYPES = (
"application/json",
"application/xml",
"text/html",
"text/plain",
"application/x-www-form-urlencoded",
)


class HttpRequestNodeAuthorizationConfig(BaseModel):
type: Literal["basic", "bearer", "custom"]
Expand Down Expand Up @@ -62,3 +71,51 @@ class HttpRequestNodeData(BaseNodeData):
params: str
body: Optional[HttpRequestNodeBody] = None
timeout: Optional[HttpRequestNodeTimeout] = None


class Response:
headers: dict[str, str]
response: httpx.Response

def __init__(self, response: httpx.Response):
self.response = response
self.headers = dict(response.headers)

@property
def is_file(self):
content_type = self.content_type
content_disposition = self.response.headers.get("Content-Disposition", "")

return "attachment" in content_disposition or (
not any(non_file in content_type for non_file in NON_FILE_CONTENT_TYPES)
and any(file_type in content_type for file_type in ("application/", "image/", "audio/", "video/"))
)

@property
def content_type(self) -> str:
return self.headers.get("Content-Type", "")

@property
def text(self) -> str:
return self.response.text

@property
def content(self) -> bytes:
return self.response.content

@property
def status_code(self) -> int:
return self.response.status_code

@property
def size(self) -> int:
return len(self.content)

@property
def readable_size(self) -> str:
if self.size < 1024:
return f"{self.size} bytes"
elif self.size < 1024 * 1024:
return f"{(self.size / 1024):.2f} KB"
else:
return f"{(self.size / 1024 / 1024):.2f} MB"
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
from core.file import file_manager
from core.helper import ssrf_proxy
from core.workflow.entities.variable_pool import VariablePool
from core.workflow.nodes.http_request.entities import (

from .entities import (
HttpRequestNodeAuthorization,
HttpRequestNodeData,
HttpRequestNodeTimeout,
Response,
)

BODY_TYPE_TO_CONTENT_TYPE = {
Expand All @@ -23,64 +25,9 @@
"form-data": "multipart/form-data",
"raw-text": "text/plain",
}
NON_FILE_CONTENT_TYPES = (
"application/json",
"application/xml",
"text/html",
"text/plain",
"application/x-www-form-urlencoded",
)


class HttpExecutorResponse:
headers: dict[str, str]
response: httpx.Response

def __init__(self, response: httpx.Response):
self.response = response
self.headers = dict(response.headers)

@property
def is_file(self):
content_type = self.content_type
content_disposition = self.response.headers.get("Content-Disposition", "")

return "attachment" in content_disposition or (
not any(non_file in content_type for non_file in NON_FILE_CONTENT_TYPES)
and any(file_type in content_type for file_type in ("application/", "image/", "audio/", "video/"))
)

@property
def content_type(self) -> str:
return self.headers.get("Content-Type", "")

@property
def text(self) -> str:
return self.response.text

@property
def content(self) -> bytes:
return self.response.content

@property
def status_code(self) -> int:
return self.response.status_code

@property
def size(self) -> int:
return len(self.content)

@property
def readable_size(self) -> str:
if self.size < 1024:
return f"{self.size} bytes"
elif self.size < 1024 * 1024:
return f"{(self.size / 1024):.2f} KB"
else:
return f"{(self.size / 1024 / 1024):.2f} MB"


class HttpExecutor:
class Executor:
method: Literal["get", "head", "post", "put", "delete", "patch"]
url: str
params: Mapping[str, str] | None
Expand Down Expand Up @@ -221,8 +168,8 @@ def _assembling_headers(self) -> dict[str, Any]:

return headers

def _validate_and_parse_response(self, response: httpx.Response) -> HttpExecutorResponse:
executor_response = HttpExecutorResponse(response)
def _validate_and_parse_response(self, response: httpx.Response) -> Response:
executor_response = Response(response)

threshold_size = (
dify_config.HTTP_REQUEST_NODE_MAX_BINARY_SIZE
Expand Down Expand Up @@ -260,7 +207,7 @@ def _do_http_request(self, headers: dict[str, Any]) -> httpx.Response:
response = getattr(ssrf_proxy, self.method)(**request_args)
return response

def invoke(self) -> HttpExecutorResponse:
def invoke(self) -> Response:
# assemble headers
headers = self._assembling_headers()
# do http request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
HttpRequestNodeData,
HttpRequestNodeTimeout,
)
from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse
from core.workflow.nodes.http_request.executor import Executor
from core.workflow.utils import variable_template_parser
from enums import NodeType
from models.workflow import WorkflowNodeExecutionStatus
Expand All @@ -24,6 +24,7 @@
read=dify_config.HTTP_REQUEST_MAX_READ_TIMEOUT,
write=dify_config.HTTP_REQUEST_MAX_WRITE_TIMEOUT,
)
from .entities import Response

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -54,7 +55,7 @@ def get_default_config(cls, filters: dict | None = None) -> dict:
def _run(self) -> NodeRunResult:
process_data = {}
try:
http_executor = HttpExecutor(
http_executor = Executor(
node_data=self.node_data,
timeout=self._get_request_timeout(self.node_data),
variable_pool=self.graph_runtime_state.variable_pool,
Expand Down Expand Up @@ -135,7 +136,7 @@ def _extract_variable_selector_to_variable_mapping(

return mapping

def extract_files(self, url: str, response: HttpExecutorResponse) -> list[File]:
def extract_files(self, url: str, response: Response) -> list[File]:
"""
Extract files from response
"""
Expand Down
2 changes: 1 addition & 1 deletion api/core/workflow/nodes/node_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from core.workflow.nodes.code.code_node import CodeNode
from core.workflow.nodes.document_extractor import DocumentExtractorNode
from core.workflow.nodes.end.end_node import EndNode
from core.workflow.nodes.http_request.http_request_node import HttpRequestNode
from core.workflow.nodes.http_request.node import HttpRequestNode
from core.workflow.nodes.if_else.if_else_node import IfElseNode
from core.workflow.nodes.iteration.iteration_node import IterationNode
from core.workflow.nodes.iteration.iteration_start_node import IterationStartNode
Expand Down
2 changes: 1 addition & 1 deletion api/tests/integration_tests/workflow/nodes/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState
from core.workflow.nodes.http_request.http_request_node import HttpRequestNode
from core.workflow.nodes.http_request.node import HttpRequestNode
from enums import UserFrom
from models.workflow import WorkflowType
from tests.integration_tests.workflow.nodes.__mock.http import setup_http_mock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,10 @@ def test_run_extract_text(

if mime_type == "application/pdf":
mock_pdf_extract = Mock(return_value=expected_text[0])
monkeypatch.setattr(
"core.workflow.nodes.document_extractor.document_extractor_node._extract_text_from_pdf", mock_pdf_extract
)
monkeypatch.setattr("core.workflow.nodes.document_extractor.node._extract_text_from_pdf", mock_pdf_extract)
elif mime_type.startswith("application/vnd.openxmlformats"):
mock_docx_extract = Mock(return_value=expected_text[0])
monkeypatch.setattr(
"core.workflow.nodes.document_extractor.document_extractor_node._extract_text_from_doc", mock_docx_extract
)
monkeypatch.setattr("core.workflow.nodes.document_extractor.node._extract_text_from_doc", mock_docx_extract)

result = document_extractor_node._run()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
HttpRequestNodeBody,
HttpRequestNodeData,
)
from core.workflow.nodes.http_request.http_executor import _plain_text_to_dict
from core.workflow.nodes.http_request.executor import _plain_text_to_dict
from enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType

Expand Down Expand Up @@ -96,7 +96,7 @@ def test_http_request_node_binary_file(monkeypatch):
),
)
monkeypatch.setattr(
"core.workflow.nodes.http_request.http_executor.file_manager.download",
"core.workflow.nodes.http_request.executor.file_manager.download",
lambda *args, **kwargs: b"test",
)
monkeypatch.setattr(
Expand Down Expand Up @@ -183,7 +183,7 @@ def test_http_request_node_form_with_file(monkeypatch):
),
)
monkeypatch.setattr(
"core.workflow.nodes.http_request.http_executor.file_manager.download",
"core.workflow.nodes.http_request.executor.file_manager.download",
lambda *args, **kwargs: b"test",
)

Expand Down