Skip to content

Commit 03f3a01

Browse files
committed
feat: Support variable splitting nodes
1 parent c0715c5 commit 03f3a01

File tree

24 files changed

+581
-10
lines changed

24 files changed

+581
-10
lines changed

apps/application/flow/step_node/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from .tool_lib_node import *
3232
from .tool_node import *
3333
from .variable_assign_node import BaseVariableAssignNode
34+
from .variable_splitting_node import BaseVariableSplittingNode
3435

3536
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchKnowledgeNode, BaseQuestionNode,
3637
BaseConditionNode, BaseReplyNode,
@@ -40,7 +41,7 @@
4041
BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode, BaseTextToVideoNode, BaseImageToVideoNode,
4142
BaseIntentNode, BaseLoopNode, BaseLoopStartStepNode,
4243
BaseLoopContinueNode,
43-
BaseLoopBreakNode]
44+
BaseLoopBreakNode, BaseVariableSplittingNode]
4445

4546

4647
def get_node(node_type):
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎
5+
@file: __init__.py.py
6+
@date:2025/10/13 14:56
7+
@desc:
8+
"""
9+
from .impl import *
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# coding=utf-8
2+
3+
from typing import Type
4+
5+
from django.utils.translation import gettext_lazy as _
6+
from rest_framework import serializers
7+
8+
from application.flow.i_step_node import INode, NodeResult
9+
10+
11+
class VariableSplittingNodeParamsSerializer(serializers.Serializer):
12+
input_variable = serializers.ListField(required=True,
13+
label=_("input variable"))
14+
15+
variable_list = serializers.ListField(required=True,
16+
label=_("Split variables"))
17+
18+
19+
class IVariableSplittingNode(INode):
20+
type = 'variable-splitting-node'
21+
22+
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
23+
return VariableSplittingNodeParamsSerializer
24+
25+
def _run(self):
26+
input_variable = self.workflow_manage.get_reference_field(
27+
self.node_params_serializer.data.get('input_variable')[0],
28+
self.node_params_serializer.data.get('input_variable')[1:])
29+
return self.execute(input_variable, self.node_params_serializer.data['variable_list'])
30+
31+
def execute(self, input_variable, variable_list, **kwargs) -> NodeResult:
32+
pass
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎
5+
@file: __init__.py.py
6+
@date:2025/10/13 15:01
7+
@desc:
8+
"""
9+
from .base_variable_splitting_node import *
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎
5+
@file: base_variable_splitting_node.py
6+
@date:2025/10/13 15:02
7+
@desc:
8+
"""
9+
from jsonpath_ng import parse
10+
11+
from application.flow.i_step_node import NodeResult
12+
from application.flow.step_node.variable_splitting_node.i_variable_splitting_node import IVariableSplittingNode
13+
14+
15+
def smart_jsonpath_search(data: dict, path: str):
16+
"""
17+
智能JSON Path搜索
18+
返回:
19+
- 单个匹配: 直接返回值
20+
- 多个匹配: 返回值的列表
21+
- 无匹配: 返回None
22+
"""
23+
jsonpath_expr = parse(path)
24+
matches = jsonpath_expr.find(data)
25+
26+
if not matches:
27+
return None
28+
elif len(matches) == 1:
29+
return matches[0].value
30+
else:
31+
return [match.value for match in matches]
32+
33+
34+
class BaseVariableSplittingNode(IVariableSplittingNode):
35+
def save_context(self, details, workflow_manage):
36+
for key, value in details.get('result').items():
37+
self.context['key'] = value
38+
self.context['result'] = details.get('result')
39+
40+
def execute(self, input_variable, variable_list, **kwargs) -> NodeResult:
41+
response = {v['field']: smart_jsonpath_search(input_variable, v['expression']) for v in variable_list}
42+
return NodeResult({'result': response, **response}, {})
43+
44+
def get_details(self, index: int, **kwargs):
45+
return {
46+
'name': self.node.properties.get('stepName'),
47+
"index": index,
48+
'run_time': self.context.get('run_time'),
49+
'type': self.node.type,
50+
'result': self.context.get('result'),
51+
'status': self.status,
52+
'err_message': self.err_message
53+
}

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dependencies = [
5757
"websockets==15.0.1",
5858
"pylint==3.3.7",
5959
"cohere==5.17.0",
60+
"jsonpath-ng==1.7.0"
6061
]
6162

6263
[tool.uv]
Lines changed: 11 additions & 0 deletions
Loading

ui/src/enums/application.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export enum WorkflowType {
3232
LoopStartNode = 'loop-start-node',
3333
LoopContinueNode = 'loop-continue-node',
3434
LoopBreakNode = 'loop-break-node',
35+
VariableSplittingNode = 'variable-splitting-node',
3536
}
3637
export enum WorkflowMode {
3738
// 应用工作流

ui/src/locales/lang/en-US/views/application-workflow.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,16 @@ You are a master of problem optimization, adept at accurately inferring user int
396396
text: 'Terminate the current loop and exit the loop body',
397397
isBreak: 'Break',
398398
},
399+
variableSplittingNode: {
400+
label: 'Variable Splitting',
401+
text: 'Used to split variables',
402+
result: 'Result',
403+
splitVariables: 'Split Variables',
404+
expression: {
405+
label: 'Expression',
406+
placeholder: 'Please enter expression',
407+
},
408+
},
399409
},
400410
compare: {
401411
is_null: 'Is null',

ui/src/locales/lang/zh-CN/dynamics-form.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export default {
2828
placeholder: '请输入关键字搜索',
2929
},
3030
paramForm: {
31+
variable: {
32+
label: '变量',
33+
},
3134
field: {
3235
label: '参数',
3336
placeholder: '请输入参数',

0 commit comments

Comments
 (0)