新增 6 个知识库文档,用于测试 L0+L1 混合检索功能: API 类: - payment-errors.md - 支付网关错误码定义 领域知识类: - spring-ai-tool-best-practices.md - Spring AI 工具定义最佳实践 基础设施类: - redis-config.md - Redis 缓存配置指南 - mysql-connection-pool.md - MySQL 连接池配置 - flyway-best-practices.md - Flyway 数据库迁移最佳实践 故障排查类: - fault-diagnosis-process.md - 故障诊断流程规范 所有文档均包含: - 标准 frontmatter 元数据 (title, keywords, summary, category) - 实用配置示例和代码片段 - 支持 L0 精确匹配的关键词
260 lines
6.7 KiB
Markdown
260 lines
6.7 KiB
Markdown
---
|
||
title: Spring AI 工具定义最佳实践
|
||
keywords: [Spring AI, @Tool, 工具定义, Agent, 函数调用]
|
||
summary: 如何为 Spring AI Agent 定义高质量的工具(Tool),包括命名、描述、参数设计和错误处理
|
||
category: domain
|
||
---
|
||
|
||
# Spring AI 工具定义最佳实践
|
||
|
||
## 工具定义基础
|
||
|
||
### 基本注解
|
||
```java
|
||
@Component
|
||
public class MyTools {
|
||
|
||
@Tool(description = "查询用户信息。参数 userId: 用户ID(必填)")
|
||
public UserInfo getUserInfo(String userId) {
|
||
// 实现
|
||
}
|
||
}
|
||
```
|
||
|
||
### 关键要素
|
||
1. **@Component** - 让 Spring 扫描到
|
||
2. **@Tool** - 标记为 Agent 可调用的工具
|
||
3. **description** - 告诉 Agent 这个工具做什么
|
||
|
||
## 描述(Description)编写规范
|
||
|
||
### 好的描述
|
||
```java
|
||
@Tool(description = "查询知识库文档。优先精确匹配关键词,未命中或多个匹配时自动补充语义相关片段。" +
|
||
"参数 query: 查询关键词,例如 'ERR_TIMEOUT'、'支付网关超时'")
|
||
public LookupResult lookupKnowledge(String query) { ... }
|
||
```
|
||
|
||
**要点**:
|
||
- ✅ 说明工具用途(查询知识库)
|
||
- ✅ 说明工作机制(精确匹配 → 语义补充)
|
||
- ✅ 说明参数含义和示例
|
||
|
||
### 差的描述
|
||
```java
|
||
@Tool(description = "查询文档") // ❌ 太简略
|
||
public LookupResult lookup(String q) { ... }
|
||
```
|
||
|
||
## 参数设计
|
||
|
||
### 参数命名
|
||
```java
|
||
// ✅ 好的命名 - 语义清晰
|
||
public Result search(String query, int maxResults, String category)
|
||
|
||
// ❌ 差的命名 - 缩写难懂
|
||
public Result search(String q, int max, String cat)
|
||
```
|
||
|
||
### 参数类型
|
||
```java
|
||
// ✅ 使用明确的类型
|
||
public UserInfo getUser(String userId)
|
||
public List<Order> getOrders(LocalDate startDate, LocalDate endDate)
|
||
|
||
// ❌ 使用 Object 或 Map
|
||
public Object getUser(Map<String, Object> params) // Agent 不知道传什么
|
||
```
|
||
|
||
### 可选参数处理
|
||
```java
|
||
@Tool(description = "查询订单。参数 status: 订单状态(可选,不传则查所有)")
|
||
public List<Order> getOrders(
|
||
@Nullable String status // 使用 @Nullable 标注
|
||
) {
|
||
if (status == null) {
|
||
return orderRepository.findAll();
|
||
}
|
||
return orderRepository.findByStatus(status);
|
||
}
|
||
```
|
||
|
||
## 返回值设计
|
||
|
||
### 使用明确的返回类型
|
||
```java
|
||
// ✅ 好的返回类型
|
||
public class LookupResult {
|
||
private boolean found;
|
||
private PrimaryResult primary;
|
||
private SupplementResult supplement;
|
||
}
|
||
|
||
// ❌ 返回 String - Agent 难以解析
|
||
public String lookup(String query) {
|
||
return "找到文档: xxx"; // 非结构化
|
||
}
|
||
```
|
||
|
||
### 返回错误信息
|
||
```java
|
||
public LookupResult lookup(String query) {
|
||
if (query == null || query.isEmpty()) {
|
||
return LookupResult.builder()
|
||
.found(false)
|
||
.error("查询关键词不能为空")
|
||
.build();
|
||
}
|
||
// 正常逻辑
|
||
}
|
||
```
|
||
|
||
## 错误处理
|
||
|
||
### 优雅降级
|
||
```java
|
||
@Tool(description = "查询用户信息")
|
||
public UserInfo getUser(String userId) {
|
||
try {
|
||
return userService.findById(userId);
|
||
} catch (UserNotFoundException e) {
|
||
log.warn("用户不存在: userId={}", userId);
|
||
return UserInfo.notFound(userId); // 返回特殊对象,不抛异常
|
||
} catch (Exception e) {
|
||
log.error("查询用户失败: userId={}", userId, e);
|
||
return UserInfo.error("系统错误,请稍后重试");
|
||
}
|
||
}
|
||
```
|
||
|
||
### 不要抛出未捕获的异常
|
||
```java
|
||
// ❌ 不要这样做
|
||
@Tool(description = "查询用户")
|
||
public UserInfo getUser(String userId) {
|
||
return userService.findById(userId); // 可能抛出异常,Agent 无法处理
|
||
}
|
||
```
|
||
|
||
## 可观测性
|
||
|
||
### 日志规范
|
||
```java
|
||
@Tool(description = "查询订单")
|
||
public List<Order> getOrders(String userId) {
|
||
String requestId = UUID.randomUUID().toString().substring(0, 8);
|
||
long startTime = System.currentTimeMillis();
|
||
|
||
log.info("[{}] 收到订单查询请求: userId={}", requestId, userId);
|
||
|
||
try {
|
||
List<Order> orders = orderService.findByUserId(userId);
|
||
long elapsed = System.currentTimeMillis() - startTime;
|
||
log.info("[{}] 查询完成: count={}, time={}ms", requestId, orders.size(), elapsed);
|
||
return orders;
|
||
} catch (Exception e) {
|
||
log.error("[{}] 查询失败: userId={}", requestId, userId, e);
|
||
throw e;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 性能优化
|
||
|
||
### 设置合理的超时
|
||
```java
|
||
@Tool(description = "查询大数据集")
|
||
public DataResult queryBigData(String query) {
|
||
// 设置超时保护
|
||
return CompletableFuture
|
||
.supplyAsync(() -> heavyQuery(query))
|
||
.orTimeout(5, TimeUnit.SECONDS)
|
||
.exceptionally(ex -> DataResult.timeout())
|
||
.join();
|
||
}
|
||
```
|
||
|
||
### 避免返回超大数据
|
||
```java
|
||
// ✅ 分页或限制数量
|
||
@Tool(description = "查询用户列表(最多返回 100 条)")
|
||
public List<User> listUsers(int page, int size) {
|
||
size = Math.min(size, 100); // 强制上限
|
||
return userService.findAll(PageRequest.of(page, size));
|
||
}
|
||
|
||
// ❌ 返回全量数据
|
||
public List<User> listAllUsers() {
|
||
return userService.findAll(); // 可能几万条
|
||
}
|
||
```
|
||
|
||
## 工具组合示例
|
||
|
||
### 查询 + 操作的组合
|
||
```java
|
||
@Component
|
||
public class OrderTools {
|
||
|
||
@Tool(description = "查询订单详情")
|
||
public OrderDetail getOrder(String orderId) { ... }
|
||
|
||
@Tool(description = "取消订单")
|
||
public CancelResult cancelOrder(String orderId, String reason) { ... }
|
||
|
||
@Tool(description = "申请退款")
|
||
public RefundResult refund(String orderId, Double amount) { ... }
|
||
}
|
||
```
|
||
|
||
**Agent 使用场景**:
|
||
1. 用户:"帮我查一下订单 12345"
|
||
2. Agent 调用 `getOrder("12345")`
|
||
3. 用户:"帮我取消这个订单"
|
||
4. Agent 调用 `cancelOrder("12345", "用户主动取消")`
|
||
|
||
## 常见陷阱
|
||
|
||
### ❌ 工具做太多事
|
||
```java
|
||
// 不要把整个业务流程塞进一个工具
|
||
@Tool(description = "处理订单")
|
||
public void processOrder(String orderId) {
|
||
// 查询订单
|
||
// 验证库存
|
||
// 扣减库存
|
||
// 创建物流单
|
||
// 发送通知
|
||
// ... 太多步骤,Agent 无法介入
|
||
}
|
||
```
|
||
|
||
### ✅ 拆分成多个工具
|
||
```java
|
||
@Tool(description = "查询订单")
|
||
public Order getOrder(String orderId) { ... }
|
||
|
||
@Tool(description = "验证库存")
|
||
public StockResult checkStock(String productId, int quantity) { ... }
|
||
|
||
@Tool(description = "创建物流单")
|
||
public ShipmentResult createShipment(String orderId) { ... }
|
||
```
|
||
|
||
### ❌ 描述不准确
|
||
```java
|
||
@Tool(description = "查询用户")
|
||
public UserInfo getUser(String query) {
|
||
// 实际上支持按 userId、email、手机号查询
|
||
// 但描述没说清楚,Agent 不知道
|
||
}
|
||
```
|
||
|
||
### ✅ 描述完整
|
||
```java
|
||
@Tool(description = "查询用户信息。支持按 userId、email 或手机号查询。" +
|
||
"参数 query: 用户ID、邮箱或手机号")
|
||
public UserInfo getUser(String query) { ... }
|
||
```
|