阿里开源AgentScope多智能体框架解析系列(五)第5章:ReActAgent 核心实现

78 阅读16分钟

本章导读

本章将深入剖析AgentScope的核心实现:ReActAgent。ReAct(Reasoning + Acting)是一种将推理和行动相结合的Agent执行模式,使Agent能够通过多轮推理-行动循环解决复杂任务。

  • 理解ReAct原理和推理-行动循环
  • 掌握ReActAgent的配置和使用
  • 深入了解推理阶段和行动阶段的实现细节
  • 学会迭代控制和终止条件设计
  • 掌握生产环境中的复杂任务处理

5.1 ReAct原理

5.1.1 什么是ReAct

ReAct是"Reasoning + Acting"的缩写,是一种让大语言模型(LLM)能够交替进行推理和行动的方法。

传统LLM的局限

用户: 请告诉我北京今天的天气
传统LLM: 抱歉,我无法获取实时天气信息。(只能基于训练数据回答)

用户: 计算 123456 * 789012
传统LLM: 大约是97000000000...(可能计算错误,因为LLM不擅长精确计算)

ReAct模式的优势

用户: 请告诉我北京今天的天气
ReAct Agent:
  [推理] 用户想知道北京的实时天气,我需要调用天气API
  [行动] 调用 get_weather(city="北京")
  [结果] 北京今天晴天,温度15-25°C
  [推理] 已获取天气信息,可以回答用户了
  [回复] 北京今天晴天,温度在15-25°C之间,适合户外活动。

用户: 计算 123456 * 789012
ReAct Agent:
  [推理] 这是一个数学计算,我应该使用计算器工具
  [行动] 调用 calculator(expression="123456 * 789012")
  [结果] 97408265472
  [推理] 计算完成,可以回答用户了
  [回复] 123456 * 789012 = 97408265472

5.1.2 ReAct的执行流程

完整的ReAct循环示例:

用户输入: "帮我分析一下苹果公司的最新财报"

━━━ 第1轮循环 ━━━

[推理阶段]
Agent思考: "用户想要分析苹果公司的财报,我需要先获取最新的财报数据"
决策: 调用工具 get_financial_report

[行动阶段]
执行工具: get_financial_report(company="Apple", period="latest")
工具返回: "2024 Q3财报:营收850亿美元,同比增长8%,净利润230亿美元..."

━━━ 第2轮循环 ━━━

[推理阶段]
Agent思考: "已经获取了财报数据,现在需要分析关键指标"
决策: 调用工具 analyze_metrics

[行动阶段]
执行工具: analyze_metrics(data="营收850亿...", metrics=["revenue", "profit", "growth"])
工具返回: "营收同比增长8%,利润率27%,增长稳健..."

━━━ 第3轮循环 ━━━

[推理阶段]
Agent思考: "已完成数据收集和分析,现在可以生成报告了"
决策: 不需要工具,直接生成回复

[最终回复]
"根据苹果公司2024年Q3财报分析:
1. 营收达到850亿美元,同比增长8%
2. 净利润230亿美元,利润率27%
3. 各项指标表现稳健,增长势头良好
..."

5.1.3 ReAct vs 传统Chain-of-Thought

Chain-of-Thought (CoT):
用户: 计算复杂问题
Agent: 让我一步步思考...
       第一步: ...
       第二步: ...
       第三步: ...
       结论: ...
       
优点: 推理过程清晰
缺点: 无法调用外部工具,只能基于知识推理

━━━━━━━━━━━━━━━━━━

ReAct:
用户: 计算复杂问题
Agent: [思考] 我需要查询数据
       [工具] 调用API获取数据
       [结果] 获得数据
       [思考] 我需要计算
       [工具] 调用计算器
       [结果] 得到答案
       [回复] 最终答案

优点: 可以调用工具,处理实际问题
缺点: 需要更多轮次,Token消耗较大

5.2 ReActAgent的配置

5.2.1 基础配置

import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.tool.Toolkit;

public class BasicReActAgentExample {
    
