Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
8d4d0a29
Commit
8d4d0a29
authored
Mar 12, 2024
by
takatost
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feat/workflow-backend' into deploy/dev
parents
a36ce0fb
15ddbb5e
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
521 additions
and
54 deletions
+521
-54
tool_manager.py
api/core/tools/tool_manager.py
+6
-3
entities.py
api/core/workflow/nodes/code/entities.py
+1
-2
entities.py
api/core/workflow/nodes/http_request/entities.py
+2
-2
http_executor.py
api/core/workflow/nodes/http_request/http_executor.py
+69
-38
http_request_node.py
api/core/workflow/nodes/http_request/http_request_node.py
+7
-3
knowledge_retrieval_node.py
...low/nodes/knowledge_retrieval/knowledge_retrieval_node.py
+9
-1
template_transform_node.py
...kflow/nodes/template_transform/template_transform_node.py
+8
-1
tool_node.py
api/core/workflow/nodes/tool/tool_node.py
+7
-4
code_executor.py
.../integration_tests/workflow/nodes/__mock/code_executor.py
+4
-0
http.py
api/tests/integration_tests/workflow/nodes/__mock/http.py
+85
-0
test_http.py
api/tests/integration_tests/workflow/nodes/test_http.py
+209
-0
test_template_transform.py
...tegration_tests/workflow/nodes/test_template_transform.py
+46
-0
test_tool.py
api/tests/integration_tests/workflow/nodes/test_tool.py
+45
-0
docker-compose.middleware.yaml
docker/docker-compose.middleware.yaml
+10
-0
docker-compose.yaml
docker/docker-compose.yaml
+13
-0
No files found.
api/core/tools/tool_manager.py
View file @
8d4d0a29
...
@@ -315,8 +315,9 @@ class ToolManager:
...
@@ -315,8 +315,9 @@ class ToolManager:
for
parameter
in
parameters
:
for
parameter
in
parameters
:
# save tool parameter to tool entity memory
# save tool parameter to tool entity memory
value
=
ToolManager
.
_init_runtime_parameter
(
parameter
,
workflow_tool
.
tool_configurations
)
if
parameter
.
form
==
ToolParameter
.
ToolParameterForm
.
FORM
:
runtime_parameters
[
parameter
.
name
]
=
value
value
=
ToolManager
.
_init_runtime_parameter
(
parameter
,
workflow_tool
.
tool_configurations
)
runtime_parameters
[
parameter
.
name
]
=
value
# decrypt runtime parameters
# decrypt runtime parameters
encryption_manager
=
ToolParameterConfigurationManager
(
encryption_manager
=
ToolParameterConfigurationManager
(
...
@@ -325,7 +326,9 @@ class ToolManager:
...
@@ -325,7 +326,9 @@ class ToolManager:
provider_name
=
workflow_tool
.
provider_id
,
provider_name
=
workflow_tool
.
provider_id
,
provider_type
=
workflow_tool
.
provider_type
,
provider_type
=
workflow_tool
.
provider_type
,
)
)
runtime_parameters
=
encryption_manager
.
decrypt_tool_parameters
(
runtime_parameters
)
if
runtime_parameters
:
runtime_parameters
=
encryption_manager
.
decrypt_tool_parameters
(
runtime_parameters
)
tool_entity
.
runtime
.
runtime_parameters
.
update
(
runtime_parameters
)
tool_entity
.
runtime
.
runtime_parameters
.
update
(
runtime_parameters
)
return
tool_entity
return
tool_entity
...
...
api/core/workflow/nodes/code/entities.py
View file @
8d4d0a29
...
@@ -12,10 +12,9 @@ class CodeNodeData(BaseNodeData):
...
@@ -12,10 +12,9 @@ class CodeNodeData(BaseNodeData):
"""
"""
class
Output
(
BaseModel
):
class
Output
(
BaseModel
):
type
:
Literal
[
'string'
,
'number'
,
'object'
,
'array[string]'
,
'array[number]'
]
type
:
Literal
[
'string'
,
'number'
,
'object'
,
'array[string]'
,
'array[number]'
]
children
:
Optional
[
dict
[
str
,
'
CodeNodeData.
Output'
]]
children
:
Optional
[
dict
[
str
,
'Output'
]]
variables
:
list
[
VariableSelector
]
variables
:
list
[
VariableSelector
]
answer
:
str
code_language
:
Literal
[
'python3'
,
'javascript'
]
code_language
:
Literal
[
'python3'
,
'javascript'
]
code
:
str
code
:
str
outputs
:
dict
[
str
,
Output
]
outputs
:
dict
[
str
,
Output
]
api/core/workflow/nodes/http_request/entities.py
View file @
8d4d0a29
from
typing
import
Literal
,
Union
from
typing
import
Literal
,
Optional
,
Union
from
pydantic
import
BaseModel
from
pydantic
import
BaseModel
...
@@ -29,4 +29,4 @@ class HttpRequestNodeData(BaseNodeData):
...
@@ -29,4 +29,4 @@ class HttpRequestNodeData(BaseNodeData):
authorization
:
Authorization
authorization
:
Authorization
headers
:
str
headers
:
str
params
:
str
params
:
str
body
:
Body
body
:
Optional
[
Body
]
\ No newline at end of file
\ No newline at end of file
api/core/workflow/nodes/http_request/http_executor.py
View file @
8d4d0a29
...
@@ -43,6 +43,7 @@ class HttpExecutor:
...
@@ -43,6 +43,7 @@ class HttpExecutor:
self
.
params
=
{}
self
.
params
=
{}
self
.
headers
=
{}
self
.
headers
=
{}
self
.
body
=
None
self
.
body
=
None
self
.
files
=
None
# init template
# init template
self
.
_init_template
(
node_data
,
variables
)
self
.
_init_template
(
node_data
,
variables
)
...
@@ -76,11 +77,17 @@ class HttpExecutor:
...
@@ -76,11 +77,17 @@ class HttpExecutor:
# fill in params
# fill in params
kv_paris
=
original_params
.
split
(
'
\n
'
)
kv_paris
=
original_params
.
split
(
'
\n
'
)
for
kv
in
kv_paris
:
for
kv
in
kv_paris
:
if
not
kv
.
strip
():
continue
kv
=
kv
.
split
(
':'
)
kv
=
kv
.
split
(
':'
)
if
len
(
kv
)
!=
2
:
if
len
(
kv
)
==
2
:
k
,
v
=
kv
elif
len
(
kv
)
==
1
:
k
,
v
=
kv
[
0
],
''
else
:
raise
ValueError
(
f
'Invalid params {kv}'
)
raise
ValueError
(
f
'Invalid params {kv}'
)
k
,
v
=
kv
self
.
params
[
k
]
=
v
self
.
params
[
k
]
=
v
# extract all template in headers
# extract all template in headers
...
@@ -96,51 +103,61 @@ class HttpExecutor:
...
@@ -96,51 +103,61 @@ class HttpExecutor:
# fill in headers
# fill in headers
kv_paris
=
original_headers
.
split
(
'
\n
'
)
kv_paris
=
original_headers
.
split
(
'
\n
'
)
for
kv
in
kv_paris
:
for
kv
in
kv_paris
:
if
not
kv
.
strip
():
continue
kv
=
kv
.
split
(
':'
)
kv
=
kv
.
split
(
':'
)
if
len
(
kv
)
!=
2
:
if
len
(
kv
)
==
2
:
k
,
v
=
kv
elif
len
(
kv
)
==
1
:
k
,
v
=
kv
[
0
],
''
else
:
raise
ValueError
(
f
'Invalid headers {kv}'
)
raise
ValueError
(
f
'Invalid headers {kv}'
)
k
,
v
=
kv
self
.
headers
[
k
]
=
v
self
.
headers
[
k
]
=
v
# extract all template in body
# extract all template in body
body_template
=
re
.
findall
(
r'{{(.*?)}}'
,
node_data
.
body
.
data
or
''
)
or
[]
if
node_data
.
body
:
body_template
=
list
(
set
(
body_template
))
body_template
=
re
.
findall
(
r'{{(.*?)}}'
,
node_data
.
body
.
data
or
''
)
or
[]
original_body
=
node_data
.
body
.
data
or
''
body_template
=
list
(
set
(
body_template
))
for
body
in
body_template
:
original_body
=
node_data
.
body
.
data
or
''
if
not
body
:
for
body
in
body_template
:
continue
if
not
body
:
continue
original_body
=
original_body
.
replace
(
f
'{{{{{body}}}}}'
,
str
(
variables
.
get
(
body
,
''
)))
original_body
=
original_body
.
replace
(
f
'{{{{{body}}}}}'
,
str
(
variables
.
get
(
body
,
''
)))
if
node_data
.
body
.
type
==
'json'
:
self
.
headers
[
'Content-Type'
]
=
'application/json'
if
node_data
.
body
.
type
==
'json'
:
elif
node_data
.
body
.
type
==
'x-www-form-urlencoded'
:
self
.
headers
[
'Content-Type'
]
=
'application/json'
self
.
headers
[
'Content-Type'
]
=
'application/x-www-form-urlencoded'
elif
node_data
.
body
.
type
==
'x-www-form-urlencoded'
:
# elif node_data.body.type == 'form-data':
self
.
headers
[
'Content-Type'
]
=
'application/x-www-form-urlencoded'
# self.headers['Content-Type'] = 'multipart/form-data'
# elif node_data.body.type == 'form-data':
# self.headers['Content-Type'] = 'multipart/form-data'
if
node_data
.
body
.
type
in
[
'form-data'
,
'x-www-form-urlencoded'
]:
body
=
{}
if
node_data
.
body
.
type
in
[
'form-data'
,
'x-www-form-urlencoded'
]:
kv_paris
=
original_body
.
split
(
'
\n
'
)
body
=
{}
for
kv
in
kv_paris
:
kv_paris
=
original_body
.
split
(
'
\n
'
)
kv
=
kv
.
split
(
':'
)
for
kv
in
kv_paris
:
if
len
(
kv
)
==
2
:
kv
=
kv
.
split
(
':'
)
body
[
kv
[
0
]]
=
kv
[
1
]
if
len
(
kv
)
!=
2
:
elif
len
(
kv
)
==
1
:
raise
ValueError
(
f
'Invalid body {kv}'
)
body
[
kv
[
0
]]
=
''
body
[
kv
[
0
]]
=
kv
[
1
]
else
:
raise
ValueError
(
f
'Invalid body {kv}'
)
if
node_data
.
body
.
type
==
'form-data'
:
self
.
files
=
{
if
node_data
.
body
.
type
==
'form-data'
:
k
:
(
''
,
v
)
for
k
,
v
in
body
.
items
()
self
.
files
=
{
}
k
:
(
''
,
v
)
for
k
,
v
in
body
.
items
()
}
else
:
self
.
body
=
urlencode
(
body
)
else
:
else
:
self
.
body
=
urlencode
(
body
)
self
.
body
=
original_body
else
:
self
.
body
=
original_body
def
_assembling_headers
(
self
)
->
dict
[
str
,
Any
]:
def
_assembling_headers
(
self
)
->
dict
[
str
,
Any
]:
authorization
=
deepcopy
(
self
.
authorization
)
authorization
=
deepcopy
(
self
.
authorization
)
headers
=
deepcopy
(
self
.
headers
)
or
[]
headers
=
deepcopy
(
self
.
headers
)
or
{}
if
self
.
authorization
.
type
==
'api-key'
:
if
self
.
authorization
.
type
==
'api-key'
:
if
self
.
authorization
.
config
.
api_key
is
None
:
if
self
.
authorization
.
config
.
api_key
is
None
:
raise
ValueError
(
'api_key is required'
)
raise
ValueError
(
'api_key is required'
)
...
@@ -232,10 +249,24 @@ class HttpExecutor:
...
@@ -232,10 +249,24 @@ class HttpExecutor:
server_url
+=
f
'?{urlencode(self.params)}'
server_url
+=
f
'?{urlencode(self.params)}'
raw_request
=
f
'{self.method.upper()} {server_url} HTTP/1.1
\n
'
raw_request
=
f
'{self.method.upper()} {server_url} HTTP/1.1
\n
'
for
k
,
v
in
self
.
headers
.
items
():
headers
=
self
.
_assembling_headers
()
for
k
,
v
in
headers
.
items
():
raw_request
+=
f
'{k}: {v}
\n
'
raw_request
+=
f
'{k}: {v}
\n
'
raw_request
+=
'
\n
'
raw_request
+=
'
\n
'
raw_request
+=
self
.
body
or
''
# if files, use multipart/form-data with boundary
if
self
.
files
:
boundary
=
'----WebKitFormBoundary7MA4YWxkTrZu0gW'
raw_request
=
f
'--{boundary}
\n
'
+
raw_request
for
k
,
v
in
self
.
files
.
items
():
raw_request
+=
f
'Content-Disposition: form-data; name="{k}"; filename="{v[0]}"
\n
'
raw_request
+=
f
'Content-Type: {v[1]}
\n\n
'
raw_request
+=
v
[
1
]
+
'
\n
'
raw_request
+=
f
'--{boundary}
\n
'
raw_request
+=
'--
\n
'
else
:
raw_request
+=
self
.
body
or
''
return
raw_request
return
raw_request
\ No newline at end of file
api/core/workflow/nodes/http_request/http_request_node.py
View file @
8d4d0a29
...
@@ -24,15 +24,17 @@ class HttpRequestNode(BaseNode):
...
@@ -24,15 +24,17 @@ class HttpRequestNode(BaseNode):
# init http executor
# init http executor
try
:
try
:
http_executor
=
HttpExecutor
(
node_data
=
node_data
,
variables
=
variables
)
http_executor
=
HttpExecutor
(
node_data
=
node_data
,
variables
=
variables
)
# invoke http executor
# invoke http executor
response
=
http_executor
.
invoke
()
response
=
http_executor
.
invoke
()
except
Exception
as
e
:
except
Exception
as
e
:
return
NodeRunResult
(
return
NodeRunResult
(
status
=
WorkflowNodeExecutionStatus
.
FAILED
,
status
=
WorkflowNodeExecutionStatus
.
FAILED
,
inputs
=
variables
,
inputs
=
variables
,
error
=
str
(
e
),
error
=
str
(
e
),
process_data
=
http_executor
.
to_raw_request
()
process_data
=
{
'request'
:
http_executor
.
to_raw_request
()
}
)
)
return
NodeRunResult
(
return
NodeRunResult
(
...
@@ -43,7 +45,9 @@ class HttpRequestNode(BaseNode):
...
@@ -43,7 +45,9 @@ class HttpRequestNode(BaseNode):
'body'
:
response
,
'body'
:
response
,
'headers'
:
response
.
headers
'headers'
:
response
.
headers
},
},
process_data
=
http_executor
.
to_raw_request
()
process_data
=
{
'request'
:
http_executor
.
to_raw_request
(),
}
)
)
...
...
api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py
View file @
8d4d0a29
from
core.workflow.entities.base_node_data_entities
import
BaseNodeData
from
core.workflow.entities.node_entities
import
NodeRunResult
from
core.workflow.entities.variable_pool
import
VariablePool
from
core.workflow.nodes.base_node
import
BaseNode
from
core.workflow.nodes.base_node
import
BaseNode
class
KnowledgeRetrievalNode
(
BaseNode
):
class
KnowledgeRetrievalNode
(
BaseNode
):
pass
def
_run
(
self
,
variable_pool
:
VariablePool
)
->
NodeRunResult
:
pass
@
classmethod
def
_extract_variable_selector_to_variable_mapping
(
cls
,
node_data
:
BaseNodeData
)
->
dict
[
str
,
list
[
str
]]:
pass
api/core/workflow/nodes/template_transform/template_transform_node.py
View file @
8d4d0a29
...
@@ -7,6 +7,7 @@ from core.workflow.nodes.base_node import BaseNode
...
@@ -7,6 +7,7 @@ from core.workflow.nodes.base_node import BaseNode
from
core.workflow.nodes.template_transform.entities
import
TemplateTransformNodeData
from
core.workflow.nodes.template_transform.entities
import
TemplateTransformNodeData
from
models.workflow
import
WorkflowNodeExecutionStatus
from
models.workflow
import
WorkflowNodeExecutionStatus
MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH
=
1000
class
TemplateTransformNode
(
BaseNode
):
class
TemplateTransformNode
(
BaseNode
):
_node_data_cls
=
TemplateTransformNodeData
_node_data_cls
=
TemplateTransformNodeData
...
@@ -48,7 +49,6 @@ class TemplateTransformNode(BaseNode):
...
@@ -48,7 +49,6 @@ class TemplateTransformNode(BaseNode):
)
)
variables
[
variable
]
=
value
variables
[
variable
]
=
value
# Run code
# Run code
try
:
try
:
result
=
CodeExecutor
.
execute_code
(
result
=
CodeExecutor
.
execute_code
(
...
@@ -62,6 +62,13 @@ class TemplateTransformNode(BaseNode):
...
@@ -62,6 +62,13 @@ class TemplateTransformNode(BaseNode):
status
=
WorkflowNodeExecutionStatus
.
FAILED
,
status
=
WorkflowNodeExecutionStatus
.
FAILED
,
error
=
str
(
e
)
error
=
str
(
e
)
)
)
if
len
(
result
[
'result'
])
>
MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH
:
return
NodeRunResult
(
inputs
=
variables
,
status
=
WorkflowNodeExecutionStatus
.
FAILED
,
error
=
f
"Output length exceeds {MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH} characters"
)
return
NodeRunResult
(
return
NodeRunResult
(
status
=
WorkflowNodeExecutionStatus
.
SUCCEEDED
,
status
=
WorkflowNodeExecutionStatus
.
SUCCEEDED
,
...
...
api/core/workflow/nodes/tool/tool_node.py
View file @
8d4d0a29
...
@@ -29,7 +29,6 @@ class ToolNode(BaseNode):
...
@@ -29,7 +29,6 @@ class ToolNode(BaseNode):
# get parameters
# get parameters
parameters
=
self
.
_generate_parameters
(
variable_pool
,
node_data
)
parameters
=
self
.
_generate_parameters
(
variable_pool
,
node_data
)
# get tool runtime
# get tool runtime
try
:
try
:
tool_runtime
=
ToolManager
.
get_workflow_tool_runtime
(
self
.
tenant_id
,
node_data
,
None
)
tool_runtime
=
ToolManager
.
get_workflow_tool_runtime
(
self
.
tenant_id
,
node_data
,
None
)
...
@@ -41,7 +40,6 @@ class ToolNode(BaseNode):
...
@@ -41,7 +40,6 @@ class ToolNode(BaseNode):
)
)
try
:
try
:
# TODO: user_id
messages
=
tool_runtime
.
invoke
(
self
.
user_id
,
parameters
)
messages
=
tool_runtime
.
invoke
(
self
.
user_id
,
parameters
)
except
Exception
as
e
:
except
Exception
as
e
:
return
NodeRunResult
(
return
NodeRunResult
(
...
@@ -68,7 +66,7 @@ class ToolNode(BaseNode):
...
@@ -68,7 +66,7 @@ class ToolNode(BaseNode):
return
{
return
{
k
.
variable
:
k
.
variable
:
k
.
value
if
k
.
variable_type
==
'static'
else
k
.
value
if
k
.
variable_type
==
'static'
else
variable_pool
.
get_variable_value
(
k
.
value
)
if
k
.
variable_type
==
'selector'
else
''
variable_pool
.
get_variable_value
(
k
.
value
_selector
)
if
k
.
variable_type
==
'selector'
else
''
for
k
in
node_data
.
tool_parameters
for
k
in
node_data
.
tool_parameters
}
}
...
@@ -77,7 +75,12 @@ class ToolNode(BaseNode):
...
@@ -77,7 +75,12 @@ class ToolNode(BaseNode):
Convert ToolInvokeMessages into tuple[plain_text, files]
Convert ToolInvokeMessages into tuple[plain_text, files]
"""
"""
# transform message and handle file storage
# transform message and handle file storage
messages
=
ToolFileMessageTransformer
.
transform_tool_invoke_messages
(
messages
)
messages
=
ToolFileMessageTransformer
.
transform_tool_invoke_messages
(
messages
=
messages
,
user_id
=
self
.
user_id
,
tenant_id
=
self
.
tenant_id
,
conversation_id
=
''
,
)
# extract plain text and files
# extract plain text and files
files
=
self
.
_extract_tool_response_binary
(
messages
)
files
=
self
.
_extract_tool_response_binary
(
messages
)
plain_text
=
self
.
_extract_tool_response_text
(
messages
)
plain_text
=
self
.
_extract_tool_response_text
(
messages
)
...
...
api/tests/integration_tests/workflow/nodes/__mock/code_executor.py
View file @
8d4d0a29
...
@@ -15,6 +15,10 @@ class MockedCodeExecutor:
...
@@ -15,6 +15,10 @@ class MockedCodeExecutor:
return
{
return
{
"result"
:
3
"result"
:
3
}
}
elif
language
==
'jinja2'
:
return
{
"result"
:
"3"
}
@
pytest
.
fixture
@
pytest
.
fixture
def
setup_code_executor_mock
(
request
,
monkeypatch
:
MonkeyPatch
):
def
setup_code_executor_mock
(
request
,
monkeypatch
:
MonkeyPatch
):
...
...
api/tests/integration_tests/workflow/nodes/__mock/http.py
0 → 100644
View file @
8d4d0a29
import
os
import
pytest
import
requests.api
as
requests
import
httpx._api
as
httpx
from
requests
import
Response
as
RequestsResponse
from
httpx
import
Request
as
HttpxRequest
from
yarl
import
URL
from
typing
import
Literal
from
_pytest.monkeypatch
import
MonkeyPatch
from
json
import
dumps
MOCK
=
os
.
getenv
(
'MOCK_SWITCH'
,
'false'
)
==
'true'
class
MockedHttp
:
def
requests_request
(
method
:
Literal
[
'GET'
,
'POST'
,
'PUT'
,
'DELETE'
,
'PATCH'
,
'OPTIONS'
],
url
:
str
,
**
kwargs
)
->
RequestsResponse
:
"""
Mocked requests.request
"""
response
=
RequestsResponse
()
response
.
url
=
str
(
URL
(
url
)
%
kwargs
.
get
(
'params'
,
{}))
response
.
headers
=
kwargs
.
get
(
'headers'
,
{})
if
url
==
'http://404.com'
:
response
.
status_code
=
404
response
.
_content
=
b
'Not Found'
return
response
# get data, files
data
=
kwargs
.
get
(
'data'
,
None
)
files
=
kwargs
.
get
(
'files'
,
None
)
if
data
is
not
None
:
resp
=
dumps
(
data
)
.
encode
(
'utf-8'
)
if
files
is
not
None
:
resp
=
dumps
(
files
)
.
encode
(
'utf-8'
)
else
:
resp
=
b
'OK'
response
.
status_code
=
200
response
.
_content
=
resp
return
response
def
httpx_request
(
method
:
Literal
[
'GET'
,
'POST'
,
'PUT'
,
'DELETE'
,
'PATCH'
,
'OPTIONS'
],
url
:
str
,
**
kwargs
)
->
httpx
.
Response
:
"""
Mocked httpx.request
"""
response
=
httpx
.
Response
(
status_code
=
200
,
request
=
HttpxRequest
(
method
,
url
)
)
response
.
headers
=
kwargs
.
get
(
'headers'
,
{})
if
url
==
'http://404.com'
:
response
.
status_code
=
404
response
.
content
=
b
'Not Found'
return
response
# get data, files
data
=
kwargs
.
get
(
'data'
,
None
)
files
=
kwargs
.
get
(
'files'
,
None
)
if
data
is
not
None
:
resp
=
dumps
(
data
)
.
encode
(
'utf-8'
)
if
files
is
not
None
:
resp
=
dumps
(
files
)
.
encode
(
'utf-8'
)
else
:
resp
=
b
'OK'
response
.
status_code
=
200
response
.
_content
=
resp
return
response
@
pytest
.
fixture
def
setup_http_mock
(
request
,
monkeypatch
:
MonkeyPatch
):
if
not
MOCK
:
yield
return
monkeypatch
.
setattr
(
requests
,
"request"
,
MockedHttp
.
requests_request
)
monkeypatch
.
setattr
(
httpx
,
"request"
,
MockedHttp
.
httpx_request
)
yield
monkeypatch
.
undo
()
\ No newline at end of file
api/tests/integration_tests/workflow/nodes/test_http.py
0 → 100644
View file @
8d4d0a29
from
calendar
import
c
import
pytest
from
core.app.entities.app_invoke_entities
import
InvokeFrom
from
core.workflow.entities.variable_pool
import
VariablePool
from
core.workflow.nodes.http_request.http_request_node
import
HttpRequestNode
from
tests.integration_tests.workflow.nodes.__mock.http
import
setup_http_mock
BASIC_NODE_DATA
=
{
'tenant_id'
:
'1'
,
'app_id'
:
'1'
,
'workflow_id'
:
'1'
,
'user_id'
:
'1'
,
'user_from'
:
InvokeFrom
.
WEB_APP
,
}
# construct variable pool
pool
=
VariablePool
(
system_variables
=
{},
user_inputs
=
{})
pool
.
append_variable
(
node_id
=
'1'
,
variable_key_list
=
[
'123'
,
'args1'
],
value
=
1
)
pool
.
append_variable
(
node_id
=
'1'
,
variable_key_list
=
[
'123'
,
'args2'
],
value
=
2
)
@
pytest
.
mark
.
parametrize
(
'setup_http_mock'
,
[[
'none'
]],
indirect
=
True
)
def
test_get
(
setup_http_mock
):
node
=
HttpRequestNode
(
config
=
{
'id'
:
'1'
,
'data'
:
{
'title'
:
'http'
,
'desc'
:
''
,
'variables'
:
[{
'variable'
:
'args1'
,
'value_selector'
:
[
'1'
,
'123'
,
'args1'
],
}],
'method'
:
'get'
,
'url'
:
'http://example.com'
,
'authorization'
:
{
'type'
:
'api-key'
,
'config'
:
{
'type'
:
'basic'
,
'api_key'
:
'ak-xxx'
,
'header'
:
'api-key'
,
}
},
'headers'
:
'X-Header:123'
,
'params'
:
'A:b'
,
'body'
:
None
,
}
},
**
BASIC_NODE_DATA
)
result
=
node
.
run
(
pool
)
data
=
result
.
process_data
.
get
(
'request'
,
''
)
assert
'?A=b'
in
data
assert
'api-key: Basic ak-xxx'
in
data
assert
'X-Header: 123'
in
data
@
pytest
.
mark
.
parametrize
(
'setup_http_mock'
,
[[
'none'
]],
indirect
=
True
)
def
test_template
(
setup_http_mock
):
node
=
HttpRequestNode
(
config
=
{
'id'
:
'1'
,
'data'
:
{
'title'
:
'http'
,
'desc'
:
''
,
'variables'
:
[{
'variable'
:
'args1'
,
'value_selector'
:
[
'1'
,
'123'
,
'args2'
],
}],
'method'
:
'get'
,
'url'
:
'http://example.com/{{args1}}'
,
'authorization'
:
{
'type'
:
'api-key'
,
'config'
:
{
'type'
:
'basic'
,
'api_key'
:
'ak-xxx'
,
'header'
:
'api-key'
,
}
},
'headers'
:
'X-Header:123
\n
X-Header2:{{args1}}'
,
'params'
:
'A:b
\n
Template:{{args1}}'
,
'body'
:
None
,
}
},
**
BASIC_NODE_DATA
)
result
=
node
.
run
(
pool
)
data
=
result
.
process_data
.
get
(
'request'
,
''
)
assert
'?A=b'
in
data
assert
'Template=2'
in
data
assert
'api-key: Basic ak-xxx'
in
data
assert
'X-Header: 123'
in
data
assert
'X-Header2: 2'
in
data
@
pytest
.
mark
.
parametrize
(
'setup_http_mock'
,
[[
'none'
]],
indirect
=
True
)
def
test_json
(
setup_http_mock
):
node
=
HttpRequestNode
(
config
=
{
'id'
:
'1'
,
'data'
:
{
'title'
:
'http'
,
'desc'
:
''
,
'variables'
:
[{
'variable'
:
'args1'
,
'value_selector'
:
[
'1'
,
'123'
,
'args1'
],
}],
'method'
:
'post'
,
'url'
:
'http://example.com'
,
'authorization'
:
{
'type'
:
'api-key'
,
'config'
:
{
'type'
:
'basic'
,
'api_key'
:
'ak-xxx'
,
'header'
:
'api-key'
,
}
},
'headers'
:
'X-Header:123'
,
'params'
:
'A:b'
,
'body'
:
{
'type'
:
'json'
,
'data'
:
'{"a": "{{args1}}"}'
},
}
},
**
BASIC_NODE_DATA
)
result
=
node
.
run
(
pool
)
data
=
result
.
process_data
.
get
(
'request'
,
''
)
assert
'{"a": "1"}'
in
data
assert
'api-key: Basic ak-xxx'
in
data
assert
'X-Header: 123'
in
data
def
test_x_www_form_urlencoded
(
setup_http_mock
):
node
=
HttpRequestNode
(
config
=
{
'id'
:
'1'
,
'data'
:
{
'title'
:
'http'
,
'desc'
:
''
,
'variables'
:
[{
'variable'
:
'args1'
,
'value_selector'
:
[
'1'
,
'123'
,
'args1'
],
},
{
'variable'
:
'args2'
,
'value_selector'
:
[
'1'
,
'123'
,
'args2'
],
}],
'method'
:
'post'
,
'url'
:
'http://example.com'
,
'authorization'
:
{
'type'
:
'api-key'
,
'config'
:
{
'type'
:
'basic'
,
'api_key'
:
'ak-xxx'
,
'header'
:
'api-key'
,
}
},
'headers'
:
'X-Header:123'
,
'params'
:
'A:b'
,
'body'
:
{
'type'
:
'x-www-form-urlencoded'
,
'data'
:
'a:{{args1}}
\n
b:{{args2}}'
},
}
},
**
BASIC_NODE_DATA
)
result
=
node
.
run
(
pool
)
data
=
result
.
process_data
.
get
(
'request'
,
''
)
assert
'a=1&b=2'
in
data
assert
'api-key: Basic ak-xxx'
in
data
assert
'X-Header: 123'
in
data
def
test_form_data
(
setup_http_mock
):
node
=
HttpRequestNode
(
config
=
{
'id'
:
'1'
,
'data'
:
{
'title'
:
'http'
,
'desc'
:
''
,
'variables'
:
[{
'variable'
:
'args1'
,
'value_selector'
:
[
'1'
,
'123'
,
'args1'
],
},
{
'variable'
:
'args2'
,
'value_selector'
:
[
'1'
,
'123'
,
'args2'
],
}],
'method'
:
'post'
,
'url'
:
'http://example.com'
,
'authorization'
:
{
'type'
:
'api-key'
,
'config'
:
{
'type'
:
'basic'
,
'api_key'
:
'ak-xxx'
,
'header'
:
'api-key'
,
}
},
'headers'
:
'X-Header:123'
,
'params'
:
'A:b'
,
'body'
:
{
'type'
:
'form-data'
,
'data'
:
'a:{{args1}}
\n
b:{{args2}}'
},
}
},
**
BASIC_NODE_DATA
)
result
=
node
.
run
(
pool
)
data
=
result
.
process_data
.
get
(
'request'
,
''
)
assert
'form-data; name="a"'
in
data
assert
'1'
in
data
assert
'form-data; name="b"'
in
data
assert
'2'
in
data
assert
'api-key: Basic ak-xxx'
in
data
assert
'X-Header: 123'
in
data
api/tests/integration_tests/workflow/nodes/test_template_transform.py
0 → 100644
View file @
8d4d0a29
import
pytest
from
core.app.entities.app_invoke_entities
import
InvokeFrom
from
core.workflow.entities.variable_pool
import
VariablePool
from
core.workflow.nodes.template_transform.template_transform_node
import
TemplateTransformNode
from
models.workflow
import
WorkflowNodeExecutionStatus
from
tests.integration_tests.workflow.nodes.__mock.code_executor
import
setup_code_executor_mock
@
pytest
.
mark
.
parametrize
(
'setup_code_executor_mock'
,
[[
'none'
]],
indirect
=
True
)
def
test_execute_code
(
setup_code_executor_mock
):
code
=
'''{{args2}}'''
node
=
TemplateTransformNode
(
tenant_id
=
'1'
,
app_id
=
'1'
,
workflow_id
=
'1'
,
user_id
=
'1'
,
user_from
=
InvokeFrom
.
WEB_APP
,
config
=
{
'id'
:
'1'
,
'data'
:
{
'title'
:
'123'
,
'variables'
:
[
{
'variable'
:
'args1'
,
'value_selector'
:
[
'1'
,
'123'
,
'args1'
],
},
{
'variable'
:
'args2'
,
'value_selector'
:
[
'1'
,
'123'
,
'args2'
]
}
],
'template'
:
code
,
}
}
)
# construct variable pool
pool
=
VariablePool
(
system_variables
=
{},
user_inputs
=
{})
pool
.
append_variable
(
node_id
=
'1'
,
variable_key_list
=
[
'123'
,
'args1'
],
value
=
1
)
pool
.
append_variable
(
node_id
=
'1'
,
variable_key_list
=
[
'123'
,
'args2'
],
value
=
3
)
# execute node
result
=
node
.
run
(
pool
)
assert
result
.
status
==
WorkflowNodeExecutionStatus
.
SUCCEEDED
assert
result
.
outputs
[
'output'
]
==
'3'
api/tests/integration_tests/workflow/nodes/test_tool.py
0 → 100644
View file @
8d4d0a29
from
core.app.entities.app_invoke_entities
import
InvokeFrom
from
core.workflow.entities.variable_pool
import
VariablePool
from
core.workflow.nodes.tool.tool_node
import
ToolNode
from
models.workflow
import
WorkflowNodeExecutionStatus
def
test_tool_invoke
():
pool
=
VariablePool
(
system_variables
=
{},
user_inputs
=
{})
pool
.
append_variable
(
node_id
=
'1'
,
variable_key_list
=
[
'123'
,
'args1'
],
value
=
'1+1'
)
node
=
ToolNode
(
tenant_id
=
'1'
,
app_id
=
'1'
,
workflow_id
=
'1'
,
user_id
=
'1'
,
user_from
=
InvokeFrom
.
WEB_APP
,
config
=
{
'id'
:
'1'
,
'data'
:
{
'title'
:
'a'
,
'desc'
:
'a'
,
'provider_id'
:
'maths'
,
'provider_type'
:
'builtin'
,
'provider_name'
:
'maths'
,
'tool_name'
:
'eval_expression'
,
'tool_label'
:
'eval_expression'
,
'tool_configurations'
:
{},
'tool_parameters'
:
[
{
'variable'
:
'expression'
,
'value_selector'
:
[
'1'
,
'123'
,
'args1'
],
'variable_type'
:
'selector'
,
'value'
:
None
},
]
}
}
)
# execute node
result
=
node
.
run
(
pool
)
assert
result
.
status
==
WorkflowNodeExecutionStatus
.
SUCCEEDED
assert
'2'
in
result
.
outputs
[
'text'
]
assert
result
.
outputs
[
'files'
]
==
[]
\ No newline at end of file
docker/docker-compose.middleware.yaml
View file @
8d4d0a29
...
@@ -50,6 +50,16 @@ services:
...
@@ -50,6 +50,16 @@ services:
AUTHORIZATION_ADMINLIST_USERS
:
'
hello@dify.ai'
AUTHORIZATION_ADMINLIST_USERS
:
'
hello@dify.ai'
ports
:
ports
:
-
"
8080:8080"
-
"
8080:8080"
# The DifySandbox
sandbox
:
image
:
langgenius/dify-sandbox:latest
restart
:
always
environment
:
# The DifySandbox configurations
API_KEY
:
dify-sandbox
ports
:
-
"
8194:8194"
# Qdrant vector store.
# Qdrant vector store.
# uncomment to use qdrant as vector store.
# uncomment to use qdrant as vector store.
...
...
docker/docker-compose.yaml
View file @
8d4d0a29
...
@@ -122,6 +122,9 @@ services:
...
@@ -122,6 +122,9 @@ services:
SENTRY_TRACES_SAMPLE_RATE
:
1.0
SENTRY_TRACES_SAMPLE_RATE
:
1.0
# The sample rate for Sentry profiles. Default: `1.0`
# The sample rate for Sentry profiles. Default: `1.0`
SENTRY_PROFILES_SAMPLE_RATE
:
1.0
SENTRY_PROFILES_SAMPLE_RATE
:
1.0
# The sandbox service endpoint.
CODE_EXECUTION_ENDPOINT
:
"
http://sandbox:8194"
CODE_EXECUTION_API_KEY
:
dify-sandbox
depends_on
:
depends_on
:
-
db
-
db
-
redis
-
redis
...
@@ -286,6 +289,16 @@ services:
...
@@ -286,6 +289,16 @@ services:
# ports:
# ports:
# - "8080:8080"
# - "8080:8080"
# The DifySandbox
sandbox
:
image
:
langgenius/dify-sandbox:latest
restart
:
always
environment
:
# The DifySandbox configurations
API_KEY
:
dify-sandbox
ports
:
-
"
8194:8194"
# Qdrant vector store.
# Qdrant vector store.
# uncomment to use qdrant as vector store.
# uncomment to use qdrant as vector store.
# (if uncommented, you need to comment out the weaviate service above,
# (if uncommented, you need to comment out the weaviate service above,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment