引言
最近刷到一个宝藏网站,叫 Learn Claude Code。名字看着挺像学习使用 Claude Code 的指南,但实际并不是,而是教你从 0 到 1 构建 nano Claude Code-like agent,每次只加一个机制。
网站链接我也再贴一遍:https://learn.shareai.run/zh/
而且网站开头就直白表示:所有 AI 编程 Agent 共享同一个循环:调用模型、执行工具、回传结果。生产级系统会在其上叠加策略、权限和生命周期层。

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)
这段代码人工翻译一下就是:
- 调模型(给指令)
- 执行工具(读写文件、跑命令)
- 回传结果(告诉模型干了啥)
- 继续迭代(直到任务干完)
就这?就这。剩下的全是围绕这个循环的各种优化和补丁。
用 Java 来写,核心循环大概是这样:
java
while (true) {
MessageResponse response = client.messagesCreate(messages, tools);
if (!"tool_use".equals(response.getStopReason())) {
break;
}
for (ToolCall toolCall : response.getContent()) {
ToolResult result = executeTool(toolCall.getName(), toolCall.getInput());
messages.add(result);
}
}
正是考虑到我们的读者多数是Java同学,因此我决定用Java来和大家一块学习下
渐进式学习路径
网站将这 12 个阶段(s01-s12)归纳为五个核心能力的进阶,看看一个成熟的 Agent 系统是如何一步步被做出来的:

S01 Agent 循环
最小可用的 Agent 内核仅仅需要:一个 while 循环 + 一个工具。
说白了就是Agent 最本质的"大脑-手脚"循环
Java实现代码
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<Map<String, Object>> messages) {
while (true) {
// 1. 调用 LLM
System.out.println(">>> 正在思考...");
Map<String, Object> 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<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"))) {
Map<String, Object> input = (Map<String, Object>) 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() > 200 ? output.substring(0, 200) + "..." : output);
// 构造工具结果
Map<String, Object> result = new HashMap<>();
result.put("type", "tool_result");
result.put("tool_use_id", toolId);
result.put("content", output);
toolResults.add(result);
}
}
// 5. 将工具结果作为用户输入再次加入历史
Map<String, Object> userTurn = new HashMap<>();
userTurn.put("role", "user");
userTurn.put("content", toolResults);
messages.add(userTurn);
}
}
// 模拟 LLM 调用 (实际需替换为 SDK 调用)
private static Map<String, Object> callLLM(List<Map<String, Object>> messages) {
// 这里是一个占位符,实际应发送 HTTP 请求给 Anthropic API
// 返回结构需匹配 API 响应
return new HashMap<>();
}
// 执行 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<Map<String, Object>> history = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
System.out.println("Agent 已启动 (输入 'q' 退出)");
while (true) {
System.out.print("\033[36ms01 >> \033[0m");
String query = scanner.nextLine();
if (query.trim().equalsIgnoreCase("q") || query.isEmpty()) {
break;
}
Map<String, Object> userMsg = new HashMap<>();
userMsg.put("role", "user");
userMsg.put("content", query);
history.add(userMsg);
agentLoop(history);
// 打印最终回复
System.out.println("Agent 执行完毕。");
}
}
}
这段代码包含了所有 AI Agent 的灵魂。
核心模式:ReAct 循环
代码中的 while 循环是 Agent 的心脏。它的逻辑是:
- 思考: 问 LLM "该做什么?"
- 行动: 如果 LLM 说要调工具(比如写代码、运行命令),代码就去执行这个工具
- 观察: 把工具执行的结果(输出、报错)再次告诉 LLM。
- 循环: LLM 根据结果决定是继续干,还是说"搞定了"。
java
while (true) {
// 1. 调用 LLM
Map<String, Object> response = callLLM(messages);
messages.add(response);
// 2. 检查是否结束
String stopReason = (String) response.get("stop_reason");
if (!"tool_use".equals(stopReason)) {
return; // 任务完成
}
// 3. 执行工具
List<Map<String, Object>> toolResults = executeTools(response);
// 4. 将结果返回给 LLM
messages.add(createUserTurn(toolResults));
}
状态管理
messages 列表。它不仅仅是聊天记录,它是 Agent 的短期记忆。
- 每次循环,我们都要把新的对话(无论是人的指令,还是工具的执行结果)
append进去。 - 如果不把工具结果放回去,LLM 就不知道自己刚才执行的命令成功了没有,也就无法进行下一步。
工具定义的标准化
在 代码的 TOOLS 变量里,我们会定义了工具长什么样(名字、参数)。
java
// 遍历响应中的工具调用块
for (Map<String, Object> block : content) {
if ("tool_use".equals(block.get("type"))) {
// 提取命令
Map<String, Object> input = (Map<String, Object>) block.get("input");
String command = (String) input.get("command");
String toolId = (String) block.get("id");
// 执行 Bash
String output = runBash(command);
// 构建工具结果
Map<String, Object> result = new HashMap<>();
result.put("type", "tool_result");
result.put("tool_use_id", toolId);
result.put("content", output);
toolResults.add(result);
}
}
关键点: LLM 不会真的"运行"代码,它只是输出一个符合这个格式的 JSON(比如 {"name": "bash", "arguments": {"command": "ls"}})。而代码才是负责解析这个 JSON 并真的去执行 Runtime.exec()。
安全围栏
代码里的 run_bash 函数不仅仅是执行命令,它还充当了防火墙。
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));
}
重要: 永远不要让 LLM 直接拥有无限制的 Shell 权限。虽然这里的检查很简单(黑名单),但在生产环境中,可能会需要沙箱环境(Docker)来运行这些命令。