    public static void main(String[] args) {
        // 步骤1:创建Model
        DashScopeChatModel model = DashScopeChatModel.builder()
            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
            .modelName("qwen-plus")
            .build();
        
        // 步骤2:创建Toolkit
        Toolkit toolkit = new Toolkit();
        toolkit.registerObject(new MyTools());  // 注册工具类
        
        // 步骤3:创建ReActAgent
        ReActAgent agent = ReActAgent.builder()
            // ===== 基本信息 =====
            .name("MyAssistant")
            .description("一个智能助手")
            
            // ===== 核心组件 =====
            .model(model)           // LLM模型
            .toolkit(toolkit)        // 工具箱
            .memory(new InMemoryMemory())  // 记忆系统
            
            // ===== 系统提示词 =====
            .sysPrompt(
                "你是一个有帮助的AI助手。\n" +
                "你可以调用提供的工具来解决用户的问题。\n" +
                "如果需要工具,请先思考需要哪个工具,然后调用它。\n" +
                "如果不需要工具,直接回答用户的问题。"
            )
            
            // ===== 执行控制 =====
            .maxIters(10)           // 最多执行10轮推理-行动循环
            .checkRunning(true)      // 启用重入检查
            
            .build();
        
        // 步骤4:使用Agent
        Msg response = agent.call("北京今天的天气怎么样?").block();
        System.out.println(response.getTextContent());
    }
}

5.2.2 高级配置

import io.agentscope.core.hook.Hook;
import io.agentscope.core.plan.LongTermMemory;
import io.agentscope.core.plan.LongTermMemoryMode;
import io.agentscope.core.tool.ExecutionConfig;

import java.time.Duration;

public class AdvancedReActAgentExample {
    
    public static ReActAgent createAdvancedAgent(String apiKey) {
        // 创建高级配置的Agent
        return ReActAgent.builder()
            .name("AdvancedAssistant")
            
            // ===== Model配置 =====
            .model(DashScopeChatModel.builder()
                .apiKey(apiKey)
                .modelName("qwen-max")  // 使用更强大的模型
                .generateOptions(GenerateOptions.builder()
                    .temperature(0.7)      // 控制随机性
                    .topP(0.9)
                    .maxTokens(4000)       // 最大生成Token数
                    .build())
                .build())
            
            // ===== 工具执行配置 =====
            .toolkit(toolkit)
            .toolExecutionConfig(ExecutionConfig.builder()
                .timeout(Duration.ofMinutes(2))  // 工具执行超时
                .maxAttempts(1)                  // 工具不重试
                .build())
            
            // ===== Model调用配置 =====
            .modelExecutionConfig(ExecutionConfig.builder()
                .timeout(Duration.ofMinutes(1))  // Model调用超时
                .maxAttempts(3)                  // 失败重试3次
                .build())
            
            // ===== 记忆系统 =====
            .memory(new InMemoryMemory())
            
            // ===== 长期记忆(可选)=====
            .withLongTermMemory(
                createLongTermMemory(),          // 长期记忆实现
                LongTermMemoryMode.HYBRID        // 混合模式:同时检索和存储
            )
            
            // ===== 任务规划(可选)=====
            .withPlanNotebook()  // 启用PlanNotebook
            
            // ===== Hook系统 =====
            .hooks(List.of(
                createLoggingHook(),     // 日志Hook
                createMonitoringHook(),  // 监控Hook
                createRAGHook()          // RAG增强Hook
            ))
            
            // ===== 执行控制 =====
            .maxIters(15)        // 允许更多轮次
            .checkRunning(true)   // 启用重入检查
            
            // ===== 系统提示词 =====
            .sysPrompt(buildAdvancedSystemPrompt())
            
            .build();
    }
    
    private static String buildAdvancedSystemPrompt() {
        return """
            你是一个高级AI助手,具备以下能力:
            
            1. 工具使用
               - 优先使用工具获取准确信息
               - 合理选择工具,避免不必要的调用
               - 如果工具调用失败,尝试其他方法或告知用户
            
            2. 任务规划
               - 对于复杂任务,先制定计划
               - 使用PlanNotebook管理任务进度
               - 定期总结进展
            
            3. 记忆管理
               - 记住用户的偏好和历史对话
               - 利用长期记忆提供个性化服务
            
            4. 质量控制
               - 确保回复准确、简洁
               - 如果不确定,明确告知用户
               - 保护用户隐私
            """;
    }
}

5.2.3 配置参数详解

/**
 * ReActAgent配置参数说明
 */
public class ReActAgentConfigGuide {
    
    /**
     * maxIters(最大迭代次数)
     * 
     * 用途: 限制推理-行动循环的最大轮次
     * 默认值: 10
     * 推荐值:
     *   - 简单对话: 5-8
     *   - 一般任务: 10-15
     *   - 复杂任务: 15-20
     * 
     * 注意: 过大会导致Token消耗过多和响应延迟
     */
    int maxIters = 10;
    
