Skip to content

Conversation

@IssacAX123
Copy link
Member

Copy link
Member

@devalibm devalibm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments, other than that logic seems fine to me

@IssacAX123 IssacAX123 force-pushed the 33462-generic-tool-paaramter-schema branch from aa2f3fc to ed3d6a7 Compare December 16, 2025 16:04
return false;
}

public static Type createCustomType(Type type, Map<TypeVariable<?>, Type> genericMap) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this (maybe to createResolvedType) and add Javadoc.

Comment on lines +344 to +349
if (type instanceof TypeVariable<?> tv) {
if (genericMap.get(tv) == null) {
return type;
} else {
return genericMap.get(tv);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall what can go in the generic map, do we need to call it in a loop or not?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we do (based on TypeUtility.populateGenericMap)

/**
*
*/
public class GenericArrayTypeImpl implements GenericArrayType {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we ever use this as a key in a map? If so we need to implement hashcode and equals.

/**
*
*/
public class ParameterizedTypeImpl implements ParameterizedType {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we might use this as a key in a map we need hashcode and equals.

Comment on lines +73 to +75
public JsonObject getToolInputSchema(AnnotatedMethod<?> toolMethod, Map<TypeVariable<?>, Type> genericMap) {
ToolKey key = new ToolKey(toolMethod, SchemaDirection.INPUT);
return schemaCache.computeIfAbsent(key, k -> SchemaGenerator.generateToolInputSchema(toolMethod, blueprintRegistry));
return schemaCache.computeIfAbsent(key, k -> SchemaGenerator.generateToolInputSchema(toolMethod, blueprintRegistry, genericMap));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is questionable - the key should incorporate all the information required to create the schema, otherwise we will return a schema from the cache if we generate a schema for the same method but a different generic map.

I think we get away with it here because it's not valid to have two tools with the same method, since they'd automatically have the same name.

When we support multiple modules however, it might be possible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I add generic Map to tool key or if you agree with this comment #33688 (comment) I could add in the argumentMaps instead

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think either of those would be fine. However, we must make sure that anything that's part of the key has equals and hashcode implemented (records have these automatically) and won't change after it's put in the map.

Comment on lines +245 to +278
private static GenericParameterDescription checkConcreteGeneric(Type baseType, Map<TypeVariable<?>, Type> genericMap, ArrayList<String> genericParams,
GenericParameterDescription genericParamDescription) {

List<Type> genericTypes;
if (baseType instanceof ParameterizedType pt) {
genericTypes = List.of(pt.getActualTypeArguments());

} else if (baseType instanceof GenericArrayType gat) {
genericTypes = List.of(gat.getGenericComponentType());

} else if (baseType instanceof TypeVariable<?> tv) {
genericTypes = List.of(tv);

} else if (baseType.getClass().isArray()) {
Type elementType = ((Class<?>) baseType).getComponentType();
genericTypes = List.of(elementType);

} else
genericTypes = List.of();

for (Type genericType : genericTypes) {
if (genericType instanceof TypeVariable<?> tv) {
if (genericMap.get(tv) == null) {
genericParamDescription = GenericParameterDescription.NONCONCRETE_GENERIC;
} else {
genericParamDescription = GenericParameterDescription.CONCRETE_GENERIC;
}
} else {
checkConcreteGeneric(genericType, genericMap, genericParams, genericParamDescription);
}
}
return genericParamDescription;

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we change this to boolean hasUnresolvableTypeVariables(Type baseType, Map<TypeVariable<?>, Type> genericMap)

genericParams is unused and genericParamDescription is not necessary.

MockAnnotatedMethod<Object> toolMethod = TestUtils.findMethod(SchemaTest.class, "updateWidget");

String toolInputSchema = registry.getToolInputSchema(toolMethod).toString();
String toolInputSchema = registry.getToolInputSchema(toolMethod, new HashMap<>()).toString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Collections.emptyMap() when you need an empty map that shouldn't change.

Comment on lines 1932 to 1945
@Tool(name = "addGenericSingleBoundToList", title = "adds generic to generic list", description = "adds person to employee list, returns nothing")
public @Schema(description = "Returns list of person object") <T extends Number> List<T> addGenericSingleBoundToList(@ToolArg(name = "generic list",
description = "List of generics") List<T> list,
@ToolArg(name = "generic",
description = "Generic object") T item) {
list.add(item);
return list;
//comment
}

@Test
@Test(expected = GenericArgumentException.class)
public void testGenericSingleBoundToolArg() {
MockAnnotatedMethod<Object> toolMethod = TestUtils.findMethod(SchemaTest.class, "addGenericSingleBoundToList");
String response = registry.getToolInputSchema(toolMethod).toString();
String expectedResponseString = """
{
"type": "object",
"properties": {
"generic list": {
"type": "array",
"items": {
"$ref": "#/$defs/T"
},
"description": "List of generics"
},
"generic": {
"$ref": "#/$defs/T",
"description": "Generic object"
}
},
"required": [
"generic list",
"generic"
],
"$defs": {
"T": {
"type": "number"
}
}
}
""";
JSONAssert.assertEquals(expectedResponseString, response, true);
String response = registry.getToolInputSchema(toolMethod, new HashMap<>()).toString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, this used to work but now doesn't...

I think it's reasonable not to allow this.

Copy link
Member Author

@IssacAX123 IssacAX123 Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In FAT test I got rid of all tool tests that had non concrete generic params, I forgot to remove the corresponding test in units tests.
The unit test still passed because there was different versions of getArgumentMap() calls were done, one version threw generic exception (called in ToolMetadat.createFrom())the other didnt (This one was callled in getInputTool) but in this new version they both call the same getArgumentMap which throws the exception.
There exists test which tests generic fields in objects directly using getSchema but not in getToolInput.

Comment on lines +258 to +260
} else if (baseType.getClass().isArray()) {
Type elementType = ((Class<?>) baseType).getComponentType();
genericTypes = List.of(elementType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (baseType.getClass().isArray()) {
Type elementType = ((Class<?>) baseType).getComponentType();
genericTypes = List.of(elementType);
} else if (baseType instanceof Class clazz && clazz.isArray()) {
Type elementType = clazz.getComponentType();
genericTypes = List.of(elementType);

Should be this I think? baseType.getClass() would return one of the subtypes of Type, whereas I think you want baseType as a Class

Comment on lines +262 to +263
} else
genericTypes = List.of();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else
genericTypes = List.of();
} else {
genericTypes = List.of();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants