Skip to content

Commit c25a950

Browse files
authored
feat: Support parameter extraction nodes (#4208)
1 parent a4330e3 commit c25a950

File tree

16 files changed

+823
-23
lines changed

16 files changed

+823
-23
lines changed

apps/application/flow/step_node/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .loop_node import *
2222
from .loop_start_node import *
2323
from .mcp_node import BaseMcpNode
24+
from .parameter_extraction_node import BaseParameterExtractionNode
2425
from .question_node import *
2526
from .reranker_node import *
2627
from .search_document_node import BaseSearchDocumentNode
@@ -44,7 +45,7 @@
4445
BaseVideoUnderstandNode,
4546
BaseIntentNode, BaseLoopNode, BaseLoopStartStepNode,
4647
BaseLoopContinueNode,
47-
BaseLoopBreakNode, BaseVariableSplittingNode]
48+
BaseLoopBreakNode, BaseVariableSplittingNode, BaseParameterExtractionNode]
4849

4950

5051
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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
model_params_setting = serializers.DictField(required=False,
19+
label=_("Model parameter settings"))
20+
21+
model_id = serializers.CharField(required=True, label=_("Model id"))
22+
23+
24+
class IParameterExtractionNode(INode):
25+
type = 'parameter-extraction-node'
26+
27+
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
28+
return VariableSplittingNodeParamsSerializer
29+
30+
def _run(self):
31+
input_variable = self.workflow_manage.get_reference_field(
32+
self.node_params_serializer.data.get('input_variable')[0],
33+
self.node_params_serializer.data.get('input_variable')[1:])
34+
return self.execute(input_variable, self.node_params_serializer.data['variable_list'],
35+
self.node_params_serializer.data['model_params_setting'],
36+
self.node_params_serializer.data['model_id'])
37+
38+
def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult:
39+
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_parameter_extraction_node import *
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
import json
10+
import re
11+
12+
from django.db.models import QuerySet
13+
from langchain_core.messages import HumanMessage
14+
from langchain_core.prompts import PromptTemplate
15+
16+
from application.flow.i_step_node import NodeResult
17+
from application.flow.step_node.parameter_extraction_node.i_parameter_extraction_node import IParameterExtractionNode
18+
from models_provider.models import Model
19+
from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential
20+
21+
prompt = """
22+
Please strictly process the text according to the following requirements:
23+
**Task**:
24+
Extract specified field information from given text
25+
26+
**Enter text**:
27+
{{question}}
28+
29+
**Extract configuration**:
30+
{{properties}}
31+
32+
**Rule**:
33+
- Strictly follow the data and field of Extract configuration
34+
- If not found, use null value
35+
- Only return pure JSON without additional text
36+
- Keep the string format neat
37+
"""
38+
39+
40+
def get_default_model_params_setting(model_id):
41+
model = QuerySet(Model).filter(id=model_id).first()
42+
credential = get_model_credential(model.provider, model.model_type, model.model_name)
43+
model_params_setting = credential.get_model_params_setting_form(
44+
model.model_name).get_default_form_data()
45+
return model_params_setting
46+
47+
48+
def generate_properties(variable_list):
49+
return {variable['field']: {'type': variable['parameter_type'], 'description': variable['desc'],
50+
'title': variable['label']} for variable in
51+
variable_list}
52+
53+
54+
def generate_example(variable_list):
55+
return {variable['field']: None for variable in variable_list}
56+
57+
58+
def generate_content(input_variable, variable_list):
59+
properties = generate_properties(variable_list)
60+
prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2')
61+
value = prompt_template.format(properties=properties, question=input_variable)
62+
return value
63+
64+
65+
def json_loads(response, expected_fields):
66+
if not response or not isinstance(response, str):
67+
return {field: None for field in expected_fields}
68+
69+
cleaned = response.strip()
70+
71+
extraction_strategies = [
72+
lambda: json.loads(cleaned),
73+
lambda: json.loads(re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned, re.DOTALL).group(1)),
74+
lambda: json.loads(re.search(r'(\{[\s\S]*\})', cleaned).group(1)),
75+
]
76+
for strategy in extraction_strategies:
77+
try:
78+
result = strategy()
79+
return result
80+
except:
81+
continue
82+
return generate_example(expected_fields)
83+
84+
85+
class BaseParameterExtractionNode(IParameterExtractionNode):
86+
87+
def save_context(self, details, workflow_manage):
88+
for key, value in details.get('result').items():
89+
self.context['key'] = value
90+
self.context['result'] = details.get('result')
91+
92+
def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult:
93+
if model_params_setting is None:
94+
model_params_setting = get_default_model_params_setting(model_id)
95+
workspace_id = self.workflow_manage.get_body().get('workspace_id')
96+
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
97+
**model_params_setting)
98+
content = generate_content(input_variable, variable_list)
99+
response = chat_model.invoke([HumanMessage(content=content)])
100+
result = json_loads(response.content, variable_list)
101+
return NodeResult({'result': result, **result}, {})
102+
103+
def get_details(self, index: int, **kwargs):
104+
return {
105+
'name': self.node.properties.get('stepName'),
106+
"index": index,
107+
'run_time': self.context.get('run_time'),
108+
'type': self.node.type,
109+
'result': self.context.get('result'),
110+
'status': self.status,
111+
'err_message': self.err_message
112+
}
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
@@ -35,6 +35,7 @@ export enum WorkflowType {
3535
LoopBreakNode = 'loop-break-node',
3636
VariableSplittingNode = 'variable-splitting-node',
3737
VideoUnderstandNode = 'video-understand-node',
38+
ParameterExtractionNode = 'parameter-extraction-node',
3839
}
3940
export enum WorkflowMode {
4041
// 应用工作流

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,20 @@ You are a master of problem optimization, adept at accurately inferring user int
429429
placeholder: 'Please enter expression',
430430
},
431431
},
432+
parameterExtractionNode: {
433+
label: '參數提取',
434+
text: '利用 AI 模型提取結構化參數',
435+
result: '結果',
436+
selectVariables: {
437+
label: '選擇變數',
438+
placeholder: '請選擇變數',
439+
},
440+
extractParameters: {
441+
label: '提取參數',
442+
desc: '描述',
443+
parameterType: '參數類型',
444+
},
445+
},
432446
},
433447
compare: {
434448
is_null: 'Is null',

ui/src/locales/lang/zh-CN/views/application-workflow.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,20 @@ export default {
442442
placeholder: '请输入表达式',
443443
},
444444
},
445+
parameterExtractionNode: {
446+
label: '参数提取',
447+
text: '利用 AI 模型提取结构化参数',
448+
result: '结果',
449+
selectVariables: {
450+
label: '选择变量',
451+
placeholder: '请选择变量',
452+
},
453+
extractParameters: {
454+
label: '提取参数',
455+
desc: '描述',
456+
parameterType: '参数类型',
457+
},
458+
},
445459
},
446460
compare: {
447461
is_null: '为空',

ui/src/locales/lang/zh-Hant/views/application-workflow.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,20 @@ export default {
415415
placeholder: '請輸入表達式',
416416
},
417417
},
418+
parameterExtractionNode: {
419+
label: 'Parameter Extraction',
420+
text: 'Extract structured parameters using AI model',
421+
result: 'Result',
422+
selectVariables: {
423+
label: 'Select Variables',
424+
placeholder: 'Please select variables',
425+
},
426+
extractParameters: {
427+
label: 'Extract Parameters',
428+
desc: 'Description',
429+
parameterType: 'Parameter Type',
430+
},
431+
},
418432
},
419433
compare: {
420434
is_null: '為空',

0 commit comments

Comments
 (0)