Spring AI工具明明注册了,模型为什么不调用?描述、Schema与Advisor完整排查
文章摘要
Spring AI项目中经常出现:工具已经通过@Tool或ToolCallback注册,ChatClient调用也没有报错,但模型始终直接回答、调用错误工具,或者生成根本不存在的参数。根因通常不在工具执行代码,而在工具是否绑定到实际ChatClient、描述是否清晰、输入Schema是否准确、模型是否支持Tool Calling、Prompt是否限制工具使用,以及ToolCallingAdvisor是否被错误关闭。本文给出从注册、Schema、模型到执行链的完整排查方法。
一、先确认工具有没有真正传给ChatClient
工具类:
public class OrderTools {
@Tool(
description = "根据订单号查询订单状态"
)
public OrderStatus queryOrder(
@ToolParam(
description = "完整订单号,例如A202607290001"
)
String orderId
) {
return orderService.query(orderId);
}
}
错误调用:
chatClient.prompt()
.user("查询订单A202607290001")
.call()
.content();
这里只是定义了工具类,并没有把它交给ChatClient。
正确方式:
chatClient.prompt()
.user("查询订单A202607290001")
.tools(new OrderTools(orderService))
.call()
.content();
或者在构建客户端时:
ChatClient client = ChatClient.builder(chatModel)
.defaultTools(
new OrderTools(orderService)
)
.build();
二、不要把“Spring Bean”误认为“自动工具”
在Spring AI 2.0中,普通:
Function
Supplier
Consumer
Bean不再自动通过名称解析为工具。
旧版:
@Bean
Function
queryOrder() {
return orderService::query;
}
并通过:
.toolNames("queryOrder")
调用的方式已经移除。
2.0应改成显式ToolCallback:
@Bean
ToolCallback queryOrderTool() {
return FunctionToolCallback
.builder(
"queryOrder",
orderService::query
)
.description(
"根据完整订单号查询订单状态"
)
.inputType(OrderRequest.class)
.build();
}
然后显式注入并传递:
chatClient.prompt()
.user(message)
.tools(queryOrderTool)
.call()
.content();
三、工具描述是否足够明确
模型依靠:
工具名称
工具描述
参数名称
参数描述
输入Schema
判断是否调用。
错误描述:
@Tool(description = "查询")
模型不知道它查询的是:
- 天气;
- 订单;
- 用户;
- 库存;
- 发票。
推荐:
@Tool(
description = """
根据完整订单号查询当前订单状态、支付状态和发货状态。
当用户询问某个具体订单的进度时使用。
不用于搜索商品,也不用于创建或取消订单。
"""
)
描述应说明:
什么时候用
能返回什么
什么时候不能用
四、工具名称不要过于相似
同时注册:
getOrder
queryOrder
findOrder
searchOrder
模型很难区分。
推荐按业务动作命名:
get_order_by_id
search_orders_by_customer
cancel_order
get_order_logistics
每个工具只承担一个清晰职责。
五、参数Schema是否准确
错误方法:
public OrderStatus queryOrder(
Map input
)
模型看到的是一个模糊对象,很难构造正确参数。
推荐使用明确Record:
public record QueryOrderRequest(
@ToolParam(
description = "订单号,格式为字母A加12位数字"
)
String orderId
) {
}
工具:
@Tool(
description = "根据订单号查询订单"
)
public OrderStatus queryOrder(
QueryOrderRequest request
) {
return orderService.query(
request.orderId()
);
}
六、必填和可选参数是否正确
如果参数允许为空,应显式声明可选。
例如:
public record SearchOrderRequest(
String customerId,
@Nullable
String status,
@Nullable
LocalDate startDate,
@Nullable
LocalDate endDate
) {
}
如果所有字段都被Schema标成必填,模型可能因为缺少用户未提供的信息而放弃调用工具。
反过来,如果订单号实际必填,却被声明为可选,模型可能生成:
{
"orderId": null
}
七、日期、枚举与数字格式是否容易生成
枚举:
public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
COMPLETED,
CANCELLED
}
参数描述应该告诉模型允许值。
日期建议使用:
YYYY-MM-DD
金额不要使用自由文本:
BigDecimal amount
String currency
而不是:
String amountText
复杂Schema可以使用JSON Schema条件能力,但也要控制深度,避免模型难以生成。
八、模型是否支持Tool Calling
不是所有模型、所有Provider版本都支持同等水平的Tool Calling。
检查:
- 当前模型是否支持工具调用;
- Provider接口是否启用;
- 模型名称是否正确;
- 兼容API是否完整实现Tool字段;
- 返回中是否出现tool_calls;
- 代理网关是否删除工具Schema;
- 流式模式是否支持工具增量事件。
先使用一个最小工具测试:
@Tool(description = "返回固定字符串PING")
public String ping() {
return "PONG";
}
Prompt:
必须调用ping工具,然后返回工具结果。
如果仍不调用,问题可能在模型或调用链,而不是业务工具。
九、Prompt是否暗示模型直接回答
System Prompt:
你必须快速回答用户问题,不得调用外部服务。
同时又注册工具,自然会冲突。
另一个常见Prompt:
尽量根据已有知识回答。
模型可能认为自己知道答案,不需要工具。
查询实时业务数据时,应明确:
订单、库存、价格、余额、权限和实时状态必须调用工具。
不得根据历史对话或常识推测。
十、是否关闭了自动ToolCallingAdvisor
Spring AI 2.0统一通过:
ToolCallingAdvisor
执行工具循环。
如果代码设置:
AdvisorParams
.toolCallingAdvisorAutoRegister(false)
则需要自己执行工具调用循环。
否则模型可能返回工具请求,但应用不会真正执行。
检查:
是否关闭自动注册
是否手工创建ToolCallingAdvisor
是否存在多个ToolAdvisor
Advisor顺序是否异常
一个ChatClient的调用链中只能存在一个负责工具循环的ToolAdvisor。
十一、工具是否被ToolSearch隐藏
工具数量很多时,可能启用:
spring.ai.chat.client.tool-search-advisor.enabled=true
此时并非所有工具定义都会直接发送给模型,而是先通过工具搜索逐步发现。
检查:
- 当前请求是否传入稳定Session ID;
- 工具索引是否构建成功;
- Regex、Lucene或Vector索引是否能命中;
- 工具描述是否包含用户常用词;
- 多租户Session是否串用;
- VectorStore是否正常;
- ToolSearch结果是否包含目标工具。
请求应提供:
.advisors(spec -> spec.param(
ChatMemory.CONVERSATION_ID,
conversationId
))
十二、工具返回类型是否可序列化
工具被调用但模型拿不到结果,常见原因:
- 返回Hibernate懒加载对象;
- 返回循环引用;
- 返回InputStream;
- 返回超大文件;
- 返回不可序列化类型;
- 返回对象包含异常字段;
- Jackson配置不一致。
不要直接返回数据库Entity。
推荐DTO:
public record OrderStatusResult(
String orderId,
String orderStatus,
String paymentStatus,
String shipmentStatus,
Instant updatedAt
) {
}
十三、工具异常是否被吞掉
错误实现:
try {
return orderService.query(orderId);
}
catch (Exception e) {
return null;
}
模型只得到:
null
不知道是:
- 订单不存在;
- 权限不足;
- 系统超时;
- 参数错误。
推荐结构化错误:
public record ToolResult(
boolean success,
String errorCode,
String message,
T data
) {
}
例如:
{
"success": false,
"errorCode": "ORDER_NOT_FOUND",
"message": "订单不存在",
"data": null
}
十四、工具是否需要用户确认
高风险工具不应由模型直接执行。
例如:
- 取消订单;
- 退款;
- 删除文件;
- 修改权限;
- 对外发邮件;
- 创建付款。
如果审批层拦截,日志可能看起来像“工具没有调用”。
正确流程:
模型提出工具调用
→ 参数校验
→ 风险识别
→ 返回待确认动作
→ 用户确认
→ 业务代码执行
排查时区分:
模型未选择工具
工具被策略层阻止
工具执行失败
工具结果未返回模型
十五、增加Tool Calling观测
记录:
request_id
conversation_id
model
available_tool_names
selected_tool_name
tool_arguments
schema_validation_result
policy_decision
execution_status
execution_duration_ms
tool_result_size
loop_iteration
final_status
敏感参数应脱敏,不要把完整支付信息写进日志。
十六、最小排查清单
□ 工具确实传给实际使用的ChatClient
□ 没有继续依赖旧版toolNames()
□ ToolCallback显式注册
□ 工具名称清晰且不重复
□ 描述包含使用和禁用场景
□ 输入参数有明确类型
□ 必填和可选定义正确
□ 当前模型支持Tool Calling
□ System Prompt没有禁止工具
□ ToolCallingAdvisor没有被误关闭
□ ToolSearch能够找到目标工具
□ 返回DTO可以序列化
□ 工具异常结构化返回
□ 高风险动作不是被审批层拦截
□ Trace能区分选择、执行和结果阶段
总结
Spring AI工具“不调用”通常集中在五类问题:
工具没有真正注册
工具描述和Schema不清晰
模型或Provider不支持
ToolCallingAdvisor链路被关闭
策略层或执行层阻止
先确认模型是否产生Tool Call,再沿着Advisor、参数校验、策略决策和执行结果逐层排查,比反复修改用户Prompt更有效。
延伸阅读
如果你正在关注企业级 AI 应用、Spring AI、RAG、Agent 与 MCP 工程化落地,欢迎访问 智元界:
https://www.zyentor.com/
智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。