    /**
     * checkRunning(重入检查)
     * 
     * 用途: 防止Agent被递归调用
     * 默认值: false
     * 推荐值: true(生产环境)
     * 
     * 场景:
     *   - 在工具中调用同一个Agent → 会报错
     *   - 多线程并发调用同一个Agent → 会报错
     */
    boolean checkRunning = true;
    
    /**
     * sysPrompt(系统提示词)
     * 
     * 用途: 定义Agent的角色和行为规范
     * 
     * 最佳实践:
     *   1. 清晰定义Agent的角色
     *   2. 说明可用的工具和使用场景
     *   3. 设定行为准则(例如:不要猜测,使用工具)
     *   4. 控制在200-500字以内
     */
    String sysPrompt = "...";
    
    /**
     * toolExecutionConfig(工具执行配置)
     * 
     * timeout: 工具执行超时时间
     *   - 快速工具(数据库查询): 5-10秒
     *   - 慢速工具(文件处理): 30秒-2分钟
     *   - 外部API: 30秒
     * 
     * maxAttempts: 工具失败重试次数
     *   - 通常设置为1(不重试)
     *   - 对于不稳定的外部API,可设置为2-3
     */
    ExecutionConfig toolExecutionConfig = ExecutionConfig.builder()
        .timeout(Duration.ofSeconds(30))
        .maxAttempts(1)
        .build();
    
    /**
     * modelExecutionConfig(Model调用配置)
     * 
     * timeout: Model调用超时时间
     *   - 短文本生成: 30秒
     *   - 长文本生成: 1-2分钟
     * 
     * maxAttempts: 失败重试次数
     *   - 推荐2-3次(处理网络抖动)
     */
    ExecutionConfig modelExecutionConfig = ExecutionConfig.builder()
        .timeout(Duration.ofMinutes(1))
        .maxAttempts(3)
        .build();
}

5.3 推理-行动循环的实现细节

5.3.1 推理阶段(Reasoning)

推理阶段是Agent思考"应该做什么"的过程。

推理阶段要做什么?

输入:
├─ 用户消息
├─ 历史对话(从Memory获取)
├─ 可用工具列表(ToolSchema)
└─ 系统提示词

处理流程:
1. 准备消息列表
   ├─ 系统消息(sysPrompt)
   ├─ 历史消息(Memory)
   └─ 当前用户消息

2. Hook增强
   ├─ RAG Hook: 检索相关知识
   ├─ LongTermMemory Hook: 检索长期记忆
   └─ 其他自定义Hook

3. 调用LLM
   ├─ 发送消息和ToolSchema给Model
   ├─ Model生成回复(可能包含工具调用)
   └─ 流式接收响应

4. 解析响应
   ├─ 提取文本内容
   ├─ 提取ToolUseBlock(如果有)
   └─ 记录到Memory

输出:
├─ Agent的回复消息(Msg)
├─ 可能包含ToolUseBlock
└─ Token使用统计

代码流程(简化版)

/**
 * 推理阶段的核心逻辑
 */
private Mono<Msg> reasoningPhase(GenerateOptions options) {
    return Mono.defer(() -> {
        // 1. 触发PreReasoningEvent Hook
        PreReasoningEvent preEvent = new PreReasoningEvent(
            this.name, 
            this.currentIterCount,
            System.currentTimeMillis()
        );
        
        return notifyHooks(preEvent)
            .flatMap(event -> {
                // 2. 准备消息列表
                List<Msg> messages = prepareMessagesForModel();
                
                // 3. 准备工具Schema
                List<ToolSchema> toolSchemas = toolkit.getToolSchemas();
                
                // 4. 调用Model(流式)
                return model.stream(messages, toolSchemas, options)
                    // 5. 累积响应块
                    .reduce(
                        ContentAccumulator.empty(), 
                        ContentAccumulator::accumulate
                    )
                    // 6. 构建最终消息
                    .flatMap(accumulator -> {
                        Msg response = accumulator.toMsg(MsgRole.ASSISTANT);
                        
                        // 7. 添加到Memory
                        memory.add(response);
                        
                        // 8. 触发PostReasoningEvent Hook
                        PostReasoningEvent postEvent = new PostReasoningEvent(
                            this.name,
                            response,
                            accumulator.getUsage(),
                            System.currentTimeMillis() - preEvent.getTimestamp()
                        );
                        
                        return notifyHooks(postEvent)
                            .thenReturn(response);
                    });
            });
    });
}

