LangChain4j Agent 模式:ReAct、Plan-and-Solve 与自主决策

LangChain4j Agent 模式:ReAct、Plan-and-Solve 与自主决策

从"问答机器"到"自主智能体"------LangChain4j 1.4.0 的 Agentic 框架,让 AI 真正具备推理、规划、行动的完整能力。


前言:当单轮对话不够用

2024 年初,我接手了一个需求:"用 AI 实现自动化报销审批"。

最初的想法很简单:用户提交报销申请 → AI 审批 → 给出结论。但需求细化后,发现这个流程需要:

  1. 解析申请:理解用户意图和申请金额
  2. 查询规则:从系统中获取对应的报销政策
  3. 核查信息:验证员工级别、项目预算余额
  4. 条件判断:金额超过 5000 需要部门主管确认
  5. 生成结果:输出审批意见和通知内容

单轮对话根本做不到------它没有工具调用、没有多步推理、没有状态管理。

这时候,我们需要的是 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 如何优化?

关键技巧:

  1. 明确角色:告诉 Agent 它是谁、能做什么
  2. 定义流程:给出清晰的步骤,减少幻觉
  3. 设定边界:明确什么不能做
  4. 输出格式:规定输出的结构
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() 完全自主决策

设计原则

  1. 最小化 Agent:能用简单工具解决的,不用 Agent
  2. 明确终止条件:每个 Agent 必须有明确的停止规则
  3. 监控与可观测:记录所有工具调用和推理过程
  4. 成本意识:根据任务复杂度选择合适的模型分级
  5. 安全边界:工具调用必须有权限校验和异常处理

如果这篇文章对你有帮助,欢迎点赞、收藏!Agent 相关问题欢迎评论区交流。

相关推荐
跨境海王哥2 小时前
ChatGPT降智怎么恢复?GPT-5.4降智原因与恢复方法
人工智能·chatgpt
码农三叔2 小时前
(10-5-01)大模型时代的人形机器人感知:基于RoboBrain大模型的人形机器人通用智能感知系统(1)构建模型
人工智能·算法·机器人·人形机器人
scott1985122 小时前
扩散模型之(十三)条件生成 Conditioned Generation
人工智能·算法·生成式
balmtv2 小时前
GPT-5.4 vs Gemini 3.1 Pro:推理与效率的终极对决
人工智能·gpt
EriccoShaanxi2 小时前
石英加速度计破局石油钻井,如何征服极端温度?
人工智能·机器人·无人机
weixin_6682 小时前
2026年AI Agent四大技术突破解析
人工智能·经验分享
weixin_509138342 小时前
迈向 AGI/ASI 的度量
人工智能·agi·智能体
AI视觉网奇2 小时前
vscode 激活环境失败
人工智能
hjs_deeplearning2 小时前
文献阅读篇#18:端到端自动驾驶:从经典范式到大模型赋能——综合综述
人工智能·机器学习·自动驾驶