[Docs] quick-start 示例照抄即复现 400:工具 inputType 使用 String.class
Is there an existing issue for this?
- I have searched the existing issues
Current Behavior
照抄官方 quick-start 的「构建一个基础 Agent」小节(FunctionToolCallback 使用 .inputType(String.class)),第二轮请求返回 400,工具调用链路中断。
第一轮请求正常;第二轮(回传模型返回的 tool_call 时)失败:
HTTP 400 - {"request_id":"330f8d4d-77f5-9671-86ba-a1b223fbb175","code":"InvalidParameter",
"message":"<400> InternalError.Algo.InvalidParameter: The \"function.arguments\" parameter of the code model must be in JSON format."}同一页「构建一个真实的 Agent」步骤 2 的两个工具(WeatherForLocationTool / UserLocationTool)写法相同,同样会失败。
Expected Behavior
正常返回天气结果,且 get_weather 被真实调用(ReAct 循环能正常走完两轮)。
Steps To Reproduce
- 打开 https://java2ai.com/docs/quick-start 的「构建一个基础 Agent」小节,原样复制该小节代码;
- 执行
agent.call("what is the weather in San Francisco"); - 第二轮请求返回上述 400。
补充:以下最小验证不需要 API Key、不联网,可直接确认根因(约 30 秒):
ToolCallback tool = FunctionToolCallback.builder("get_weather", new WeatherTool())
.description("Get weather for a given city")
.inputType(String.class)
.build();
System.out.println(tool.getToolDefinition().inputSchema());
// 输出: { "type" : "string", "additionalProperties" : false }把 inputType 换成 record 类型后,输出为 "type" : "object",且 400 消失。
Environment
Spring AI Alibaba version(s): 1.1.2.0
| 项 | 值 |
| ----------------------------------- | ----------------------------------------------------------- |
| JDK | 21.0.10 |
| Spring Boot | 3.5.5 |
| spring-ai-alibaba-agent-framework | 1.1.2.0 |
| spring-ai-alibaba-starter-dashscope | 1.1.2.0 |
| 模型 | 默认(未显式指定 model) |
| 文档页 / 源码 | https://java2ai.com/docs/quick-start ・ docs/quick-start.md |Debug logs
失败时的日志(第二轮):
WARN o.s.a.r.a.SpringAiRetryAutoConfiguration : Retry error. Retry count: 1, Exception: HTTP 400 -
{"request_id":"330f8d4d-77f5-9671-86ba-a1b223fbb175","code":"InvalidParameter",
"message":"<400> InternalError.Algo.InvalidParameter: The \"function.arguments\" parameter of the code model must be in JSON format."}
org.springframework.ai.retry.NonTransientAiException: HTTP 400 - {"request_id":"330f8d4d-77f5-9671-86ba-a1b223fbb175", ...}改为 inputType(WeatherRequest.class) 后,工具被真实调用:
DEBUG o.s.a.t.function.FunctionToolCallback : Starting execution of tool: get_weather
DEBUG o.s.a.t.function.FunctionToolCallback : Successful execution of tool: get_weather
DEBUG o.s.a.t.e.DefaultToolCallResultConverter : Converting tool result to JSON.Anything else?
根因
FunctionToolCallback.Builder.build() 只依据 inputType 生成 schema,不读取 apply() 的方法签名:
.inputSchema(StringUtils.hasText(this.inputSchema) ? this.inputSchema
: JsonSchemaGenerator.generateForType(this.inputType)) // ← 只看 inputType因此两种写法的产出完全不同(以下均在本机通过 toolCallback.getToolDefinition().inputSchema() 实测打印):
inputType(String.class) → { "type" : "string", "additionalProperties" : false }
inputType(WeatherRequest.class) → { "type" : "object",
"properties" : { "city" : { "type" : "string", ... } },
"required" : [ "city" ], "additionalProperties" : false }DashScope 要求 function 的 parameters 为 object。schema 为 string 时模型无法按对象结构产参,
回传时被服务端判定为非法 JSON —— 即上面这个 400。
一点背景(供参考):方法式工具(
@Tool注解的方法 /MethodToolCallback)走的是JsonSchemaGenerator.generateForMethodInput(),它始终先写"type": "object"再填properties, 所以「只有一个 String 参数就直接传String.class」在方法式工具上不会出问题。
建议修复
// 工具的入参必须是一个 JSON 对象(record / POJO),不能是 String
public record WeatherRequest(
@ToolParam(description = "The city name") String city) {
}
// 定义天气查询工具
public class WeatherTool implements BiFunction<WeatherRequest, ToolContext, String> {
@Override
public String apply(WeatherRequest request, ToolContext toolContext) {
return "It's always sunny in " + request.city() + "!";
}
}
ToolCallback weatherTool = FunctionToolCallback.builder("get_weather", new WeatherTool())
.description("Get weather for a given city")
.inputType(WeatherRequest.class)
.build();注意:不能只把 inputSchema 手写成 object 而保留 inputType(String.class)。框架会用 inputType
反序列化模型回传的 arguments:
I request = JsonParser.fromJson(toolInput, this.toolInputType);两者必须一致,否则只是把问题挪到另一个地方。
同一页面的其它问题(次要,可随 PR 一并处理)
- 「构建一个真实的 Agent」步骤 2 的两个工具是同一个 bug:
WeatherForLocationTool与UserLocationTool都是BiFunction<String, ToolContext, String>+.inputType(String.class)。 @ToolParam目前放在不生效的位置:它写在apply()的方法参数上,而 schema 完全由inputType决定,这个 description 不会进入发给模型的 schema(实测:inputType(String.class)生成的 schema 里没有任何description字段)。若改用 record,应写在 record 组件上。SYSTEM_PROMPT中的工具名与实际注册名不一致:提示词里是get_weather_for_location/get_user_location,实际注册的是getWeatherForLocation/getUserLocation。- 依赖版本在同一页面内不一致:「添加依赖」小节是
1.1.2.0,步骤 3 是1.1.2.1。 (经核对,1.1.2.1是 Maven 中央仓库中的有效版本,统一即可。) HumanInTheLoopHook示例中的工具名.approvalOn("getWeatherTool", ...)在本文档中不存在 (文档注册的是getWeatherForLocation/getUserLocation)。若该参数接受的是 Bean 名而非工具名, 请忽略此条。- 文案:步骤 1 列表中的
System Prom应为System Prompt。
相关 issue / PR(同一根因,供参考)
- #4942(PR,open):
fix: use object input schema for ls and glob filesystem tools—— 框架自身也在把ListFilesTool/GlobTool从inputType(String.class)改为对象类型, PR 描述里明确写到「inputType(String.class)produces a top-level JSON schema withtype: "string", providers ... returning HTTP 400」,并说明这与ReadFileTool/WriteFileTool/EditFileTool/GrepTool的既有写法保持一致。也就是说,框架内部已经认为这种写法不正确,但文档示例仍在教它。 - #4084(closed):同样照抄 quick-start 的示例代码,报错为
Conversion from JSON to java.lang.String failed(FunctionToolCallback.call→JsonParser.fromJson), 评论区已有同学给出与本文一致的修复方向(改用 Request DTO)。 - #4565(open):同一个 400 报错,定位到
ListFilesTool的.inputType(String.class), 同样通过包一层 record 解决。 - #4533(closed):同一个 400 报错,示例写法同为
.inputType(String.class)(ChatModel+withToolCallbacks路径)。
这也正是我在文档层面单独提这个 issue 的原因:代码侧已经在修,而文档示例会持续产生同样的报告。
补充
如需要,我可以按上述内容向 spring-ai-alibaba/website 提交一个 PR(改动集中在 docs/quick-start.md)。
如果维护者希望保持「基础 Agent」示例的极简风格,也可以只加一句说明、不改结构:
注意:
FunctionToolCallback的inputType必须是 JSON 对象类型(record / POJO), 不能传String.class,否则 DashScope 会返回 400。
Source: alibaba/spring-ai-alibaba