在AI Agent的世界里,Tool就是Agent的双手和眼睛。一个没有Tool的Agent只能聊天,而拥有丰富Tool的Agent可以查询数据库、调用API、发送邮件、操作文件系统。本节将深入探讨如何定义高质量的Tool,以及如何让多个Tool协同工作。
时间:30分钟 | 难度:⭐⭐⭐ | Week 3 Day 18
📋 学习目标
- 理解Tool的本质和LangChain4J中的Tool机制
- 掌握@Tool和@P注解的完整用法
- 了解Tool支持的参数类型和返回值处理
- 学会设计清晰、可维护的Tool接口
- 掌握Tool链接和组合的最佳实践
- 能够实现动态Tool注册和监控
- 理解MCP协议与@Tool的关系和各自适用场景
🚀 快速入门:什么是Tool?
Tool的本质
Tool在LangChain4J中是Agent可以调用的函数。关键点:
- Agent决定何时调用 - 不是你强制调用,是LLM根据用户问题自主选择
- Agent决定调用哪个 - 从多个Tool中选择最合适的
- Agent决定参数值 - 从对话上下文中提取参数
用户请求 → LLM分析 → 选择Tool → 执行 → 结果返回 → LLM整合回答
↓ ↓ ↓ ↓ ↓ ↓
"今天天气" 需要天气 weather API "晴天" "今天北京
数据 Tool 调用 20°C 晴天20度"
ASCII工作流程图
┌─────────────┐
│ 用户输入 │ "帮我查一下北京明天的天气,如果下雨就发邮件提醒我"
└──────┬──────┘
│
▼
┌─────────────────────────────────────────────────┐
│ LLM 推理引擎 │
│ 分析:需要2个步骤 │
│ 1. 查询天气 → 使用 getWeather("北京", "明天") │
│ 2. 如果下雨 → 使用 sendEmail(...) │
└──────┬──────────────────────────────────────────┘
│
├─────────────┬─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Weather │ │Calendar │ │ Email │
│ Tool │ │ Tool │ │ Tool │
└────┬────┘ └─────────┘ └────┬────┘
│ │
▼ ▼
调用API 发送邮件
│ │
└──────────┬───────────────┘
▼
┌──────────────┐
│ LLM 整合答案 │
└──────┬───────┘
▼
"明天北京有雨,已发送邮件提醒"
🔥 深度讲解
1️⃣ @Tool注解详解
@Tool是定义工具的核心注解,有以下属性:
import dev.langchain4j.agent.tool.Tool;
public class MathTools {
// 最简单的Tool:只有描述
@Tool("计算两个数的和")
public double add(double a, double b) {
return a + b;
}
// 完整的Tool定义
@Tool(
name = "multiply", // Tool名称,默认使用方法名
value = "计算两个数的乘积,支持整数和小数" // 描述,帮助LLM选择Tool
)
public double multiply(double a, double b) {
System.out.println("正在计算: " + a + " × " + b);
return a * b;
}
// 复杂业务逻辑的Tool
@Tool("执行数学表达式,支持 +、-、*、/、括号")
public String evaluate(String expression) {
try {
// 使用表达式解析库
double result = new ExpressionParser().parse(expression).evaluate();
return "结果是: " + result;
} catch (Exception e) {
return "表达式解析错误: " + e.getMessage();
}
}
}
最佳实践:
- 描述要精确 - LLM根据描述决定是否调用这个Tool
- 一个Tool做一件事 - 不要创建"万能Tool"
- 命名要语义化 -
calculateSum优于calc
// ❌ 不好的例子
@Tool("处理数据")
public String process(String data) { ... }
// ✅ 好的例子
@Tool("将JSON字符串转换为格式化的表格")
public String jsonToTable(String json) { ... }
2️⃣ @P参数注解:让LLM理解参数含义
@P注解为每个参数提供描述,这对LLM至关重要:
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
public class UserService {
@Tool("搜索用户信息")
public User findUser(
@P("用户ID,格式为UUID,例如:123e4567-e89b-12d3-a456-426614174000")
String userId,
@P("是否包含详细信息(地址、订单历史等),true表示包含")
boolean includeDetails
) {
User user = database.findById(userId);
if (includeDetails) {
user.loadDetails();
}
return user;
}
@Tool("根据多个条件搜索用户")
public List<User> searchUsers(
@P("用户名,支持模糊匹配,不区分大小写")
String name,
@P("最小年龄,包含此年龄")
int minAge,
@P("最大年龄,包含此年龄")
int maxAge,
@P("用户状态:ACTIVE(活跃)、INACTIVE(未激活)、BANNED(已封禁)")
String status
) {
return database.search(name, minAge, maxAge, status);
}
}
参数描述的艺术:
public class DateTools {
@Tool("添加指定天数到日期")
public String addDays(
// ❌ 描述不够清晰
// @P("日期") String date,
// ✅ 清晰的格式说明
@P("日期,格式必须是 yyyy-MM-dd,例如:2024-03-15")
String date,
// ❌ 没说明正负
// @P("天数") int days
// ✅ 说明范围和含义
@P("要添加的天数,正数表示未来,负数表示过去,范围:-365到365")
int days
) {
LocalDate localDate = LocalDate.parse(date);
return localDate.plusDays(days).toString();
}
}
3️⃣ 支持的参数类型
LangChain4J支持丰富的参数类型:
基本类型
public class BasicTypeTools {
@Tool("格式化文本")
public String format(
@P("要格式化的文本") String text,
@P("是否转为大写") boolean uppercase,
@P("重复次数") int repeat,
@P("缩放因子") double scale
) {
String result = uppercase ? text.toUpperCase() : text;
result = result.repeat(repeat);
return result;
}
}
枚举类型
public class TaskTools {
enum Priority {
LOW("低优先级"),
MEDIUM("中优先级"),
HIGH("高优先级"),
CRITICAL("紧急");
private final String description;
Priority(String description) {
this.description = description;
}
}
enum TaskStatus {
TODO, IN_PROGRESS, REVIEW, DONE, CANCELLED
}
@Tool("创建新任务")
public String createTask(
@P("任务标题") String title,
@P("优先级:LOW、MEDIUM、HIGH、CRITICAL") Priority priority,
@P("任务状态") TaskStatus status
) {
Task task = new Task(title, priority, status);
database.save(task);
return "任务已创建,ID: " + task.getId();
}
@Tool("更新任务优先级")
public String updatePriority(
@P("任务ID") String taskId,
@P("新的优先级") Priority newPriority
) {
Task task = database.findTask(taskId);
task.setPriority(newPriority);
return String.format("任务 %s 优先级已更新为 %s",
taskId, newPriority.description);
}
}
POJO(Plain Old Java Object)
public class OrderTools {
// 定义请求POJO
public static class CreateOrderRequest {
public String customerId;
public List<OrderItem> items;
public String shippingAddress;
public PaymentMethod paymentMethod;
public static class OrderItem {
public String productId;
public int quantity;
public double price;
}
public enum PaymentMethod {
CREDIT_CARD, PAYPAL, ALIPAY, WECHAT_PAY
}
}
@Tool("创建新订单")
public String createOrder(
@P("订单详细信息,包含客户ID、商品列表、配送地址、支付方式")
CreateOrderRequest request
) {
// 验证订单
if (request.items.isEmpty()) {
return "错误:订单必须包含至少一个商品";
}
// 计算总价
double total = request.items.stream()
.mapToDouble(item -> item.price * item.quantity)
.sum();
// 保存订单
Order order = new Order(request);
database.save(order);
return String.format("订单创建成功!订单号:%s,总金额:%.2f元",
order.getId(), total);
}
}
集合类型
public class BatchTools {
@Tool("批量查询用户信息")
public String batchGetUsers(
@P("用户ID列表,多个ID用逗号分隔") List<String> userIds
) {
List<User> users = database.findByIds(userIds);
return users.stream()
.map(User::toString)
.collect(Collectors.joining("\n"));
}
@Tool("标记多个任务为完成")
public String completeTasks(
@P("要完成的任务ID列表") List<String> taskIds
) {
int count = 0;
for (String taskId : taskIds) {
Task task = database.findTask(taskId);
if (task != null) {
task.setStatus(TaskStatus.DONE);
count++;
}
}
return String.format("成功完成 %d 个任务", count);
}
}
Optional参数
public class SearchTools {
@Tool("搜索文章")
public String searchArticles(
@P("搜索关键词,必填") String keyword,
@P("分类,可选,不指定则搜索所有分类") Optional<String> category,
@P("作者,可选") Optional<String> author,
@P("最大结果数,可选,默认10") Optional<Integer> limit
) {
SearchQuery query = new SearchQuery(keyword);
category.ifPresent(query::setCategory);
author.ifPresent(query::setAuthor);
int maxResults = limit.orElse(10);
List<Article> results = database.search(query, maxResults);
return String.format("找到 %d 篇文章", results.size());
}
}
4️⃣ Tool错误处理
Tool执行可能失败,需要优雅地处理错误:
public class DatabaseTools {
@Tool("执行SQL查询")
public String executeQuery(@P("SQL查询语句") String sql) {
try {
// 验证SQL
if (sql.trim().toUpperCase().startsWith("DROP") ||
sql.trim().toUpperCase().startsWith("DELETE")) {
return "错误:不允许执行危险操作(DROP/DELETE)";
}
// 执行查询
List<Map<String, Object>> results = database.query(sql);
if (results.isEmpty()) {
return "查询成功,但没有找到数据";
}
// 格式化结果
return formatResults(results);
} catch (SQLException e) {
// 返回清晰的错误信息,LLM会看到并调整策略
return "SQL错误: " + e.getMessage() +
"\n提示:请检查表名和列名是否正确";
} catch (Exception e) {
return "执行失败: " + e.getMessage();
}
}
@Tool("检查数据库连接")
public String checkConnection() {
try {
boolean isConnected = database.ping();
if (isConnected) {
return "数据库连接正常";
} else {
return "警告:数据库连接失败,请检查配置";
}
} catch (Exception e) {
return "无法连接到数据库: " + e.getMessage();
}
}
}
错误处理最佳实践:
public class APITools {
@Tool("调用第三方API")
public String callExternalAPI(
@P("API端点") String endpoint,
@P("请求参数JSON") String params
) {
try {
// 1. 参数验证
if (!endpoint.startsWith("https://")) {
return "错误:只允许HTTPS请求";
}
// 2. 业务逻辑
HttpResponse response = httpClient.post(endpoint, params);
// 3. 结果检查
if (response.statusCode() != 200) {
return String.format(
"API调用失败:HTTP %d - %s",
response.statusCode(),
response.body()
);
}
return "API调用成功: " + response.body();
} catch (JsonParseException e) {
// 4. 具体的错误类型
return "JSON格式错误: " + e.getMessage() +
"\n正确格式示例: {\"key\": \"value\"}";
} catch (TimeoutException e) {
return "请求超时,请稍后重试";
} catch (Exception e) {
// 5. 通用错误兜底
return "未知错误: " + e.getMessage();
}
}
}
5️⃣ Tool链接:多个Tool协同工作
真实场景中,经常需要多个Tool配合:
public class WorkflowTools {
private final WeatherAPI weatherAPI;
private final EmailService emailService;
private final CalendarService calendarService;
// Tool 1: 查询天气
@Tool("查询指定城市的天气预报")
public String getWeather(
@P("城市名称,如:北京、上海、深圳") String city,
@P("日期,格式 yyyy-MM-dd") String date
) {
WeatherInfo weather = weatherAPI.getWeather(city, date);
return String.format(
"城市:%s,日期:%s,天气:%s,温度:%d°C,降雨概率:%d%%",
city, date, weather.getCondition(),
weather.getTemperature(), weather.getRainProbability()
);
}
// Tool 2: 发送邮件
@Tool("发送邮件通知")
public String sendEmail(
@P("收件人邮箱地址") String to,
@P("邮件主题") String subject,
@P("邮件内容") String body
) {
try {
emailService.send(to, subject, body);
return "邮件已发送到: " + to;
} catch (Exception e) {
return "发送失败: " + e.getMessage();
}
}
// Tool 3: 添加日历事件
@Tool("在日历中添加事件")
public String addCalendarEvent(
@P("事件标题") String title,
@P("开始时间,格式 yyyy-MM-dd HH:mm") String startTime,
@P("事件描述") String description
) {
Event event = new Event(title, startTime, description);
calendarService.addEvent(event);
return "日历事件已添加: " + title;
}
}
// Agent会自动链接这些Tool:
// 用户: "查下明天北京天气,如果下雨提醒我带伞,并加到日历"
//
// LLM推理过程:
// 1. 调用 getWeather("北京", "2024-03-16")
// → 结果: "降雨概率:80%"
// 2. 判断:降雨概率高,需要提醒
// 3. 调用 sendEmail("user@example.com", "明天记得带伞", "...")
// 4. 调用 addCalendarEvent("带雨伞", "2024-03-16 08:00", "...")
// 5. 整合回答: "明天北京有雨,已发邮件提醒并添加到日历"
Tool依赖关系示例:
public class DataPipelineTools {
@Tool("从数据库提取数据")
public String extractData(@P("SQL查询") String sql) {
List<Record> data = database.query(sql);
// 返回JSON格式,供下一个Tool使用
return JsonUtils.toJson(data);
}
@Tool("转换数据格式")
public String transformData(
@P("输入数据,JSON格式") String inputJson,
@P("转换规则,如:rename_column, filter_rows") String rule
) {
List<Record> records = JsonUtils.fromJson(inputJson);
List<Record> transformed = applyRule(records, rule);
return JsonUtils.toJson(transformed);
}
@Tool("加载数据到目标系统")
public String loadData(
@P("数据,JSON格式") String dataJson,
@P("目标表名") String tableName
) {
List<Record> records = JsonUtils.fromJson(dataJson);
int count = targetDB.bulkInsert(tableName, records);
return String.format("成功加载 %d 条记录到表 %s", count, tableName);
}
}
// ETL流程自动链接:
// 用户: "把users表的数据导出,过滤掉已删除的,然后加载到备份库"
//
// LLM自动构建流程:
// 1. extractData("SELECT * FROM users")
// 2. transformData(上一步结果, "filter_rows: deleted = false")
// 3. loadData(上一步结果, "users_backup")
6️⃣ 动态Tool注册
有时需要在运行时动态添加Tool:
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
public class DynamicToolExample {
public void setupDynamicTools() {
// 定义Tool规格
ToolSpecification weatherTool = ToolSpecification.builder()
.name("getWeather")
.description("获取指定城市的天气信息")
.addParameter("city", "string", "城市名称")
.addParameter("date", "string", "日期,格式yyyy-MM-dd")
.build();
// 定义Tool执行器
ToolExecutor weatherExecutor = (ToolExecutionRequest request) -> {
String city = request.argument("city");
String date = request.argument("date");
// 执行实际逻辑
return weatherAPI.get(city, date).toString();
};
// 注册到Agent
AiServices<?> aiService = AiServices.builder(MyAssistant.class)
.chatLanguageModel(model)
.tools(weatherTool, weatherExecutor)
.build();
}
// 插件化Tool系统
public class PluginManager {
private Map<String, ToolSpecification> toolSpecs = new HashMap<>();
private Map<String, ToolExecutor> toolExecutors = new HashMap<>();
public void registerPlugin(String pluginName,
ToolSpecification spec,
ToolExecutor executor) {
toolSpecs.put(pluginName, spec);
toolExecutors.put(pluginName, executor);
System.out.println("插件已注册: " + pluginName);
}
public void unregisterPlugin(String pluginName) {
toolSpecs.remove(pluginName);
toolExecutors.remove(pluginName);
System.out.println("插件已卸载: " + pluginName);
}
public AiServices<?> buildAgent() {
return AiServices.builder(MyAssistant.class)
.chatLanguageModel(model)
.tools(toolSpecs.values(), toolExecutors.values())
.build();
}
}
}
7️⃣ MCP:Tool的标准化协议
前面学的 @Tool 是 LangChain4J 私有的工具定义方式。如果换个框架(LangChain Python、Spring AI、Semantic Kernel),工具定义方式完全不同,工具不能复用。
MCP(Model Context Protocol) 是 Anthropic 在 2024 年底开源的工具接口标准协议,解决的就是这个问题。
MCP 的本质:给 Tool 定义一个"USB接口"
没有 MCP(现状):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ LangChain4J │ │ Spring AI │ │ LangChain │
│ @Tool注解 │ │ @Bean注入 │ │ @tool装饰器 │
│ Java方法 │ │ Java方法 │ │ Python函数 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
天气Tool(Java) 天气Tool(Java) 天气Tool(Python)
↑ 完全不同的实现! ↑ 又写一遍! ↑ 再写一遍!
有了 MCP:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ LangChain4J │ │ Spring AI │ │ LangChain │
│ MCP Client │ │ MCP Client │ │ MCP Client │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────┬───────┴───────────────────┘
│ 统一的 MCP 协议(JSON-RPC)
▼
┌──────────────┐
│ MCP Server │ ← 天气工具只实现一次!
│ 天气服务 │ 任何框架都能调用
└──────────────┘
MCP 架构:三层分离
┌──────────────────────────────────────────────────────────┐
│ AI 应用层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Claude │ │ GPT Agent │ │ 你的Agent │ │
│ │ Desktop │ │ │ │ (LangChain4J)│ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
│ ┌─────┴───────────────┴───────────────┴──────┐ │
│ │ MCP Client(协议层) │ │
│ │ discover() → 发现可用工具 │ │
│ │ call() → 调用工具 │ │
│ │ 结果 ← 接收返回 │ │
│ └─────┬───────────────┬───────────────┬──────┘ │
│ │ │ │ │
└────────┼───────────────┼───────────────┼─────────────────┘
│ JSON-RPC │ JSON-RPC │ JSON-RPC
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ MCP Server │ │ MCP Server │ │ MCP Server │
│ 天气服务 │ │ 数据库查询 │ │ 文件系统 │
│ (Node.js) │ │ (Python) │ │ (Go) │
└────────────┘ └────────────┘ └────────────┘
独立进程 独立进程 独立进程
@Tool vs MCP:本质区别
// ===== 方式A:@Tool(紧耦合,编译时绑定)=====
// 工具代码和Agent代码在同一个JVM进程中
@Tool("查询天气")
public String getWeather(@P("城市") String city) {
return weatherAPI.get(city); // 直接调用,同进程
}
// 构建时绑定:
AiServices.builder(MyAgent.class)
.tools(new WeatherTools()) // ← 工具对象直接传入
.build();
// ===== 方式B:MCP(松耦合,运行时发现)=====
// 工具代码运行在独立进程/机器上
// MCP Server 端(可以是任何语言):
// weather-server.js
server.tool("getWeather", {
description: "查询天气",
parameters: {
city: { type: "string", description: "城市" }
}
}, async (args) => {
return await weatherAPI.get(args.city); // 独立进程
});
// MCP Client 端(LangChain4J中):
// 运行时动态发现并调用:
McpClient client = McpClient.connect("weather-server");
List<ToolSpecification> tools = client.listTools(); // ← 动态发现!
// Agent 使用这些 tools 就像使用 @Tool 一样
核心对比
┌─────────────┬────────────────────────┬────────────────────────┐
│ │ @Tool(LangChain4J) │ MCP │
├─────────────┼────────────────────────┼────────────────────────┤
│ 绑定时机 │ 编译时(注解扫描) │ 运行时(协议发现) │
│ 运行位置 │ 同一个 JVM 进程 │ 独立进程(可跨机器) │
│ 语言限制 │ 只能 Java │ 任何语言 │
│ 工具复用 │ ❌ 框架绑定 │ ✅ 跨框架跨语言 │
│ 部署方式 │ 和应用一起打包 │ 独立部署,独立扩缩 │
│ 调用开销 │ 低(方法调用) │ 中(进程间通信) │
│ 适合场景 │ 简单/内部工具 │ 共享/标准化/生态工具 │
│ 类比 │ Java 本地方法调用 │ HTTP API / gRPC 调用 │
└─────────────┴────────────────────────┴────────────────────────┘
一句话:@Tool 是"函数调用",MCP 是"远程服务调用"。
MCP 的工具描述格式
@Tool 在 LangChain4J 中的转换链:
@Tool注解 → ToolSpecification → OpenAI Function Calling JSON
(Java 编译时) (框架内部) (发给 LLM API)
MCP 的转换链:
MCP Server 声明 → JSON Schema → LLM Function Calling JSON
(任何语言实现) (标准协议) (发给 LLM API)
两者最终发给 LLM 的格式是一样的!都是 JSON Schema:
{
"name": "getWeather",
"description": "查询天气",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市" }
}
}
}
所以 LLM 并不关心工具是 @Tool 定义的还是 MCP 定义的。
LLM 只看到 JSON Schema 描述 → 决定是否调用 → 返回调用请求。
区别仅在于"谁执行这个调用":
@Tool → JVM 内反射调用你的 Java 方法
MCP → 通过协议发送到外部 MCP Server
MCP 解决了什么问题
问题1:工具碎片化
❌ 每个框架自己的工具格式,社区无法共享
✅ MCP 统一格式,写一次到处用
问题2:工具与应用强绑定
❌ 天气工具写在你的 Java 应用里,Python 团队用不了
✅ MCP Server 独立运行,任何 Client 都能调用
问题3:工具生态建设
❌ 每个LLM应用都要自己实现一套工具
✅ MCP 社区共享工具库(GitHub、Slack、Jira...已有现成Server)
问题4:安全与权限
❌ @Tool 在同进程,工具有应用的全部权限
✅ MCP Server 独立进程,可以精细控制权限
LangChain4J 中使用 MCP
/**
* LangChain4J 集成 MCP 工具
* 工具发现和调用对 Agent 完全透明
*/
@Configuration
public class McpIntegrationConfig {
@Bean
public MyAgent agentWithMcpTools(ChatLanguageModel model) {
// 1. 连接 MCP Server
McpClient weatherServer = McpClient.builder()
.transport(new StdioTransport("node", "weather-server.js"))
.build();
McpClient dbServer = McpClient.builder()
.transport(new SseTransport("http://localhost:3001/mcp"))
.build();
// 2. 从 MCP Server 动态获取工具定义
List<ToolSpecification> mcpTools = new ArrayList<>();
mcpTools.addAll(weatherServer.listTools()); // 天气相关工具
mcpTools.addAll(dbServer.listTools()); // 数据库相关工具
// 3. 也可以混合使用 @Tool 和 MCP 工具
LocalTools localTools = new LocalTools(); // 本地 @Tool 工具
// 4. 构建 Agent(同时拥有本地工具和MCP工具)
return AiServices.builder(MyAgent.class)
.chatLanguageModel(model)
.tools(localTools) // 本地 @Tool 工具
.tools(mcpTools) // MCP 远程工具
.build();
}
}
// Agent 使用时完全无感知工具来源
// LLM 根据描述选择工具,框架自动路由到本地方法或MCP Server
什么时候用 @Tool,什么时候用 MCP?
选择 @Tool 当:
├─ 工具逻辑简单,和业务代码紧密相关
├─ 只在一个应用中使用
├─ 需要最低延迟(同进程调用)
├─ 团队统一使用 Java / LangChain4J
└─ 例:内部业务规则计算、数据格式转换
选择 MCP 当:
├─ 工具需要被多个应用 / 团队复用
├─ 工具需要独立部署和扩缩容
├─ 工具用非 Java 语言实现更方便
├─ 想利用社区现成的 MCP Server(GitHub、Slack、数据库...)
├─ 需要精细的权限控制和审计
└─ 例:公司统一的数据查询服务、第三方集成
实际项目中,两者经常混合使用:
本地 @Tool → 业务逻辑、数据转换、简单计算
MCP Server → 外部服务集成、跨团队共享工具、社区工具
💡 本质理解:
@Tool和 MCP 不是替代关系,而是不同层次的抽象。@Tool 是代码级的工具定义("函数"),MCP 是协议级的工具接口("服务")。就像 Java 方法调用 vs REST API — 两者可以共存,各有适用场景。LLM 不关心工具来自哪里,它只看 JSON Schema 描述。
8️⃣ 四大模式对比:RAG vs @Tool vs MCP vs Fine-tuning
学到这里,我们已经接触了扩展 LLM 能力的四种核心模式。它们解决的是同一个根本问题:LLM 自身的知识和能力是有限的,如何突破这个限制?
四种模式,四种思路
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ LLM 的两大限制: │
│ 1. 知识有限 — 训练数据截止到某个时间,不知道你的私有数据 │
│ 2. 能力有限 — 不能查数据库、不能发邮件、不能调API │
│ │
│ 四种解法: │
│ │
│ ┌─────────────┐ 解决知识限制 │
│ │ Fine-tuning │ 把知识"烧"进模型权重 ← 改变模型本身 │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ 解决知识限制 │
│ │ RAG │ 检索后注入上下文 ← 不改模型,改输入 │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ 解决能力限制 │
│ │ @Tool │ 给LLM装备函数 ← 不改模型,加工具 │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ 解决能力限制 + 工具标准化 │
│ │ MCP │ 标准化工具协议 ← 不改模型,加标准化工具 │
│ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
全维度对比
┌──────────┬─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│ │ Fine-tuning │ RAG │ @Tool │ MCP │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 一句话 │ 改造大脑 │ 给一本参考书 │ 给一套工具箱 │ 给一个工具商店 │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 解决什么 │ 知识/风格/能力 │ 知识 │ 能力 │ 能力 + 标准化 │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 修改对象 │ 模型权重 │ 输入Prompt │ 输出Action │ 输出Action │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 数据新鲜度│ ❌ 训练时固定 │ ✅ 实时更新 │ ✅ 实时 │ ✅ 实时 │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 实现成本 │ 高(GPU训练) │ 中(向量库) │ 低(写代码) │ 中(Server开发)│
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 运行成本 │ 低(直接生成) │ 中(检索+生成) │ 高(多轮调用) │ 高(多轮+RPC) │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 延迟 │ 最低 │ 中 │ 高(Agent循环) │ 较高(+网络) │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 可复用性 │ ❌ 模型专属 │ ❌ 应用专属 │ ❌ 框架绑定 │ ✅ 跨框架跨语言 │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ LLM感知 │ 无感知(内化了) │ 被动(自动注入) │ 主动(LLM决策) │ 主动(LLM决策) │
├──────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 可解释性 │ ❌ 黑盒 │ ✅ 可引用来源 │ ✅ 可追踪调用 │ ✅ 可追踪调用 │
└──────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┘
本质区别:知识在哪里,能力在哪里
// ===== Fine-tuning:知识在模型里 =====
// 训练后,模型"记住了"你的数据
String answer = model.generate("公司年假政策是什么?");
// → 直接回答(因为训练数据里有)
// 特点:不需要额外步骤,但知识不能更新
// ===== RAG:知识在数据库里 =====
// 每次提问,先检索再回答
List<TextSegment> docs = retriever.retrieve("年假政策"); // ← 额外步骤
String augmented = "根据以下文档回答:" + docs + "\n问题:年假政策?";
String answer = model.generate(augmented);
// → 基于检索到的文档回答
// 特点:需要向量库,但知识可实时更新
// ===== @Tool:能力在本地方法里 =====
// LLM 决定调用哪个工具
@Tool("查询员工年假余额")
public String getLeaveBalance(@P("员工ID") String empId) {
return hrSystem.query(empId); // ← 同JVM直接调用
}
// → LLM自主决定:"需要调用 getLeaveBalance"
// 特点:LLM主动选择,工具在同进程
// ===== MCP:能力在远程服务里 =====
// LLM 决定调用哪个工具,工具在外部Server
// MCP Server (独立部署的HR服务)
server.tool("getLeaveBalance", schema, async (args) => {
return await hrAPI.query(args.empId); // ← 独立进程
});
// → LLM同样自主决定,但执行在远程
// 特点:工具可跨应用共享,独立部署
流程对比图
Fine-tuning(最短路径):
用户问题 → LLM直接回答
════════════════════════
只有1步,但知识可能过时
RAG(先检索再生成):
用户问题 → 向量化 → 检索数据库 → 注入Prompt → LLM回答
════════════════════════════════════════════════════════
每次都检索,LLM是被动的
@Tool(LLM主动调用):
用户问题 → LLM推理 → 选择Tool → 执行(同JVM) → 观察结果 → ... → LLM回答
════════════════════════════════════════════════════════════════════════
多轮循环,LLM是主动的
MCP(LLM主动调用远程服务):
用户问题 → LLM推理 → 选择Tool → JSON-RPC→MCP Server → 结果 → ... → LLM回答
════════════════════════════════════════════════════════════════════════════
和@Tool一样的循环,但工具执行在远端
RAG vs Tool:最容易混淆的两个
关键区别:谁决定"要不要检索/调用"?
RAG:
每次都检索,不管用户问什么
┌─────────────┐
│ "你好" │ → 也会去检索文档 → 找不到相关的 → LLM回答"你好"
│ "年假多少天" │ → 检索文档 → 找到相关内容 → 基于文档回答
└─────────────┘
LLM 不需要决策,框架自动检索
Tool(@Tool / MCP):
LLM自己决定要不要调用
┌─────────────┐
│ "你好" │ → LLM判断:不需要工具 → 直接回答
│ "查下年假" │ → LLM判断:需要查HR系统 → 调用getLeaveBalance
│ "查天气+发邮件"│ → LLM判断:先调天气,再调邮件 → 多步执行
└─────────────┘
LLM 是决策者
所以:
RAG = 被动增强(每次都做,成本固定)
Tool = 主动调用(按需触发,成本不确定)
类比:
RAG = 开卷考试(每次都带参考书,不管用不用)
Tool = 有手机的考试(需要时自己决定查什么)
组合使用:实际项目中的架构
实际项目往往不是选其中一个,而是组合使用:
┌─────────────────────────────────────────────────┐
│ 你的 AI 应用 │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ AiServices (Agent) │ │
│ │ │ │
│ │ ┌─────────┐ ┌──────────┐ ┌────────────┐ │ │
│ │ │ RAG │ │ @Tool │ │ MCP Tools │ │ │
│ │ │知识检索 │ │本地工具 │ │远程工具 │ │ │
│ │ └────┬────┘ └────┬─────┘ └─────┬──────┘ │ │
│ │ │ │ │ │ │
│ └───────┼────────────┼──────────────┼──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 向量数据库 Java方法调用 MCP Server │
│ (公司文档) (业务逻辑) (GitHub/Slack) │
│ │
│ + Fine-tuned 模型(如果需要特定领域风格) │
└─────────────────────────────────────────────────────┘
常见组合:
├─ RAG + @Tool:知识库问答 + 业务操作(最常见)
├─ RAG + MCP:知识库 + 外部服务集成
├─ @Tool + MCP:本地工具 + 远程工具混合
└─ Fine-tuning + RAG:领域模型 + 实时知识(最强但最贵)
选型决策树
你的需求是什么?
│
├─ 让LLM知道私有数据(文档、知识库)
│ ├─ 数据量大,经常更新 → RAG ✅
│ ├─ 数据量小,变化少 → Fine-tuning
│ └─ 只有几篇文档 → 直接放进Prompt(长上下文)
│
├─ 让LLM执行操作(查数据库、调API、发邮件)
│ ├─ 只在一个Java应用中使用 → @Tool ✅
│ ├─ 需要跨应用/跨团队共享 → MCP ✅
│ └─ 社区已有现成实现 → MCP(用社区Server)
│
├─ 让LLM掌握特定风格/术语
│ └─ Fine-tuning ✅(唯一选择)
│
└─ 构建复杂AI应用
└─ 通常需要组合:RAG + @Tool + MCP
💡 终极理解:这四种模式本质上回答的是同一个问题 — "如何让LLM做它原本做不到的事"。Fine-tuning 改变了模型本身;RAG 改变了模型的输入;Tool 和 MCP 改变了模型的输出(让它可以触发外部动作)。理解了这个区别,选型就变得简单了。
🎯 Tool设计原则
原则1:单一职责
// ❌ 不好:一个Tool做太多事
@Tool("处理用户请求")
public String handleUser(String action, String userId, String data) {
if (action.equals("create")) {
// 创建逻辑
} else if (action.equals("update")) {
// 更新逻辑
} else if (action.equals("delete")) {
// 删除逻辑
}
// ...
}
// ✅ 好:每个Tool做一件事
@Tool("创建新用户")
public String createUser(@P("用户信息JSON") String userJson) { ... }
@Tool("更新用户信息")
public String updateUser(@P("用户ID") String id, @P("更新数据") String data) { ... }
@Tool("删除用户")
public String deleteUser(@P("用户ID") String userId) { ... }
原则2:清晰的描述
// ❌ 描述太简略
@Tool("获取数据")
public String getData(String id) { ... }
// ✅ 描述详细准确
@Tool("根据订单ID获取订单详情,包含商品列表、价格、状态等完整信息")
public String getOrderDetails(@P("订单ID,格式为ORD-xxxxxxxx") String orderId) { ... }
原则3:合理的粒度
public class FileTools {
// ✅ 合理粒度:基础操作
@Tool("读取文件内容")
public String readFile(@P("文件路径") String path) { ... }
@Tool("写入文件")
public String writeFile(@P("文件路径") String path, @P("内容") String content) { ... }
@Tool("删除文件")
public String deleteFile(@P("文件路径") String path) { ... }
// ✅ 也可以提供高级封装
@Tool("备份文件到指定目录")
public String backupFile(
@P("源文件路径") String sourcePath,
@P("备份目录") String backupDir
) {
String content = readFile(sourcePath);
String backupPath = backupDir + "/" + getFileName(sourcePath);
writeFile(backupPath, content);
return "文件已备份到: " + backupPath;
}
}
原则4:可预测的返回值
public class ConsistentTools {
// ✅ 返回格式一致
@Tool("查询用户")
public String findUser(@P("用户ID") String userId) {
User user = database.findById(userId);
if (user == null) {
return "错误:用户不存在,ID: " + userId;
}
return "用户信息: " + user.toJson();
}
// ✅ 状态码 + 消息
@Tool("创建文章")
public String createArticle(@P("文章内容") ArticleRequest request) {
try {
Article article = service.create(request);
return "成功:文章已创建,ID: " + article.getId();
} catch (ValidationException e) {
return "验证失败:" + e.getMessage();
} catch (Exception e) {
return "错误:" + e.getMessage();
}
}
}
🚀 实战:构建多工具Agent
完整示例:个人助理Agent,整合天气、日历、邮件功能。
// 1. 定义Tool服务类
public class PersonalAssistantTools {
private final WeatherAPI weatherAPI;
private final GoogleCalendar calendar;
private final EmailService emailService;
private final TaskDatabase taskDB;
public PersonalAssistantTools() {
this.weatherAPI = new WeatherAPI();
this.calendar = new GoogleCalendar();
this.emailService = new EmailService();
this.taskDB = new TaskDatabase();
}
// === 天气相关 ===
@Tool("获取当前天气")
public String getCurrentWeather(@P("城市名称") String city) {
WeatherInfo weather = weatherAPI.getCurrent(city);
return String.format(
"【%s 当前天气】\n" +
"天气:%s\n" +
"温度:%d°C\n" +
"湿度:%d%%\n" +
"风力:%s",
city, weather.condition, weather.temp,
weather.humidity, weather.wind
);
}
@Tool("获取未来天气预报")
public String getWeatherForecast(
@P("城市名称") String city,
@P("天数,1-7天") int days
) {
List<WeatherInfo> forecast = weatherAPI.getForecast(city, days);
StringBuilder result = new StringBuilder();
result.append(String.format("【%s 未来%d天天气】\n", city, days));
for (WeatherInfo w : forecast) {
result.append(String.format(
"%s: %s, %d-%d°C, 降雨%d%%\n",
w.date, w.condition, w.tempMin, w.tempMax, w.rainChance
));
}
return result.toString();
}
// === 日历相关 ===
@Tool("查看今天的日程")
public String getTodaySchedule() {
LocalDate today = LocalDate.now();
List<Event> events = calendar.getEvents(today);
if (events.isEmpty()) {
return "今天没有安排的日程";
}
StringBuilder sb = new StringBuilder("【今日日程】\n");
for (Event event : events) {
sb.append(String.format(
"• %s - %s: %s\n",
event.startTime, event.endTime, event.title
));
}
return sb.toString();
}
@Tool("添加日程到日历")
public String addEvent(
@P("日程标题") String title,
@P("开始时间,格式:yyyy-MM-dd HH:mm") String startTime,
@P("持续时间(分钟)") int durationMinutes,
@P("描述,可选") String description
) {
Event event = Event.builder()
.title(title)
.startTime(LocalDateTime.parse(startTime,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")))
.duration(durationMinutes)
.description(description)
.build();
calendar.addEvent(event);
return String.format("已添加日程:%s (%s)", title, startTime);
}
@Tool("检查时间段是否有空")
public String checkAvailability(
@P("日期,格式:yyyy-MM-dd") String date,
@P("开始时间,格式:HH:mm") String startTime,
@P("结束时间,格式:HH:mm") String endTime
) {
boolean available = calendar.isAvailable(date, startTime, endTime);
if (available) {
return String.format("时间段 %s %s-%s 有空", date, startTime, endTime);
} else {
return String.format("时间段 %s %s-%s 已有安排", date, startTime, endTime);
}
}
// === 邮件相关 ===
@Tool("发送邮件")
public String sendEmail(
@P("收件人邮箱") String to,
@P("邮件主题") String subject,
@P("邮件正文") String body
) {
try {
emailService.send(Email.builder()
.to(to)
.subject(subject)
.body(body)
.build());
return "邮件已发送到: " + to;
} catch (Exception e) {
return "发送失败: " + e.getMessage();
}
}
@Tool("查看最新邮件")
public String getRecentEmails(@P("邮件数量,最多20") int count) {
List<Email> emails = emailService.getRecent(Math.min(count, 20));
StringBuilder sb = new StringBuilder("【最新邮件】\n");
for (Email email : emails) {
sb.append(String.format(
"来自:%s\n主题:%s\n时间:%s\n%s\n\n",
email.from, email.subject, email.time,
email.isRead ? "[已读]" : "[未读]"
));
}
return sb.toString();
}
// === 任务管理 ===
@Tool("创建待办任务")
public String createTask(
@P("任务标题") String title,
@P("优先级:LOW/MEDIUM/HIGH/CRITICAL") Priority priority,
@P("截止日期,格式:yyyy-MM-dd,可选") String dueDate
) {
Task task = Task.builder()
.title(title)
.priority(priority)
.dueDate(dueDate != null ? LocalDate.parse(dueDate) : null)
.status(TaskStatus.TODO)
.build();
taskDB.save(task);
return String.format("任务已创建:%s [%s]", title, priority);
}
@Tool("查看待办任务列表")
public String listTasks(@P("状态过滤,可选:TODO/IN_PROGRESS/DONE") String status) {
List<Task> tasks;
if (status != null) {
tasks = taskDB.findByStatus(TaskStatus.valueOf(status));
} else {
tasks = taskDB.findAll();
}
if (tasks.isEmpty()) {
return "没有找到任务";
}
StringBuilder sb = new StringBuilder("【任务列表】\n");
for (Task task : tasks) {
sb.append(String.format(
"• [%s] %s - %s %s\n",
task.priority, task.title, task.status,
task.dueDate != null ? "(截止: " + task.dueDate + ")" : ""
));
}
return sb.toString();
}
@Tool("完成任务")
public String completeTask(@P("任务标题或ID") String taskIdentifier) {
Task task = taskDB.findByTitleOrId(taskIdentifier);
if (task == null) {
return "错误:未找到任务 " + taskIdentifier;
}
task.setStatus(TaskStatus.DONE);
task.setCompletedAt(LocalDateTime.now());
taskDB.update(task);
return String.format("任务已完成:%s", task.title);
}
}
// 2. 创建Agent接口
interface PersonalAssistant {
String chat(String message);
}
// 3. 构建Agent
public class PersonalAssistantDemo {
public static void main(String[] args) {
// 初始化工具
PersonalAssistantTools tools = new PersonalAssistantTools();
// 创建语言模型
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.temperature(0.7)
.build();
// 构建Agent
PersonalAssistant assistant = AiServices.builder(PersonalAssistant.class)
.chatLanguageModel(model)
.tools(tools)
.chatMemory(MessageWindowChatMemory.withMaxMessages(20))
.build();
// 测试场景
testScenarios(assistant);
}
private static void testScenarios(PersonalAssistant assistant) {
// 场景1:天气查询 + 日程安排
System.out.println("=== 场景1:智能日程安排 ===");
String response1 = assistant.chat(
"查一下明天北京的天气,如果不下雨的话," +
"帮我在明天下午3点安排一个2小时的户外团建活动"
);
System.out.println(response1);
// 场景2:任务管理 + 邮件
System.out.println("\n=== 场景2:任务提醒 ===");
String response2 = assistant.chat(
"创建一个高优先级任务'完成季度报告',截止日期3月20日," +
"然后给boss@company.com发邮件说我会按时完成"
);
System.out.println(response2);
// 场景3:复杂工作流
System.out.println("\n=== 场景3:一周工作规划 ===");
String response3 = assistant.chat(
"帮我规划下周的工作:" +
"1. 查看下周天气,选择一个好天气安排客户拜访\n" +
"2. 创建3个任务:写周报、准备演讲、代码review\n" +
"3. 检查周三下午2-5点是否有空,没空的话找其他时间"
);
System.out.println(response3);
}
}
运行效果:
=== 场景1:智能日程安排 ===
我已经查询了明天(3月16日)北京的天气,预报显示晴天,温度18-25°C,
降雨概率只有10%,非常适合户外活动!
我已经帮你在日历中添加了:
• 活动:户外团建活动
• 时间:2024-03-16 15:00-17:00
• 天气条件:晴天,适合户外
=== 场景2:任务提醒 ===
已为你完成以下操作:
1. ✅ 创建任务:完成季度报告 [HIGH] 截止:2024-03-20
2. ✅ 已发送邮件到 boss@company.com
主题:关于季度报告的进度
内容:您好,季度报告任务已在计划中,我会在3月20日前完成。
=== 场景3:一周工作规划 ===
下周工作已规划完成:
📅 客户拜访:安排在周二(3月19日),当天晴天,温度适宜
✅ 任务创建:
• 写周报 [MEDIUM] - 已创建
• 准备演讲 [HIGH] - 已创建
• 代码review [MEDIUM] - 已创建
⏰ 周三下午2-5点:已有会议安排(产品讨论会 14:00-16:00)
建议改为周四下午2-5点,该时段有空
📊 Tool监控和日志
生产环境需要监控Tool的执行情况:
public class MonitoredTools {
private final MetricsCollector metrics;
private final Logger logger = LoggerFactory.getLogger(MonitoredTools.class);
@Tool("查询数据库")
public String queryDatabase(@P("SQL语句") String sql) {
// 记录开始时间
long startTime = System.currentTimeMillis();
String toolName = "queryDatabase";
try {
logger.info("开始执行Tool: {}, SQL: {}", toolName, sql);
// 执行实际逻辑
List<Record> results = database.query(sql);
// 记录成功
long duration = System.currentTimeMillis() - startTime;
metrics.recordSuccess(toolName, duration);
logger.info("Tool执行成功: {}, 耗时: {}ms, 结果数: {}",
toolName, duration, results.size());
return formatResults(results);
} catch (Exception e) {
// 记录失败
long duration = System.currentTimeMillis() - startTime;
metrics.recordFailure(toolName, duration, e);
logger.error("Tool执行失败: {}, 耗时: {}ms, 错误: {}",
toolName, duration, e.getMessage(), e);
return "查询失败: " + e.getMessage();
}
}
}
// 指标收集器
class MetricsCollector {
private final Map<String, ToolMetrics> metricsMap = new ConcurrentHashMap<>();
public void recordSuccess(String toolName, long durationMs) {
metricsMap.computeIfAbsent(toolName, k -> new ToolMetrics())
.recordSuccess(durationMs);
}
public void recordFailure(String toolName, long durationMs, Exception e) {
metricsMap.computeIfAbsent(toolName, k -> new ToolMetrics())
.recordFailure(durationMs, e);
}
public void printReport() {
System.out.println("=== Tool执行报告 ===");
metricsMap.forEach((name, metrics) -> {
System.out.printf(
"Tool: %s\n" +
" 总调用: %d 次\n" +
" 成功率: %.2f%%\n" +
" 平均耗时: %.2f ms\n" +
" 最大耗时: %d ms\n",
name, metrics.totalCalls, metrics.getSuccessRate(),
metrics.getAvgDuration(), metrics.maxDuration
);
});
}
}
// Tool指标
class ToolMetrics {
int totalCalls = 0;
int successCount = 0;
int failureCount = 0;
long totalDuration = 0;
long maxDuration = 0;
List<Exception> errors = new ArrayList<>();
synchronized void recordSuccess(long duration) {
totalCalls++;
successCount++;
totalDuration += duration;
maxDuration = Math.max(maxDuration, duration);
}
synchronized void recordFailure(long duration, Exception e) {
totalCalls++;
failureCount++;
totalDuration += duration;
errors.add(e);
}
double getSuccessRate() {
return totalCalls == 0 ? 0 : (successCount * 100.0 / totalCalls);
}
double getAvgDuration() {
return totalCalls == 0 ? 0 : (totalDuration * 1.0 / totalCalls);
}
}
📝 练习题
练习1:设计购物助手Tool
为电商网站设计一套Tool,支持:
- 搜索商品(按关键词、分类、价格区间)
- 查看商品详情
- 添加到购物车
- 查看购物车
- 下单
要求:
- 每个Tool单一职责
- 参数描述清晰
- 错误处理完善
练习2:实现Tool链接
创建一个"智能行程规划"功能,整合:
- 天气查询Tool
- 景点推荐Tool
- 酒店搜索Tool
- 行程生成Tool
用户输入:"帮我规划3天北京旅游",Agent自动:
- 查询3天天气
- 根据天气推荐景点
- 搜索附近酒店
- 生成完整行程
练习3:监控和优化
为现有Tool添加:
- 执行时间监控
- 调用次数统计
- 错误率追踪
- 慢查询告警(>1s)
- 生成日报表
🎓 总结
本节学习了Tool的完整体系:
✅ 核心概念
- Tool是Agent的能力扩展
- @Tool和@P注解是基础
- LLM根据描述自主选择和调用
✅ 最佳实践
- 单一职责原则
- 清晰的描述和参数说明
- 优雅的错误处理
- 合理的粒度划分
✅ 高级特性
- 支持丰富的参数类型(基本类型、枚举、POJO)
- Tool自动链接形成工作流
- 动态Tool注册
- 监控和日志
- MCP标准化协议 — 跨框架跨语言工具复用
通过合理设计Tool,你的Agent可以:
- 🔍 查询各种数据源
- ✉️ 调用外部API和服务
- 📝 操作文件和数据库
- 🤖 执行复杂的业务逻辑
- 🔗 多Tool协同完成复杂任务
🚀 下一步
恭喜完成Tool定义和最佳实践的学习!
实践建议:
- 为你的业务场景设计一套完整的Tool体系
- 实现Tool监控和告警机制
- 测试Tool在不同LLM上的表现(GPT-4、Claude等)
- 优化Tool描述,提升选择准确率
记住:好的Tool设计是优秀Agent的基础。投入时间完善Tool,Agent的能力会成倍提升!