AI图鉴

AI图鉴首页 › AI 资讯 › Spring AI 2.0 Tool Calling 实战:用一个订单查询跑通整

Spring AI 2.0 Tool Calling 实战:用一个订单查询跑通整个调用链

来源:掘金 · 发布时间:10-25 · 由 AI图鉴每小时自动聚合

用订单查询拆解 Spring AI 2.0 Tool Calling 的两轮模型调用、Java 工具执行与结果回填,结合 returnDirect 和 Advisor 日志理解工具循环。

做 AI 订单客服,最先遇到的问题往往很实际。

用户发来一条消息:

帮我查一下订单 A1024 到哪了。

订单数据存在业务系统里,模型手里没有这条记录。要给它提供一个可以调用的 Java 方法:

queryOrder("A1024")

这就是 Tool Calling。

我把本次查询的每一轮消息都打印出来了。结果就是:1 次用户请求,内部调用 2 次模型,执行 1 次 Java 工具。

这篇就用这个订单案例跑通 Spring AI 2.0 Tool Calling,看清楚两次模型调用之间发生了什么。

先看运行结果

我在 ToolCallingAdvisor 循环内外各放了一个日志 Advisor,运行之后得到了下面这条轨迹:

[outside-loop] before #1 messages=[user]

[inside-loop] before #1 messages=[user]

[model] call #1

[inside-loop] after #1 hasToolCalls=true

[tool] queryOrder(A1024), tenantId=shop-001

[inside-loop] before #2 messages=[user, assistant, tool]

[model] call #2

[model] received tool result: {"orderId":"A1024","status":"SHIPPED","carrier":"顺丰","trackingNo":"SF1234567890"}

[inside-loop] after #2 hasToolCalls=false

[outside-loop] after #1 hasToolCalls=false

[answer] 查到了:订单 A1024 已发货,承运商是顺丰,运单号 SF1234567890。

案例使用 Java 21、Spring Boot 4.0.8 和 Spring AI 2.0.1。为了固定两轮响应,测试使用 ScriptedOrderChatModel 驱动 Spring AI 调用链。本文结论覆盖框架执行过程;真实模型选择工具的质量还需要另外测试。

第一步:创建订单工具

项目引入 spring-boot-starter-webmvc、spring-ai-starter-model-openai 和 Spring AI 2.0.1 BOM 后,先创建一个普通的 Spring Bean:

@Component

public class OrderTools {

@Tool(

name = "queryOrder",

description = "根据订单号查询物流状态、承运商和运单号"

)

public OrderSnapshot queryOrder(

@ToolParam(description = "订单号,例如 A1024")

String orderId,

ToolContext toolContext) {

String tenantId = (String) toolContext

.getContext()

.get("tenantId");

System.out.printf(

"[tool] queryOrder(%s), tenantId=%s%n",

orderId,

tenantId);

return orderService.query(tenantId, orderId);

}

}

这里有三个重要的地方。

@Tool 让模型知道这个方法能做什么

Spring AI 会读取工具名称、描述和方法签名,然后生成包含 JSON Schema 的 Tool Definition。工具描述会影响模型的选择,所以要写清适用场景和返回数据。

@ToolParam 说明参数应该怎么填

模型会把用户的提问转成 JSON 参数。订单号、日期和金额格式,都可以写进参数说明里。

ToolContext 传递本地上下文

tenantId 来自当前登录会话,由应用确定。官方文档提到,ToolContext 数据不会发给模型。用户 ID、租户 ID、权限和 Trace ID 都可以放在里面。

第二步:把工具交给 ChatClient

然后创建一个订单客服接口:

@RestController

public class OrderController {

private final ChatClient chatClient;

OrderController(

ChatClient.Builder builder,

OrderTools orderTools) {

this.chatClient = builder

.defaultTools(orderTools)

.build();

}

@GetMapping("/orders/ask")

String ask(@RequestParam String message) {

return chatClient.prompt()

.system("""

你是订单客服。

涉及订单状态时必须调用查询工具,

根据工具返回结果回答。

""")

.user(message)

.toolContext(Map.of("tenantId", "shop-001"))

.call()

.content();

}

}