/**
 * 准备发送给Model的消息
 */
private List<Msg> prepareMessagesForModel() {
    List<Msg> messages = new ArrayList<>();
    
    // 1. 添加系统消息
    if (sysPrompt != null && !sysPrompt.isEmpty()) {
        messages.add(Msg.builder()
            .role(MsgRole.SYSTEM)
            .textContent(sysPrompt)
            .build());
    }
    
    // 2. 添加历史消息(从Memory获取)
    List<Msg> history = memory.getMemory();
    messages.addAll(history);
    
    return messages;
}

5.3.2 行动阶段(Acting)

行动阶段是Agent执行工具的过程。

行动阶段要做什么?

输入:
└─ 推理阶段的响应(可能包含ToolUseBlock)

处理流程:
1. 检查是否需要执行工具
   ├─ 如果没有ToolUseBlock → 跳过,直接返回
   └─ 如果有ToolUseBlock → 继续

2. 提取工具调用
   ├─ 工具名称
   ├─ 工具参数(JSON格式)
   └─ 调用ID

3. 执行工具
   ├─ 触发PreActingEvent Hook
   ├─ 查找工具实现
   ├─ 参数验证和转换
   ├─ 反射调用方法
   └─ 捕获返回值或异常

4. 生成工具结果
   ├─ ToolResultBlock(成功)
   └─ ToolResultBlock(失败,包含错误信息)

5. 添加到Memory
   ├─ 包含ToolUseBlock的消息
   └─ 包含ToolResultBlock的消息

6. 触发PostActingEvent Hook

输出:
└─ 工具执行结果(ToolResultBlock)

代码流程(简化版)

/**
 * 行动阶段的核心逻辑
 */
private Mono<Void> actingPhase(Msg reasoningResponse) {
    return Mono.defer(() -> {
        // 1. 提取所有ToolUseBlock
        List<ToolUseBlock> toolCalls = reasoningResponse.getContent().stream()
            .filter(block -> block instanceof ToolUseBlock)
            .map(block -> (ToolUseBlock) block)
            .toList();
        
        // 2. 如果没有工具调用,直接返回
        if (toolCalls.isEmpty()) {
            return Mono.empty();
        }
        
        // 3. 执行每个工具调用
        return Flux.fromIterable(toolCalls)
            .flatMap(toolCall -> executeToolCall(toolCall))
            .collectList()
            .flatMap(toolResults -> {
                // 4. 创建包含工具结果的消息
                Msg toolResultMsg = Msg.builder()
                    .role(MsgRole.TOOL)
                    .content(toolResults.toArray(new ContentBlock[0]))
                    .build();
                
                // 5. 添加到Memory
                memory.add(toolResultMsg);
                
                return Mono.empty();
            });
    });
}

/**
 * 执行单个工具调用
 */
private Mono<ToolResultBlock> executeToolCall(ToolUseBlock toolCall) {
    return Mono.defer(() -> {
        // 1. 触发PreActingEvent Hook
        PreActingEvent preEvent = new PreActingEvent(
            this.name,
            toolCall.getName(),
            toolCall.getArguments(),
            System.currentTimeMillis()
        );
        
        return notifyHooks(preEvent)
            .flatMap(event -> {
                long startTime = System.currentTimeMillis();
                
                try {
                    // 2. 查找工具
                    AgentTool tool = toolkit.getTool(toolCall.getName());
                    if (tool == null) {
                        return Mono.just(createErrorResult(
                            toolCall, 
                            "Tool not found: " + toolCall.getName()
                        ));
                    }
                    
                    // 3. 执行工具
                    Object result = tool.invoke(toolCall.getArguments());
                    
                    // 4. 转换为字符串
                    String resultText = result instanceof String 
                        ? (String) result 
                        : result.toString();
                    
                    // 5. 创建成功结果
                    ToolResultBlock resultBlock = ToolResultBlock.builder()
                        .id(toolCall.getId())
                        .name(toolCall.getName())
                        .content(resultText)
                        .isError(false)
                        .build();
                    
                    // 6. 触发PostActingEvent Hook
                    PostActingEvent postEvent = new PostActingEvent(
                        this.name,
                        toolCall.getName(),
                        resultText,
                        false,
                        System.currentTimeMillis() - startTime
                    );
                    
                    return notifyHooks(postEvent)
                        .thenReturn(resultBlock);
                        
                } catch (Exception e) {
                    // 7. 处理异常
                    ToolResultBlock errorBlock = createErrorResult(
                        toolCall, 
                        "Tool execution failed: " + e.getMessage()
                    );
                    
                    PostActingEvent postEvent = new PostActingEvent(
                        this.name,
                        toolCall.getName(),
                        e.getMessage(),
                        true,
                        System.currentTimeMillis() - startTime
                    );
                    
                    return notifyHooks(postEvent)
                        .thenReturn(errorBlock);
                }
            });
    });
}

