diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 7b2411b96f..66bcbccefe 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -34,7 +34,7 @@ class AppListApi(Resource): parser = reqparse.RequestParser() parser.add_argument('page', type=inputs.int_range(1, 99999), required=False, default=1, location='args') parser.add_argument('limit', type=inputs.int_range(1, 100), required=False, default=20, location='args') - parser.add_argument('mode', type=str, choices=['chat', 'workflow', 'agent', 'channel', 'all'], default='all', location='args', required=False) + parser.add_argument('mode', type=str, choices=['chat', 'workflow', 'agent-chat', 'channel', 'all'], default='all', location='args', required=False) parser.add_argument('name', type=str, location='args', required=False) args = parser.parse_args() diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 54585d8519..5dfb2b1443 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -1,3 +1,5 @@ +import json + from flask_restful import Resource, marshal_with, reqparse from controllers.console import api @@ -147,7 +149,7 @@ class PublishedWorkflowApi(Resource): } -class DefaultBlockConfigApi(Resource): +class DefaultBlockConfigsApi(Resource): @setup_required @login_required @account_initialization_required @@ -161,6 +163,34 @@ class DefaultBlockConfigApi(Resource): return workflow_service.get_default_block_configs() +class DefaultBlockConfigApi(Resource): + @setup_required + @login_required + @account_initialization_required + @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) + def get(self, app_model: App, block_type: str): + """ + Get default block config + """ + parser = reqparse.RequestParser() + parser.add_argument('q', type=str, location='args') + args = parser.parse_args() + + filters = None + if args.get('q'): + try: + filters = json.loads(args.get('q')) + except json.JSONDecodeError: + raise ValueError('Invalid filters') + + # Get default block configs + workflow_service = WorkflowService() + return workflow_service.get_default_block_config( + node_type=block_type, + filters=filters + ) + + class ConvertToWorkflowApi(Resource): @setup_required @login_required @@ -188,5 +218,6 @@ api.add_resource(DraftWorkflowRunApi, '/apps//workflows/draft/run') api.add_resource(WorkflowTaskStopApi, '/apps//workflows/tasks//stop') api.add_resource(DraftWorkflowNodeRunApi, '/apps//workflows/draft/nodes//run') api.add_resource(PublishedWorkflowApi, '/apps//workflows/published') -api.add_resource(DefaultBlockConfigApi, '/apps//workflows/default-workflow-block-configs') +api.add_resource(DefaultBlockConfigsApi, '/apps//workflows/default-workflow-block-configs') +api.add_resource(DefaultBlockConfigApi, '/apps//workflows/default-workflow-block-configs/:block_type') api.add_resource(ConvertToWorkflowApi, '/apps//convert-to-workflow') diff --git a/api/core/app/apps/advanced_chat/generate_task_pipeline.py b/api/core/app/apps/advanced_chat/generate_task_pipeline.py index 2aa649afea..77e779a0ad 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -30,7 +30,7 @@ from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.errors.invoke import InvokeAuthorizationError, InvokeError from core.moderation.output_moderation import ModerationRule, OutputModeration from core.tools.tool_file_manager import ToolFileManager -from core.workflow.entities.NodeEntities import NodeType +from core.workflow.entities.node_entities import NodeType from events.message_event import message_was_created from extensions.ext_database import db from models.model import Conversation, Message, MessageFile diff --git a/api/core/workflow/entities/NodeEntities.py b/api/core/workflow/entities/node_entities.py similarity index 100% rename from api/core/workflow/entities/NodeEntities.py rename to api/core/workflow/entities/node_entities.py diff --git a/api/core/workflow/nodes/base_node.py b/api/core/workflow/nodes/base_node.py new file mode 100644 index 0000000000..665338af08 --- /dev/null +++ b/api/core/workflow/nodes/base_node.py @@ -0,0 +1,12 @@ +from typing import Optional + + +class BaseNode: + @classmethod + def get_default_config(cls, filters: Optional[dict] = None) -> dict: + """ + Get default config of node. + :param filters: filter by node config parameters. + :return: + """ + return {} diff --git a/api/core/workflow/nodes/code/__init__.py b/api/core/workflow/nodes/code/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/code/code_node.py b/api/core/workflow/nodes/code/code_node.py new file mode 100644 index 0000000000..7e69f91d11 --- /dev/null +++ b/api/core/workflow/nodes/code/code_node.py @@ -0,0 +1,64 @@ +from typing import Optional + +from core.workflow.nodes.base_node import BaseNode + + +class CodeNode(BaseNode): + @classmethod + def get_default_config(cls, filters: Optional[dict] = None) -> dict: + """ + Get default config of node. + :param filters: filter by node config parameters. + :return: + """ + if filters and filters.get("code_language") == "javascript": + return { + "type": "code", + "config": { + "variables": [ + { + "variable": "arg1", + "value_selector": [] + }, + { + "variable": "arg2", + "value_selector": [] + } + ], + "code_language": "javascript", + "code": "async function main(arg1, arg2) {\n return new Promise((resolve, reject) => {" + "\n if (true) {\n resolve({\n \"result\": arg1 + arg2" + "\n });\n } else {\n reject(\"e\");\n }\n });\n}", + "outputs": [ + { + "variable": "result", + "variable_type": "number" + } + ] + } + } + + return { + "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" + } + ] + } + } diff --git a/api/core/workflow/nodes/direct_answer/__init__.py b/api/core/workflow/nodes/direct_answer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/direct_answer/direct_answer_node.py b/api/core/workflow/nodes/direct_answer/direct_answer_node.py new file mode 100644 index 0000000000..c6013974b8 --- /dev/null +++ b/api/core/workflow/nodes/direct_answer/direct_answer_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class DirectAnswerNode(BaseNode): + pass diff --git a/api/core/workflow/nodes/end/end_node.py b/api/core/workflow/nodes/end/end_node.py index e69de29bb2..f9aea89af7 100644 --- a/api/core/workflow/nodes/end/end_node.py +++ b/api/core/workflow/nodes/end/end_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class EndNode(BaseNode): + pass diff --git a/api/core/workflow/nodes/http_request/__init__.py b/api/core/workflow/nodes/http_request/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/http_request/http_request_node.py b/api/core/workflow/nodes/http_request/http_request_node.py new file mode 100644 index 0000000000..5be25a9834 --- /dev/null +++ b/api/core/workflow/nodes/http_request/http_request_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class HttpRequestNode(BaseNode): + pass diff --git a/api/core/workflow/nodes/if_else/__init__.py b/api/core/workflow/nodes/if_else/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/if_else/if_else_node.py b/api/core/workflow/nodes/if_else/if_else_node.py new file mode 100644 index 0000000000..98a5c85db2 --- /dev/null +++ b/api/core/workflow/nodes/if_else/if_else_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class IfElseNode(BaseNode): + pass diff --git a/api/core/workflow/nodes/knowledge_retrieval/__init__.py b/api/core/workflow/nodes/knowledge_retrieval/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py new file mode 100644 index 0000000000..c6dd624921 --- /dev/null +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class KnowledgeRetrievalNode(BaseNode): + pass diff --git a/api/core/workflow/nodes/llm/__init__.py b/api/core/workflow/nodes/llm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/llm/llm_node.py b/api/core/workflow/nodes/llm/llm_node.py new file mode 100644 index 0000000000..1c7277e942 --- /dev/null +++ b/api/core/workflow/nodes/llm/llm_node.py @@ -0,0 +1,40 @@ +from typing import Optional + +from core.workflow.nodes.base_node import BaseNode + + +class LLMNode(BaseNode): + @classmethod + def get_default_config(cls, filters: Optional[dict] = None) -> dict: + """ + Get default config of node. + :param filters: filter by node config parameters. + :return: + """ + return { + "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 " + " XML tags.\n\n\n{{" + "#histories#}}\n\n\n\nHuman: {{#query#}}\n\nAssistant:" + }, + "stop": ["Human:"] + } + } + } + } diff --git a/api/core/workflow/nodes/question_classifier/__init__.py b/api/core/workflow/nodes/question_classifier/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/question_classifier/question_classifier_node.py b/api/core/workflow/nodes/question_classifier/question_classifier_node.py new file mode 100644 index 0000000000..f676b6372a --- /dev/null +++ b/api/core/workflow/nodes/question_classifier/question_classifier_node.py @@ -0,0 +1,19 @@ +from typing import Optional + +from core.workflow.nodes.base_node import BaseNode + + +class QuestionClassifierNode(BaseNode): + @classmethod + def get_default_config(cls, filters: Optional[dict] = None) -> dict: + """ + Get default config of node. + :param filters: filter by node config parameters. + :return: + """ + return { + "type": "question-classifier", + "config": { + "instructions": "" # TODO + } + } diff --git a/api/core/workflow/nodes/start/__init__.py b/api/core/workflow/nodes/start/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/start/start_node.py b/api/core/workflow/nodes/start/start_node.py new file mode 100644 index 0000000000..8cce655728 --- /dev/null +++ b/api/core/workflow/nodes/start/start_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class StartNode(BaseNode): + pass diff --git a/api/core/workflow/nodes/template_transform/__init__.py b/api/core/workflow/nodes/template_transform/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/template_transform/template_transform_node.py b/api/core/workflow/nodes/template_transform/template_transform_node.py new file mode 100644 index 0000000000..2bf26e307e --- /dev/null +++ b/api/core/workflow/nodes/template_transform/template_transform_node.py @@ -0,0 +1,25 @@ +from typing import Optional + +from core.workflow.nodes.base_node import BaseNode + + +class TemplateTransformNode(BaseNode): + @classmethod + def get_default_config(cls, filters: Optional[dict] = None) -> dict: + """ + Get default config of node. + :param filters: filter by node config parameters. + :return: + """ + return { + "type": "template-transform", + "config": { + "variables": [ + { + "variable": "arg1", + "value_selector": [] + } + ], + "template": "{{ arg1 }}" + } + } diff --git a/api/core/workflow/nodes/tool/__init__.py b/api/core/workflow/nodes/tool/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/tool/tool_node.py b/api/core/workflow/nodes/tool/tool_node.py new file mode 100644 index 0000000000..b805a53d2f --- /dev/null +++ b/api/core/workflow/nodes/tool/tool_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class ToolNode(BaseNode): + pass diff --git a/api/core/workflow/nodes/variable_assigner/__init__.py b/api/core/workflow/nodes/variable_assigner/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/workflow/nodes/variable_assigner/variable_assigner_node.py b/api/core/workflow/nodes/variable_assigner/variable_assigner_node.py new file mode 100644 index 0000000000..231a26a661 --- /dev/null +++ b/api/core/workflow/nodes/variable_assigner/variable_assigner_node.py @@ -0,0 +1,5 @@ +from core.workflow.nodes.base_node import BaseNode + + +class VariableAssignerNode(BaseNode): + pass diff --git a/api/core/workflow/workflow_engine_manager.py b/api/core/workflow/workflow_engine_manager.py index f7955a87e8..73e92d5e89 100644 --- a/api/core/workflow/workflow_engine_manager.py +++ b/api/core/workflow/workflow_engine_manager.py @@ -1,9 +1,37 @@ from typing import Optional +from core.workflow.entities.node_entities import NodeType +from core.workflow.nodes.code.code_node import CodeNode +from core.workflow.nodes.direct_answer.direct_answer_node import DirectAnswerNode +from core.workflow.nodes.end.end_node import EndNode +from core.workflow.nodes.http_request.http_request_node import HttpRequestNode +from core.workflow.nodes.if_else.if_else_node import IfElseNode +from core.workflow.nodes.knowledge_retrieval.knowledge_retrieval_node import KnowledgeRetrievalNode +from core.workflow.nodes.llm.llm_node import LLMNode +from core.workflow.nodes.question_classifier.question_classifier_node import QuestionClassifierNode +from core.workflow.nodes.start.start_node import StartNode +from core.workflow.nodes.template_transform.template_transform_node import TemplateTransformNode +from core.workflow.nodes.tool.tool_node import ToolNode +from core.workflow.nodes.variable_assigner.variable_assigner_node import VariableAssignerNode from extensions.ext_database import db from models.model import App from models.workflow import Workflow +node_classes = { + NodeType.START: StartNode, + NodeType.END: EndNode, + NodeType.DIRECT_ANSWER: DirectAnswerNode, + NodeType.LLM: LLMNode, + NodeType.KNOWLEDGE_RETRIEVAL: KnowledgeRetrievalNode, + NodeType.IF_ELSE: IfElseNode, + NodeType.CODE: CodeNode, + NodeType.TEMPLATE_TRANSFORM: TemplateTransformNode, + NodeType.QUESTION_CLASSIFIER: QuestionClassifierNode, + NodeType.HTTP_REQUEST: HttpRequestNode, + NodeType.TOOL: ToolNode, + NodeType.VARIABLE_ASSIGNER: VariableAssignerNode, +} + class WorkflowEngineManager: def get_draft_workflow(self, app_model: App) -> Optional[Workflow]: @@ -36,3 +64,35 @@ class WorkflowEngineManager: # return published workflow return workflow + + def get_default_configs(self) -> list[dict]: + """ + Get default block configs + """ + default_block_configs = [] + for node_type, node_class in node_classes.items(): + default_config = node_class.get_default_config() + if default_config: + default_block_configs.append({ + 'type': node_type.value, + 'config': default_config + }) + + return default_block_configs + + def get_default_config(self, node_type: NodeType, filters: Optional[dict] = None) -> Optional[dict]: + """ + Get default config of node. + :param node_type: node type + :param filters: filter by node config parameters. + :return: + """ + node_class = node_classes.get(node_type) + if not node_class: + return None + + default_config = node_class.get_default_config(filters=filters) + if not default_config: + return None + + return default_config diff --git a/api/services/app_service.py b/api/services/app_service.py index f1d0e3df19..6011b6a667 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -35,7 +35,7 @@ class AppService: filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value])) elif args['mode'] == 'chat': filters.append(App.mode.in_([AppMode.CHAT.value, AppMode.ADVANCED_CHAT.value])) - elif args['mode'] == 'agent': + elif args['mode'] == 'agent-chat': filters.append(App.mode == AppMode.AGENT_CHAT.value) elif args['mode'] == 'channel': filters.append(App.mode == AppMode.CHANNEL.value) diff --git a/api/services/workflow/defaults.py b/api/services/workflow/defaults.py deleted file mode 100644 index 67804fa4eb..0000000000 --- a/api/services/workflow/defaults.py +++ /dev/null @@ -1,72 +0,0 @@ -# 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 " - " XML tags.\n\n\n{{" - "#histories#}}\n\n\n\nHuman: {{#query#}}\n\nAssistant:" - }, - "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 - } - } -] diff --git a/api/services/workflow/workflow_converter.py b/api/services/workflow/workflow_converter.py index 527c654381..4c7e4db47a 100644 --- a/api/services/workflow/workflow_converter.py +++ b/api/services/workflow/workflow_converter.py @@ -18,7 +18,7 @@ from core.helper import encrypter from core.model_runtime.entities.llm_entities import LLMMode from core.model_runtime.utils.encoders import jsonable_encoder from core.prompt.simple_prompt_transform import SimplePromptTransform -from core.workflow.entities.NodeEntities import NodeType +from core.workflow.entities.node_entities import NodeType from core.workflow.nodes.end.entities import EndNodeOutputType from events.app_event import app_was_created from extensions.ext_database import db diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 13ea67d343..396845d16a 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -4,6 +4,7 @@ from typing import Optional from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager +from core.workflow.entities.node_entities import NodeType from core.workflow.workflow_engine_manager import WorkflowEngineManager from extensions.ext_database import db from models.account import Account @@ -121,12 +122,26 @@ class WorkflowService: # return new workflow return workflow - def get_default_block_configs(self) -> dict: + def get_default_block_configs(self) -> list[dict]: """ Get default block configs """ # return default block config - return default_block_configs + workflow_engine_manager = WorkflowEngineManager() + return workflow_engine_manager.get_default_configs() + + def get_default_block_config(self, node_type: str, filters: Optional[dict] = None) -> Optional[dict]: + """ + Get default config of node. + :param node_type: node type + :param filters: filter by node config parameters. + :return: + """ + node_type = NodeType.value_of(node_type) + + # return default block config + workflow_engine_manager = WorkflowEngineManager() + return workflow_engine_manager.get_default_config(node_type, filters) def convert_to_workflow(self, app_model: App, account: Account) -> App: """