问题背景
LLM 在调用工具时,有时会自行伪造(hallucinate)未定义的参数。由于当前框架对额外参数的处理规则是静默忽略,工具仍能正常返回结果,导致:
- LLM 误以为伪造参数生效 — 工具正常返回了结果,LLM 没有收到任何错误反馈,便认为自己的传参(包括伪造的参数)都被正确处理了
- LLM 对返回数据做出错误解读 — 例如 LLM 伪造了
"owners": ["admin"] 参数,工具返回了全量数据,LLM 误以为返回的是按 owner 过滤后的结果,从而对数据做出错误的判断和决策
- 错误链持续传播 — 由于没有任何校验拦截,LLM 不会意识到参数有误,在整个推理链路中持续基于错误的假设进行后续操作
现状分析
目前框架中设置 additionalProperties 的途径存在以下问题:
1. 注解方式注册的工具没有设置入口
使用 @Tool 注解生成的 schema 中,没有任何地方可以设置 additionalProperties,生成的 schema 默认允许额外参数。
2. Schema 方式注册的工具天然支持
使用 SchemaOnlyTool 或直接实现 AgentTool 接口时,getParameters() 返回的 schema 由用户自行构建,天然可以包含 additionalProperties: false。
3. SkillBox.registration().extendedModel() 方式无法生效(疑似 Bug)
尝试通过 SimpleExtendedModel 添加 additionalProperties: false:
skillBox.registration()
.tool(toolObject)
.extendedModel(new SimpleExtendedModel(
Map.of("additionalProperties", false), List.of()))
.register();
但 ExtendedModel.mergeWithBaseSchema() 的实现会将 getAdditionalProperties() 返回的 Map 全部合并进 schema 的 properties 字段中(见 ExtendedModel.java 第 70-72 行),导致 additionalProperties 被错误地放进 properties 里,变成了一个名为 "additionalProperties" 的属性定义,而不是顶层的 additionalProperties: false。这是否是一个 Bug,请维护者确认。
期望行为
在 @Tool 注解上支持声明 additionalProperties,默认 false(不允许额外参数),与 JSON Schema 语义一致:
// 默认严格模式,拒绝额外参数
@Tool(name = "query_business_data")
public String queryData(@ToolParam(name = "reqVo") ReqVo reqVo) { ... }
// 显式允许额外参数(宽松模式)
@Tool(name = "query_business_data", additionalProperties = true)
public String queryData(@ToolParam(name = "reqVo") ReqVo reqVo) { ... }
当 LLM 传入未定义的参数时,ToolValidator 基于 additionalProperties: false 校验失败,返回错误信息,LLM 收到明确的校验错误反馈后可以自我纠正。
生成的 schema 应递归地为所有 type=object 节点注入 additionalProperties: false,包括嵌套对象的 properties、items、oneOf/anyOf/allOf、$defs 等。仅在顶层加不够,因为 LLM 伪造的参数往往出现在嵌套对象内部。
参考实现
以下是我们验证过的代码改动,供维护者参考:
1. Tool.java — 新增 additionalProperties 属性
Class<? extends ToolResultConverter> converter() default DefaultToolResultConverter.class;
/**
* Whether to allow additional properties beyond those defined in the schema.
*
* <p>Corresponds to the {@code additionalProperties} keyword in JSON Schema.
* When set to {@code false} (default), any extra parameters passed by the LLM
* that are not defined in the schema will cause a validation error.
* When set to {@code true}, extra parameters are silently ignored.
*
* <p>This setting is applied recursively to all nested objects within the schema.
*
* @return {@code false} to disallow additional properties (default), {@code true} to allow
*/
boolean additionalProperties() default false;
2. ToolSchemaGenerator.java — 新增重载方法 + 递归注入
Map<String, Object> generateParameterSchema(Method method, Set<String> excludeParams) {
return generateParameterSchema(method, excludeParams, false);
}
@SuppressWarnings("unchecked")
Map<String, Object> generateParameterSchema(
Method method, Set<String> excludeParams, boolean additionalProperties) {
// ... 原有生成逻辑不变 ...
if (!additionalProperties) {
addAdditionalPropertiesFalseRecursively(schema);
}
return schema;
}
@SuppressWarnings("unchecked")
private void addAdditionalPropertiesFalseRecursively(Map<String, Object> schema) {
if (!"object".equals(schema.get("type"))) {
return;
}
schema.put("additionalProperties", false);
Object propsObj = schema.get("properties");
if (propsObj instanceof Map) {
Map<String, Object> props = (Map<String, Object>) propsObj;
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getValue() instanceof Map) {
addAdditionalPropertiesFalseRecursively((Map<String, Object>) entry.getValue());
}
}
}
Object itemsObj = schema.get("items");
if (itemsObj instanceof Map) {
addAdditionalPropertiesFalseRecursively((Map<String, Object>) itemsObj);
}
for (String key : new String[]{"oneOf", "anyOf", "allOf"}) {
Object listObj = schema.get(key);
if (listObj instanceof Iterable) {
for (Object item : (Iterable<?>) listObj) {
if (item instanceof Map) {
addAdditionalPropertiesFalseRecursively((Map<String, Object>) item);
}
}
}
}
Object defsObj = schema.get("$defs");
if (defsObj instanceof Map) {
Map<String, Object> defs = (Map<String, Object>) defsObj;
for (Map.Entry<String, Object> entry : defs.entrySet()) {
if (entry.getValue() instanceof Map) {
addAdditionalPropertiesFalseRecursively((Map<String, Object>) entry.getValue());
}
}
}
Object definitionsObj = schema.get("definitions");
if (definitionsObj instanceof Map) {
Map<String, Object> definitions = (Map<String, Object>) definitionsObj;
for (Map.Entry<String, Object> entry : definitions.entrySet()) {
if (entry.getValue() instanceof Map) {
addAdditionalPropertiesFalseRecursively((Map<String, Object>) entry.getValue());
}
}
}
}
3. Toolkit.java — 读取注解值传给 generator
// registerToolMethod() 中 getParameters() 的实现
@Override
public Map<String, Object> getParameters() {
Set<String> excludeParams =
presetParameters != null
? presetParameters.keySet()
: Collections.emptySet();
return schemaGenerator.generateParameterSchema(
method, excludeParams, toolAnnotation.additionalProperties());
}
影响范围
- 向后兼容:默认
false 即严格模式,这是更安全的默认行为。如果担心破坏现有用户,可以将默认值改为 true,让用户显式声明 additionalProperties = false 来启用严格模式
- 仅影响注解方式注册的工具:
SchemaOnlyTool 和程序式注册的 AgentTool 不受影响
问题背景
LLM 在调用工具时,有时会自行伪造(hallucinate)未定义的参数。由于当前框架对额外参数的处理规则是静默忽略,工具仍能正常返回结果,导致:
"owners": ["admin"]参数,工具返回了全量数据,LLM 误以为返回的是按 owner 过滤后的结果,从而对数据做出错误的判断和决策现状分析
目前框架中设置
additionalProperties的途径存在以下问题:1. 注解方式注册的工具没有设置入口
使用
@Tool注解生成的 schema 中,没有任何地方可以设置additionalProperties,生成的 schema 默认允许额外参数。2. Schema 方式注册的工具天然支持
使用
SchemaOnlyTool或直接实现AgentTool接口时,getParameters()返回的 schema 由用户自行构建,天然可以包含additionalProperties: false。3.
SkillBox.registration().extendedModel()方式无法生效(疑似 Bug)尝试通过
SimpleExtendedModel添加additionalProperties: false:但
ExtendedModel.mergeWithBaseSchema()的实现会将getAdditionalProperties()返回的 Map 全部合并进 schema 的properties字段中(见ExtendedModel.java第 70-72 行),导致additionalProperties被错误地放进properties里,变成了一个名为"additionalProperties"的属性定义,而不是顶层的additionalProperties: false。这是否是一个 Bug,请维护者确认。期望行为
在
@Tool注解上支持声明additionalProperties,默认false(不允许额外参数),与 JSON Schema 语义一致:当 LLM 传入未定义的参数时,
ToolValidator基于additionalProperties: false校验失败,返回错误信息,LLM 收到明确的校验错误反馈后可以自我纠正。生成的 schema 应递归地为所有
type=object节点注入additionalProperties: false,包括嵌套对象的properties、items、oneOf/anyOf/allOf、$defs等。仅在顶层加不够,因为 LLM 伪造的参数往往出现在嵌套对象内部。参考实现
以下是我们验证过的代码改动,供维护者参考:
1.
Tool.java— 新增additionalProperties属性2.
ToolSchemaGenerator.java— 新增重载方法 + 递归注入3.
Toolkit.java— 读取注解值传给 generator影响范围
false即严格模式,这是更安全的默认行为。如果担心破坏现有用户,可以将默认值改为true,让用户显式声明additionalProperties = false来启用严格模式SchemaOnlyTool和程序式注册的AgentTool不受影响