Build a Minimal Java ReAct Agent in 200 Lines: A Hands‑On Tutorial
This tutorial walks you through constructing a lightweight ReAct agent using Java, explaining the Thought‑Action‑Observation loop, providing a 200‑line code example, and demonstrating a real‑world approval workflow with prompts, tool definitions, and step‑by‑step interaction logs.
Introduction
This article presents a practical guide to building a minimal ReAct agent in Java. By following a concise 200‑line example, readers can grasp the core ReAct loop—Thought, Action, Observation—and see how to apply it to a concrete scenario such as approving a replenishment plan order.
Core Idea
The ReAct paradigm repeatedly cycles through three stages: the model thinks about the next best step, decides which tool to invoke, and observes the tool's result before feeding it back into the next prompt.
while (true) {
// 1. Thought: LLM decides whether a tool is needed and which one
if (noToolNeeded) {
break;
}
// 2. Action: invoke the selected tool
// 3. Observation: append tool result to prompt and repeat
}Implementation
The Java implementation defines the agent, tools, memory, and LLM wrapper. The main loop reads user input, builds the prompt, calls the LLM, parses its Thought/Action/Observation output, and executes the appropriate tool.
public class ReActAgent {
private static final String AGENT_ACTION_TEMPLATE = "工具名称,必须是[{0}]中的一个";
private static final List<Tool> TOOLS = Arrays.asList(
Tool.builder()
.name("查询补货计划单详情")
.desc("根据补货计划单号查询补货计划单详情")
.parameters(Collections.singletonList(
Tool.Parameter.builder()
.name("orderCode")
.desc("补货计划单号")
.type("string")
.required(true)
.build()))
.build(),
Tool.builder()
.name("审批补货计划单")
.desc("根据补货计划单号审批补货计划单")
.parameters(Collections.singletonList(
Tool.Parameter.builder()
.name("orderCode")
.desc("补货计划单号")
.type("string")
.required(true)
.build()))
.build()
);
private static final String USER_PROMPT = "# 角色设定
你是一位经验丰富的供应链智能助理,专注于补货计划单的审批,具备如下技能:...";
public static void main(String[] args) {
// omitted for brevity: argument check, scanner, memory init
while (true) {
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) { break; }
StringBuilder latestInput = new StringBuilder(input);
String output = reAct(ak, memory, latestInput);
System.out.println("AI输出: " + output);
memory.add(new Memory.ChatMsg(Memory.ChatMsg.USER, input));
memory.add(new Memory.ChatMsg(Memory.ChatMsg.AI, output));
}
scanner.close();
}
private static String reAct(String ak, Memory memory, StringBuilder latestInput) {
while (true) {
String prompt = prompt(USER_PROMPT, TOOLS, memory, latestInput);
String llmResult = LLM.llm(prompt, ak);
if (isQueryPlanOrderAction(llmResult)) {
String toolResult = JSON.toJSONString(queryPlanOrder(null));
latestInput.append("
").append(llmResult).append("
Observation: ").append(toolResult);
} else if (isAuditPlanOrderAction(llmResult)) {
String toolResult = JSON.toJSONString(auditPlanOrder(null));
latestInput.append("
").append(llmResult).append("
Observation: ").append(toolResult);
} else {
return llmResult;
}
}
}
// helper methods omitted for brevity
}Tool Definitions
public class Tool {
private String name;
private String desc;
private List<Parameter> parameters;
@Data @Builder public static class Parameter {
private String name;
private String desc;
private String type;
private Boolean required;
private List<Parameter> properties;
}
}Memory Management
public class Memory {
private List<ChatMsg> memories = new ArrayList<>();
public void add(ChatMsg chatMsg) { memories.add(chatMsg); }
public String getAll() {
StringBuilder sb = new StringBuilder();
for (ChatMsg msg : memories) {
sb.append(msg.getRole()).append(":
").append(msg.getMsg()).append("
");
}
return sb.toString();
}
@Data public static class ChatMsg {
public static final String USER = "用户";
public static final String AI = "AI";
private String role;
private String msg;
public ChatMsg(String role, String msg) { this.role = role; this.msg = msg; }
}
}LLM Invocation
public class LLM {
private static final String model = "你的模型名称";
public static String llm(String prompt, String ak) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
JSONObject reqData = new JSONObject();
reqData.put("model", model);
JSONObject reqBody = new JSONObject();
reqBody.put("role", "user");
reqBody.put("content", prompt);
reqData.put("messages", Collections.singletonList(reqBody));
RequestBody body = RequestBody.create(mediaType, reqData.toJSONString());
Request request = new Request.Builder()
.url("https://你的API地址/api/openai/v1/chat/completions")
.header("Authorization", "Bearer " + ak)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new RuntimeException("req llm exception, res : " + response);
}
String res = response.body().string();
GearsAIResult result = JSON.parseObject(res, GearsAIResult.class);
return Optional.ofNullable(result)
.map(GearsAIResult::getChoices)
.map(e -> e.get(0))
.map(GearsAIChoice::getMessage)
.map(GearsAIMessage::getContent)
.orElseThrow(() -> new RuntimeException("调用llm异常"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// inner DTO classes omitted for brevity
}Example Scenario: Replenishment Plan Approval
The demo simulates a user asking the AI to approve a replenishment plan order. The conversation proceeds in two rounds:
User: "帮我审批下补货计划单" AI replies that it needs the order code.
User: "单号是:BH001" AI generates a Thought to query the order, calls the 查询补货计划单详情 tool, receives {"status":"待审批"}, then decides to call 审批补货计划单 and receives {"data":"审批成功","success":true}. Finally it confirms the approval.
This walk‑through illustrates how the Thought‑Action‑Observation cycle drives multi‑step reasoning and tool usage.
Conclusion
The tutorial demonstrates the essential mechanics of a ReAct agent, from prompt design and tool registration to the iterative loop that enables LLMs to reason, act, and observe. While simplified for clarity, the pattern can be extended to more complex workflows and richer toolsets.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