5.3.3 迭代控制与终止条件

ReActAgent的核心是推理-行动循环。如何控制循环次数和终止条件非常关键。

循环控制逻辑:

while (iterCount < maxIters) {
    // 1. 推理阶段
    Msg response = reasoning();
    
    // 2. 检查终止条件
    if (shouldStop(response)) {
        return response;  // 完成
    }
    
    // 3. 行动阶段
    acting(response);
    
    // 4. 增加计数
    iterCount++;
}

// 达到最大迭代次数
return finalResponse;

终止条件

/**
 * 判断是否应该停止循环
 */
private boolean shouldStop(Msg response) {
    // 条件1:没有工具调用(表示Agent生成了最终答案)
    boolean hasToolCall = response.getContent().stream()
        .anyMatch(block -> block instanceof ToolUseBlock);
    
    if (!hasToolCall) {
        return true;  // 停止
    }
    
    // 条件2:Agent明确表示完成
    // (某些Model会在文本中说"已完成")
    String text = response.getTextContent().toLowerCase();
    if (text.contains("已完成") || text.contains("任务完成") || 
        text.contains("finished") || text.contains("done")) {
        return true;
    }
    
    // 条件3:工具调用失败且无法继续
    // (这个逻辑可以在Hook中实现)
    
    return false;  // 继续循环
}

完整的循环实现

/**
 * ReActAgent的核心循环(简化版)
 */
private Mono<Msg> executeReActLoop(List<Msg> inputMsgs) {
    return Mono.defer(() -> {
        // 1. 初始化
        int iterCount = 0;
        Msg lastResponse = null;
        
        // 2. 添加输入消息到Memory
        inputMsgs.forEach(msg -> memory.add(msg));
        
        // 3. 循环执行
        while (iterCount < maxIters) {
            try {
                // (1) 推理阶段
                Msg reasoningResult = reasoningPhase(null).block();
                lastResponse = reasoningResult;
                
                // (2) 检查终止条件
                if (shouldStop(reasoningResult)) {
                    return Mono.just(reasoningResult);  // 完成
                }
                
                // (3) 行动阶段
                actingPhase(reasoningResult).block();
                
                // (4) 增加计数
                iterCount++;
                
            } catch (Exception e) {
                // 处理异常
                return Mono.error(new RuntimeException(
                    "ReAct loop failed at iteration " + iterCount, e));
            }
        }
        
        // 4. 达到最大迭代次数
        if (lastResponse != null) {
            // 添加提示信息
            String warning = "\n\n[注意: 已达到最大迭代次数 " + maxIters + "]";
            Msg finalMsg = Msg.builder()
                .role(lastResponse.getRole())
                .textContent(lastResponse.getTextContent() + warning)
                .build();
            return Mono.just(finalMsg);
        }
        
        return Mono.error(new RuntimeException(
            "Max iterations reached without completion"));
    });
}

5.4 生产场景:复杂任务处理

5.4.1 场景:数据分析Agent

import io.agentscope.core.ReActAgent;
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import io.agentscope.core.tool.Toolkit;

import java.util.List;

/**
 * 数据分析Agent
 * 
 * 功能:
 * 1. 查询数据库
 * 2. 统计分析
 * 3. 生成可视化图表
 * 4. 输出分析报告
 */
public class DataAnalysisAgentExample {
    
    // ============ 工具类 ============
    
    public static class DataAnalysisTools {
        
