diff --git a/easy-agents-flow/pom.xml b/easy-agents-flow/pom.xml index 59bd45a..ac8ff6c 100644 --- a/easy-agents-flow/pom.xml +++ b/easy-agents-flow/pom.xml @@ -62,6 +62,12 @@ 77.1 + + junit + junit + test + + diff --git a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java index ea8b93e..be2fe97 100644 --- a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java +++ b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/Chain.java @@ -220,6 +220,7 @@ public class Chain { if (variables != null && !variables.isEmpty()) { state.getMemory().putAll(variables); + applyStartParameterAliases(state.getMemory(), variables); fields.add(ChainStateField.MEMORY); } @@ -245,6 +246,48 @@ public class Chain { } } + /** + * 为开始节点输入参数补齐 `nodeId.paramName` 与 `paramName` 双向别名。 + * 这样既兼容运行表单仅提交裸参数名,也兼容设计器内部统一保存完整引用路径。 + * + * @param memory 流程内存 + * @param variables 本次注入的变量 + */ + public void applyStartParameterAliases(Map memory, Map variables) { + if (memory == null || variables == null || variables.isEmpty()) { + return; + } + List startNodes = definition == null ? Collections.emptyList() : definition.getStartNodes(); + if (startNodes == null || startNodes.isEmpty()) { + return; + } + for (Node startNode : startNodes) { + if (startNode == null || StringUtil.noText(startNode.getId())) { + continue; + } + List parameters = startNode.getParameters(); + if (parameters == null || parameters.isEmpty()) { + continue; + } + for (Parameter parameter : parameters) { + if (parameter == null || parameter.getRefType() != RefType.INPUT || StringUtil.noText(parameter.getName())) { + continue; + } + String parameterName = parameter.getName().trim(); + String scopedName = startNode.getId() + "." + parameterName; + Object plainValue = variables.get(parameterName); + Object scopedValue = variables.get(scopedName); + + if (plainValue != null && !memory.containsKey(scopedName)) { + memory.put(scopedName, plainValue); + } + if (scopedValue != null && !memory.containsKey(parameterName)) { + memory.put(parameterName, scopedValue); + } + } + } + } + public void executeNode(Node node, Trigger trigger) { try { EXECUTION_THREAD_LOCAL.set(this); @@ -745,4 +788,4 @@ public class Chain { public void setStateInstanceId(String stateInstanceId) { this.stateInstanceId = stateInstanceId; } -} \ No newline at end of file +} diff --git a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java index 938671e..3f6bdb2 100644 --- a/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java +++ b/easy-agents-flow/src/main/java/com/easyagents/flow/core/chain/runtime/ChainExecutor.java @@ -156,6 +156,7 @@ public class ChainExecutor { if (variables != null && !variables.isEmpty()) { temp.updateStateSafely(s -> { s.getMemory().putAll(variables); + temp.applyStartParameterAliases(s.getMemory(), variables); return EnumSet.of(ChainStateField.MEMORY); }); } diff --git a/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java b/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java index 8d33f56..eebc8b1 100644 --- a/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java +++ b/easy-agents-flow/src/main/java/com/easyagents/flow/core/util/TextTemplate.java @@ -226,6 +226,14 @@ public class TextTemplate { */ private Object getValueByJsonPath(Map root, String path, boolean escapeForJsonOutput) { try { + Object directValue = MapUtil.getByPath(root, path); + if (directValue != null) { + if (escapeForJsonOutput && directValue instanceof String) { + return escapeJsonString((String) directValue); + } + return directValue; + } + String fullPath = path.startsWith("$") ? path : "$." + path; JSONPath compiled = MapUtil.computeIfAbsent(JSONPATH_CACHE, fullPath, JSONPath::compile); Object value = compiled.eval(root); diff --git a/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/ChainStartParameterAliasTest.java b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/ChainStartParameterAliasTest.java new file mode 100644 index 0000000..ad4bfdf --- /dev/null +++ b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/ChainStartParameterAliasTest.java @@ -0,0 +1,60 @@ +package com.easyagents.flow.core.test; + +import com.easyagents.flow.core.chain.Chain; +import com.easyagents.flow.core.chain.ChainDefinition; +import com.easyagents.flow.core.chain.Parameter; +import com.easyagents.flow.core.chain.RefType; +import com.easyagents.flow.core.node.StartNode; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * 验证开始节点输入参数在运行时会同时写入裸参数名与 `nodeId.paramName` 别名。 + */ +public class ChainStartParameterAliasTest { + + @Test + public void shouldCreateScopedAliasFromPlainStartInput() { + Chain chain = createChain(); + Map memory = new HashMap<>(); + Map variables = new HashMap<>(); + variables.put("user_input", "hello"); + + chain.applyStartParameterAliases(memory, variables); + + Assert.assertEquals("hello", memory.get("start_1.user_input")); + Assert.assertNull(memory.get("user_input")); + } + + @Test + public void shouldCreatePlainAliasFromScopedStartInput() { + Chain chain = createChain(); + Map memory = new HashMap<>(); + Map variables = new HashMap<>(); + variables.put("start_1.user_input", "hello"); + + chain.applyStartParameterAliases(memory, variables); + + Assert.assertEquals("hello", memory.get("user_input")); + Assert.assertNull(memory.get("start_1.user_input")); + } + + private Chain createChain() { + ChainDefinition definition = new ChainDefinition(); + StartNode startNode = new StartNode(); + startNode.setId("start_1"); + + Parameter parameter = new Parameter(); + parameter.setName("user_input"); + parameter.setRefType(RefType.INPUT); + startNode.setParameters(java.util.Collections.singletonList(parameter)); + + definition.addNode(startNode); + definition.setEdges(Collections.emptyList()); + return new Chain(definition, "state_1"); + } +} diff --git a/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/TextTemplatePathTest.java b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/TextTemplatePathTest.java new file mode 100644 index 0000000..e47427d --- /dev/null +++ b/easy-agents-flow/src/test/java/com/easyagents/flow/core/test/TextTemplatePathTest.java @@ -0,0 +1,24 @@ +package com.easyagents.flow.core.test; + +import com.easyagents.flow.core.util.TextTemplate; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * 验证文本模板能解析带点号的扁平 key,例如 `nodeId.paramName`。 + */ +public class TextTemplatePathTest { + + @Test + public void shouldResolveFlatScopedKeyBeforeJsonPathFallback() { + Map parameters = new HashMap<>(); + parameters.put("node_1.user_input", "你好啊"); + + String result = TextTemplate.of("{{node_1.user_input}}").formatToString(parameters); + + Assert.assertEquals("你好啊", result); + } +}