Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/application/flow/step_node/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .loop_node import *
from .loop_start_node import *
from .mcp_node import BaseMcpNode
from .parameter_extraction_node import BaseParameterExtractionNode
from .question_node import *
from .reranker_node import *
from .search_document_node import BaseSearchDocumentNode
Expand All @@ -44,7 +45,7 @@
BaseVideoUnderstandNode,
BaseIntentNode, BaseLoopNode, BaseLoopStartStepNode,
BaseLoopContinueNode,
BaseLoopBreakNode, BaseVariableSplittingNode]
BaseLoopBreakNode, BaseVariableSplittingNode, BaseParameterExtractionNode]


def get_node(node_type):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# coding=utf-8
"""
@project: MaxKB
@Author:虎虎
@file: __init__.py.py
@date:2025/10/13 14:56
@desc:
"""
from .impl import *
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# coding=utf-8

from typing import Type

from django.utils.translation import gettext_lazy as _
from rest_framework import serializers

from application.flow.i_step_node import INode, NodeResult


class VariableSplittingNodeParamsSerializer(serializers.Serializer):
input_variable = serializers.ListField(required=True,
label=_("input variable"))

variable_list = serializers.ListField(required=True,
label=_("Split variables"))

model_params_setting = serializers.DictField(required=False,
label=_("Model parameter settings"))

model_id = serializers.CharField(required=True, label=_("Model id"))


class IParameterExtractionNode(INode):
type = 'parameter-extraction-node'

def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
return VariableSplittingNodeParamsSerializer

def _run(self):
input_variable = self.workflow_manage.get_reference_field(
self.node_params_serializer.data.get('input_variable')[0],
self.node_params_serializer.data.get('input_variable')[1:])
return self.execute(input_variable, self.node_params_serializer.data['variable_list'],
self.node_params_serializer.data['model_params_setting'],
self.node_params_serializer.data['model_id'])

def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# coding=utf-8
"""
@project: MaxKB
@Author:虎虎
@file: __init__.py.py
@date:2025/10/13 15:01
@desc:
"""
from .base_parameter_extraction_node import *
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# coding=utf-8
"""
@project: MaxKB
@Author:虎虎
@file: base_variable_splitting_node.py
@date:2025/10/13 15:02
@desc:
"""
import json
import re

from django.db.models import QuerySet
from langchain_core.messages import HumanMessage
from langchain_core.prompts import PromptTemplate

from application.flow.i_step_node import NodeResult
from application.flow.step_node.parameter_extraction_node.i_parameter_extraction_node import IParameterExtractionNode
from models_provider.models import Model
from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential

prompt = """
Please strictly process the text according to the following requirements:
**Task**:
Extract specified field information from given text

**Enter text**:
{{question}}

**Extract configuration**:
{{properties}}

**Rule**:
- Strictly follow the data and field of Extract configuration
- If not found, use null value
- Only return pure JSON without additional text
- Keep the string format neat
"""


def get_default_model_params_setting(model_id):
model = QuerySet(Model).filter(id=model_id).first()
credential = get_model_credential(model.provider, model.model_type, model.model_name)
model_params_setting = credential.get_model_params_setting_form(
model.model_name).get_default_form_data()
return model_params_setting


def generate_properties(variable_list):
return {variable['field']: {'type': variable['parameter_type'], 'description': variable['desc'],
'title': variable['label']} for variable in
variable_list}


def generate_example(variable_list):
return {variable['field']: None for variable in variable_list}


def generate_content(input_variable, variable_list):
properties = generate_properties(variable_list)
prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2')
value = prompt_template.format(properties=properties, question=input_variable)
return value


def json_loads(response, expected_fields):
if not response or not isinstance(response, str):
return {field: None for field in expected_fields}

cleaned = response.strip()

extraction_strategies = [
lambda: json.loads(cleaned),
lambda: json.loads(re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned, re.DOTALL).group(1)),
lambda: json.loads(re.search(r'(\{[\s\S]*\})', cleaned).group(1)),
]
for strategy in extraction_strategies:
try:
result = strategy()
return result
except:
continue
return generate_example(expected_fields)


class BaseParameterExtractionNode(IParameterExtractionNode):

def save_context(self, details, workflow_manage):
for key, value in details.get('result').items():
self.context['key'] = value
self.context['result'] = details.get('result')

