助力亚马逊精品服务商 發表於 2026-4-10 09:00:00

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

<p>这是 Agent 进化的关键一步:<strong>从“只会说话”变成了“真正干活”</strong>。</p>
<h2 id="java-实现代码">Java 实现代码</h2>
<pre><code class="language-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&lt;String, Object&gt; args) throws Exception;
    }

    // 3. 注册工具处理逻辑
    private static final Map&lt;String, ToolExecutor&gt; TOOL_HANDLERS = new HashMap&lt;&gt;();

    static {
      TOOL_HANDLERS.put(ToolType.BASH.name, args -&gt; {
            String command = (String) args.get("command");
            return runBash(command);
      });
      TOOL_HANDLERS.put(ToolType.READ_FILE.name, args -&gt; {
            String path = (String) args.get("path");
            Integer limit = (Integer) args.get("limit");
            return runRead(path, limit);
      });
      TOOL_HANDLERS.put(ToolType.WRITE_FILE.name, args -&gt; {
            String path = (String) args.get("path");
            String content = (String) args.get("content");
            return runWrite(path, content);
      });
      TOOL_HANDLERS.put(ToolType.EDIT_FILE.name, args -&gt; {
            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&lt;Map&lt;String, Object&gt;&gt; messages) {
      while (true) {
            // ... 省略相同的 LLM 调用、消息追加逻辑
            
            // 4. 执行工具
            List&lt;Map&lt;String, Object&gt;&gt; toolResults = new ArrayList&lt;&gt;();
            List&lt;Map&lt;String, Object&gt;&gt; content = (List&lt;Map&lt;String, Object&gt;&gt;) response.get("content");

            for (Map&lt;String, Object&gt; block : content) {
                if ("tool_use".equals(block.get("type"))) {
                  String toolName = (String) block.get("name");// 关键新增
                  String toolId = (String) block.get("id");
                  Map&lt;String, Object&gt; inputArgs = (Map&lt;String, Object&gt;) 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("&gt; " + 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 &amp;&amp; limit &lt; 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;
    }
}
</code></pre>
<p>这段代码相比 s01,最大的进步在于<strong>能力的扩展</strong>和<strong>安全边界</strong>。说白了就是,你可以像搭积木一样给 Agent 塞入各种工具函数,让它的能力边界随插件无限延伸。</p>
<p>这段代码应该已经很清晰,我这里就不多解释了</p>
<h2 id="工具抽象框架策略模式">工具抽象框架(策略模式)</h2>
<p><strong>核心思想</strong>:从"硬编码工具"升级为"可插拔架构",实现<strong>工具与主循环的解耦</strong>。</p>
<pre><code class="language-java">// 工具枚举 - 集中定义所有可用工具
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提供工具列表时使用
}
</code></pre>
<pre><code class="language-java">// 工具执行接口 - 统一调用契约
@FunctionalInterface
interface ToolExecutor {
    String execute(Map&lt;String, Object&gt; args) throws Exception;
    // 统一接口:所有工具都实现此方法
    // 参数和返回值标准化
}
</code></pre>
<pre><code class="language-java">// 工具注册表 - 动态路由
private static final Map&lt;String, ToolExecutor&gt; TOOL_HANDLERS = new HashMap&lt;&gt;();

static {
    TOOL_HANDLERS.put("bash", args -&gt; {
      // 工具实现1
    });
    TOOL_HANDLERS.put("read_file", args -&gt; {
      // 工具实现2
    });
    // 注册中心:工具名 -&gt; 实现函数
    // 新增工具只需在这里注册
}
</code></pre>
<ul>
<li><strong>开闭原则</strong>:不修改主循环就能添加新工具</li>
<li><strong>统一管理</strong>:所有工具注册、调用逻辑一致</li>
<li><strong>类型安全</strong>:通过枚举定义工具,避免硬编码字符串</li>
</ul>
<h2 id="文件操作工具集">文件操作工具集</h2>
<p><strong>核心思想</strong>:为Agent提供<strong>文件系统读写能力</strong>,使其能像人类开发者一样操作文件。</p>
<pre><code class="language-java">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;
    // 安全沙箱:确保工具只能操作工作目录内的文件
    // 防止路径逃逸攻击
}
</code></pre>
<pre><code class="language-java">private static String runRead(String pathStr, Integer limit) throws IOException {
    Path path = safePath(pathStr);
    String content = Files.readString(path);
    if (limit != null &amp;&amp; limit &lt; content.length()) {
      return content.substring(0, limit) + "... (truncated)";
    }
    return content;
    // 带限制的读取:防止大文件内存溢出
    // 自动截断,返回友好提示
}
</code></pre>
<pre><code class="language-java">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;
    // 自动创建目录:用户体验优化
    // 明确的结果反馈
}
</code></pre>
<pre><code class="language-java">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;
    // 简单的文件编辑:文本查找替换
    // 先验证后操作,避免损坏文件
}
</code></pre>
<ul>
<li><strong>沙箱安全</strong>:所有文件操作都经过<code>safePath</code>检查</li>
<li><strong>渐进式反馈</strong>:读操作支持截断,避免响应过大</li>
<li><strong>容错处理</strong>:编辑前检查文本是否存在</li>
<li><strong>自动化</strong>:写文件时自动创建父目录</li>
</ul>
<h2 id="动态工具路由">动态工具路由</h2>
<pre><code class="language-java">// 在agentLoop中
String toolName = (String) block.get("name");// 从LLM响应中提取工具名
Map&lt;String, Object&gt; inputArgs = (Map&lt;String, Object&gt;) 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();// 统一错误处理
}
</code></pre>
<ul>
<li><strong>动态分派</strong>:根据LLM选择的工具名调用对应实现</li>
<li><strong>统一错误处理</strong>:未知工具、执行异常都有统一格式的返回</li>
<li><strong>解耦</strong>:主循环不需要知道具体工具的实现细节</li>
</ul>
<h2 id="架构对比与价值">架构对比与价值</h2>
<p><strong>从AgentLoop到AgentWithTools的演进</strong>:</p>
<table>
<thead>
<tr>
<th>维度</th>
<th>AgentLoop</th>
<th>AgentWithTools</th>
</tr>
</thead>
<tbody>
<tr>
<td>工具数量</td>
<td>1个(Bash)</td>
<td>4+个(可扩展)</td>
</tr>
<tr>
<td>架构设计</td>
<td>硬编码</td>
<td>策略模式</td>
</tr>
<tr>
<td>添加新工具</td>
<td>修改主代码</td>
<td>注册表添加</td>
</tr>
<tr>
<td>文件操作</td>
<td>无</td>
<td>读写编辑</td>
</tr>
<tr>
<td>安全性</td>
<td>命令检查</td>
<td>沙箱路径</td>
</tr>
<tr>
<td>代码复用</td>
<td>低</td>
<td>高</td>
</tr>
</tbody>
</table>
<p><strong>核心价值</strong>:</p>
<ol>
<li><strong>可扩展性</strong>:添加新工具只需在注册表中添加一行</li>
<li><strong>维护性</strong>:工具实现与主循环分离</li>
<li><strong>安全性</strong>:统一的路径和权限控制</li>
<li><strong>专业性</strong>:为开发任务优化的专用工具集</li>
</ol>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top</p><br><br>
来源:https://www.cnblogs.com/sevencoding/p/19821502
頁: [1]
查看完整版本: 【从0到1构建一个ClaudeAgent】工具与执行-工具