diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java index 700f891c..96489d23 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java @@ -832,11 +832,36 @@ public class WorkflowCheckService { continue; } if (workflowDatacenterContentService.isLlmNode(node.type)) { + checkLlmUserPrompt(node, issues, issueKeys); checkLlmQueryContext(node, parsed, issues, issueKeys); } } } + /** + * 校验大模型节点的用户提示词,避免空提示词进入运行时解析。 + * + * @param node 大模型节点 + * @param issues 问题列表 + * @param issueKeys 问题去重键 + */ + private void checkLlmUserPrompt( + NodeView node, + List issues, + Set issueKeys) { + String userPrompt = node.data == null ? null : node.data.getString("userPrompt"); + if (!StringUtils.hasText(userPrompt)) { + addIssue( + issues, + issueKeys, + "LLM_USER_PROMPT_EMPTY", + "大模型节点的用户提示词不能为空", + node.id, + null, + node.name); + } + } + private void checkMakeFileNode(NodeView node, List issues, Set issueKeys) { diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java index 40eefd45..2be7f0ff 100644 --- a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java @@ -436,6 +436,41 @@ public class WorkflowCheckServiceTest { assertHasCode(result, "SEARCH_DATASET_INVALID"); } + /** + * 验证保存阶段拒绝空白的大模型用户提示词。 + */ + @Test + public void testSaveShouldBlockBlankLlmUserPrompt() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject llmData = data("大模型"); + llmData.put("userPrompt", " "); + String content = workflowJson( + array(node("llm-1", "llmNode", null, llmData)), + new JSONArray() + ); + + WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null); + Assert.assertFalse(result.isPassed()); + assertHasCode(result, "LLM_USER_PROMPT_EMPTY"); + } + + /** + * 验证保存阶段接受有效的大模型用户提示词。 + */ + @Test + public void testSaveShouldPassNonBlankLlmUserPrompt() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject llmData = data("大模型"); + llmData.put("userPrompt", "{{start-1.user_input}}"); + String content = workflowJson( + array(node("llm-1", "llmNode", null, llmData)), + new JSONArray() + ); + + WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null); + Assert.assertTrue(result.isPassed()); + } + @Test public void testPreExecuteShouldBlockMissingStartOrEnd() throws Exception { WorkflowCheckService service = newService(new HashMap<>()); @@ -453,6 +488,34 @@ public class WorkflowCheckServiceTest { assertHasCode(result, "END_NODE_MISSING"); } + /** + * 验证执行前校验拒绝空的大模型用户提示词。 + */ + @Test + public void testPreExecuteShouldBlockEmptyLlmUserPrompt() throws Exception { + WorkflowCheckService service = newService(new HashMap<>()); + JSONObject llmData = data("大模型"); + llmData.put("userPrompt", ""); + String content = workflowJson( + array( + node("s1", "startNode", null, data("开始")), + node("llm-1", "llmNode", null, llmData), + node("e1", "endNode", null, data("结束")) + ), + array( + edge("edge-1", "s1", "llm-1"), + edge("edge-2", "llm-1", "e1") + ) + ); + + WorkflowCheckResult result = service.checkContent( + content, + WorkflowCheckStage.PRE_EXECUTE, + BigInteger.ONE); + Assert.assertFalse(result.isPassed()); + assertHasCode(result, "LLM_USER_PROMPT_EMPTY"); + } + @Test public void testPreExecuteShouldPassForSourceOnlySearchDatasetNode() throws Exception { WorkflowCheckService service = newService(new HashMap<>()); diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte index 0da514e5..68b643ba 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte @@ -52,9 +52,10 @@ return param.formType ? [param.formType] : []; }); let parameterNameDraft = $state(null); + let systemLabelDraft = $state(null); let displayParamName = $derived.by(() => { if (isSystemStartParam) { - return '用户问题'; + return systemLabelDraft ?? trimString(param.formLabel); } return parameterNameDraft ?? param.name; }); @@ -76,6 +77,8 @@ const applyStartFieldPatch = (fieldKey: string, patch: Record) => { const currentFieldId = trimString(param.id); + const shouldSyncReferences = Object.prototype.hasOwnProperty.call(patch, 'key') + || Object.prototype.hasOwnProperty.call(patch, 'label'); store.updateNodes((nodes) => { const edges = store.getEdges(); let nextFieldKey = fieldKey; @@ -98,6 +101,9 @@ } }; }); + if (!shouldSyncReferences) { + return nextNodes; + } return renameStartFieldReferencesInNodes( nextNodes, edges, @@ -172,11 +178,11 @@ const updateName = (event: Event) => { const input = event.target as HTMLInputElement; const newValue = input.value; + parameterNameDraft = newValue; if (isStartNodeInputParam) { const normalizedValue = trimString(newValue); if (!normalizedValue) { parameterNameError = '参数名不能为空'; - parameterNameDraft = newValue; return; } if (!isStartFormFieldKeyAvailable( @@ -185,13 +191,66 @@ normalizedValue )) { parameterNameError = '参数名已存在'; - parameterNameDraft = newValue; return; } } + parameterNameError = ''; + }; + + const updateVisibleName = (event: Event) => { + if (isSystemStartParam) { + systemLabelDraft = (event.target as HTMLInputElement).value; + return; + } + updateName(event); + }; + + const commitName = () => { + if (parameterNameDraft === null) { + return; + } + const draftValue = parameterNameDraft; + if (isStartNodeInputParam) { + const normalizedValue = trimString(draftValue); + if (!normalizedValue) { + parameterNameError = '参数名不能为空'; + return; + } + if (!isStartFormFieldKeyAvailable( + node?.current?.data as Record, + param.name || '', + normalizedValue + )) { + parameterNameError = '参数名已存在'; + return; + } + parameterNameDraft = null; + parameterNameError = ''; + if (normalizedValue !== trimString(param.name)) { + updateParameter('name', normalizedValue); + } + return; + } parameterNameDraft = null; parameterNameError = ''; - updateParameter('name', newValue); + if (draftValue !== (param.name || '')) { + updateParameter('name', draftValue); + } + }; + + const commitVisibleName = () => { + if (!isSystemStartParam) { + commitName(); + return; + } + if (systemLabelDraft === null) { + return; + } + const nextLabel = trimString(systemLabelDraft); + systemLabelDraft = null; + if (nextLabel !== trimString(param.formLabel)) { + updateParameter('formLabel', nextLabel); + } }; const updateRequired = (event: Event) => { @@ -236,11 +295,13 @@
- + oninput={updateVisibleName} + onchange={commitVisibleName} /> {#if parameterNameError}