首页 > 基础资料 博客日记

【从0到1构建一个ClaudeAgent】工具与执行-工具

2026-04-10 09:30:02基础资料围观1

文章【从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+个(可扩展)
架构设计 硬编码 策略模式
添加新工具 修改主代码 注册表添加
文件操作 读写编辑
安全性 命令检查 沙箱路径
代码复用

核心价值

  1. 可扩展性:添加新工具只需在注册表中添加一行
  2. 维护性:工具实现与主循环分离
  3. 安全性:统一的路径和权限控制
  4. 专业性:为开发任务优化的专用工具集

文章来源:https://www.cnblogs.com/sevencoding/p/19821502
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云