【从0到1构建一个ClaudeAgent】工具与执行-工具
这是 Agent 进化的关键一步:从“只会说话”变成了“真正干活”。
Java 实现代码
public class AgentWithTools {
// 配置
private static final Path WORKDIR = Paths.get(System.getProperty("user.dir"));
// --- 核心:工具定义与分发 ---
// 1. 定义工具枚举
public enum ToolType {
BASH("bash", "Run a shell command."),
READ_FILE("read_file", "Read file contents."),
WRITE_FILE("write_file", "Write content to file."),
EDIT_FILE("edit_file", "Replace exact text in file.");
// ... 省略构造器
}
// 2. 工具执行接口
@FunctionalInterface
interface ToolExecutor {
String execute(Map<String, Object> args) throws Exception;
}
// 3. 注册工具处理逻辑
private static final Map<String, ToolExecutor> TOOL_HANDLERS = new HashMap<>();
static {
TOOL_HANDLERS.put(ToolType.BASH.name, args -> {
String command = (String) args.get("command");
return runBash(command);
});
TOOL_HANDLERS.put(ToolType.READ_FILE.name, args -> {
String path = (String) args.get("path");
Integer limit = (Integer) args.get("limit");
return runRead(path, limit);
});
TOOL_HANDLERS.put(ToolType.WRITE_FILE.name, args -> {
String path = (String) args.get("path");
String content = (String) args.get("content");
return runWrite(path, content);
});
TOOL_HANDLERS.put(ToolType.EDIT_FILE.name, args -> {
String path = (String) args.get("path");
String oldText = (String) args.get("old_text");
String newText = (String) args.get("new_text");
return runEdit(path, oldText, newText);
});
}
// --- 核心循环 ---
public static void agentLoop(List<Map<String, Object>> messages) {
while (true) {
// ... 省略相同的 LLM 调用、消息追加逻辑
// 4. 执行工具
List<Map<String, Object>> toolResults = new ArrayList<>();
List<Map<String, Object>> content = (List<Map<String, Object>>) response.get("content");
for (Map<String, Object> block : content) {
if ("tool_use".equals(block.get("type"))) {
String toolName = (String) block.get("name"); // 关键新增
String toolId = (String) block.get("id");
Map<String, Object> inputArgs = (Map<String, Object>) block.get("input");
// 路由分发
ToolExecutor handler = TOOL_HANDLERS.get(toolName);
String output;
try {
if (handler != null) {
output = handler.execute(inputArgs);
} else {
output = "Error: Unknown tool " + toolName;
}
} catch (Exception e) {
output = "Error: " + e.getMessage();
}
System.out.println("> " + toolName + ": " + output.substring(0, Math.min(output.length(), 100)));
// ... 省略相同的工具结果构造逻辑
}
}
// ... 省略相同的回传逻辑
}
}
// --- 工具具体实现 ---
private static Path safePath(String p) throws IOException {
Path path = WORKDIR.resolve(p).normalize();
if (!path.startsWith(WORKDIR)) {
throw new IOException("Path escapes workspace: " + p);
}
return path;
}
// ... 省略与之前相同的 runBash 实现
private static String runRead(String pathStr, Integer limit) throws IOException {
Path path = safePath(pathStr);
String content = Files.readString(path);
if (limit != null && limit < content.length()) {
return content.substring(0, limit) + "... (truncated)";
}
return content;
}
private static String runWrite(String pathStr, String content) throws IOException {
Path path = safePath(pathStr);
Files.createDirectories(path.getParent());
Files.writeString(path, content);
return "Wrote " + content.length() + " bytes to " + pathStr;
}
private static String runEdit(String pathStr, String oldText, String newText) throws IOException {
Path path = safePath(pathStr);
String content = Files.readString(path);
if (!content.contains(oldText)) {
return "Error: Text not found in " + pathStr;
}
String newContent = content.replace(oldText, newText);
Files.writeString(path, newContent);
return "Edited " + pathStr;
}
}
这段代码相比 s01,最大的进步在于能力的扩展和安全边界。说白了就是,你可以像搭积木一样给 Agent 塞入各种工具函数,让它的能力边界随插件无限延伸。
这段代码应该已经很清晰,我这里就不多解释了
工具抽象框架(策略模式)
核心思想:从"硬编码工具"升级为"可插拔架构",实现工具与主循环的解耦。
// 工具枚举 - 集中定义所有可用工具
public enum ToolType {
BASH("bash", "Run a shell command."),
READ_FILE("read_file", "Read file contents."),
WRITE_FILE("write_file", "Write content to file.");
// 枚举定义:工具名 + 描述
// 为LLM提供工具列表时使用
}
// 工具执行接口 - 统一调用契约
@FunctionalInterface
interface ToolExecutor {
String execute(Map<String, Object> args) throws Exception;
// 统一接口:所有工具都实现此方法
// 参数和返回值标准化
}
// 工具注册表 - 动态路由
private static final Map<String, ToolExecutor> TOOL_HANDLERS = new HashMap<>();
static {
TOOL_HANDLERS.put("bash", args -> {
// 工具实现1
});
TOOL_HANDLERS.put("read_file", args -> {
// 工具实现2
});
// 注册中心:工具名 -> 实现函数
// 新增工具只需在这里注册
}
- 开闭原则:不修改主循环就能添加新工具
- 统一管理:所有工具注册、调用逻辑一致
- 类型安全:通过枚举定义工具,避免硬编码字符串
文件操作工具集
核心思想:为Agent提供文件系统读写能力,使其能像人类开发者一样操作文件。
private static Path safePath(String p) throws IOException {
Path path = WORKDIR.resolve(p).normalize();
if (!path.startsWith(WORKDIR)) {
throw new IOException("Path escapes workspace: " + p);
}
return path;
// 安全沙箱:确保工具只能操作工作目录内的文件
// 防止路径逃逸攻击
}
private static String runRead(String pathStr, Integer limit) throws IOException {
Path path = safePath(pathStr);
String content = Files.readString(path);
if (limit != null && limit < content.length()) {
return content.substring(0, limit) + "... (truncated)";
}
return content;
// 带限制的读取:防止大文件内存溢出
// 自动截断,返回友好提示
}
private static String runWrite(String pathStr, String content) throws IOException {
Path path = safePath(pathStr);
Files.createDirectories(path.getParent()); // 自动创建父目录
Files.writeString(path, content);
return "Wrote " + content.length() + " bytes to " + pathStr;
// 自动创建目录:用户体验优化
// 明确的结果反馈
}
private static String runEdit(String pathStr, String oldText, String newText) throws IOException {
Path path = safePath(pathStr);
String content = Files.readString(path);
if (!content.contains(oldText)) {
return "Error: Text not found in " + pathStr; // 错误处理
}
String newContent = content.replace(oldText, newText);
Files.writeString(path, newContent);
return "Edited " + pathStr;
// 简单的文件编辑:文本查找替换
// 先验证后操作,避免损坏文件
}
- 沙箱安全:所有文件操作都经过
safePath检查 - 渐进式反馈:读操作支持截断,避免响应过大
- 容错处理:编辑前检查文本是否存在
- 自动化:写文件时自动创建父目录
动态工具路由
// 在agentLoop中
String toolName = (String) block.get("name"); // 从LLM响应中提取工具名
Map<String, Object> inputArgs = (Map<String, Object>) block.get("input");
// 根据工具名查找处理器
ToolExecutor handler = TOOL_HANDLERS.get(toolName);
String output;
try {
if (handler != null) {
output = handler.execute(inputArgs); // 动态调用
} else {
output = "Error: Unknown tool " + toolName;
}
} catch (Exception e) {
output = "Error: " + e.getMessage(); // 统一错误处理
}
- 动态分派:根据LLM选择的工具名调用对应实现
- 统一错误处理:未知工具、执行异常都有统一格式的返回
- 解耦:主循环不需要知道具体工具的实现细节
架构对比与价值
从AgentLoop到AgentWithTools的演进:
| 维度 | AgentLoop | AgentWithTools |
|---|---|---|
| 工具数量 | 1个(Bash) | 4+个(可扩展) |
| 架构设计 | 硬编码 | 策略模式 |
| 添加新工具 | 修改主代码 | 注册表添加 |
| 文件操作 | 无 | 读写编辑 |
| 安全性 | 命令检查 | 沙箱路径 |
| 代码复用 | 低 | 高 |
核心价值:
- 可扩展性:添加新工具只需在注册表中添加一行
- 维护性:工具实现与主循环分离
- 安全性:统一的路径和权限控制
- 专业性:为开发任务优化的专用工具集
本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top

浙公网安备 33010602011771号