From 56b82c1be23e8637b6200a9e0e17d79e63a8ec36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 20 Mar 2026 17:01:14 +0800 Subject: [PATCH 01/14] feat: `Variable assign` supports convert type --- .../impl/base_variable_assign_node.py | 34 ++++++++++++++++--- .../nodes/variable-assign-node/index.ts | 8 ++++- .../nodes/variable-assign-node/index.vue | 17 ++++++++-- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 3ff1982f573..67d0584af0d 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -31,6 +31,25 @@ def chat_evaluation(self, variable, value): else: self.workflow_manage.chat_context[variable['fields'][1]] = value + def convert(self, val, target_type): + if not target_type or val is None: + return val + + if target_type == 'json_object': + return json.loads(val) + elif target_type == 'json_string': + return json.dumps(val, ensure_ascii=False) + elif target_type == 'string': + return str(val) + elif target_type == 'int': + return int(val) + elif target_type == 'float': + return float(val) + elif target_type == 'boolean': + return bool(val) + else: + return val + def handle(self, variable, evaluation): result = { 'name': variable['name'], @@ -42,19 +61,23 @@ def handle(self, variable, evaluation): val = variable['value'] else: val = json.loads(variable['value']) + val = self.convert(val, variable['target_type']) evaluation(variable, val) result['output_value'] = variable['value'] = val elif variable['type'] == 'string': # 变量解析 例如:{{global.xxx}} val = self.workflow_manage.generate_prompt(variable['value']) + val = self.convert(val, variable['target_type']) evaluation(variable, val) result['output_value'] = val else: val = variable['value'] + val = self.convert(val, variable['target_type']) evaluation(variable, val) result['output_value'] = val else: reference = self.get_reference_content(variable['reference']) + reference = self.convert(reference, variable['target_type']) evaluation(variable, reference) result['output_value'] = reference return result @@ -62,22 +85,23 @@ def handle(self, variable, evaluation): def execute(self, variable_list, **kwargs) -> NodeResult: # result_list = [] - is_chat = False + contains_chat_variable = False for variable in variable_list: if 'fields' not in variable: continue + if 'global' == variable['fields'][0]: result = self.handle(variable, self.global_evaluation) result_list.append(result) - if 'chat' == variable['fields'][0]: + elif 'chat' == variable['fields'][0]: result = self.handle(variable, self.chat_evaluation) result_list.append(result) - is_chat = True - if 'loop' == variable['fields'][0]: + contains_chat_variable = True + elif 'loop' == variable['fields'][0]: result = self.handle(variable, self.loop_evaluation) result_list.append(result) - if is_chat: + if contains_chat_variable: from application.flow.loop_workflow_manage import LoopWorkflowManage if isinstance(self.workflow_manage, LoopWorkflowManage): self.workflow_manage.parentWorkflowManage.get_chat_info().set_chat_variable( diff --git a/ui/src/workflow/nodes/variable-assign-node/index.ts b/ui/src/workflow/nodes/variable-assign-node/index.ts index 567bf425ae1..6c50d4b73cd 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.ts +++ b/ui/src/workflow/nodes/variable-assign-node/index.ts @@ -7,8 +7,14 @@ class VariableAssignNode extends AppNode { } } +class VariableAssignModel extends AppNodeModel { + get_width() { + return 450 + } +} + export default { type: 'variable-assign-node', - model: AppNodeModel, + model: VariableAssignModel, view: VariableAssignNode } diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index a455730e0d6..79cd5698197 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -68,6 +68,7 @@ + + + + + + + + @@ -174,6 +186,7 @@ const workflowMode = inject('workflowMode') as WorkflowMode const props = defineProps<{ nodeModel: any }>() const typeOptions = ['string', 'num', 'json', 'bool'] +const targetTypeOptions = ['string', 'int', 'float', 'json_object', 'json_string', 'boolean'] const wheel = (e: any) => { if (e.ctrlKey === true) { From 74684af56a85e29066478681bafdabed06215124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 20 Mar 2026 18:53:23 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/locales/lang/en-US/workflow.ts | 1 + ui/src/locales/lang/zh-CN/workflow.ts | 1 + ui/src/locales/lang/zh-Hant/workflow.ts | 1 + .../nodes/variable-assign-node/index.vue | 39 +++++++++++-------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/ui/src/locales/lang/en-US/workflow.ts b/ui/src/locales/lang/en-US/workflow.ts index 939706357f9..fdaa6981557 100644 --- a/ui/src/locales/lang/en-US/workflow.ts +++ b/ui/src/locales/lang/en-US/workflow.ts @@ -317,6 +317,7 @@ You are a master of problem optimization, adept at accurately inferring user int label: 'Variable Assign', text: 'Update the value of the global variable', assign: 'Set Value', + convertType: 'Convert type', }, variableAggregationNode: { label: 'Variable Aggregation', diff --git a/ui/src/locales/lang/zh-CN/workflow.ts b/ui/src/locales/lang/zh-CN/workflow.ts index 79164f83bff..6ea6beec65d 100644 --- a/ui/src/locales/lang/zh-CN/workflow.ts +++ b/ui/src/locales/lang/zh-CN/workflow.ts @@ -316,6 +316,7 @@ export default { label: '变量赋值', text: '更新全局变量的值', assign: '赋值', + convertType: '转换类型', }, mcpNode: { label: 'MCP 调用', diff --git a/ui/src/locales/lang/zh-Hant/workflow.ts b/ui/src/locales/lang/zh-Hant/workflow.ts index 326b8c5e863..ce575ac7c19 100644 --- a/ui/src/locales/lang/zh-Hant/workflow.ts +++ b/ui/src/locales/lang/zh-Hant/workflow.ts @@ -316,6 +316,7 @@ export default { label: '變數賦值', text: '更新全域變數的值', assign: '賦值', + convertType: '轉換類型', }, variableAggregationNode: { label: '變量聚合', diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index 79cd5698197..900b14bc801 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -68,7 +68,7 @@ - + - + - - + + @@ -154,13 +153,13 @@ ref="nodeCascaderRef2" :nodeModel="nodeModel" class="mr-8" - style="width: 250px" + style="width: 230px" :placeholder="$t('workflow.variable.placeholder')" v-model="item.reference" /> - - + + @@ -186,7 +185,15 @@ const workflowMode = inject('workflowMode') as WorkflowMode const props = defineProps<{ nodeModel: any }>() const typeOptions = ['string', 'num', 'json', 'bool'] -const targetTypeOptions = ['string', 'int', 'float', 'json_object', 'json_string', 'boolean'] +const targetTypeOptions = [ + { label: t('workflow.nodes.variableAssignNode.convertType'), key: '' }, + { label: 'string', key: 'string' }, + { label: 'int', key: 'int' }, + { label: 'float', key: 'float' }, + { label: 'json_object', key: 'json_object' }, + { label: 'json_string', key: 'json_string' }, + { label: 'boolean', key: 'boolean' }, +] const wheel = (e: any) => { if (e.ctrlKey === true) { From e3563e677b01c3b907b917cb9eeb88d1a6f07139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 20 Mar 2026 19:11:51 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=8F=AA=E6=9C=89string=E9=9C=80=E8=A6=81=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E8=BD=AC=E6=8D=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/base_variable_assign_node.py | 12 ++++++++++++ ui/src/workflow/nodes/variable-assign-node/index.vue | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 67d0584af0d..3d807d1dce6 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -36,16 +36,28 @@ def convert(self, val, target_type): return val if target_type == 'json_object': + if isinstance(val, dict) or isinstance(val, list): + return val return json.loads(val) elif target_type == 'json_string': + if isinstance(val, str): + return val return json.dumps(val, ensure_ascii=False) elif target_type == 'string': + if isinstance(val, str): + return val return str(val) elif target_type == 'int': + if isinstance(val, int): + return val return int(val) elif target_type == 'float': + if isinstance(val, float): + return val return float(val) elif target_type == 'boolean': + if isinstance(val, bool): + return val return bool(val) else: return val diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index 900b14bc801..eb585bfb678 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -144,7 +144,12 @@ - + From 92cd721261de834b1d79a7b5c51813c71be52ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 20 Mar 2026 19:30:34 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=B0=8F=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/variable-assign-node/index.vue | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index eb585bfb678..ae4db407182 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -67,8 +67,8 @@ - + - + @@ -147,7 +148,7 @@ @@ -157,13 +158,12 @@ - + From de6a04b7bc491d6f468843b215c8b7c5fafddd44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 20 Mar 2026 22:56:07 +0800 Subject: [PATCH 05/14] fix --- .../impl/base_variable_assign_node.py | 8 ++++---- ui/src/locales/lang/en-US/workflow.ts | 1 + ui/src/locales/lang/zh-CN/workflow.ts | 1 + ui/src/locales/lang/zh-Hant/workflow.ts | 1 + ui/src/workflow/nodes/variable-assign-node/index.vue | 8 +++----- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index a209e01e4c1..13c16f48be3 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -80,23 +80,23 @@ def handle(self, variable, evaluation): val = variable['value'] else: val = json.loads(variable['value']) - val = self.convert(val, variable['target_type']) + val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = variable['value'] = val elif variable['type'] == 'string': # 变量解析 例如:{{global.xxx}} val = self.workflow_manage.generate_prompt(variable['value']) - val = self.convert(val, variable['target_type']) + val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = val else: val = variable['value'] - val = self.convert(val, variable['target_type']) + val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = val else: reference = self.get_reference_content(variable['reference']) - reference = self.convert(reference, variable['target_type']) + reference = self.convert(reference, variable.get('target_type')) evaluation(variable, reference) result['output_value'] = reference return result diff --git a/ui/src/locales/lang/en-US/workflow.ts b/ui/src/locales/lang/en-US/workflow.ts index fdaa6981557..13aea3091d9 100644 --- a/ui/src/locales/lang/en-US/workflow.ts +++ b/ui/src/locales/lang/en-US/workflow.ts @@ -318,6 +318,7 @@ You are a master of problem optimization, adept at accurately inferring user int text: 'Update the value of the global variable', assign: 'Set Value', convertType: 'Convert type', + doNotConvert: 'Do not convert', }, variableAggregationNode: { label: 'Variable Aggregation', diff --git a/ui/src/locales/lang/zh-CN/workflow.ts b/ui/src/locales/lang/zh-CN/workflow.ts index 6ea6beec65d..1efb31645b3 100644 --- a/ui/src/locales/lang/zh-CN/workflow.ts +++ b/ui/src/locales/lang/zh-CN/workflow.ts @@ -317,6 +317,7 @@ export default { text: '更新全局变量的值', assign: '赋值', convertType: '转换类型', + doNotConvert: '不转换', }, mcpNode: { label: 'MCP 调用', diff --git a/ui/src/locales/lang/zh-Hant/workflow.ts b/ui/src/locales/lang/zh-Hant/workflow.ts index ce575ac7c19..a48464a886a 100644 --- a/ui/src/locales/lang/zh-Hant/workflow.ts +++ b/ui/src/locales/lang/zh-Hant/workflow.ts @@ -317,6 +317,7 @@ export default { text: '更新全域變數的值', assign: '賦值', convertType: '轉換類型', + doNotConvert: '不轉換', }, variableAggregationNode: { label: '變量聚合', diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index ae4db407182..962b6afcdf5 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -123,10 +123,8 @@ @@ -191,7 +189,7 @@ const props = defineProps<{ nodeModel: any }>() const typeOptions = ['string', 'num', 'json', 'bool'] const targetTypeOptions = [ - { label: t('workflow.nodes.variableAssignNode.convertType'), key: '' }, + { label: t('workflow.nodes.variableAssignNode.doNotConvert'), key: '' }, { label: 'string', key: 'string' }, { label: 'int', key: 'int' }, { label: 'float', key: 'float' }, From bb2c7db5b6d51d3b343869fd01ed52c4a0130576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 20 Mar 2026 23:37:07 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=EF=BC=9A=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../variable_assign_node/impl/base_variable_assign_node.py | 4 ++++ ui/src/workflow/nodes/variable-assign-node/index.vue | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 13c16f48be3..81511c64348 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -89,6 +89,10 @@ def handle(self, variable, evaluation): val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = val + elif variable['type'] == 'null': + val = None + evaluation(variable, val) + result['output_value'] = val else: val = variable['value'] val = self.convert(val, variable.get('target_type')) diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index 962b6afcdf5..5d9b5a0c22b 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -187,7 +187,7 @@ import { WorkflowMode } from '@/enums/application' const workflowMode = inject('workflowMode') as WorkflowMode const props = defineProps<{ nodeModel: any }>() -const typeOptions = ['string', 'num', 'json', 'bool'] +const typeOptions = ['string', 'num', 'json', 'bool', 'null'] const targetTypeOptions = [ { label: t('workflow.nodes.variableAssignNode.doNotConvert'), key: '' }, { label: 'string', key: 'string' }, From 4ab9407587cc3c3f1fc98a8ef5082e0d9c23534f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Sat, 21 Mar 2026 00:24:21 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E7=AE=80=E5=8C=96=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=E4=B8=BAnull=E7=9A=84=E9=85=8D=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/base_variable_assign_node.py | 8 ++++---- ui/src/workflow/nodes/variable-assign-node/index.vue | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 81511c64348..b3397314b28 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -89,15 +89,15 @@ def handle(self, variable, evaluation): val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = val - elif variable['type'] == 'null': - val = None - evaluation(variable, val) - result['output_value'] = val else: val = variable['value'] val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = val + elif variable['source'] == 'null': + val = None + evaluation(variable, val) + result['output_value'] = val else: reference = self.get_reference_content(variable['reference']) reference = self.convert(reference, variable.get('target_type')) diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index 5d9b5a0c22b..1fbc4074090 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -45,6 +45,7 @@ + @@ -86,7 +87,6 @@ > - + () -const typeOptions = ['string', 'num', 'json', 'bool', 'null'] +const typeOptions = ['string', 'num', 'json', 'bool'] const targetTypeOptions = [ { label: t('workflow.nodes.variableAssignNode.doNotConvert'), key: '' }, { label: 'string', key: 'string' }, From 42f6c9bf425e18f6a0a8903ebe1e01721133db03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Sat, 21 Mar 2026 00:31:15 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E5=B0=8F=E8=B0=83=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/workflow/nodes/variable-assign-node/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/workflow/nodes/variable-assign-node/index.vue b/ui/src/workflow/nodes/variable-assign-node/index.vue index 1fbc4074090..a78b477e8f3 100644 --- a/ui/src/workflow/nodes/variable-assign-node/index.vue +++ b/ui/src/workflow/nodes/variable-assign-node/index.vue @@ -45,7 +45,7 @@ - + From af90a947579a7b400377cf9dcf01149a5f91ed8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Wed, 25 Mar 2026 12:57:03 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E6=98=BE=E7=A4=BA=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E5=92=8C=E8=BE=93=E5=87=BA=E5=8F=82=E6=95=B0=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../variable_assign_node/impl/base_variable_assign_node.py | 3 +++ ui/src/components/execution-detail-card/index.vue | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index b3397314b28..54149a7333c 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -103,6 +103,9 @@ def handle(self, variable, evaluation): reference = self.convert(reference, variable.get('target_type')) evaluation(variable, reference) result['output_value'] = reference + + result['input_type'] = type(result.get('input_value')).__name__ if result.get('input_value') is not None else 'null' + result['output_type'] = type(result.get('output_value')).__name__ if result.get('output_value') is not None else 'null' return result def execute(self, variable_list, **kwargs) -> NodeResult: diff --git a/ui/src/components/execution-detail-card/index.vue b/ui/src/components/execution-detail-card/index.vue index b7c91965977..93984e08ec9 100644 --- a/ui/src/components/execution-detail-card/index.vue +++ b/ui/src/components/execution-detail-card/index.vue @@ -880,7 +880,7 @@
- {{ f.name }}: {{ f.input_value }} + {{ f.name }} ({{ f.input_type }}): {{ f.input_value }}
@@ -890,7 +890,7 @@
- {{ f.name }}: {{ f.output_value }} + {{ f.name }} ({{ f.output_type }}): {{ f.output_value }}
From e62183426abbb3e349ed656b97af8a8bb83489c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Thu, 26 Mar 2026 10:09:20 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=8A=A5=E9=94=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../variable_assign_node/impl/base_variable_assign_node.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 54149a7333c..041aa8b9667 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -99,7 +99,7 @@ def handle(self, variable, evaluation): evaluation(variable, val) result['output_value'] = val else: - reference = self.get_reference_content(variable['reference']) + reference = self.get_reference_content(variable.get('reference')) reference = self.convert(reference, variable.get('target_type')) evaluation(variable, reference) result['output_value'] = reference @@ -109,7 +109,6 @@ def handle(self, variable, evaluation): return result def execute(self, variable_list, **kwargs) -> NodeResult: - # result_list = [] contains_chat_variable = False for variable in variable_list: From 136c30e288b40067878785f1fd326359d83571ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Thu, 26 Mar 2026 10:15:18 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E6=92=A4=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../variable_assign_node/impl/base_variable_assign_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 041aa8b9667..30340d5fb81 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -99,7 +99,7 @@ def handle(self, variable, evaluation): evaluation(variable, val) result['output_value'] = val else: - reference = self.get_reference_content(variable.get('reference')) + reference = self.get_reference_content(variable['reference']) reference = self.convert(reference, variable.get('target_type')) evaluation(variable, reference) result['output_value'] = reference From 2d1449aeb6715f03b96edb1840ea8cab9bea7221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Tue, 31 Mar 2026 14:21:35 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E5=B0=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/locales/lang/en-US/workflow.ts | 18 +++++++++--------- ui/src/locales/lang/zh-CN/workflow.ts | 6 +++--- ui/src/locales/lang/zh-Hant/workflow.ts | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ui/src/locales/lang/en-US/workflow.ts b/ui/src/locales/lang/en-US/workflow.ts index ec1b5e39c83..e39d262a977 100644 --- a/ui/src/locales/lang/en-US/workflow.ts +++ b/ui/src/locales/lang/en-US/workflow.ts @@ -200,18 +200,18 @@ export default { label: 'Question Optimization', text: 'Optimize and improve the current question based on historical chat records to better match knowledge segments', result: 'Optimized Question Result', - systemDefault: `#Role + systemDefault: `# Role You are a master of problem optimization, adept at accurately inferring user intentions based on context and optimizing the questions raised by users. -##Skills -###Skill 1: Optimizing Problems -2. Receive user input questions. -3. Carefully analyze the meaning of the problem based on the context. -4. Output optimized problems. +## Skills +### Skill 1: Optimizing Problems +1. Receive user input questions. +2. Carefully analyze the meaning of the problem based on the context. +3. Output optimized problems. -##Limitations: --Only return the optimized problem without any additional explanation or clarification. --Ensure that the optimized problem accurately reflects the original problem intent and does not alter the original intention.`, +## Limitations: +- Only return the optimized problem without any additional explanation or clarification. +- Ensure that the optimized problem accurately reflects the original problem intent and does not alter the original intention.`, }, conditionNode: { label: 'Conditional Branch', diff --git a/ui/src/locales/lang/zh-CN/workflow.ts b/ui/src/locales/lang/zh-CN/workflow.ts index b3169c439ce..3c004d437c2 100644 --- a/ui/src/locales/lang/zh-CN/workflow.ts +++ b/ui/src/locales/lang/zh-CN/workflow.ts @@ -205,9 +205,9 @@ export default { ## 技能 ### 技能 1: 优化问题 -2. 接收用户输入的问题。 -3. 依据上下文仔细分析问题含义。 -4. 输出优化后的问题。 +1. 接收用户输入的问题。 +2. 依据上下文仔细分析问题含义。 +3. 输出优化后的问题。 ## 限制: - 仅返回优化后的问题,不进行额外解释或说明。 diff --git a/ui/src/locales/lang/zh-Hant/workflow.ts b/ui/src/locales/lang/zh-Hant/workflow.ts index c62b2e507d6..a7fd05c9f1b 100644 --- a/ui/src/locales/lang/zh-Hant/workflow.ts +++ b/ui/src/locales/lang/zh-Hant/workflow.ts @@ -205,9 +205,9 @@ export default { ## 技能 ### 技能 1: 優化問題 -2. 接收用戶輸入的問題。 -3. 依據上下文仔細分析問題含義。 -4. 輸出優化後的問題。 +1. 接收用戶輸入的問題。 +2. 依據上下文仔細分析問題含義。 +3. 輸出優化後的問題。 ## 限制: - 僅返回優化後的問題,不進行額外解釋或說明。 From dca4d226bb4897208e54e93c7d8b2d9b3726cfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Tue, 14 Apr 2026 16:04:53 +0800 Subject: [PATCH 13/14] =?UTF-8?q?feat:=20=E5=90=88=E5=B9=B6=E6=88=91?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=8F=90=E4=BA=A4=E7=9A=84PR=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../step/chat_step/impl/base_chat_step.py | 2 +- apps/application/flow/compare/__init__.py | 4 +- .../application/flow/compare/regex_compare.py | 40 +++++++++ .../flow/compare/wildcard_compare.py | 43 ++++++++++ .../ai_chat_step_node/impl/base_chat_node.py | 4 +- .../step_node/intent_node/i_intent_node.py | 2 + .../intent_node/impl/base_intent_node.py | 63 +++++++++------ .../intent_node/impl/prompt_template.py | 7 +- .../question_node/impl/base_question_node.py | 2 +- .../tool_lib_node/impl/base_tool_lib_node.py | 3 +- .../impl/base_variable_aggregation_node.py | 2 +- .../impl/base_variable_assign_node.py | 23 +++--- .../impl/base_variable_splitting_node.py | 2 +- .../serializers/application_chat_record.py | 3 +- apps/chat/serializers/chat.py | 8 +- .../serializers/chat_embed_serializers.py | 5 +- apps/chat/serializers/chat_record.py | 3 +- apps/common/handle/impl/common_handle.py | 81 +++++++++---------- .../handle/impl/text/pdf_split_handle.py | 14 +++- .../handle/impl/text/zip_split_handle.py | 4 +- apps/knowledge/serializers/common.py | 11 ++- apps/knowledge/serializers/document.py | 32 ++++---- apps/knowledge/serializers/paragraph.py | 1 - apps/maxkb/urls/web.py | 5 +- .../aws_bedrock_model_provider/model/llm.py | 6 +- .../ollama_model_provider.py | 2 +- .../handler/impl/trigger/event_trigger.py | 2 +- apps/users/serializers/user.py | 9 +-- .../execution-detail-card/index.vue | 8 +- .../locales/lang/en-US/views/application.ts | 5 ++ ui/src/locales/lang/en-US/workflow.ts | 28 +++++++ .../locales/lang/zh-CN/views/application.ts | 5 ++ ui/src/locales/lang/zh-CN/workflow.ts | 28 +++++++ .../locales/lang/zh-Hant/views/application.ts | 5 ++ ui/src/locales/lang/zh-Hant/workflow.ts | 28 +++++++ ui/src/styles/app.scss | 5 ++ ui/src/views/application-workflow/index.vue | 5 +- ui/src/views/knowledge-workflow/index.vue | 5 +- ui/src/views/tool-workflow/index.vue | 3 + ui/src/workflow/common/NodeContainer.vue | 2 +- ui/src/workflow/common/data.ts | 2 + .../nodes/intent-classify-node/index.ts | 6 +- .../nodes/intent-classify-node/index.vue | 67 +++++++++++++++ 43 files changed, 433 insertions(+), 152 deletions(-) create mode 100644 apps/application/flow/compare/regex_compare.py create mode 100644 apps/application/flow/compare/wildcard_compare.py diff --git a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py index 6eec69e7bd6..fce84f5a097 100644 --- a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py +++ b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py @@ -183,7 +183,7 @@ def execute(self, message_list: List[BaseMessage], mcp_output_enable=True, **kwargs): chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, - **model_params_setting) if model_id is not None else None + **(model_params_setting or {})) if model_id is not None else None if stream: return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model, paragraph_list, diff --git a/apps/application/flow/compare/__init__.py b/apps/application/flow/compare/__init__.py index f0dd01523f5..4e99bfa7270 100644 --- a/apps/application/flow/compare/__init__.py +++ b/apps/application/flow/compare/__init__.py @@ -25,10 +25,12 @@ from .lt_compare import * from .not_contain_compare import * from .not_equal_compare import * +from .regex_compare import RegexCompare from .start_with import StartWithCompare +from .wildcard_compare import WildcardCompare compare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(), LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(), IsNullCompare(), IsNotNullCompare(), NotContainCompare(), NotEqualCompare(), IsTrueCompare(), IsNotTrueCompare(), StartWithCompare(), - EndWithCompare()] + EndWithCompare(), RegexCompare(), WildcardCompare()] diff --git a/apps/application/flow/compare/regex_compare.py b/apps/application/flow/compare/regex_compare.py new file mode 100644 index 00000000000..8fd98f3eca8 --- /dev/null +++ b/apps/application/flow/compare/regex_compare.py @@ -0,0 +1,40 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:wangliang181230 + @file: regex_compare.py + @date:2026/3/30 12:11 + @desc: +""" +import re +from typing import List + +from application.flow.compare import Compare +from common.cache.mem_cache import MemCache + + +match_cache = MemCache('regex', { + 'TIMEOUT': 3600, # 缓存有效期为 1 小时 + 'OPTIONS': { + 'MAX_ENTRIES': 500, # 最多缓存 500 个条目 + 'CULL_FREQUENCY': 10, # 达到上限时,删除约 1/10 的缓存 + }, +}) + + +def compile_and_cache(regex): + match = match_cache.get(regex) + if not match: + match = re.compile(regex).match + match_cache.set(regex, match) + return match + +class RegexCompare(Compare): + + def support(self, node_id, fields: List[str], source_value, compare, target_value): + if compare == 'regex': + return True + + def compare(self, source_value, compare, target_value): + match = compile_and_cache(str(target_value)) + return bool(match(str(source_value))) diff --git a/apps/application/flow/compare/wildcard_compare.py b/apps/application/flow/compare/wildcard_compare.py new file mode 100644 index 00000000000..65ea2c74a5a --- /dev/null +++ b/apps/application/flow/compare/wildcard_compare.py @@ -0,0 +1,43 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:wangliang181230 + @file: wildcard_compare.py + @date:2026/3/30 12:11 + @desc: +""" +import fnmatch +import re +from typing import List + +from application.flow.compare import Compare +from common.cache.mem_cache import MemCache + + +match_cache = MemCache('wildcard_to_regex', { + 'TIMEOUT': 3600, # 缓存有效期为 1 小时 + 'OPTIONS': { + 'MAX_ENTRIES': 500, # 最多缓存 500 个条目 + 'CULL_FREQUENCY': 10, # 达到上限时,删除约 1/10 的缓存 + }, +}) + + +def translate_and_compile_and_cache(wildcard): + match = match_cache.get(wildcard) + if not match: + regex = fnmatch.translate(wildcard) + match = re.compile(regex).match + match_cache.set(wildcard, match) + return match + +class WildcardCompare(Compare): + + def support(self, node_id, fields: List[str], source_value, compare, target_value): + if compare == 'wildcard': + return True + + def compare(self, source_value, compare, target_value): + # 转成正则,性能更高 + match = translate_and_compile_and_cache(str(target_value)) + return bool(match(str(source_value))) diff --git a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py b/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py index 664b696ed63..e0048a5a582 100644 --- a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py +++ b/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py @@ -364,7 +364,7 @@ def handle_variables(self, tool_params): tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k]) if type(v) == dict: self.handle_variables(v) - if (type(v) == list) and (type(v[0]) == str): + if (type(v) == list) and len(v) > 0 and (type(v[0]) == str): tool_params[k] = self.get_reference_content(v) return tool_params @@ -390,7 +390,7 @@ def generate_prompt_question(self, prompt): def generate_message_list(self, system: str, prompt: str, history_message): if system is not None and len(system) > 0: - return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, + return [SystemMessage(system), *history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] else: return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] diff --git a/apps/application/flow/step_node/intent_node/i_intent_node.py b/apps/application/flow/step_node/intent_node/i_intent_node.py index d22d321c842..e57931da0a0 100644 --- a/apps/application/flow/step_node/intent_node/i_intent_node.py +++ b/apps/application/flow/step_node/intent_node/i_intent_node.py @@ -20,12 +20,14 @@ class IntentNodeSerializer(serializers.Serializer): model_id_type = serializers.CharField(required=False, default='custom', label=_("Model id type")) model_id_reference = serializers.ListField(required=False, child=serializers.CharField(), allow_empty=True, label=_("Reference Field")) + prompt_template = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Prompt template")) content_list = serializers.ListField(required=True, label=_("Text content")) dialogue_number = serializers.IntegerField(required=True, label= _("Number of multi-round conversations")) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) branch = IntentBranchSerializer(many=True) + output_reason = serializers.BooleanField(required=False, label=_("Output reason"), default=True) class IIntentNode(INode): diff --git a/apps/application/flow/step_node/intent_node/impl/base_intent_node.py b/apps/application/flow/step_node/intent_node/impl/base_intent_node.py index 64822aa9a20..5692bcb2912 100644 --- a/apps/application/flow/step_node/intent_node/impl/base_intent_node.py +++ b/apps/application/flow/step_node/intent_node/impl/base_intent_node.py @@ -12,7 +12,7 @@ from application.flow.step_node.intent_node.i_intent_node import IIntentNode from models_provider.models import Model from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential -from .prompt_template import PROMPT_TEMPLATE +from .prompt_template import DEFAULT_PROMPT_TEMPLATE def get_default_model_params_setting(model_id): @@ -52,7 +52,7 @@ def save_context(self, details, workflow_manage): self.context['branch_id'] = details.get('branch_id') self.context['category'] = details.get('category') - def execute(self, model_id, dialogue_number, history_chat_record, user_input, branch, + def execute(self, model_id, prompt_template, dialogue_number, history_chat_record, user_input, branch, output_reason, model_params_setting=None, model_id_type=None, model_id_reference=None, **kwargs) -> NodeResult: # 处理引用类型 if model_id_type == 'reference' and model_id_reference: @@ -77,18 +77,19 @@ def execute(self, model_id, dialogue_number, history_chat_record, user_input, br ) # 获取历史对话 - history_message = self.get_history_message(history_chat_record, dialogue_number) + history_message = self.get_history_message(history_chat_record, dialogue_number) if history_chat_record and dialogue_number > 0 else [] self.context['history_message'] = history_message # 保存问题到上下文 self.context['user_input'] = user_input # 构建分类提示词 - prompt = self.build_classification_prompt(user_input, branch) + prompt_template = self.workflow_manage.generate_prompt(prompt_template) if prompt_template else None + prompt = self.build_classification_prompt(prompt_template, user_input, branch, output_reason) + self.context['system'] = prompt # 生成消息列表 - system = self.build_system_prompt() - message_list = self.generate_message_list(system, prompt, history_message) + message_list = self.generate_message_list(prompt, history_message) self.context['message_list'] = message_list # 调用模型进行分类 @@ -106,7 +107,7 @@ def execute(self, model_id, dialogue_number, history_chat_record, user_input, br 'history_message': history_message, 'user_input': user_input, 'branch_id': matched_branch['id'], - 'reason': self.parse_result_reason(r.content), + 'reason': self.parse_result_reason(r.content) if output_reason is not False else '', 'category': matched_branch.get('content', matched_branch['id']) }, {}, _write_context=write_context) @@ -136,11 +137,7 @@ def get_history_message(history_chat_record, dialogue_number): message.content = re.sub('[\d\D]*?<\/form_rander>', '', message.content) return history_message - def build_system_prompt(self) -> str: - """构建系统提示词""" - return "你是一个专业的意图识别助手,请根据用户输入和意图选项,准确识别用户的真实意图。" - - def build_classification_prompt(self, user_input: str, branch: List[Dict]) -> str: + def build_classification_prompt(self, prompt_template: str, user_input: str, branch: List[Dict], output_reason: bool) -> str: """构建分类提示词""" classification_list = [] @@ -162,18 +159,19 @@ def build_classification_prompt(self, user_input: str, branch: List[Dict]) -> st }) classification_id += 1 - return PROMPT_TEMPLATE.format( - classification_list=classification_list, - user_input=user_input + # 构建输出JSON结构 + reason_field = ',\n"reason": ""' if output_reason is not False else '' + output_json = f'{{\n"classificationId": 0{reason_field}\n}}' + + return (prompt_template or DEFAULT_PROMPT_TEMPLATE).format( + classification_list=json.dumps(classification_list, ensure_ascii=False), + user_input=user_input, + output_json=output_json ) - def generate_message_list(self, system: str, prompt: str, history_message): + def generate_message_list(self, prompt: str, history_message): """生成消息列表""" - if system is None or len(system) == 0: - return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] - else: - return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, - HumanMessage(self.workflow_manage.generate_prompt(prompt))] + return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] def parse_classification_result(self, result: str, branch: List[Dict]) -> Dict[str, Any]: """解析分类结果""" @@ -193,14 +191,23 @@ def get_branch_by_id(category_id: int): return None try: - result_json = json.loads(result) - classification_id = result_json.get('classificationId') + classification_id = None + + # 如果长度小于5,先尝试解析为数字(增加自由度,在自定义提示词模板时,可提示大模型只输出意图分类的ID值) + if len(result) < 5: + classification_id = self.to_int(result) + + # 尝试解析为 JSON + if classification_id is None: + result_json = json.loads(result) + classification_id = result_json.get('classificationId') + # 如果是 0 ,返回其他分支 matched_branch = get_branch_by_id(classification_id) if matched_branch: return matched_branch - except Exception as e: + except Exception: # json 解析失败,re 提取 numbers = re.findall(r'"classificationId":\s*(\d+)', result) if numbers: @@ -218,7 +225,7 @@ def parse_result_reason(self, result: str): try: result_json = json.loads(result) return result_json.get('reason', '') - except Exception as e: + except Exception: reason_patterns = [ r'"reason":\s*"([^"]*)"', # 标准格式 r'"reason":\s*"([^"]*)', # 缺少结束引号 @@ -234,6 +241,12 @@ def parse_result_reason(self, result: str): return '' + def to_int(self, str): + try: + return int(str) + except ValueError: + return None + def find_other_branch(self, branch: List[Dict]) -> Dict[str, Any] | None: """查找其他分支""" for b in branch: diff --git a/apps/application/flow/step_node/intent_node/impl/prompt_template.py b/apps/application/flow/step_node/intent_node/impl/prompt_template.py index 1bcfd61743e..79062022edb 100644 --- a/apps/application/flow/step_node/intent_node/impl/prompt_template.py +++ b/apps/application/flow/step_node/intent_node/impl/prompt_template.py @@ -1,6 +1,6 @@ -PROMPT_TEMPLATE = """# Role +DEFAULT_PROMPT_TEMPLATE = """# Role You are an intention classification expert, good at being able to judge which classification the user's input belongs to. ## Skills @@ -20,10 +20,7 @@ - Strictly ensure that the output is in a valid JSON format. - Do not add prefix ```json or suffix ``` - The answer needs to include the following fields such as: -{{ -"classificationId": 0, -"reason": "" -}} +{output_json} ## Limit - Please do not reply in text.""" diff --git a/apps/application/flow/step_node/question_node/impl/base_question_node.py b/apps/application/flow/step_node/question_node/impl/base_question_node.py index 3f622c5f2dc..9173c6d27a5 100644 --- a/apps/application/flow/step_node/question_node/impl/base_question_node.py +++ b/apps/application/flow/step_node/question_node/impl/base_question_node.py @@ -138,7 +138,7 @@ def generate_prompt_question(self, prompt): def generate_message_list(self, system: str, prompt: str, history_message): if system is not None and len(system) > 0: - return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, + return [SystemMessage(system), *history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] else: return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] diff --git a/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py b/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py index f2434284d76..1553fad918e 100644 --- a/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py +++ b/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py @@ -251,6 +251,7 @@ def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: def tool_exec_record(self, tool_lib, all_params): task_record_id = uuid.uuid7() start_time = time.time() + filtered_args = all_params try: # 过滤掉 tool_init_params 中的参数 tool_init_params = json.loads(rsa_long_decrypt(tool_lib.init_params)) if tool_lib.init_params else {} @@ -259,8 +260,6 @@ def tool_exec_record(self, tool_lib, all_params): k: v for k, v in all_params.items() if k not in tool_init_params } - else: - filtered_args = all_params if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): source_id = self.workflow_manage.params.get('knowledge_id') diff --git a/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py b/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py index fa1d6479e5c..341f2e0eab9 100644 --- a/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py +++ b/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py @@ -24,7 +24,7 @@ class BaseVariableAggregationNode(IVariableAggregation): def save_context(self, details, workflow_manage): for key, value in details.get('result').items(): - self.context['key'] = value + self.context[key] = value self.context['result'] = details.get('result') self.context['strategy'] = details.get('strategy') self.context['group_list'] = details.get('group_list') diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 30340d5fb81..0df405f39be 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -94,38 +94,41 @@ def handle(self, variable, evaluation): val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = val - elif variable['source'] == 'null': - val = None - evaluation(variable, val) - result['output_value'] = val - else: + elif variable['source'] == 'referencing': reference = self.get_reference_content(variable['reference']) reference = self.convert(reference, variable.get('target_type')) evaluation(variable, reference) result['output_value'] = reference + else: + val = None + evaluation(variable, val) + result['output_value'] = val + # 获取输入输出值的类型,用于显示在执行详情页面中 result['input_type'] = type(result.get('input_value')).__name__ if result.get('input_value') is not None else 'null' result['output_type'] = type(result.get('output_value')).__name__ if result.get('output_value') is not None else 'null' + return result def execute(self, variable_list, **kwargs) -> NodeResult: result_list = [] contains_chat_variable = False for variable in variable_list: - if 'fields' not in variable: + if not variable.get('fields'): continue - if 'global' == variable['fields'][0]: + field0 = variable['fields'][0] + if 'global' == field0: result = self.handle(variable, self.global_evaluation) result_list.append(result) - elif 'chat' == variable['fields'][0]: + elif 'chat' == field0: result = self.handle(variable, self.chat_evaluation) result_list.append(result) contains_chat_variable = True - elif 'loop' == variable['fields'][0]: + elif 'loop' == field0: result = self.handle(variable, self.loop_evaluation) result_list.append(result) - elif 'output' == variable['fields'][0]: + elif 'output' == field0: result = self.handle(variable, self.out_evaluation) result_list.append(result) diff --git a/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py b/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py index 90a47613a46..274604e2328 100644 --- a/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py +++ b/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py @@ -16,7 +16,7 @@ jsonpath_expr_cache = MemCache('parse_path', { 'TIMEOUT': 3600, # 缓存有效期为 1 小时 'OPTIONS': { - 'MAX_ENTRIES': 1000, # 最多缓存 500 个条目 + 'MAX_ENTRIES': 1000, # 最多缓存 1000 个条目 'CULL_FREQUENCY': 10, # 达到上限时,删除约 1/10 的缓存 }, }) diff --git a/apps/application/serializers/application_chat_record.py b/apps/application/serializers/application_chat_record.py index 540d9310dd9..92946bbdc01 100644 --- a/apps/application/serializers/application_chat_record.py +++ b/apps/application/serializers/application_chat_record.py @@ -104,7 +104,6 @@ def is_valid(self, *, raise_exception=False): def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')) order_by = 'create_time' if self.data.get('order_asc') is None or self.data.get( 'order_asc') else '-create_time' return [ChatRecordSerializerModel(chat_record).data for chat_record in @@ -169,7 +168,7 @@ def reset_chat_record(chat_record, show_source, show_exec): 'padding_problem_text': chat_record.details.get('problem_padding').get( 'padding_problem_text') if 'problem_padding' in chat_record.details else None, **(show_source_dict if show_source else {}), - **(show_exec_dict if show_exec else show_exec_dict) + **(show_exec_dict if show_exec else {}) } def page(self, current_page: int, page_size: int, with_valid=True, show_source=None, show_exec=None): diff --git a/apps/chat/serializers/chat.py b/apps/chat/serializers/chat.py index ddb08cbcc6f..4514dc457bc 100644 --- a/apps/chat/serializers/chat.py +++ b/apps/chat/serializers/chat.py @@ -382,10 +382,10 @@ def get_chat_record(chat_info, chat_record_id): str(chat_record.id) == str(chat_record_id)] if chat_record_list is not None and len(chat_record_list): return chat_record_list[-1] - chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first() - if chat_record is None: - if not is_valid_uuid(chat_record_id): - raise ChatException(500, _("Conversation record does not exist")) + chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first() + if chat_record is None: + if not is_valid_uuid(chat_record_id): + raise ChatException(500, _("Conversation record does not exist")) chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first() return chat_record diff --git a/apps/chat/serializers/chat_embed_serializers.py b/apps/chat/serializers/chat_embed_serializers.py index 7f330f93e94..5594e5b0abe 100644 --- a/apps/chat/serializers/chat_embed_serializers.py +++ b/apps/chat/serializers/chat_embed_serializers.py @@ -32,9 +32,8 @@ def get_embed(self, with_valid=True, params=None): if with_valid: self.is_valid(raise_exception=True) index_path = os.path.join(PROJECT_DIR, 'apps', "chat", 'template', 'embed.js') - file = open(index_path, "r", encoding='utf-8') - content = file.read() - file.close() + with open(index_path, "r", encoding='utf-8') as file: + content = file.read() application_access_token = QuerySet(ApplicationAccessToken).filter( access_token=self.data.get('token')).first() is_draggable = 'false' diff --git a/apps/chat/serializers/chat_record.py b/apps/chat/serializers/chat_record.py index d094216f53f..ee6d446dd57 100644 --- a/apps/chat/serializers/chat_record.py +++ b/apps/chat/serializers/chat_record.py @@ -75,8 +75,7 @@ def vote(self, instance: Dict, with_valid=True): chat_record_details_model.vote_status = VoteChoices.STAR chat_record_details_model.vote_reason = vote_reason chat_record_details_model.vote_other_content = vote_other_content - - if vote_status == VoteChoices.TRAMPLE: + elif vote_status == VoteChoices.TRAMPLE: # 点踩 chat_record_details_model.vote_status = VoteChoices.TRAMPLE chat_record_details_model.vote_reason = vote_reason diff --git a/apps/common/handle/impl/common_handle.py b/apps/common/handle/impl/common_handle.py index 16c647a9626..98b7537926f 100644 --- a/apps/common/handle/impl/common_handle.py +++ b/apps/common/handle/impl/common_handle.py @@ -86,46 +86,45 @@ def handle_images(deps, archive: ZipFile) -> []: def xlsx_embed_cells_images(buffer) -> {}: - archive = ZipFile(buffer) - # 解析cellImage.xml文件 - deps = get_dependents(archive, get_rels_path("xl/cellimages.xml")) - image_rel = handle_images(deps=deps, archive=archive) - # 工作表及其中图片ID - sheet_list = {} - for item in archive.namelist(): - if not item.startswith('xl/worksheets/sheet'): - continue - key = item.split('/')[-1].split('.')[0].split('sheet')[-1] - sheet_list[key] = parse_element_sheet_xml(fromstring(archive.read(item))) - cell_images_xml = parse_element(fromstring(archive.read("xl/cellimages.xml"))) - cell_images_rel = {} - for image in image_rel: - cell_images_rel[image.embed] = image - for cnv, embed in cell_images_xml.items(): - cell_images_xml[cnv] = cell_images_rel.get(embed) - result = {} - for key, img in cell_images_xml.items(): - all_cells = [ - cell - for _sheet_id, sheet in sheet_list.items() - if sheet is not None - for cell in sheet or [] - ] + with ZipFile(buffer) as archive: + # 解析cellImage.xml文件 + deps = get_dependents(archive, get_rels_path("xl/cellimages.xml")) + image_rel = handle_images(deps=deps, archive=archive) + # 工作表及其中图片ID + sheet_list = {} + for item in archive.namelist(): + if not item.startswith('xl/worksheets/sheet'): + continue + key = item.split('/')[-1].split('.')[0].split('sheet')[-1] + sheet_list[key] = parse_element_sheet_xml(fromstring(archive.read(item))) + cell_images_xml = parse_element(fromstring(archive.read("xl/cellimages.xml"))) + cell_images_rel = {} + for image in image_rel: + cell_images_rel[image.embed] = image + for cnv, embed in cell_images_xml.items(): + cell_images_xml[cnv] = cell_images_rel.get(embed) + result = {} + for key, img in cell_images_xml.items(): + all_cells = [ + cell + for _sheet_id, sheet in sheet_list.items() + if sheet is not None + for cell in sheet or [] + ] - image_excel_id_list = [ - cell for cell in all_cells - if isinstance(cell, str) and key in cell - ] - # print(key, img) - if img is None: - continue - if len(image_excel_id_list) > 0: - image_excel_id = image_excel_id_list[-1] - f = archive.open(img.target) - img_byte = io.BytesIO() - im = PILImage.open(f).convert('RGB') - im.save(img_byte, format='JPEG') - image = File(id=uuid.uuid7(), file_name=img.path, meta={'debug': False, 'content': img_byte.getvalue()}) - result['=' + image_excel_id] = image - archive.close() + image_excel_id_list = [ + cell for cell in all_cells + if isinstance(cell, str) and key in cell + ] + # print(key, img) + if img is None: + continue + if len(image_excel_id_list) > 0: + image_excel_id = image_excel_id_list[-1] + with archive.open(img.target) as f: + img_byte = io.BytesIO() + im = PILImage.open(f).convert('RGB') + im.save(img_byte, format='JPEG') + image = File(id=uuid.uuid7(), file_name=img.path, meta={'debug': False, 'content': img_byte.getvalue()}) + result['=' + image_excel_id] = image return result diff --git a/apps/common/handle/impl/text/pdf_split_handle.py b/apps/common/handle/impl/text/pdf_split_handle.py index f3172e29884..0ffafff61c0 100644 --- a/apps/common/handle/impl/text/pdf_split_handle.py +++ b/apps/common/handle/impl/text/pdf_split_handle.py @@ -49,8 +49,9 @@ def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_bu # 获取临时文件的路径 temp_file_path = temp_file.name - pdf_document = fitz.open(temp_file_path) + pdf_document = None try: + pdf_document = fitz.open(temp_file_path) if type(limit) is str: limit = int(limit) if type(with_filter) is str: @@ -79,7 +80,8 @@ def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_bu 'content': [] } finally: - pdf_document.close() + if pdf_document is not None: + pdf_document.close() # 处理完后可以删除临时文件 os.remove(temp_file_path) @@ -331,9 +333,15 @@ def get_content(self, file, save_image): # 获取临时文件的路径 temp_file_path = temp_file.name - pdf_document = fitz.open(temp_file_path) + pdf_document = None try: + pdf_document = fitz.open(temp_file_path) return self.handle_pdf_content(file, pdf_document) except BaseException as e: traceback.print_exception(e) return f'{e}' + finally: + if pdf_document is not None: + pdf_document.close() + # 处理完后可以删除临时文件 + os.remove(temp_file_path) diff --git a/apps/common/handle/impl/text/zip_split_handle.py b/apps/common/handle/impl/text/zip_split_handle.py index d8365d5c285..498afbea848 100644 --- a/apps/common/handle/impl/text/zip_split_handle.py +++ b/apps/common/handle/impl/text/zip_split_handle.py @@ -83,7 +83,7 @@ def get_image_list(result_list: list, zip_files: List[str]): if not zip_files.__contains__(image_path): continue if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'): - image_id = image_path.replace('oss/file/', '').replace('oss/file/', '') + image_id = image_path.replace('oss/file/', '').replace('oss/image/', '') if is_valid_uuid(image_id): image_file_list.append({'source_file': image_path, 'image_id': image_id}) @@ -115,7 +115,7 @@ def get_image_list_by_content(name: str, content: str, zip_files: List[str]): if not zip_files.__contains__(image_path): continue if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'): - image_id = image_path.replace('oss/file/', '').replace('oss/file/', '') + image_id = image_path.replace('oss/file/', '').replace('oss/image/', '') if is_valid_uuid(image_id): image_file_list.append({'source_file': image_path, 'image_id': image_id}) diff --git a/apps/knowledge/serializers/common.py b/apps/knowledge/serializers/common.py index e33fa545251..c6f5264b2b1 100644 --- a/apps/knowledge/serializers/common.py +++ b/apps/knowledge/serializers/common.py @@ -164,12 +164,11 @@ def get_embedding_model_id_by_knowledge_id_list(knowledge_id_list: List): def zip_dir(zip_path, output=None): output = output or os.path.basename(zip_path) + '.zip' - zip = zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) - for root, dirs, files in os.walk(zip_path): - relative_root = '' if root == zip_path else root.replace(zip_path, '') + os.sep - for filename in files: - zip.write(os.path.join(root, filename), relative_root + filename) - zip.close() + with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as zip: + for root, dirs, files in os.walk(zip_path): + relative_root = '' if root == zip_path else root.replace(zip_path, '') + os.sep + for filename in files: + zip.write(os.path.join(root, filename), relative_root + filename) def is_valid_uuid(s): diff --git a/apps/knowledge/serializers/document.py b/apps/knowledge/serializers/document.py index d8466663464..007ac8489d9 100644 --- a/apps/knowledge/serializers/document.py +++ b/apps/knowledge/serializers/document.py @@ -228,19 +228,17 @@ def export(self, with_valid=True): self.is_valid(raise_exception=True) language = get_language() if self.data.get('type') == 'csv': - file = open( + with open( os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'csv_template_{to_locale(language)}.csv'), - "rb") - content = file.read() - file.close() + "rb") as file: + content = file.read() return HttpResponse(content, status=200, headers={'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename="csv_template.csv"'}) elif self.data.get('type') == 'excel': - file = open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', - f'excel_template_{to_locale(language)}.xlsx'), "rb") - content = file.read() - file.close() + with open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', + f'excel_template_{to_locale(language)}.xlsx'), "rb") as file: + content = file.read() return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel', 'Content-Disposition': 'attachment; filename="excel_template.xlsx"'}) else: @@ -251,20 +249,18 @@ def table_export(self, with_valid=True): self.is_valid(raise_exception=True) language = get_language() if self.data.get('type') == 'csv': - file = open( + with open( os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'table_template_{to_locale(language)}.csv'), - "rb") - content = file.read() - file.close() - return HttpResponse(content, status=200, headers={'Content-Type': 'text/cxv', + "rb") as file: + content = file.read() + return HttpResponse(content, status=200, headers={'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename="csv_template.csv"'}) elif self.data.get('type') == 'excel': - file = open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', - f'table_template_{to_locale(language)}.xlsx'), - "rb") - content = file.read() - file.close() + with open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', + f'table_template_{to_locale(language)}.xlsx'), + "rb") as file: + content = file.read() return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel', 'Content-Disposition': 'attachment; filename="excel_template.xlsx"'}) else: diff --git a/apps/knowledge/serializers/paragraph.py b/apps/knowledge/serializers/paragraph.py index a1df1bd8100..c3a36c2b2ba 100644 --- a/apps/knowledge/serializers/paragraph.py +++ b/apps/knowledge/serializers/paragraph.py @@ -226,7 +226,6 @@ def edit(self, instance: Dict): return self.one(), instance, self.data.get('knowledge_id') def get_problem_list(self): - ProblemParagraphMapping(ProblemParagraphMapping) problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter( paragraph_id=self.data.get("paragraph_id")) if len(problem_paragraph_mapping) > 0: diff --git a/apps/maxkb/urls/web.py b/apps/maxkb/urls/web.py index fb043d56a64..1fee100f073 100644 --- a/apps/maxkb/urls/web.py +++ b/apps/maxkb/urls/web.py @@ -80,9 +80,8 @@ def pro(): def get_index_html(index_path): - file = open(index_path, "r", encoding='utf-8') - content = file.read() - file.close() + with open(index_path, "r", encoding='utf-8') as file: + content = file.read() return content diff --git a/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py b/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py index 50ee4abfe65..e5d32ab1c9d 100644 --- a/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py +++ b/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py @@ -92,7 +92,11 @@ def _update_aws_credentials(profile_name, access_key_id, secret_access_key): credentials_path = os.path.join(os.path.expanduser("~"), ".aws", "credentials") os.makedirs(os.path.dirname(credentials_path), exist_ok=True) - content = open(credentials_path, 'r').read() if os.path.exists(credentials_path) else '' + if os.path.exists(credentials_path): + with open(credentials_path, 'r') as f: + content = f.read() + else: + content = '' pattern = rf'\n*\[{profile_name}\]\n*(aws_access_key_id = .*)\n*(aws_secret_access_key = .*)\n*' content = re.sub(pattern, '', content, flags=re.DOTALL) diff --git a/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py b/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py index 2ad2107e1a2..5bb8a54049f 100644 --- a/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py +++ b/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py @@ -224,7 +224,7 @@ def convert_to_down_model_chunk(row_str: str, chunk_index: int): if row.get('status').__contains__("pulling"): progress = 0 status = DownModelChunkStatus.pulling - if 'total' in row and 'completed' in row: + if 'total' in row and 'completed' in row and row.get('total'): progress = (row.get('completed') / row.get('total') * 100) elif 'error' in row: status = DownModelChunkStatus.error diff --git a/apps/trigger/handler/impl/trigger/event_trigger.py b/apps/trigger/handler/impl/trigger/event_trigger.py index 6368cf58b13..cb3df4c26f4 100644 --- a/apps/trigger/handler/impl/trigger/event_trigger.py +++ b/apps/trigger/handler/impl/trigger/event_trigger.py @@ -119,7 +119,7 @@ def execute(trigger, request=None, **kwargs): trigger_setting = trigger.get('trigger_setting') if trigger_setting.get('token'): token = request.META.get('HTTP_AUTHORIZATION') - if trigger_setting.get('token') != token.replace('Bearer ', ''): + if not token or trigger_setting.get('token') != token.replace('Bearer ', ''): raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) is_active = trigger.get('is_active') if not is_active: diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 8eafdddc9e9..cef803a9cf0 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1074,11 +1074,10 @@ def send(self): ]), range(6)))) # 获取邮件模板 language = get_language() - file = open( - os.path.join(PROJECT_DIR, "apps", "common", 'template', f'email_template_{to_locale(language)}.html'), "r", - encoding='utf-8') - content = file.read() - file.close() + with open( + os.path.join(PROJECT_DIR, "apps", "common", 'template', f'email_template_{to_locale(language)}.html'), + "r", encoding='utf-8') as file: + content = file.read() code_cache_key = email + ":" + state code_cache_key_lock = code_cache_key + "_lock" # 设置缓存 diff --git a/ui/src/components/execution-detail-card/index.vue b/ui/src/components/execution-detail-card/index.vue index 255ec043837..a50fa373586 100644 --- a/ui/src/components/execution-detail-card/index.vue +++ b/ui/src/components/execution-detail-card/index.vue @@ -194,7 +194,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
@@ -257,7 +257,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
@@ -547,7 +547,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
@@ -633,7 +633,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
diff --git a/ui/src/locales/lang/en-US/views/application.ts b/ui/src/locales/lang/en-US/views/application.ts index 14c3622917e..36a3f599ba9 100644 --- a/ui/src/locales/lang/en-US/views/application.ts +++ b/ui/src/locales/lang/en-US/views/application.ts @@ -88,6 +88,11 @@ export default { - Please use concise and professional language to answer the user's question. `, }, + prompt_template: { + label: 'Prompt Template', + requiredMessage: 'Please enter Prompt template', + tooltip: 'Please pay attention to the placeholders in the template: {classification_list}、{user_input}、{output_json}', + }, historyRecord: { label: 'Chat History', }, diff --git a/ui/src/locales/lang/en-US/workflow.ts b/ui/src/locales/lang/en-US/workflow.ts index e39d262a977..e6e393e49be 100644 --- a/ui/src/locales/lang/en-US/workflow.ts +++ b/ui/src/locales/lang/en-US/workflow.ts @@ -449,9 +449,34 @@ You are a master of problem optimization, adept at accurately inferring user int classify: { label: 'Intent classify', }, + output_reason: 'Output Reason', input: { label: 'Input', }, + default_prompt_template: `# Role +You are an intention classification expert, good at being able to judge which classification the user's input belongs to. + +## Skills +Skill 1: Clearly determine which of the following intention classifications the user's input belongs to. +Intention classification list: +{classification_list} + +Note: +- Please determine the match between the user's input content and the Intention classification list content, without judging or categorizing the match with the classification ID. +- **When classifying, you must give higher weight to the context and intent continuity shown in the historical conversation. Do not rely solely on the literal meaning of the current input; instead, prioritize the most consistent classification with the previous dialogue flow.** + +## User Input +{user_input} + +## Reply requirements +- The answer must be returned in JSON format. +- Strictly ensure that the output is in a valid JSON format. +- Do not add prefix \`\`\`json or suffix \`\`\` +- The answer needs to include the following fields such as: +{output_json} + +## Limit +- Please do not reply in text.`, }, applicationNode: { label: 'Agent Node', @@ -540,9 +565,12 @@ You are a master of problem optimization, adept at accurately inferring user int len_lt: 'Length less than', is_true: 'Is true', is_not_true: 'Is not true', + regex: 'Regex matching', + wildcard: 'Wildcard matching', }, SystemPromptPlaceholder: 'System Prompt, can reference variables in the system, such as', UserPromptPlaceholder: 'User Prompt, can reference variables in the system, such as', + PromptTemplatePlaceholder: 'Prompt Template, can reference variables in the system, such as', initiator: 'Iniiator', abnormalInformation: 'Abnormal Information', } diff --git a/ui/src/locales/lang/zh-CN/views/application.ts b/ui/src/locales/lang/zh-CN/views/application.ts index d1d00f7a8b8..7f413a0a689 100644 --- a/ui/src/locales/lang/zh-CN/views/application.ts +++ b/ui/src/locales/lang/zh-CN/views/application.ts @@ -81,6 +81,11 @@ export default { 回答要求: - 请使用中文回答用户问题`, }, + prompt_template: { + label: '提示词模板', + requiredMessage: '请输入提示词模板', + tooltip: '请注意模板中的占位符: {classification_list}、{user_input}、{output_json}', + }, historyRecord: { label: '历史聊天记录', }, diff --git a/ui/src/locales/lang/zh-CN/workflow.ts b/ui/src/locales/lang/zh-CN/workflow.ts index 3c004d437c2..d30c435e5c5 100644 --- a/ui/src/locales/lang/zh-CN/workflow.ts +++ b/ui/src/locales/lang/zh-CN/workflow.ts @@ -441,9 +441,34 @@ export default { classify: { label: '意图分类', }, + output_reason: '输出理由', input: { label: '输入', }, + default_prompt_template: `# 角色 +你是一位意图分类专家,擅长判断用户输入属于哪个分类。 + +## 技能 +技能1:明确判断用户输入属于以下哪种意图分类。 +意图分类列表: +{classification_list} + +注: +- 请判断用户输入内容与意图分类列表内容之间的匹配度,注意不要以分类ID作为评判或归类的依据。 +- **在分类时,必须更加重视历史对话中表现的上下文和意图连贯性。不要仅依赖当前输入的字面意思;相反,应优先考虑与先前对话流程最匹配的分类。** + +## 用户输入 +{user_input} + +## 回复要求 +- 回复内容必须以JSON格式返回。 +- 严格确保输出为有效的JSON格式。 +- 不要添加前缀 \`\`\`json 或 后缀 \`\`\` +- 回复内容需要包含以下字段: +{output_json} + +## 限制 +- 请勿以文本形式回复。`, }, applicationNode: { label: '智能体节点', @@ -531,9 +556,12 @@ export default { len_lt: '长度小于', is_true: '为真', is_not_true: '不为真', + regex: '正则匹配', + wildcard: '通配符匹配', }, SystemPromptPlaceholder: '系统提示词,可以引用系统中的变量:如', UserPromptPlaceholder: '用户提示词,可以引用系统中的变量:如', + PromptTemplatePlaceholder: '提示词模板,可以引用系统中的变量:如', initiator: '发起人', abnormalInformation: '异常信息' diff --git a/ui/src/locales/lang/zh-Hant/views/application.ts b/ui/src/locales/lang/zh-Hant/views/application.ts index dce197edf1c..ecd6210e336 100644 --- a/ui/src/locales/lang/zh-Hant/views/application.ts +++ b/ui/src/locales/lang/zh-Hant/views/application.ts @@ -81,6 +81,11 @@ export default { 回答要求: - 請使用中文回答用戶問題`, }, + prompt_template: { + label: '提示詞模板', + requiredMessage: '請輸入提示詞模板', + tooltip: '請注意模板中的佔位符: {classification_list}、{user_input}、{output_json}', + }, historyRecord: { label: '歷史對話紀錄', }, diff --git a/ui/src/locales/lang/zh-Hant/workflow.ts b/ui/src/locales/lang/zh-Hant/workflow.ts index a7fd05c9f1b..ccfd0a6ab75 100644 --- a/ui/src/locales/lang/zh-Hant/workflow.ts +++ b/ui/src/locales/lang/zh-Hant/workflow.ts @@ -441,9 +441,34 @@ export default { classify: { label: '意圖分類', }, + output_reason: '輸出理由', input: { label: '輸入', }, + default_prompt_template: `# 角色 +你是一位意圖分類專家,擅長判斷用戶輸入屬於哪個分類。 + +## 技能 +技能1:明確判斷用戶輸入屬於以下哪種意圖分類。 +意圖分類列表: +{classification_list} + +注: +- 請判斷用戶輸入內容與意圖分類列表內容之間的匹配度,注意不要以分類ID作爲評判或歸類的依據。 +- **在分類時,必須更加重視歷史對話中表現的上下文和意圖連貫性。不要僅依賴當前輸入的字面意思;相反,應優先考慮與先前對話流程最匹配的分類。** + +## 用戶輸入 +{user_input} + +## 回覆要求 +- 回覆內容必須以JSON格式返回。 +- 嚴格確保輸出爲有效的JSON格式。 +- 不要添加前綴 \`\`\`json 或 後綴 \`\`\` +- 回覆內容需要包含以下字段: +{output_json} + +## 限制 +- 請勿以文本形式回覆。`, }, applicationNode: { label: '智能體節點', @@ -525,9 +550,12 @@ export default { len_lt: '長度小於', is_true: '為真', is_not_true: '不為真', + regex: '正則匹配', + wildcard: '通配符匹配', }, SystemPromptPlaceholder: '系統提示詞,可以引用系統中的變量:如', UserPromptPlaceholder: '用戶提示詞,可以引用系統中的變量:如', + PromptTemplatePlaceholder: '提示詞模板,可以引用系統中的變量:如', initiator: '發起人', abnormalInformation: '異常信息', } diff --git a/ui/src/styles/app.scss b/ui/src/styles/app.scss index f203477fef0..f36ed527084 100644 --- a/ui/src/styles/app.scss +++ b/ui/src/styles/app.scss @@ -31,6 +31,11 @@ body { color: var(--el-text-color-primary); } +.card-never pre { + font-family: 'PingFang SC', AlibabaPuHuiTi !important; + white-space: break-spaces; +} + #app { height: 100%; } diff --git a/ui/src/views/application-workflow/index.vue b/ui/src/views/application-workflow/index.vue index d178f32f71a..3a130af64f3 100644 --- a/ui/src/views/application-workflow/index.vue +++ b/ui/src/views/application-workflow/index.vue @@ -229,6 +229,9 @@ const shareUrl = computed( function back() { if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) { + console.debug("JSON.stringify(cloneWorkFlow.value):", JSON.stringify(cloneWorkFlow.value)) + console.debug("JSON.stringify(getGraphData()):", JSON.stringify(getGraphData())) + MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), { confirmButtonText: t('workflow.setting.exitSave'), cancelButtonText: t('workflow.setting.exit'), @@ -699,7 +702,7 @@ onBeforeUnmount(() => { bottom: 16px; right: 16px; overflow: hidden; - width: 460px; + width: 550px; height: 680px; .workflow-debug-header { diff --git a/ui/src/views/knowledge-workflow/index.vue b/ui/src/views/knowledge-workflow/index.vue index 6a1073e5c04..a2413cc8314 100644 --- a/ui/src/views/knowledge-workflow/index.vue +++ b/ui/src/views/knowledge-workflow/index.vue @@ -260,6 +260,9 @@ const isPublish = computed(() => detail.value?.is_publish) function back() { if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) { + console.debug("JSON.stringify(cloneWorkFlow.value):", JSON.stringify(cloneWorkFlow.value)) + console.debug("JSON.stringify(getGraphData()):", JSON.stringify(getGraphData())) + MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), { confirmButtonText: t('workflow.setting.exitSave'), cancelButtonText: t('workflow.setting.exit'), @@ -735,7 +738,7 @@ onBeforeUnmount(() => { bottom: 16px; right: 16px; overflow: hidden; - width: 460px; + width: 550px; height: 680px; .workflow-debug-header { diff --git a/ui/src/views/tool-workflow/index.vue b/ui/src/views/tool-workflow/index.vue index 0d1496a661f..64b41b65ace 100644 --- a/ui/src/views/tool-workflow/index.vue +++ b/ui/src/views/tool-workflow/index.vue @@ -227,6 +227,9 @@ const isPublish = computed(() => detail.value?.is_publish) function back() { if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) { + console.debug("JSON.stringify(cloneWorkFlow.value):", JSON.stringify(cloneWorkFlow.value)) + console.debug("JSON.stringify(getGraphData()):", JSON.stringify(getGraphData())) + MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), { confirmButtonText: t('workflow.setting.exitSave'), cancelButtonText: t('workflow.setting.exit'), diff --git a/ui/src/workflow/common/NodeContainer.vue b/ui/src/workflow/common/NodeContainer.vue index 86cf69a6df6..4fb3f9cdb4f 100644 --- a/ui/src/workflow/common/NodeContainer.vue +++ b/ui/src/workflow/common/NodeContainer.vue @@ -253,7 +253,7 @@ const condition = computed({ if (props.nodeModel.properties.condition) { return props.nodeModel.properties.condition } - set(props.nodeModel.properties, 'condition', 'AND') + set(props.nodeModel.properties, 'condition', 'OR') return true }, }) diff --git a/ui/src/workflow/common/data.ts b/ui/src/workflow/common/data.ts index c6120fd60df..a4c2fa1c2ea 100644 --- a/ui/src/workflow/common/data.ts +++ b/ui/src/workflow/common/data.ts @@ -1135,6 +1135,8 @@ export const compareList = [ { value: 'is_not_true', label: t('workflow.compare.is_not_true') }, { value: 'start_with', label: 'startWith' }, { value: 'end_with', label: 'endWith' }, + { value: 'regex', label: t('workflow.compare.regex') }, + { value: 'wildcard', label: t('workflow.compare.wildcard') }, ] export const nodeDict: any = { [WorkflowType.AiChat]: aiChatNode, diff --git a/ui/src/workflow/nodes/intent-classify-node/index.ts b/ui/src/workflow/nodes/intent-classify-node/index.ts index c39167e9294..406a66f5278 100644 --- a/ui/src/workflow/nodes/intent-classify-node/index.ts +++ b/ui/src/workflow/nodes/intent-classify-node/index.ts @@ -48,11 +48,11 @@ class IntentModel extends AppNodeModel { if (branch_condition_list) { - const FORM_ITEMS_HEIGHT = 397 // 上方表单占用高度 - + const FORM_ITEMS_HEIGHT = 542 // 上方表单占用高度 + for (let index = 0; index < branch_condition_list.length; index++) { const element = branch_condition_list[index] - + anchors.push({ x: x + width / 2 - 10, y: showNode diff --git a/ui/src/workflow/nodes/intent-classify-node/index.vue b/ui/src/workflow/nodes/intent-classify-node/index.vue index 7ed132c959c..2e52bbc8716 100644 --- a/ui/src/workflow/nodes/intent-classify-node/index.vue +++ b/ui/src/workflow/nodes/intent-classify-node/index.vue @@ -76,6 +76,42 @@ v-model="form_data.model_id_reference" /> + + + +
+ +
+
+ {{ $t('workflow.nodes.intentNode.output_reason') }} +
+
+ +
+
+
@@ -304,10 +353,17 @@ const model_change = (model_id?: string) => { } } +const default_prompt_template = t('workflow.nodes.intentNode.default_prompt_template', { + classification_list: '{classification_list}', + user_input: '{user_input}', + output_json: '{output_json}', +}) + const form = { model_id: '', model_id_type: 'custom', model_id_reference: [], + prompt_template: default_prompt_template, branch: [ { id: randomId(), @@ -322,12 +378,17 @@ const form = { ], dialogue_number: 1, content_list: [], + output_reason: true, } function refreshParam(data: any) { set(props.nodeModel.properties.node_data, 'model_params_setting', data) } +function submitTemplateDialog(val: string) { + set(props.nodeModel.properties.node_data, 'prompt_template', val) +} + const openAIParamSettingDialog = (modelId: string) => { if (modelId) { AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting) @@ -342,6 +403,12 @@ const form_data = computed({ if (!props.nodeModel.properties.node_data.model_id_reference) { set(props.nodeModel.properties.node_data, 'model_id_reference', []) } + if (!props.nodeModel.properties.node_data.prompt_template) { + set(props.nodeModel.properties.node_data, 'prompt_template', default_prompt_template) + } + if (props.nodeModel.properties.node_data.output_reason == null) { + set(props.nodeModel.properties.node_data, 'output_reason', true) + } return props.nodeModel.properties.node_data } else { set(props.nodeModel.properties, 'node_data', form) From 3a09a22284bdd5c851e03bf413cc076c275c960f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Tue, 14 Apr 2026 16:12:19 +0800 Subject: [PATCH 14/14] =?UTF-8?q?Revert=20"feat:=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E6=88=91=E6=89=80=E6=9C=89=E6=8F=90=E4=BA=A4=E7=9A=84PR?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E3=80=82"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit dca4d226bb4897208e54e93c7d8b2d9b3726cfd9. --- .../step/chat_step/impl/base_chat_step.py | 2 +- apps/application/flow/compare/__init__.py | 4 +- .../application/flow/compare/regex_compare.py | 40 --------- .../flow/compare/wildcard_compare.py | 43 ---------- .../ai_chat_step_node/impl/base_chat_node.py | 4 +- .../step_node/intent_node/i_intent_node.py | 2 - .../intent_node/impl/base_intent_node.py | 63 ++++++--------- .../intent_node/impl/prompt_template.py | 7 +- .../question_node/impl/base_question_node.py | 2 +- .../tool_lib_node/impl/base_tool_lib_node.py | 3 +- .../impl/base_variable_aggregation_node.py | 2 +- .../impl/base_variable_assign_node.py | 23 +++--- .../impl/base_variable_splitting_node.py | 2 +- .../serializers/application_chat_record.py | 3 +- apps/chat/serializers/chat.py | 8 +- .../serializers/chat_embed_serializers.py | 5 +- apps/chat/serializers/chat_record.py | 3 +- apps/common/handle/impl/common_handle.py | 81 ++++++++++--------- .../handle/impl/text/pdf_split_handle.py | 14 +--- .../handle/impl/text/zip_split_handle.py | 4 +- apps/knowledge/serializers/common.py | 11 +-- apps/knowledge/serializers/document.py | 32 ++++---- apps/knowledge/serializers/paragraph.py | 1 + apps/maxkb/urls/web.py | 5 +- .../aws_bedrock_model_provider/model/llm.py | 6 +- .../ollama_model_provider.py | 2 +- .../handler/impl/trigger/event_trigger.py | 2 +- apps/users/serializers/user.py | 9 ++- .../execution-detail-card/index.vue | 8 +- .../locales/lang/en-US/views/application.ts | 5 -- ui/src/locales/lang/en-US/workflow.ts | 28 ------- .../locales/lang/zh-CN/views/application.ts | 5 -- ui/src/locales/lang/zh-CN/workflow.ts | 28 ------- .../locales/lang/zh-Hant/views/application.ts | 5 -- ui/src/locales/lang/zh-Hant/workflow.ts | 28 ------- ui/src/styles/app.scss | 5 -- ui/src/views/application-workflow/index.vue | 5 +- ui/src/views/knowledge-workflow/index.vue | 5 +- ui/src/views/tool-workflow/index.vue | 3 - ui/src/workflow/common/NodeContainer.vue | 2 +- ui/src/workflow/common/data.ts | 2 - .../nodes/intent-classify-node/index.ts | 6 +- .../nodes/intent-classify-node/index.vue | 67 --------------- 43 files changed, 152 insertions(+), 433 deletions(-) delete mode 100644 apps/application/flow/compare/regex_compare.py delete mode 100644 apps/application/flow/compare/wildcard_compare.py diff --git a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py index fce84f5a097..6eec69e7bd6 100644 --- a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py +++ b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py @@ -183,7 +183,7 @@ def execute(self, message_list: List[BaseMessage], mcp_output_enable=True, **kwargs): chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id, - **(model_params_setting or {})) if model_id is not None else None + **model_params_setting) if model_id is not None else None if stream: return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model, paragraph_list, diff --git a/apps/application/flow/compare/__init__.py b/apps/application/flow/compare/__init__.py index 4e99bfa7270..f0dd01523f5 100644 --- a/apps/application/flow/compare/__init__.py +++ b/apps/application/flow/compare/__init__.py @@ -25,12 +25,10 @@ from .lt_compare import * from .not_contain_compare import * from .not_equal_compare import * -from .regex_compare import RegexCompare from .start_with import StartWithCompare -from .wildcard_compare import WildcardCompare compare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(), LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(), IsNullCompare(), IsNotNullCompare(), NotContainCompare(), NotEqualCompare(), IsTrueCompare(), IsNotTrueCompare(), StartWithCompare(), - EndWithCompare(), RegexCompare(), WildcardCompare()] + EndWithCompare()] diff --git a/apps/application/flow/compare/regex_compare.py b/apps/application/flow/compare/regex_compare.py deleted file mode 100644 index 8fd98f3eca8..00000000000 --- a/apps/application/flow/compare/regex_compare.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding=utf-8 -""" - @project: maxkb - @Author:wangliang181230 - @file: regex_compare.py - @date:2026/3/30 12:11 - @desc: -""" -import re -from typing import List - -from application.flow.compare import Compare -from common.cache.mem_cache import MemCache - - -match_cache = MemCache('regex', { - 'TIMEOUT': 3600, # 缓存有效期为 1 小时 - 'OPTIONS': { - 'MAX_ENTRIES': 500, # 最多缓存 500 个条目 - 'CULL_FREQUENCY': 10, # 达到上限时,删除约 1/10 的缓存 - }, -}) - - -def compile_and_cache(regex): - match = match_cache.get(regex) - if not match: - match = re.compile(regex).match - match_cache.set(regex, match) - return match - -class RegexCompare(Compare): - - def support(self, node_id, fields: List[str], source_value, compare, target_value): - if compare == 'regex': - return True - - def compare(self, source_value, compare, target_value): - match = compile_and_cache(str(target_value)) - return bool(match(str(source_value))) diff --git a/apps/application/flow/compare/wildcard_compare.py b/apps/application/flow/compare/wildcard_compare.py deleted file mode 100644 index 65ea2c74a5a..00000000000 --- a/apps/application/flow/compare/wildcard_compare.py +++ /dev/null @@ -1,43 +0,0 @@ -# coding=utf-8 -""" - @project: maxkb - @Author:wangliang181230 - @file: wildcard_compare.py - @date:2026/3/30 12:11 - @desc: -""" -import fnmatch -import re -from typing import List - -from application.flow.compare import Compare -from common.cache.mem_cache import MemCache - - -match_cache = MemCache('wildcard_to_regex', { - 'TIMEOUT': 3600, # 缓存有效期为 1 小时 - 'OPTIONS': { - 'MAX_ENTRIES': 500, # 最多缓存 500 个条目 - 'CULL_FREQUENCY': 10, # 达到上限时,删除约 1/10 的缓存 - }, -}) - - -def translate_and_compile_and_cache(wildcard): - match = match_cache.get(wildcard) - if not match: - regex = fnmatch.translate(wildcard) - match = re.compile(regex).match - match_cache.set(wildcard, match) - return match - -class WildcardCompare(Compare): - - def support(self, node_id, fields: List[str], source_value, compare, target_value): - if compare == 'wildcard': - return True - - def compare(self, source_value, compare, target_value): - # 转成正则,性能更高 - match = translate_and_compile_and_cache(str(target_value)) - return bool(match(str(source_value))) diff --git a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py b/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py index e0048a5a582..664b696ed63 100644 --- a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py +++ b/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py @@ -364,7 +364,7 @@ def handle_variables(self, tool_params): tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k]) if type(v) == dict: self.handle_variables(v) - if (type(v) == list) and len(v) > 0 and (type(v[0]) == str): + if (type(v) == list) and (type(v[0]) == str): tool_params[k] = self.get_reference_content(v) return tool_params @@ -390,7 +390,7 @@ def generate_prompt_question(self, prompt): def generate_message_list(self, system: str, prompt: str, history_message): if system is not None and len(system) > 0: - return [SystemMessage(system), *history_message, + return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] else: return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] diff --git a/apps/application/flow/step_node/intent_node/i_intent_node.py b/apps/application/flow/step_node/intent_node/i_intent_node.py index e57931da0a0..d22d321c842 100644 --- a/apps/application/flow/step_node/intent_node/i_intent_node.py +++ b/apps/application/flow/step_node/intent_node/i_intent_node.py @@ -20,14 +20,12 @@ class IntentNodeSerializer(serializers.Serializer): model_id_type = serializers.CharField(required=False, default='custom', label=_("Model id type")) model_id_reference = serializers.ListField(required=False, child=serializers.CharField(), allow_empty=True, label=_("Reference Field")) - prompt_template = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Prompt template")) content_list = serializers.ListField(required=True, label=_("Text content")) dialogue_number = serializers.IntegerField(required=True, label= _("Number of multi-round conversations")) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) branch = IntentBranchSerializer(many=True) - output_reason = serializers.BooleanField(required=False, label=_("Output reason"), default=True) class IIntentNode(INode): diff --git a/apps/application/flow/step_node/intent_node/impl/base_intent_node.py b/apps/application/flow/step_node/intent_node/impl/base_intent_node.py index 5692bcb2912..64822aa9a20 100644 --- a/apps/application/flow/step_node/intent_node/impl/base_intent_node.py +++ b/apps/application/flow/step_node/intent_node/impl/base_intent_node.py @@ -12,7 +12,7 @@ from application.flow.step_node.intent_node.i_intent_node import IIntentNode from models_provider.models import Model from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential -from .prompt_template import DEFAULT_PROMPT_TEMPLATE +from .prompt_template import PROMPT_TEMPLATE def get_default_model_params_setting(model_id): @@ -52,7 +52,7 @@ def save_context(self, details, workflow_manage): self.context['branch_id'] = details.get('branch_id') self.context['category'] = details.get('category') - def execute(self, model_id, prompt_template, dialogue_number, history_chat_record, user_input, branch, output_reason, + def execute(self, model_id, dialogue_number, history_chat_record, user_input, branch, model_params_setting=None, model_id_type=None, model_id_reference=None, **kwargs) -> NodeResult: # 处理引用类型 if model_id_type == 'reference' and model_id_reference: @@ -77,19 +77,18 @@ def execute(self, model_id, prompt_template, dialogue_number, history_chat_recor ) # 获取历史对话 - history_message = self.get_history_message(history_chat_record, dialogue_number) if history_chat_record and dialogue_number > 0 else [] + history_message = self.get_history_message(history_chat_record, dialogue_number) self.context['history_message'] = history_message # 保存问题到上下文 self.context['user_input'] = user_input # 构建分类提示词 - prompt_template = self.workflow_manage.generate_prompt(prompt_template) if prompt_template else None - prompt = self.build_classification_prompt(prompt_template, user_input, branch, output_reason) - self.context['system'] = prompt + prompt = self.build_classification_prompt(user_input, branch) # 生成消息列表 - message_list = self.generate_message_list(prompt, history_message) + system = self.build_system_prompt() + message_list = self.generate_message_list(system, prompt, history_message) self.context['message_list'] = message_list # 调用模型进行分类 @@ -107,7 +106,7 @@ def execute(self, model_id, prompt_template, dialogue_number, history_chat_recor 'history_message': history_message, 'user_input': user_input, 'branch_id': matched_branch['id'], - 'reason': self.parse_result_reason(r.content) if output_reason is not False else '', + 'reason': self.parse_result_reason(r.content), 'category': matched_branch.get('content', matched_branch['id']) }, {}, _write_context=write_context) @@ -137,7 +136,11 @@ def get_history_message(history_chat_record, dialogue_number): message.content = re.sub('[\d\D]*?<\/form_rander>', '', message.content) return history_message - def build_classification_prompt(self, prompt_template: str, user_input: str, branch: List[Dict], output_reason: bool) -> str: + def build_system_prompt(self) -> str: + """构建系统提示词""" + return "你是一个专业的意图识别助手,请根据用户输入和意图选项,准确识别用户的真实意图。" + + def build_classification_prompt(self, user_input: str, branch: List[Dict]) -> str: """构建分类提示词""" classification_list = [] @@ -159,19 +162,18 @@ def build_classification_prompt(self, prompt_template: str, user_input: str, bra }) classification_id += 1 - # 构建输出JSON结构 - reason_field = ',\n"reason": ""' if output_reason is not False else '' - output_json = f'{{\n"classificationId": 0{reason_field}\n}}' - - return (prompt_template or DEFAULT_PROMPT_TEMPLATE).format( - classification_list=json.dumps(classification_list, ensure_ascii=False), - user_input=user_input, - output_json=output_json + return PROMPT_TEMPLATE.format( + classification_list=classification_list, + user_input=user_input ) - def generate_message_list(self, prompt: str, history_message): + def generate_message_list(self, system: str, prompt: str, history_message): """生成消息列表""" - return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] + if system is None or len(system) == 0: + return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] + else: + return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, + HumanMessage(self.workflow_manage.generate_prompt(prompt))] def parse_classification_result(self, result: str, branch: List[Dict]) -> Dict[str, Any]: """解析分类结果""" @@ -191,23 +193,14 @@ def get_branch_by_id(category_id: int): return None try: - classification_id = None - - # 如果长度小于5,先尝试解析为数字(增加自由度,在自定义提示词模板时,可提示大模型只输出意图分类的ID值) - if len(result) < 5: - classification_id = self.to_int(result) - - # 尝试解析为 JSON - if classification_id is None: - result_json = json.loads(result) - classification_id = result_json.get('classificationId') - + result_json = json.loads(result) + classification_id = result_json.get('classificationId') # 如果是 0 ,返回其他分支 matched_branch = get_branch_by_id(classification_id) if matched_branch: return matched_branch - except Exception: + except Exception as e: # json 解析失败,re 提取 numbers = re.findall(r'"classificationId":\s*(\d+)', result) if numbers: @@ -225,7 +218,7 @@ def parse_result_reason(self, result: str): try: result_json = json.loads(result) return result_json.get('reason', '') - except Exception: + except Exception as e: reason_patterns = [ r'"reason":\s*"([^"]*)"', # 标准格式 r'"reason":\s*"([^"]*)', # 缺少结束引号 @@ -241,12 +234,6 @@ def parse_result_reason(self, result: str): return '' - def to_int(self, str): - try: - return int(str) - except ValueError: - return None - def find_other_branch(self, branch: List[Dict]) -> Dict[str, Any] | None: """查找其他分支""" for b in branch: diff --git a/apps/application/flow/step_node/intent_node/impl/prompt_template.py b/apps/application/flow/step_node/intent_node/impl/prompt_template.py index 79062022edb..1bcfd61743e 100644 --- a/apps/application/flow/step_node/intent_node/impl/prompt_template.py +++ b/apps/application/flow/step_node/intent_node/impl/prompt_template.py @@ -1,6 +1,6 @@ -DEFAULT_PROMPT_TEMPLATE = """# Role +PROMPT_TEMPLATE = """# Role You are an intention classification expert, good at being able to judge which classification the user's input belongs to. ## Skills @@ -20,7 +20,10 @@ - Strictly ensure that the output is in a valid JSON format. - Do not add prefix ```json or suffix ``` - The answer needs to include the following fields such as: -{output_json} +{{ +"classificationId": 0, +"reason": "" +}} ## Limit - Please do not reply in text.""" diff --git a/apps/application/flow/step_node/question_node/impl/base_question_node.py b/apps/application/flow/step_node/question_node/impl/base_question_node.py index 9173c6d27a5..3f622c5f2dc 100644 --- a/apps/application/flow/step_node/question_node/impl/base_question_node.py +++ b/apps/application/flow/step_node/question_node/impl/base_question_node.py @@ -138,7 +138,7 @@ def generate_prompt_question(self, prompt): def generate_message_list(self, system: str, prompt: str, history_message): if system is not None and len(system) > 0: - return [SystemMessage(system), *history_message, + return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] else: return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] diff --git a/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py b/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py index 1553fad918e..f2434284d76 100644 --- a/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py +++ b/apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py @@ -251,7 +251,6 @@ def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult: def tool_exec_record(self, tool_lib, all_params): task_record_id = uuid.uuid7() start_time = time.time() - filtered_args = all_params try: # 过滤掉 tool_init_params 中的参数 tool_init_params = json.loads(rsa_long_decrypt(tool_lib.init_params)) if tool_lib.init_params else {} @@ -260,6 +259,8 @@ def tool_exec_record(self, tool_lib, all_params): k: v for k, v in all_params.items() if k not in tool_init_params } + else: + filtered_args = all_params if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__( self.workflow_manage.flow.workflow_mode): source_id = self.workflow_manage.params.get('knowledge_id') diff --git a/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py b/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py index 341f2e0eab9..fa1d6479e5c 100644 --- a/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py +++ b/apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py @@ -24,7 +24,7 @@ class BaseVariableAggregationNode(IVariableAggregation): def save_context(self, details, workflow_manage): for key, value in details.get('result').items(): - self.context[key] = value + self.context['key'] = value self.context['result'] = details.get('result') self.context['strategy'] = details.get('strategy') self.context['group_list'] = details.get('group_list') diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index 0df405f39be..30340d5fb81 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -94,41 +94,38 @@ def handle(self, variable, evaluation): val = self.convert(val, variable.get('target_type')) evaluation(variable, val) result['output_value'] = val - elif variable['source'] == 'referencing': + elif variable['source'] == 'null': + val = None + evaluation(variable, val) + result['output_value'] = val + else: reference = self.get_reference_content(variable['reference']) reference = self.convert(reference, variable.get('target_type')) evaluation(variable, reference) result['output_value'] = reference - else: - val = None - evaluation(variable, val) - result['output_value'] = val - # 获取输入输出值的类型,用于显示在执行详情页面中 result['input_type'] = type(result.get('input_value')).__name__ if result.get('input_value') is not None else 'null' result['output_type'] = type(result.get('output_value')).__name__ if result.get('output_value') is not None else 'null' - return result def execute(self, variable_list, **kwargs) -> NodeResult: result_list = [] contains_chat_variable = False for variable in variable_list: - if not variable.get('fields'): + if 'fields' not in variable: continue - field0 = variable['fields'][0] - if 'global' == field0: + if 'global' == variable['fields'][0]: result = self.handle(variable, self.global_evaluation) result_list.append(result) - elif 'chat' == field0: + elif 'chat' == variable['fields'][0]: result = self.handle(variable, self.chat_evaluation) result_list.append(result) contains_chat_variable = True - elif 'loop' == field0: + elif 'loop' == variable['fields'][0]: result = self.handle(variable, self.loop_evaluation) result_list.append(result) - elif 'output' == field0: + elif 'output' == variable['fields'][0]: result = self.handle(variable, self.out_evaluation) result_list.append(result) diff --git a/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py b/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py index 274604e2328..90a47613a46 100644 --- a/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py +++ b/apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py @@ -16,7 +16,7 @@ jsonpath_expr_cache = MemCache('parse_path', { 'TIMEOUT': 3600, # 缓存有效期为 1 小时 'OPTIONS': { - 'MAX_ENTRIES': 1000, # 最多缓存 1000 个条目 + 'MAX_ENTRIES': 1000, # 最多缓存 500 个条目 'CULL_FREQUENCY': 10, # 达到上限时,删除约 1/10 的缓存 }, }) diff --git a/apps/application/serializers/application_chat_record.py b/apps/application/serializers/application_chat_record.py index 92946bbdc01..540d9310dd9 100644 --- a/apps/application/serializers/application_chat_record.py +++ b/apps/application/serializers/application_chat_record.py @@ -104,6 +104,7 @@ def is_valid(self, *, raise_exception=False): def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) + QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')) order_by = 'create_time' if self.data.get('order_asc') is None or self.data.get( 'order_asc') else '-create_time' return [ChatRecordSerializerModel(chat_record).data for chat_record in @@ -168,7 +169,7 @@ def reset_chat_record(chat_record, show_source, show_exec): 'padding_problem_text': chat_record.details.get('problem_padding').get( 'padding_problem_text') if 'problem_padding' in chat_record.details else None, **(show_source_dict if show_source else {}), - **(show_exec_dict if show_exec else {}) + **(show_exec_dict if show_exec else show_exec_dict) } def page(self, current_page: int, page_size: int, with_valid=True, show_source=None, show_exec=None): diff --git a/apps/chat/serializers/chat.py b/apps/chat/serializers/chat.py index 4514dc457bc..ddb08cbcc6f 100644 --- a/apps/chat/serializers/chat.py +++ b/apps/chat/serializers/chat.py @@ -382,10 +382,10 @@ def get_chat_record(chat_info, chat_record_id): str(chat_record.id) == str(chat_record_id)] if chat_record_list is not None and len(chat_record_list): return chat_record_list[-1] - chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first() - if chat_record is None: - if not is_valid_uuid(chat_record_id): - raise ChatException(500, _("Conversation record does not exist")) + chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first() + if chat_record is None: + if not is_valid_uuid(chat_record_id): + raise ChatException(500, _("Conversation record does not exist")) chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first() return chat_record diff --git a/apps/chat/serializers/chat_embed_serializers.py b/apps/chat/serializers/chat_embed_serializers.py index 5594e5b0abe..7f330f93e94 100644 --- a/apps/chat/serializers/chat_embed_serializers.py +++ b/apps/chat/serializers/chat_embed_serializers.py @@ -32,8 +32,9 @@ def get_embed(self, with_valid=True, params=None): if with_valid: self.is_valid(raise_exception=True) index_path = os.path.join(PROJECT_DIR, 'apps', "chat", 'template', 'embed.js') - with open(index_path, "r", encoding='utf-8') as file: - content = file.read() + file = open(index_path, "r", encoding='utf-8') + content = file.read() + file.close() application_access_token = QuerySet(ApplicationAccessToken).filter( access_token=self.data.get('token')).first() is_draggable = 'false' diff --git a/apps/chat/serializers/chat_record.py b/apps/chat/serializers/chat_record.py index ee6d446dd57..d094216f53f 100644 --- a/apps/chat/serializers/chat_record.py +++ b/apps/chat/serializers/chat_record.py @@ -75,7 +75,8 @@ def vote(self, instance: Dict, with_valid=True): chat_record_details_model.vote_status = VoteChoices.STAR chat_record_details_model.vote_reason = vote_reason chat_record_details_model.vote_other_content = vote_other_content - elif vote_status == VoteChoices.TRAMPLE: + + if vote_status == VoteChoices.TRAMPLE: # 点踩 chat_record_details_model.vote_status = VoteChoices.TRAMPLE chat_record_details_model.vote_reason = vote_reason diff --git a/apps/common/handle/impl/common_handle.py b/apps/common/handle/impl/common_handle.py index 98b7537926f..16c647a9626 100644 --- a/apps/common/handle/impl/common_handle.py +++ b/apps/common/handle/impl/common_handle.py @@ -86,45 +86,46 @@ def handle_images(deps, archive: ZipFile) -> []: def xlsx_embed_cells_images(buffer) -> {}: - with ZipFile(buffer) as archive: - # 解析cellImage.xml文件 - deps = get_dependents(archive, get_rels_path("xl/cellimages.xml")) - image_rel = handle_images(deps=deps, archive=archive) - # 工作表及其中图片ID - sheet_list = {} - for item in archive.namelist(): - if not item.startswith('xl/worksheets/sheet'): - continue - key = item.split('/')[-1].split('.')[0].split('sheet')[-1] - sheet_list[key] = parse_element_sheet_xml(fromstring(archive.read(item))) - cell_images_xml = parse_element(fromstring(archive.read("xl/cellimages.xml"))) - cell_images_rel = {} - for image in image_rel: - cell_images_rel[image.embed] = image - for cnv, embed in cell_images_xml.items(): - cell_images_xml[cnv] = cell_images_rel.get(embed) - result = {} - for key, img in cell_images_xml.items(): - all_cells = [ - cell - for _sheet_id, sheet in sheet_list.items() - if sheet is not None - for cell in sheet or [] - ] + archive = ZipFile(buffer) + # 解析cellImage.xml文件 + deps = get_dependents(archive, get_rels_path("xl/cellimages.xml")) + image_rel = handle_images(deps=deps, archive=archive) + # 工作表及其中图片ID + sheet_list = {} + for item in archive.namelist(): + if not item.startswith('xl/worksheets/sheet'): + continue + key = item.split('/')[-1].split('.')[0].split('sheet')[-1] + sheet_list[key] = parse_element_sheet_xml(fromstring(archive.read(item))) + cell_images_xml = parse_element(fromstring(archive.read("xl/cellimages.xml"))) + cell_images_rel = {} + for image in image_rel: + cell_images_rel[image.embed] = image + for cnv, embed in cell_images_xml.items(): + cell_images_xml[cnv] = cell_images_rel.get(embed) + result = {} + for key, img in cell_images_xml.items(): + all_cells = [ + cell + for _sheet_id, sheet in sheet_list.items() + if sheet is not None + for cell in sheet or [] + ] - image_excel_id_list = [ - cell for cell in all_cells - if isinstance(cell, str) and key in cell - ] - # print(key, img) - if img is None: - continue - if len(image_excel_id_list) > 0: - image_excel_id = image_excel_id_list[-1] - with archive.open(img.target) as f: - img_byte = io.BytesIO() - im = PILImage.open(f).convert('RGB') - im.save(img_byte, format='JPEG') - image = File(id=uuid.uuid7(), file_name=img.path, meta={'debug': False, 'content': img_byte.getvalue()}) - result['=' + image_excel_id] = image + image_excel_id_list = [ + cell for cell in all_cells + if isinstance(cell, str) and key in cell + ] + # print(key, img) + if img is None: + continue + if len(image_excel_id_list) > 0: + image_excel_id = image_excel_id_list[-1] + f = archive.open(img.target) + img_byte = io.BytesIO() + im = PILImage.open(f).convert('RGB') + im.save(img_byte, format='JPEG') + image = File(id=uuid.uuid7(), file_name=img.path, meta={'debug': False, 'content': img_byte.getvalue()}) + result['=' + image_excel_id] = image + archive.close() return result diff --git a/apps/common/handle/impl/text/pdf_split_handle.py b/apps/common/handle/impl/text/pdf_split_handle.py index 0ffafff61c0..f3172e29884 100644 --- a/apps/common/handle/impl/text/pdf_split_handle.py +++ b/apps/common/handle/impl/text/pdf_split_handle.py @@ -49,9 +49,8 @@ def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_bu # 获取临时文件的路径 temp_file_path = temp_file.name - pdf_document = None + pdf_document = fitz.open(temp_file_path) try: - pdf_document = fitz.open(temp_file_path) if type(limit) is str: limit = int(limit) if type(with_filter) is str: @@ -80,8 +79,7 @@ def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_bu 'content': [] } finally: - if pdf_document is not None: - pdf_document.close() + pdf_document.close() # 处理完后可以删除临时文件 os.remove(temp_file_path) @@ -333,15 +331,9 @@ def get_content(self, file, save_image): # 获取临时文件的路径 temp_file_path = temp_file.name - pdf_document = None + pdf_document = fitz.open(temp_file_path) try: - pdf_document = fitz.open(temp_file_path) return self.handle_pdf_content(file, pdf_document) except BaseException as e: traceback.print_exception(e) return f'{e}' - finally: - if pdf_document is not None: - pdf_document.close() - # 处理完后可以删除临时文件 - os.remove(temp_file_path) diff --git a/apps/common/handle/impl/text/zip_split_handle.py b/apps/common/handle/impl/text/zip_split_handle.py index 498afbea848..d8365d5c285 100644 --- a/apps/common/handle/impl/text/zip_split_handle.py +++ b/apps/common/handle/impl/text/zip_split_handle.py @@ -83,7 +83,7 @@ def get_image_list(result_list: list, zip_files: List[str]): if not zip_files.__contains__(image_path): continue if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'): - image_id = image_path.replace('oss/file/', '').replace('oss/image/', '') + image_id = image_path.replace('oss/file/', '').replace('oss/file/', '') if is_valid_uuid(image_id): image_file_list.append({'source_file': image_path, 'image_id': image_id}) @@ -115,7 +115,7 @@ def get_image_list_by_content(name: str, content: str, zip_files: List[str]): if not zip_files.__contains__(image_path): continue if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'): - image_id = image_path.replace('oss/file/', '').replace('oss/image/', '') + image_id = image_path.replace('oss/file/', '').replace('oss/file/', '') if is_valid_uuid(image_id): image_file_list.append({'source_file': image_path, 'image_id': image_id}) diff --git a/apps/knowledge/serializers/common.py b/apps/knowledge/serializers/common.py index c6f5264b2b1..e33fa545251 100644 --- a/apps/knowledge/serializers/common.py +++ b/apps/knowledge/serializers/common.py @@ -164,11 +164,12 @@ def get_embedding_model_id_by_knowledge_id_list(knowledge_id_list: List): def zip_dir(zip_path, output=None): output = output or os.path.basename(zip_path) + '.zip' - with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as zip: - for root, dirs, files in os.walk(zip_path): - relative_root = '' if root == zip_path else root.replace(zip_path, '') + os.sep - for filename in files: - zip.write(os.path.join(root, filename), relative_root + filename) + zip = zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) + for root, dirs, files in os.walk(zip_path): + relative_root = '' if root == zip_path else root.replace(zip_path, '') + os.sep + for filename in files: + zip.write(os.path.join(root, filename), relative_root + filename) + zip.close() def is_valid_uuid(s): diff --git a/apps/knowledge/serializers/document.py b/apps/knowledge/serializers/document.py index 007ac8489d9..d8466663464 100644 --- a/apps/knowledge/serializers/document.py +++ b/apps/knowledge/serializers/document.py @@ -228,17 +228,19 @@ def export(self, with_valid=True): self.is_valid(raise_exception=True) language = get_language() if self.data.get('type') == 'csv': - with open( + file = open( os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'csv_template_{to_locale(language)}.csv'), - "rb") as file: - content = file.read() + "rb") + content = file.read() + file.close() return HttpResponse(content, status=200, headers={'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename="csv_template.csv"'}) elif self.data.get('type') == 'excel': - with open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', - f'excel_template_{to_locale(language)}.xlsx'), "rb") as file: - content = file.read() + file = open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', + f'excel_template_{to_locale(language)}.xlsx'), "rb") + content = file.read() + file.close() return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel', 'Content-Disposition': 'attachment; filename="excel_template.xlsx"'}) else: @@ -249,18 +251,20 @@ def table_export(self, with_valid=True): self.is_valid(raise_exception=True) language = get_language() if self.data.get('type') == 'csv': - with open( + file = open( os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', f'table_template_{to_locale(language)}.csv'), - "rb") as file: - content = file.read() - return HttpResponse(content, status=200, headers={'Content-Type': 'text/csv', + "rb") + content = file.read() + file.close() + return HttpResponse(content, status=200, headers={'Content-Type': 'text/cxv', 'Content-Disposition': 'attachment; filename="csv_template.csv"'}) elif self.data.get('type') == 'excel': - with open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', - f'table_template_{to_locale(language)}.xlsx'), - "rb") as file: - content = file.read() + file = open(os.path.join(PROJECT_DIR, "apps", "knowledge", 'template', + f'table_template_{to_locale(language)}.xlsx'), + "rb") + content = file.read() + file.close() return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel', 'Content-Disposition': 'attachment; filename="excel_template.xlsx"'}) else: diff --git a/apps/knowledge/serializers/paragraph.py b/apps/knowledge/serializers/paragraph.py index c3a36c2b2ba..a1df1bd8100 100644 --- a/apps/knowledge/serializers/paragraph.py +++ b/apps/knowledge/serializers/paragraph.py @@ -226,6 +226,7 @@ def edit(self, instance: Dict): return self.one(), instance, self.data.get('knowledge_id') def get_problem_list(self): + ProblemParagraphMapping(ProblemParagraphMapping) problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter( paragraph_id=self.data.get("paragraph_id")) if len(problem_paragraph_mapping) > 0: diff --git a/apps/maxkb/urls/web.py b/apps/maxkb/urls/web.py index 1fee100f073..fb043d56a64 100644 --- a/apps/maxkb/urls/web.py +++ b/apps/maxkb/urls/web.py @@ -80,8 +80,9 @@ def pro(): def get_index_html(index_path): - with open(index_path, "r", encoding='utf-8') as file: - content = file.read() + file = open(index_path, "r", encoding='utf-8') + content = file.read() + file.close() return content diff --git a/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py b/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py index e5d32ab1c9d..50ee4abfe65 100644 --- a/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py +++ b/apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py @@ -92,11 +92,7 @@ def _update_aws_credentials(profile_name, access_key_id, secret_access_key): credentials_path = os.path.join(os.path.expanduser("~"), ".aws", "credentials") os.makedirs(os.path.dirname(credentials_path), exist_ok=True) - if os.path.exists(credentials_path): - with open(credentials_path, 'r') as f: - content = f.read() - else: - content = '' + content = open(credentials_path, 'r').read() if os.path.exists(credentials_path) else '' pattern = rf'\n*\[{profile_name}\]\n*(aws_access_key_id = .*)\n*(aws_secret_access_key = .*)\n*' content = re.sub(pattern, '', content, flags=re.DOTALL) diff --git a/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py b/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py index 5bb8a54049f..2ad2107e1a2 100644 --- a/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py +++ b/apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py @@ -224,7 +224,7 @@ def convert_to_down_model_chunk(row_str: str, chunk_index: int): if row.get('status').__contains__("pulling"): progress = 0 status = DownModelChunkStatus.pulling - if 'total' in row and 'completed' in row and row.get('total'): + if 'total' in row and 'completed' in row: progress = (row.get('completed') / row.get('total') * 100) elif 'error' in row: status = DownModelChunkStatus.error diff --git a/apps/trigger/handler/impl/trigger/event_trigger.py b/apps/trigger/handler/impl/trigger/event_trigger.py index cb3df4c26f4..6368cf58b13 100644 --- a/apps/trigger/handler/impl/trigger/event_trigger.py +++ b/apps/trigger/handler/impl/trigger/event_trigger.py @@ -119,7 +119,7 @@ def execute(trigger, request=None, **kwargs): trigger_setting = trigger.get('trigger_setting') if trigger_setting.get('token'): token = request.META.get('HTTP_AUTHORIZATION') - if not token or trigger_setting.get('token') != token.replace('Bearer ', ''): + if trigger_setting.get('token') != token.replace('Bearer ', ''): raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) is_active = trigger.get('is_active') if not is_active: diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index cef803a9cf0..8eafdddc9e9 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1074,10 +1074,11 @@ def send(self): ]), range(6)))) # 获取邮件模板 language = get_language() - with open( - os.path.join(PROJECT_DIR, "apps", "common", 'template', f'email_template_{to_locale(language)}.html'), - "r", encoding='utf-8') as file: - content = file.read() + file = open( + os.path.join(PROJECT_DIR, "apps", "common", 'template', f'email_template_{to_locale(language)}.html'), "r", + encoding='utf-8') + content = file.read() + file.close() code_cache_key = email + ":" + state code_cache_key_lock = code_cache_key + "_lock" # 设置缓存 diff --git a/ui/src/components/execution-detail-card/index.vue b/ui/src/components/execution-detail-card/index.vue index a50fa373586..255ec043837 100644 --- a/ui/src/components/execution-detail-card/index.vue +++ b/ui/src/components/execution-detail-card/index.vue @@ -194,7 +194,7 @@ {{ $t('views.application.form.roleSettings.label') }}
-
{{ data.system || '-' }}
+ {{ data.system || '-' }}
@@ -257,7 +257,7 @@ {{ $t('views.application.form.roleSettings.label') }}
-
{{ data.system || '-' }}
+ {{ data.system || '-' }}
@@ -547,7 +547,7 @@ {{ $t('views.application.form.roleSettings.label') }}
-
{{ data.system || '-' }}
+ {{ data.system || '-' }}
@@ -633,7 +633,7 @@ {{ $t('views.application.form.roleSettings.label') }}
-
{{ data.system || '-' }}
+ {{ data.system || '-' }}
diff --git a/ui/src/locales/lang/en-US/views/application.ts b/ui/src/locales/lang/en-US/views/application.ts index 36a3f599ba9..14c3622917e 100644 --- a/ui/src/locales/lang/en-US/views/application.ts +++ b/ui/src/locales/lang/en-US/views/application.ts @@ -88,11 +88,6 @@ export default { - Please use concise and professional language to answer the user's question. `, }, - prompt_template: { - label: 'Prompt Template', - requiredMessage: 'Please enter Prompt template', - tooltip: 'Please pay attention to the placeholders in the template: {classification_list}、{user_input}、{output_json}', - }, historyRecord: { label: 'Chat History', }, diff --git a/ui/src/locales/lang/en-US/workflow.ts b/ui/src/locales/lang/en-US/workflow.ts index e6e393e49be..e39d262a977 100644 --- a/ui/src/locales/lang/en-US/workflow.ts +++ b/ui/src/locales/lang/en-US/workflow.ts @@ -449,34 +449,9 @@ You are a master of problem optimization, adept at accurately inferring user int classify: { label: 'Intent classify', }, - output_reason: 'Output Reason', input: { label: 'Input', }, - default_prompt_template: `# Role -You are an intention classification expert, good at being able to judge which classification the user's input belongs to. - -## Skills -Skill 1: Clearly determine which of the following intention classifications the user's input belongs to. -Intention classification list: -{classification_list} - -Note: -- Please determine the match between the user's input content and the Intention classification list content, without judging or categorizing the match with the classification ID. -- **When classifying, you must give higher weight to the context and intent continuity shown in the historical conversation. Do not rely solely on the literal meaning of the current input; instead, prioritize the most consistent classification with the previous dialogue flow.** - -## User Input -{user_input} - -## Reply requirements -- The answer must be returned in JSON format. -- Strictly ensure that the output is in a valid JSON format. -- Do not add prefix \`\`\`json or suffix \`\`\` -- The answer needs to include the following fields such as: -{output_json} - -## Limit -- Please do not reply in text.`, }, applicationNode: { label: 'Agent Node', @@ -565,12 +540,9 @@ Note: len_lt: 'Length less than', is_true: 'Is true', is_not_true: 'Is not true', - regex: 'Regex matching', - wildcard: 'Wildcard matching', }, SystemPromptPlaceholder: 'System Prompt, can reference variables in the system, such as', UserPromptPlaceholder: 'User Prompt, can reference variables in the system, such as', - PromptTemplatePlaceholder: 'Prompt Template, can reference variables in the system, such as', initiator: 'Iniiator', abnormalInformation: 'Abnormal Information', } diff --git a/ui/src/locales/lang/zh-CN/views/application.ts b/ui/src/locales/lang/zh-CN/views/application.ts index 7f413a0a689..d1d00f7a8b8 100644 --- a/ui/src/locales/lang/zh-CN/views/application.ts +++ b/ui/src/locales/lang/zh-CN/views/application.ts @@ -81,11 +81,6 @@ export default { 回答要求: - 请使用中文回答用户问题`, }, - prompt_template: { - label: '提示词模板', - requiredMessage: '请输入提示词模板', - tooltip: '请注意模板中的占位符: {classification_list}、{user_input}、{output_json}', - }, historyRecord: { label: '历史聊天记录', }, diff --git a/ui/src/locales/lang/zh-CN/workflow.ts b/ui/src/locales/lang/zh-CN/workflow.ts index d30c435e5c5..3c004d437c2 100644 --- a/ui/src/locales/lang/zh-CN/workflow.ts +++ b/ui/src/locales/lang/zh-CN/workflow.ts @@ -441,34 +441,9 @@ export default { classify: { label: '意图分类', }, - output_reason: '输出理由', input: { label: '输入', }, - default_prompt_template: `# 角色 -你是一位意图分类专家,擅长判断用户输入属于哪个分类。 - -## 技能 -技能1:明确判断用户输入属于以下哪种意图分类。 -意图分类列表: -{classification_list} - -注: -- 请判断用户输入内容与意图分类列表内容之间的匹配度,注意不要以分类ID作为评判或归类的依据。 -- **在分类时,必须更加重视历史对话中表现的上下文和意图连贯性。不要仅依赖当前输入的字面意思;相反,应优先考虑与先前对话流程最匹配的分类。** - -## 用户输入 -{user_input} - -## 回复要求 -- 回复内容必须以JSON格式返回。 -- 严格确保输出为有效的JSON格式。 -- 不要添加前缀 \`\`\`json 或 后缀 \`\`\` -- 回复内容需要包含以下字段: -{output_json} - -## 限制 -- 请勿以文本形式回复。`, }, applicationNode: { label: '智能体节点', @@ -556,12 +531,9 @@ export default { len_lt: '长度小于', is_true: '为真', is_not_true: '不为真', - regex: '正则匹配', - wildcard: '通配符匹配', }, SystemPromptPlaceholder: '系统提示词,可以引用系统中的变量:如', UserPromptPlaceholder: '用户提示词,可以引用系统中的变量:如', - PromptTemplatePlaceholder: '提示词模板,可以引用系统中的变量:如', initiator: '发起人', abnormalInformation: '异常信息' diff --git a/ui/src/locales/lang/zh-Hant/views/application.ts b/ui/src/locales/lang/zh-Hant/views/application.ts index ecd6210e336..dce197edf1c 100644 --- a/ui/src/locales/lang/zh-Hant/views/application.ts +++ b/ui/src/locales/lang/zh-Hant/views/application.ts @@ -81,11 +81,6 @@ export default { 回答要求: - 請使用中文回答用戶問題`, }, - prompt_template: { - label: '提示詞模板', - requiredMessage: '請輸入提示詞模板', - tooltip: '請注意模板中的佔位符: {classification_list}、{user_input}、{output_json}', - }, historyRecord: { label: '歷史對話紀錄', }, diff --git a/ui/src/locales/lang/zh-Hant/workflow.ts b/ui/src/locales/lang/zh-Hant/workflow.ts index ccfd0a6ab75..a7fd05c9f1b 100644 --- a/ui/src/locales/lang/zh-Hant/workflow.ts +++ b/ui/src/locales/lang/zh-Hant/workflow.ts @@ -441,34 +441,9 @@ export default { classify: { label: '意圖分類', }, - output_reason: '輸出理由', input: { label: '輸入', }, - default_prompt_template: `# 角色 -你是一位意圖分類專家,擅長判斷用戶輸入屬於哪個分類。 - -## 技能 -技能1:明確判斷用戶輸入屬於以下哪種意圖分類。 -意圖分類列表: -{classification_list} - -注: -- 請判斷用戶輸入內容與意圖分類列表內容之間的匹配度,注意不要以分類ID作爲評判或歸類的依據。 -- **在分類時,必須更加重視歷史對話中表現的上下文和意圖連貫性。不要僅依賴當前輸入的字面意思;相反,應優先考慮與先前對話流程最匹配的分類。** - -## 用戶輸入 -{user_input} - -## 回覆要求 -- 回覆內容必須以JSON格式返回。 -- 嚴格確保輸出爲有效的JSON格式。 -- 不要添加前綴 \`\`\`json 或 後綴 \`\`\` -- 回覆內容需要包含以下字段: -{output_json} - -## 限制 -- 請勿以文本形式回覆。`, }, applicationNode: { label: '智能體節點', @@ -550,12 +525,9 @@ export default { len_lt: '長度小於', is_true: '為真', is_not_true: '不為真', - regex: '正則匹配', - wildcard: '通配符匹配', }, SystemPromptPlaceholder: '系統提示詞,可以引用系統中的變量:如', UserPromptPlaceholder: '用戶提示詞,可以引用系統中的變量:如', - PromptTemplatePlaceholder: '提示詞模板,可以引用系統中的變量:如', initiator: '發起人', abnormalInformation: '異常信息', } diff --git a/ui/src/styles/app.scss b/ui/src/styles/app.scss index f36ed527084..f203477fef0 100644 --- a/ui/src/styles/app.scss +++ b/ui/src/styles/app.scss @@ -31,11 +31,6 @@ body { color: var(--el-text-color-primary); } -.card-never pre { - font-family: 'PingFang SC', AlibabaPuHuiTi !important; - white-space: break-spaces; -} - #app { height: 100%; } diff --git a/ui/src/views/application-workflow/index.vue b/ui/src/views/application-workflow/index.vue index 3a130af64f3..d178f32f71a 100644 --- a/ui/src/views/application-workflow/index.vue +++ b/ui/src/views/application-workflow/index.vue @@ -229,9 +229,6 @@ const shareUrl = computed( function back() { if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) { - console.debug("JSON.stringify(cloneWorkFlow.value):", JSON.stringify(cloneWorkFlow.value)) - console.debug("JSON.stringify(getGraphData()):", JSON.stringify(getGraphData())) - MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), { confirmButtonText: t('workflow.setting.exitSave'), cancelButtonText: t('workflow.setting.exit'), @@ -702,7 +699,7 @@ onBeforeUnmount(() => { bottom: 16px; right: 16px; overflow: hidden; - width: 550px; + width: 460px; height: 680px; .workflow-debug-header { diff --git a/ui/src/views/knowledge-workflow/index.vue b/ui/src/views/knowledge-workflow/index.vue index a2413cc8314..6a1073e5c04 100644 --- a/ui/src/views/knowledge-workflow/index.vue +++ b/ui/src/views/knowledge-workflow/index.vue @@ -260,9 +260,6 @@ const isPublish = computed(() => detail.value?.is_publish) function back() { if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) { - console.debug("JSON.stringify(cloneWorkFlow.value):", JSON.stringify(cloneWorkFlow.value)) - console.debug("JSON.stringify(getGraphData()):", JSON.stringify(getGraphData())) - MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), { confirmButtonText: t('workflow.setting.exitSave'), cancelButtonText: t('workflow.setting.exit'), @@ -738,7 +735,7 @@ onBeforeUnmount(() => { bottom: 16px; right: 16px; overflow: hidden; - width: 550px; + width: 460px; height: 680px; .workflow-debug-header { diff --git a/ui/src/views/tool-workflow/index.vue b/ui/src/views/tool-workflow/index.vue index 64b41b65ace..0d1496a661f 100644 --- a/ui/src/views/tool-workflow/index.vue +++ b/ui/src/views/tool-workflow/index.vue @@ -227,9 +227,6 @@ const isPublish = computed(() => detail.value?.is_publish) function back() { if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) { - console.debug("JSON.stringify(cloneWorkFlow.value):", JSON.stringify(cloneWorkFlow.value)) - console.debug("JSON.stringify(getGraphData()):", JSON.stringify(getGraphData())) - MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), { confirmButtonText: t('workflow.setting.exitSave'), cancelButtonText: t('workflow.setting.exit'), diff --git a/ui/src/workflow/common/NodeContainer.vue b/ui/src/workflow/common/NodeContainer.vue index 4fb3f9cdb4f..86cf69a6df6 100644 --- a/ui/src/workflow/common/NodeContainer.vue +++ b/ui/src/workflow/common/NodeContainer.vue @@ -253,7 +253,7 @@ const condition = computed({ if (props.nodeModel.properties.condition) { return props.nodeModel.properties.condition } - set(props.nodeModel.properties, 'condition', 'OR') + set(props.nodeModel.properties, 'condition', 'AND') return true }, }) diff --git a/ui/src/workflow/common/data.ts b/ui/src/workflow/common/data.ts index a4c2fa1c2ea..c6120fd60df 100644 --- a/ui/src/workflow/common/data.ts +++ b/ui/src/workflow/common/data.ts @@ -1135,8 +1135,6 @@ export const compareList = [ { value: 'is_not_true', label: t('workflow.compare.is_not_true') }, { value: 'start_with', label: 'startWith' }, { value: 'end_with', label: 'endWith' }, - { value: 'regex', label: t('workflow.compare.regex') }, - { value: 'wildcard', label: t('workflow.compare.wildcard') }, ] export const nodeDict: any = { [WorkflowType.AiChat]: aiChatNode, diff --git a/ui/src/workflow/nodes/intent-classify-node/index.ts b/ui/src/workflow/nodes/intent-classify-node/index.ts index 406a66f5278..c39167e9294 100644 --- a/ui/src/workflow/nodes/intent-classify-node/index.ts +++ b/ui/src/workflow/nodes/intent-classify-node/index.ts @@ -48,11 +48,11 @@ class IntentModel extends AppNodeModel { if (branch_condition_list) { - const FORM_ITEMS_HEIGHT = 542 // 上方表单占用高度 - + const FORM_ITEMS_HEIGHT = 397 // 上方表单占用高度 + for (let index = 0; index < branch_condition_list.length; index++) { const element = branch_condition_list[index] - + anchors.push({ x: x + width / 2 - 10, y: showNode diff --git a/ui/src/workflow/nodes/intent-classify-node/index.vue b/ui/src/workflow/nodes/intent-classify-node/index.vue index 2e52bbc8716..7ed132c959c 100644 --- a/ui/src/workflow/nodes/intent-classify-node/index.vue +++ b/ui/src/workflow/nodes/intent-classify-node/index.vue @@ -76,42 +76,6 @@ v-model="form_data.model_id_reference" /> - - - -
- -
-
- {{ $t('workflow.nodes.intentNode.output_reason') }} -
-
- -
-
-
@@ -353,17 +304,10 @@ const model_change = (model_id?: string) => { } } -const default_prompt_template = t('workflow.nodes.intentNode.default_prompt_template', { - classification_list: '{classification_list}', - user_input: '{user_input}', - output_json: '{output_json}', -}) - const form = { model_id: '', model_id_type: 'custom', model_id_reference: [], - prompt_template: default_prompt_template, branch: [ { id: randomId(), @@ -378,17 +322,12 @@ const form = { ], dialogue_number: 1, content_list: [], - output_reason: true, } function refreshParam(data: any) { set(props.nodeModel.properties.node_data, 'model_params_setting', data) } -function submitTemplateDialog(val: string) { - set(props.nodeModel.properties.node_data, 'prompt_template', val) -} - const openAIParamSettingDialog = (modelId: string) => { if (modelId) { AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting) @@ -403,12 +342,6 @@ const form_data = computed({ if (!props.nodeModel.properties.node_data.model_id_reference) { set(props.nodeModel.properties.node_data, 'model_id_reference', []) } - if (!props.nodeModel.properties.node_data.prompt_template) { - set(props.nodeModel.properties.node_data, 'prompt_template', default_prompt_template) - } - if (props.nodeModel.properties.node_data.output_reason == null) { - set(props.nodeModel.properties.node_data, 'output_reason', true) - } return props.nodeModel.properties.node_data } else { set(props.nodeModel.properties, 'node_data', form)