        @Tool(name = "query_database",
              description = "Query data from database with SQL")
        public String queryDatabase(
            @ToolParam(name = "sql", description = "SQL query statement") 
                String sql,
            @ToolParam(name = "limit", description = "Maximum number of rows to return") 
                Integer limit) {
            
            // 实际实现:连接数据库执行查询
            try {
                List<Map<String, Object>> results = executeSQL(sql, limit);
                return formatQueryResults(results);
            } catch (Exception e) {
                return "Error executing query: " + e.getMessage();
            }
        }
        
        @Tool(name = "calculate_statistics",
              description = "Calculate statistical metrics (mean, median, std, etc.)")
        public String calculateStatistics(
            @ToolParam(name = "data", description = "Comma-separated numbers") 
                String data,
            @ToolParam(name = "metrics", description = "Metrics to calculate: mean,median,std,min,max") 
                String metrics) {
            
            try {
                double[] numbers = parseNumbers(data);
                List<String> requestedMetrics = Arrays.asList(metrics.split(","));
                
                StringBuilder result = new StringBuilder("Statistics:\n");
                
                if (requestedMetrics.contains("mean")) {
                    result.append("Mean: ").append(calculateMean(numbers)).append("\n");
                }
                if (requestedMetrics.contains("median")) {
                    result.append("Median: ").append(calculateMedian(numbers)).append("\n");
                }
                if (requestedMetrics.contains("std")) {
                    result.append("Std Dev: ").append(calculateStd(numbers)).append("\n");
                }
                if (requestedMetrics.contains("min")) {
                    result.append("Min: ").append(Arrays.stream(numbers).min().orElse(0)).append("\n");
                }
                if (requestedMetrics.contains("max")) {
                    result.append("Max: ").append(Arrays.stream(numbers).max().orElse(0)).append("\n");
                }
                
                return result.toString();
            } catch (Exception e) {
                return "Error calculating statistics: " + e.getMessage();
            }
        }
        
        @Tool(name = "generate_chart",
              description = "Generate a chart visualization")
        public String generateChart(
            @ToolParam(name = "data", description = "Data points in JSON format") 
                String data,
            @ToolParam(name = "chart_type", description = "Chart type: bar, line, pie, scatter") 
                String chartType,
            @ToolParam(name = "title", description = "Chart title") 
                String title) {
            
            try {
                // 实际实现:生成图表并保存为文件
                String chartPath = generateChartImage(data, chartType, title);
                return "Chart generated successfully at: " + chartPath;
            } catch (Exception e) {
                return "Error generating chart: " + e.getMessage();
            }
        }
        
        @Tool(name = "create_report",
              description = "Create a formatted analysis report")
        public String createReport(
            @ToolParam(name = "title", description = "Report title") 
                String title,
            @ToolParam(name = "sections", description = "Report sections in JSON format") 
                String sections) {
            
            try {
                // 实际实现:生成PDF或HTML报告
                String reportPath = generateReport(title, sections);
                return "Report created successfully at: " + reportPath;
            } catch (Exception e) {
                return "Error creating report: " + e.getMessage();
            }
        }
        
        // 辅助方法...
        private List<Map<String, Object>> executeSQL(String sql, Integer limit) {
            // 数据库查询实现
            return null;
        }
        
        private double[] parseNumbers(String data) {
            return Arrays.stream(data.split(","))
                .mapToDouble(Double::parseDouble)
                .toArray();
        }
        
        private double calculateMean(double[] numbers) {
            return Arrays.stream(numbers).average().orElse(0);
        }
        
        private double calculateMedian(double[] numbers) {
            double[] sorted = Arrays.copyOf(numbers, numbers.length);
            Arrays.sort(sorted);
            int mid = sorted.length / 2;
            return sorted.length % 2 == 0 
                ? (sorted[mid - 1] + sorted[mid]) / 2 
                : sorted[mid];
        }
        
        private double calculateStd(double[] numbers) {
            double mean = calculateMean(numbers);
            double variance = Arrays.stream(numbers)
                .map(x -> Math.pow(x - mean, 2))
                .average()
                .orElse(0);
            return Math.sqrt(variance);
        }
    }
    
    // ============ 创建Agent ============
    
