400 行 Java 代码手搓 AI Agent,ReAct 循环 + Tool Call,我跑起来了
大家好,我是二哥呀。
说实话,用 Claude Code、Qoder CLI 和 Codex 进行 AI编程也有一段时间了,我一直很好奇这玩意儿到底是怎么实现的。
它们看起来就是简单的命令行界面,但背后却能理解我的需求、调用各种工具、读写文件、执行命令,甚至能自己改代码。

这到底是什么原理?
今天这篇文章,我们就从零开始,用 Java 实现一个最简单的 Agent CLI。它能配置 GLM-5.1 的 API Key,能接收你的输入,有 Agent Loop,能 Tool Call,能根据上下文编程或输出内容。
一个超级简化版的 Claude Code,大概 400 行代码就能跑起来。

01、Agent 的核心原理
在动手写代码之前,先搞清楚 Agent 到底是什么东西。
Agent 的核心就三个东西:推理(Reasoning)、行动(Acting)、观察(Observing)。这三者循环起来,就是著名的 ReAct 模式。

流程是这样的:
- 你输入一个任务
- LLM 思考:我需要做什么?要不要调用工具?
- 如果需要工具,LLM 输出工具调用请求
- Agent 执行工具,拿到结果
- 把结果喂给 LLM,继续思考
- 重复 2-5,直到 LLM 觉得任务完成
- 输出最终结果
这个循环就是 Agent 的灵魂。Claude Code、Qoder CLI、OpenClaw,本质上都是这个循环的不同实现。
02、项目结构
我们用 Java 17 + Maven 来搭建项目,不依赖任何第三方 Agent 框架,从零手写。
paicli/
├── pom.xml
├── .env
└── src/main/java/com/paicli/
├── cli/Main.java # 入口类
├── agent/Agent.java # Agent 核心(ReAct 循环)
├── llm/GLMClient.java # GLM-5.1 API 客户端
└── tool/ToolRegistry.java # 工具注册表

Maven 依赖
我们需要这几个依赖:
- Jackson:处理 JSON
- OkHttp:发送 HTTP 请求
- SLF4J:简单日志
com.fasterxml.jackson.core
jackson-databind
2.16.0
com.squareup.okhttp3
okhttp
4.12.0
org.slf4j
slf4j-simple
2.0.9
03、GLM-5.1 API 客户端
Agent 要能理解我们的提示词,得能调用大模型。所以我们需要先封装一个 GLMClient,支持普通对话和工具调用。
public class GLMClient {
private static final String API_URL =
"https://open.bigmodel.cn/api/paas/v4/chat/completions";
private static final String MODEL = "glm-5.1";
private final String apiKey;
private final OkHttpClient httpClient;
public GLMClient(String apiKey) {
this.apiKey = apiKey;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.build();
}
}
核心是 chat 方法,它接收消息历史和一个工具列表,返回 LLM 响应。
消息格式
GLM-5.1 的 API 兼容 OpenAI 格式,消息有三种角色:
system:系统提示,定义 Agent 的身份和能力user:用户输入assistant:助手回复,可以包含文本或工具调用tool:工具执行结果
public record Message(String role, String content,
List toolCalls, String toolCallId) {
public static Message system(String content) {
return new Message("system", content, null, null);
}
public static Message user(String content) {
return new Message("user", content, null, null);
}
public static Message assistant(String content) {
return new Message("assistant", content, null, null);
}
public static Message tool(String toolCallId, String content) {
return new Message("tool", content, null, toolCallId);
}
}
工具定义格式
要让 LLM 知道有哪些工具可用,需要按照特定格式定义工具:
public record Tool(String name, String description, JsonNode parameters) {}
parameters 是一个 JSON Schema,描述工具需要哪些参数、参数类型是什么。比如 write_file 工具的参数定义:
{
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "文件路径"
},
"content": {
"type": "string",
"description": "文件内容"
}
},
"required": ["path", "content"]
}
LLM 会根据这个定义,在需要时生成正确的参数。
完整的 chat 方法
public ChatResponse chat(List messages, List tools)
throws IOException {
// 构建请求体
ObjectNode requestBody = mapper.createObjectNode();
requestBody.put("model", MODEL);
// 添加消息历史
ArrayNode messagesArray = requestBody.putArray("messages");
for (Message msg : messages) {
ObjectNode msgNode = messagesArray.addObject();
msgNode.put("role", msg.role());
msgNode.put("content", msg.content());
// 如果有工具调用,序列化 tool_calls
if (msg.toolCalls() != null && !msg.toolCalls().isEmpty()) {
ArrayNode toolCallsArray = msgNode.putArray("tool_calls");
for (ToolCall tc : msg.toolCalls()) {
ObjectNode tcNode = toolCallsArray.addObject();
tcNode.put("id", tc.id());
tcNode.put("type", "function");
ObjectNode functionNode = tcNode.putObject("function");
functionNode.put("name", tc.function().name());
functionNode.put("... 企业级Agent工作流编排项目PaiFlow
Vibe Coding版本的PaiAgent
派聪明RAG AI知识库Java版本+Go版本
微服务 PmHub、技术派、MYDB
求职派JobClaw(OpenClaw/Hermes架构
PaiCLI(类似Claude Code的Agent
派简历(代码已完成)
等实战项目。
1. 微信扫右侧的优惠券加入知识星球
2. 解锁星球的实战项目教程和源码: 项目源码+教程获取
热门评论
4 条评论
回复