【learn-claude-code】S01AgentLoop - Agent 循环:模型与真实世界的第一道连接

【learn-claude-code】S01AgentLoop - Agent 循环:模型与真实世界的第一道连接 核心理念“模型即 Agent代码即 Harness”源码https://github.com/xiayongchao/learn-claude-code-4j/blob/main/src/main/java/org/jc/agents/S01AgentLoop.java原版https://github.com/shareAI-lab/learn-claude-code这个项目源自 shareAI-lab/learn-claude-code旨在帮助 Java 程序员理解 AI Agent 的核心机制。不同于 Python 原版本文用 Java 实现相同的 Agent Loop让习惯 Java 的开发者也能深入学习。问题背景语言模型能推理代码但它无法直接触碰真实世界——不能读文件、不能运行测试、不能查看错误日志。没有 Agent Loop每次工具调用后你都需要手动把结果粘回去。你自己就是那个循环。解决方案一个循环控制整个流程-------- ------- --------- | User | --- | LLM | --- | Tool | | prompt | | | | execute | -------- ------ -------- ^ | | tool_result | ---------------- (loop until 模型不再调用工具)Java 实现详解1. 入口main 方法publicstaticvoidmain(String[]args){ScannerscannernewScanner(System.in);ListChatCompletionMessageParammessagesnewArrayList();while(true){System.out.print(请输入 );Stringqueryscanner.nextLine();if(List.of(q,exit,).contains(query.strip())){break;}// 添加用户消息messages.add(ChatCompletionMessageParam.ofUser(ChatCompletionUserMessageParam.builder().content(query).build()));// 启动 Agent 循环agentLoop(messages);// 输出最终回复System.out.println( Commons.getText(messages.get(messages.size()-1)));}}2. 核心agentLoop 方法// 工具处理器注册privatestaticfinalMapString,FunctionString,StringTOOL_HANDLERSnewHashMap();static{TOOL_HANDLERS.put(bash,Tools::runBash);}privatestaticfinalStringSYSTEM你当前工作目录为 Commons.CWD作为编程智能体使用 Bash 完成任务直接执行、无需解释;publicstaticvoidagentLoop(ListChatCompletionMessageParammessages){while(true){// 1. 构建完整消息列表系统提示 历史消息ListChatCompletionMessageParamfullMessagesnewArrayList();fullMessages.add(ChatCompletionMessageParam.ofSystem(ChatCompletionSystemMessageParam.builder().content(SYSTEM).build()));fullMessages.addAll(messages);// 2. 调用 LLM APIChatCompletionCreateParamsparamsChatCompletionCreateParams.builder().model(qwen3.5-plus).messages(fullMessages).tools(List.of(Tools.bashTool())).build();ChatCompletionchatCompletionCommons.getClient().chat().completions().create(params);ChatCompletionMessagemessagechatCompletion.choices().get(0).message();// 3. 将模型回复加入历史messages.add(ChatCompletionMessageParam.ofAssistant(message.toParam()));// 4. 检查是否有工具调用OptionalListChatCompletionMessageToolCalltoolCallsOptionalmessage.toolCalls();if(toolCallsOptional.isEmpty()){return;// 无工具调用结束循环}// 5. 执行工具调用for(ChatCompletionMessageToolCalltoolCall:toolCallsOptional.get()){ChatCompletionMessageParamtoolMessageTools.exe(TOOL_HANDLERS,toolCall);if(toolMessage!null){messages.add(toolMessage);// 将工具结果发回给模型}}}}3. 工具执行Tools.exe 方法publicstaticChatCompletionMessageParamexe(MapString,FunctionString,StringTOOL_HANDLERS,ChatCompletionMessageToolCalltoolCall){if(toolCallnull||!toolCall.isFunction()){returnnull;}ChatCompletionMessageFunctionToolCallfunctionCalltoolCall.asFunction();StringfunctionNamefunctionCall.function().name();StringargumentsfunctionCall.function().arguments();FunctionString,StringtoolHandlerTOOL_HANDLERS.get(functionName);if(toolHandlernull){returnChatCompletionMessageParam.ofTool(ChatCompletionToolMessageParam.builder().content(String.format(未知的工具%s,functionName)).toolCallId(functionCall.id()).build());}returnChatCompletionMessageParam.ofTool(ChatCompletionToolMessageParam.builder().content(toolHandler.apply(arguments)).toolCallId(functionCall.id()).build());}4. Bash 工具定义publicstaticChatCompletionToolbashTool(){MapString,JsonValueparamMapnewHashMap();paramMap.put(type,JsonValue.from(object));MapString,JsonValuecommandPropnewHashMap();commandProp.put(type,JsonValue.from(string));commandProp.put(description,JsonValue.from(要执行的shell命令));paramMap.put(properties,JsonValue.from(Map.of(command,JsonValue.from(commandProp))));paramMap.put(required,JsonValue.from(List.of(command)));FunctionParametersparametersFunctionParameters.builder().putAllAdditionalProperties(paramMap).build();returnChatCompletionTool.ofFunction(ChatCompletionFunctionTool.builder().function(FunctionDefinition.builder().name(bash).description(在当前工作区中运行 shell 命令).parameters(parameters).build()).build());}5. 危险命令防护Agent 执行 shell 命令存在安全风险需要拦截危险操作privatestaticfinalSetStringdangerousnewHashSet(List.of(rm -rf /,sudo,shutdown,reboot));publicstaticStringrunBash(Stringarguments){if(dangerous.contains(arguments)){return错误危险命令被阻止;}// ... 执行命令}防护策略黑名单拦截rm -rf /、sudo、shutdown、reboot等危险命令直接拒绝路径沙箱限制操作在指定工作目录内s02 详解超时控制防止命令永久阻塞Python vs Java 对比组件PythonJavaAPI 客户端anthropic.AnthropicOpenAIOkHttpClient消息类型{role: user, ...}ChatCompletionMessageParam.ofUser()工具定义{name: bash, ...}ChatCompletionToolFunctionDefinition停止判断response.stop_reason ! tool_usemessage.toolCalls().isEmpty()工具执行TOOL_HANDLERS[name](args)Tools.exe(TOOL_HANDLERS, toolCall)工具注册{bash: run_bash}TOOL_HANDLERS.put(bash, Tools::runBash)项目依赖dependencygroupIdcom.openai/groupIdartifactIdopenai-java/artifactIdversion4.29.1/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion2.0.32/version/dependency试试看创建名为hello.java的文件使其输出打印 “Hello, World!”列出当前目录下所有 Java 文件当前 Git 分支是什么创建名为test_output的文件夹并在其中新建 3 个文件核心要义“One loop Bash is all you need”一个工具 一个循环 一个 Agent循环本身不变后续所有机制s02-s12都在此基础上叠加s02: 添加更多工具s03: 任务规划TodoWrites04: 子 Agent 隔离s05: 技能加载…模型决定何时调用工具代码负责执行工具并返回结果。这就是 Agent 的全部秘密。