LangChain4j Agent 模式:ReAct、Plan-and-Solve 与自主决策
从"问答机器"到"自主智能体"------LangChain4j 1.4.0 的 Agentic 框架,让 AI 真正具备推理、规划、行动的完整能力。
前言:当单轮对话不够用
2024 年初,我接手了一个需求:"用 AI 实现自动化报销审批"。
最初的想法很简单:用户提交报销申请 → AI 审批 → 给出结论。但需求细化后,发现这个流程需要:
- 解析申请:理解用户意图和申请金额
- 查询规则:从系统中获取对应的报销政策
- 核查信息:验证员工级别、项目预算余额
- 条件判断:金额超过 5000 需要部门主管确认
- 生成结果:输出审批意见和通知内容
单轮对话根本做不到------它没有工具调用、没有多步推理、没有状态管理。
这时候,我们需要的是 Agent(智能体)。
Agent 的核心能力:
- 自主决定下一步做什么(推理)
- 调用外部工具获取信息或执行操作(行动)
- 根据执行结果调整计划(观察-反思)
- 在达成目标后终止(控制)
LangChain4j 1.4.0 的 Agentic 框架提供了完整的 Agent 构建能力。本文将从原理到实战,完整讲解 ReAct、Plan-and-Solve 等核心模式。
一、Agent 基础:AiServices 与工具调用
在深入 ReAct 之前,先理解 LangChain4j 中 Agent 的基础------带工具的 AiServices。
1.1 LangChain4j 中的 Agent 本质
LangChain4j Agent 工作原理:
用户输入
│
▼
+------------------+
| ChatLanguage |
| Model |
| |
| 1. 分析用户意图 |
| 2. 决定调用哪个 |
| 工具或直接回答|
+--------+---------+
│
工具调用?
│ │
是 否
│ │
▼ ▼
+-------+ 直接
|执行工具| 返回
|获取结果|
+---+---+
│
▼
将工具结果加入上下文
继续让模型推理
│
▼
达到目标?
│ │
是 否(继续循环)
│
▼
返回最终答案
这个"感知-推理-行动"循环,就是 ReAct(Reasoning + Acting)的核心思想。
1.2 最简 Agent 示例
java
// 第一步:定义工具
public class SearchTools {
@Tool("在公司知识库中搜索信息")
public String searchKnowledgeBase(
@P("搜索关键词") String query
) {
// 实际中会调用 RAG 检索
return knowledgeBaseService.search(query);
}
@Tool("查询员工信息")
public String getEmployeeInfo(
@P("员工ID") String employeeId
) {
return employeeService.findById(employeeId).toString();
}
@Tool("查询项目预算余额")
public double getProjectBudget(
@P("项目编号") String projectCode
) {
return projectService.getRemainingBudget(projectCode);
}
}
// 第二步:定义 Agent 接口
@AiService
public interface ExpenseApprovalAgent {
@SystemMessage("""
你是一个专业的报销审批助手。
审批流程:
1. 理解申请内容(金额、类型、项目)
2. 查询相关报销政策
3. 验证员工信息和项目预算
4. 根据以下规则做出判断:
- 金额 <= 500 元:自动批准
- 500 < 金额 <= 5000 元:核实预算后批准
- 金额 > 5000 元:建议人工复核
5. 生成审批意见
始终基于查询到的真实信息做决策,不要凭感觉。
""")
String processExpenseRequest(
@MemoryId String requestId,
@UserMessage String request
);
}
// 第三步:创建 Agent
@Configuration
public class AgentConfig {
@Bean
public ExpenseApprovalAgent expenseAgent(
ChatLanguageModel model,
SearchTools tools,
ChatMemoryProvider memoryProvider
) {
return AiServices.builder(ExpenseApprovalAgent.class)
.chatLanguageModel(model)
.tools(tools)
.chatMemoryProvider(memoryProvider)
.build();
}
}
// 使用
String result = agent.processExpenseRequest(
"req-001",
"申请报销:参加上海 AI 大会交通费 1200 元,项目编号 P-2024-15"
);
二、ReAct 模式:推理与行动的循环
2.1 ReAct 原理剖析
ReAct(Re asoning + Acting)是由 Google 研究团队提出的 Agent 框架,核心思想是将推理过程(Thought)与具体行动(Action)交替进行。
传统 Chain 的局限:
用户问题 -> 模型直接回答(可能有误)
ReAct 模式:
用户问题
│
▼
[Thought] 我需要查询当前股价
│
▼
[Action] 调用 getStockPrice("AAPL")
│
▼
[Observation] AAPL 当前价格:182.5 USD
│
▼
[Thought] 我还需要了解近期走势
│
▼
[Action] 调用 getStockHistory("AAPL", "7d")
│
▼
[Observation] 近 7 天从 175 涨到 182.5,涨幅 4.3%
│
▼
[Thought] 已有足够信息,可以回答了
│
▼
[Answer] AAPL 当前价格 182.5 USD,近 7 天上涨 4.3%
每一次循环都:
- 观察(Observation):当前状态是什么
- 推理(Thought):我应该做什么
- 行动(Action):执行工具或生成答案
2.2 LangChain4j 中的 ReAct 实现
LangChain4j 中,带工具的 AiServices 天然实现了 ReAct 模式------LLM 在每次调用后都会"思考"是否需要继续调用工具,直到得出最终答案。
java
// 模拟一个复杂的 ReAct Agent
public class StockAnalysisAgent {
// 工具集合
@Tool("获取指定股票的当前价格")
public String getStockPrice(@P("股票代码,如 AAPL") String symbol) {
// 调用真实行情 API
return stockApiClient.getCurrentPrice(symbol);
}
@Tool("获取股票历史走势数据")
public String getStockHistory(
@P("股票代码") String symbol,
@P("时间范围,如 7d/30d/90d") String period
) {
return stockApiClient.getHistory(symbol, period);
}
@Tool("获取股票基本面数据(市盈率、市值等)")
public String getStockFundamentals(@P("股票代码") String symbol) {
return stockApiClient.getFundamentals(symbol);
}
@Tool("获取最新财经新闻")
public String getFinancialNews(@P("关键词") String keywords) {
return newsApiClient.search(keywords);
}
@Tool("执行技术分析计算(MA、RSI、MACD等)")
public String calculateTechnicalIndicators(
@P("股票代码") String symbol,
@P("指标类型,如 MA20/RSI14/MACD") String indicator
) {
return technicalAnalysisService.calculate(symbol, indicator);
}
}
@AiService
public interface StockAnalystAssistant {
@SystemMessage("""
你是一位专业的股票分析师,具备多种金融数据查询工具。
分析原则:
1. 先查询基本数据(价格、走势)
2. 再分析基本面(市盈率、市值)
3. 结合技术指标(MA、RSI)
4. 搜索相关新闻
5. 综合给出分析意见和投资建议
始终基于真实数据分析,明确说明不确定性。
不提供具体买卖建议,只提供分析参考。
""")
String analyze(@UserMessage String query);
}
2.3 观察 ReAct 推理过程
通过添加日志监听器,可以清楚看到 ReAct 的每个步骤:
java
public class ReActTracer implements ChatModelListener {
private int iterationCount = 0;
@Override
public void onRequest(ChatModelRequestContext context) {
iterationCount++;
System.out.println("\n=== ReAct 迭代 #" + iterationCount + " ===");
// 打印最新消息
List<ChatMessage> messages = context.messages();
ChatMessage lastMessage = messages.get(messages.size() - 1);
System.out.println("[输入] " + lastMessage.text().substring(0,
Math.min(100, lastMessage.text().length())) + "...");
}
@Override
public void onResponse(ChatModelResponseContext context) {
AiMessage response = context.response().content();
if (response.hasToolExecutionRequests()) {
// 模型决定调用工具
response.toolExecutionRequests().forEach(req -> {
System.out.println("[Thought] 需要调用工具:" + req.name());
System.out.println("[Action] 参数:" + req.arguments());
});
} else {
// 模型直接给出答案
System.out.println("[Answer] " + response.text().substring(0,
Math.min(200, response.text().length())) + "...");
System.out.println("=== 总迭代次数:" + iterationCount + " ===\n");
iterationCount = 0; // 重置
}
}
}
// 配置追踪器
@Bean
public ChatLanguageModel tracedModel() {
ReActTracer tracer = new ReActTracer();
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o")
.listeners(List.of(tracer))
.build();
}
三、Agentic 工作流:LangChain4j 1.4.0 的新框架
LangChain4j 1.4.0 引入了全新的 AgenticServices API,提供更结构化的 Agent 编排方式。
3.1 AgenticServices 核心概念
AgenticServices 架构:
+-----------------------------------+
| AgenticScope | <- 共享上下文(状态)
| {key: value, key: value, ...} |
+-----------------------------------+
| | |
▼ ▼ ▼
+--------+ +--------+ +--------+
| Agent1 | | Agent2 | | Agent3 | <- 各Agent共享状态
+--------+ +--------+ +--------+
| | |
└────┬────┘ |
| |
工作流编排 工具执行
|
▼
+------------------+
| 顺序/循环/条件/并行 | <- 4种工作流模式
+------------------+
3.2 定义 Agent
java
// @Agent 注解用于定义 Agentic 接口方法
public interface WritingPipeline {
@UserMessage("你是一位创意作家。根据主题 {{topic}} 生成不超过 200 字的故事草稿。")
@Agent("生成故事草稿")
String generateDraft(@V("topic") String topic);
}
public interface EditingPipeline {
@UserMessage("""
原始草稿:{{draft}}
请从以下维度优化:
1. 语言流畅度
2. 情感表达
3. 逻辑一致性
输出优化后的版本。
""")
@Agent("优化故事质量")
String polish(@V("draft") String draft);
}
// 创建 Agent 实例
WritingPipeline writer = AgenticServices.agentBuilder(WritingPipeline.class)
.chatModel(creativityModel)
.outputName("draft") // 输出存入 "draft" 变量
.build();
EditingPipeline editor = AgenticServices.agentBuilder(EditingPipeline.class)
.chatModel(qualityModel)
.outputName("final_story") // 输出存入 "final_story" 变量
.build();
3.3 顺序工作流(Sequential)
最基础的工作流:多个 Agent 按顺序执行,前一个的输出是后一个的输入。
java
public interface ContentCreationAgent {
String create(@V("topic") String topic);
}
// 构建三阶段顺序写作流水线
UntypedAgent contentPipeline = AgenticServices.sequenceBuilder()
.subAgents(writer, editor, reviewer) // writer -> editor -> reviewer
.outputName("published_content")
.build();
// 执行
ContentCreationAgent agent = AgenticServices
.agentBuilder(ContentCreationAgent.class)
.agent(contentPipeline)
.build();
String result = agent.create("人工智能改变教育");
// 访问中间状态
AgenticScope scope = agent.getLastScope();
String draft = scope.readState("draft"); // 获取草稿
String polished = scope.readState("final_story"); // 获取润色版
适用场景:
- 内容创作流水线(写作-润色-审核)
- 数据处理管道(提取-转换-加载)
- 报告生成流程(数据分析-可视化-撰写)
3.4 循环工作流(Loop)
重复执行直到满足条件------这是 Plan-and-Solve 的核心。
java
public interface QualityScorer {
@UserMessage("""
请对以下内容进行质量评分(0-1之间的小数):
{{content}}
评分标准:逻辑性(40%) + 可读性(30%) + 专业性(30%)
只输出分数,格式:0.XX
""")
@Agent("内容质量评分")
double scoreContent(@V("content") String content);
}
public interface ContentImprover {
@UserMessage("""
当前内容(质量评分:{{score}}):
{{content}}
请针对以下方面改进:
- 如果逻辑不清晰,重新组织结构
- 如果可读性差,简化语言
- 如果专业度不足,增加深度
输出改进后的完整内容。
""")
@Agent("改进内容质量")
String improveContent(@V("content") String content, @V("score") double score);
}
// 构建自我改进循环
UntypedAgent selfImprovingAgent = AgenticServices.loopBuilder()
.subAgents(scorer, improver)
.maxIterations(5) // 最多循环 5 次,防止无限循环
.exitCondition(scope -> {
// 退出条件:质量评分达到 0.85 以上
double score = scope.readState("score", 0.0);
return score >= 0.85;
})
.build();
Plan-and-Solve 的循环实现:
java
// 经典 Plan-and-Solve 模式
public interface Planner {
@UserMessage("""
用户任务:{{task}}
请制定详细的执行计划,格式:
步骤1:[具体操作]
步骤2:[具体操作]
...
计划要具体可执行。
""")
@Agent("制定执行计划")
String createPlan(@V("task") String task);
}
public interface Executor {
@UserMessage("""
执行计划:
{{plan}}
已执行步骤:
{{executed_steps}}
请执行下一个未完成的步骤,并输出执行结果。
如果所有步骤都已完成,输出"ALL_DONE"。
""")
@Agent("执行计划步骤")
String executeNextStep(@V("plan") String plan,
@V("executed_steps") String executedSteps);
}
// Plan-and-Solve 循环
UntypedAgent planAndSolveAgent = AgenticServices.sequenceBuilder()
.subAgents(
planner, // 第一步:制定计划
AgenticServices.loopBuilder() // 第二步:循环执行
.subAgents(executor)
.maxIterations(10)
.exitCondition(scope ->
"ALL_DONE".equals(scope.readState("step_result")))
.build()
)
.build();
3.5 条件工作流(Conditional)
根据上下文动态路由到不同的 Agent:
java
// 智能客服路由示例
public interface RequestClassifier {
@UserMessage("""
用户请求:{{request}}
请分类为以下类型之一:
- TECHNICAL: 技术问题
- BILLING: 账单问题
- COMPLAINT: 投诉
- GENERAL: 一般咨询
只输出类型名称。
""")
@Agent("请求分类")
String classifyRequest(@V("request") String request);
}
// 按类型路由
UntypedAgent routedService = AgenticServices.conditionalBuilder()
.subAgents(
scope -> "TECHNICAL".equals(scope.readState("category")),
technicalSupportAgent
)
.subAgents(
scope -> "BILLING".equals(scope.readState("category")),
billingAgent
)
.subAgents(
scope -> "COMPLAINT".equals(scope.readState("category")),
complaintHandlingAgent
)
.defaultAgent(generalSupportAgent) // 兜底
.build();
// 完整流程:分类 -> 路由
UntypedAgent intelligentService = AgenticServices.sequenceBuilder()
.subAgents(classifier, routedService)
.build();
3.6 并行工作流(Parallel)
多个 Agent 同时执行,提高处理效率:
java
// 多维度同步分析
public interface MarketAnalysisAgent {
String analyze(@V("company") String company);
}
// 四个维度同时分析
UntypedAgent comprehensiveAnalysis = AgenticServices.parallelBuilder(
MarketAnalysisAgent.class
)
.subAgents(
fundamentalAnalyst, // 基本面分析
technicalAnalyst, // 技术面分析
sentimentAnalyst, // 市场情绪分析
competitorAnalyst // 竞争对手分析
)
.executorService(Executors.newFixedThreadPool(4)) // 4 个线程并行
.build();
// 汇总结果
String report = AgenticServices.agentBuilder(ReportWriter.class)
.agent(comprehensiveAnalysis)
.postProcess(scope -> {
// 整合各维度分析结果
String fundamental = scope.readState("fundamental_analysis");
String technical = scope.readState("technical_analysis");
String sentiment = scope.readState("sentiment_analysis");
String competitor = scope.readState("competitor_analysis");
return synthesizeReport(fundamental, technical, sentiment, competitor);
})
.build();
四、监督 Agent(Supervisor):自主决策的最高形态
4.1 监督 Agent 原理
监督 Agent 是最接近 ReAct 完整实现的模式------Planner(监督者)根据任务自主决定调用哪些子 Agent,形成真正的自主决策。
监督 Agent 工作流:
用户任务:将100欧元从Mario账户转给Georgios
监督Agent (Planner)
│
├─ 思考:需要先做货币转换
│ └─ 调用 ExchangeAgent
│ └─ 100欧元 = 108美元
│
├─ 思考:从Mario账户扣款
│ └─ 调用 WithdrawAgent
│ └─ Mario账户扣除108美元 (新余额:892美元)
│
├─ 思考:向Georgios账户存款
│ └─ 调用 CreditAgent
│ └─ Georgios账户增加108美元 (新余额:1108美元)
│
└─ 任务完成,生成摘要
4.2 实现监督 Agent
java
// 子 Agent 定义
public interface ExchangeAgent {
@UserMessage("将 {{amount}} {{from_currency}} 转换为 {{to_currency}},返回转换后金额")
@Agent("货币转换")
double exchange(@V("amount") double amount,
@V("from_currency") String from,
@V("to_currency") String to);
}
public interface WithdrawAgent {
@UserMessage("从 {{user}} 账户扣款 {{amount}} 美元,返回扣款后余额")
@Agent("账户扣款")
double withdraw(@V("user") String user, @V("amount") double amount);
}
public interface CreditAgent {
@UserMessage("向 {{user}} 账户存入 {{amount}} 美元,返回存入后余额")
@Agent("账户存款")
double credit(@V("user") String user, @V("amount") double amount);
}
// 创建监督 Agent(配置 Planner 模型和执行模型)
ChatLanguageModel plannerModel = OpenAiChatModel.builder()
.modelName("gpt-4o") // Planner 用强模型
.build();
ChatLanguageModel executorModel = OpenAiChatModel.builder()
.modelName("gpt-4o-mini") // 子 Agent 用轻量模型
.build();
ExchangeAgent exchangeAgent = AgenticServices
.agentBuilder(ExchangeAgent.class)
.chatModel(executorModel)
.outputName("exchange_result")
.build();
WithdrawAgent withdrawAgent = AgenticServices
.agentBuilder(WithdrawAgent.class)
.chatModel(executorModel)
.tools(bankTool) // 绑定实际工具
.outputName("withdraw_result")
.build();
CreditAgent creditAgent = AgenticServices
.agentBuilder(CreditAgent.class)
.chatModel(executorModel)
.tools(bankTool)
.outputName("credit_result")
.build();
// 监督 Agent:自主决定调用顺序
SupervisorAgent bankSupervisor = AgenticServices.supervisorBuilder()
.chatModel(plannerModel)
.subAgents(exchangeAgent, withdrawAgent, creditAgent)
.responseStrategy(SupervisorResponseStrategy.SUMMARY) // 汇总所有结果
.maxIterations(5) // 防止无限调用
.build();
// 使用
String result = bankSupervisor.process(
"从 Mario 账户转 100 欧元给 Georgios"
);
五、防止无限循环:终止机制设计
Agent 的一大风险是陷入无限循环,必须设计完善的终止机制。
5.1 多层终止策略
java
public class SafeAgentWrapper<T> {
private final T agent;
private final int maxIterations;
private final long timeoutMillis;
private final double costBudget; // API 调用成本预算(美元)
// 迭代计数器
private final AtomicInteger iterationCount = new AtomicInteger(0);
private volatile long startTime;
private volatile double totalCost = 0.0;
/**
* 通过 ChatModelListener 监控迭代
*/
public ChatModelListener buildListener() {
return new ChatModelListener() {
@Override
public void onRequest(ChatModelRequestContext context) {
// 检查是否超过最大迭代次数
int count = iterationCount.incrementAndGet();
if (count > maxIterations) {
throw new MaxIterationsExceededException(
"已超过最大迭代次数 " + maxIterations +
",当前:" + count
);
}
// 检查超时
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed > timeoutMillis) {
throw new AgentTimeoutException(
"Agent 执行超时:" + elapsed + "ms"
);
}
}
@Override
public void onResponse(ChatModelResponseContext context) {
// 累计成本
TokenUsage usage = context.response().tokenUsage();
if (usage != null) {
double inputCost = usage.inputTokenCount() * 0.000005;
double outputCost = usage.outputTokenCount() * 0.000015;
totalCost += inputCost + outputCost;
if (totalCost > costBudget) {
throw new CostBudgetExceededException(
"已超过成本预算:$" + String.format("%.4f", totalCost)
);
}
}
}
};
}
public String execute(String task) {
iterationCount.set(0);
totalCost = 0.0;
startTime = System.currentTimeMillis();
try {
// 执行 agent
return ((Function<String, String>) agent).apply(task);
} catch (MaxIterationsExceededException e) {
return "任务执行已达到最大步骤数,当前进展:" + getProgress();
} catch (AgentTimeoutException e) {
return "任务执行超时,请简化您的需求";
} catch (CostBudgetExceededException e) {
return "成本超出预算,请稍后再试";
}
}
}
5.2 熔断模式(Circuit Breaker)
java
@Component
public class AgentCircuitBreaker {
private enum State { CLOSED, OPEN, HALF_OPEN }
private volatile State state = State.CLOSED;
private final AtomicInteger failureCount = new AtomicInteger(0);
private volatile long lastFailureTime = 0;
private final int failureThreshold = 5; // 失败 5 次后熔断
private final long cooldownPeriod = 60_000; // 熔断 60 秒
private final int halfOpenLimit = 3; // 半开状态允许 3 次试探
private final AtomicInteger halfOpenAttempts = new AtomicInteger(0);
public boolean allowRequest() {
switch (state) {
case CLOSED:
return true;
case OPEN:
// 检查是否可以进入半开状态
if (System.currentTimeMillis() - lastFailureTime > cooldownPeriod) {
state = State.HALF_OPEN;
halfOpenAttempts.set(0);
return true;
}
return false;
case HALF_OPEN:
return halfOpenAttempts.get() < halfOpenLimit;
default:
return false;
}
}
public void recordSuccess() {
failureCount.set(0);
if (state == State.HALF_OPEN) {
state = State.CLOSED;
}
}
public void recordFailure() {
lastFailureTime = System.currentTimeMillis();
int count = failureCount.incrementAndGet();
if (state == State.HALF_OPEN || count >= failureThreshold) {
state = State.OPEN;
System.err.println("[熔断] Agent 熔断器已打开,等待 " +
cooldownPeriod/1000 + " 秒后恢复");
}
}
/**
* 包装 Agent 调用
*/
public <T> T executeWithCircuitBreaker(Supplier<T> agentCall) {
if (!allowRequest()) {
throw new CircuitOpenException("Agent 服务熔断中,请稍后再试");
}
try {
T result = agentCall.get();
recordSuccess();
return result;
} catch (Exception e) {
recordFailure();
throw e;
}
}
}
5.3 工具调用防重
java
/**
* 防止 Agent 重复调用相同工具(幂等性保护)
*/
public class DeduplicatedToolWrapper {
private final Set<String> calledTools = new HashSet<>();
private final Map<String, String> toolResultCache = new HashMap<>();
public String wrapToolCall(
String toolName,
String arguments,
Supplier<String> toolCall) {
String callKey = toolName + ":" + arguments;
// 如果相同工具+参数已调用,直接返回缓存
if (toolResultCache.containsKey(callKey)) {
System.out.println("[防重] 工具 " + toolName +
" 已被调用,返回缓存结果");
return toolResultCache.get(callKey);
}
String result = toolCall.get();
toolResultCache.put(callKey, result);
calledTools.add(toolName);
return result;
}
public void reset() {
calledTools.clear();
toolResultCache.clear();
}
}
六、完整实战:企业报销审批 Agent
将前面所有技术整合,构建一个生产级报销审批 Agent:
java
// 1. 定义所有工具
@Component
public class ExpenseTools {
@Autowired private PolicyService policyService;
@Autowired private EmployeeService employeeService;
@Autowired private ProjectService projectService;
@Autowired private NotificationService notificationService;
@Tool("查询报销政策,输入费用类型返回对应政策")
public String getExpensePolicy(@P("费用类型,如:差旅、餐饮、培训") String expenseType) {
return policyService.getPolicy(expenseType);
}
@Tool("查询员工信息,包括级别、所在部门、审批权限")
public String getEmployeeInfo(@P("员工工号") String employeeId) {
return employeeService.getDetail(employeeId);
}
@Tool("查询项目预算余额")
public Map<String, Object> checkProjectBudget(@P("项目编号") String projectCode) {
Project project = projectService.findByCode(projectCode);
return Map.of(
"projectName", project.getName(),
"totalBudget", project.getTotalBudget(),
"usedBudget", project.getUsedBudget(),
"remainingBudget", project.getRemainingBudget(),
"canApprove", project.getRemainingBudget() > 0
);
}
@Tool("提交审批结果,记录审批意见并发送通知")
public String submitApprovalResult(
@P("报销申请ID") String requestId,
@P("审批结论:APPROVED/REJECTED/REVIEW_NEEDED") String decision,
@P("审批意见") String comments
) {
ApprovalResult result = new ApprovalResult(requestId, decision, comments);
approvalRepository.save(result);
notificationService.sendApprovalNotification(requestId, decision, comments);
return "审批已提交,决策:" + decision;
}
}
// 2. 定义审批 Agent 接口
@AiService
public interface ExpenseApprovalAgent {
@SystemMessage("""
你是一个专业的报销审批 AI,负责自动化处理报销申请。
审批流程:
1. 分析申请内容(识别费用类型、金额、申请人、项目)
2. 查询对应的报销政策
3. 核实申请人信息和权限
4. 检查项目预算余额
5. 根据以下规则判断:
- 金额 ≤ 500:自动批准
- 500 < 金额 ≤ 5000:核实预算后批准(预算充足则批准)
- 金额 > 5000:建议人工复核
- 违反政策:拒绝并说明原因
6. 提交审批结果(调用 submitApprovalResult 工具)
重要原则:
- 必须查询真实数据,不能凭感觉判断
- 审批意见要具体说明依据
- 预算不足时必须拒绝并说明
""")
String processRequest(
@MemoryId String requestId,
@UserMessage String request
);
}
// 3. 配置 Agent
@Bean
public ExpenseApprovalAgent expenseAgent(
ChatLanguageModel model,
ExpenseTools tools
) {
return AiServices.builder(ExpenseApprovalAgent.class)
.chatLanguageModel(model)
.tools(tools)
.chatMemory(MessageWindowChatMemory.withMaxMessages(20))
.build();
}
// 4. 测试
@SpringBootTest
class ExpenseApprovalAgentTest {
@Autowired ExpenseApprovalAgent agent;
@Test
void testStandardApproval() {
String result = agent.processRequest(
"REQ-2024-001",
"""
报销申请信息:
- 申请人:张三(工号:E10023)
- 费用类型:出差交通费
- 金额:2800 元
- 出差地点:北京-上海往返(高铁)
- 出差日期:2024-03-15 至 2024-03-16
- 所属项目:P-2024-08(AI 平台建设)
- 附件:火车票电子凭证 2 张
"""
);
System.out.println(result);
/*
Agent 执行过程(6 步 ReAct 迭代):
Iteration 1: 调用 getExpensePolicy("出差交通费")
→ 政策:高铁二等座、飞机经济舱,单次出差限 5000 元
Iteration 2: 调用 getEmployeeInfo("E10023")
→ 张三,研发工程师,三级,在职
Iteration 3: 调用 checkProjectBudget("P-2024-08")
→ 总预算 50万,已用 32万,余额 18万,可审批
Iteration 4: 综合判断
→ 金额 2800 元(范围:500-5000),政策合规,预算充足
Iteration 5: 调用 submitApprovalResult("REQ-2024-001", "APPROVED", "...")
→ 审批已提交
最终回复:报销申请已批准。金额 2800 元符合出差交通费政策,
项目 P-2024-08 预算余额充足(18万),审批通过。
*/
}
}
七、性能对比与选型建议
7.1 不同 Agent 模式性能对比
| 模式 | 平均响应时间 | 适合任务复杂度 | 可解释性 | 成本 |
|---|---|---|---|---|
| 单次调用(无工具) | 1-2s | 低 | 高 | 低 |
| 带工具的 AiServices | 3-8s | 中 | 中 | 中 |
| 顺序工作流 | 5-15s | 中高 | 高 | 中高 |
| 循环工作流(3次迭代) | 10-30s | 高 | 中 | 高 |
| 监督 Agent | 15-60s | 极高 | 低 | 极高 |
7.2 选型建议
如何选择 Agent 模式?
任务是否需要多步推理?
├── 否 → 普通 AiServices(最快、最便宜)
└── 是
│
任务步骤是否固定?
├── 是 → 顺序工作流(可预测、可追踪)
└── 否
│
是否需要迭代优化?
├── 是 → 循环工作流(Quality Loop)
└── 否
│
是否需要动态路由?
├── 是 → 条件工作流
└── 需要完全自主决策 → 监督 Agent(最强、最贵)
7.3 成本控制实践
java
// 通过模型分级降低成本
public class CostOptimizedAgentConfig {
// 简单任务用 mini 模型
@Bean("fastModel")
public ChatLanguageModel fastModel() {
return OpenAiChatModel.builder()
.modelName("gpt-4o-mini")
.maxTokens(500)
.build();
}
// 复杂推理用强模型
@Bean("powerModel")
public ChatLanguageModel powerModel() {
return OpenAiChatModel.builder()
.modelName("gpt-4o")
.maxTokens(2000)
.build();
}
@Bean
public SupervisorAgent supervisorAgent(
@Qualifier("powerModel") ChatLanguageModel plannerModel,
@Qualifier("fastModel") ChatLanguageModel executorModel,
List<UntypedAgent> subAgents
) {
// Planner 用强模型,各子 Agent 用快速模型
return AgenticServices.supervisorBuilder()
.chatModel(plannerModel) // 只有 Planner 用强模型
.subAgents(subAgents.stream()
.map(a -> a.withChatModel(executorModel)) // 子 Agent 用快速模型
.toArray(UntypedAgent[]::new))
.build();
}
}
八、常见问题(FAQ)
Q1: ReAct 循环次数太多怎么控制?
方法一 :通过 maxIterations 限制(推荐)
java
UntypedAgent agent = AgenticServices.loopBuilder()
.maxIterations(5) // 硬性上限
.build();
方法二:通过工具调用计数器
java
// 在工具执行中计数
private final AtomicInteger toolCallCount = new AtomicInteger(0);
@Tool("...")
public String myTool(String input) {
if (toolCallCount.incrementAndGet() > 10) {
throw new RuntimeException("工具调用次数超限,请简化任务");
}
return doWork(input);
}
Q2: Agent 的 System Prompt 如何优化?
关键技巧:
- 明确角色:告诉 Agent 它是谁、能做什么
- 定义流程:给出清晰的步骤,减少幻觉
- 设定边界:明确什么不能做
- 输出格式:规定输出的结构
java
@SystemMessage("""
# 角色:你是一个专业的 [领域] 分析师
# 能力:你可以使用以下工具
- 工具A:用于 [描述]
- 工具B:用于 [描述]
# 工作流程:
1. 步骤1
2. 步骤2
# 约束:
- 不做 [禁止事项]
- 必须先 [前置条件] 再 [操作]
# 输出格式:[具体格式]
""")
Q3: 如何调试 Agent 的推理过程?
java
// 使用 ChatModelListener 打印完整推理过程
@Bean
public ChatLanguageModel debugModel() {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o")
.listeners(List.of(new AgentDebugListener()))
.logRequests(true) // 打印完整请求
.logResponses(true) // 打印完整响应
.build();
}
九、总结
LangChain4j 1.4.0 的 Agentic 框架提供了从简单到复杂的完整 Agent 工具箱:
核心模式速查
| 模式 | 核心 API | 适用场景 |
|---|---|---|
| ReAct 基础 | AiServices + @Tool | 需要工具调用的单任务 |
| 顺序工作流 | sequenceBuilder() | 固定步骤流水线 |
| 循环工作流 | loopBuilder() | 需要迭代优化 |
| 条件工作流 | conditionalBuilder() | 动态路由分发 |
| 并行工作流 | parallelBuilder() | 多维度同时处理 |
| 监督 Agent | supervisorBuilder() | 完全自主决策 |
设计原则
- 最小化 Agent:能用简单工具解决的,不用 Agent
- 明确终止条件:每个 Agent 必须有明确的停止规则
- 监控与可观测:记录所有工具调用和推理过程
- 成本意识:根据任务复杂度选择合适的模型分级
- 安全边界:工具调用必须有权限校验和异常处理
如果这篇文章对你有帮助,欢迎点赞、收藏!Agent 相关问题欢迎评论区交流。