Skip to content

Commit 319b26d

Browse files
authored
feat: Implement array and collection items schema generation in ToolSchemaGenerator and add a corresponding test. (#205)
1 parent bb1149e commit 319b26d

3 files changed

Lines changed: 66 additions & 62 deletions

File tree

agentscope-core/src/main/java/io/agentscope/core/tool/ToolSchemaGenerator.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828

2929
/**
3030
* Generates JSON Schema for tool parameters.
31-
* This class handles the conversion of Java method signatures to JSON Schema format
31+
* This class handles the conversion of Java method signatures to JSON Schema
32+
* format
3233
* compatible with OpenAI's function calling API.
3334
*/
3435
class ToolSchemaGenerator {
@@ -46,11 +47,14 @@ Map<String, Object> generateParameterSchema(Method method) {
4647
/**
4748
* Generate parameter schema for a method with excluded parameters.
4849
*
49-
* <p>This overload allows excluding certain parameters from the generated schema, which is
50+
* <p>
51+
* This overload allows excluding certain parameters from the generated schema,
52+
* which is
5053
* useful for preset parameters that should not be exposed to the agent.
5154
*
52-
* @param method the method to generate schema for
53-
* @param excludeParams set of parameter names to exclude from the schema (may be null or empty)
55+
* @param method the method to generate schema for
56+
* @param excludeParams set of parameter names to exclude from the schema (may
57+
* be null or empty)
5458
* @return JSON Schema map in OpenAI format
5559
*/
5660
Map<String, Object> generateParameterSchema(Method method, Set<String> excludeParams) {
@@ -62,7 +66,8 @@ Map<String, Object> generateParameterSchema(Method method, Set<String> excludePa
6266

6367
Parameter[] parameters = method.getParameters();
6468
for (Parameter param : parameters) {
65-
// Skip framework parameters like ToolEmitter and Agent - they should not be in the
69+
// Skip framework parameters like ToolEmitter and Agent - they should not be in
70+
// the
6671
// schema
6772
if (param.getType() == ToolEmitter.class || param.getType() == Agent.class) {
6873
continue;
@@ -101,8 +106,10 @@ private ParameterInfo extractParameterInfo(Parameter param) {
101106
// Use name from @ToolParam annotation, fallback to reflection-based name
102107
String paramName = (toolParam != null) ? toolParam.name() : param.getName();
103108

104-
Map<String, Object> paramSchema = new HashMap<>();
105-
paramSchema.put("type", JsonSchemaUtils.mapJavaTypeToJsonType(param.getType()));
109+
// Generate schema using JsonSchemaUtils with full type support (including
110+
// generics)
111+
Map<String, Object> paramSchema =
112+
JsonSchemaUtils.generateSchemaFromType(param.getParameterizedType());
106113

107114
boolean required = false;
108115
if (toolParam != null) {

agentscope-core/src/main/java/io/agentscope/core/util/JsonSchemaUtils.java

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,24 @@
1717
package io.agentscope.core.util;
1818

1919
import com.fasterxml.jackson.core.type.TypeReference;
20+
import com.fasterxml.jackson.databind.JavaType;
2021
import com.fasterxml.jackson.databind.ObjectMapper;
2122
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
2223
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
23-
import java.util.List;
24+
import java.lang.reflect.Type;
2425
import java.util.Map;
2526

2627
/**
2728
* Utility class for JSON Schema operations.
2829
*
29-
* <p>This class provides utility methods for:
30+
* <p>
31+
* This class provides utility methods for:
3032
* <ul>
31-
* <li>Generating JSON schemas from Java classes (for structured output)</li>
32-
* <li>Converting between Maps and typed objects</li>
33-
* <li>Mapping Java types to JSON Schema types</li>
33+
* <li>Generating JSON schemas from Java classes (for structured output)</li>
34+
* <li>Converting between Maps and typed objects</li>
35+
* <li>Mapping Java types to JSON Schema types</li>
3436
* </ul>
37+
*
3538
* @hidden
3639
*/
3740
public class JsonSchemaUtils {
@@ -42,13 +45,15 @@ public class JsonSchemaUtils {
4245

4346
/**
4447
* Generate JSON Schema from a Java class.
45-
* This method is suitable for structured output scenarios where complex nested objects
48+
* This method is suitable for structured output scenarios where complex nested
49+
* objects
4650
* need to be converted to JSON Schema format.
4751
*
4852
* @param clazz The class to generate schema for
4953
* @return JSON Schema as a Map
5054
* @throws RuntimeException if schema generation fails due to reflection errors,
51-
* Jackson configuration issues, or other processing errors
55+
* Jackson configuration issues, or other processing
56+
* errors
5257
*/
5358
public static Map<String, Object> generateSchemaFromClass(Class<?> clazz) {
5459
try {
@@ -59,16 +64,34 @@ public static Map<String, Object> generateSchemaFromClass(Class<?> clazz) {
5964
}
6065
}
6166

67+
/**
68+
* Generate JSON Schema from a Java Type (supports Generics).
69+
*
70+
* @param type The type to generate schema for
71+
* @return JSON Schema as a Map
72+
*/
73+
public static Map<String, Object> generateSchemaFromType(Type type) {
74+
try {
75+
JavaType javaType = objectMapper.constructType(type);
76+
JsonSchema schema = schemaGenerator.generateSchema(javaType);
77+
return objectMapper.convertValue(schema, new TypeReference<Map<String, Object>>() {});
78+
} catch (Exception e) {
79+
throw new RuntimeException(
80+
"Failed to generate JSON schema for " + type.getTypeName(), e);
81+
}
82+
}
83+
6284
/**
6385
* Convert Map to typed object.
6486
*
65-
* @param data The data map
87+
* @param data The data map
6688
* @param targetClass The target class
67-
* @param <T> The type
89+
* @param <T> The type
6890
* @return Converted object
6991
* @throws IllegalStateException if the input data is null
70-
* @throws RuntimeException if the conversion fails due to type mismatch,
71-
* JSON parsing errors, or incompatible data structure
92+
* @throws RuntimeException if the conversion fails due to type mismatch,
93+
* JSON parsing errors, or incompatible data
94+
* structure
7295
*/
7396
public static <T> T convertToObject(Object data, Class<T> targetClass) {
7497
if (data == null) {
@@ -81,33 +104,4 @@ public static <T> T convertToObject(Object data, Class<T> targetClass) {
81104
throw new RuntimeException("Failed to convert metadata to " + targetClass.getName(), e);
82105
}
83106
}
84-
85-
/**
86-
* Map Java type to JSON Schema type.
87-
* This is a simple type mapping suitable for basic tool parameters.
88-
*
89-
* @param clazz the Java class
90-
* @return JSON type string
91-
*/
92-
public static String mapJavaTypeToJsonType(Class<?> clazz) {
93-
if (clazz == String.class) {
94-
return "string";
95-
} else if (clazz == Integer.class
96-
|| clazz == int.class
97-
|| clazz == Long.class
98-
|| clazz == long.class) {
99-
return "integer";
100-
} else if (clazz == Double.class
101-
|| clazz == double.class
102-
|| clazz == Float.class
103-
|| clazz == float.class) {
104-
return "number";
105-
} else if (clazz == Boolean.class || clazz == boolean.class) {
106-
return "boolean";
107-
} else if (clazz.isArray() || List.class.isAssignableFrom(clazz)) {
108-
return "array";
109-
} else {
110-
return "object";
111-
}
112-
}
113107
}

agentscope-core/src/test/java/io/agentscope/core/util/JsonSchemaUtilsTest.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import static org.junit.jupiter.api.Assertions.assertThrows;
2222
import static org.junit.jupiter.api.Assertions.assertTrue;
2323

24+
import com.fasterxml.jackson.core.type.TypeReference;
25+
import java.lang.reflect.Type;
2426
import java.util.List;
2527
import java.util.Map;
2628
import org.junit.jupiter.api.Test;
@@ -137,20 +139,21 @@ void testConvertToObjectInvalidData() {
137139
}
138140

139141
@Test
140-
void testMapJavaTypeToJsonType() {
141-
assertEquals("string", JsonSchemaUtils.mapJavaTypeToJsonType(String.class));
142-
assertEquals("integer", JsonSchemaUtils.mapJavaTypeToJsonType(Integer.class));
143-
assertEquals("integer", JsonSchemaUtils.mapJavaTypeToJsonType(int.class));
144-
assertEquals("integer", JsonSchemaUtils.mapJavaTypeToJsonType(Long.class));
145-
assertEquals("integer", JsonSchemaUtils.mapJavaTypeToJsonType(long.class));
146-
assertEquals("number", JsonSchemaUtils.mapJavaTypeToJsonType(Double.class));
147-
assertEquals("number", JsonSchemaUtils.mapJavaTypeToJsonType(double.class));
148-
assertEquals("number", JsonSchemaUtils.mapJavaTypeToJsonType(Float.class));
149-
assertEquals("number", JsonSchemaUtils.mapJavaTypeToJsonType(float.class));
150-
assertEquals("boolean", JsonSchemaUtils.mapJavaTypeToJsonType(Boolean.class));
151-
assertEquals("boolean", JsonSchemaUtils.mapJavaTypeToJsonType(boolean.class));
152-
assertEquals("array", JsonSchemaUtils.mapJavaTypeToJsonType(String[].class));
153-
assertEquals("array", JsonSchemaUtils.mapJavaTypeToJsonType(List.class));
154-
assertEquals("object", JsonSchemaUtils.mapJavaTypeToJsonType(SimpleModel.class));
142+
void testGenerateSchemaFromType() {
143+
// Test List<String>
144+
Type listType = new TypeReference<List<String>>() {}.getType();
145+
Map<String, Object> listSchema = JsonSchemaUtils.generateSchemaFromType(listType);
146+
assertNotNull(listSchema);
147+
assertEquals("array", listSchema.get("type"));
148+
@SuppressWarnings("unchecked")
149+
Map<String, Object> items = (Map<String, Object>) listSchema.get("items");
150+
assertNotNull(items);
151+
assertEquals("string", items.get("type"));
152+
153+
// Test Map<String, Integer>
154+
Type mapType = new TypeReference<Map<String, Integer>>() {}.getType();
155+
Map<String, Object> mapSchema = JsonSchemaUtils.generateSchemaFromType(mapType);
156+
assertNotNull(mapSchema);
157+
assertEquals("object", mapSchema.get("type"));
155158
}
156159
}

0 commit comments

Comments
 (0)