阿里开源AgentScope多智能体框架解析系列(六)第6章:工具系统(Tool & Toolkit)

本章导读

本章将深入讲解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 本章总结

关键要点

  1. 工具定义

    • 使用@Tool注解标记方法
    • 使用@ToolParam注解标记参数
    • 命名使用snake_case规范
    • 描述要清晰简洁
  2. 工具注册

    • 创建Toolkit实例
    • 使用registerObject()注册工具类
    • 自动扫描@Tool注解
    • 生成ToolSchema
  3. 工具调用流程

    • ToolSchema发送给LLM
    • LLM生成ToolUseBlock
    • Agent执行工具
    • 返回ToolResultBlock
    • 继续推理循环
  4. 最佳实践

    • 单一职责原则
    • 参数简洁(≤5个)
    • 返回结构化文本
    • 完善的错误处理
    • 支持异步执行

工具开发检查清单

复制代码
设计阶段:
☐ 工具功能单一明确
☐ 参数数量合理(≤5个)
☐ 描述清晰完整

实现阶段:
☐ 参数验证完整
☐ 异常全部捕获
☐ 返回值格式统一
☐ 支持异步执行(如需要)

测试阶段:
☐ 正常流程测试
☐ 异常流程测试
☐ 边界条件测试
☐ 性能测试

集成阶段:
☐ 注册到Toolkit
☐ 与Agent集成测试
☐ 端到端场景测试

下一章预告

第7-9章将讲解记忆系统、Hook机制、Formatter适配,探讨如何管理对话历史、如何扩展Agent功能、如何适配不同LLM模型等高级主题。

相关推荐
m0_726965985 小时前
RAG源代码笔记JAVA-高级RAG
笔记·ai·agent·rag
AI大模型8 小时前
免费自学 AI?这 10 个 GitHub 宝藏项目就够了!建议收藏
langchain·llm·agent
AlanHou11 小时前
AI 智能体从入门到进阶再到落地完整教程
人工智能·agent
wumingxiaoyao12 小时前
AI - Agent 到底怎么“接模型”?怎么认证?公司网关/本地模型怎么接?
agent·gemini·litellm·adk·ai model
致Great13 小时前
使用 GRPO 和 OpenEnv 微调小型语言模型实现浏览器控制
数据库·人工智能·深度学习·语言模型·自然语言处理·agent·智能体
gentle coder13 小时前
【Prompt】 提示词工程(持续更新中~)
ai·prompt·agent·提示词工程·#广告顾问
ㄣ知冷煖★1 天前
【Google系列】AI智能体技术白皮书
人工智能·agent
语落心生1 天前
阿里开源AgentScope多智能体框架解析系列(十)第10章:中断机制
agent