Skip to content

Commit 9323e87

Browse files
feat(tool): add additionalProperties support to @tool annotation
Add additionalProperties parameter to @tool annotation to control whether LLM can pass extra parameters not defined in the schema. When set to false, ToolValidator rejects hallucinated params. Closes #1379
1 parent 9e83aa7 commit 9323e87

4 files changed

Lines changed: 422 additions & 1 deletion

File tree

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,18 @@
130130
* @see DefaultToolResultConverter
131131
*/
132132
Class<? extends ToolResultConverter> converter() default DefaultToolResultConverter.class;
133+
134+
/**
135+
* Whether to allow additional properties beyond those defined in the schema.
136+
*
137+
* <p>Corresponds to the {@code additionalProperties} keyword in JSON Schema. When set to
138+
* {@code false}, any extra parameters passed by the LLM that are not defined in the schema will
139+
* cause a validation error, preventing the LLM from hallucinating undefined parameters.
140+
*
141+
* <p>This setting is applied recursively to all nested objects within the generated schema.
142+
*
143+
* @return {@code true} to allow additional properties (default, backward compatible), {@code
144+
* false} to disallow
145+
*/
146+
boolean additionalProperties() default true;
133147
}

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,77 @@
3333
*/
3434
class ToolSchemaGenerator {
3535

36+
/**
37+
* Generate parameter schema with additionalProperties control.
38+
*
39+
* @param method the method to generate schema for
40+
* @param excludeParams set of parameter names to exclude (may be null or empty)
41+
* @param allowAdditionalProperties whether to allow additional properties in the schema
42+
* @return JSON Schema map
43+
*/
44+
@SuppressWarnings("unchecked")
45+
Map<String, Object> generateParameterSchema(
46+
Method method, Set<String> excludeParams, boolean allowAdditionalProperties) {
47+
Map<String, Object> schema = generateParameterSchema(method, excludeParams);
48+
49+
if (!allowAdditionalProperties) {
50+
addAdditionalPropertiesFalseRecursively(schema);
51+
}
52+
53+
return schema;
54+
}
55+
56+
@SuppressWarnings("unchecked")
57+
private void addAdditionalPropertiesFalseRecursively(Map<String, Object> schema) {
58+
if (!"object".equals(schema.get("type"))) {
59+
return;
60+
}
61+
// Skip if user already set additionalProperties explicitly
62+
if (schema.containsKey("additionalProperties")) {
63+
return;
64+
}
65+
schema.put("additionalProperties", false);
66+
67+
Object propsObj = schema.get("properties");
68+
if (propsObj instanceof Map) {
69+
Map<String, Object> props = (Map<String, Object>) propsObj;
70+
for (Map.Entry<String, Object> entry : props.entrySet()) {
71+
if (entry.getValue() instanceof Map) {
72+
addAdditionalPropertiesFalseRecursively((Map<String, Object>) entry.getValue());
73+
}
74+
}
75+
}
76+
77+
Object itemsObj = schema.get("items");
78+
if (itemsObj instanceof Map) {
79+
addAdditionalPropertiesFalseRecursively((Map<String, Object>) itemsObj);
80+
}
81+
82+
for (String key : new String[] {"oneOf", "anyOf", "allOf"}) {
83+
Object listObj = schema.get(key);
84+
if (listObj instanceof Iterable) {
85+
for (Object item : (Iterable<?>) listObj) {
86+
if (item instanceof Map) {
87+
addAdditionalPropertiesFalseRecursively((Map<String, Object>) item);
88+
}
89+
}
90+
}
91+
}
92+
93+
for (String defsKey : new String[] {"$defs", "definitions"}) {
94+
Object defsObj = schema.get(defsKey);
95+
if (defsObj instanceof Map) {
96+
Map<String, Object> defs = (Map<String, Object>) defsObj;
97+
for (Map.Entry<String, Object> entry : defs.entrySet()) {
98+
if (entry.getValue() instanceof Map) {
99+
addAdditionalPropertiesFalseRecursively(
100+
(Map<String, Object>) entry.getValue());
101+
}
102+
}
103+
}
104+
}
105+
}
106+
36107
/**
37108
* Generate parameter schema for a method with excluded parameters.
38109
*

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,8 @@ public Map<String, Object> getParameters() {
373373
presetParameters != null
374374
? presetParameters.keySet()
375375
: Collections.emptySet();
376-
return schemaGenerator.generateParameterSchema(method, excludeParams);
376+
return schemaGenerator.generateParameterSchema(
377+
method, excludeParams, toolAnnotation.additionalProperties());
377378
}
378379

379380
@Override

0 commit comments

Comments
 (0)