Merge pull request #515 from SuanmoSuanyangTechnology/feature/workflow-notes
feat(workflow): add support for notes nodes
This commit is contained in:
@@ -8,34 +8,60 @@ from typing import Any
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from app.core.workflow.adapters.base_converter import BaseConverter
|
from app.core.workflow.adapters.base_converter import BaseConverter
|
||||||
from app.core.workflow.adapters.errors import UnsupportVariableType, UnknowModelWarning, ExceptionDefineition, \
|
from app.core.workflow.adapters.errors import (
|
||||||
|
UnsupportVariableType,
|
||||||
|
UnknowModelWarning,
|
||||||
|
ExceptionDefineition,
|
||||||
ExceptionType
|
ExceptionType
|
||||||
from app.core.workflow.nodes.assigner import AssignerNodeConfig
|
)
|
||||||
from app.core.workflow.nodes.assigner.config import AssignmentItem
|
from app.core.workflow.nodes.assigner.config import AssignmentItem
|
||||||
from app.core.workflow.nodes.base_config import VariableDefinition, BaseNodeConfig
|
from app.core.workflow.nodes.base_config import VariableDefinition, BaseNodeConfig
|
||||||
from app.core.workflow.nodes.code import CodeNodeConfig
|
|
||||||
from app.core.workflow.nodes.code.config import InputVariable, OutputVariable
|
from app.core.workflow.nodes.code.config import InputVariable, OutputVariable
|
||||||
from app.core.workflow.nodes.configs import StartNodeConfig, LLMNodeConfig
|
from app.core.workflow.nodes.configs import (
|
||||||
from app.core.workflow.nodes.cycle_graph import LoopNodeConfig, IterationNodeConfig
|
StartNodeConfig,
|
||||||
from app.core.workflow.nodes.cycle_graph.config import ConditionDetail as LoopConditionDetail, ConditionsConfig, \
|
LLMNodeConfig,
|
||||||
|
AssignerNodeConfig,
|
||||||
|
CodeNodeConfig,
|
||||||
|
LoopNodeConfig,
|
||||||
|
IterationNodeConfig,
|
||||||
|
EndNodeConfig,
|
||||||
|
HttpRequestNodeConfig,
|
||||||
|
IfElseNodeConfig,
|
||||||
|
JinjaRenderNodeConfig,
|
||||||
|
KnowledgeRetrievalNodeConfig,
|
||||||
|
NoteNodeConfig,
|
||||||
|
ParameterExtractorNodeConfig,
|
||||||
|
QuestionClassifierNodeConfig,
|
||||||
|
VariableAggregatorNodeConfig
|
||||||
|
)
|
||||||
|
from app.core.workflow.nodes.cycle_graph.config import (
|
||||||
|
ConditionDetail as LoopConditionDetail,
|
||||||
|
ConditionsConfig,
|
||||||
CycleVariable
|
CycleVariable
|
||||||
from app.core.workflow.nodes.end import EndNodeConfig
|
)
|
||||||
from app.core.workflow.nodes.enums import ValueInputType, ComparisonOperator, AssignmentOperator, HttpAuthType, \
|
from app.core.workflow.nodes.enums import (
|
||||||
HttpContentType, HttpErrorHandle
|
ValueInputType,
|
||||||
from app.core.workflow.nodes.http_request import HttpRequestNodeConfig
|
ComparisonOperator,
|
||||||
from app.core.workflow.nodes.http_request.config import HttpAuthConfig, HttpContentTypeConfig, HttpFormData, \
|
AssignmentOperator,
|
||||||
HttpTimeOutConfig, HttpRetryConfig, HttpErrorDefaultTamplete, HttpErrorHandleConfig
|
HttpAuthType,
|
||||||
from app.core.workflow.nodes.if_else import IfElseNodeConfig
|
HttpContentType,
|
||||||
|
HttpErrorHandle,
|
||||||
|
NodeType
|
||||||
|
)
|
||||||
|
from app.core.workflow.nodes.http_request.config import (
|
||||||
|
HttpAuthConfig,
|
||||||
|
HttpContentTypeConfig,
|
||||||
|
HttpFormData,
|
||||||
|
HttpTimeOutConfig,
|
||||||
|
HttpRetryConfig,
|
||||||
|
HttpErrorDefaultTamplete,
|
||||||
|
HttpErrorHandleConfig
|
||||||
|
)
|
||||||
from app.core.workflow.nodes.if_else.config import ConditionDetail, ConditionBranchConfig
|
from app.core.workflow.nodes.if_else.config import ConditionDetail, ConditionBranchConfig
|
||||||
from app.core.workflow.nodes.jinja_render import JinjaRenderNodeConfig
|
|
||||||
from app.core.workflow.nodes.jinja_render.config import VariablesMappingConfig
|
from app.core.workflow.nodes.jinja_render.config import VariablesMappingConfig
|
||||||
from app.core.workflow.nodes.knowledge import KnowledgeRetrievalNodeConfig
|
|
||||||
from app.core.workflow.nodes.llm.config import MemoryWindowSetting, MessageConfig
|
from app.core.workflow.nodes.llm.config import MemoryWindowSetting, MessageConfig
|
||||||
from app.core.workflow.nodes.parameter_extractor import ParameterExtractorNodeConfig
|
|
||||||
from app.core.workflow.nodes.parameter_extractor.config import ParamsConfig
|
from app.core.workflow.nodes.parameter_extractor.config import ParamsConfig
|
||||||
from app.core.workflow.nodes.question_classifier import QuestionClassifierNodeConfig
|
|
||||||
from app.core.workflow.nodes.question_classifier.config import ClassifierConfig
|
from app.core.workflow.nodes.question_classifier.config import ClassifierConfig
|
||||||
from app.core.workflow.nodes.variable_aggregator import VariableAggregatorNodeConfig
|
|
||||||
from app.core.workflow.variable.base_variable import VariableType, DEFAULT_VALUE
|
from app.core.workflow.variable.base_variable import VariableType, DEFAULT_VALUE
|
||||||
|
|
||||||
|
|
||||||
@@ -48,24 +74,24 @@ class DifyConverter(BaseConverter):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.CONFIG_CONVERT_MAP = {
|
self.CONFIG_CONVERT_MAP = {
|
||||||
"start": self.convert_start_node_config,
|
NodeType.START: self.convert_start_node_config,
|
||||||
"llm": self.convert_llm_node_config,
|
NodeType.LLM: self.convert_llm_node_config,
|
||||||
"answer": self.convert_end_node_config,
|
NodeType.END: self.convert_end_node_config,
|
||||||
"if-else": self.convert_if_else_node_config,
|
NodeType.IF_ELSE: self.convert_if_else_node_config,
|
||||||
"loop": self.convert_loop_node_config,
|
NodeType.LOOP: self.convert_loop_node_config,
|
||||||
"iteration": self.convert_iteration_node_config,
|
NodeType.ITERATION: self.convert_iteration_node_config,
|
||||||
"assigner": self.convert_assigner_node_config,
|
NodeType.ASSIGNER: self.convert_assigner_node_config,
|
||||||
"code": self.convert_code_node_config,
|
NodeType.CODE: self.convert_code_node_config,
|
||||||
"http-request": self.convert_http_node_config,
|
NodeType.HTTP_REQUEST: self.convert_http_node_config,
|
||||||
"template-transform": self.convert_jinja_render_node_config,
|
NodeType.JINJARENDER: self.convert_jinja_render_node_config,
|
||||||
"knowledge-retrieval": self.convert_knowledge_node_config,
|
NodeType.KNOWLEDGE_RETRIEVAL: self.convert_knowledge_node_config,
|
||||||
"parameter-extractor": self.convert_parameter_extractor_node_config,
|
NodeType.PARAMETER_EXTRACTOR: self.convert_parameter_extractor_node_config,
|
||||||
"question-classifier": self.convert_question_classifier_node_config,
|
NodeType.QUESTION_CLASSIFIER: self.convert_question_classifier_node_config,
|
||||||
"variable-aggregator": self.convert_variable_aggregator_node_config,
|
NodeType.VAR_AGGREGATOR: self.convert_variable_aggregator_node_config,
|
||||||
"tool": self.convert_tool_node_config,
|
NodeType.TOOL: self.convert_tool_node_config,
|
||||||
"loop-start": lambda x: {},
|
NodeType.NOTES: self.convert_notes_config,
|
||||||
"iteration-start": lambda x: {},
|
NodeType.CYCLE_START: lambda x: {},
|
||||||
"loop-end": lambda x: {},
|
NodeType.BREAK: lambda x: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_node_convert(self, node_type):
|
def get_node_convert(self, node_type):
|
||||||
@@ -732,3 +758,16 @@ class DifyConverter(BaseConverter):
|
|||||||
detail=f"Please reconfigure the tool node.",
|
detail=f"Please reconfigure the tool node.",
|
||||||
))
|
))
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_notes_config(node: dict):
|
||||||
|
node_data = node["data"]
|
||||||
|
result = NoteNodeConfig.model_construct(
|
||||||
|
author=node_data.get("author", ""),
|
||||||
|
text=node_data.get("text", ""),
|
||||||
|
width=node_data.get("width", 80),
|
||||||
|
height=node_data.get("height", 80),
|
||||||
|
theme=node_data.get("theme", "blue"),
|
||||||
|
show_author=node_data.get("showAuthor", True)
|
||||||
|
).model_dump()
|
||||||
|
return result
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
|
|||||||
|
|
||||||
def __init__(self, config: dict[str, Any]):
|
def __init__(self, config: dict[str, Any]):
|
||||||
DifyConverter.__init__(self)
|
DifyConverter.__init__(self)
|
||||||
BasePlatformAdapter.__init__(self, config)
|
BasePlatformAdapter.__init__(self, config)
|
||||||
|
|
||||||
def get_metadata(self) -> PlatformMetadata:
|
def get_metadata(self) -> PlatformMetadata:
|
||||||
return PlatformMetadata(
|
return PlatformMetadata(
|
||||||
@@ -59,7 +59,7 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
|
|||||||
support_node_types=list(self.NODE_TYPE_MAPPING.keys())
|
support_node_types=list(self.NODE_TYPE_MAPPING.keys())
|
||||||
)
|
)
|
||||||
|
|
||||||
def map_node_type(self, platform_node_type) -> str:
|
def map_node_type(self, platform_node_type) -> NodeType:
|
||||||
return self.NODE_TYPE_MAPPING.get(platform_node_type, NodeType.UNKNOWN)
|
return self.NODE_TYPE_MAPPING.get(platform_node_type, NodeType.UNKNOWN)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -84,7 +84,7 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
|
|||||||
require_fields = frozenset({'app', 'kind', 'version', 'workflow'})
|
require_fields = frozenset({'app', 'kind', 'version', 'workflow'})
|
||||||
if not all(field in self.config for field in require_fields):
|
if not all(field in self.config for field in require_fields):
|
||||||
return False
|
return False
|
||||||
if self.config.get("app",{}).get("mode") == "workflow":
|
if self.config.get("app", {}).get("mode") == "workflow":
|
||||||
self.errors.append(ExceptionDefineition(
|
self.errors.append(ExceptionDefineition(
|
||||||
type=ExceptionType.PLATFORM,
|
type=ExceptionType.PLATFORM,
|
||||||
detail="workflow mode is not supported"
|
detail="workflow mode is not supported"
|
||||||
@@ -163,13 +163,14 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
|
|||||||
def _convert_node(self, node: dict[str, Any]) -> NodeDefinition | None:
|
def _convert_node(self, node: dict[str, Any]) -> NodeDefinition | None:
|
||||||
node_data = node["data"]
|
node_data = node["data"]
|
||||||
try:
|
try:
|
||||||
|
node_type = self.map_node_type(node_data["type"])
|
||||||
return NodeDefinition(
|
return NodeDefinition(
|
||||||
id=node["id"],
|
id=node["id"],
|
||||||
type=self.map_node_type(node_data["type"]),
|
type=node_type,
|
||||||
name=node_data.get("title") or "notes",
|
name=node_data.get("title") or "notes",
|
||||||
cycle=node.get("parentId"),
|
cycle=node.get("parentId"),
|
||||||
description=None,
|
description=None,
|
||||||
config=self._convert_node_config(node),
|
config=self._convert_node_config(node_type, node),
|
||||||
position={
|
position={
|
||||||
"x": node["position"]["x"],
|
"x": node["position"]["x"],
|
||||||
"y": node["position"]["y"]
|
"y": node["position"]["y"]
|
||||||
@@ -183,17 +184,16 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"convert node error - {e}", exc_info=True)
|
logger.debug(f"convert node error - {e}", exc_info=True)
|
||||||
|
|
||||||
def _convert_node_config(self, node: dict):
|
def _convert_node_config(self, node_type: NodeType, node: dict):
|
||||||
node_data = node["data"]
|
|
||||||
node_type = node_data["type"]
|
|
||||||
try:
|
try:
|
||||||
|
node_data = node["data"]
|
||||||
converter = self.get_node_convert(node_type)
|
converter = self.get_node_convert(node_type)
|
||||||
if node_type not in self.CONFIG_CONVERT_MAP:
|
if node_type == NodeType.UNKNOWN:
|
||||||
self.errors.append(ExceptionDefineition(
|
self.errors.append(ExceptionDefineition(
|
||||||
type=ExceptionType.NODE,
|
type=ExceptionType.NODE,
|
||||||
node_id=node["id"],
|
node_id=node["id"],
|
||||||
node_name=node["data"]["title"],
|
node_name=node["data"]["title"],
|
||||||
detail=f"node type {node_type if node_type else 'notes'} is unsupported",
|
detail=f"node type {node_data.get('type')} is unsupported",
|
||||||
))
|
))
|
||||||
return converter(node)
|
return converter(node)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -214,7 +214,7 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
|
|||||||
if source in self.branch_node_cache:
|
if source in self.branch_node_cache:
|
||||||
case_id = edge["sourceHandle"]
|
case_id = edge["sourceHandle"]
|
||||||
if case_id == "false":
|
if case_id == "false":
|
||||||
label = f'CASE{len(self.branch_node_cache[source])+1}'
|
label = f'CASE{len(self.branch_node_cache[source]) + 1}'
|
||||||
else:
|
else:
|
||||||
label = f'CASE{self.branch_node_cache[source].index(case_id) + 1}'
|
label = f'CASE{self.branch_node_cache[source].index(case_id) + 1}'
|
||||||
if source in self.error_branch_node_cache:
|
if source in self.error_branch_node_cache:
|
||||||
@@ -257,5 +257,3 @@ class DifyAdapter(BasePlatformAdapter, DifyConverter):
|
|||||||
|
|
||||||
def _convert_execution(self, execution: dict[str, Any]) -> ExecutionConfig:
|
def _convert_execution(self, execution: dict[str, Any]) -> ExecutionConfig:
|
||||||
return ExecutionConfig()
|
return ExecutionConfig()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from app.core.workflow.nodes.question_classifier.config import QuestionClassifie
|
|||||||
from app.core.workflow.nodes.start.config import StartNodeConfig
|
from app.core.workflow.nodes.start.config import StartNodeConfig
|
||||||
from app.core.workflow.nodes.tool.config import ToolNodeConfig
|
from app.core.workflow.nodes.tool.config import ToolNodeConfig
|
||||||
from app.core.workflow.nodes.variable_aggregator.config import VariableAggregatorNodeConfig
|
from app.core.workflow.nodes.variable_aggregator.config import VariableAggregatorNodeConfig
|
||||||
|
from app.core.workflow.nodes.notes.config import NoteNodeConfig
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# 基础类
|
# 基础类
|
||||||
@@ -47,5 +48,6 @@ __all__ = [
|
|||||||
"ToolNodeConfig",
|
"ToolNodeConfig",
|
||||||
"MemoryReadNodeConfig",
|
"MemoryReadNodeConfig",
|
||||||
"MemoryWriteNodeConfig",
|
"MemoryWriteNodeConfig",
|
||||||
"CodeNodeConfig"
|
"CodeNodeConfig",
|
||||||
|
"NoteNodeConfig"
|
||||||
]
|
]
|
||||||
|
|||||||
0
api/app/core/workflow/nodes/notes/__init__.py
Normal file
0
api/app/core/workflow/nodes/notes/__init__.py
Normal file
12
api/app/core/workflow/nodes/notes/config.py
Normal file
12
api/app/core/workflow/nodes/notes/config.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from app.core.workflow.nodes.base_config import BaseNodeConfig
|
||||||
|
|
||||||
|
|
||||||
|
class NoteNodeConfig(BaseNodeConfig):
|
||||||
|
author: str = Field(default="", description="author")
|
||||||
|
text: str = Field(default="", description="note content")
|
||||||
|
width: int = Field(default=80)
|
||||||
|
height: int = Field(default=80)
|
||||||
|
theme: str = Field(default="blue")
|
||||||
|
show_author: bool = Field(default=True)
|
||||||
Reference in New Issue
Block a user