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
f0679472
Commit
f0679472
authored
Feb 20, 2024
by
takatost
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add workflow logics
parent
9ad6bd78
Changes
44
Hide whitespace changes
Inline
Side-by-side
Showing
44 changed files
with
891 additions
and
386 deletions
+891
-386
model_template.py
api/constants/model_template.py
+65
-26
__init__.py
api/controllers/console/__init__.py
+1
-1
app.py
api/controllers/console/app/app.py
+23
-31
audio.py
api/controllers/console/app/audio.py
+1
-1
completion.py
api/controllers/console/app/completion.py
+2
-1
conversation.py
api/controllers/console/app/conversation.py
+1
-2
error.py
api/controllers/console/app/error.py
+6
-0
message.py
api/controllers/console/app/message.py
+1
-49
statistic.py
api/controllers/console/app/statistic.py
+1
-1
workflow.py
api/controllers/console/app/workflow.py
+71
-13
wraps.py
api/controllers/console/app/wraps.py
+14
-7
message.py
api/controllers/console/explore/message.py
+0
-47
ping.py
api/controllers/console/ping.py
+17
-0
account.py
api/controllers/console/workspace/account.py
+1
-14
members.py
api/controllers/console/workspace/members.py
+3
-18
message.py
api/controllers/web/message.py
+0
-47
basic_app_runner.py
api/core/app_runner/basic_app_runner.py
+2
-2
application_manager.py
api/core/application_manager.py
+30
-4
application_entities.py
api/core/entities/application_entities.py
+34
-21
prompt_transform.py
api/core/prompt/prompt_transform.py
+1
-1
__init__.py
api/core/workflow/__init__.py
+0
-0
NodeEntities.py
api/core/workflow/entities/NodeEntities.py
+32
-0
__init__.py
api/core/workflow/entities/__init__.py
+0
-0
__init__.py
api/core/workflow/nodes/__init__.py
+0
-0
__init__.py
api/core/workflow/nodes/end/__init__.py
+0
-0
end_node.py
api/core/workflow/nodes/end/end_node.py
+0
-0
entities.py
api/core/workflow/nodes/end/entities.py
+25
-0
workflow_engine_manager.py
api/core/workflow/workflow_engine_manager.py
+0
-0
annotation_fields.py
api/fields/annotation_fields.py
+1
-7
conversation_fields.py
api/fields/conversation_fields.py
+4
-9
member_fields.py
api/fields/member_fields.py
+38
-0
workflow_fields.py
api/fields/workflow_fields.py
+16
-0
b289e2408ee2_add_workflow.py
api/migrations/versions/b289e2408ee2_add_workflow.py
+1
-1
model.py
api/models/model.py
+28
-1
workflow.py
api/models/workflow.py
+54
-1
advanced_prompt_template_service.py
api/services/advanced_prompt_template_service.py
+1
-1
app_model_config_service.py
api/services/app_model_config_service.py
+1
-18
completion_service.py
api/services/completion_service.py
+1
-59
__init__.py
api/services/errors/__init__.py
+1
-1
app.py
api/services/errors/app.py
+0
-2
__init__.py
api/services/workflow/__init__.py
+0
-0
defaults.py
api/services/workflow/defaults.py
+72
-0
workflow_converter.py
api/services/workflow/workflow_converter.py
+259
-0
workflow_service.py
api/services/workflow_service.py
+83
-0
No files found.
api/constants/model_template.py
View file @
f0679472
import
json
import
json
model_templates
=
{
model_templates
=
{
#
completion
default mode
#
workflow
default mode
'
completion
_default'
:
{
'
workflow
_default'
:
{
'app'
:
{
'app'
:
{
'mode'
:
'
completion
'
,
'mode'
:
'
workflow
'
,
'enable_site'
:
True
,
'enable_site'
:
True
,
'enable_api'
:
True
,
'enable_api'
:
True
,
'is_demo'
:
False
,
'is_demo'
:
False
,
...
@@ -15,24 +15,7 @@ model_templates = {
...
@@ -15,24 +15,7 @@ model_templates = {
'model_config'
:
{
'model_config'
:
{
'provider'
:
''
,
'provider'
:
''
,
'model_id'
:
''
,
'model_id'
:
''
,
'configs'
:
{},
'configs'
:
{}
'model'
:
json
.
dumps
({
"provider"
:
"openai"
,
"name"
:
"gpt-3.5-turbo-instruct"
,
"mode"
:
"completion"
,
"completion_params"
:
{}
}),
'user_input_form'
:
json
.
dumps
([
{
"paragraph"
:
{
"label"
:
"Query"
,
"variable"
:
"query"
,
"required"
:
True
,
"default"
:
""
}
}
]),
'pre_prompt'
:
'{{query}}'
}
}
},
},
...
@@ -48,14 +31,70 @@ model_templates = {
...
@@ -48,14 +31,70 @@ model_templates = {
'status'
:
'normal'
'status'
:
'normal'
},
},
'model_config'
:
{
'model_config'
:
{
'provider'
:
''
,
'provider'
:
'openai'
,
'model_id'
:
''
,
'model_id'
:
'gpt-4'
,
'configs'
:
{},
'configs'
:
{
'prompt_template'
:
''
,
'prompt_variables'
:
[],
'completion_params'
:
{
'max_token'
:
512
,
'temperature'
:
1
,
'top_p'
:
1
,
'presence_penalty'
:
0
,
'frequency_penalty'
:
0
,
}
},
'model'
:
json
.
dumps
({
'model'
:
json
.
dumps
({
"provider"
:
"openai"
,
"provider"
:
"openai"
,
"name"
:
"gpt-
3.5-turbo
"
,
"name"
:
"gpt-
4
"
,
"mode"
:
"chat"
,
"mode"
:
"chat"
,
"completion_params"
:
{}
"completion_params"
:
{
"max_tokens"
:
512
,
"temperature"
:
1
,
"top_p"
:
1
,
"presence_penalty"
:
0
,
"frequency_penalty"
:
0
}
})
}
},
# agent default mode
'agent_default'
:
{
'app'
:
{
'mode'
:
'agent'
,
'enable_site'
:
True
,
'enable_api'
:
True
,
'is_demo'
:
False
,
'api_rpm'
:
0
,
'api_rph'
:
0
,
'status'
:
'normal'
},
'model_config'
:
{
'provider'
:
'openai'
,
'model_id'
:
'gpt-4'
,
'configs'
:
{
'prompt_template'
:
''
,
'prompt_variables'
:
[],
'completion_params'
:
{
'max_token'
:
512
,
'temperature'
:
1
,
'top_p'
:
1
,
'presence_penalty'
:
0
,
'frequency_penalty'
:
0
,
}
},
'model'
:
json
.
dumps
({
"provider"
:
"openai"
,
"name"
:
"gpt-4"
,
"mode"
:
"chat"
,
"completion_params"
:
{
"max_tokens"
:
512
,
"temperature"
:
1
,
"top_p"
:
1
,
"presence_penalty"
:
0
,
"frequency_penalty"
:
0
}
})
})
}
}
},
},
...
...
api/controllers/console/__init__.py
View file @
f0679472
...
@@ -5,7 +5,7 @@ bp = Blueprint('console', __name__, url_prefix='/console/api')
...
@@ -5,7 +5,7 @@ bp = Blueprint('console', __name__, url_prefix='/console/api')
api
=
ExternalApi
(
bp
)
api
=
ExternalApi
(
bp
)
# Import other controllers
# Import other controllers
from
.
import
admin
,
apikey
,
extension
,
feature
,
setup
,
version
from
.
import
admin
,
apikey
,
extension
,
feature
,
setup
,
version
,
ping
# Import app controllers
# Import app controllers
from
.app
import
(
advanced_prompt_template
,
annotation
,
app
,
audio
,
completion
,
conversation
,
generator
,
message
,
from
.app
import
(
advanced_prompt_template
,
annotation
,
app
,
audio
,
completion
,
conversation
,
generator
,
message
,
model_config
,
site
,
statistic
,
workflow
)
model_config
,
site
,
statistic
,
workflow
)
...
...
api/controllers/console/app/app.py
View file @
f0679472
...
@@ -26,7 +26,7 @@ from fields.app_fields import (
...
@@ -26,7 +26,7 @@ from fields.app_fields import (
template_list_fields
,
template_list_fields
,
)
)
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.model
import
App
,
AppModelConfig
,
Site
from
models.model
import
App
,
AppModelConfig
,
Site
,
AppMode
from
services.app_model_config_service
import
AppModelConfigService
from
services.app_model_config_service
import
AppModelConfigService
from
core.tools.utils.configuration
import
ToolParameterConfigurationManager
from
core.tools.utils.configuration
import
ToolParameterConfigurationManager
from
core.tools.tool_manager
import
ToolManager
from
core.tools.tool_manager
import
ToolManager
...
@@ -80,7 +80,7 @@ class AppListApi(Resource):
...
@@ -80,7 +80,7 @@ class AppListApi(Resource):
"""Create app"""
"""Create app"""
parser
=
reqparse
.
RequestParser
()
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'name'
,
type
=
str
,
required
=
True
,
location
=
'json'
)
parser
.
add_argument
(
'name'
,
type
=
str
,
required
=
True
,
location
=
'json'
)
parser
.
add_argument
(
'mode'
,
type
=
str
,
choices
=
[
'completion'
,
'chat'
,
'assistant'
],
location
=
'json'
)
parser
.
add_argument
(
'mode'
,
type
=
str
,
choices
=
[
mode
.
value
for
mode
in
AppMode
],
location
=
'json'
)
parser
.
add_argument
(
'icon'
,
type
=
str
,
location
=
'json'
)
parser
.
add_argument
(
'icon'
,
type
=
str
,
location
=
'json'
)
parser
.
add_argument
(
'icon_background'
,
type
=
str
,
location
=
'json'
)
parser
.
add_argument
(
'icon_background'
,
type
=
str
,
location
=
'json'
)
parser
.
add_argument
(
'model_config'
,
type
=
dict
,
location
=
'json'
)
parser
.
add_argument
(
'model_config'
,
type
=
dict
,
location
=
'json'
)
...
@@ -90,18 +90,7 @@ class AppListApi(Resource):
...
@@ -90,18 +90,7 @@ class AppListApi(Resource):
if
not
current_user
.
is_admin_or_owner
:
if
not
current_user
.
is_admin_or_owner
:
raise
Forbidden
()
raise
Forbidden
()
try
:
# TODO: MOVE TO IMPORT API
provider_manager
=
ProviderManager
()
default_model_entity
=
provider_manager
.
get_default_model
(
tenant_id
=
current_user
.
current_tenant_id
,
model_type
=
ModelType
.
LLM
)
except
(
ProviderTokenNotInitError
,
LLMBadRequestError
):
default_model_entity
=
None
except
Exception
as
e
:
logging
.
exception
(
e
)
default_model_entity
=
None
if
args
[
'model_config'
]
is
not
None
:
if
args
[
'model_config'
]
is
not
None
:
# validate config
# validate config
model_config_dict
=
args
[
'model_config'
]
model_config_dict
=
args
[
'model_config'
]
...
@@ -150,27 +139,30 @@ class AppListApi(Resource):
...
@@ -150,27 +139,30 @@ class AppListApi(Resource):
if
'mode'
not
in
args
or
args
[
'mode'
]
is
None
:
if
'mode'
not
in
args
or
args
[
'mode'
]
is
None
:
abort
(
400
,
message
=
"mode is required"
)
abort
(
400
,
message
=
"mode is required"
)
model_config_template
=
model_templates
[
args
[
'mode'
]
+
'_default'
]
app_mode
=
AppMode
.
value_of
(
args
[
'mode'
])
model_config_template
=
model_templates
[
app_mode
.
value
+
'_default'
]
app
=
App
(
**
model_config_template
[
'app'
])
app
=
App
(
**
model_config_template
[
'app'
])
app_model_config
=
AppModelConfig
(
**
model_config_template
[
'model_config'
])
app_model_config
=
AppModelConfig
(
**
model_config_template
[
'model_config'
])
# get model provider
if
app_mode
in
[
AppMode
.
CHAT
,
AppMode
.
AGENT
]:
model_manager
=
ModelManager
()
# get model provider
model_manager
=
ModelManager
()
try
:
model_instance
=
model_manager
.
get_default_model_instance
(
try
:
tenant_id
=
current_user
.
current_tenant_id
,
model_instance
=
model_manager
.
get_default_model_instance
(
model_type
=
ModelType
.
LLM
tenant_id
=
current_user
.
current_tenant_id
,
)
model_type
=
ModelType
.
LLM
except
ProviderTokenNotInitError
:
)
model_instance
=
None
except
ProviderTokenNotInitError
:
model_instance
=
None
if
model_instance
:
model_dict
=
app_model_config
.
model_dict
if
model_instance
:
model_dict
[
'provider'
]
=
model_instance
.
provider
model_dict
=
app_model_config
.
model_dict
model_dict
[
'name'
]
=
model_instance
.
model
model_dict
[
'provider'
]
=
model_instance
.
provider
app_model_config
.
model
=
json
.
dumps
(
model_dict
)
model_dict
[
'name'
]
=
model_instance
.
model
app_model_config
.
model
=
json
.
dumps
(
model_dict
)
app
.
name
=
args
[
'name'
]
app
.
name
=
args
[
'name'
]
app
.
mode
=
args
[
'mode'
]
app
.
mode
=
args
[
'mode'
]
...
...
api/controllers/console/app/audio.py
View file @
f0679472
...
@@ -20,10 +20,10 @@ from controllers.console.app.error import (
...
@@ -20,10 +20,10 @@ from controllers.console.app.error import (
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
from
controllers.console.wraps
import
account_initialization_required
from
core.entities.application_entities
import
AppMode
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.model_runtime.errors.invoke
import
InvokeError
from
core.model_runtime.errors.invoke
import
InvokeError
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.model
import
AppMode
from
services.audio_service
import
AudioService
from
services.audio_service
import
AudioService
from
services.errors.audio
import
(
from
services.errors.audio
import
(
AudioTooLargeServiceError
,
AudioTooLargeServiceError
,
...
...
api/controllers/console/app/completion.py
View file @
f0679472
...
@@ -22,11 +22,12 @@ from controllers.console.app.wraps import get_app_model
...
@@ -22,11 +22,12 @@ from controllers.console.app.wraps import get_app_model
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
from
controllers.console.wraps
import
account_initialization_required
from
core.application_queue_manager
import
ApplicationQueueManager
from
core.application_queue_manager
import
ApplicationQueueManager
from
core.entities.application_entities
import
AppMode
,
InvokeFrom
from
core.entities.application_entities
import
InvokeFrom
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.model_runtime.errors.invoke
import
InvokeError
from
core.model_runtime.errors.invoke
import
InvokeError
from
libs.helper
import
uuid_value
from
libs.helper
import
uuid_value
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.model
import
AppMode
from
services.completion_service
import
CompletionService
from
services.completion_service
import
CompletionService
...
...
api/controllers/console/app/conversation.py
View file @
f0679472
...
@@ -12,7 +12,6 @@ from controllers.console import api
...
@@ -12,7 +12,6 @@ from controllers.console import api
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
from
controllers.console.wraps
import
account_initialization_required
from
core.entities.application_entities
import
AppMode
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
fields.conversation_fields
import
(
from
fields.conversation_fields
import
(
conversation_detail_fields
,
conversation_detail_fields
,
...
@@ -22,7 +21,7 @@ from fields.conversation_fields import (
...
@@ -22,7 +21,7 @@ from fields.conversation_fields import (
)
)
from
libs.helper
import
datetime_string
from
libs.helper
import
datetime_string
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.model
import
Conversation
,
Message
,
MessageAnnotation
from
models.model
import
Conversation
,
Message
,
MessageAnnotation
,
AppMode
class
CompletionConversationApi
(
Resource
):
class
CompletionConversationApi
(
Resource
):
...
...
api/controllers/console/app/error.py
View file @
f0679472
...
@@ -85,3 +85,9 @@ class TooManyFilesError(BaseHTTPException):
...
@@ -85,3 +85,9 @@ class TooManyFilesError(BaseHTTPException):
error_code
=
'too_many_files'
error_code
=
'too_many_files'
description
=
"Only one file is allowed."
description
=
"Only one file is allowed."
code
=
400
code
=
400
class
DraftWorkflowNotExist
(
BaseHTTPException
):
error_code
=
'draft_workflow_not_exist'
description
=
"Draft workflow need to be initialized."
code
=
400
api/controllers/console/app/message.py
View file @
f0679472
...
@@ -11,7 +11,6 @@ from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
...
@@ -11,7 +11,6 @@ from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
from
controllers.console
import
api
from
controllers.console
import
api
from
controllers.console.app.error
import
(
from
controllers.console.app.error
import
(
AppMoreLikeThisDisabledError
,
CompletionRequestError
,
CompletionRequestError
,
ProviderModelCurrentlyNotSupportError
,
ProviderModelCurrentlyNotSupportError
,
ProviderNotInitializeError
,
ProviderNotInitializeError
,
...
@@ -20,7 +19,6 @@ from controllers.console.app.error import (
...
@@ -20,7 +19,6 @@ from controllers.console.app.error import (
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
,
cloud_edition_billing_resource_check
from
controllers.console.wraps
import
account_initialization_required
,
cloud_edition_billing_resource_check
from
core.entities.application_entities
import
AppMode
,
InvokeFrom
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.model_runtime.errors.invoke
import
InvokeError
from
core.model_runtime.errors.invoke
import
InvokeError
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
...
@@ -28,10 +26,8 @@ from fields.conversation_fields import annotation_fields, message_detail_fields
...
@@ -28,10 +26,8 @@ from fields.conversation_fields import annotation_fields, message_detail_fields
from
libs.helper
import
uuid_value
from
libs.helper
import
uuid_value
from
libs.infinite_scroll_pagination
import
InfiniteScrollPagination
from
libs.infinite_scroll_pagination
import
InfiniteScrollPagination
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.model
import
Conversation
,
Message
,
MessageAnnotation
,
MessageFeedback
from
models.model
import
Conversation
,
Message
,
MessageAnnotation
,
MessageFeedback
,
AppMode
from
services.annotation_service
import
AppAnnotationService
from
services.annotation_service
import
AppAnnotationService
from
services.completion_service
import
CompletionService
from
services.errors.app
import
MoreLikeThisDisabledError
from
services.errors.conversation
import
ConversationNotExistsError
from
services.errors.conversation
import
ConversationNotExistsError
from
services.errors.message
import
MessageNotExistsError
from
services.errors.message
import
MessageNotExistsError
from
services.message_service
import
MessageService
from
services.message_service
import
MessageService
...
@@ -183,49 +179,6 @@ class MessageAnnotationCountApi(Resource):
...
@@ -183,49 +179,6 @@ class MessageAnnotationCountApi(Resource):
return
{
'count'
:
count
}
return
{
'count'
:
count
}
class
MessageMoreLikeThisApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
get_app_model
(
mode
=
AppMode
.
COMPLETION
)
def
get
(
self
,
app_model
,
message_id
):
message_id
=
str
(
message_id
)
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'response_mode'
,
type
=
str
,
required
=
True
,
choices
=
[
'blocking'
,
'streaming'
],
location
=
'args'
)
args
=
parser
.
parse_args
()
streaming
=
args
[
'response_mode'
]
==
'streaming'
try
:
response
=
CompletionService
.
generate_more_like_this
(
app_model
=
app_model
,
user
=
current_user
,
message_id
=
message_id
,
invoke_from
=
InvokeFrom
.
DEBUGGER
,
streaming
=
streaming
)
return
compact_response
(
response
)
except
MessageNotExistsError
:
raise
NotFound
(
"Message Not Exists."
)
except
MoreLikeThisDisabledError
:
raise
AppMoreLikeThisDisabledError
()
except
ProviderTokenNotInitError
as
ex
:
raise
ProviderNotInitializeError
(
ex
.
description
)
except
QuotaExceededError
:
raise
ProviderQuotaExceededError
()
except
ModelCurrentlyNotSupportError
:
raise
ProviderModelCurrentlyNotSupportError
()
except
InvokeError
as
e
:
raise
CompletionRequestError
(
e
.
description
)
except
ValueError
as
e
:
raise
e
except
Exception
as
e
:
logging
.
exception
(
"internal server error."
)
raise
InternalServerError
()
def
compact_response
(
response
:
Union
[
dict
,
Generator
])
->
Response
:
def
compact_response
(
response
:
Union
[
dict
,
Generator
])
->
Response
:
if
isinstance
(
response
,
dict
):
if
isinstance
(
response
,
dict
):
return
Response
(
response
=
json
.
dumps
(
response
),
status
=
200
,
mimetype
=
'application/json'
)
return
Response
(
response
=
json
.
dumps
(
response
),
status
=
200
,
mimetype
=
'application/json'
)
...
@@ -291,7 +244,6 @@ class MessageApi(Resource):
...
@@ -291,7 +244,6 @@ class MessageApi(Resource):
return
message
return
message
api
.
add_resource
(
MessageMoreLikeThisApi
,
'/apps/<uuid:app_id>/completion-messages/<uuid:message_id>/more-like-this'
)
api
.
add_resource
(
MessageSuggestedQuestionApi
,
'/apps/<uuid:app_id>/chat-messages/<uuid:message_id>/suggested-questions'
)
api
.
add_resource
(
MessageSuggestedQuestionApi
,
'/apps/<uuid:app_id>/chat-messages/<uuid:message_id>/suggested-questions'
)
api
.
add_resource
(
ChatMessageListApi
,
'/apps/<uuid:app_id>/chat-messages'
,
endpoint
=
'console_chat_messages'
)
api
.
add_resource
(
ChatMessageListApi
,
'/apps/<uuid:app_id>/chat-messages'
,
endpoint
=
'console_chat_messages'
)
api
.
add_resource
(
MessageFeedbackApi
,
'/apps/<uuid:app_id>/feedbacks'
)
api
.
add_resource
(
MessageFeedbackApi
,
'/apps/<uuid:app_id>/feedbacks'
)
...
...
api/controllers/console/app/statistic.py
View file @
f0679472
...
@@ -10,10 +10,10 @@ from controllers.console import api
...
@@ -10,10 +10,10 @@ from controllers.console import api
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
from
controllers.console.wraps
import
account_initialization_required
from
core.entities.application_entities
import
AppMode
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
libs.helper
import
datetime_string
from
libs.helper
import
datetime_string
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.model
import
AppMode
class
DailyConversationStatistic
(
Resource
):
class
DailyConversationStatistic
(
Resource
):
...
...
api/controllers/console/app/workflow.py
View file @
f0679472
from
flask_restful
import
Resource
,
reqparse
from
flask_restful
import
Resource
,
reqparse
,
marshal_with
from
controllers.console
import
api
from
controllers.console
import
api
from
controllers.console.app.error
import
DraftWorkflowNotExist
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
from
controllers.console.wraps
import
account_initialization_required
from
core.entities.application_entities
import
AppMode
from
fields.workflow_fields
import
workflow_fields
from
libs.login
import
login_required
from
libs.login
import
login_required
,
current_user
from
models.model
import
App
,
ChatbotAppEngine
,
AppMode
from
services.workflow_service
import
WorkflowService
class
DefaultBlockConfigApi
(
Resource
):
class
DraftWorkflowApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
get_app_model
(
mode
=
[
AppMode
.
CHAT
,
AppMode
.
WORKFLOW
],
app_engine
=
ChatbotAppEngine
.
WORKFLOW
)
@
marshal_with
(
workflow_fields
)
def
get
(
self
,
app_model
:
App
):
"""
Get draft workflow
"""
# fetch draft workflow by app_model
workflow_service
=
WorkflowService
()
workflow
=
workflow_service
.
get_draft_workflow
(
app_model
=
app_model
)
if
not
workflow
:
raise
DraftWorkflowNotExist
()
# return workflow, if not found, return None (initiate graph by frontend)
return
workflow
@
setup_required
@
setup_required
@
login_required
@
login_required
@
account_initialization_required
@
account_initialization_required
def
get
(
self
):
@
get_app_model
(
mode
=
[
AppMode
.
CHAT
,
AppMode
.
WORKFLOW
],
app_engine
=
ChatbotAppEngine
.
WORKFLOW
)
def
post
(
self
,
app_model
:
App
):
"""
Sync draft workflow
"""
parser
=
reqparse
.
RequestParser
()
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'app_mode'
,
type
=
str
,
required
=
True
,
nullable
=
False
,
parser
.
add_argument
(
'graph'
,
type
=
dict
,
required
=
True
,
nullable
=
False
,
location
=
'json'
)
choices
=
[
AppMode
.
CHAT
.
value
,
AppMode
.
WORKFLOW
.
value
],
location
=
'args'
)
args
=
parser
.
parse_args
()
args
=
parser
.
parse_args
()
app_mode
=
args
.
get
(
'app_mode'
)
workflow_service
=
WorkflowService
()
app_mode
=
AppMode
.
value_of
(
app_mode
)
workflow_service
.
sync_draft_workflow
(
app_model
=
app_model
,
graph
=
args
.
get
(
'graph'
),
account
=
current_user
)
# TODO: implement this
return
{
return
{
"
blocks"
:
[]
"
result"
:
"success"
}
}
api
.
add_resource
(
DefaultBlockConfigApi
,
'/default-workflow-block-configs'
)
class
DefaultBlockConfigApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
get_app_model
(
mode
=
[
AppMode
.
CHAT
,
AppMode
.
WORKFLOW
],
app_engine
=
ChatbotAppEngine
.
WORKFLOW
)
def
get
(
self
,
app_model
:
App
):
"""
Get default block config
"""
# Get default block configs
workflow_service
=
WorkflowService
()
return
workflow_service
.
get_default_block_configs
()
class
ConvertToWorkflowApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
get_app_model
(
mode
=
AppMode
.
CHAT
)
@
marshal_with
(
workflow_fields
)
def
post
(
self
,
app_model
:
App
):
"""
Convert basic mode of chatbot app to workflow
"""
# convert to workflow mode
workflow_service
=
WorkflowService
()
workflow
=
workflow_service
.
chatbot_convert_to_workflow
(
app_model
=
app_model
)
# return workflow
return
workflow
api
.
add_resource
(
DraftWorkflowApi
,
'/apps/<uuid:app_id>/workflows/draft'
)
api
.
add_resource
(
DefaultBlockConfigApi
,
'/apps/<uuid:app_id>/workflows/default-workflow-block-configs'
)
api
.
add_resource
(
ConvertToWorkflowApi
,
'/apps/<uuid:app_id>/convert-to-workflow'
)
api/controllers/console/app/wraps.py
View file @
f0679472
...
@@ -3,13 +3,14 @@ from functools import wraps
...
@@ -3,13 +3,14 @@ from functools import wraps
from
typing
import
Optional
,
Union
from
typing
import
Optional
,
Union
from
controllers.console.app.error
import
AppNotFoundError
from
controllers.console.app.error
import
AppNotFoundError
from
core.entities.application_entities
import
AppMode
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
libs.login
import
current_user
from
libs.login
import
current_user
from
models.model
import
App
from
models.model
import
App
,
ChatbotAppEngine
,
AppMode
def
get_app_model
(
view
:
Optional
[
Callable
]
=
None
,
*
,
mode
:
Union
[
AppMode
,
list
[
AppMode
]]
=
None
):
def
get_app_model
(
view
:
Optional
[
Callable
]
=
None
,
*
,
mode
:
Union
[
AppMode
,
list
[
AppMode
]]
=
None
,
app_engine
:
ChatbotAppEngine
=
None
):
def
decorator
(
view_func
):
def
decorator
(
view_func
):
@
wraps
(
view_func
)
@
wraps
(
view_func
)
def
decorated_view
(
*
args
,
**
kwargs
):
def
decorated_view
(
*
args
,
**
kwargs
):
...
@@ -37,14 +38,20 @@ def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[
...
@@ -37,14 +38,20 @@ def get_app_model(view: Optional[Callable] = None, *, mode: Union[AppMode, list[
else
:
else
:
modes
=
[
mode
]
modes
=
[
mode
]
# [temp] if workflow is in the mode list, then completion should be in the mode list
if
AppMode
.
WORKFLOW
in
modes
:
modes
.
append
(
AppMode
.
COMPLETION
)
if
app_mode
not
in
modes
:
if
app_mode
not
in
modes
:
mode_values
=
{
m
.
value
for
m
in
modes
}
mode_values
=
{
m
.
value
for
m
in
modes
}
raise
AppNotFoundError
(
f
"App mode is not in the supported list: {mode_values}"
)
raise
AppNotFoundError
(
f
"App mode is not in the supported list: {mode_values}"
)
if
app_engine
is
not
None
:
if
app_mode
not
in
[
AppMode
.
CHAT
,
AppMode
.
WORKFLOW
]:
raise
AppNotFoundError
(
f
"App mode is not supported for {app_engine.value} app engine."
)
if
app_mode
==
AppMode
.
CHAT
:
# fetch current app model config
app_model_config
=
app_model
.
app_model_config
if
not
app_model_config
or
app_model_config
.
chatbot_app_engine
!=
app_engine
.
value
:
raise
AppNotFoundError
(
f
"{app_engine.value} app engine is not supported."
)
kwargs
[
'app_model'
]
=
app_model
kwargs
[
'app_model'
]
=
app_model
return
view_func
(
*
args
,
**
kwargs
)
return
view_func
(
*
args
,
**
kwargs
)
...
...
api/controllers/console/explore/message.py
View file @
f0679472
...
@@ -12,7 +12,6 @@ from werkzeug.exceptions import InternalServerError, NotFound
...
@@ -12,7 +12,6 @@ from werkzeug.exceptions import InternalServerError, NotFound
import
services
import
services
from
controllers.console
import
api
from
controllers.console
import
api
from
controllers.console.app.error
import
(
from
controllers.console.app.error
import
(
AppMoreLikeThisDisabledError
,
CompletionRequestError
,
CompletionRequestError
,
ProviderModelCurrentlyNotSupportError
,
ProviderModelCurrentlyNotSupportError
,
ProviderNotInitializeError
,
ProviderNotInitializeError
,
...
@@ -24,13 +23,10 @@ from controllers.console.explore.error import (
...
@@ -24,13 +23,10 @@ from controllers.console.explore.error import (
NotCompletionAppError
,
NotCompletionAppError
,
)
)
from
controllers.console.explore.wraps
import
InstalledAppResource
from
controllers.console.explore.wraps
import
InstalledAppResource
from
core.entities.application_entities
import
InvokeFrom
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.model_runtime.errors.invoke
import
InvokeError
from
core.model_runtime.errors.invoke
import
InvokeError
from
fields.message_fields
import
message_infinite_scroll_pagination_fields
from
fields.message_fields
import
message_infinite_scroll_pagination_fields
from
libs.helper
import
uuid_value
from
libs.helper
import
uuid_value
from
services.completion_service
import
CompletionService
from
services.errors.app
import
MoreLikeThisDisabledError
from
services.errors.conversation
import
ConversationNotExistsError
from
services.errors.conversation
import
ConversationNotExistsError
from
services.errors.message
import
MessageNotExistsError
,
SuggestedQuestionsAfterAnswerDisabledError
from
services.errors.message
import
MessageNotExistsError
,
SuggestedQuestionsAfterAnswerDisabledError
from
services.message_service
import
MessageService
from
services.message_service
import
MessageService
...
@@ -76,48 +72,6 @@ class MessageFeedbackApi(InstalledAppResource):
...
@@ -76,48 +72,6 @@ class MessageFeedbackApi(InstalledAppResource):
return
{
'result'
:
'success'
}
return
{
'result'
:
'success'
}
class
MessageMoreLikeThisApi
(
InstalledAppResource
):
def
get
(
self
,
installed_app
,
message_id
):
app_model
=
installed_app
.
app
if
app_model
.
mode
!=
'completion'
:
raise
NotCompletionAppError
()
message_id
=
str
(
message_id
)
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'response_mode'
,
type
=
str
,
required
=
True
,
choices
=
[
'blocking'
,
'streaming'
],
location
=
'args'
)
args
=
parser
.
parse_args
()
streaming
=
args
[
'response_mode'
]
==
'streaming'
try
:
response
=
CompletionService
.
generate_more_like_this
(
app_model
=
app_model
,
user
=
current_user
,
message_id
=
message_id
,
invoke_from
=
InvokeFrom
.
EXPLORE
,
streaming
=
streaming
)
return
compact_response
(
response
)
except
MessageNotExistsError
:
raise
NotFound
(
"Message Not Exists."
)
except
MoreLikeThisDisabledError
:
raise
AppMoreLikeThisDisabledError
()
except
ProviderTokenNotInitError
as
ex
:
raise
ProviderNotInitializeError
(
ex
.
description
)
except
QuotaExceededError
:
raise
ProviderQuotaExceededError
()
except
ModelCurrentlyNotSupportError
:
raise
ProviderModelCurrentlyNotSupportError
()
except
InvokeError
as
e
:
raise
CompletionRequestError
(
e
.
description
)
except
ValueError
as
e
:
raise
e
except
Exception
:
logging
.
exception
(
"internal server error."
)
raise
InternalServerError
()
def
compact_response
(
response
:
Union
[
dict
,
Generator
])
->
Response
:
def
compact_response
(
response
:
Union
[
dict
,
Generator
])
->
Response
:
if
isinstance
(
response
,
dict
):
if
isinstance
(
response
,
dict
):
return
Response
(
response
=
json
.
dumps
(
response
),
status
=
200
,
mimetype
=
'application/json'
)
return
Response
(
response
=
json
.
dumps
(
response
),
status
=
200
,
mimetype
=
'application/json'
)
...
@@ -166,5 +120,4 @@ class MessageSuggestedQuestionApi(InstalledAppResource):
...
@@ -166,5 +120,4 @@ class MessageSuggestedQuestionApi(InstalledAppResource):
api
.
add_resource
(
MessageListApi
,
'/installed-apps/<uuid:installed_app_id>/messages'
,
endpoint
=
'installed_app_messages'
)
api
.
add_resource
(
MessageListApi
,
'/installed-apps/<uuid:installed_app_id>/messages'
,
endpoint
=
'installed_app_messages'
)
api
.
add_resource
(
MessageFeedbackApi
,
'/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/feedbacks'
,
endpoint
=
'installed_app_message_feedback'
)
api
.
add_resource
(
MessageFeedbackApi
,
'/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/feedbacks'
,
endpoint
=
'installed_app_message_feedback'
)
api
.
add_resource
(
MessageMoreLikeThisApi
,
'/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/more-like-this'
,
endpoint
=
'installed_app_more_like_this'
)
api
.
add_resource
(
MessageSuggestedQuestionApi
,
'/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/suggested-questions'
,
endpoint
=
'installed_app_suggested_question'
)
api
.
add_resource
(
MessageSuggestedQuestionApi
,
'/installed-apps/<uuid:installed_app_id>/messages/<uuid:message_id>/suggested-questions'
,
endpoint
=
'installed_app_suggested_question'
)
api/controllers/console/ping.py
0 → 100644
View file @
f0679472
from
flask_restful
import
Resource
from
controllers.console
import
api
class
PingApi
(
Resource
):
def
get
(
self
):
"""
For connection health check
"""
return
{
"result"
:
"pong"
}
api
.
add_resource
(
PingApi
,
'/ping'
)
api/controllers/console/workspace/account.py
View file @
f0679472
...
@@ -16,26 +16,13 @@ from controllers.console.workspace.error import (
...
@@ -16,26 +16,13 @@ from controllers.console.workspace.error import (
)
)
from
controllers.console.wraps
import
account_initialization_required
from
controllers.console.wraps
import
account_initialization_required
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
fields.member_fields
import
account_fields
from
libs.helper
import
TimestampField
,
timezone
from
libs.helper
import
TimestampField
,
timezone
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.account
import
AccountIntegrate
,
InvitationCode
from
models.account
import
AccountIntegrate
,
InvitationCode
from
services.account_service
import
AccountService
from
services.account_service
import
AccountService
from
services.errors.account
import
CurrentPasswordIncorrectError
as
ServiceCurrentPasswordIncorrectError
from
services.errors.account
import
CurrentPasswordIncorrectError
as
ServiceCurrentPasswordIncorrectError
account_fields
=
{
'id'
:
fields
.
String
,
'name'
:
fields
.
String
,
'avatar'
:
fields
.
String
,
'email'
:
fields
.
String
,
'is_password_set'
:
fields
.
Boolean
,
'interface_language'
:
fields
.
String
,
'interface_theme'
:
fields
.
String
,
'timezone'
:
fields
.
String
,
'last_login_at'
:
TimestampField
,
'last_login_ip'
:
fields
.
String
,
'created_at'
:
TimestampField
}
class
AccountInitApi
(
Resource
):
class
AccountInitApi
(
Resource
):
...
...
api/controllers/console/workspace/members.py
View file @
f0679472
from
flask
import
current_app
from
flask
import
current_app
from
flask_login
import
current_user
from
flask_login
import
current_user
from
flask_restful
import
Resource
,
abort
,
fields
,
marshal_with
,
reqparse
from
flask_restful
import
Resource
,
abort
,
marshal_with
,
reqparse
import
services
import
services
from
controllers.console
import
api
from
controllers.console
import
api
from
controllers.console.setup
import
setup_required
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
,
cloud_edition_billing_resource_check
from
controllers.console.wraps
import
account_initialization_required
,
cloud_edition_billing_resource_check
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
libs.helper
import
TimestampField
from
fields.member_fields
import
account_with_role_list_fields
from
libs.login
import
login_required
from
libs.login
import
login_required
from
models.account
import
Account
from
models.account
import
Account
from
services.account_service
import
RegisterService
,
TenantService
from
services.account_service
import
RegisterService
,
TenantService
from
services.errors.account
import
AccountAlreadyInTenantError
from
services.errors.account
import
AccountAlreadyInTenantError
account_fields
=
{
'id'
:
fields
.
String
,
'name'
:
fields
.
String
,
'avatar'
:
fields
.
String
,
'email'
:
fields
.
String
,
'last_login_at'
:
TimestampField
,
'created_at'
:
TimestampField
,
'role'
:
fields
.
String
,
'status'
:
fields
.
String
,
}
account_list_fields
=
{
'accounts'
:
fields
.
List
(
fields
.
Nested
(
account_fields
))
}
class
MemberListApi
(
Resource
):
class
MemberListApi
(
Resource
):
"""List all members of current tenant."""
"""List all members of current tenant."""
...
@@ -35,7 +20,7 @@ class MemberListApi(Resource):
...
@@ -35,7 +20,7 @@ class MemberListApi(Resource):
@
setup_required
@
setup_required
@
login_required
@
login_required
@
account_initialization_required
@
account_initialization_required
@
marshal_with
(
account_list_fields
)
@
marshal_with
(
account_
with_role_
list_fields
)
def
get
(
self
):
def
get
(
self
):
members
=
TenantService
.
get_tenant_members
(
current_user
.
current_tenant
)
members
=
TenantService
.
get_tenant_members
(
current_user
.
current_tenant
)
return
{
'result'
:
'success'
,
'accounts'
:
members
},
200
return
{
'result'
:
'success'
,
'accounts'
:
members
},
200
...
...
api/controllers/web/message.py
View file @
f0679472
...
@@ -11,7 +11,6 @@ from werkzeug.exceptions import InternalServerError, NotFound
...
@@ -11,7 +11,6 @@ from werkzeug.exceptions import InternalServerError, NotFound
import
services
import
services
from
controllers.web
import
api
from
controllers.web
import
api
from
controllers.web.error
import
(
from
controllers.web.error
import
(
AppMoreLikeThisDisabledError
,
AppSuggestedQuestionsAfterAnswerDisabledError
,
AppSuggestedQuestionsAfterAnswerDisabledError
,
CompletionRequestError
,
CompletionRequestError
,
NotChatAppError
,
NotChatAppError
,
...
@@ -21,14 +20,11 @@ from controllers.web.error import (
...
@@ -21,14 +20,11 @@ from controllers.web.error import (
ProviderQuotaExceededError
,
ProviderQuotaExceededError
,
)
)
from
controllers.web.wraps
import
WebApiResource
from
controllers.web.wraps
import
WebApiResource
from
core.entities.application_entities
import
InvokeFrom
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.model_runtime.errors.invoke
import
InvokeError
from
core.model_runtime.errors.invoke
import
InvokeError
from
fields.conversation_fields
import
message_file_fields
from
fields.conversation_fields
import
message_file_fields
from
fields.message_fields
import
agent_thought_fields
from
fields.message_fields
import
agent_thought_fields
from
libs.helper
import
TimestampField
,
uuid_value
from
libs.helper
import
TimestampField
,
uuid_value
from
services.completion_service
import
CompletionService
from
services.errors.app
import
MoreLikeThisDisabledError
from
services.errors.conversation
import
ConversationNotExistsError
from
services.errors.conversation
import
ConversationNotExistsError
from
services.errors.message
import
MessageNotExistsError
,
SuggestedQuestionsAfterAnswerDisabledError
from
services.errors.message
import
MessageNotExistsError
,
SuggestedQuestionsAfterAnswerDisabledError
from
services.message_service
import
MessageService
from
services.message_service
import
MessageService
...
@@ -113,48 +109,6 @@ class MessageFeedbackApi(WebApiResource):
...
@@ -113,48 +109,6 @@ class MessageFeedbackApi(WebApiResource):
return
{
'result'
:
'success'
}
return
{
'result'
:
'success'
}
class
MessageMoreLikeThisApi
(
WebApiResource
):
def
get
(
self
,
app_model
,
end_user
,
message_id
):
if
app_model
.
mode
!=
'completion'
:
raise
NotCompletionAppError
()
message_id
=
str
(
message_id
)
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'response_mode'
,
type
=
str
,
required
=
True
,
choices
=
[
'blocking'
,
'streaming'
],
location
=
'args'
)
args
=
parser
.
parse_args
()
streaming
=
args
[
'response_mode'
]
==
'streaming'
try
:
response
=
CompletionService
.
generate_more_like_this
(
app_model
=
app_model
,
user
=
end_user
,
message_id
=
message_id
,
invoke_from
=
InvokeFrom
.
WEB_APP
,
streaming
=
streaming
)
return
compact_response
(
response
)
except
MessageNotExistsError
:
raise
NotFound
(
"Message Not Exists."
)
except
MoreLikeThisDisabledError
:
raise
AppMoreLikeThisDisabledError
()
except
ProviderTokenNotInitError
as
ex
:
raise
ProviderNotInitializeError
(
ex
.
description
)
except
QuotaExceededError
:
raise
ProviderQuotaExceededError
()
except
ModelCurrentlyNotSupportError
:
raise
ProviderModelCurrentlyNotSupportError
()
except
InvokeError
as
e
:
raise
CompletionRequestError
(
e
.
description
)
except
ValueError
as
e
:
raise
e
except
Exception
:
logging
.
exception
(
"internal server error."
)
raise
InternalServerError
()
def
compact_response
(
response
:
Union
[
dict
,
Generator
])
->
Response
:
def
compact_response
(
response
:
Union
[
dict
,
Generator
])
->
Response
:
if
isinstance
(
response
,
dict
):
if
isinstance
(
response
,
dict
):
return
Response
(
response
=
json
.
dumps
(
response
),
status
=
200
,
mimetype
=
'application/json'
)
return
Response
(
response
=
json
.
dumps
(
response
),
status
=
200
,
mimetype
=
'application/json'
)
...
@@ -202,5 +156,4 @@ class MessageSuggestedQuestionApi(WebApiResource):
...
@@ -202,5 +156,4 @@ class MessageSuggestedQuestionApi(WebApiResource):
api
.
add_resource
(
MessageListApi
,
'/messages'
)
api
.
add_resource
(
MessageListApi
,
'/messages'
)
api
.
add_resource
(
MessageFeedbackApi
,
'/messages/<uuid:message_id>/feedbacks'
)
api
.
add_resource
(
MessageFeedbackApi
,
'/messages/<uuid:message_id>/feedbacks'
)
api
.
add_resource
(
MessageMoreLikeThisApi
,
'/messages/<uuid:message_id>/more-like-this'
)
api
.
add_resource
(
MessageSuggestedQuestionApi
,
'/messages/<uuid:message_id>/suggested-questions'
)
api
.
add_resource
(
MessageSuggestedQuestionApi
,
'/messages/<uuid:message_id>/suggested-questions'
)
api/core/app_runner/basic_app_runner.py
View file @
f0679472
...
@@ -6,7 +6,6 @@ from core.application_queue_manager import ApplicationQueueManager, PublishFrom
...
@@ -6,7 +6,6 @@ from core.application_queue_manager import ApplicationQueueManager, PublishFrom
from
core.callback_handler.index_tool_callback_handler
import
DatasetIndexToolCallbackHandler
from
core.callback_handler.index_tool_callback_handler
import
DatasetIndexToolCallbackHandler
from
core.entities.application_entities
import
(
from
core.entities.application_entities
import
(
ApplicationGenerateEntity
,
ApplicationGenerateEntity
,
AppMode
,
DatasetEntity
,
DatasetEntity
,
InvokeFrom
,
InvokeFrom
,
ModelConfigEntity
,
ModelConfigEntity
,
...
@@ -16,7 +15,7 @@ from core.memory.token_buffer_memory import TokenBufferMemory
...
@@ -16,7 +15,7 @@ from core.memory.token_buffer_memory import TokenBufferMemory
from
core.model_manager
import
ModelInstance
from
core.model_manager
import
ModelInstance
from
core.moderation.base
import
ModerationException
from
core.moderation.base
import
ModerationException
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
models.model
import
App
,
Conversation
,
Message
from
models.model
import
App
,
Conversation
,
Message
,
AppMode
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -250,6 +249,7 @@ class BasicApplicationRunner(AppRunner):
...
@@ -250,6 +249,7 @@ class BasicApplicationRunner(AppRunner):
invoke_from
invoke_from
)
)
# TODO
if
(
app_record
.
mode
==
AppMode
.
COMPLETION
.
value
and
dataset_config
if
(
app_record
.
mode
==
AppMode
.
COMPLETION
.
value
and
dataset_config
and
dataset_config
.
retrieve_config
.
query_variable
):
and
dataset_config
.
retrieve_config
.
query_variable
):
query
=
inputs
.
get
(
dataset_config
.
retrieve_config
.
query_variable
,
""
)
query
=
inputs
.
get
(
dataset_config
.
retrieve_config
.
query_variable
,
""
)
...
...
api/core/application_manager.py
View file @
f0679472
...
@@ -28,7 +28,7 @@ from core.entities.application_entities import (
...
@@ -28,7 +28,7 @@ from core.entities.application_entities import (
ModelConfigEntity
,
ModelConfigEntity
,
PromptTemplateEntity
,
PromptTemplateEntity
,
SensitiveWordAvoidanceEntity
,
SensitiveWordAvoidanceEntity
,
TextToSpeechEntity
,
TextToSpeechEntity
,
VariableEntity
,
)
)
from
core.entities.model_entities
import
ModelStatus
from
core.entities.model_entities
import
ModelStatus
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
from
core.errors.error
import
ModelCurrentlyNotSupportError
,
ProviderTokenNotInitError
,
QuotaExceededError
...
@@ -93,7 +93,7 @@ class ApplicationManager:
...
@@ -93,7 +93,7 @@ class ApplicationManager:
app_id
=
app_id
,
app_id
=
app_id
,
app_model_config_id
=
app_model_config_id
,
app_model_config_id
=
app_model_config_id
,
app_model_config_dict
=
app_model_config_dict
,
app_model_config_dict
=
app_model_config_dict
,
app_orchestration_config_entity
=
self
.
_
convert_from_app_model_config_dict
(
app_orchestration_config_entity
=
self
.
convert_from_app_model_config_dict
(
tenant_id
=
tenant_id
,
tenant_id
=
tenant_id
,
app_model_config_dict
=
app_model_config_dict
app_model_config_dict
=
app_model_config_dict
),
),
...
@@ -234,7 +234,7 @@ class ApplicationManager:
...
@@ -234,7 +234,7 @@ class ApplicationManager:
logger
.
exception
(
e
)
logger
.
exception
(
e
)
raise
e
raise
e
def
_
convert_from_app_model_config_dict
(
self
,
tenant_id
:
str
,
app_model_config_dict
:
dict
)
\
def
convert_from_app_model_config_dict
(
self
,
tenant_id
:
str
,
app_model_config_dict
:
dict
)
\
->
AppOrchestrationConfigEntity
:
->
AppOrchestrationConfigEntity
:
"""
"""
Convert app model config dict to entity.
Convert app model config dict to entity.
...
@@ -384,8 +384,10 @@ class ApplicationManager:
...
@@ -384,8 +384,10 @@ class ApplicationManager:
config
=
external_data_tool
[
'config'
]
config
=
external_data_tool
[
'config'
]
)
)
)
)
properties
[
'variables'
]
=
[]
#
current
external_data_tools
#
variables and
external_data_tools
for
variable
in
copy_app_model_config_dict
.
get
(
'user_input_form'
,
[]):
for
variable
in
copy_app_model_config_dict
.
get
(
'user_input_form'
,
[]):
typ
=
list
(
variable
.
keys
())[
0
]
typ
=
list
(
variable
.
keys
())[
0
]
if
typ
==
'external_data_tool'
:
if
typ
==
'external_data_tool'
:
...
@@ -397,6 +399,30 @@ class ApplicationManager:
...
@@ -397,6 +399,30 @@ class ApplicationManager:
config
=
val
[
'config'
]
config
=
val
[
'config'
]
)
)
)
)
elif
typ
in
[
VariableEntity
.
Type
.
TEXT_INPUT
.
value
,
VariableEntity
.
Type
.
PARAGRAPH
.
value
]:
properties
[
'variables'
]
.
append
(
VariableEntity
(
type
=
VariableEntity
.
Type
.
TEXT_INPUT
,
variable
=
variable
[
typ
]
.
get
(
'variable'
),
description
=
variable
[
typ
]
.
get
(
'description'
),
label
=
variable
[
typ
]
.
get
(
'label'
),
required
=
variable
[
typ
]
.
get
(
'required'
,
False
),
max_length
=
variable
[
typ
]
.
get
(
'max_length'
),
default
=
variable
[
typ
]
.
get
(
'default'
),
)
)
elif
typ
==
VariableEntity
.
Type
.
SELECT
.
value
:
properties
[
'variables'
]
.
append
(
VariableEntity
(
type
=
VariableEntity
.
Type
.
SELECT
,
variable
=
variable
[
typ
]
.
get
(
'variable'
),
description
=
variable
[
typ
]
.
get
(
'description'
),
label
=
variable
[
typ
]
.
get
(
'label'
),
required
=
variable
[
typ
]
.
get
(
'required'
,
False
),
options
=
variable
[
typ
]
.
get
(
'options'
),
default
=
variable
[
typ
]
.
get
(
'default'
),
)
)
# show retrieve source
# show retrieve source
show_retrieve_source
=
False
show_retrieve_source
=
False
...
...
api/core/entities/application_entities.py
View file @
f0679472
...
@@ -9,26 +9,6 @@ from core.model_runtime.entities.message_entities import PromptMessageRole
...
@@ -9,26 +9,6 @@ from core.model_runtime.entities.message_entities import PromptMessageRole
from
core.model_runtime.entities.model_entities
import
AIModelEntity
from
core.model_runtime.entities.model_entities
import
AIModelEntity
class
AppMode
(
Enum
):
COMPLETION
=
'completion'
# will be deprecated in the future
WORKFLOW
=
'workflow'
# instead of 'completion'
CHAT
=
'chat'
AGENT
=
'agent'
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'AppMode'
:
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for
mode
in
cls
:
if
mode
.
value
==
value
:
return
mode
raise
ValueError
(
f
'invalid mode value {value}'
)
class
ModelConfigEntity
(
BaseModel
):
class
ModelConfigEntity
(
BaseModel
):
"""
"""
Model Config Entity.
Model Config Entity.
...
@@ -106,6 +86,38 @@ class PromptTemplateEntity(BaseModel):
...
@@ -106,6 +86,38 @@ class PromptTemplateEntity(BaseModel):
advanced_completion_prompt_template
:
Optional
[
AdvancedCompletionPromptTemplateEntity
]
=
None
advanced_completion_prompt_template
:
Optional
[
AdvancedCompletionPromptTemplateEntity
]
=
None
class
VariableEntity
(
BaseModel
):
"""
Variable Entity.
"""
class
Type
(
Enum
):
TEXT_INPUT
=
'text-input'
SELECT
=
'select'
PARAGRAPH
=
'paragraph'
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'VariableEntity.Type'
:
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for
mode
in
cls
:
if
mode
.
value
==
value
:
return
mode
raise
ValueError
(
f
'invalid variable type value {value}'
)
variable
:
str
label
:
str
description
:
Optional
[
str
]
=
None
type
:
Type
required
:
bool
=
False
max_length
:
Optional
[
int
]
=
None
options
:
Optional
[
list
[
str
]]
=
None
default
:
Optional
[
str
]
=
None
class
ExternalDataVariableEntity
(
BaseModel
):
class
ExternalDataVariableEntity
(
BaseModel
):
"""
"""
External Data Variable Entity.
External Data Variable Entity.
...
@@ -245,6 +257,7 @@ class AppOrchestrationConfigEntity(BaseModel):
...
@@ -245,6 +257,7 @@ class AppOrchestrationConfigEntity(BaseModel):
"""
"""
model_config
:
ModelConfigEntity
model_config
:
ModelConfigEntity
prompt_template
:
PromptTemplateEntity
prompt_template
:
PromptTemplateEntity
variables
:
list
[
VariableEntity
]
=
[]
external_data_variables
:
list
[
ExternalDataVariableEntity
]
=
[]
external_data_variables
:
list
[
ExternalDataVariableEntity
]
=
[]
agent
:
Optional
[
AgentEntity
]
=
None
agent
:
Optional
[
AgentEntity
]
=
None
...
@@ -256,7 +269,7 @@ class AppOrchestrationConfigEntity(BaseModel):
...
@@ -256,7 +269,7 @@ class AppOrchestrationConfigEntity(BaseModel):
show_retrieve_source
:
bool
=
False
show_retrieve_source
:
bool
=
False
more_like_this
:
bool
=
False
more_like_this
:
bool
=
False
speech_to_text
:
bool
=
False
speech_to_text
:
bool
=
False
text_to_speech
:
dict
=
{}
text_to_speech
:
Optional
[
TextToSpeechEntity
]
=
None
sensitive_word_avoidance
:
Optional
[
SensitiveWordAvoidanceEntity
]
=
None
sensitive_word_avoidance
:
Optional
[
SensitiveWordAvoidanceEntity
]
=
None
...
...
api/core/prompt/prompt_transform.py
View file @
f0679472
...
@@ -6,7 +6,6 @@ from typing import Optional, cast
...
@@ -6,7 +6,6 @@ from typing import Optional, cast
from
core.entities.application_entities
import
(
from
core.entities.application_entities
import
(
AdvancedCompletionPromptTemplateEntity
,
AdvancedCompletionPromptTemplateEntity
,
AppMode
,
ModelConfigEntity
,
ModelConfigEntity
,
PromptTemplateEntity
,
PromptTemplateEntity
,
)
)
...
@@ -24,6 +23,7 @@ from core.model_runtime.entities.model_entities import ModelPropertyKey
...
@@ -24,6 +23,7 @@ from core.model_runtime.entities.model_entities import ModelPropertyKey
from
core.model_runtime.model_providers.__base.large_language_model
import
LargeLanguageModel
from
core.model_runtime.model_providers.__base.large_language_model
import
LargeLanguageModel
from
core.prompt.prompt_builder
import
PromptBuilder
from
core.prompt.prompt_builder
import
PromptBuilder
from
core.prompt.prompt_template
import
PromptTemplateParser
from
core.prompt.prompt_template
import
PromptTemplateParser
from
models.model
import
AppMode
class
ModelMode
(
enum
.
Enum
):
class
ModelMode
(
enum
.
Enum
):
...
...
api/core/workflow/__init__.py
0 → 100644
View file @
f0679472
api/core/workflow/entities/NodeEntities.py
0 → 100644
View file @
f0679472
from
enum
import
Enum
class
NodeType
(
Enum
):
"""
Node Types.
"""
START
=
'start'
END
=
'end'
DIRECT_ANSWER
=
'direct-answer'
LLM
=
'llm'
KNOWLEDGE_RETRIEVAL
=
'knowledge-retrieval'
IF_ELSE
=
'if-else'
CODE
=
'code'
TEMPLATE_TRANSFORM
=
'template-transform'
QUESTION_CLASSIFIER
=
'question-classifier'
HTTP_REQUEST
=
'http-request'
TOOL
=
'tool'
VARIABLE_ASSIGNER
=
'variable-assigner'
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'BlockType'
:
"""
Get value of given block type.
:param value: block type value
:return: block type
"""
for
block_type
in
cls
:
if
block_type
.
value
==
value
:
return
block_type
raise
ValueError
(
f
'invalid block type value {value}'
)
api/core/workflow/entities/__init__.py
0 → 100644
View file @
f0679472
api/core/workflow/nodes/__init__.py
0 → 100644
View file @
f0679472
api/core/workflow/nodes/end/__init__.py
0 → 100644
View file @
f0679472
api/core/workflow/nodes/end/end_node.py
0 → 100644
View file @
f0679472
api/core/workflow/nodes/end/entities.py
0 → 100644
View file @
f0679472
from
enum
import
Enum
class
EndNodeOutputType
(
Enum
):
"""
END Node Output Types.
none, plain-text, structured
"""
NONE
=
'none'
PLAIN_TEXT
=
'plain-text'
STRUCTURED
=
'structured'
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'OutputType'
:
"""
Get value of given output type.
:param value: output type value
:return: output type
"""
for
output_type
in
cls
:
if
output_type
.
value
==
value
:
return
output_type
raise
ValueError
(
f
'invalid output type value {value}'
)
api/core/workflow/workflow_engine_manager.py
0 → 100644
View file @
f0679472
api/fields/annotation_fields.py
View file @
f0679472
...
@@ -2,12 +2,6 @@ from flask_restful import fields
...
@@ -2,12 +2,6 @@ from flask_restful import fields
from
libs.helper
import
TimestampField
from
libs.helper
import
TimestampField
account_fields
=
{
'id'
:
fields
.
String
,
'name'
:
fields
.
String
,
'email'
:
fields
.
String
}
annotation_fields
=
{
annotation_fields
=
{
"id"
:
fields
.
String
,
"id"
:
fields
.
String
,
...
@@ -15,7 +9,7 @@ annotation_fields = {
...
@@ -15,7 +9,7 @@ annotation_fields = {
"answer"
:
fields
.
Raw
(
attribute
=
'content'
),
"answer"
:
fields
.
Raw
(
attribute
=
'content'
),
"hit_count"
:
fields
.
Integer
,
"hit_count"
:
fields
.
Integer
,
"created_at"
:
TimestampField
,
"created_at"
:
TimestampField
,
# 'account': fields.Nested(account_fields, allow_null=True)
# 'account': fields.Nested(
simple_
account_fields, allow_null=True)
}
}
annotation_list_fields
=
{
annotation_list_fields
=
{
...
...
api/fields/conversation_fields.py
View file @
f0679472
from
flask_restful
import
fields
from
flask_restful
import
fields
from
fields.member_fields
import
simple_account_fields
from
libs.helper
import
TimestampField
from
libs.helper
import
TimestampField
...
@@ -8,31 +9,25 @@ class MessageTextField(fields.Raw):
...
@@ -8,31 +9,25 @@ class MessageTextField(fields.Raw):
return
value
[
0
][
'text'
]
if
value
else
''
return
value
[
0
][
'text'
]
if
value
else
''
account_fields
=
{
'id'
:
fields
.
String
,
'name'
:
fields
.
String
,
'email'
:
fields
.
String
}
feedback_fields
=
{
feedback_fields
=
{
'rating'
:
fields
.
String
,
'rating'
:
fields
.
String
,
'content'
:
fields
.
String
,
'content'
:
fields
.
String
,
'from_source'
:
fields
.
String
,
'from_source'
:
fields
.
String
,
'from_end_user_id'
:
fields
.
String
,
'from_end_user_id'
:
fields
.
String
,
'from_account'
:
fields
.
Nested
(
account_fields
,
allow_null
=
True
),
'from_account'
:
fields
.
Nested
(
simple_
account_fields
,
allow_null
=
True
),
}
}
annotation_fields
=
{
annotation_fields
=
{
'id'
:
fields
.
String
,
'id'
:
fields
.
String
,
'question'
:
fields
.
String
,
'question'
:
fields
.
String
,
'content'
:
fields
.
String
,
'content'
:
fields
.
String
,
'account'
:
fields
.
Nested
(
account_fields
,
allow_null
=
True
),
'account'
:
fields
.
Nested
(
simple_
account_fields
,
allow_null
=
True
),
'created_at'
:
TimestampField
'created_at'
:
TimestampField
}
}
annotation_hit_history_fields
=
{
annotation_hit_history_fields
=
{
'annotation_id'
:
fields
.
String
(
attribute
=
'id'
),
'annotation_id'
:
fields
.
String
(
attribute
=
'id'
),
'annotation_create_account'
:
fields
.
Nested
(
account_fields
,
allow_null
=
True
),
'annotation_create_account'
:
fields
.
Nested
(
simple_
account_fields
,
allow_null
=
True
),
'created_at'
:
TimestampField
'created_at'
:
TimestampField
}
}
...
...
api/fields/member_fields.py
0 → 100644
View file @
f0679472
from
flask_restful
import
fields
from
libs.helper
import
TimestampField
simple_account_fields
=
{
'id'
:
fields
.
String
,
'name'
:
fields
.
String
,
'email'
:
fields
.
String
}
account_fields
=
{
'id'
:
fields
.
String
,
'name'
:
fields
.
String
,
'avatar'
:
fields
.
String
,
'email'
:
fields
.
String
,
'is_password_set'
:
fields
.
Boolean
,
'interface_language'
:
fields
.
String
,
'interface_theme'
:
fields
.
String
,
'timezone'
:
fields
.
String
,
'last_login_at'
:
TimestampField
,
'last_login_ip'
:
fields
.
String
,
'created_at'
:
TimestampField
}
account_with_role_fields
=
{
'id'
:
fields
.
String
,
'name'
:
fields
.
String
,
'avatar'
:
fields
.
String
,
'email'
:
fields
.
String
,
'last_login_at'
:
TimestampField
,
'created_at'
:
TimestampField
,
'role'
:
fields
.
String
,
'status'
:
fields
.
String
,
}
account_with_role_list_fields
=
{
'accounts'
:
fields
.
List
(
fields
.
Nested
(
account_with_role_fields
))
}
api/fields/workflow_fields.py
0 → 100644
View file @
f0679472
import
json
from
flask_restful
import
fields
from
fields.member_fields
import
simple_account_fields
from
libs.helper
import
TimestampField
workflow_fields
=
{
'id'
:
fields
.
String
,
'graph'
:
fields
.
Raw
(
attribute
=
lambda
x
:
json
.
loads
(
x
.
graph
)
if
hasattr
(
x
,
'graph'
)
else
None
),
'created_by'
:
fields
.
Nested
(
simple_account_fields
,
attribute
=
'created_by_account'
),
'created_at'
:
TimestampField
,
'updated_by'
:
fields
.
Nested
(
simple_account_fields
,
attribute
=
'updated_by_account'
,
allow_null
=
True
),
'updated_at'
:
TimestampField
}
api/migrations/versions/b289e2408ee2_add_workflow.py
View file @
f0679472
...
@@ -102,7 +102,7 @@ def upgrade():
...
@@ -102,7 +102,7 @@ def upgrade():
sa
.
PrimaryKeyConstraint
(
'id'
,
name
=
'workflow_pkey'
)
sa
.
PrimaryKeyConstraint
(
'id'
,
name
=
'workflow_pkey'
)
)
)
with
op
.
batch_alter_table
(
'workflows'
,
schema
=
None
)
as
batch_op
:
with
op
.
batch_alter_table
(
'workflows'
,
schema
=
None
)
as
batch_op
:
batch_op
.
create_index
(
'workflow_version_idx'
,
[
'tenant_id'
,
'app_id'
,
'
type'
,
'
version'
],
unique
=
False
)
batch_op
.
create_index
(
'workflow_version_idx'
,
[
'tenant_id'
,
'app_id'
,
'version'
],
unique
=
False
)
with
op
.
batch_alter_table
(
'app_model_configs'
,
schema
=
None
)
as
batch_op
:
with
op
.
batch_alter_table
(
'app_model_configs'
,
schema
=
None
)
as
batch_op
:
batch_op
.
add_column
(
sa
.
Column
(
'chatbot_app_engine'
,
sa
.
String
(
length
=
255
),
server_default
=
sa
.
text
(
"'normal'::character varying"
),
nullable
=
False
))
batch_op
.
add_column
(
sa
.
Column
(
'chatbot_app_engine'
,
sa
.
String
(
length
=
255
),
server_default
=
sa
.
text
(
"'normal'::character varying"
),
nullable
=
False
))
...
...
api/models/model.py
View file @
f0679472
import
json
import
json
import
uuid
import
uuid
from
enum
import
Enum
from
typing
import
Optional
from
flask
import
current_app
,
request
from
flask
import
current_app
,
request
from
flask_login
import
UserMixin
from
flask_login
import
UserMixin
...
@@ -25,6 +27,25 @@ class DifySetup(db.Model):
...
@@ -25,6 +27,25 @@ class DifySetup(db.Model):
setup_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
setup_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
class
AppMode
(
Enum
):
WORKFLOW
=
'workflow'
CHAT
=
'chat'
AGENT
=
'agent'
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'AppMode'
:
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for
mode
in
cls
:
if
mode
.
value
==
value
:
return
mode
raise
ValueError
(
f
'invalid mode value {value}'
)
class
App
(
db
.
Model
):
class
App
(
db
.
Model
):
__tablename__
=
'apps'
__tablename__
=
'apps'
__table_args__
=
(
__table_args__
=
(
...
@@ -56,7 +77,7 @@ class App(db.Model):
...
@@ -56,7 +77,7 @@ class App(db.Model):
return
site
return
site
@
property
@
property
def
app_model_config
(
self
):
def
app_model_config
(
self
)
->
Optional
[
'AppModelConfig'
]
:
app_model_config
=
db
.
session
.
query
(
AppModelConfig
)
.
filter
(
app_model_config
=
db
.
session
.
query
(
AppModelConfig
)
.
filter
(
AppModelConfig
.
id
==
self
.
app_model_config_id
)
.
first
()
AppModelConfig
.
id
==
self
.
app_model_config_id
)
.
first
()
return
app_model_config
return
app_model_config
...
@@ -130,6 +151,12 @@ class App(db.Model):
...
@@ -130,6 +151,12 @@ class App(db.Model):
return
deleted_tools
return
deleted_tools
class
ChatbotAppEngine
(
Enum
):
NORMAL
=
'normal'
WORKFLOW
=
'workflow'
class
AppModelConfig
(
db
.
Model
):
class
AppModelConfig
(
db
.
Model
):
__tablename__
=
'app_model_configs'
__tablename__
=
'app_model_configs'
__table_args__
=
(
__table_args__
=
(
...
...
api/models/workflow.py
View file @
f0679472
from
enum
import
Enum
from
typing
import
Union
from
sqlalchemy.dialects.postgresql
import
UUID
from
sqlalchemy.dialects.postgresql
import
UUID
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
models.account
import
Account
from
models.model
import
AppMode
class
WorkflowType
(
Enum
):
"""
Workflow Type Enum
"""
WORKFLOW
=
'workflow'
CHAT
=
'chat'
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'WorkflowType'
:
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for
mode
in
cls
:
if
mode
.
value
==
value
:
return
mode
raise
ValueError
(
f
'invalid workflow type value {value}'
)
@
classmethod
def
from_app_mode
(
cls
,
app_mode
:
Union
[
str
,
AppMode
])
->
'WorkflowType'
:
"""
Get workflow type from app mode.
:param app_mode: app mode
:return: workflow type
"""
app_mode
=
app_mode
if
isinstance
(
app_mode
,
AppMode
)
else
AppMode
.
value_of
(
app_mode
)
return
cls
.
WORKFLOW
if
app_mode
==
AppMode
.
WORKFLOW
else
cls
.
CHAT
class
Workflow
(
db
.
Model
):
class
Workflow
(
db
.
Model
):
...
@@ -39,7 +76,7 @@ class Workflow(db.Model):
...
@@ -39,7 +76,7 @@ class Workflow(db.Model):
__tablename__
=
'workflows'
__tablename__
=
'workflows'
__table_args__
=
(
__table_args__
=
(
db
.
PrimaryKeyConstraint
(
'id'
,
name
=
'workflow_pkey'
),
db
.
PrimaryKeyConstraint
(
'id'
,
name
=
'workflow_pkey'
),
db
.
Index
(
'workflow_version_idx'
,
'tenant_id'
,
'app_id'
,
'
type'
,
'
version'
),
db
.
Index
(
'workflow_version_idx'
,
'tenant_id'
,
'app_id'
,
'version'
),
)
)
id
=
db
.
Column
(
UUID
,
server_default
=
db
.
text
(
'uuid_generate_v4()'
))
id
=
db
.
Column
(
UUID
,
server_default
=
db
.
text
(
'uuid_generate_v4()'
))
...
@@ -53,6 +90,14 @@ class Workflow(db.Model):
...
@@ -53,6 +90,14 @@ class Workflow(db.Model):
updated_by
=
db
.
Column
(
UUID
)
updated_by
=
db
.
Column
(
UUID
)
updated_at
=
db
.
Column
(
db
.
DateTime
)
updated_at
=
db
.
Column
(
db
.
DateTime
)
@
property
def
created_by_account
(
self
):
return
Account
.
query
.
get
(
self
.
created_by
)
@
property
def
updated_by_account
(
self
):
return
Account
.
query
.
get
(
self
.
updated_by
)
class
WorkflowRun
(
db
.
Model
):
class
WorkflowRun
(
db
.
Model
):
"""
"""
...
@@ -116,6 +161,14 @@ class WorkflowRun(db.Model):
...
@@ -116,6 +161,14 @@ class WorkflowRun(db.Model):
created_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
created_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
finished_at
=
db
.
Column
(
db
.
DateTime
)
finished_at
=
db
.
Column
(
db
.
DateTime
)
@
property
def
created_by_account
(
self
):
return
Account
.
query
.
get
(
self
.
created_by
)
@
property
def
updated_by_account
(
self
):
return
Account
.
query
.
get
(
self
.
updated_by
)
class
WorkflowNodeExecution
(
db
.
Model
):
class
WorkflowNodeExecution
(
db
.
Model
):
"""
"""
...
...
api/services/advanced_prompt_template_service.py
View file @
f0679472
import
copy
import
copy
from
core.entities.application_entities
import
AppMode
from
core.prompt.advanced_prompt_templates
import
(
from
core.prompt.advanced_prompt_templates
import
(
BAICHUAN_CHAT_APP_CHAT_PROMPT_CONFIG
,
BAICHUAN_CHAT_APP_CHAT_PROMPT_CONFIG
,
BAICHUAN_CHAT_APP_COMPLETION_PROMPT_CONFIG
,
BAICHUAN_CHAT_APP_COMPLETION_PROMPT_CONFIG
,
...
@@ -14,6 +13,7 @@ from core.prompt.advanced_prompt_templates import (
...
@@ -14,6 +13,7 @@ from core.prompt.advanced_prompt_templates import (
COMPLETION_APP_COMPLETION_PROMPT_CONFIG
,
COMPLETION_APP_COMPLETION_PROMPT_CONFIG
,
CONTEXT
,
CONTEXT
,
)
)
from
models.model
import
AppMode
class
AdvancedPromptTemplateService
:
class
AdvancedPromptTemplateService
:
...
...
api/services/app_model_config_service.py
View file @
f0679472
...
@@ -9,6 +9,7 @@ from core.model_runtime.model_providers import model_provider_factory
...
@@ -9,6 +9,7 @@ from core.model_runtime.model_providers import model_provider_factory
from
core.moderation.factory
import
ModerationFactory
from
core.moderation.factory
import
ModerationFactory
from
core.provider_manager
import
ProviderManager
from
core.provider_manager
import
ProviderManager
from
models.account
import
Account
from
models.account
import
Account
from
models.model
import
AppMode
from
services.dataset_service
import
DatasetService
from
services.dataset_service
import
DatasetService
SUPPORT_TOOLS
=
[
"dataset"
,
"google_search"
,
"web_reader"
,
"wikipedia"
,
"current_datetime"
]
SUPPORT_TOOLS
=
[
"dataset"
,
"google_search"
,
"web_reader"
,
"wikipedia"
,
"current_datetime"
]
...
@@ -315,9 +316,6 @@ class AppModelConfigService:
...
@@ -315,9 +316,6 @@ class AppModelConfigService:
if
"tool_parameters"
not
in
tool
:
if
"tool_parameters"
not
in
tool
:
raise
ValueError
(
"tool_parameters is required in agent_mode.tools"
)
raise
ValueError
(
"tool_parameters is required in agent_mode.tools"
)
# dataset_query_variable
cls
.
is_dataset_query_variable_valid
(
config
,
app_mode
)
# advanced prompt validation
# advanced prompt validation
cls
.
is_advanced_prompt_valid
(
config
,
app_mode
)
cls
.
is_advanced_prompt_valid
(
config
,
app_mode
)
...
@@ -443,21 +441,6 @@ class AppModelConfigService:
...
@@ -443,21 +441,6 @@ class AppModelConfigService:
config
=
config
config
=
config
)
)
@
classmethod
def
is_dataset_query_variable_valid
(
cls
,
config
:
dict
,
mode
:
str
)
->
None
:
# Only check when mode is completion
if
mode
!=
'completion'
:
return
agent_mode
=
config
.
get
(
"agent_mode"
,
{})
tools
=
agent_mode
.
get
(
"tools"
,
[])
dataset_exists
=
"dataset"
in
str
(
tools
)
dataset_query_variable
=
config
.
get
(
"dataset_query_variable"
)
if
dataset_exists
and
not
dataset_query_variable
:
raise
ValueError
(
"Dataset query variable is required when dataset is exist"
)
@
classmethod
@
classmethod
def
is_advanced_prompt_valid
(
cls
,
config
:
dict
,
app_mode
:
str
)
->
None
:
def
is_advanced_prompt_valid
(
cls
,
config
:
dict
,
app_mode
:
str
)
->
None
:
# prompt_type
# prompt_type
...
...
api/services/completion_service.py
View file @
f0679472
...
@@ -8,12 +8,10 @@ from core.application_manager import ApplicationManager
...
@@ -8,12 +8,10 @@ from core.application_manager import ApplicationManager
from
core.entities.application_entities
import
InvokeFrom
from
core.entities.application_entities
import
InvokeFrom
from
core.file.message_file_parser
import
MessageFileParser
from
core.file.message_file_parser
import
MessageFileParser
from
extensions.ext_database
import
db
from
extensions.ext_database
import
db
from
models.model
import
Account
,
App
,
AppModelConfig
,
Conversation
,
EndUser
,
Message
from
models.model
import
Account
,
App
,
AppModelConfig
,
Conversation
,
EndUser
from
services.app_model_config_service
import
AppModelConfigService
from
services.app_model_config_service
import
AppModelConfigService
from
services.errors.app
import
MoreLikeThisDisabledError
from
services.errors.app_model_config
import
AppModelConfigBrokenError
from
services.errors.app_model_config
import
AppModelConfigBrokenError
from
services.errors.conversation
import
ConversationCompletedError
,
ConversationNotExistsError
from
services.errors.conversation
import
ConversationCompletedError
,
ConversationNotExistsError
from
services.errors.message
import
MessageNotExistsError
class
CompletionService
:
class
CompletionService
:
...
@@ -157,62 +155,6 @@ class CompletionService:
...
@@ -157,62 +155,6 @@ class CompletionService:
}
}
)
)
@
classmethod
def
generate_more_like_this
(
cls
,
app_model
:
App
,
user
:
Union
[
Account
,
EndUser
],
message_id
:
str
,
invoke_from
:
InvokeFrom
,
streaming
:
bool
=
True
)
\
->
Union
[
dict
,
Generator
]:
if
not
user
:
raise
ValueError
(
'user cannot be None'
)
message
=
db
.
session
.
query
(
Message
)
.
filter
(
Message
.
id
==
message_id
,
Message
.
app_id
==
app_model
.
id
,
Message
.
from_source
==
(
'api'
if
isinstance
(
user
,
EndUser
)
else
'console'
),
Message
.
from_end_user_id
==
(
user
.
id
if
isinstance
(
user
,
EndUser
)
else
None
),
Message
.
from_account_id
==
(
user
.
id
if
isinstance
(
user
,
Account
)
else
None
),
)
.
first
()
if
not
message
:
raise
MessageNotExistsError
()
current_app_model_config
=
app_model
.
app_model_config
more_like_this
=
current_app_model_config
.
more_like_this_dict
if
not
current_app_model_config
.
more_like_this
or
more_like_this
.
get
(
"enabled"
,
False
)
is
False
:
raise
MoreLikeThisDisabledError
()
app_model_config
=
message
.
app_model_config
model_dict
=
app_model_config
.
model_dict
completion_params
=
model_dict
.
get
(
'completion_params'
)
completion_params
[
'temperature'
]
=
0.9
model_dict
[
'completion_params'
]
=
completion_params
app_model_config
.
model
=
json
.
dumps
(
model_dict
)
# parse files
message_file_parser
=
MessageFileParser
(
tenant_id
=
app_model
.
tenant_id
,
app_id
=
app_model
.
id
)
file_objs
=
message_file_parser
.
transform_message_files
(
message
.
files
,
app_model_config
)
application_manager
=
ApplicationManager
()
return
application_manager
.
generate
(
tenant_id
=
app_model
.
tenant_id
,
app_id
=
app_model
.
id
,
app_model_config_id
=
app_model_config
.
id
,
app_model_config_dict
=
app_model_config
.
to_dict
(),
app_model_config_override
=
True
,
user
=
user
,
invoke_from
=
invoke_from
,
inputs
=
message
.
inputs
,
query
=
message
.
query
,
files
=
file_objs
,
conversation
=
None
,
stream
=
streaming
,
extras
=
{
"auto_generate_conversation_name"
:
False
}
)
@
classmethod
@
classmethod
def
get_cleaned_inputs
(
cls
,
user_inputs
:
dict
,
app_model_config
:
AppModelConfig
):
def
get_cleaned_inputs
(
cls
,
user_inputs
:
dict
,
app_model_config
:
AppModelConfig
):
if
user_inputs
is
None
:
if
user_inputs
is
None
:
...
...
api/services/errors/__init__.py
View file @
f0679472
# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-
__all__
=
[
__all__
=
[
'base'
,
'conversation'
,
'message'
,
'index'
,
'app_model_config'
,
'account'
,
'document'
,
'dataset'
,
'base'
,
'conversation'
,
'message'
,
'index'
,
'app_model_config'
,
'account'
,
'document'
,
'dataset'
,
'
app'
,
'
completion'
,
'audio'
,
'file'
'completion'
,
'audio'
,
'file'
]
]
from
.
import
*
from
.
import
*
api/services/errors/app.py
deleted
100644 → 0
View file @
9ad6bd78
class
MoreLikeThisDisabledError
(
Exception
):
pass
api/services/workflow/__init__.py
0 → 100644
View file @
f0679472
api/services/workflow/defaults.py
0 → 100644
View file @
f0679472
# default block config
default_block_configs
=
[
{
"type"
:
"llm"
,
"config"
:
{
"prompt_templates"
:
{
"chat_model"
:
{
"prompts"
:
[
{
"role"
:
"system"
,
"text"
:
"You are a helpful AI assistant."
}
]
},
"completion_model"
:
{
"conversation_histories_role"
:
{
"user_prefix"
:
"Human"
,
"assistant_prefix"
:
"Assistant"
},
"prompt"
:
{
"text"
:
"Here is the chat histories between human and assistant, inside "
"<histories></histories> XML tags.
\n\n
<histories>
\n
{{"
"#histories#}}
\n
</histories>
\n\n\n
Human: {{#query#}}
\n\n
Assistant:"
},
"stop"
:
[
"Human:"
]
}
}
}
},
{
"type"
:
"code"
,
"config"
:
{
"variables"
:
[
{
"variable"
:
"arg1"
,
"value_selector"
:
[]
},
{
"variable"
:
"arg2"
,
"value_selector"
:
[]
}
],
"code_language"
:
"python3"
,
"code"
:
"def main(
\n
arg1: int,
\n
arg2: int,
\n
) -> int:
\n
return {
\n
\"
result
\"
: arg1 "
"+ arg2
\n
}"
,
"outputs"
:
[
{
"variable"
:
"result"
,
"variable_type"
:
"number"
}
]
}
},
{
"type"
:
"template-transform"
,
"config"
:
{
"variables"
:
[
{
"variable"
:
"arg1"
,
"value_selector"
:
[]
}
],
"template"
:
"{{ arg1 }}"
}
},
{
"type"
:
"question-classifier"
,
"config"
:
{
"instructions"
:
""
# TODO
}
}
]
api/services/workflow/workflow_converter.py
0 → 100644
View file @
f0679472
import
json
from
typing
import
Optional
from
core.application_manager
import
ApplicationManager
from
core.entities.application_entities
import
ModelConfigEntity
,
PromptTemplateEntity
,
FileUploadEntity
,
\
ExternalDataVariableEntity
,
DatasetEntity
,
VariableEntity
from
core.model_runtime.utils
import
helper
from
core.workflow.entities.NodeEntities
import
NodeType
from
core.workflow.nodes.end.entities
import
EndNodeOutputType
from
extensions.ext_database
import
db
from
models.account
import
Account
from
models.model
import
App
,
AppMode
,
ChatbotAppEngine
from
models.workflow
import
Workflow
,
WorkflowType
class
WorkflowConverter
:
"""
App Convert to Workflow Mode
"""
def
convert_to_workflow
(
self
,
app_model
:
App
,
account
:
Account
)
->
Workflow
:
"""
Convert to workflow mode
- basic mode of chatbot app
- advanced mode of assistant app (for migration)
- completion app (for migration)
:param app_model: App instance
:param account: Account instance
:return: workflow instance
"""
# get original app config
app_model_config
=
app_model
.
app_model_config
# convert app model config
application_manager
=
ApplicationManager
()
application_manager
.
convert_from_app_model_config_dict
(
tenant_id
=
app_model
.
tenant_id
,
app_model_config_dict
=
app_model_config
.
to_dict
()
)
# init workflow graph
graph
=
{
"nodes"
:
[],
"edges"
:
[]
}
# Convert list:
# - variables -> start
# - model_config -> llm
# - prompt_template -> llm
# - file_upload -> llm
# - external_data_variables -> http-request
# - dataset -> knowledge-retrieval
# - show_retrieve_source -> knowledge-retrieval
# convert to start node
start_node
=
self
.
_convert_to_start_node
(
variables
=
app_model_config
.
variables
)
graph
[
'nodes'
]
.
append
(
start_node
)
# convert to http request node
if
app_model_config
.
external_data_variables
:
http_request_node
=
self
.
_convert_to_http_request_node
(
external_data_variables
=
app_model_config
.
external_data_variables
)
graph
=
self
.
_append_node
(
graph
,
http_request_node
)
# convert to knowledge retrieval node
if
app_model_config
.
dataset
:
knowledge_retrieval_node
=
self
.
_convert_to_knowledge_retrieval_node
(
dataset
=
app_model_config
.
dataset
,
show_retrieve_source
=
app_model_config
.
show_retrieve_source
)
graph
=
self
.
_append_node
(
graph
,
knowledge_retrieval_node
)
# convert to llm node
llm_node
=
self
.
_convert_to_llm_node
(
model_config
=
app_model_config
.
model_config
,
prompt_template
=
app_model_config
.
prompt_template
,
file_upload
=
app_model_config
.
file_upload
)
graph
=
self
.
_append_node
(
graph
,
llm_node
)
# convert to end node by app mode
end_node
=
self
.
_convert_to_end_node
(
app_model
=
app_model
)
graph
=
self
.
_append_node
(
graph
,
end_node
)
# get new app mode
app_mode
=
self
.
_get_new_app_mode
(
app_model
)
# create workflow record
workflow
=
Workflow
(
tenant_id
=
app_model
.
tenant_id
,
app_id
=
app_model
.
id
,
type
=
WorkflowType
.
from_app_mode
(
app_mode
)
.
value
,
version
=
'draft'
,
graph
=
json
.
dumps
(
graph
),
created_by
=
account
.
id
)
db
.
session
.
add
(
workflow
)
db
.
session
.
flush
()
# create new app model config record
new_app_model_config
=
app_model_config
.
copy
()
new_app_model_config
.
external_data_tools
=
''
new_app_model_config
.
model
=
''
new_app_model_config
.
user_input_form
=
''
new_app_model_config
.
dataset_query_variable
=
None
new_app_model_config
.
pre_prompt
=
None
new_app_model_config
.
agent_mode
=
''
new_app_model_config
.
prompt_type
=
'simple'
new_app_model_config
.
chat_prompt_config
=
''
new_app_model_config
.
completion_prompt_config
=
''
new_app_model_config
.
dataset_configs
=
''
new_app_model_config
.
chatbot_app_engine
=
ChatbotAppEngine
.
WORKFLOW
.
value
\
if
app_mode
==
AppMode
.
CHAT
else
ChatbotAppEngine
.
NORMAL
.
value
new_app_model_config
.
workflow_id
=
workflow
.
id
db
.
session
.
add
(
new_app_model_config
)
db
.
session
.
commit
()
return
workflow
def
_convert_to_start_node
(
self
,
variables
:
list
[
VariableEntity
])
->
dict
:
"""
Convert to Start Node
:param variables: list of variables
:return:
"""
return
{
"id"
:
"start"
,
"position"
:
None
,
"data"
:
{
"title"
:
"START"
,
"type"
:
NodeType
.
START
.
value
,
"variables"
:
[
helper
.
dump_model
(
v
)
for
v
in
variables
]
}
}
def
_convert_to_http_request_node
(
self
,
external_data_variables
:
list
[
ExternalDataVariableEntity
])
->
dict
:
"""
Convert API Based Extension to HTTP Request Node
:param external_data_variables: list of external data variables
:return:
"""
# TODO: implement
pass
def
_convert_to_knowledge_retrieval_node
(
self
,
new_app_mode
:
AppMode
,
dataset
:
DatasetEntity
)
->
dict
:
"""
Convert datasets to Knowledge Retrieval Node
:param new_app_mode: new app mode
:param dataset: dataset
:return:
"""
# TODO: implement
if
new_app_mode
==
AppMode
.
CHAT
:
query_variable_selector
=
[
"start"
,
"sys.query"
]
else
:
pass
return
{
"id"
:
"knowledge-retrieval"
,
"position"
:
None
,
"data"
:
{
"title"
:
"KNOWLEDGE RETRIEVAL"
,
"type"
:
NodeType
.
KNOWLEDGE_RETRIEVAL
.
value
,
}
}
def
_convert_to_llm_node
(
self
,
model_config
:
ModelConfigEntity
,
prompt_template
:
PromptTemplateEntity
,
file_upload
:
Optional
[
FileUploadEntity
]
=
None
)
->
dict
:
"""
Convert to LLM Node
:param model_config: model config
:param prompt_template: prompt template
:param file_upload: file upload config (optional)
"""
# TODO: implement
pass
def
_convert_to_end_node
(
self
,
app_model
:
App
)
->
dict
:
"""
Convert to End Node
:param app_model: App instance
:return:
"""
if
app_model
.
mode
==
AppMode
.
CHAT
.
value
:
return
{
"id"
:
"end"
,
"position"
:
None
,
"data"
:
{
"title"
:
"END"
,
"type"
:
NodeType
.
END
.
value
,
}
}
elif
app_model
.
mode
==
"completion"
:
# for original completion app
return
{
"id"
:
"end"
,
"position"
:
None
,
"data"
:
{
"title"
:
"END"
,
"type"
:
NodeType
.
END
.
value
,
"outputs"
:
{
"type"
:
EndNodeOutputType
.
PLAIN_TEXT
.
value
,
"plain_text_selector"
:
[
"llm"
,
"text"
]
}
}
}
def
_create_edge
(
self
,
source
:
str
,
target
:
str
)
->
dict
:
"""
Create Edge
:param source: source node id
:param target: target node id
:return:
"""
return
{
"id"
:
f
"{source}-{target}"
,
"source"
:
source
,
"target"
:
target
}
def
_append_node
(
self
,
graph
:
dict
,
node
:
dict
)
->
dict
:
"""
Append Node to Graph
:param graph: Graph, include: nodes, edges
:param node: Node to append
:return:
"""
previous_node
=
graph
[
'nodes'
][
-
1
]
graph
[
'nodes'
]
.
append
(
node
)
graph
[
'edges'
]
.
append
(
self
.
_create_edge
(
previous_node
[
'id'
],
node
[
'id'
]))
return
graph
def
_get_new_app_mode
(
self
,
app_model
:
App
)
->
AppMode
:
"""
Get new app mode
:param app_model: App instance
:return: AppMode
"""
if
app_model
.
mode
==
"completion"
:
return
AppMode
.
WORKFLOW
else
:
return
AppMode
.
value_of
(
app_model
.
mode
)
api/services/workflow_service.py
0 → 100644
View file @
f0679472
import
json
from
datetime
import
datetime
from
extensions.ext_database
import
db
from
models.account
import
Account
from
models.model
import
App
,
ChatbotAppEngine
from
models.workflow
import
Workflow
,
WorkflowType
from
services.workflow.defaults
import
default_block_configs
from
services.workflow.workflow_converter
import
WorkflowConverter
class
WorkflowService
:
"""
Workflow Service
"""
def
get_draft_workflow
(
self
,
app_model
:
App
)
->
Workflow
:
"""
Get draft workflow
"""
# fetch draft workflow by app_model
workflow
=
db
.
session
.
query
(
Workflow
)
.
filter
(
Workflow
.
tenant_id
==
app_model
.
tenant_id
,
Workflow
.
app_id
==
app_model
.
id
,
Workflow
.
version
==
'draft'
)
.
first
()
# return draft workflow
return
workflow
def
sync_draft_workflow
(
self
,
app_model
:
App
,
graph
:
dict
,
account
:
Account
)
->
Workflow
:
"""
Sync draft workflow
"""
# fetch draft workflow by app_model
workflow
=
self
.
get_draft_workflow
(
app_model
=
app_model
)
# create draft workflow if not found
if
not
workflow
:
workflow
=
Workflow
(
tenant_id
=
app_model
.
tenant_id
,
app_id
=
app_model
.
id
,
type
=
WorkflowType
.
from_app_mode
(
app_model
.
mode
)
.
value
,
version
=
'draft'
,
graph
=
json
.
dumps
(
graph
),
created_by
=
account
.
id
)
db
.
session
.
add
(
workflow
)
# update draft workflow if found
else
:
workflow
.
graph
=
json
.
dumps
(
graph
)
workflow
.
updated_by
=
account
.
id
workflow
.
updated_at
=
datetime
.
utcnow
()
# commit db session changes
db
.
session
.
commit
()
# return draft workflow
return
workflow
def
get_default_block_configs
(
self
)
->
dict
:
"""
Get default block configs
"""
# return default block config
return
default_block_configs
def
chatbot_convert_to_workflow
(
self
,
app_model
:
App
)
->
Workflow
:
"""
basic mode of chatbot app to workflow
:param app_model: App instance
:return:
"""
# check if chatbot app is in basic mode
if
app_model
.
app_model_config
.
chatbot_app_engine
!=
ChatbotAppEngine
.
NORMAL
:
raise
ValueError
(
'Chatbot app already in workflow mode'
)
# convert to workflow mode
workflow_converter
=
WorkflowConverter
()
workflow
=
workflow_converter
.
convert_to_workflow
(
app_model
=
app_model
)
return
workflow
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