Spring AI工具明明注册了,模型为什么不调用?描述、Schema与Advisor完整排查

文章摘要

Spring AI项目中经常出现:工具已经通过@ToolToolCallback注册,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/

智元界将持续分享可运行的技术实战、架构设计、问题排查与企业应用案例。