def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult:
if model_params_setting is None:
model_params_setting = get_default_model_params_setting(model_id)
workspace_id = self.workflow_manage.get_body().get('workspace_id')
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
**model_params_setting)
content = generate_content(input_variable, variable_list)
response = chat_model.invoke([HumanMessage(content=content)])
result = json_loads(response.content, variable_list)
return NodeResult({'result': result, **result}, {})

def get_details(self, index: int, **kwargs):
return {
'name': self.node.properties.get('stepName'),
"index": index,
'run_time': self.context.get('run_time'),
'type': self.node.type,
'result': self.context.get('result'),
'status': self.status,
'err_message': self.err_message
}
11 changes: 11 additions & 0 deletions ui/src/assets/workflow/icon_parameter_extraction.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions ui/src/enums/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export enum WorkflowType {
LoopBreakNode = 'loop-break-node',
VariableSplittingNode = 'variable-splitting-node',
VideoUnderstandNode = 'video-understand-node',
ParameterExtractionNode = 'parameter-extraction-node',
}
export enum WorkflowMode {
// 应用工作流
Expand Down
14 changes: 14 additions & 0 deletions ui/src/locales/lang/en-US/views/application-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,20 @@ You are a master of problem optimization, adept at accurately inferring user int
placeholder: 'Please enter expression',
},
},
parameterExtractionNode: {
label: '參數提取',
text: '利用 AI 模型提取結構化參數',
result: '結果',
selectVariables: {
label: '選擇變數',
placeholder: '請選擇變數',
},
extractParameters: {
label: '提取參數',
desc: '描述',
parameterType: '參數類型',
},
},
},
compare: {
is_null: 'Is null',
Expand Down
14 changes: 14 additions & 0 deletions ui/src/locales/lang/zh-CN/views/application-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,20 @@ export default {
placeholder: '请输入表达式',
},
},
parameterExtractionNode: {
label: '参数提取',
text: '利用 AI 模型提取结构化参数',
result: '结果',
selectVariables: {
label: '选择变量',
placeholder: '请选择变量',
},
extractParameters: {
label: '提取参数',
desc: '描述',
parameterType: '参数类型',
},
},
},
compare: {
is_null: '为空',
Expand Down
14 changes: 14 additions & 0 deletions ui/src/locales/lang/zh-Hant/views/application-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,20 @@ export default {
placeholder: '請輸入表達式',
},
},
parameterExtractionNode: {
label: 'Parameter Extraction',
text: 'Extract structured parameters using AI model',
result: 'Result',
selectVariables: {
label: 'Select Variables',
placeholder: 'Please select variables',
},
extractParameters: {
label: 'Extract Parameters',
desc: 'Description',
parameterType: 'Parameter Type',
},
},
},
compare: {
is_null: '為空',
Expand Down
62 changes: 40 additions & 22 deletions ui/src/workflow/common/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const startNode = {
},
],
globalFields: [
{label: t('views.applicationWorkflow.nodes.startNode.currentTime'), value: 'time'},
{ label: t('views.applicationWorkflow.nodes.startNode.currentTime'), value: 'time' },
{
label: t('views.application.form.historyRecord.label'),
value: 'history_context',
Expand All @@ -28,9 +28,9 @@ export const startNode = {
},
],
},
fields: [{label: t('views.applicationWorkflow.nodes.startNode.question'), value: 'question'}],
fields: [{ label: t('views.applicationWorkflow.nodes.startNode.question'), value: 'question' }],
globalFields: [
{label: t('views.applicationWorkflow.nodes.startNode.currentTime'), value: 'time'},
{ label: t('views.applicationWorkflow.nodes.startNode.currentTime'), value: 'time' },
],
showNode: true,
},
Expand All @@ -53,7 +53,7 @@ export const baseNode = {
},
config: {},
showNode: true,
user_input_config: {title: t('chat.userInput')},
user_input_config: { title: t('chat.userInput') },
user_input_field_list: [],
},
}
Expand Down Expand Up @@ -181,6 +181,24 @@ export const variableSplittingNode = {
},
}

export const parameterExtractionNode = {
type: WorkflowType.ParameterExtractionNode,
text: t('views.applicationWorkflow.nodes.parameterExtractionNode.text', '变量拆分'),
label: t('views.applicationWorkflow.nodes.parameterExtractionNode.label', '变量拆分'),
height: 345,
properties: {
stepName: t('views.applicationWorkflow.nodes.parameterExtractionNode.label', '变量拆分'),
config: {
fields: [
{
label: t('views.applicationWorkflow.nodes.parameterExtractionNode.result', '结果'),
value: 'result',
},
],
},
},
}