    public static ReActAgent createDataAnalysisAgent(String apiKey) {
        // 创建Toolkit
        Toolkit toolkit = new Toolkit();
        toolkit.registerObject(new DataAnalysisTools());
        
        // 创建Agent
        return ReActAgent.builder()
            .name("DataAnalyst")
            .description("专业的数据分析助手")
            .model(DashScopeChatModel.builder()
                .apiKey(apiKey)
                .modelName("qwen-max")  // 使用更强大的模型
                .generateOptions(GenerateOptions.builder()
                    .temperature(0.3)  // 降低随机性,提高准确性
                    .build())
                .build())
            .toolkit(toolkit)
            .memory(new InMemoryMemory())
            .maxIters(12)  // 允许更多轮次
            .sysPrompt("""
                你是一个专业的数据分析师。
                
                工作流程:
                1. 理解用户的分析需求
                2. 使用query_database查询所需数据
                3. 使用calculate_statistics计算统计指标
                4. 使用generate_chart生成可视化图表
                5. 使用create_report生成分析报告
                
                注意事项:
                - 查询数据时要设置合理的limit避免数据过多
                - 统计分析要选择合适的指标
                - 图表类型要符合数据特点
                - 报告要包含关键发现和建议
                """)
            .build();
    }
    
    // ============ 使用示例 ============
    
    public static void main(String[] args) {
        ReActAgent analyst = createDataAnalysisAgent(
            System.getenv("DASHSCOPE_API_KEY"));
        
        // 场景:分析销售数据
        String task = """
            请分析2024年Q1的销售数据:
            1. 查询所有销售记录
            2. 计算总销售额、平均订单金额、销售趋势
            3. 生成月度销售柱状图
            4. 输出分析报告,包含关键发现和建议
            """;
        
        Msg response = analyst.call(task).block();
        
        System.out.println("=== 分析结果 ===");
        System.out.println(response.getTextContent());
        
        // 查看Token使用
        if (response.getChatUsage() != null) {
            System.out.println("\n=== 资源使用 ===");
            System.out.println("Total Tokens: " + response.getChatUsage().getTotalTokens());
            System.out.println("Estimated Cost: $" + 
                calculateCost(response.getChatUsage()));
        }
    }
    
    private static double calculateCost(ChatUsage usage) {
        // qwen-max的定价(示例)
        double inputCostPerK = 0.04;   // 每1K input tokens
        double outputCostPerK = 0.12;  // 每1K output tokens
        
        return (usage.getPromptTokens() / 1000.0 * inputCostPerK) +
               (usage.getCompletionTokens() / 1000.0 * outputCostPerK);
    }
}

执行过程示例

用户: 请分析2024年Q1的销售数据...

━━━ 第1轮 ━━━
[推理] 需要先查询销售数据
[工具] query_database(sql="SELECT * FROM sales WHERE date >= '2024-01-01' AND date < '2024-04-01'", limit=1000)
[结果] 返回856条销售记录

━━━ 第2轮 ━━━
[推理] 需要计算统计指标
[工具] calculate_statistics(data="销售额数据...", metrics="mean,median,std,min,max")
[结果] Mean: ¥1250, Median: ¥980, Std: ¥450...

━━━ 第3轮 ━━━
[推理] 需要生成可视化图表
[工具] generate_chart(data="月度数据...", chart_type="bar", title="Q1月度销售趋势")
[结果] Chart generated at: /path/to/chart.png

━━━ 第4轮 ━━━
[推理] 可以生成最终报告了
[工具] create_report(title="2024 Q1销售分析报告", sections="...")
[结果] Report created at: /path/to/report.pdf

━━━ 第5轮 ━━━
[推理] 所有工具已执行完成,现在可以总结了
[回复] 
2024年Q1销售数据分析已完成:

关键发现:
1. 总销售额:¥1,070,000
2. 订单总数:856单
3. 平均订单金额:¥1,250
4. 销售趋势:1月最高(¥420,000),2月略有下降,3月回升

建议:
1. 加强2月的营销活动
2. 重点关注高价值客户(订单金额>¥2000)
3. 优化库存管理,减少缺货情况

详细报告和图表已生成:
- 图表: /path/to/chart.png
- 报告: /path/to/report.pdf

5.5 性能优化和最佳实践

5.5.1 Token使用优化

/**
 * 优化Token使用的策略
 */
public class TokenOptimizationExample {
    
