阿土木 發表於 2026-4-9 09:00:00

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

<h2 id="引言">引言</h2>
<p>最近刷到一个宝藏网站,叫&nbsp;<strong>Learn Claude Code</strong>。名字看着挺像学习使用&nbsp;Claude Code 的指南,但实际并不是,而是教你从 0 到 1 构建 nano Claude Code-like agent,每次只加一个机制。</p>
<p>网站链接我也再贴一遍:https://learn.shareai.run/zh/</p>
<p>而且网站开头就直白表示:所有 AI 编程 Agent 共享同一个循环:调用模型、执行工具、回传结果。生产级系统会在其上叠加策略、权限和生命周期层。</p>
<p><img src="https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202604022045651.png" alt="" loading="lazy"></p>
<pre><code class="language-python">while True:
    response = client.messages.create(messages=messages, tools=tools)
    if response.stop_reason != "tool_use":
      break
    for tool_call in response.content:
      result = execute_tool(tool_call.name, tool_call.input)
      messages.append(result)
</code></pre>
<p>这段代码人工翻译一下就是:</p>
<ol>
<li>调模型(给指令)</li>
<li>执行工具(读写文件、跑命令)</li>
<li>回传结果(告诉模型干了啥)</li>
<li>继续迭代(直到任务干完)</li>
</ol>
<p>就这?就这。剩下的全是围绕这个循环的各种优化和补丁。</p>
<p>用 Java 来写,核心循环大概是这样:</p>
<pre><code class="language-java">while&nbsp;(true) {
&nbsp; &nbsp; MessageResponse response = client.messagesCreate(messages, tools);
&nbsp; &nbsp;&nbsp;if&nbsp;(!"tool_use".equals(response.getStopReason())) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;for&nbsp;(ToolCall toolCall : response.getContent()) {
&nbsp; &nbsp; &nbsp; &nbsp; ToolResult result = executeTool(toolCall.getName(), toolCall.getInput());
&nbsp; &nbsp; &nbsp; &nbsp; messages.add(result);
&nbsp; &nbsp; }
}
</code></pre>
<p>正是考虑到我们的读者多数是Java同学,因此我决定用Java来和大家一块学习下</p>
<h2 id="渐进式学习路径">渐进式学习路径</h2>
<p>网站将这 12 个阶段(s01-s12)归纳为五个核心能力的进阶,看看一个成熟的 Agent 系统是如何一步步被做出来的:</p>
<p><img src="https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202604022052924.png" alt="" loading="lazy"></p>
<h3 id="s01-agent-循环">S01 Agent 循环</h3>
<p>最小可用的 Agent 内核仅仅需要:一个 while 循环 + 一个工具。</p>
<p>说白了就是Agent 最本质的“大脑-手脚”循环</p>
<h3 id="java实现代码">Java实现代码</h3>
<pre><code class="language-java">public class AgentLoop {

    // 模拟 Anthropic API 客户端
    private static final String API_KEY = System.getenv("ANTHROPIC_API_KEY");
    private static final String MODEL_ID = System.getenv("MODEL_ID");
    private static final HttpClient client = HttpClient.newHttpClient();

    // 核心循环
    public static void agentLoop(List&lt;Map&lt;String, Object&gt;&gt; messages) {
      while (true) {
            // 1. 调用 LLM
            System.out.println("&gt;&gt;&gt; 正在思考...");
            Map&lt;String, Object&gt; response = callLLM(messages);
            
            // 2. 将助手回复加入历史
            messages.add(response);

            // 3. 检查停止原因
            // 注意:这里简化了逻辑,实际需解析 JSON 中的 stop_reason
            String stopReason = (String) response.get("stop_reason");
            
            if (!"tool_use".equals(stopReason)) {
                return; // 任务完成,退出循环
            }

            // 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"))) {
                  Map&lt;String, Object&gt; input = (Map&lt;String, Object&gt;) block.get("input");
                  String command = (String) input.get("command");
                  String toolId = (String) block.get("id");

                  System.out.println("\033[33m$ " + command + "\033[0m"); // 黄色输出命令
                  
                  // 执行 Bash
                  String output = runBash(command);
                  System.out.println(output.length() &gt; 200 ? output.substring(0, 200) + "..." : output);

                  // 构造工具结果
                  Map&lt;String, Object&gt; result = new HashMap&lt;&gt;();
                  result.put("type", "tool_result");
                  result.put("tool_use_id", toolId);
                  result.put("content", output);
                  toolResults.add(result);
                }
            }

            // 5. 将工具结果作为用户输入再次加入历史
            Map&lt;String, Object&gt; userTurn = new HashMap&lt;&gt;();
            userTurn.put("role", "user");
            userTurn.put("content", toolResults);
            messages.add(userTurn);
      }
    }

    // 模拟 LLM 调用 (实际需替换为 SDK 调用)
    private static Map&lt;String, Object&gt; callLLM(List&lt;Map&lt;String, Object&gt;&gt; messages) {
      // 这里是一个占位符,实际应发送 HTTP 请求给 Anthropic API
      // 返回结构需匹配 API 响应
      return new HashMap&lt;&gt;();
    }

    // 执行 Shell 命令
    private static String runBash(String command) {
      // 安全检查
      if (command.contains("rm -rf /") || command.contains("sudo")) {
            return "Error: Dangerous command blocked";
      }

      try {
            ProcessBuilder pb = new ProcessBuilder("bash", "-c", command);
            pb.redirectErrorStream(true);
            Process p = pb.start();

            // 读取输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }

            // 等待完成 (带超时)
            if (!p.waitFor(120, TimeUnit.SECONDS)) {
                p.destroyForcibly();
                return "Error: Timeout (120s)";
            }

            String result = output.toString().trim();
            return result.isEmpty() ? "(no output)" : result.substring(0, Math.min(result.length(), 50000));

      } catch (IOException | InterruptedException e) {
            return "Error: " + e.getMessage();
      }
    }

    public static void main(String[] args) {
      List&lt;Map&lt;String, Object&gt;&gt; history = new ArrayList&lt;&gt;();
      Scanner scanner = new Scanner(System.in);

      System.out.println("Agent 已启动 (输入 'q' 退出)");

      while (true) {
            System.out.print("\033[36ms01 &gt;&gt; \033[0m");
            String query = scanner.nextLine();

            if (query.trim().equalsIgnoreCase("q") || query.isEmpty()) {
                break;
            }

            Map&lt;String, Object&gt; userMsg = new HashMap&lt;&gt;();
            userMsg.put("role", "user");
            userMsg.put("content", query);
            history.add(userMsg);

            agentLoop(history);
            
            // 打印最终回复
            System.out.println("Agent 执行完毕。");
      }
    }
}
</code></pre>
<p>这段代码包含了所有 AI Agent 的<strong>灵魂</strong>。</p>
<h3 id="核心模式react-循环">核心模式:ReAct 循环</h3>
<p>代码中的&nbsp;<code>while</code>&nbsp;循环是 Agent 的心脏。它的逻辑是:</p>
<ol>
<li><strong>思考:</strong>&nbsp;问 LLM "该做什么?"</li>
<li><strong>行动:</strong>&nbsp;如果 LLM 说要调工具(比如写代码、运行命令),代码就去执行这个工具</li>
<li><strong>观察:</strong>&nbsp;把工具执行的结果(输出、报错)再次告诉 LLM。</li>
<li><strong>循环:</strong>&nbsp;LLM 根据结果决定是继续干,还是说“搞定了”。</li>
</ol>
<pre><code class="language-java">while (true) {
    // 1. 调用 LLM
    Map&lt;String, Object&gt; response = callLLM(messages);
    messages.add(response);
   
    // 2. 检查是否结束
    String stopReason = (String) response.get("stop_reason");
    if (!"tool_use".equals(stopReason)) {
      return; // 任务完成
    }
   
    // 3. 执行工具
    List&lt;Map&lt;String, Object&gt;&gt; toolResults = executeTools(response);
   
    // 4. 将结果返回给 LLM
    messages.add(createUserTurn(toolResults));
}
</code></pre>
<h3 id="状态管理">状态管理</h3>
<p><code>messages</code>&nbsp;列表。它不仅仅是聊天记录,它是 Agent 的<strong>短期记忆</strong>。</p>
<ul>
<li>每次循环,我们都要把新的对话(无论是人的指令,还是工具的执行结果)<code>append</code>&nbsp;进去。</li>
<li>如果不把工具结果放回去,LLM 就不知道自己刚才执行的命令成功了没有,也就无法进行下一步。</li>
</ul>
<h3 id="工具定义的标准化">工具定义的标准化</h3>
<p>在 代码的&nbsp;<code>TOOLS</code>&nbsp;变量里,我们会定义了工具长什么样(名字、参数)。</p>
<pre><code class="language-java">// 遍历响应中的工具调用块
for (Map&lt;String, Object&gt; block : content) {
    if ("tool_use".equals(block.get("type"))) {
      // 提取命令
      Map&lt;String, Object&gt; input = (Map&lt;String, Object&gt;) block.get("input");
      String command = (String) input.get("command");
      String toolId = (String) block.get("id");
      
      // 执行 Bash
      String output = runBash(command);
      
      // 构建工具结果
      Map&lt;String, Object&gt; result = new HashMap&lt;&gt;();
      result.put("type", "tool_result");
      result.put("tool_use_id", toolId);
      result.put("content", output);
      toolResults.add(result);
    }
}
</code></pre>
<p><strong>关键点:</strong>&nbsp;LLM 不会真的“运行”代码,它只是输出一个符合这个格式的 JSON(比如&nbsp;<code>{"name": "bash", "arguments": {"command": "ls"}}</code>)。而代码才是负责解析这个 JSON 并真的去执行&nbsp;<code>Runtime.exec()</code>。</p>
<h3 id="安全围栏">安全围栏</h3>
<p>代码里的&nbsp;<code>run_bash</code>&nbsp;函数不仅仅是执行命令,它还充当了<strong>防火墙</strong>。</p>
<pre><code class="language-java">private static String runBash(String command) {
    // 安全检查
    if (command.contains("rm -rf /") || command.contains("sudo")) {
      return "Error: Dangerous command blocked";
    }
   
    // 带超时的命令执行
    if (!p.waitFor(120, TimeUnit.SECONDS)) {
      p.destroyForcibly();
      return "Error: Timeout (120s)";
    }
   
    // 限制输出长度
    return result.substring(0, Math.min(result.length(), 50000));
}
</code></pre>
<p><strong>重要:</strong>&nbsp;永远不要让 LLM 直接拥有无限制的 Shell 权限。虽然这里的检查很简单(黑名单),但在生产环境中,可能会需要沙箱环境(Docker)来运行这些命令。</p>


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