.defaultTools(orderTools) 会把订单工具注册到这个 ChatClient 的每次请求中。某次请求才需要工具时,也可以在 prompt() 后调用 .tools(orderTools)。

Spring AI 2.0 的 ChatClient 会自动注册 ToolCallingAdvisor,常规业务代码不需要手动创建它。

到这里,最小的 Tool Calling 已经接上了。

两次模型调用做了什么

第一次模型请求会包含:

SystemMessage + UserMessage + ToolDefinition

模型读到“订单 A1024”后,就会发出一个 tool call:

{

"name": "queryOrder",

"arguments": {

"orderId": "A1024"

}

}

此时还没有查询订单表。这个 JSON 表示模型建议应用执行 queryOrder。

ToolCallingAdvisor 检查到 hasToolCalls=true 后,就会把响应交给 ToolCallingManager。Manager 根据名称找到工具,把 JSON 转成 Java 参数,然后执行方法。

工具返回:

{

"orderId": "A1024",

"status": "SHIPPED",

"carrier": "顺丰",

"trackingNo": "SF1234567890"

}

Spring AI 同时把这两条消息追加到当前调用的历史中:

AssistantMessage(tool_call: queryOrder)

ToolResponseMessage(order status JSON)

所以,第二轮请求的消息类型是:

messages=[user, assistant, tool]

第二次调用会读取工具结果,把 JSON 整理成用户可以理解的客服回答。如果这一轮仍然产生 tool call,ToolCallingAdvisor 还会进入下一轮。模型返回不包含 tool call 的回复后,循环就结束了。

整条链路可以简化为下面六步:

用户问题

↓

第一次调用模型:选择工具,生成参数

↓

ToolCallingManager:执行本地 Java 方法

↓

ToolResponseMessage:追加工具结果

↓

第二次调用模型:组织最终回答

↓

没有新 tool call,结束

四个组件,记住它们各自的作用

Spring AI 2.0 把工具执行循环提到 Advisor 链上,日志、Memory、重试和观测代码都可以根据顺序进入循环内外。

ToolCallingAdvisor 的默认顺序是 Ordered.HIGHEST_PRECEDENCE + 300。本次测试中,它前面的日志执行了 1 次,后面的日志跟着模型请求执行了 2 次。

请求审计应该放在循环外。要检查每轮模型延迟、工具参数和失败原因时,日志就要进入循环,同时对订单号和工具结果做好脱敏。

一个有用的对照:returnDirect

有些工具已经返回了前端可以直接使用的 JSON,可以设置为:

@Tool(

name = "queryOrderDirect",

description = "返回订单原始状态",

returnDirect = true

)

String queryOrderDirect(String orderId) {

return """

{"orderId":"%s","status":"SHIPPED"}

""".formatted(orderId);

}

对照测试的结果是:

[return-direct] {"orderId":"A1024","status":"SHIPPED"}

这条路径只调用 1 次模型、执行 1 次工具,原始结果直接返回客户端。对话式客服还需要第二轮模型来组织自然语言回答。

回到开头

用户只问了这么一句,Spring AI 内部调用了两次模型。

现在可以给出确切的答案:

- 第一次调用负责选择工具并生成参数。

- ToolCallingManager 在应用中调用 Java 方法。

- ToolResponseMessage 将查询结果放入消息历史。

- 第二次调用读取工具结果,再整理好答案。

把这四步记下来,之后再看多工具 Agent、Tool Search、Chat Memory 和 MCP,就会有一条明了的主线。

下一篇文章会解决一个实际问题:当 Agent 里有几十个工具时,怎么用 ToolSearchToolCallingAdvisor 只把当前需要的工具发给模型。

本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。

相关 AI 工具

相关免费羊毛

同源资讯

阅读原文(juejin.cn)

标题、摘要与正文均来自该信源的公开页面,版权归原作者所有;本站自动聚合与翻译,仅作出处跳转。

← 返回 AI 资讯列表AI工具图鉴首页

返回 AI图鉴首页 · 提交工具 · 商务合作 · 关于本站 · 591 款 AI 工具分类导航 · 每日 AI 热门项目与动态