    public static ReActAgent createOptimizedAgent(String apiKey) {
        return ReActAgent.builder()
            .name("OptimizedAgent")
            .model(DashScopeChatModel.builder()
                .apiKey(apiKey)
                .modelName("qwen-plus")
                .generateOptions(GenerateOptions.builder()
                    // 策略1:限制最大Token数
                    .maxTokens(1500)  // 避免生成过长的回复
                    
                    // 策略2:降低temperature
                    .temperature(0.5)  // 减少随机性,提高确定性
                    
                    build())
                .build())
            
            // 策略3:限制最大迭代次数
            .maxIters(8)  // 避免过多的推理循环
            
            // 策略4:使用简洁的系统提示词
            .sysPrompt(
                "你是AI助手。使用工具获取信息,回复要简洁。"  // 100字以内
            )
            
            // 策略5:限制Memory大小
            .memory(new InMemoryMemory(20))  // 只保留最近20条消息
            
            .build();
    }
}

5.5.2 错误处理和重试

/**
 * 错误处理最佳实践
 */
public class ErrorHandlingExample {
    
    public static void robustAgentExecution() {
        ReActAgent agent = ReActAgent.builder()
            .name("RobustAgent")
            .model(model)
            .toolkit(toolkit)
            
            // 配置超时和重试
            .modelExecutionConfig(ExecutionConfig.builder()
                .timeout(Duration.ofSeconds(30))
                .maxAttempts(3)  // Model调用失败重试3次
                .build())
            
            .toolExecutionConfig(ExecutionConfig.builder()
                .timeout(Duration.ofSeconds(20))
                .maxAttempts(1)  // 工具不重试(避免副作用)
                .build())
            
            .build();
        
        // 使用时添加错误处理
        agent.call("用户请求")
            .timeout(Duration.ofMinutes(2))  // 整体超时
            .retry(2)  // 整个调用重试2次
            .doOnError(error -> {
                // 记录错误日志
                log.error("Agent execution failed", error);
                // 发送告警
                sendAlert("Agent failed: " + error.getMessage());
            })
            .onErrorResume(error -> {
                // 返回降级响应
                return Mono.just(Msg.builder()
                    .textContent("抱歉,系统暂时无法处理您的请求。")
                    .build());
            })
            .subscribe();
    }
}

5.5.3 并发控制

/**
 * 多用户并发场景
 */
public class ConcurrencyExample {
    
    private final Map<String, ReActAgent> userAgents = new ConcurrentHashMap<>();
    
    /**
     * 为每个用户创建独立的Agent实例
     */
    public ReActAgent getOrCreateAgent(String userId) {
        return userAgents.computeIfAbsent(userId, id -> 
            ReActAgent.builder()
                .name("Agent-" + id)
                .model(model)
                .memory(new InMemoryMemory())  // 独立的Memory
                .checkRunning(true)  // 防止重入
                .build()
        );
    }
    
    /**
     * 处理并发请求
     */
    public Flux<Msg> handleConcurrentRequests(List<UserRequest> requests) {
        return Flux.fromIterable(requests)
            // 并发执行,最多10个并发
            .flatMap(request -> {
                ReActAgent agent = getOrCreateAgent(request.getUserId());
                return agent.call(request.getMessage());
            }, 10)
            .onErrorContinue((error, item) -> {
                // 单个请求失败不影响其他请求
                log.error("Request failed: " + item, error);
            });
    }
}

5.6 本章总结

关键要点

  1. ReAct原理

    • 推理(Reasoning)+ 行动(Acting)
    • 交替执行,多轮循环
    • 支持工具调用,解决实际问题
  2. 推理阶段

    • 准备消息(系统提示+历史+工具Schema)
    • 调用LLM生成回复
    • 解析响应(文本+工具调用)
  3. 行动阶段

    • 提取ToolUseBlock
    • 执行工具
    • 生成ToolResultBlock
    • 添加到Memory
  4. 循环控制

    • maxIters限制最大轮次
    • shouldStop()判断终止条件
    • 达到最大轮次强制结束
  5. 性能优化

    • 限制Token使用
    • 控制迭代次数
    • 合理设置超时
    • 错误处理和重试

最佳实践

✓ 合理设置maxIters(通常5-15)
✓ 简洁的系统提示词(200-500字)
✓ 限制Memory大小(10-50条消息)
✓ 为每个用户创建独立Agent
✓ 启用checkRunning防止重入
✓ 配置超时和重试策略
✓ 监控Token使用和成本
✗ 不要在工具中调用同一个Agent
✗ 不要忽略错误处理
✗ 不要使用过长的系统提示词

下一章预告

第6章将深入讲解工具系统,探讨@Tool注解、工具注册、工具调用流程、参数验证等内容,并提供生产级的工具开发案例。