一、Tool Calling 是什么?
LLM 擅长对话,但它对你的数据一无所知——不知道你的库存、不知道你的 API、不知道你的系统如何运作。
Tool Calling 让 LLM 能够"调用"你的函数:
"我无法直接回答这个问题,但我想调用这个函数,参数是这些。"
你执行函数,把结果返回给模型,模型将其整合到最终回答中。
二、REST API 实现 Tool Calling(5步完整流程)
场景:用户问"AirPods Pro 有货吗?"
Step 1:发送 Prompt + 工具定义
POST /v1/chat/completions
{
"model": "gpt-4o",
"messages": [
{ "role": "user", "content": "AirPods Pro 有货吗?" }
],
"tools": [
{
"type": "function",
"function": {
"name": "findProductByName",
"description": "根据名称或描述查找商品",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "商品名称或关键词"
}
},
"required": ["name"]
}
}
}
],
"tool_choice": "auto"
}
关键点:
tools数组定义可用工具tool_choice可设为auto(自动选择)、none(不调用)、或强制指定
Step 2:模型响应工具调用请求
{
"choices": [
{
"message": {
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "findProductByName",
"arguments": "{\"name\":\"AirPods Pro\"}"
}
}
]
}
}
]
}
模型告诉你:请运行 findProductByName 函数,参数是 {"name": "AirPods Pro"}。
Step 3:执行函数
// 你的业务逻辑
List<Product> result = productService.findByName("AirPods Pro");
// 序列化为 JSON
{
"name": "AirPods Pro",
"price": 249,
"stock": 5
}
Step 4:将工具结果返回给模型
{
"messages": [
{ "role": "user", "content": "AirPods Pro 有货吗?" },
{
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "findProductByName",
"arguments": "{\"name\":\"AirPods Pro\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"name\":\"AirPods Pro\",\"price\":249,\"stock\":5}"
}
],
"tools": [ /* 同样的工具定义 */ ],
"tool_choice": "auto"
}
注意:必须保持完整的消息历史,包括用户的原始问题和 assistant 的工具调用请求。
Step 5:模型返回最终答案
{
"choices": [
{
"message": {
"content": "AirPods Pro 有货,售价 $249,库存 5 件。"
}
}
]
}
完整流程图:
用户提问 → 模型判断需调用工具 → 返回工具调用请求
↓
执行函数 → 返回结果 → 模型整合 → 最终回答
三、手动实现的痛点
如果你用 REST API 手动实现 Tool Calling,需要处理:
| 痛点 | 说明 |
|---|---|
| JSON Schema 编写 | 每个工具都要写参数 Schema |
| tool_call_id 追踪 | 多工具调用时要正确关联 ID |
| 参数解析绑定 | 从 JSON 字符串解析参数 |
| 响应序列化 | 将结果转回 JSON |
| 多工具编排 | 并行/顺序调用的复杂逻辑 |
| 会话状态管理 | 维护完整的消息历史 |
| 错误处理 | 工具执行失败的重试逻辑 |
这些工作繁琐且易出错,Spring AI 可以帮你搞定。
四、Spring AI 实现:零胶水代码
4.1 定义工具方法
@Component
public class ProductTools {
private final ProductService productService;
public ProductTools(ProductService productService) {
this.productService = productService;
}
@Tool(description = "根据名称或描述查找商品")
public String findProductByName(
@ToolParam(description = "商品名称或关键词", required = true) String name
) {
List<Product> products = productService.findByName(name);
return toJson(products);
}
private String toJson(Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (JsonProcessingException e) {
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
}
关键注解:
@Tool:标记方法为可调用工具@ToolParam:描述参数,支持required、description
4.2 调用 ChatClient
@RestController
@RequestMapping("/api")
public class ChatController {
private final ChatModel chatModel;
private final ProductTools productTools;
public ChatController(ChatModel chatModel, ProductTools productTools) {
this.chatModel = chatModel;
this.productTools = productTools;
}
@PostMapping("/chat")
public ChatResponse chat(@RequestBody ChatRequest request) {
String answer = ChatClient.builder(chatModel)
.defaultTools(productTools)
.build()
.prompt()
.user(request.question())
.call()
.content();
return new ChatResponse(request.question(), answer);
}
}
4.3 Spring AI 自动处理的事项
| 功能 | 说明 |
|---|---|
| ✅ 工具 Schema 生成 | 自动从 @Tool 注解生成 JSON Schema |
| ✅ 参数绑定 | 自动解析 JSON 并绑定到方法参数 |
| ✅ tool_call_id 映射 | 自动关联请求和响应 |
| ✅ 消息状态管理 | 自动维护对话历史 |
| ✅ 并行工具编排 | 自动处理多工具并行调用 |
| ✅ 顺序工具路由 | 支持链式工具调用 |
| ✅ 可观测性 | 集成 Micrometer 指标 |
五、进阶:多工具调用
当用户问:"AirPods Pro 和 Galaxy Buds 哪个便宜?"
模型会并行发起多个工具调用:
{
"tool_calls": [
{
"id": "call_001",
"function": { "name": "findProductByName", "arguments": "{\"name\":\"AirPods Pro\"}" }
},
{
"id": "call_002",
"function": { "name": "findProductByName", "arguments": "{\"name\":\"Galaxy Buds\"}" }
}
]
}
Spring AI 会自动并行执行这两个工具,然后将结果一起返回给模型。
六、进阶:顺序推理(SQL 生成场景)
更复杂的场景:用户问"库存少于 10 的商品有哪些?"
模型可能需要顺序调用多个工具:
1. listTables() → 获取数据库表列表
2. getTableSchema("products") → 获取表结构
3. executeSQL("SELECT * FROM products WHERE stock < 10") → 执行查询
这需要系统提示引导:
@SystemMessage("""
你是一个数据库助手。
当用户询问数据时,按以下步骤操作:
1. 先调用 listTables 了解数据库结构
2. 再调用 getTableSchema 了解表结构
3. 最后调用 executeSQL 执行查询
""")
七、坑点与最佳实践
7.1 工具描述要精准
// ❌ 不好的描述
@Tool(description = "查找商品")
public String findProduct(String name) { ... }
// ✅ 好的描述
@Tool(description = "根据商品名称或描述模糊查找,返回商品列表(包含价格、库存)")
public String findProductByName(
@ToolParam(description = "商品名称、型号或关键词,支持模糊匹配") String name
) { ... }
原因:模型根据描述决定是否调用工具,描述越精准,调用越准确。
7.2 返回值必须是 String
// ❌ 编译通过,但运行时会出问题
@Tool
public List<Product> findProduct(String name) { ... }
// ✅ 正确做法:返回 JSON 字符串
@Tool
public String findProduct(String name) {
List<Product> products = productService.findByName(name);
return objectMapper.writeValueAsString(products);
}
7.3 处理工具执行错误
@Tool(description = "查询商品库存")
public String checkStock(@ToolParam(description = "商品ID") String productId) {
try {
Product product = productService.findById(productId);
if (product == null) {
return "{\"error\": \"商品不存在\"}";
}
return objectMapper.writeValueAsString(product);
} catch (Exception e) {
return "{\"error\": \"" + e.getMessage() + "\"}";
}
}
模型能够理解错误信息并给出友好提示。
7.4 控制工具调用次数
ChatResponse response = ChatClient.builder(chatModel)
.defaultTools(productTools)
.build()
.prompt()
.user(question)
.call()
.chatResponse();
// 检查调用次数
List<AssistantMessage.ToolCall> toolCalls =
response.getResult().getOutput().getToolCalls();
System.out.println("工具调用次数: " + toolCalls.size());
八、MCP 扩展:工具即服务
如果你的工具需要被其他 Agent 或前端调用,可以用 MCP(Model Context Protocol):
8.1 添加依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
8.2 配置 MCP Server
spring:
ai:
mcp:
server:
type: sse # 或 stdio
效果:你的 @Tool 方法自动成为 MCP 兼容端点,无需额外代码!
同一个 @Tool 方法 → LLM 可调用
→ MCP 客户端可调用
→ 其他 Agent 可调用
九、完整代码示例
项目结构
src/main/java/com/xalgocapital/toolaicall/
├── ToolAiCallApplication.java
├── config/
│ └── AiConfig.java
├── controller/
│ └── ChatController.java
├── service/
│ └── ProductService.java
├── tools/
│ └── ProductTools.java
├── model/
│ ├── Product.java
│ ├── ChatRequest.java
│ └── ChatResponse.java
└── repository/
└── ProductRepository.java
pom.xml 关键依赖
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- 可选:MCP Server -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
</dependencies>
application.yml
spring:
application:
name: tool-ai-call
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com
chat:
options:
model: gpt-4o
temperature: 0.7
# MCP Server(可选)
mcp:
server:
type: sse
十、验证步骤
10.1 启动应用
./mvnw spring-boot:run
10.2 测试工具调用
curl -X POST http://localhost:8080/api/chat \
-H "Content-Type: application/json" \
-d '{"question": "AirPods Pro 有货吗?"}'
预期输出:
{
"question": "AirPods Pro 有货吗?",
"answer": "AirPods Pro 有货,售价 $249,库存 5 件。"
}
10.3 检查日志
[INFO] Tool calling: findProductByName({"name": "AirPods Pro"})
[INFO] Tool result: {"name":"AirPods Pro","price":249,"stock":5}
十一、总结
| 方式 | 代码量 | 灵活性 | 维护成本 |
|---|---|---|---|
| REST API 手动实现 | 高 | 最高 | 高 |
Spring AI @Tool | 低 | 高 | 低 |
| Spring AI + MCP | 低 | 最高 | 低 |
推荐:
- 简单场景:直接用
@Tool注解 - 需要跨 Agent 共享:启用 MCP
参考资料
本文基于 Spring AI 2.0.0-M3 编写,适用于 Spring Boot 4.0.x