Skip to content

Commit b72415a

Browse files
authored
Support output scheme and refactor the CreateTool (alibaba#14057)
* support output scheme Change-Id: I086a629ea8b6361aeb534efaae1dcdbec70bc555 * feat: Introduce a new UI for creating and managing AI tools with a schema editor for input/output definitions. Change-Id: I127ec18c00437d32cc70a87b942a0ae4dc867eaf
1 parent 8c6560c commit b72415a

25 files changed

Lines changed: 3851 additions & 2747 deletions

File tree

ai/src/test/java/com/alibaba/nacos/ai/utils/McpRequestUtilTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ class McpRequestUtilTest {
4646

4747
private static final String MCP_TOOL_SPEC =
4848
"{\"tools\":[{\"name\":\"list_namespace\",\"description\":\"list namespace in nacos\","
49-
+ "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"a\":{\"type\":\"string\",\"description\":\"aaa\"}}}}],"
49+
+ "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"a\":{\"type\":\"string\",\"description\":\"aaa\"}}},"
50+
+ "\"outputSchema\":{\"type\":\"object\",\"properties\":{\"result\":{\"type\":\"string\"}}}}],"
5051
+ "\"toolsMeta\":{\"list_namespace\":{\"invokeContext\":{\"path\":\"/xxx\",\"method\":\"GET\"},\"enabled\":true,"
5152
+ "\"templates\":{\"json-go-tamplate\":{\"templateType\":\"string\",\"requestTemplate\":{\"url\":\"\",\"method\":\"GET\","
5253
+ "\"headers\":[],\"argsToJsonBody\":false,\"argsToUrlParam\":true,\"argsToFormBody\":true,\"body\":\"string\"},"
@@ -130,6 +131,8 @@ void parseMcpToolsSuccess() throws NacosApiException {
130131
assertEquals("list_namespace", actual.getTools().get(0).getName());
131132
assertEquals("list namespace in nacos", actual.getTools().get(0).getDescription());
132133
assertEquals(2, actual.getTools().get(0).getInputSchema().size());
134+
assertNotNull(actual.getTools().get(0).getOutputSchema());
135+
assertEquals("object", actual.getTools().get(0).getOutputSchema().get("type"));
133136
assertEquals(1, actual.getToolsMeta().size());
134137
assertNotNull(actual.getToolsMeta().get("list_namespace"));
135138
assertNotNull(actual.getToolsMeta().get("list_namespace").getInvokeContext());

api/src/main/java/com/alibaba/nacos/api/ai/model/mcp/McpTool.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public class McpTool {
3030
private String description;
3131

3232
private Map<String, Object> inputSchema;
33+
34+
private Map<String, Object> outputSchema;
3335

3436
public String getName() {
3537
return name;
@@ -54,5 +56,13 @@ public Map<String, Object> getInputSchema() {
5456
public void setInputSchema(Map<String, Object> inputSchema) {
5557
this.inputSchema = inputSchema;
5658
}
59+
60+
public Map<String, Object> getOutputSchema() {
61+
return outputSchema;
62+
}
63+
64+
public void setOutputSchema(Map<String, Object> outputSchema) {
65+
this.outputSchema = outputSchema;
66+
}
5767

5868
}

api/src/test/java/com/alibaba/nacos/api/ai/model/mcp/McpToolSpecificationTest.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ void testSerialize() throws JsonProcessingException {
6969
aSchema.put("type", "string");
7070
aSchema.put("description", "aaa");
7171
mcpTool.setInputSchema(inputSchema);
72+
73+
Map<String, Object> outputSchema = new HashMap<>();
74+
outputSchema.put("type", "object");
75+
Map<String, Object> outProperties = new HashMap<>();
76+
Map<String, String> resultSchema = new HashMap<>();
77+
resultSchema.put("type", "string");
78+
resultSchema.put("description", "result");
79+
outProperties.put("result", resultSchema);
80+
outputSchema.put("properties", outProperties);
81+
mcpTool.setOutputSchema(outputSchema);
7282

7383
McpToolMeta mcpToolMeta = new McpToolMeta();
7484
Map<String, Object> templates = new HashMap<>();
@@ -116,6 +126,7 @@ void testSerialize() throws JsonProcessingException {
116126
assertTrue(json.contains("\"inputSchema\":{"));
117127
assertTrue(json.contains("{\"type\":\"object\""));
118128
assertTrue(json.contains("\"properties\":{\"a\":{"));
129+
assertTrue(json.contains("\"outputSchema\":{"));
119130
assertTrue(json.contains("\"toolsMeta\":{\"testTool\":{"));
120131
assertTrue(json.contains("\"invokeContext\":{"));
121132
assertTrue(json.contains("\"templates\":{"));
@@ -127,12 +138,19 @@ void testSerialize() throws JsonProcessingException {
127138
void testDeserialize() throws JsonProcessingException {
128139
String json = "{\"specificationType\":\"encrypted\",\"encryptData\":{\"data\":\"encryptedData\","
129140
+ "\"encryptInfo\":{\"alg\":\"AES\",\"iv\":\"initialVector\"}},"
130-
+ "\"tools\":[{\"name\":\"testTool\",\"description\":\"test tool description\",\"inputSchema\":{\"type\":\"object\","
131-
+ "\"properties\":{\"a\":{\"description\":\"aaa\",\"type\":\"string\"}}}}],\"toolsMeta\":{\"testTool\":"
141+
+ "\"tools\":[{\"name\":\"testTool\",\"description\":\"test tool description\","
142+
+ "\"inputSchema\":{\"type\":\"object\","
143+
+ "\"properties\":{\"a\":{\"description\":\"aaa\",\"type\":\"string\"}}},"
144+
+ "\"outputSchema\":{\"type\":\"object\","
145+
+ "\"properties\":{\"result\":{\"type\":\"string\","
146+
+ "\"description\":\"result\"}}}}],"
147+
+ "\"toolsMeta\":{\"testTool\":"
132148
+ "{\"invokeContext\":{\"path\":\"/xxx\",\"method\":\"GET\"},\"enabled\":true,\"templates\":"
133149
+ "{\"json-go-tamplate\":{\"templateType\":\"string\",\"responseTemplate\":{\"body\":\"string\"},"
134-
+ "\"requestTemplate\":{\"headers\":[],\"method\":\"GET\",\"argsToFormBody\":true,\"argsToJsonBody\":false,"
135-
+ "\"body\":\"string\",\"url\":\"\",\"argsToUrlParam\":true}}}}},\"securitySchemes\":[{\"id\":\"1\","
150+
+ "\"requestTemplate\":{\"headers\":[],\"method\":\"GET\",\"argsToFormBody\":true,"
151+
+ "\"argsToJsonBody\":false,"
152+
+ "\"body\":\"string\",\"url\":\"\",\"argsToUrlParam\":true}}}}},"
153+
+ "\"securitySchemes\":[{\"id\":\"1\","
136154
+ "\"type\":\"apiKey\",\"scheme\":\"\",\"in\":\"header\",\"name\":\"testSecurity\","
137155
+ "\"defaultCredential\":\"publicKey\"}]}";
138156

@@ -148,6 +166,8 @@ void testDeserialize() throws JsonProcessingException {
148166
assertEquals("test tool description", result.getTools().get(0).getDescription());
149167
assertEquals("object", result.getTools().get(0).getInputSchema().get("type"));
150168
assertNotNull(result.getTools().get(0).getInputSchema().get("properties"));
169+
assertEquals("object", result.getTools().get(0).getOutputSchema().get("type"));
170+
assertNotNull(result.getTools().get(0).getOutputSchema().get("properties"));
151171
assertEquals(1, result.getToolsMeta().size());
152172
assertNotNull(result.getToolsMeta().get("testTool"));
153173
assertNotNull(result.getToolsMeta().get("testTool").getInvokeContext());

api/src/test/java/com/alibaba/nacos/api/ai/model/mcp/McpToolTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ void testSerialize() throws JsonProcessingException {
4646

4747
inputSchema.put("properties", properties);
4848
mcpTool.setInputSchema(inputSchema);
49+
50+
Map<String, Object> outputSchema = new HashMap<>();
51+
outputSchema.put("type", "object");
52+
Map<String, Object> outputProperties = new HashMap<>();
53+
Map<String, String> resultSchema = new HashMap<>();
54+
resultSchema.put("type", "string");
55+
resultSchema.put("description", "Result");
56+
outputProperties.put("result", resultSchema);
57+
outputSchema.put("properties", outputProperties);
58+
mcpTool.setOutputSchema(outputSchema);
4959

5060
String json = mapper.writeValueAsString(mcpTool);
5161
assertTrue(json.contains("\"name\":\"testTool\""));
@@ -56,13 +66,19 @@ void testSerialize() throws JsonProcessingException {
5666
assertTrue(json.contains("\"a\":{"));
5767
assertTrue(json.contains("\"type\":\"string\""));
5868
assertTrue(json.contains("\"description\":\"Parameter A\""));
69+
70+
assertTrue(json.contains("\"outputSchema\":{"));
71+
assertTrue(json.contains("\"result\":{"));
72+
assertTrue(json.contains("\"description\":\"Result\""));
5973
}
6074

6175
@Test
6276
void testDeserialize() throws JsonProcessingException {
6377
String json = "{\"name\":\"testTool\",\"description\":\"A test tool for MCP\","
6478
+ "\"inputSchema\":{\"type\":\"object\",\"properties\":{\"a\":{\"type\":\"string\","
65-
+ "\"description\":\"Parameter A\"}}}}";
79+
+ "\"description\":\"Parameter A\"}}},"
80+
+ "\"outputSchema\":{\"type\":\"object\",\"properties\":{\"result\":{\"type\":\"string\","
81+
+ "\"description\":\"Result\"}}}}";
6682

6783
McpTool result = mapper.readValue(json, McpTool.class);
6884
assertNotNull(result);
@@ -76,5 +92,10 @@ void testDeserialize() throws JsonProcessingException {
7692
Map<String, String> paramA = (Map<String, String>) properties.get("a");
7793
assertEquals("string", paramA.get("type"));
7894
assertEquals("Parameter A", paramA.get("description"));
95+
96+
assertNotNull(result.getOutputSchema());
97+
assertEquals("object", result.getOutputSchema().get("type"));
98+
Map<String, Object> outProps = (Map<String, Object>) result.getOutputSchema().get("properties");
99+
assertNotNull(outProps.get("result"));
79100
}
80101
}

console-ui/src/locales/en-US.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,9 @@ const I18N_CONF = {
889889
online: 'Enable',
890890
offline: 'Disable',
891891
toolInputSchema: 'Tool Input Parameter Description',
892+
toolOutputSchema: 'Tool Output Parameter Description',
893+
outputSchemaHelp: 'Optional: provide JSON Schema to describe tool outputs',
894+
outputSchemaParseError: 'Output schema is not valid JSON',
892895
toolParamName: 'Name',
893896
toolParamType: 'Type',
894897
toolParamDescription: 'Description',
@@ -925,6 +928,7 @@ const I18N_CONF = {
925928
toolMetadata: 'Tool metadata',
926929
baseData: 'Basic data',
927930
ArgumentsList: 'Arguments',
931+
OutputArgumentsList: 'Output Arguments',
928932
AddNewArg: 'Add new Arg',
929933
AddNewProperties: 'Add new Properties',
930934
ArgumentInfo: 'Argument Info',
@@ -972,6 +976,9 @@ const I18N_CONF = {
972976
close: 'Close',
973977
toolDescription: 'Tool Description',
974978
toolInputSchema: 'Tool Input Parameter Description',
979+
toolOutputSchema: 'Tool Output Parameter Description',
980+
outputSchemaHelp: 'Optional: provide JSON Schema to describe tool outputs',
981+
outputSchemaParseError: 'Output schema is not valid JSON',
975982
toolParamName: 'Name',
976983
toolParamType: 'Type',
977984
toolParamDescription: 'Description',

console-ui/src/locales/zh-CN.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,9 @@ const I18N_CONF = {
886886
online: '启用',
887887
offline: '禁用',
888888
toolInputSchema: 'Tool 入参描述',
889+
toolOutputSchema: 'Tool 出参描述',
890+
outputSchemaHelp: '可选:填写 JSON Schema,用于描述 Tool 的输出结构',
891+
outputSchemaParseError: '出参配置不是合法 JSON,请检查',
889892
toolParamName: '名称',
890893
toolParamType: '类型',
891894
toolParamDescription: '描述',
@@ -922,6 +925,7 @@ const I18N_CONF = {
922925
toolMetadata: 'Tool 元数据',
923926
baseData: '基础数据',
924927
ArgumentsList: '参数列表',
928+
OutputArgumentsList: '出参列表',
925929
AddNewArg: '添加参数',
926930
AddNewProperties: '添加属性',
927931
ArgumentInfo: '参数信息',
@@ -968,6 +972,9 @@ const I18N_CONF = {
968972
close: '关闭',
969973
toolDescription: 'Tool 描述',
970974
toolInputSchema: 'Tool 入参描述',
975+
toolOutputSchema: 'Tool 出参描述',
976+
outputSchemaHelp: '可选:填写 JSON Schema,用于描述 Tool 的输出结构',
977+
outputSchemaParseError: '出参配置不是合法 JSON,请检查',
971978
toolParamName: '名称',
972979
toolParamType: '类型',
973980
toolParamDescription: '描述',

0 commit comments

Comments
 (0)