第7章 工具系统基础
本章目标:掌握工具的定义、注册和使用,理解工具调用的执行流程
7.1 工具系统概述
7.1.1 为什么需要工具
LLM 本身只能生成文本,无法执行实际操作。工具系统让智能体能够:
┌─────────────────────────────────────────────────────────────┐
│ 工具系统的作用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户: "帮我查一下北京天气" │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 智能体 │ "我需要调用天气查询工具..." │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 工具系统 │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ 天气查询 │ │ 发邮件 │ │ 搜索引擎 │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ └─────────────────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 外部服务 / API / 系统 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
7.1.2 工具系统特点
| 特点 | 说明 |
|---|---|
| 注解驱动 | 使用 @Tool 和 @ToolParam 注解定义工具 |
| 自动 Schema 生成 | 框架自动生成 JSON Schema 供 LLM 理解 |
| 类型安全 | 参数和返回值都是强类型的 |
| 响应式支持 | 支持同步、异步(Mono/Flux)和流式工具 |
| 灵活管理 | 工具组、预设参数、动态激活等 |
7.2 定义工具
7.2.1 基本语法
import io.agentscope.core.tool.annotation.Tool;
import io.agentscope.core.tool.annotation.ToolParam;
/**
* 工具类示例
*
* 一个类可以包含多个工具方法
*/
public class WeatherTools {
@Tool(
name = "get_weather",
// 工具名称
// - LLM 看到的名称
// - 建议使用 snake_case
// - 要求唯一
description = "获取指定城市的天气信息,包括温度、天气状况和空气质量"
// 工具描述
// - 帮助 LLM 理解何时使用这个工具
// - 描述越清晰,LLM 选择越准确
)
public WeatherInfo getWeather(
@ToolParam(
name = "city",
// 参数名(必需)
// - Java 运行时不保留参数名,必须显式指定
description = "城市名称,如:北京、上海、广州"
// 参数描述
// - 帮助 LLM 理解参数含义
)
String city,
@ToolParam(
name = "include_forecast",
description = "是否包含未来几天的预报",
required = false
// required 默认为 true
// 设为 false 表示可选参数
)
Boolean includeForecast
) {
// 工具实现
return weatherService.getWeather(city, includeForecast);
}
}
7.2.2 参数类型支持
public class TypeExamples {
@Tool(name = "demo_types", description = "演示支持的参数类型")
public String demoTypes(
// ======== 基本类型 ========
@ToolParam(name = "text") String text,
@ToolParam(name = "count") Integer count,
@ToolParam(name = "price") Double price,
@ToolParam(name = "active") Boolean active,
// ======== 集合类型 ========
@ToolParam(name = "tags") List<String> tags,
@ToolParam(name = "scores") List<Integer> scores,
// ======== 复杂对象 ========
@ToolParam(name = "config") Config config
// 复杂对象会被序列化为 JSON
) {
return "处理完成";
}
}
// 复杂对象定义
public class Config {
public String mode;
public int timeout;
public List<String> options;
}
7.2.3 返回值类型
public class ReturnTypeExamples {
// ======== 字符串返回 ========
@Tool(name = "string_result", description = "返回字符串")
public String stringResult(@ToolParam(name = "input") String input) {
return "处理结果: " + input;
}
// ======== 对象返回 ========
// 自动序列化为 JSON
@Tool(name = "object_result", description = "返回对象")
public UserInfo objectResult(@ToolParam(name = "userId") String userId) {
return new UserInfo("张三", 25, "北京");
}
// ======== 列表返回 ========
@Tool(name = "list_result", description = "返回列表")
public List<Product> listResult(@ToolParam(name = "category") String category) {
return productService.findByCategory(category);
}
// ======== ToolResultBlock 返回 ========
// 最灵活的方式,可以包含多种内容
@Tool(name = "rich_result", description = "返回富内容")
public ToolResultBlock richResult(@ToolParam(name = "query") String query) {
return ToolResultBlock.of("id", "rich_result",
TextBlock.builder().text("文本结果").build(),
ImageBlock.builder()
.source(Base64Source.builder()
.data(base64Image)
.mediaType("image/png")
.build())
.build()
);
}
}
7.3 工具类型
7.3.1 同步工具
最简单的工具类型,直接返回结果:
@Tool(name = "calculate", description = "执行数学计算")
public double calculate(
@ToolParam(name = "expression") String expression
) {
// 同步执行,直接返回结果
return evaluator.evaluate(expression);
}
7.3.2 异步工具
返回 Mono 或 Flux,支持非阻塞执行:
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
// ======== Mono 返回 ========
@Tool(name = "async_search", description = "异步搜索")
public Mono<SearchResult> asyncSearch(
@ToolParam(name = "query") String query
) {
// 返回 Mono,框架自动处理订阅
return webClient.get()
.uri("/search?q=" + query)
.retrieve()
.bodyToMono(SearchResult.class);
}
// ======== Flux 返回 ========
// 所有元素收集后作为列表返回给 LLM
@Tool(name = "stream_fetch", description = "流式获取数据")
public Flux<DataItem> streamFetch(
@ToolParam(name = "source") String source
) {
return dataService.streamFrom(source);
}
7.3.3 流式工具(ToolEmitter)
使用 ToolEmitter 发送中间进度,适合长时间任务:
import io.agentscope.core.tool.ToolEmitter;
import io.agentscope.core.message.ToolResultBlock;
@Tool(name = "process_files", description = "处理多个文件")
public ToolResultBlock processFiles(
@ToolParam(name = "files") List<String> files,
ToolEmitter emitter // 自动注入,不需要 @ToolParam
) {
int total = files.size();
int processed = 0;
for (String file : files) {
// 处理文件
processFile(file);
processed++;
// 发送进度更新
// 这些更新会通过 Hook 的 ActingChunkEvent 传递
emitter.emit(ToolResultBlock.text(
String.format("进度: %d/%d - 正在处理 %s",
processed, total, file)
));
}
// 返回最终结果
return ToolResultBlock.text("全部处理完成,共 " + total + " 个文件");
}
在 Hook 中接收进度:
Hook progressHook = new Hook() {
@Override
public <T extends HookEvent> Mono<T> onEvent(T event) {
if (event instanceof ActingChunkEvent e) {
// 接收工具进度更新
System.out.println("进度: " + e.getChunk().getTextContent());
}
return Mono.just(event);
}
};
7.4 注册工具
7.4.1 Toolkit 基础使用
import io.agentscope.core.tool.Toolkit;
// ========================================
// 【创建和注册工具】
// ========================================
// 创建工具包
Toolkit toolkit = new Toolkit();
// 注册工具类
toolkit.registerTool(new WeatherTools());
toolkit.registerTool(new CalculatorTools());
toolkit.registerTool(new SearchTools());
// 在智能体中使用
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.model(model)
.toolkit(toolkit)
.build();
7.4.2 链式注册
// ========================================
// 【链式注册方式】
// 更灵活的配置选项
// ========================================
Toolkit toolkit = new Toolkit();
toolkit.registration()
.tool(new WeatherTools())
.apply();
toolkit.registration()
.tool(new SearchTools())
.group("search") // 分配到工具组
.apply();
toolkit.registration()
.tool(new AdminTools())
.presetParameters(Map.of(
"admin_command", Map.of("apiKey", System.getenv("ADMIN_KEY"))
))
.apply();
7.4.3 查看注册的工具
// 获取所有工具信息
List<ToolInfo> tools = toolkit.getToolInfos();
for (ToolInfo tool : tools) {
System.out.println("工具: " + tool.getName());
System.out.println("描述: " + tool.getDescription());
System.out.println("参数: " + tool.getParameters());
System.out.println("---");
}
7.5 工具执行流程
7.5.1 执行流程图
┌─────────────────────────────────────────────────────────────┐
│ 工具执行流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. LLM 推理 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 生成 ToolUseBlock │ │
│ │ { │ │
│ │ "name": "get_weather", │ │
│ │ "input": {"city": "北京"} │ │
│ │ } │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. 框架处理 │
│ │ │
│ ├─── PreActingEvent (Hook) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Toolkit 查找并执行工具 │ │
│ │ - 参数反序列化 │ │
│ │ - 调用工具方法 │ │
│ │ - 结果序列化 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ├─── PostActingEvent (Hook) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 生成 ToolResultBlock │ │
│ │ { │ │
│ │ "name": "get_weather", │ │
│ │ "output": "北京:晴,25℃" │ │
│ │ } │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. 结果返回 LLM,继续推理 │
│ │
└─────────────────────────────────────────────────────────────┘
7.5.2 并行执行
当 LLM 一次请求多个工具时,框架默认并行执行:
// LLM 可能一次请求多个工具:
// 1. get_weather(city="北京")
// 2. get_weather(city="上海")
// 3. get_time(timezone="Asia/Shanghai")
// 框架会并行执行这些工具,提高效率
7.6 错误处理
7.6.1 工具内部错误处理
@Tool(name = "risky_operation", description = "可能失败的操作")
public String riskyOperation(
@ToolParam(name = "input") String input
) {
try {
return performOperation(input);
} catch (BusinessException e) {
// 返回友好的错误信息
// LLM 会看到这个信息并做出相应处理
return "操作失败: " + e.getMessage();
} catch (Exception e) {
// 记录日志
log.error("工具执行异常", e);
return "系统错误,请稍后重试";
}
}
7.6.2 返回错误结果
@Tool(name = "validate_input", description = "验证输入")
public ToolResultBlock validateInput(
@ToolParam(name = "data") String data
) {
if (!isValid(data)) {
// 返回标记为错误的结果
return ToolResultBlock.builder()
.id("tool-id")
.name("validate_input")
.isError(true) // 标记为错误
.output(List.of(TextBlock.builder()
.text("输入格式不正确: " + getErrorDetails(data))
.build()))
.build();
}
return ToolResultBlock.text("验证通过");
}
7.7 完整示例
package com.example.tools;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.tool.annotation.Tool;
import io.agentscope.core.tool.annotation.ToolParam;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
/**
* 工具系统完整示例
*/
public class ToolSystemExample {
// ========================================
// 【工具类定义】
// ========================================
public static class UtilityTools {
@Tool(
name = "get_current_datetime",
description = "获取当前的日期和时间"
)
public String getCurrentDatetime() {
return LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@Tool(
name = "calculate",
description = "执行基本数学运算(加减乘除)"
)
public double calculate(
@ToolParam(name = "a", description = "第一个数") double a,
@ToolParam(name = "operation", description = "运算符:+, -, *, /") String operation,
@ToolParam(name = "b", description = "第二个数") double b
) {
return switch (operation) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> {
if (b == 0) throw new IllegalArgumentException("除数不能为零");
yield a / b;
}
default -> throw new IllegalArgumentException("不支持的运算: " + operation);
};
}
@Tool(
name = "format_list",
description = "将列表格式化为编号列表"
)
public String formatList(
@ToolParam(name = "items", description = "要格式化的项目列表")
List<String> items,
@ToolParam(name = "style", description = "样式:numbered 或 bullet", required = false)
String style
) {
if (style == null) style = "numbered";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
if ("bullet".equals(style)) {
sb.append("• ").append(items.get(i)).append("\n");
} else {
sb.append(i + 1).append(". ").append(items.get(i)).append("\n");
}
}
return sb.toString().trim();
}
}
// ========================================
// 【主程序】
// ========================================
public static void main(String[] args) {
String apiKey = System.getenv("DASHSCOPE_API_KEY");
// 1. 创建模型
DashScopeChatModel model = DashScopeChatModel.builder()
.apiKey(apiKey)
.modelName("qwen-max")
.stream(true)
.build();
// 2. 创建并注册工具
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new UtilityTools());
// 3. 创建智能体
ReActAgent agent = ReActAgent.builder()
.name("UtilityAssistant")
.sysPrompt("""
你是一个实用工具助手,可以:
1. 查询当前时间
2. 进行数学计算
3. 格式化列表
请根据用户需求使用合适的工具。
""")
.model(model)
.toolkit(toolkit)
.build();
// 4. 测试工具调用
String[] testQueries = {
"现在几点了?",
"计算 123 乘以 456 等于多少",
"把这些内容做成列表:苹果、香蕉、橙子、葡萄"
};
for (String query : testQueries) {
System.out.println("问题: " + query);
Msg response = agent.call(
Msg.builder().textContent(query).build()
).block();
System.out.println("回答: " + response.getTextContent());
System.out.println("---");
}
}
}
7.8 本章小结
本章我们学习了:
- 工具定义
- @Tool 注解:name、description
- @ToolParam 注解:name(必需)、description、required
- 支持的参数和返回类型
- 工具类型
- 同步工具:直接返回结果 - 异步工具:返回 Mono/Flux - 流式工具:使用 ToolEmitter
- 工具注册
- Toolkit 基础使用 - 链式注册方式 - 查看注册的工具
- 执行流程
- LLM 生成 ToolUseBlock - 框架执行工具 - 返回 ToolResultBlock
- 错误处理
- 工具内部异常处理 - 返回错误结果
练习
- 实现天气工具:对接真实的天气 API
- 异步搜索工具:使用 WebClient 实现异步 HTTP 请求
- 进度报告工具:使用 ToolEmitter 报告长任务进度
- 错误处理:测试各种错误情况的处理
下一章 → 第8章 工具高级特性