docs(knowledge): 添加测试知识库文档
新增 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 精确匹配的关键词
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
---
|
||||
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) { ... }
|
||||
```
|
||||
Reference in New Issue
Block a user