本章导读
本章将深入讲解AgentScope的工具系统。工具是Agent与外部世界交互的桥梁,使Agent能够查询数据库、调用API、执行计算、操作文件等。掌握工具系统是构建实用Agent的关键。
- 理解工具系统的设计理念
- 掌握@Tool注解的使用方法
- 学会工具注册和管理
- 了解工具调用的完整流程
- 掌握工具开发的最佳实践
- 学会构建生产级工具系统
6.1 工具系统设计理念
6.1.1 什么是工具
在AgentScope中,工具(Tool) 是Agent可以调用的函数,用于执行特定的操作或获取信息。
typescript
工具的本质:
┌────────────────────────────────────┐
│ Java方法 + @Tool注解 │
│ ↓ │
│ ToolSchema(工具的JSON描述) │
│ ↓ │
│ 发送给LLM │
│ ↓ │
│ LLM决定是否调用 │
│ ↓ │
│ Agent执行工具方法 │
│ ↓ │
│ 返回结果给LLM │
└────────────────────────────────────┘
工具的作用:
makefile
没有工具的Agent:
用户: 北京今天的天气怎么样?
Agent: 抱歉,我无法获取实时天气信息。
有工具的Agent:
用户: 北京今天的天气怎么样?
Agent: [调用 get_weather(city="北京")]
→ 获得结果:晴天,15-25°C
→ 回复:北京今天晴天,温度在15-25°C之间。
6.1.2 工具系统的架构
markdown
工具系统的核心组件:
1. @Tool注解
└─ 标记方法为工具
└─ 定义工具名称和描述
2. @ToolParam注解
└─ 标记参数
└─ 定义参数名称和描述
3. Toolkit(工具箱)
└─ 管理所有工具
└─ 注册和查找工具
└─ 生成ToolSchema
4. AgentTool(工具包装类)
└─ 封装Java方法
└─ 支持反射调用
└─ 参数转换和验证
5. ToolExecutor(工具执行器)
└─ 执行工具调用
└─ 处理异常
└─ 生成ToolResultBlock
6.2 @Tool注解的使用
6.2.1 定义简单工具
java
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
public class BasicTools {
/**
* 最简单的工具示例
*/
@Tool(
name = "get_current_time",
description = "Get the current time in specified timezone"
)
public String getCurrentTime(
@ToolParam(
name = "timezone",
description = "Timezone name (e.g. Asia/Shanghai, America/New_York)"
) String timezone
) {
try {
ZoneId zoneId = ZoneId.of(timezone);
ZonedDateTime now = ZonedDateTime.now(zoneId);
return now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
} catch (Exception e) {
return "Error: Invalid timezone " + timezone;
}
}
/**
* 带多个参数的工具
*/
@Tool(
name = "calculate",
description = "Perform basic arithmetic calculation"
)
public String calculate(
@ToolParam(name = "a", description = "First number") Double a,
@ToolParam(name = "b", description = "Second number") Double b,
@ToolParam(name = "operation", description = "Operation: add, subtract, multiply, divide") String operation
) {
try {
double result = switch (operation.toLowerCase()) {
case "add" -> a + b;
case "subtract" -> a - b;
case "multiply" -> a * b;
case "divide" -> {
if (b == 0) {
throw new IllegalArgumentException("Cannot divide by zero");
}
yield a / b;
}
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
};
return String.format("%.2f %s %.2f = %.2f", a, operation, b, result);
} catch (Exception e) {
return "Calculation error: " + e.getMessage();
}
}
}
6.2.2 注册和使用工具
java
import io.agentscope.core.ReActAgent;
import io.agentscope.core.tool.Toolkit;
public class ToolRegistrationExample {
public static void main(String[] args) {
// 步骤1:创建Toolkit
Toolkit toolkit = new Toolkit();
// 步骤2:注册工具对象(自动扫描@Tool注解)
toolkit.registerObject(new BasicTools());
// 步骤3:创建Agent并关联Toolkit
ReActAgent agent = ReActAgent.builder()
.name("ToolAgent")
.model(model)
.toolkit(toolkit) // 关联工具箱
.build();
// 步骤4:使用Agent(Agent会自动调用工具)
Msg response = agent.call("现在上海是几点?").block();
System.out.println(response.getTextContent());
// 执行过程:
// Agent思考: "用户想知道上海的时间,我应该调用get_current_time工具"
// Agent调用: get_current_time(timezone="Asia/Shanghai")
// 工具返回: "2024-12-30 14:30:00"
// Agent回复: "现在上海时间是2024年12月30日14点30分。"
}
}
6.2.3 @Tool注解参数详解
java
/**
* @Tool注解的完整参数
*/
@Tool(
name = "tool_name", // 必需:工具名称(snake_case)
description = "Tool description" // 必需:工具描述(100字以内)
)
public String toolMethod(
@ToolParam(
name = "param_name", // 必需:参数名称(snake_case)
description = "Param description", // 必需:参数描述
required = true // 可选:是否必需(默认true)
) String paramName
) {
// 方法实现
return "result";
}
命名规范:
sql
✓ 正确的命名:
- 工具名: get_user_info, query_database, send_email
- 参数名: user_id, start_date, max_count
✗ 错误的命名:
- 工具名: getUserInfo(不要用camelCase)
- 参数名: userId(不要用camelCase)
- 工具名: get-user-info(不要用kebab-case)
描述规范:
markdown
✓ 好的描述:
"Query user information from database by user ID"
- 清晰说明功能
- 说明参数的作用和格式
✗ 差的描述:
"Get info"
- 太模糊
- 没有说明参数格式
6.3 工具调用流程
6.3.1 完整的工具调用流程
makefile
完整流程(从定义到执行):
━━━ 步骤1:工具定义 ━━━
开发者编写:
@Tool(name = "get_weather", description = "...")
public String getWeather(
@ToolParam(name = "city", ...) String city
)
━━━ 步骤2:工具注册 ━━━
toolkit.registerObject(new WeatherTools());
↓
扫描@Tool注解
↓
生成ToolSchema (JSON格式的工具描述):
{
"name": "get_weather",
"description": "Get weather information for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
}
━━━ 步骤3:发送给LLM ━━━
Agent调用Model时携带ToolSchema:
messages: [...]
tools: [ToolSchema1, ToolSchema2, ...]
↓
LLM看到可用的工具列表
━━━ 步骤4:LLM决策 ━━━
LLM分析用户请求:
"北京今天的天气怎么样?"
↓
LLM决定调用get_weather工具:
{
"name": "get_weather",
"arguments": {"city": "北京"}
}
↓
返回ToolUseBlock
━━━ 步骤5:Agent执行工具 ━━━
提取ToolUseBlock:
name: get_weather
arguments: {city: "北京"}
↓
查找工具实现:
WeatherTools.getWeather
↓
参数转换:
Map<String, Object> → String city = "北京"
↓
反射调用:
Object result = method.invoke(toolInstance, "北京")
↓
获得结果:
"北京今天晴天,15-25°C"
━━━ 步骤6:返回结果 ━━━
生成ToolResultBlock:
{
"id": "call_123",
"name": "get_weather",
"content": "北京今天晴天,15-25°C",
"isError": false
}
↓
添加到Memory
↓
进入下一轮推理循环
━━━ 步骤7:LLM生成最终回复 ━━━
LLM看到工具结果:
"北京今天晴天,15-25°C"
↓
生成用户友好的回复:
"北京今天天气晴朗,温度在15-25°C之间,非常适合户外活动。"
6.3.2 ToolSchema的生成
AgentScope会自动将@Tool注解转换为LLM能理解的JSON Schema格式。
java
/**
* ToolSchema生成示例
*/
public class ToolSchemaExample {
// 原始工具定义
@Tool(
name = "search_products",
description = "Search products in the database"
)
public String searchProducts(
@ToolParam(name = "keyword", description = "Search keyword") String keyword,
@ToolParam(name = "category", description = "Product category (optional)", required = false) String category,
@ToolParam(name = "max_price", description = "Maximum price in yuan (optional)", required = false) Double maxPrice
) {
// 实现...
return "...";
}
// 自动生成的ToolSchema(JSON格式)
/*
{
"type": "function",
"function": {
"name": "search_products",
"description": "Search products in the database",
"parameters": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "Search keyword"
},
"category": {
"type": "string",
"description": "Product category (optional)"
},
"max_price": {
"type": "number",
"description": "Maximum price in yuan (optional)"
}
},
"required": ["keyword"]
}
}
}
*/
}
6.3.3 参数转换和验证
工具调用时,需要将LLM返回的JSON参数转换为Java方法参数。
java
/**
* 参数转换示例
*/
public class ParameterConversionExample {
// LLM返回的工具调用
ToolUseBlock toolCall = ToolUseBlock.builder()
.name("search_products")
.arguments(Map.of(
"keyword", "iPhone",
"category", "electronics",
"max_price", 10000.0
))
.build();
// AgentScope执行转换
private void executeToolCall(ToolUseBlock toolCall) {
// 1. 查找工具
AgentTool tool = toolkit.getTool("search_products");
// 2. 获取方法参数类型
Method method = tool.getMethod();
Parameter[] params = method.getParameters();
// params[0]: String keyword
// params[1]: String category
// params[2]: Double maxPrice
// 3. 从arguments中提取值并转换
Map<String, Object> args = toolCall.getArguments();
Object[] convertedArgs = new Object[params.length];
for (int i = 0; i < params.length; i++) {
Parameter param = params[i];
ToolParam annotation = param.getAnnotation(ToolParam.class);
String paramName = annotation.name();
Object value = args.get(paramName);
// 类型转换
if (value != null) {
Class<?> targetType = param.getType();
convertedArgs[i] = convertType(value, targetType);
} else if (annotation.required()) {
throw new IllegalArgumentException(
"Missing required parameter: " + paramName);
} else {
convertedArgs[i] = null;
}
}
// 4. 反射调用
Object result = method.invoke(toolInstance, convertedArgs);
}
private Object convertType(Object value, Class<?> targetType) {
if (targetType == String.class) {
return value.toString();
} else if (targetType == Integer.class || targetType == int.class) {
return ((Number) value).intValue();
} else if (targetType == Double.class || targetType == double.class) {
return ((Number) value).doubleValue();
} else if (targetType == Boolean.class || targetType == boolean.class) {
return Boolean.valueOf(value.toString());
}
// ... 其他类型转换
return value;
}
}
6.4 工具开发最佳实践
6.4.1 工具设计原则
scss
原则1:单一职责
✓ 每个工具只做一件事
✓ 工具名称清晰表达功能
示例:
✓ get_user_info(user_id) # 只获取用户信息
✗ manage_user(user_id, action) # 太宽泛
原则2:参数简洁
✓ 参数数量不超过5个
✓ 使用JSON字符串传递复杂数据
示例:
✓ create_order(items_json, address)
✗ create_order(item1, qty1, item2, qty2, ..., address)
原则3:返回值结构化
✓ 返回清晰的文本描述
✓ 使用统一的格式
示例:
✓ "Query result: Found 3 users. Details: ..."
✗ 直接返回JSON对象(LLM难以理解)
原则4:错误处理完善
✓ 捕获所有异常
✓ 返回友好的错误信息
✓ 不要抛出异常到Agent
示例:
✓ return "Error: User not found with id " + userId;
✗ throw new UserNotFoundException(userId);
6.4.2 工具返回值格式
java
/**
* 工具返回值的最佳实践
*/
public class ToolReturnValueExample {
/**
* ✓ 好的返回值格式
*/
@Tool(name = "query_order", description = "Query order details")
public String queryOrder(@ToolParam(name = "order_id") String orderId) {
try {
Order order = orderService.getOrder(orderId);
// 返回结构化的文本描述(便于LLM理解)
return String.format(
"Order Details:\n" +
"- Order ID: %s\n" +
"- Status: %s\n" +
"- Total Amount: ¥%.2f\n" +
"- Items: %d\n" +
"- Shipping Address: %s\n" +
"- Created Time: %s",
order.getId(),
order.getStatus(),
order.getTotalAmount(),
order.getItems().size(),
order.getShippingAddress(),
order.getCreatedTime()
);
} catch (OrderNotFoundException e) {
// 返回清晰的错误信息
return "Error: Order not found with ID " + orderId;
} catch (Exception e) {
// 通用错误处理
return "Error querying order: " + e.getMessage();
}
}
/**
* ✗ 不好的返回值格式
*/
@Tool(name = "query_order_bad", description = "Query order details")
public Object queryOrderBad(@ToolParam(name = "order_id") String orderId) {
Order order = orderService.getOrder(orderId);
// 直接返回对象(LLM无法理解)
return order;
}
/**
* ✓ 列表数据的返回格式
*/
@Tool(name = "search_users", description = "Search users")
public String searchUsers(@ToolParam(name = "keyword") String keyword) {
List<User> users = userService.search(keyword);
if (users.isEmpty()) {
return "No users found matching: " + keyword;
}
StringBuilder result = new StringBuilder();
result.append("Found ").append(users.size()).append(" users:\n");
for (int i = 0; i < Math.min(10, users.size()); i++) {
User user = users.get(i);
result.append(String.format(
"%d. %s (ID: %s, Email: %s)\n",
i + 1, user.getName(), user.getId(), user.getEmail()
));
}
if (users.size() > 10) {
result.append("... and ").append(users.size() - 10).append(" more");
}
return result.toString();
}
}
6.4.3 异步工具的实现
某些工具需要长时间运行(例如文件处理、外部API调用),应该使用异步实现。
java
import reactor.core.publisher.Mono;
/**
* 异步工具示例
*/
public class AsyncToolExample {
private final WebClient webClient = WebClient.create();
/**
* 同步工具(阻塞,不推荐用于慢速操作)
*/
@Tool(name = "fetch_url_sync", description = "Fetch content from URL (sync)")
public String fetchUrlSync(@ToolParam(name = "url") String url) {
try {
// 阻塞等待HTTP响应(可能很慢)
String content = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.block(Duration.ofSeconds(10));
return "Content fetched: " + content.substring(0, 200) + "...";
} catch (Exception e) {
return "Error fetching URL: " + e.getMessage();
}
}
/**
* 异步工具(推荐)
* AgentScope支持Mono<String>返回类型
*/
@Tool(name = "fetch_url", description = "Fetch content from URL")
public Mono<String> fetchUrl(@ToolParam(name = "url") String url) {
return webClient.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(10))
.map(content -> "Content fetched: " + content.substring(0, 200) + "...")
.onErrorResume(error ->
Mono.just("Error fetching URL: " + error.getMessage())
);
}
/**
* 复杂异步工具(多个步骤)
*/
@Tool(name = "process_document", description = "Process document with multiple steps")
public Mono<String> processDocument(
@ToolParam(name = "doc_id") String docId
) {
return Mono.defer(() -> {
// 步骤1:下载文档
return downloadDocument(docId)
// 步骤2:提取文本
.flatMap(doc -> extractText(doc))
// 步骤3:分析文本
.flatMap(text -> analyzeText(text))
// 步骤4:生成报告
.flatMap(analysis -> generateReport(analysis))
// 错误处理
.onErrorResume(error ->
Mono.just("Error processing document: " + error.getMessage())
);
});
}
private Mono<Document> downloadDocument(String docId) {
return documentService.download(docId)
.timeout(Duration.ofMinutes(1));
}
private Mono<String> extractText(Document doc) {
return textExtractor.extract(doc)
.timeout(Duration.ofMinutes(2));
}
private Mono<Analysis> analyzeText(String text) {
return textAnalyzer.analyze(text)
.timeout(Duration.ofSeconds(30));
}
private Mono<String> generateReport(Analysis analysis) {
return reportGenerator.generate(analysis)
.map(report -> "Document processed successfully. " +
"Analysis summary: " + report.getSummary());
}
}
6.5 生产场景:电商订单处理工具系统
6.5.1 完整的电商工具类
java
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 电商订单处理工具系统
*
* 功能:
* 1. 商品搜索
* 2. 商品详情查询
* 3. 订单创建
* 4. 订单查询
* 5. 退货处理
* 6. 优惠券验证
*/
@RequiredArgsConstructor
public class ECommerceTools {
// 依赖的服务
private final OrderService orderService;
private final InventoryService inventoryService;
private final PricingService pricingService;
// ============ 工具1:商品搜索 ============
@Tool(
name = "search_products",
description = "Search products by keyword, category, or price range. " +
"Returns a list of matching products with basic information."
)
public String searchProducts(
@ToolParam(
name = "query",
description = "Search keyword (product name, description)"
) String query,
@ToolParam(
name = "category",
description = "Product category filter (optional): electronics, clothing, food, books, etc.",
required = false
) String category,
@ToolParam(
name = "max_price",
description = "Maximum price in yuan (optional)",
required = false
) Double maxPrice
) {
try {
// 执行搜索
List<Product> products = inventoryService.search(
query, category, maxPrice);
if (products.isEmpty()) {
return String.format(
"No products found matching query='%s'%s%s",
query,
category != null ? ", category='" + category + "'" : "",
maxPrice != null ? ", max_price=¥" + maxPrice : ""
);
}
// 格式化结果(最多显示前10个)
StringBuilder result = new StringBuilder();
result.append(String.format("Found %d products:\n", products.size()));
int displayCount = Math.min(10, products.size());
for (int i = 0; i < displayCount; i++) {
Product p = products.get(i);
result.append(String.format(
"%d. [%s] %s\n" +
" Price: ¥%.2f | Stock: %d units | Rating: %.1f/5.0 (%d reviews)\n",
i + 1,
p.getId(),
p.getName(),
p.getPrice(),
p.getStock(),
p.getRating(),
p.getReviewCount()
));
}
if (products.size() > 10) {
result.append(String.format(
"\n... and %d more products. Use get_product_details to see more information.",
products.size() - 10
));
}
return result.toString();
} catch (Exception e) {
return "Error searching products: " + e.getMessage();
}
}
// ============ 工具2:商品详情 ============
@Tool(
name = "get_product_details",
description = "Get detailed information about a specific product including " +
"description, specifications, warranty, and customer reviews."
)
public String getProductDetails(
@ToolParam(
name = "product_id",
description = "Product ID (e.g. PROD-12345)"
) String productId
) {
try {
Product product = inventoryService.getProduct(productId);
if (product == null) {
return "Error: Product not found with ID " + productId;
}
return String.format(
"Product Details:\n" +
"━━━━━━━━━━━━━━━━━━━━\n" +
"ID: %s\n" +
"Name: %s\n" +
"Category: %s\n" +
"Price: ¥%.2f\n" +
"Stock: %d units available\n" +
"Rating: %.1f/5.0 (based on %d reviews)\n" +
"\nDescription:\n%s\n" +
"\nSpecifications:\n%s\n" +
"\nWarranty: %d months\n" +
"Shipping: %s",
product.getId(),
product.getName(),
product.getCategory(),
product.getPrice(),
product.getStock(),
product.getRating(),
product.getReviewCount(),
product.getDescription(),
formatSpecifications(product.getSpecifications()),
product.getWarrantyMonths(),
product.getFreeShipping() ? "Free shipping" : "Standard shipping fee applies"
);
} catch (Exception e) {
return "Error getting product details: " + e.getMessage();
}
}
// ============ 工具3:创建订单 ============
@Tool(
name = "create_order",
description = "Create a new order with specified products and quantities. " +
"Validates stock availability and applies discount coupons if provided."
)
public String createOrder(
@ToolParam(
name = "items",
description = "Order items in JSON format: " +
"[{\"product_id\": \"PROD-123\", \"quantity\": 2}, ...]"
) String itemsJson,
@ToolParam(
name = "shipping_address",
description = "Delivery address (full address including postal code)"
) String shippingAddress,
@ToolParam(
name = "coupon_code",
description = "Discount coupon code (optional)",
required = false
) String couponCode
) {
try {
// 1. 解析订单项
List<OrderItem> items = parseOrderItems(itemsJson);
if (items.isEmpty()) {
return "Error: No items specified in the order";
}
// 2. 验证库存
for (OrderItem item : items) {
Product product = inventoryService.getProduct(item.getProductId());
if (product == null) {
return String.format(
"Error: Product not found with ID %s",
item.getProductId()
);
}
if (product.getStock() < item.getQuantity()) {
return String.format(
"Error: Insufficient stock for product '%s'. " +
"Available: %d, Requested: %d",
product.getName(),
product.getStock(),
item.getQuantity()
);
}
// 设置价格
item.setPrice(product.getPrice());
item.setProductName(product.getName());
}
// 3. 计算总金额
double subtotal = items.stream()
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
double discount = 0.0;
String discountInfo = "";
// 4. 应用优惠券
if (couponCode != null && !couponCode.isEmpty()) {
Double couponDiscount = pricingService.validateCoupon(
couponCode, subtotal);
if (couponDiscount != null && couponDiscount > 0) {
discount = couponDiscount;
discountInfo = String.format(
"\nDiscount Applied: -¥%.2f (Coupon: %s)",
discount, couponCode
);
} else {
return "Error: Invalid or expired coupon code: " + couponCode;
}
}
double totalAmount = subtotal - discount;
// 5. 创建订单
Order order = new Order();
order.setItems(items);
order.setShippingAddress(shippingAddress);
order.setSubtotal(subtotal);
order.setDiscount(discount);
order.setTotalAmount(totalAmount);
order.setStatus("PENDING_PAYMENT");
order.setCreatedTime(LocalDateTime.now());
// 6. 保存订单
String orderId = orderService.createOrder(order);
// 7. 返回订单确认信息
StringBuilder itemsList = new StringBuilder();
for (OrderItem item : items) {
itemsList.append(String.format(
" - %s × %d = ¥%.2f\n",
item.getProductName(),
item.getQuantity(),
item.getPrice() * item.getQuantity()
));
}
return String.format(
"Order Created Successfully!\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"Order ID: %s\n" +
"Status: PENDING_PAYMENT\n" +
"\nItems:\n%s" +
"\nSubtotal: ¥%.2f%s\n" +
"Total Amount: ¥%.2f\n" +
"\nShipping Address:\n%s\n" +
"\nNext Steps:\n" +
"1. Please proceed to payment\n" +
"2. Estimated delivery: 3-5 business days after payment\n" +
"3. Use check_order_status to track your order",
orderId,
itemsList.toString(),
subtotal,
discountInfo,
totalAmount,
shippingAddress
);
} catch (Exception e) {
return "Error creating order: " + e.getMessage();
}
}
// ============ 工具4:查询订单状态 ============
@Tool(
name = "check_order_status",
description = "Check the current status of an existing order including " +
"payment status, shipping status, and tracking information."
)
public String checkOrderStatus(
@ToolParam(
name = "order_id",
description = "Order ID (e.g. ORD-2024-12345)"
) String orderId
) {
try {
Order order = orderService.getOrder(orderId);
if (order == null) {
return "Error: Order not found with ID " + orderId;
}
// 格式化订单状态
String statusDescription = switch (order.getStatus()) {
case "PENDING_PAYMENT" -> "Awaiting payment";
case "PAID" -> "Payment received, preparing shipment";
case "SHIPPED" -> "Order shipped";
case "DELIVERED" -> "Order delivered";
case "CANCELLED" -> "Order cancelled";
default -> order.getStatus();
};
StringBuilder result = new StringBuilder();
result.append(String.format(
"Order Status:\n" +
"━━━━━━━━━━━━━━━━━━\n" +
"Order ID: %s\n" +
"Status: %s\n" +
"Created: %s\n" +
"Total Amount: ¥%.2f\n",
orderId,
statusDescription,
order.getCreatedTime().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
order.getTotalAmount()
));
// 添加物流信息
if (order.getTrackingNumber() != null) {
result.append(String.format(
"\nShipping Information:\n" +
"Tracking Number: %s\n" +
"Carrier: %s\n" +
"Estimated Delivery: %s\n",
order.getTrackingNumber(),
order.getCarrier(),
order.getEstimatedDelivery()
));
}
// 添加订单项
result.append("\nItems:\n");
for (OrderItem item : order.getItems()) {
result.append(String.format(
" - %s × %d\n",
item.getProductName(),
item.getQuantity()
));
}
return result.toString();
} catch (Exception e) {
return "Error checking order status: " + e.getMessage();
}
}
// ============ 工具5:处理退货 ============
@Tool(
name = "process_return_request",
description = "Process a return request for an order. " +
"Validates return eligibility and initiates the return process."
)
public String processReturnRequest(
@ToolParam(
name = "order_id",
description = "Order ID to return"
) String orderId,
@ToolParam(
name = "reason",
description = "Reason for return"
) String reason,
@ToolParam(
name = "items",
description = "Items to return in JSON format (optional, if not specified, return all items)",
required = false
) String itemsJson
) {
try {
// 1. 验证订单
Order order = orderService.getOrder(orderId);
if (order == null) {
return "Error: Order not found with ID " + orderId;
}
// 2. 检查是否可以退货
if (!canReturn(order)) {
return String.format(
"Error: This order cannot be returned. " +
"Reason: %s",
getReturnIneligibilityReason(order)
);
}
// 3. 处理退货请求
String returnId = orderService.createReturnRequest(
orderId, reason, itemsJson);
return String.format(
"Return Request Created Successfully!\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"Return ID: %s\n" +
"Order ID: %s\n" +
"Reason: %s\n" +
"Status: PENDING_REVIEW\n" +
"\nNext Steps:\n" +
"1. Our team will review your request within 24 hours\n" +
"2. If approved, you will receive a return shipping label\n" +
"3. Refund will be processed within 5-7 business days after we receive the items\n" +
"\nEstimated Refund Amount: ¥%.2f",
returnId,
orderId,
reason,
order.getTotalAmount()
);
} catch (Exception e) {
return "Error processing return request: " + e.getMessage();
}
}
// ============ 工具6:验证优惠券 ============
@Tool(
name = "validate_coupon",
description = "Validate a coupon code and check the discount amount it provides."
)
public String validateCoupon(
@ToolParam(
name = "coupon_code",
description = "Coupon code to validate"
) String couponCode,
@ToolParam(
name = "order_amount",
description = "Order total amount in yuan"
) Double orderAmount
) {
try {
Double discount = pricingService.validateCoupon(couponCode, orderAmount);
if (discount == null || discount <= 0) {
return String.format(
"Coupon '%s' is invalid or has expired.",
couponCode
);
}
double discountRate = (discount / orderAmount) * 100;
double finalAmount = orderAmount - discount;
return String.format(
"Coupon Validation Result:\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"Coupon Code: %s\n" +
"Status: VALID\n" +
"\nDiscount Details:\n" +
"Original Amount: ¥%.2f\n" +
"Discount Amount: -¥%.2f (%.1f%% off)\n" +
"Final Amount: ¥%.2f\n" +
"\nYou save ¥%.2f with this coupon!",
couponCode,
orderAmount,
discount,
discountRate,
finalAmount,
discount
);
} catch (Exception e) {
return "Error validating coupon: " + e.getMessage();
}
}
// ============ 辅助方法 ============
private List<OrderItem> parseOrderItems(String itemsJson) {
// 解析JSON格式的订单项
// 实现省略...
return null;
}
private String formatSpecifications(Map<String, String> specs) {
if (specs == null || specs.isEmpty()) {
return "N/A";
}
return specs.entrySet().stream()
.map(entry -> " - " + entry.getKey() + ": " + entry.getValue())
.collect(Collectors.joining("\n"));
}
private boolean canReturn(Order order) {
// 检查订单是否在退货期限内
// 实现省略...
return true;
}
private String getReturnIneligibilityReason(Order order) {
// 返回不能退货的原因
// 实现省略...
return "";
}
}
6.5.2 使用电商工具的Agent
java
/**
* 电商客服Agent示例
*/
public class ECommerceAgentExample {
public static void main(String[] args) {
// 创建服务实例
OrderService orderService = new OrderServiceImpl();
InventoryService inventoryService = new InventoryServiceImpl();
PricingService pricingService = new PricingServiceImpl();
// 创建工具箱
Toolkit toolkit = new Toolkit();
toolkit.registerObject(new ECommerceTools(
orderService, inventoryService, pricingService));
// 创建Agent
ReActAgent agent = ReActAgent.builder()
.name("ECommerceAssistant")
.description("专业的电商客服助手")
.model(DashScopeChatModel.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build())
.toolkit(toolkit)
.memory(new InMemoryMemory())
.maxIters(10)
.sysPrompt("""
你是一个专业的电商客服助手。
你的职责:
1. 帮助用户搜索和查看商品
2. 协助用户创建订单
3. 查询订单状态
4. 处理退货请求
5. 验证优惠券
工作原则:
- 优先使用工具获取准确信息
- 涉及金钱的操作要与用户确认
- 友好、耐心、专业
- 如果无法解决问题,建议转人工客服
""")
.build();
// 场景1:用户购物
testShoppingScenario(agent);
// 场景2:用户查询订单
testOrderQueryScenario(agent);
// 场景3:用户退货
testReturnScenario(agent);
}
private static void testShoppingScenario(ReActAgent agent) {
System.out.println("===== 购物场景 =====\n");
String userMessage =
"我想买一个iPhone 15 Pro,价格不超过10000块,有优惠吗?";
Msg response = agent.call(userMessage).block();
System.out.println("客服: " + response.getTextContent());
// Agent执行过程:
// 1. [推理] 用户想买iPhone 15 Pro,价格限制10000,我需要先搜索商品
// 2. [工具] search_products(query="iPhone 15 Pro", max_price=10000)
// 3. [结果] 返回商品列表
// 4. [推理] 找到商品了,用户问有没有优惠,我需要检查优惠券
// 5. [回复] "找到了iPhone 15 Pro,当前价格9999元。我们有新用户优惠券..."
}
private static void testOrderQueryScenario(ReActAgent agent) {
System.out.println("\n===== 订单查询场景 =====\n");
String userMessage = "我的订单ORD-2024-001什么时候能到?";
Msg response = agent.call(userMessage).block();
System.out.println("客服: " + response.getTextContent());
// Agent执行过程:
// 1. [推理] 用户想查询订单状态,我需要调用check_order_status
// 2. [工具] check_order_status(order_id="ORD-2024-001")
// 3. [结果] 订单状态:已发货,预计3天后到达
// 4. [回复] "您的订单已发货,物流单号123456,预计3天后送达。"
}
private static void testReturnScenario(ReActAgent agent) {
System.out.println("\n===== 退货场景 =====\n");
String userMessage = "我想退订单ORD-2024-001,商品有质量问题。";
Msg response = agent.call(userMessage).block();
System.out.println("客服: " + response.getTextContent());
// Agent执行过程:
// 1. [推理] 用户要退货,我需要先确认订单信息
// 2. [工具] check_order_status(order_id="ORD-2024-001")
// 3. [推理] 订单确实存在,可以处理退货请求
// 4. [工具] process_return_request(order_id="ORD-2024-001", reason="质量问题")
// 5. [结果] 退货申请已创建
// 6. [回复] "退货申请已提交,退货编号RET-001,我们会在24小时内审核..."
}
}
6.6 本章总结
关键要点
-
工具定义
- 使用@Tool注解标记方法
- 使用@ToolParam注解标记参数
- 命名使用snake_case规范
- 描述要清晰简洁
-
工具注册
- 创建Toolkit实例
- 使用registerObject()注册工具类
- 自动扫描@Tool注解
- 生成ToolSchema
-
工具调用流程
- ToolSchema发送给LLM
- LLM生成ToolUseBlock
- Agent执行工具
- 返回ToolResultBlock
- 继续推理循环
-
最佳实践
- 单一职责原则
- 参数简洁(≤5个)
- 返回结构化文本
- 完善的错误处理
- 支持异步执行
工具开发检查清单
设计阶段:
☐ 工具功能单一明确
☐ 参数数量合理(≤5个)
☐ 描述清晰完整
实现阶段:
☐ 参数验证完整
☐ 异常全部捕获
☐ 返回值格式统一
☐ 支持异步执行(如需要)
测试阶段:
☐ 正常流程测试
☐ 异常流程测试
☐ 边界条件测试
☐ 性能测试
集成阶段:
☐ 注册到Toolkit
☐ 与Agent集成测试
☐ 端到端场景测试
下一章预告
第7-9章将讲解记忆系统、Hook机制、Formatter适配,探讨如何管理对话历史、如何扩展Agent功能、如何适配不同LLM模型等高级主题。