【从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<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;
}
}
</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<String, Object> args) throws Exception;
// 统一接口:所有工具都实现此方法
// 参数和返回值标准化
}
</code></pre>
<pre><code class="language-java">// 工具注册表 - 动态路由
private static final Map<String, ToolExecutor> TOOL_HANDLERS = new HashMap<>();
static {
TOOL_HANDLERS.put("bash", args -> {
// 工具实现1
});
TOOL_HANDLERS.put("read_file", args -> {
// 工具实现2
});
// 注册中心:工具名 -> 实现函数
// 新增工具只需在这里注册
}
</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 && limit < 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<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();// 统一错误处理
}
</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]