Skip to content

Commit c3f85c2

Browse files
authored
feat(studio): support MiddleOutput, Script and Iteration for Studio DSL (#2379)
* feat: add MiddleOutputNode for Studio DSL * refactor: refactor renderEdges * refactor: remove unused BranchNode.java * feat: refactor IterationNodeDataConverter to support Studio DSL primarily * fix: fix dify iteration generate bugs * fix: fix dify iteration bugs * feat: enhance CodeExecutorNodeAction and fix bugs * feat: enhance CodeNodeSection for Dify DSL * feat: support CodeNodeSection for Studio DSL * feat: support IterationNode for Studio DSL * refactor: add NodeSection#getImports Method
1 parent 5a88ea3 commit c3f85c2

File tree

52 files changed

+1812
-939
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1812
-939
lines changed

spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/node/BranchNode.java

Lines changed: 0 additions & 72 deletions
This file was deleted.

spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/node/code/CodeExecutorNodeAction.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.HashMap;
20-
import java.util.LinkedHashMap;
2120
import java.util.List;
2221
import java.util.Map;
22+
import java.util.Optional;
23+
import java.util.stream.Collectors;
2324

2425
import com.alibaba.cloud.ai.graph.OverAllState;
2526
import com.alibaba.cloud.ai.graph.action.NodeAction;
2627
import com.alibaba.cloud.ai.graph.node.code.entity.CodeBlock;
2728
import com.alibaba.cloud.ai.graph.node.code.entity.CodeExecutionConfig;
2829
import com.alibaba.cloud.ai.graph.node.code.entity.CodeExecutionResult;
2930
import com.alibaba.cloud.ai.graph.node.code.entity.CodeLanguage;
31+
import com.alibaba.cloud.ai.graph.node.code.entity.CodeParam;
32+
import com.alibaba.cloud.ai.graph.node.code.entity.CodeStyle;
3033
import com.alibaba.cloud.ai.graph.node.code.entity.RunnerAndPreload;
3134
import com.alibaba.cloud.ai.graph.node.code.javascript.NodeJsTemplateTransformer;
3235
import com.alibaba.cloud.ai.graph.node.code.python3.Python3TemplateTransformer;
@@ -47,10 +50,12 @@ public class CodeExecutorNodeAction implements NodeAction {
4750

4851
private final CodeExecutionConfig codeExecutionConfig;
4952

50-
private Map<String, Object> params;
53+
private final List<CodeParam> params;
5154

5255
private final String outputKey;
5356

57+
private final CodeStyle style;
58+
5459
private static final Map<CodeLanguage, TemplateTransformer> CODE_TEMPLATE_TRANSFORMERS = Map.of(
5560
CodeLanguage.PYTHON3, new Python3TemplateTransformer(), CodeLanguage.PYTHON,
5661
new Python3TemplateTransformer(), CodeLanguage.JAVASCRIPT, new NodeJsTemplateTransformer(),
@@ -61,24 +66,25 @@ CodeLanguage.PYTHON3, new Python3TemplateTransformer(), CodeLanguage.PYTHON,
6166
CodeLanguage.PYTHON3.getValue(), CodeLanguage.PYTHON, CodeLanguage.PYTHON.getValue(), CodeLanguage.JAVA,
6267
CodeLanguage.JAVA.getValue());
6368

64-
public CodeExecutorNodeAction(CodeExecutor codeExecutor, String codeLanguage, String code,
65-
CodeExecutionConfig config, Map<String, Object> params, String outputKey) {
69+
public CodeExecutorNodeAction(CodeExecutor codeExecutor, String codeLanguage, String code, CodeStyle style,
70+
CodeExecutionConfig config, List<CodeParam> params, String outputKey) {
6671
this.codeExecutor = codeExecutor;
6772
this.codeLanguage = codeLanguage;
73+
this.style = style;
6874
this.code = code;
6975
this.codeExecutionConfig = config;
7076
this.params = params;
7177
this.outputKey = outputKey;
7278
}
7379

74-
private Map<String, Object> executeWorkflowCodeTemplate(CodeLanguage language, String code, List<Object> inputs)
75-
throws Exception {
80+
private Map<String, Object> executeWorkflowCodeTemplate(CodeLanguage language, String code,
81+
Map<String, Object> inputs) throws Exception {
7682
TemplateTransformer templateTransformer = CODE_TEMPLATE_TRANSFORMERS.get(language);
7783
if (templateTransformer == null) {
7884
throw new RuntimeException("Unsupported language: " + language);
7985
}
8086

81-
RunnerAndPreload runnerAndPreload = templateTransformer.transformCaller(code, inputs);
87+
RunnerAndPreload runnerAndPreload = templateTransformer.transformCaller(code, inputs, style);
8288
String response = executeCode(language, runnerAndPreload.preloadScript(), runnerAndPreload.runnerScript());
8389

8490
return templateTransformer.transformResponse(response);
@@ -100,12 +106,12 @@ private String executeCode(CodeLanguage language, String preloadScript, String c
100106

101107
@Override
102108
public Map<String, Object> apply(OverAllState state) throws Exception {
103-
List<Object> inputs = new ArrayList<>(10);
104-
if (params != null && !params.isEmpty()) {
105-
for (String key : params.keySet()) {
106-
inputs.add(state.data().get((String) params.get(key)));
107-
}
108-
}
109+
Map<String, Object> inputs = Optional.ofNullable(params)
110+
.orElse(List.of())
111+
.stream()
112+
.collect(Collectors.toUnmodifiableMap(CodeParam::argName, param -> Optional.ofNullable(param.value())
113+
.or(() -> StringUtils.hasText(param.stateKey()) ? state.value(param.stateKey()) : Optional.empty())
114+
.orElseThrow(() -> new IllegalStateException("param has no value and legal key!"))));
109115
Map<String, Object> resultObjectMap = executeWorkflowCodeTemplate(CodeLanguage.fromValue(codeLanguage), code,
110116
inputs);
111117
Map<String, Object> updatedState = new HashMap<>();
@@ -127,13 +133,16 @@ public static class Builder {
127133

128134
private String code;
129135

136+
private CodeStyle style;
137+
130138
private CodeExecutionConfig config;
131139

132-
private Map<String, Object> params;
140+
private List<CodeParam> params;
133141

134142
private String outputKey;
135143

136144
public Builder() {
145+
style = CodeStyle.EXPLICIT_PARAMETERS;
137146
}
138147

139148
public Builder codeExecutor(CodeExecutor codeExecutor) {
@@ -151,13 +160,18 @@ public Builder code(String code) {
151160
return this;
152161
}
153162

163+
public Builder codeStyle(CodeStyle style) {
164+
this.style = style;
165+
return this;
166+
}
167+
154168
public Builder config(CodeExecutionConfig config) {
155169
this.config = config;
156170
return this;
157171
}
158172

159-
public Builder params(Map<String, String> params) {
160-
this.params = new LinkedHashMap<>(params);
173+
public Builder params(List<CodeParam> params) {
174+
this.params = List.copyOf(params);
161175
return this;
162176
}
163177

@@ -167,7 +181,7 @@ public Builder outputKey(String outputKey) {
167181
}
168182

169183
public CodeExecutorNodeAction build() {
170-
return new CodeExecutorNodeAction(codeExecutor, codeLanguage, code, config, params, outputKey);
184+
return new CodeExecutorNodeAction(codeExecutor, codeLanguage, code, style, config, params, outputKey);
171185
}
172186

173187
}

spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/node/code/TemplateTransformer.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616

1717
package com.alibaba.cloud.ai.graph.node.code;
1818

19+
import com.alibaba.cloud.ai.graph.node.code.entity.CodeStyle;
1920
import com.alibaba.cloud.ai.graph.node.code.entity.RunnerAndPreload;
2021
import com.fasterxml.jackson.databind.ObjectMapper;
2122

2223
import java.nio.charset.StandardCharsets;
2324
import java.util.Base64;
24-
import java.util.List;
2525
import java.util.Map;
2626
import java.util.regex.Matcher;
2727
import java.util.regex.Pattern;
@@ -38,8 +38,8 @@ public abstract class TemplateTransformer {
3838

3939
protected static final String RESULT_TAG = "<<RESULT>>";
4040

41-
public RunnerAndPreload transformCaller(String code, List<Object> inputs) throws Exception {
42-
String runnerScript = assembleRunnerScript(code, inputs);
41+
public RunnerAndPreload transformCaller(String code, Map<String, Object> inputs, CodeStyle style) throws Exception {
42+
String runnerScript = assembleRunnerScript(code, inputs, style);
4343
String preloadScript = getPreloadScript();
4444

4545
return new RunnerAndPreload(runnerScript, preloadScript);
@@ -52,7 +52,7 @@ public Map<String, Object> transformResponse(String response) throws Exception {
5252
mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class));
5353
}
5454

55-
public abstract String getRunnerScript();
55+
public abstract String getRunnerScript(CodeStyle style);
5656

5757
private String extractResultStrFromResponse(String response) {
5858
Pattern pattern = Pattern.compile(RESULT_TAG + "(.*?)" + RESULT_TAG, Pattern.DOTALL);
@@ -66,14 +66,14 @@ private String extractResultStrFromResponse(String response) {
6666
}
6767
}
6868

69-
private String serializeInputs(List<Object> inputs) throws Exception {
69+
private String serializeInputs(Map<String, Object> inputs) throws Exception {
7070
ObjectMapper mapper = new ObjectMapper();
7171
String inputsJsonStr = mapper.writeValueAsString(inputs);
7272
return Base64.getEncoder().encodeToString(inputsJsonStr.getBytes(StandardCharsets.UTF_8));
7373
}
7474

75-
private String assembleRunnerScript(String code, List<Object> inputs) throws Exception {
76-
String script = getRunnerScript();
75+
private String assembleRunnerScript(String code, Map<String, Object> inputs, CodeStyle style) throws Exception {
76+
String script = getRunnerScript(style);
7777
script = script.replace(CODE_PLACEHOLDER, code);
7878
script = script.replace(INPUTS_PLACEHOLDER, serializeInputs(inputs));
7979
return script;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.alibaba.cloud.ai.graph.node.code.entity;
18+
19+
/**
20+
* @author vlsmb
21+
* @since 2025/9/11
22+
* @param argName 参数在代码中对应的名称
23+
* @param value 参数值,如果为null,则从OverallState中获取
24+
* @param stateKey 参数在OverallState中的key,如果value不为null,则忽略stateKey
25+
*/
26+
public record CodeParam(String argName, Object value, String stateKey) {
27+
public CodeParam(String argName, String stateKey) {
28+
this(argName, null, stateKey);
29+
}
30+
31+
public static CodeParam withValue(String argName, Object value) {
32+
return new CodeParam(argName, value, null);
33+
}
34+
35+
public static CodeParam withKey(String argName, String stateKey) {
36+
return new CodeParam(argName, null, stateKey);
37+
}
38+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.alibaba.cloud.ai.graph.node.code.entity;
18+
19+
/**
20+
* @author vlsmb
21+
* @since 2025/9/11
22+
*/
23+
public enum CodeStyle {
24+
25+
/**
26+
* 参数直接作为函数形参的风格 示例: def main(x: int, y: int) -> dict:
27+
*/
28+
EXPLICIT_PARAMETERS,
29+
30+
/**
31+
* 参数通过全局字典访问的风格 示例: def main(): x = params['x']
32+
*/
33+
GLOBAL_DICTIONARY
34+
35+
}

0 commit comments

Comments
 (0)