Skip to content

Commit f10542f

Browse files
VLSMBco63oc
authored andcommitted
feat(studio): support mcp, api node for studio dsl and fix some bugs (alibaba#2444)
* feat: support MCP Node for Studio DSL * feat: support API(HTTP) Node for Studio DSL * fix some bugs
1 parent 8f891cf commit f10542f

File tree

20 files changed

+348
-315
lines changed

20 files changed

+348
-315
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ public ParameterParsingNode(ChatClient chatClient, String inputText, String inpu
138138
}
139139

140140
private String renderTemplate(OverAllState state, String template) {
141+
if (!StringUtils.hasText(template)) {
142+
return template;
143+
}
141144
Map<String, Object> params = Stream.of(template)
142145
.map(VAR_TEMPLATE_PATTERN::matcher)
143146
.map(Matcher::results)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ public QuestionClassifierNode(ChatClient chatClient, String inputTextKey, Map<St
104104
}
105105

106106
private String renderTemplate(OverAllState state, String template) {
107+
if (!StringUtils.hasText(template)) {
108+
return template;
109+
}
107110
Map<String, Object> params = Stream.of(template)
108111
.map(VAR_TEMPLATE_PATTERN::matcher)
109112
.map(Matcher::results)

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/frontend/packages/main/src/pages/App/Workflow/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ export const FlowEditor = memo((props: IProps) => {
292292
try {
293293
// 准备请求参数
294294
const params = {
295-
dependencies: 'spring-ai-alibaba-graph,web,spring-ai-alibaba-starter-dashscope',
295+
dependencies: 'spring-ai-alibaba-graph,web,spring-ai-alibaba-starter-dashscope,spring-ai-starter-mcp-client',
296296
appMode: 'workflow',
297297
dslDialectType: 'studio',
298298
type: 'maven-project',

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/spring-ai-alibaba-studio-server-admin/src/main/java/com/alibaba/cloud/ai/studio/admin/generator/model/workflow/NodeType.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,16 @@ public enum NodeType {
4747

4848
QUESTION_CLASSIFIER("question-classifier", "question-classifier", "Classifier"),
4949

50-
HTTP("http", "http-request", "UNSUPPORTED"),
50+
HTTP("http", "http-request", "API"),
5151

5252
LIST_OPERATOR("list-operator", "list-operator", "UNSUPPORTED"),
5353

5454
PARAMETER_PARSING("parameter-parsing", "parameter-extractor", "ParameterExtractor"),
5555

5656
TOOL("tool", "tool", "UNSUPPORTED"),
5757

58-
MCP("mcp", "UNSUPPORTED", "UNSUPPORTED"),
58+
// Dify的MCP使用ToolNode定义
59+
MCP("mcp", "UNSUPPORTED", "MCP"),
5960

6061
TEMPLATE_TRANSFORM("template-transform", "template-transform", "UNSUPPORTED"),
6162

@@ -93,6 +94,11 @@ public String studioValue() {
9394
return this.studioValue;
9495
}
9596

97+
public static boolean isEmpty(NodeType nodeType) {
98+
return NodeType.EMPTY.equals(nodeType) || NodeType.ITERATION_START.equals(nodeType)
99+
|| NodeType.ITERATION_END.equals(nodeType);
100+
}
101+
96102
public static Optional<NodeType> fromValue(String value) {
97103
return Arrays.stream(NodeType.values()).filter(nodeType -> nodeType.value.equals(value)).findFirst();
98104
}

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/spring-ai-alibaba-studio-server-admin/src/main/java/com/alibaba/cloud/ai/studio/admin/generator/model/workflow/nodedata/HttpNodeData.java

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.alibaba.cloud.ai.studio.admin.generator.model.VariableType;
3030
import com.alibaba.cloud.ai.studio.admin.generator.model.workflow.NodeData;
3131

32+
import com.alibaba.cloud.ai.studio.admin.generator.service.dsl.DSLDialectType;
3233
import org.springframework.http.HttpMethod;
3334

3435
/**
@@ -37,25 +38,30 @@
3738
*/
3839
public class HttpNodeData extends NodeData {
3940

40-
public static List<Variable> getDefaultOutputSchemas() {
41-
return List.of(new Variable("body", VariableType.STRING), new Variable("status_code", VariableType.NUMBER),
42-
new Variable("headers", VariableType.OBJECT), new Variable("files", VariableType.ARRAY_FILE));
41+
public static List<Variable> getDefaultOutputSchemas(DSLDialectType dialectType) {
42+
return switch (dialectType) {
43+
case DIFY ->
44+
List.of(new Variable("body", VariableType.STRING), new Variable("status_code", VariableType.NUMBER),
45+
new Variable("headers", VariableType.OBJECT), new Variable("files", VariableType.ARRAY_FILE));
46+
case STUDIO -> List.of(new Variable("output", VariableType.STRING));
47+
default -> List.of();
48+
};
4349
}
4450

4551
/** HTTP method, default GET */
46-
private HttpMethod method = HttpMethod.GET;
52+
private HttpMethod method;
4753

4854
/** Request URL */
4955
private String url;
5056

5157
/** Request header */
52-
private Map<String, String> headers = Collections.emptyMap();
58+
private Map<String, String> headers;
5359

5460
/** queryParams */
55-
private Map<String, String> queryParams = Collections.emptyMap();
61+
private Map<String, String> queryParams;
5662

5763
/** body */
58-
private HttpRequestNodeBody body = new HttpRequestNodeBody();
64+
private HttpRequestNodeBody body;
5965

6066
/**
6167
* rawBodyMap
@@ -66,7 +72,7 @@ public static List<Variable> getDefaultOutputSchemas() {
6672
private AuthConfig authConfig;
6773

6874
/** retryConfig */
69-
private RetryConfig retryConfig = new RetryConfig(3, 1000, true);
75+
private RetryConfig retryConfig;
7076

7177
/** TimeoutConfig */
7278
private TimeoutConfig timeoutConfig;
@@ -90,12 +96,6 @@ public HttpNodeData(List<VariableSelector> inputs, List<Variable> outputs, HttpM
9096
this.rawBodyMap = null;
9197
}
9298

93-
public HttpNodeData(List<VariableSelector> inputs, List<Variable> outputs) {
94-
this(inputs, outputs, HttpMethod.GET, null, Collections.emptyMap(), Collections.emptyMap(),
95-
new HttpRequestNodeBody(), null, new RetryConfig(3, 1000, true),
96-
new TimeoutConfig(10, 60, 20, 300, 600, 6000), null);
97-
}
98-
9999
public HttpMethod getMethod() {
100100
return method;
101101
}
@@ -136,6 +136,14 @@ public void setBody(HttpRequestNodeBody body) {
136136
this.body = body;
137137
}
138138

139+
public Map<String, Object> getRawBodyMap() {
140+
return rawBodyMap;
141+
}
142+
143+
public void setRawBodyMap(Map<String, Object> rawBodyMap) {
144+
this.rawBodyMap = rawBodyMap;
145+
}
146+
139147
public AuthConfig getAuthConfig() {
140148
return authConfig;
141149
}
@@ -152,20 +160,20 @@ public void setRetryConfig(RetryConfig retryConfig) {
152160
this.retryConfig = retryConfig;
153161
}
154162

155-
public String getOutputKey() {
156-
return outputKey;
163+
public TimeoutConfig getTimeoutConfig() {
164+
return timeoutConfig;
157165
}
158166

159-
public void setOutputKey(String outputKey) {
160-
this.outputKey = outputKey;
167+
public void setTimeoutConfig(TimeoutConfig timeoutConfig) {
168+
this.timeoutConfig = timeoutConfig;
161169
}
162170

163-
public Map<String, Object> getRawBodyMap() {
164-
return rawBodyMap;
171+
public String getOutputKey() {
172+
return outputKey;
165173
}
166174

167-
public void setRawBodyMap(Map<String, Object> rawBodyMap) {
168-
this.rawBodyMap = rawBodyMap;
175+
public void setOutputKey(String outputKey) {
176+
this.outputKey = outputKey;
169177
}
170178

171179
}

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/spring-ai-alibaba-studio-server-admin/src/main/java/com/alibaba/cloud/ai/studio/admin/generator/model/workflow/nodedata/MCPNodeData.java

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,92 +16,71 @@
1616

1717
package com.alibaba.cloud.ai.studio.admin.generator.model.workflow.nodedata;
1818

19-
import java.util.Collections;
20-
import java.util.List;
21-
import java.util.Map;
22-
23-
import com.alibaba.cloud.ai.studio.admin.generator.model.VariableSelector;
2419
import com.alibaba.cloud.ai.studio.admin.generator.model.workflow.NodeData;
2520

26-
/**
27-
* NodeData for McpNode: Contains fields such as url, tool, headers, params, outputKey,
28-
* inputParamKeys, etc.
29-
*/
21+
import java.util.List;
22+
23+
// 本类仅考虑Studio的MCP使用,Dify的MCP使用ToolNode定义
3024
public class MCPNodeData extends NodeData {
3125

32-
private String url;
26+
private String toolName;
3327

34-
private String tool;
28+
private String serverName;
3529

36-
private Map<String, String> headers;
30+
private String serverCode;
3731

38-
private Map<String, Object> params;
32+
private String inputJsonTemplate = "";
3933

40-
private String outputKey;
34+
private List<String> inputKeys = List.of();
4135

42-
private List<String> inputParamKeys;
36+
private String outputKey = "output";
4337

44-
public MCPNodeData() {
45-
super(Collections.emptyList(), Collections.emptyList());
38+
public String getToolName() {
39+
return toolName;
4640
}
4741

48-
public MCPNodeData(List<VariableSelector> inputs,
49-
List<com.alibaba.cloud.ai.studio.admin.generator.model.Variable> outputs) {
50-
super(inputs, outputs);
42+
public void setToolName(String toolName) {
43+
this.toolName = toolName;
5144
}
5245

53-
public String getUrl() {
54-
return url;
46+
public String getServerName() {
47+
return serverName;
5548
}
5649

57-
public MCPNodeData setUrl(String url) {
58-
this.url = url;
59-
return this;
50+
public void setServerName(String serverName) {
51+
this.serverName = serverName;
6052
}
6153

62-
public String getTool() {
63-
return tool;
54+
public String getServerCode() {
55+
return serverCode;
6456
}
6557

66-
public MCPNodeData setTool(String tool) {
67-
this.tool = tool;
68-
return this;
58+
public void setServerCode(String serverCode) {
59+
this.serverCode = serverCode;
6960
}
7061

71-
public Map<String, String> getHeaders() {
72-
return headers;
62+
public String getInputJsonTemplate() {
63+
return inputJsonTemplate;
7364
}
7465

75-
public MCPNodeData setHeaders(Map<String, String> headers) {
76-
this.headers = headers;
77-
return this;
66+
public void setInputJsonTemplate(String inputJsonTemplate) {
67+
this.inputJsonTemplate = inputJsonTemplate;
7868
}
7969

80-
public Map<String, Object> getParams() {
81-
return params;
70+
public List<String> getInputKeys() {
71+
return inputKeys;
8272
}
8373

84-
public MCPNodeData setParams(Map<String, Object> params) {
85-
this.params = params;
86-
return this;
74+
public void setInputKeys(List<String> inputKeys) {
75+
this.inputKeys = inputKeys;
8776
}
8877

8978
public String getOutputKey() {
9079
return outputKey;
9180
}
9281

93-
public MCPNodeData setOutputKey(String outputKey) {
82+
public void setOutputKey(String outputKey) {
9483
this.outputKey = outputKey;
95-
return this;
96-
}
97-
98-
public List<String> getInputParamKeys() {
99-
return inputParamKeys;
100-
}
101-
102-
public MCPNodeData setInputParamKeys(List<String> inputParamKeys) {
103-
this.inputParamKeys = inputParamKeys;
104-
return this;
10584
}
10685

10786
}

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/spring-ai-alibaba-studio-server-admin/src/main/java/com/alibaba/cloud/ai/studio/admin/generator/service/dsl/adapters/DifyDSLAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ private List<Node> constructNodes(List<Map<String, Object>> nodeMaps) {
291291
NodeData data = converter.parseMapData(nodeDataMap, DSLDialectType.DIFY);
292292

293293
// Generate a readable varName and inject it into NodeData
294-
int count = counters.merge(nodeType, 1, Integer::sum);
294+
int count = counters.merge(NodeType.isEmpty(nodeType) ? NodeType.EMPTY : nodeType, 1, Integer::sum);
295295
String varName = converter.generateVarName(count);
296296

297297
data.setVarName(varName);

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/spring-ai-alibaba-studio-server-admin/src/main/java/com/alibaba/cloud/ai/studio/admin/generator/service/dsl/adapters/StudioDSLAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ private Graph constructGraph(Map<String, Object> data) {
137137
// 展开迭代节点内部的Node和Edge
138138
nodeMap.forEach(map -> {
139139
NodeType type = NodeType.fromStudioValue(MapReadUtil.getMapDeepValue(map, String.class, "type"))
140-
.orElseThrow();
140+
.orElseThrow(() -> new UnsupportedOperationException("unsupported node type " + map.get("type")));
141141
if (NodeType.ITERATION.equals(type)) {
142142
List<Map<String, Object>> innerNode = MapReadUtil.safeCastToListWithMap(
143143
MapReadUtil.getMapDeepValue(map, List.class, "config", "node_param", "block", "nodes"));
@@ -242,7 +242,7 @@ private List<Node> constructNodes(List<Map<String, Object>> nodeMaps) {
242242
NodeData data = converter.parseMapData(nodeMap, DSLDialectType.STUDIO);
243243

244244
// Generate a readable varName and inject it into NodeData
245-
int count = counters.merge(nodeType, 1, Integer::sum);
245+
int count = counters.merge(NodeType.isEmpty(nodeType) ? NodeType.EMPTY : nodeType, 1, Integer::sum);
246246
String varName = converter.generateVarName(count);
247247

248248
data.setVarName(varName);

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/spring-ai-alibaba-studio-server-admin/src/main/java/com/alibaba/cloud/ai/studio/admin/generator/service/dsl/converter/EmptyNodeDataConverter.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,7 @@ protected List<DialectConverter<EmptyNodeData>> getDialectConverters() {
7575

7676
@Override
7777
public Boolean supportNodeType(NodeType nodeType) {
78-
// 迭代节点的起始节点与迭代节点共享一个data,故转换时不需要提取数据
79-
return NodeType.EMPTY.equals(nodeType) || NodeType.ITERATION_START.equals(nodeType)
80-
|| NodeType.ITERATION_END.equals(nodeType);
78+
return NodeType.isEmpty(nodeType);
8179
}
8280

8381
@Override

spring-ai-alibaba-studio/spring-ai-alibaba-studio-server/spring-ai-alibaba-studio-server-admin/src/main/java/com/alibaba/cloud/ai/studio/admin/generator/service/dsl/converter/EndNodeDataConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public Map<String, Object> dump(EndNodeData nodeData) {
7979
data.put("outputs", outputsMap);
8080
return data;
8181
}
82-
}), STUDIO(new DialectConverter<EndNodeData>() {
82+
}), STUDIO(new DialectConverter<>() {
8383
@Override
8484
public Boolean supportDialect(DSLDialectType dialectType) {
8585
return DSLDialectType.STUDIO.equals(dialectType);

0 commit comments

Comments
 (0)