掌握Agent的核心原理:七大设计范式(ReAct、Plan-Execute、Reflexion、REWOO、Self-Ask、LATS、Multi-Agent)、Tool定义、AiServices模式,实现能自主推理和行动的智能体
时间:60分钟 | 难度:⭐⭐⭐⭐ | Week 2 Day 11
官方Example信息
- GitHub链接:ServiceWithToolsExample.java
- 相关Example:ServiceWithToolsExample、ChatWithToolsExample
- 所在路径:src/main/java/dev/langchain4j/examples/
- 代码行数:约100-200行
- 难度:高级 ⭐⭐⭐⭐
学习目标
- 理解Agent的本质和ReAct循环
- 掌握Agent七大范式的区别和选型
- 掌握@Tool和@P注解定义工具
- 学会使用AiServices创建Agent
- 能手动实现Agent Loop
- 掌握Agent的错误处理和超时控制
- 能监控和调试Agent行为
🚀 快速入门:什么是Agent?
Agent的本质
Agent = LLM + Tool + 决策循环
传统方式(Chain):
输入 → 步骤1 → 步骤2 → 步骤3 → 输出
(固定流程,人类决定每一步)
Agent方式:
输入 → LLM思考 → 选择工具 → 执行 → 观察结果 → 继续思考...
(动态流程,LLM自己决定下一步)
Agent vs Chain 的区别
Chain(链式):
"先分析代码,再检查性能,再检查安全"
→ 固定3步,即使代码很简单也要走完
→ 人类编排流程
Agent(智能体):
"帮我审查这段代码"
→ LLM自己决定:先看看代码结构...
→ 发现有SQL拼接,调用安全检查工具
→ 发现有性能问题,调用性能分析工具
→ 综合所有结果,给出建议
→ LLM自主编排流程
深度讲解
1️⃣ ReAct循环:Agent的思维模式
ReAct = Reasoning(推理) + Action(行动)
┌──────────────────────────────────┐
│ ReAct 循环 │
│ │
│ 用户输入 │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 思考 │ LLM分析问题 │
│ │(Reason) │ 决定下一步行动 │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 行动 │ 调用选中的工具 │
│ │(Action) │ 传入参数 │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 观察 │ 获取工具返回结果 │
│ │(Observe) │ 分析结果 │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ 够了吗?──── 否 ──→ 回到思考 │
│ │ │
│ 是 │
│ │ │
│ ▼ │
│ 最终回答 │
└──────────────────────────────────┘
ReAct Demo:最简单的ReAct实现
/**
* ReAct 范式 Demo
* 核心循环:Think(推理) → Act(行动) → Observe(观察) → 重复
*
* 这个例子展示了ReAct的完整过程:
* 用户问"杭州今天适合跑步吗?" →
* Think: 需要知道天气 → Act: 调用天气工具 → Observe: 25°C晴天
* Think: 需要知道空气质量 → Act: 调用AQI工具 → Observe: AQI 45
* Think: 信息足够了 → 最终回答
*/
@Service
public class ReActDemo {
private final ChatLanguageModel model;
private final List<ToolSpecification> toolSpecs;
private final Map<String, ToolExecutor> executors;
private static final int MAX_ITERATIONS = 5;
/**
* ReAct 主循环 — 这是整个范式的核心
*/
public String run(String userQuestion) {
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("""
你是一个helpful助手。使用工具获取信息后再回答。
每次只调用一个最相关的工具。
信息足够时,直接给出最终回答。
"""));
messages.add(UserMessage.from(userQuestion));
for (int i = 1; i <= MAX_ITERATIONS; i++) {
// ============ THINK(推理)============
// LLM 分析当前信息,决定下一步
Response<AiMessage> response = model.generate(messages, toolSpecs);
AiMessage aiMessage = response.content();
messages.add(aiMessage);
// ============ 判断:够了吗? ============
if (!aiMessage.hasToolExecutionRequests()) {
// LLM认为信息足够,直接给出最终回答
log.info("[ReAct] 第{}轮结束,给出最终回答", i);
return aiMessage.text();
}
// ============ ACT(行动)============
// 执行LLM选择的工具
for (ToolExecutionRequest toolReq : aiMessage.toolExecutionRequests()) {
log.info("[ReAct] 第{}轮 → Act: 调用工具 {}({})",
i, toolReq.name(), toolReq.arguments());
String toolResult = executors.get(toolReq.name())
.execute(toolReq, null);
// ============ OBSERVE(观察)============
// 把工具结果反馈给LLM
log.info("[ReAct] 第{}轮 → Observe: {}", i, toolResult);
messages.add(ToolExecutionResultMessage.from(toolReq, toolResult));
}
// → 回到循环顶部,LLM继续Think...
}
return "达到最大迭代次数,无法完成推理";
}
}
// ===== 工具定义 =====
public class WeatherTools {
@Tool("查询指定城市的当前天气(温度、天气状况)")
public String getWeather(@P("城市名称") String city) {
// 实际项目中调用天气API
return "{"city":"" + city + "","temp":"25°C","condition":"晴天"}";
}
@Tool("查询指定城市的空气质量指数AQI")
public String getAirQuality(@P("城市名称") String city) {
return "{"city":"" + city + "","aqi":45,"level":"优"}";
}
}
运行过程(日志输出):
[ReAct] 第1轮 → Act: 调用工具 getWeather({"city":"杭州"})
[ReAct] 第1轮 → Observe: {"city":"杭州","temp":"25°C","condition":"晴天"}
[ReAct] 第2轮 → Act: 调用工具 getAirQuality({"city":"杭州"})
[ReAct] 第2轮 → Observe: {"city":"杭州","aqi":45,"level":"优"}
[ReAct] 第3轮结束,给出最终回答
最终回答:杭州今天非常适合跑步!气温25°C,晴天,空气质量AQI 45(优),
建议选择早晨或傍晚时段,注意防晒和补水。
关键理解:每轮循环中,LLM 自主决定"调用哪个工具"还是"直接回答"。这就是 ReAct 的核心 — 推理驱动行动。
💡 对比:后面第4️⃣节的"手动实现Agent Loop"是 ReAct 的生产级版本,增加了错误处理、超时控制、多工具并行等能力。
ReAct之外:Agent七大范式全景
ReAct只是Agent设计的范式之一。根据思考、行动、观察三者的编排方式不同,业界发展出了七大核心范式:
┌──────────────┬──────────────────┬─────────────┬───────────┐
│ 范式 │ 一句话本质 │ LLM调用次数 │ 核心优势 │
├──────────────┼──────────────────┼─────────────┼───────────┤
│ ReAct │ 边想边做 │ N次(每步1次)│ 灵活通用 │
│ Plan-Execute │ 先想后做 │ 2+N次 │ 全局规划 │
│ Reflexion │ 做完回头看 │ 3N次(最贵) │ 自我改进 │
│ REWOO │ 想一次,做一次 │ 2次(最便宜)│ 成本最低 │
│ Self-Ask │ 大问题拆小问题 │ N次 │ 多跳推理 │
│ LATS │ 像下棋一样搜索 │ 分支×深度 │ 找最优解 │
│ Multi-Agent │ 分工协作 │ Agent数×轮数│ 专业分工 │
└──────────────┴──────────────────┴─────────────┴───────────┘
范式一:Plan-and-Execute(计划-执行)
本质:先制定完整计划,再逐步执行。规划和执行可以用不同模型(强模型规划、弱模型执行 = 省钱)。
用户问题
│
▼
┌──────────┐
│ Planner │ ← 强模型(GPT-4o)只调用1次
│ 规划全部 │ → 输出:[步骤1, 步骤2, 步骤3, ...]
└────┬─────┘
│
▼
┌──────────┐
│ Executor │ ← 弱模型(GPT-4o-mini)逐步执行
│ 逐步执行 │ → 每步一个结果
└────┬─────┘
│
▼
┌──────────┐
│ Replanner│ ← 可选:根据执行结果动态调整计划
│ 重新规划 │
└──────────┘
/**
* Plan-and-Execute 范式实现
* 核心思想:分离规划和执行,降低成本
*/
@Service
public class PlanAndExecuteAgent {
@Autowired @Qualifier("strongModel") // GPT-4o 做规划
private ChatLanguageModel planner;
@Autowired @Qualifier("fastModel") // GPT-4o-mini 做执行
private ChatLanguageModel executor;
public String execute(String query) {
// Phase 1: 规划 — 一次LLM调用,生成完整步骤
String plan = planner.generate("""
请将以下任务分解为3-5个具体执行步骤,每步一行,格式为"1. xxx":
任务:%s
""".formatted(query));
List<String> steps = parseSteps(plan);
log.info("[Plan] 生成{}个步骤", steps.size());
// Phase 2: 执行 — 用便宜模型逐步执行
StringBuilder context = new StringBuilder();
for (int i = 0; i < steps.size(); i++) {
String stepResult = executor.generate("""
任务上下文:%s
已完成的步骤结果:
%s
请执行当前步骤:%s
""".formatted(query, context, steps.get(i)));
context.append("步骤").append(i + 1).append(":")
.append(stepResult).append("\n");
log.info("[Execute] 步骤{}/{} 完成", i + 1, steps.size());
}
// Phase 3: 综合 — 用强模型生成最终答案
return planner.generate(
"请基于以下执行结果,给出最终完整答案:\n" + context);
}
}
// 成本对比:规划1次(强) + 执行N次(弱) + 综合1次(强)
// vs ReAct:每步都用强模型 → Plan-Execute省约60%成本
vs ReAct:
| ReAct | Plan-and-Execute | |
|---|---|---|
| 规划方式 | 每步临时决定 | 预先制定完整计划 |
| 全局视角 | ❌ 没有 | ✅ 有 |
| 灵活性 | 高(随时调整) | 中(按计划执行) |
| 成本 | 高(每步用强模型) | 低(执行用弱模型) |
适用场景:复杂多步任务、成本敏感场景、步骤之间有明确依赖关系。
范式二:Reflexion(自我反思)
本质:做完之后回头审视结果,从错误中学习,带着教训重新执行。一种元认知机制。
用户问题
│
▼
┌──────────┐
│ Actor │ ← 第1次执行,得到初步结果
│ 执行 │
└────┬─────┘
│ 初步结果
▼
┌──────────┐
│Evaluator │ ← 评估结果质量(打分 / 运行测试)
│ 评估 │
└────┬─────┘
│ 评分:6/10,有问题
▼
┌──────────┐
│Reflector │ ← 反思:"我漏掉了边界条件处理..."
│ 反思 │ → 教训存入记忆
└────┬─────┘
│ 带着教训
▼
┌──────────┐
│ Actor │ ← 第2次执行,避免之前的错误
│ 重新执行 │ → 评分:9/10 ✅
└──────────┘
/**
* Reflexion 范式实现
* 核心思想:通过自我反思迭代改进结果
*/
@Service
public class ReflexionAgent {
private final ChatLanguageModel model;
public String execute(String query) {
List<String> reflectionMemory = new ArrayList<>(); // 反思记忆
String bestResult = null;
int bestScore = 0;
for (int attempt = 0; attempt < 3; attempt++) {
// Step 1: 执行(带上历史反思教训)
String reflections = reflectionMemory.isEmpty()
? "无" : String.join("\n", reflectionMemory);
String result = model.generate("""
任务:%s
请避免以下历史错误:
%s
请认真执行任务。
""".formatted(query, reflections));
// Step 2: 评估(可以是LLM评估,也可以是运行测试)
String evaluation = model.generate("""
请评估以下结果的质量(1-10分),并指出具体问题:
任务:%s
结果:%s
输出格式:分数:X 问题:xxx
""".formatted(query, result));
int score = extractScore(evaluation);
log.info("[Reflexion] 第{}次尝试,得分:{}/10", attempt + 1, score);
if (score > bestScore) {
bestScore = score;
bestResult = result;
}
// Step 3: 足够好则提前返回
if (score >= 8) return result;
// Step 4: 反思并记忆(关键步骤!)
String reflection = model.generate("""
你的回答得了 %d/10 分。问题是:%s
请用一句话总结教训,下次如何改进?
""".formatted(score, evaluation));
reflectionMemory.add("第%d次教训:%s".formatted(attempt + 1, reflection));
}
return bestResult; // 返回最优结果
}
}
// 特点:每次尝试约3次LLM调用(执行+评估+反思),最贵但准确率最高
适用场景:代码生成(生成→测试→反思→修复)、写作改进(初稿→审核→反思→重写)、需要高准确性的推理任务。
范式三:REWOO(无观察推理)
本质:一次性规划所有工具调用,批量执行后再综合。把N次LLM调用压缩到仅2次,成本最低。
对比:
ReAct: LLM → Tool → LLM → Tool → LLM → Tool → LLM (7次LLM调用)
REWOO: LLM(规划全部) → Tool → Tool → Tool → LLM(综合) (2次LLM调用)
流程:
┌──────────────┐
│ Planner │ ← 第1次LLM调用
│ 一次性规划 │ → #E1 = search("Java性能优化")
│ 所有工具调用 │ → #E2 = analyzeCode(代码)
│ (用变量引用)│ → #E3 = checkSecurity(代码)
└──────┬───────┘
│
▼
┌──────────────┐
│ Worker │ ← 不调LLM,纯工具执行
│ 顺序执行工具 │ → 解析变量依赖
│ 收集所有结果 │ → 可并行执行无依赖的工具
└──────┬───────┘
│ 所有工具结果
▼
┌──────────────┐
│ Solver │ ← 第2次LLM调用
│ 综合所有结果 │ → 生成最终答案
└──────────────┘
/**
* REWOO 范式实现
* 核心思想:用变量引用预规划所有工具调用,减少LLM调用次数
*/
@Service
public class RewooAgent {
private final ChatLanguageModel model;
private final Map<String, Function<String, String>> tools;
public String execute(String query) {
// Step 1: Planner — 一次LLM调用,规划所有工具
String plan = model.generate("""
请为以下任务规划工具调用。用#E1、#E2等变量引用前序结果。
可用工具:search(query), analyzeCode(code), checkSecurity(code)
任务:%s
输出格式(每行一个步骤):
#E1 = search("相关查询")
#E2 = analyzeCode(代码)
#E3 = checkSecurity(代码)
""".formatted(query));
// Step 2: Worker — 按顺序执行,解析变量依赖(不调用LLM)
Map<String, String> results = new LinkedHashMap<>();
for (String step : plan.lines().filter(l -> l.startsWith("#E")).toList()) {
String varName = step.split("=")[0].trim(); // #E1
String toolCall = step.split("=", 2)[1].trim(); // search("xxx")
// 将变量引用替换为实际结果
for (var entry : results.entrySet()) {
toolCall = toolCall.replace(entry.getKey(), entry.getValue());
}
String result = executeTool(toolCall);
results.put(varName, result);
}
// Step 3: Solver — 一次LLM调用,综合所有结果
String allResults = results.entrySet().stream()
.map(e -> e.getKey() + " = " + e.getValue())
.collect(Collectors.joining("\n\n"));
return model.generate("""
任务:%s
工具执行结果:
%s
请基于以上结果给出完整答案。
""".formatted(query, allResults));
}
}
// 关键优势:只需2次LLM调用,成本比ReAct降低70%+
// 局限:无法根据中间结果动态调整工具调用计划
适用场景:成本敏感(LLM调用次数降低70%+)、工具调用依赖关系明确、批量数据处理。
范式四:Self-Ask(自问自答)
本质:将复杂问题递归拆解为子问题,逐个解答后合成最终答案。
问题:"LangChain4J 最适合用哪种向量数据库部署在K8s上?"
│
├─ 子问题1:"LangChain4J 支持哪些向量数据库?"
│ └─ 回答:Milvus, Chroma, Pinecone, Redis...
│
├─ 子问题2:"这些数据库中哪些适合K8s部署?"
│ └─ 回答:Milvus(有Helm Chart)、Chroma(轻量容器化)
│
├─ 子问题3:"Milvus vs Chroma 在K8s上的运维差异?"
│ └─ 回答:Milvus适合大规模、Chroma适合小规模
│
└─ 最终答案:大规模选Milvus,小规模选Chroma
/**
* Self-Ask 范式实现
* 核心思想:将多跳推理问题分解为单跳子问题
*/
@Service
public class SelfAskAgent {
private final ChatLanguageModel model;
private final SearchTool searchTool;
public String execute(String query) {
List<String> qaHistory = new ArrayList<>();
for (int i = 0; i < 5; i++) {
String context = qaHistory.isEmpty()
? "暂无" : String.join("\n", qaHistory);
// 问LLM:需要先回答什么子问题?还是可以直接回答了?
String response = model.generate("""
原始问题:%s
已知信息:
%s
请判断:
- 如果需要先回答一个子问题,输出:Follow up: <子问题>
- 如果已经可以回答原始问题,输出:Final Answer: <最终答案>
""".formatted(query, context));
if (response.contains("Final Answer:")) {
return response.split("Final Answer:")[1].trim();
}
// 提取子问题,用搜索工具回答
String subQuestion = response.split("Follow up:")[1].trim();
String subAnswer = searchTool.search(subQuestion);
qaHistory.add("Q: " + subQuestion + "\nA: " + subAnswer);
log.info("[Self-Ask] 子问题{}: {}", i + 1, subQuestion);
}
return "无法在限定步数内回答";
}
}
适用场景:多跳推理("A的老板的公司的CEO是谁?")、知识密集型问答、与搜索引擎结合的问答系统。
范式五:LATS(语言智能体树搜索)
本质:像下棋一样,同时探索多条行动路径,评估打分后选最优路径继续深入。借鉴了蒙特卡洛树搜索(MCTS)。
问题
│
┌─────────┼─────────┐
│ │ │
行动A 行动B 行动C ← 同时生成多种行动
│ │ │
评分:7 评分:9 评分:5 ← LLM评估每条路径
│ │
× 选择B ✅ ← 剪枝,只走最优
│
┌─────┼─────┐
行动B1 行动B2 行动B3 ← 继续分支探索
│ │
评分:6 评分:8 ✅
│
最终答案
适用场景:编程竞赛(探索多种算法解法)、数学证明(多条推导路径)、需要找最优解的复杂决策。
范式六:Multi-Agent(多智能体协作)
本质:多个Agent角色分工,各司其职协作完成任务。
模式A - 辩论式:Agent正方 ◄──► Agent反方 → 辩论出更好的答案
模式B - 流水线:Agent研究 → Agent写作 → Agent审核 → Agent优化
模式C - 主从式:协调者Agent → 分派任务给多个Worker Agent
详细实现见 [[2019-多智能体协作和复杂推理]]。
范式选择决策树
你的任务是什么?
│
├─ 简单工具调用
│ └─→ ReAct ✅(最通用,LangChain4J默认支持)
│
├─ 复杂多步任务
│ ├─ 需要省钱? → REWOO ✅(只需2次LLM调用)
│ └─ 需要全局规划? → Plan-and-Execute ✅
│
├─ 需要高准确性(代码生成、推理)
│ └─→ Reflexion ✅(自我纠错,迭代改进)
│
├─ 多跳知识问答
│ └─→ Self-Ask ✅(递归分解子问题)
│
├─ 需要探索多种解法
│ └─→ LATS ✅(树搜索找最优)
│
└─ 一个Agent搞不定
└─→ Multi-Agent ✅(角色分工协作)
本质上,所有范式都在回答同一个问题:LLM的思考、行动、观察三者之间,应该如何编排?
- ReAct:交替进行 | Plan-Execute:思考先集中做完 | Reflexion:多加一步回顾
- REWOO:行动先集中做完 | Self-Ask:思考递归分解 | LATS:并行探索多条路径
2️⃣ Tool定义:给Agent装备工具
基础Tool定义
/**
* 使用@Tool注解定义工具
* LLM会根据方法名和描述来决定何时调用
*/
public class CodeAnalysisTools {
@Tool("分析Java代码的结构,包括类、方法、字段的数量和复杂度")
public String analyzeCodeStructure(
@P("要分析的Java源代码文本") String code) {
int classCount = countPattern(code, "class\\s+\\w+");
int methodCount = countPattern(code, "(public|private|protected)\\s+\\w+\\s+\\w+\\s*\\(");
int fieldCount = countPattern(code, "(private|public|protected)\\s+\\w+\\s+\\w+\\s*;");
return String.format("代码结构分析:%d个类,%d个方法,%d个字段",
classCount, methodCount, fieldCount);
}
@Tool("检查代码中的安全漏洞,如SQL注入、XSS等")
public String checkSecurity(
@P("要检查的Java源代码") String code) {
List<String> issues = new ArrayList<>();
if (code.contains("Statement") && !code.contains("PreparedStatement")) {
issues.add("发现SQL注入风险:使用了Statement而非PreparedStatement");
}
if (code.contains("innerHTML") || code.contains("document.write")) {
issues.add("发现XSS风险:直接操作DOM");
}
return issues.isEmpty() ? "未发现安全漏洞" : String.join("\n", issues);
}
@Tool("检查代码的性能问题")
public String checkPerformance(
@P("要检查的Java源代码") String code) {
List<String> issues = new ArrayList<>();
if (code.contains("for") && code.contains("for")) {
issues.add("可能存在嵌套循环,注意时间复杂度");
}
if (code.contains("synchronized")) {
issues.add("使用了synchronized,注意死锁和性能影响");
}
return issues.isEmpty() ? "未发现明显性能问题" : String.join("\n", issues);
}
private int countPattern(String text, String pattern) {
return (int) Pattern.compile(pattern).matcher(text).results().count();
}
}
@Tool注解详解
public class ToolAnnotationExamples {
// 1. 基本用法 - 方法名作为工具名
@Tool("获取当前时间")
public String getCurrentTime() {
return LocalDateTime.now().toString();
}
// 2. 带参数描述
@Tool("计算两个数字的和")
public int add(
@P("第一个数字") int a,
@P("第二个数字") int b) {
return a + b;
}
// 3. 返回复杂对象(会被序列化为JSON)
@Tool("查询用户信息")
public UserInfo queryUser(@P("用户ID") String userId) {
return userService.findById(userId);
}
// 4. 可选参数
@Tool("搜索代码中的特定模式")
public String searchCode(
@P("要搜索的代码") String code,
@P("搜索的模式(正则表达式)") String pattern) {
Matcher matcher = Pattern.compile(pattern).matcher(code);
List<String> matches = new ArrayList<>();
while (matcher.find()) {
matches.add(matcher.group());
}
return "找到 " + matches.size() + " 个匹配:" + matches;
}
// 5. 枚举参数
@Tool("根据严重程度过滤问题")
public List<String> filterIssues(
@P("严重程度:HIGH, MEDIUM, LOW") String severity) {
Severity level = Severity.valueOf(severity);
return issueService.findBySeverity(level);
}
}
3️⃣ AiServices模式:声明式Agent创建
定义Agent接口
/**
* 使用AiServices创建Agent
* 接口定义 → 框架自动实现 → 包含Tool调用能力
*/
public interface CodeReviewAgent {
@SystemMessage("""
你是一个专业的代码审查助手。
你可以使用工具来分析代码的结构、安全性和性能。
请根据分析结果给出综合的代码审查建议。
审查流程:
1. 先分析代码结构
2. 检查安全问题
3. 检查性能问题
4. 综合给出建议
""")
String reviewCode(@UserMessage String codeDescription);
}
创建和使用Agent
@Configuration
public class AgentConfig {
@Bean
public CodeReviewAgent codeReviewAgent(ChatLanguageModel model) {
// 创建工具实例
CodeAnalysisTools tools = new CodeAnalysisTools();
// 使用AiServices构建Agent
return AiServices.builder(CodeReviewAgent.class)
.chatLanguageModel(model)
.tools(tools) // 注册工具
.chatMemory(MessageWindowChatMemory.withMaxMessages(20))
.build();
}
}
// 使用Agent
@Service
public class CodeReviewService {
@Autowired
private CodeReviewAgent agent;
public String review(String code) {
return agent.reviewCode("请审查以下代码:\n" + code);
}
}
AiServices的工作原理
AiServices 是 LangChain4J 实现 Agent 的核心机制。理解它的内部原理,才能真正掌握 Agent。
第一层:JDK 动态代理 — Agent的"壳"
// 你写的代码:
CodeReviewAgent agent = AiServices.builder(CodeReviewAgent.class)
.chatLanguageModel(model)
.tools(new CodeAnalysisTools())
.build();
// LangChain4J内部做了什么?
// ↓↓↓ 等价于 ↓↓↓
CodeReviewAgent agent = (CodeReviewAgent) Proxy.newProxyInstance(
CodeReviewAgent.class.getClassLoader(),
new Class[]{CodeReviewAgent.class},
new AiServiceInvocationHandler(model, tools, memory, ...) // 核心!
);
AiServices.build() 的内部过程:
1. 扫描接口上的注解
├─ @SystemMessage → 提取系统提示词
├─ @UserMessage → 标记用户输入参数
└─ @V → 模板变量
2. 扫描工具对象上的 @Tool 注解
├─ 方法名 → 工具名
├─ @Tool("描述") → 工具描述(给LLM看的)
├─ @P("参数描述") → 参数描述
└─ 生成 List<ToolSpecification>(JSON Schema格式)
3. 创建 JDK 动态代理
└─ 所有方法调用都被 InvocationHandler 拦截
第二层:@Tool → ToolSpecification — 让LLM"看懂"工具
// 你定义的工具:
public class CodeAnalysisTools {
@Tool("分析Java代码的结构,返回类数、方法数")
public String analyzeStructure(@P("Java源代码") String code) { ... }
}
// LangChain4J 扫描后转换成的 ToolSpecification(发给LLM的JSON):
// ↓↓↓ 等价于 ↓↓↓
ToolSpecification.builder()
.name("analyzeStructure") // 方法名
.description("分析Java代码的结构,返回类数、方法数") // @Tool的值
.addParameter("code", JsonSchemaProperty.STRING, // 参数类型
description("Java源代码")) // @P的值
.build();
// 最终发给 OpenAI API 的格式:
// {
// "type": "function",
// "function": {
// "name": "analyzeStructure",
// "description": "分析Java代码的结构,返回类数、方法数",
// "parameters": {
// "type": "object",
// "properties": {
// "code": { "type": "string", "description": "Java源代码" }
// },
// "required": ["code"]
// }
// }
// }
关键理解:
@Tool 注解的描述 = LLM 决定"用不用这个工具"的唯一依据!
LLM 收到的信息:
┌───────────────────────────────────────┐
│ 你有以下工具可用: │
│ │
│ 1. analyzeStructure │
│ 描述:"分析Java代码的结构,返回类数、方法数"│
│ 参数:code (string) - "Java源代码" │
│ │
│ 2. checkSecurity │
│ 描述:"检查代码中的安全漏洞" │
│ 参数:code (string) - "Java源代码" │
│ │
│ 你可以选择调用工具,也可以直接回答。 │
└───────────────────────────────────────┘
第三层:InvocationHandler — Agent循环的核心引擎
当你调用 agent.reviewCode("审查这段代码") 时,InvocationHandler 内部执行的完整流程:
/**
* 伪代码:AiServices 内部的 InvocationHandler
* 这就是 LangChain4J Agent 的"心脏"
*/
class AiServiceInvocationHandler implements InvocationHandler {
private final ChatLanguageModel model;
private final List<ToolSpecification> toolSpecs; // 工具定义列表
private final Map<String, ToolExecutor> toolExecutors; // 工具执行器
private final ChatMemory memory; // 对话记忆
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// ===== Step 1: 构建消息 =====
// 从注解提取 SystemMessage
String systemPrompt = method.getAnnotation(SystemMessage.class).value();
memory.add(SystemMessage.from(systemPrompt));
// 从参数提取 UserMessage
String userInput = (String) args[0]; // @UserMessage 标注的参数
memory.add(UserMessage.from(userInput));
// ===== Step 2: Agent 循环(ReAct 的核心!)=====
int maxIterations = 10; // 防止死循环
for (int i = 0; i < maxIterations; i++) {
// 2a. 调用 LLM(带工具定义)
Response<AiMessage> response = model.generate(
memory.messages(), // 完整的消息历史
toolSpecs // 所有可用工具的定义
);
AiMessage aiMessage = response.content();
memory.add(aiMessage);
// 2b. 判断:LLM 是否要调用工具?
if (!aiMessage.hasToolExecutionRequests()) {
// ══════════════════════════════════════
// LLM 没有调用工具 → 循环结束,返回结果
// 这就是 ReAct 中的"够了吗?→ 是"
// ══════════════════════════════════════
return aiMessage.text();
}
// 2c. LLM 决定调用工具 → 执行
for (ToolExecutionRequest req : aiMessage.toolExecutionRequests()) {
// 找到对应的工具执行器
ToolExecutor executor = toolExecutors.get(req.name());
// 执行工具(调用你的 @Tool 方法)
// 内部:JSON参数 → 反序列化 → 反射调用方法 → 结果序列化
String result = executor.execute(req, memoryId);
// 把工具结果放回消息历史
memory.add(ToolExecutionResultMessage.from(req, result));
}
// → 回到循环顶部,LLM 基于工具结果继续思考
}
throw new RuntimeException("Agent达到最大迭代次数");
}
}
第四层:完整调用链时序图
你的代码 AiServices 内部 LLM API
│ │ │
│ agent.reviewCode() │ │
│─────────────────────>│ │
│ │ │
│ │ Step 1: 构建消息 │
│ │ ┌─────────────────────┐ │
│ │ │ SystemMsg: "你是审查专家"│ │
│ │ │ UserMsg: "审查这段代码" │ │
│ │ │ Tools: [analyze, check]│ │
│ │ └─────────────────────┘ │
│ │ │
│ │ ──── 第1轮循环 ──── │
│ │ │
│ │ generate(messages, tools) │
│ │ ──────────────────────────────>│
│ │ │
│ │ AiMessage: 调用analyzeStructure
│ │ <──────────────────────────────│
│ │ │
│ │ 反射调用你的 @Tool 方法 │
│ │ tools.analyzeStructure(code) │
│ │ → 结果: "3个类,10个方法" │
│ │ │
│ │ 把结果加入消息历史 │
│ │ │
│ │ ──── 第2轮循环 ──── │
│ │ │
│ │ generate(messages, tools) │
│ │ ──────────────────────────────>│
│ │ │
│ │ AiMessage: 调用checkSecurity │
│ │ <──────────────────────────────│
│ │ │
│ │ tools.checkSecurity(code) │
│ │ → 结果: "发现SQL注入风险" │
│ │ │
│ │ ──── 第3轮循环 ──── │
│ │ │
│ │ generate(messages, tools) │
│ │ ──────────────────────────────>│
│ │ │
│ │ AiMessage: 不调用工具,给出文本
│ │ "综合审查结果:发现1个SQL注入..."
│ │ <──────────────────────────────│
│ │ │
│ │ hasToolExecutionRequests = false│
│ │ → 循环结束! │
│ │ │
│ return "综合审查结果..." │ │
│<─────────────────────│ │
第五层:ToolExecutor 内部 — 从JSON到方法调用
/**
* 伪代码:ToolExecutor 如何执行你的 @Tool 方法
*
* LLM返回的是 JSON:{"name":"checkSecurity", "arguments":"{\"code\":\"...\"}"}
* ToolExecutor 需要把 JSON 转换为 Java 方法调用
*/
class DefaultToolExecutor implements ToolExecutor {
private final Object toolObject; // 你的工具类实例(如 CodeAnalysisTools)
private final Method method; // @Tool标注的方法(如 checkSecurity)
@Override
public String execute(ToolExecutionRequest request, Object memoryId) {
// 1. 解析LLM返回的JSON参数
String jsonArgs = request.arguments(); // {"code":"public class Foo {...}"}
Map<String, Object> args = JsonParser.parse(jsonArgs);
// 2. 将JSON参数映射到Java方法参数
Object[] methodArgs = new Object[method.getParameterCount()];
Parameter[] params = method.getParameters();
for (int i = 0; i < params.length; i++) {
String paramName = params[i].getAnnotation(P.class) != null
? params[i].getName() : params[i].getName();
methodArgs[i] = convertType(args.get(paramName), params[i].getType());
}
// 3. 反射调用你的方法
Object result = method.invoke(toolObject, methodArgs);
// 4. 结果序列化为字符串返回给LLM
return result.toString();
}
}
总结:三句话理解 LangChain4J Agent
1. 外壳:JDK动态代理 — 你调接口方法,它调LLM
2. 引擎:while循环 — LLM返回工具调用就执行,返回文本就结束
3. 桥梁:ToolExecutor — JSON参数 → 反射调用 → 结果字符串
整个 Agent 的本质 = 动态代理 + while(hasToolCalls) + 反射
💡 关键洞察:LangChain4J 的 Agent 并没有什么"魔法"。它只是把 LLM 的 Function Calling 能力 + Java 的动态代理 + 一个 while 循环组合在一起。理解了这三点,你就理解了整个 Agent 实现。
4️⃣ 手动实现Agent Loop
/**
* 手动实现Agent循环
* 完全控制每一步的执行过程
*/
@Service
public class ManualAgentLoop {
private static final Logger log = LoggerFactory.getLogger(ManualAgentLoop.class);
private static final int MAX_ITERATIONS = 10;
private final ChatLanguageModel model;
private final Map<String, ToolExecutor> toolExecutors;
private final List<ToolSpecification> toolSpecs;
public ManualAgentLoop(ChatLanguageModel model) {
this.model = model;
// 注册工具
CodeAnalysisTools tools = new CodeAnalysisTools();
this.toolSpecs = ToolSpecifications.toolSpecificationsFrom(tools);
this.toolExecutors = new HashMap<>();
for (ToolSpecification spec : toolSpecs) {
toolExecutors.put(spec.name(),
new DefaultToolExecutor(tools, spec));
}
}
/**
* 执行Agent循环
*/
public String execute(String userQuery) {
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from(
"你是代码审查助手。使用可用的工具来分析代码。"));
messages.add(UserMessage.from(userQuery));
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
log.info("[Agent] 第{}轮迭代,消息数:{}", iteration + 1, messages.size());
// 步骤1:调用LLM(带工具定义)
Response<AiMessage> response = model.generate(messages, toolSpecs);
AiMessage aiMessage = response.content();
messages.add(aiMessage);
// 步骤2:检查是否有工具调用
if (!aiMessage.hasToolExecutionRequests()) {
// 没有工具调用 = LLM认为任务完成
log.info("[Agent] 任务完成,共{}轮迭代", iteration + 1);
return aiMessage.text();
}
// 步骤3:执行所有工具调用
for (ToolExecutionRequest toolRequest : aiMessage.toolExecutionRequests()) {
String toolName = toolRequest.name();
String toolArgs = toolRequest.arguments();
log.info("[Agent] 调用工具:{} 参数:{}", toolName, toolArgs);
// 执行工具
ToolExecutor executor = toolExecutors.get(toolName);
if (executor == null) {
String errorMsg = "未知工具:" + toolName;
log.error("[Agent] {}", errorMsg);
messages.add(ToolExecutionResultMessage.from(
toolRequest, errorMsg));
continue;
}
String result = executor.execute(toolRequest, null);
log.info("[Agent] 工具结果:{}", result);
// 把工具结果加入消息历史
messages.add(ToolExecutionResultMessage.from(
toolRequest, result));
}
}
log.warn("[Agent] 达到最大迭代次数 {}", MAX_ITERATIONS);
return "抱歉,我无法在规定步骤内完成任务。";
}
}
5️⃣ 错误处理和超时控制
@Service
public class RobustAgent {
private static final Logger log = LoggerFactory.getLogger(RobustAgent.class);
private static final int MAX_ITERATIONS = 10;
private static final long TOOL_TIMEOUT_MS = 30000; // 工具调用超时30秒
private static final long TOTAL_TIMEOUT_MS = 120000; // 总超时2分钟
public String execute(String query) {
long startTime = System.currentTimeMillis();
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("你是代码审查助手。"));
messages.add(UserMessage.from(query));
for (int i = 0; i < MAX_ITERATIONS; i++) {
// 检查总超时
if (System.currentTimeMillis() - startTime > TOTAL_TIMEOUT_MS) {
log.error("[Agent] 总执行时间超时");
return "执行超时,已部分完成的分析结果:" + getPartialResult(messages);
}
try {
Response<AiMessage> response = model.generate(messages, toolSpecs);
AiMessage aiMessage = response.content();
messages.add(aiMessage);
if (!aiMessage.hasToolExecutionRequests()) {
return aiMessage.text();
}
// 执行工具(带超时)
for (ToolExecutionRequest request : aiMessage.toolExecutionRequests()) {
String result = executeToolWithTimeout(request);
messages.add(ToolExecutionResultMessage.from(request, result));
}
} catch (Exception e) {
log.error("[Agent] 第{}轮出错:{}", i + 1, e.getMessage());
messages.add(UserMessage.from(
"工具调用出错:" + e.getMessage() + ",请尝试其他方法"));
}
}
return "达到最大迭代次数,部分结果:" + getPartialResult(messages);
}
private String executeToolWithTimeout(ToolExecutionRequest request) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<String> future = executor.submit(() -> {
ToolExecutor toolExec = toolExecutors.get(request.name());
return toolExec.execute(request, null);
});
return future.get(TOOL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
log.warn("[Tool] {} 执行超时", request.name());
return "工具执行超时,请使用其他方法";
} catch (Exception e) {
log.error("[Tool] {} 执行失败:{}", request.name(), e.getMessage());
return "工具执行失败:" + e.getMessage();
} finally {
executor.shutdownNow();
}
}
private String getPartialResult(List<ChatMessage> messages) {
// 从消息历史中提取已完成的工具结果
return messages.stream()
.filter(m -> m instanceof ToolExecutionResultMessage)
.map(m -> ((ToolExecutionResultMessage) m).text())
.collect(Collectors.joining("\n"));
}
}
6️⃣ 监控和调试Agent行为
@Service
public class MonitoredAgent {
private static final Logger log = LoggerFactory.getLogger(MonitoredAgent.class);
// 使用Micrometer记录指标
private final MeterRegistry meterRegistry;
private final Counter iterationCounter;
private final Counter toolCallCounter;
private final Timer agentTimer;
public MonitoredAgent(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.iterationCounter = Counter.builder("agent.iterations")
.description("Agent迭代次数").register(meterRegistry);
this.toolCallCounter = Counter.builder("agent.tool_calls")
.description("工具调用次数").register(meterRegistry);
this.agentTimer = Timer.builder("agent.execution_time")
.description("Agent执行时间").register(meterRegistry);
}
public String execute(String query) {
return agentTimer.record(() -> {
List<ChatMessage> messages = buildMessages(query);
for (int i = 0; i < MAX_ITERATIONS; i++) {
iterationCounter.increment();
Response<AiMessage> response = model.generate(messages, toolSpecs);
AiMessage aiMessage = response.content();
// 详细日志
log.info("[Agent] 迭代={} finishReason={} hasTools={} tokenUsage={}",
i + 1,
response.finishReason(),
aiMessage.hasToolExecutionRequests(),
response.tokenUsage());
messages.add(aiMessage);
if (!aiMessage.hasToolExecutionRequests()) {
log.info("[Agent] 完成!共{}轮", i + 1);
return aiMessage.text();
}
for (ToolExecutionRequest req : aiMessage.toolExecutionRequests()) {
toolCallCounter.increment();
log.info("[Tool] 调用: name={} args={}", req.name(), req.arguments());
Timer.Sample sample = Timer.start();
String result = executeTool(req);
sample.stop(Timer.builder("agent.tool_duration")
.tag("tool", req.name())
.register(meterRegistry));
log.info("[Tool] 结果: {}",
result.length() > 200 ? result.substring(0, 200) + "..." : result);
messages.add(ToolExecutionResultMessage.from(req, result));
}
}
return "达到最大迭代次数";
});
}
}
💻 实战:完整的代码审查Agent
/**
* 生产级代码审查Agent
* 包含:多工具、错误处理、监控、缓存
*/
// 1. 定义Agent接口
public interface ProductionCodeReviewAgent {
@SystemMessage("""
你是一位资深的Java代码审查专家。
审查时请遵循以下流程:
1. 首先分析代码结构(类、方法、字段)
2. 检查是否存在安全漏洞
3. 评估性能问题
4. 检查代码风格
5. 给出综合评分和改进建议
评分标准:
- 安全性(30%)
- 性能(25%)
- 可读性(25%)
- 最佳实践(20%)
""")
String reviewCode(@UserMessage String request);
}
// 2. 定义工具类
public class ProductionCodeTools {
@Tool("分析Java代码结构,返回类数量、方法数量、代码行数等")
public String analyzeStructure(@P("Java源代码") String code) {
String[] lines = code.split("\n");
int codeLines = (int) Arrays.stream(lines)
.filter(l -> !l.trim().isEmpty() && !l.trim().startsWith("//"))
.count();
int commentLines = (int) Arrays.stream(lines)
.filter(l -> l.trim().startsWith("//") || l.trim().startsWith("*"))
.count();
int methodCount = countMatches(code, "(public|private|protected)\\s+\\w+\\s+\\w+\\s*\\(");
return String.format(
"代码行数:%d(代码%d行,注释%d行),方法数:%d,注释率:%.1f%%",
lines.length, codeLines, commentLines, methodCount,
commentLines * 100.0 / lines.length);
}
@Tool("检查代码中的安全漏洞")
public String securityScan(@P("Java源代码") String code) {
List<String> vulnerabilities = new ArrayList<>();
if (code.contains("Runtime.getRuntime().exec")) {
vulnerabilities.add("[严重] 命令注入风险:使用了Runtime.exec()");
}
if (code.matches("(?s).*Statement.*execute.*\\+.*")) {
vulnerabilities.add("[严重] SQL注入风险:字符串拼接SQL");
}
if (code.contains("new File(") && code.contains("request.getParameter")) {
vulnerabilities.add("[高] 路径遍历风险:用户输入直接用于文件路径");
}
if (!code.contains("@Valid") && code.contains("@RequestBody")) {
vulnerabilities.add("[中] 缺少参数校验:RequestBody没有@Valid");
}
return vulnerabilities.isEmpty()
? "安全扫描通过,未发现漏洞"
: "发现 " + vulnerabilities.size() + " 个安全问题:\n" +
String.join("\n", vulnerabilities);
}
@Tool("分析代码性能瓶颈")
public String performanceAnalysis(@P("Java源代码") String code) {
List<String> issues = new ArrayList<>();
if (code.contains("for") && code.contains("for")) {
issues.add("[性能] 嵌套循环,时间复杂度可能为O(n²)");
}
if (code.contains(".stream()") && code.contains(".collect(")) {
issues.add("[建议] Stream操作较多,考虑是否需要并行流");
}
if (code.contains("new ArrayList") && !code.contains("initialCapacity")) {
issues.add("[建议] ArrayList未指定初始容量");
}
return issues.isEmpty()
? "性能分析通过"
: String.join("\n", issues);
}
@Tool("检查代码风格和最佳实践")
public String checkStyle(@P("Java源代码") String code) {
List<String> suggestions = new ArrayList<>();
if (code.contains("System.out.println")) {
suggestions.add("[风格] 使用Logger替代System.out.println");
}
if (code.contains("catch (Exception e)") && code.contains("e.printStackTrace()")) {
suggestions.add("[风格] 使用Logger记录异常,不要用printStackTrace");
}
if (!code.contains("final ") && code.contains("private ")) {
suggestions.add("[建议] 考虑使用final修饰不变的字段");
}
return suggestions.isEmpty()
? "代码风格良好"
: String.join("\n", suggestions);
}
private int countMatches(String text, String regex) {
return (int) Pattern.compile(regex).matcher(text).results().count();
}
}
// 3. 配置和使用
@Configuration
public class AgentConfiguration {
@Bean
public ProductionCodeReviewAgent codeReviewAgent(
ChatLanguageModel model) {
return AiServices.builder(ProductionCodeReviewAgent.class)
.chatLanguageModel(model)
.tools(new ProductionCodeTools())
.chatMemory(MessageWindowChatMemory.withMaxMessages(30))
.build();
}
}
// 4. 服务层调用
@Service
public class CodeReviewService {
private static final Logger log = LoggerFactory.getLogger(CodeReviewService.class);
@Autowired
private ProductionCodeReviewAgent agent;
@Cacheable(value = "codeReviews", key = "#code.hashCode()")
public String reviewCode(String code) {
log.info("[审查] 开始审查,代码长度:{}", code.length());
long start = System.currentTimeMillis();
try {
String result = agent.reviewCode("请审查以下Java代码:\n```java\n" + code + "\n```");
long duration = System.currentTimeMillis() - start;
log.info("[审查] 完成,耗时:{}ms", duration);
return result;
} catch (Exception e) {
log.error("[审查] 失败", e);
throw new RuntimeException("代码审查失败:" + e.getMessage(), e);
}
}
}
Agent调用时序图
用户 Agent(LLM) Tools
│ │ │
│ "审查这段代码" │ │
│─────────────────────>│ │
│ │ │
│ │ 思考:需要先分析结构 │
│ │──analyzeStructure──>│
│ │<──结果:3个方法───── │
│ │ │
│ │ 思考:检查安全 │
│ │──securityScan──────>│
│ │<──结果:发现SQL注入── │
│ │ │
│ │ 思考:检查性能 │
│ │──performanceAnalysis>│
│ │<──结果:嵌套循环───── │
│ │ │
│ │ 思考:已足够,生成报告 │
│ "综合审查结果..." │ │
│<─────────────────────│ │
🔧 最佳实践
✅ 好的Agent设计
// 1. 工具描述要清晰
@Tool("分析Java代码结构,返回类数、方法数、行数等统计信息")
public String analyze(@P("完整的Java源代码文本") String code) { }
// 2. 工具返回结构化结果
@Tool("检查安全漏洞")
public String checkSecurity(@P("Java源代码") String code) {
return "发现2个问题:\n1. [严重] SQL注入\n2. [中等] 缺少校验";
// 而不是返回 "有问题"
}
// 3. 设置合理的迭代上限
AiServices.builder(Agent.class)
.chatLanguageModel(model)
.tools(tools)
.build();
// 框架默认有迭代上限
// 4. SystemMessage要提供清晰的指导
@SystemMessage("""
你是代码审查专家。
审查流程:1.结构 2.安全 3.性能 4.风格
每个工具只调用一次,避免重复。
""")
❌ 坏的Agent设计
// 1. 工具描述模糊
@Tool("分析代码") // LLM不知道具体做什么
public String analyze(String code) { }
// 2. 工具返回不明确
@Tool("检查安全")
public boolean checkSecurity(String code) {
return true; // LLM无法从boolean理解细节
}
// 3. 没有迭代限制(可能死循环)
// 4. SystemMessage太笼统
@SystemMessage("你是助手") // LLM不知道要做什么
学习成果检查:
- 能解释Agent的本质和ReAct循环
- 能区分七大范式并根据场景选型
- 能使用@Tool和@P定义工具
- 能用AiServices创建Agent
- 能手动实现Agent Loop
- 能为Agent添加错误处理和监控
下一步:学习组件综合对比,掌握何时用ChatModel、Chain、Agent的选择策略。