export const conditionNode = {
type: WorkflowType.Condition,
text: t('views.applicationWorkflow.nodes.conditionNode.text'),
Expand Down Expand Up @@ -315,7 +333,6 @@ export const videoUnderstandNode = {
},
}


export const variableAssignNode = {
type: WorkflowType.VariableAssignNode,
text: t('views.applicationWorkflow.nodes.variableAssignNode.text'),
Expand Down Expand Up @@ -609,7 +626,7 @@ export const menuNodes = [
},
{
label: t('views.applicationWorkflow.nodes.classify.dataProcessing', '数据处理'),
list: [variableSplittingNode],
list: [variableSplittingNode, parameterExtractionNode],
},
{
label: t('views.applicationWorkflow.nodes.classify.other'),
Expand Down Expand Up @@ -699,22 +716,22 @@ export const applicationNode = {
}

export const compareList = [
{value: 'is_null', label: t('views.applicationWorkflow.compare.is_null')},
{value: 'is_not_null', label: t('views.applicationWorkflow.compare.is_not_null')},
{value: 'contain', label: t('views.applicationWorkflow.compare.contain')},
{value: 'not_contain', label: t('views.applicationWorkflow.compare.not_contain')},
{value: 'eq', label: t('views.applicationWorkflow.compare.eq')},
{value: 'ge', label: t('views.applicationWorkflow.compare.ge')},
{value: 'gt', label: t('views.applicationWorkflow.compare.gt')},
{value: 'le', label: t('views.applicationWorkflow.compare.le')},
{value: 'lt', label: t('views.applicationWorkflow.compare.lt')},
{value: 'len_eq', label: t('views.applicationWorkflow.compare.len_eq')},
{value: 'len_ge', label: t('views.applicationWorkflow.compare.len_ge')},
{value: 'len_gt', label: t('views.applicationWorkflow.compare.len_gt')},
{value: 'len_le', label: t('views.applicationWorkflow.compare.len_le')},
{value: 'len_lt', label: t('views.applicationWorkflow.compare.len_lt')},
{value: 'is_true', label: t('views.applicationWorkflow.compare.is_true')},
{value: 'is_not_true', label: t('views.applicationWorkflow.compare.is_not_true')},
{ value: 'is_null', label: t('views.applicationWorkflow.compare.is_null') },
{ value: 'is_not_null', label: t('views.applicationWorkflow.compare.is_not_null') },
{ value: 'contain', label: t('views.applicationWorkflow.compare.contain') },
{ value: 'not_contain', label: t('views.applicationWorkflow.compare.not_contain') },
{ value: 'eq', label: t('views.applicationWorkflow.compare.eq') },
{ value: 'ge', label: t('views.applicationWorkflow.compare.ge') },
{ value: 'gt', label: t('views.applicationWorkflow.compare.gt') },
{ value: 'le', label: t('views.applicationWorkflow.compare.le') },
{ value: 'lt', label: t('views.applicationWorkflow.compare.lt') },
{ value: 'len_eq', label: t('views.applicationWorkflow.compare.len_eq') },
{ value: 'len_ge', label: t('views.applicationWorkflow.compare.len_ge') },
{ value: 'len_gt', label: t('views.applicationWorkflow.compare.len_gt') },
{ value: 'len_le', label: t('views.applicationWorkflow.compare.len_le') },
{ value: 'len_lt', label: t('views.applicationWorkflow.compare.len_lt') },
{ value: 'is_true', label: t('views.applicationWorkflow.compare.is_true') },
{ value: 'is_not_true', label: t('views.applicationWorkflow.compare.is_not_true') },
]

export const nodeDict: any = {
Expand Down Expand Up @@ -748,6 +765,7 @@ export const nodeDict: any = {
[WorkflowType.LoopContinueNode]: loopContinueNode,
[WorkflowType.VariableSplittingNode]: variableSplittingNode,
[WorkflowType.VideoUnderstandNode]: videoUnderstandNode,
[WorkflowType.ParameterExtractionNode]: parameterExtractionNode,
}

export function isWorkFlow(type: string | undefined) {
Expand Down
6 changes: 6 additions & 0 deletions ui/src/workflow/icons/parameter-extraction-node-icon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template>
<el-avatar shape="square" class="avatar-blue">
<img src="@/assets/workflow/icon_parameter_extraction.svg" style="width: 100%" alt="" />
</el-avatar>
</template>
<script setup lang="ts"></script>
Loading
Loading