1 Agent vs ChatBot:从根本上理解区别
1.1 一个直观的例子
假设你对 AI 说:"帮我分析一下我们公司上周的销售数据,找出表现最好的产品,并给团队发一封总结邮件"。
ChatBot 的反应:
1ChatBot: "要分析销售数据,你可以按以下步骤操作: 21. 打开数据库,执行 SQL 查询获取上周的销售记录 32. 使用 Excel 或 Python 进行数据汇总 43. 找出销售额最高的产品 54. 撰写邮件总结发送给团队 6 7你需要我帮你写 SQL 查询语句吗?" 8 9→ ChatBot 只能"说",不能"做"。它给你建议,但执行全靠你自己。 10
Agent 的反应:
1Agent 思考: 用户想要三件事: ①分析销售数据 ②找最佳产品 ③发邮件 2 3Step 1: 调用 query_database 工具 4 → 执行 SQL 查询上周销售数据 5 → 结果: 获得 500 条销售记录 6 7Step 2: 调用 data_analysis 工具 8 → 分析数据,按产品分组统计 9 → 结果: 产品 A 销售额最高($125,000),增长 23% 10 11Step 3: 调用 send_email 工具 12 → 撰写邮件: "团队好,上周销售总结如下..." 13 → 发送给团队成员列表 14 15Agent: "已完成分析并发送邮件!上周表现最好的产品是 A, 16 销售额 $125,000,环比增长 23%。详细总结已发送到团队邮箱。" 17 18→ Agent 不仅"说",还能"做"。它自主规划、执行、汇报。 19
1.2 本质区别的三个维度
1维度一: 自主性 (Autonomy) 2──────────────────────────────────────────→ 3ChatBot Agent 4被动响应 主动行动 5一问一答 自主循环 6用户驱动每一步 用户只给目标 7 8 9维度二: 能力边界 (Capability) 10──────────────────────────────────────────→ 11ChatBot Agent 12只能生成文本 能执行动作 13无法访问外部系统 连接各种工具和API 14知识受限于训练数据 可以实时获取信息 15 16 17维度三: 状态管理 (State) 18──────────────────────────────────────────→ 19ChatBot Agent 20无状态/简单上下文 完整的状态管理 21不记住跨会话信息 长期记忆 22没有目标追踪 目标导向,跟踪进度 23
1.3 Agent 的定义
综合来说,Agent = LLM + 自主循环 + 工具调用 + 记忆
1// 伪代码展示 Agent 的核心循环 2public class Agent { 3 private final LLM llm; // 大脑 4 private final List<Tool> tools; // 工具箱 5 private final Memory memory; // 记忆 6 7 public Agent(LLM llm, List<Tool> tools, Memory memory) { 8 this.llm = llm; 9 this.tools = tools; 10 this.memory = memory; 11 } 12 13 public String run(String userGoal) { 14 // Agent 的核心运行循环 15 16 // 把用户目标加入工作上下文 17 var context = memory.getRelevant(userGoal); 18 19 while (true) { // ← 关键: 自主循环,不是一问一答 20 21 // 1. 思考: 分析当前状态,决定下一步 22 var thought = llm.think(userGoal, context, tools); 23 24 // 2. 判断: 目标完成了吗? 25 if (thought.isGoalComplete()) { 26 return thought.finalAnswer(); 27 } 28 29 // 3. 行动: 调用工具执行 30 var toolResult = executeTool( 31 thought.chosenTool(), 32 thought.toolParams() 33 ); 34 35 // 4. 观察: 把工具结果加入上下文 36 context.add(toolResult); 37 38 // 5. 记忆: 存储有用的信息 39 memory.save(thought, toolResult); 40 41 // 循环继续,直到目标完成或超过最大步数 42 } 43 } 44} 45
2 Agent 四大组成部分——深入理解
2.1 大脑(LLM):Agent 的决策中心
LLM 在 Agent 中扮演"指挥官"的角色:
1LLM 在 Agent 中的职责: 2 3┌─────────────────────────────────────────────┐ 4│ LLM (大脑) │ 5│ │ 6│ ① 理解意图: 用户到底想要什么? │ 7│ "帮我查下订单" → 意图=查询, 实体=订单 │ 8│ │ 9│ ② 制定计划: 需要几步?先做什么? │ 10│ 步骤1: 查订单 → 步骤2: 分析状态 → 步骤3: 回答 │ 11│ │ 12│ ③ 选择工具: 这一步该用什么工具? │ 13│ "查订单" → 使用 query_orders 工具 │ 14│ │ 15│ ④ 生成参数: 工具需要什么输入? │ 16│ query_orders(order_id="12345") │ 17│ │ 18│ ⑤ 分析结果: 工具返回了什么?达到目标了吗? │ 19│ 结果显示订单已发货 → 可以回答用户了 │ 20│ │ 21│ ⑥ 生成回答: 组织语言回复用户 │ 22│ "您的订单 12345 已发货,预计明天送达" │ 23└─────────────────────────────────────────────┘ 24
选择合适的 LLM 作为大脑:
1不同任务对"大脑"的要求: 2 3简单任务(意图分类、格式转换): 4 → 小模型即可 (Haiku / GPT-4o-mini) 5 → 快速、便宜 6 7中等任务(文档问答、代码修改): 8 → 中等模型 (Sonnet / GPT-4o) 9 → 平衡速度和质量 10 11复杂任务(多步推理、架构设计): 12 → 强力模型 (Opus / GPT-4o with reasoning) 13 → 准确性最重要 14 15实际做法: 一个 Agent 内部可以用多个模型! 16 - 路由层: 小模型(判断用户意图) 17 - 执行层: 中等模型(执行具体任务) 18 - 难题: 大模型(处理复杂推理) 19
2.2 规划(Planning):Agent 的策略中心
规划能力是 Agent 区别于 ChatBot 的关键能力。
任务分解(Task Decomposition)
1用户: "帮我搭建一个博客网站" 2 3Agent 的任务分解: 4├── 1. 技术选型 5│ ├── 1.1 确定前端框架 (React/Vue/Next.js) 6│ ├── 1.2 确定后端方案 (Node.js/Python) 7│ └── 1.3 确定数据库 (PostgreSQL/MongoDB) 8├── 2. 项目初始化 9│ ├── 2.1 创建项目目录结构 10│ ├── 2.2 初始化包管理 11│ └── 2.3 配置开发环境 12├── 3. 核心功能开发 13│ ├── 3.1 文章 CRUD API 14│ ├── 3.2 用户认证 15│ └── 3.3 前端页面 16├── 4. 测试与部署 17│ ├── 4.1 单元测试 18│ ├── 4.2 Docker 容器化 19│ └── 4.3 部署上线 20
计划调整(Re-planning)
好的 Agent 会根据执行结果动态调整计划:
1原计划: 使用 MySQL 作为数据库 2 3执行步骤 2.1 时发现: 服务器只有 512MB 内存 4 5Agent 反思: "MySQL 对内存要求较高,512MB 可能不够。 6 应该改用 SQLite 或使用云数据库。" 7 8调整后的计划: 改用 SQLite(轻量级,适合小型博客) 9 10→ 这就是 Re-planning,Agent 能根据实际情况灵活调整 11
2.3 记忆(Memory):Agent 的信息中心
三种记忆的详细解释
1┌───────────────────────────────────────────────────┐ 2│ Agent 记忆系统 │ 3│ │ 4│ ┌────────────────────────────────────────────┐ │ 5│ │ 短期记忆 (Short-term Memory) │ │ 6│ │ │ │ 7│ │ 本质: LLM 的上下文窗口 │ │ 8│ │ 内容: 当前对话的 messages 列表 │ │ 9│ │ 容量: 受上下文窗口限制 (如 128K tokens) │ │ 10│ │ 寿命: 当前对话结束就没了 │ │ 11│ │ │ │ 12│ │ 例: │ │ 13│ │ messages = [ │ │ 14│ │ {system: "你是助手"}, │ │ 15│ │ {user: "你好"}, │ │ 16│ │ {assistant: "你好!"}, │ │ 17│ │ {user: "帮我查天气"}, ← 短期记忆 │ │ 18│ │ {assistant: "北京今天晴"}, │ │ 19│ │ ... │ │ 20│ │ ] │ │ 21│ └────────────────────────────────────────────┘ │ 22│ │ 23│ ┌────────────────────────────────────────────┐ │ 24│ │ 长期记忆 (Long-term Memory) │ │ 25│ │ │ │ 26│ │ 本质: 外部持久化存储 │ │ 27│ │ 内容: 跨对话的知识和用户信息 │ │ 28│ │ 容量: 理论上无限 │ │ 29│ │ 寿命: 永久(除非主动删除) │ │ 30│ │ │ │ 31│ │ 存储方式: │ │ 32│ │ ├── 向量数据库: 把文档变成数字向量存储, │ │ 33│ │ │ 能做"语义搜索"(详见模块五 5.2) │ │ 34│ │ ├── 知识图谱: 用"实体→关系→实体"的网络 │ │ 35│ │ │ 存储知识(详见模块五 5.3) │ │ 36│ │ ├── Key-Value 存储: 用户偏好等简单键值对 │ │ 37│ │ └── 关系数据库: 结构化历史数据 │ │ 38│ │ │ │ 39│ │ 例: │ │ 40│ │ - 用户偏好: "张三喜欢 Python,用 VS Code" │ │ 41│ │ - 历史知识: "订单系统用的是 Spring Boot" │ │ 42│ │ - 对话摘要: "上次讨论了 Redis 缓存方案" │ │ 43│ └────────────────────────────────────────────┘ │ 44│ │ 45│ ┌────────────────────────────────────────────┐ │ 46│ │ 工作记忆 (Working Memory) │ │ 47│ │ │ │ 48│ │ 本质: 当前任务的中间状态 │ │ 49│ │ 内容: 已执行的步骤、中间结果、当前计划 │ │ 50│ │ 容量: 较小,只保留关键信息 │ │ 51│ │ 寿命: 当前任务结束就清除 │ │ 52│ │ │ │ 53│ │ 例 (Agent 正在做数据分析): │ │ 54│ │ working_memory = { │ │ 55│ │ "current_plan": ["①查数据", "②分析", ...],│ │ 56│ │ "completed_steps": ["①查数据 ✓"], │ │ 57│ │ "intermediate_results": { │ │ 58│ │ "raw_data": "500条销售记录", │ │ 59│ │ "top_product": "产品A" │ │ 60│ │ } │ │ 61│ │ } │ │ 62│ └────────────────────────────────────────────┘ │ 63└───────────────────────────────────────────────────┘ 64
上下文窗口管理策略
当对话太长,超出上下文窗口时怎么办?
1策略一: 滑动窗口 2 保留最近 N 轮对话,删除最早的 3 [对话1, 对话2, 对话3, 对话4, 对话5] 4 → 删除对话1 5 [对话2, 对话3, 对话4, 对话5, 对话6] 6 7 优点: 简单 8 缺点: 可能丢失重要的早期信息 9 10策略二: 摘要压缩 11 用 LLM 把旧对话压缩成摘要 12 [对话1-10 的摘要, 对话11, 对话12, ...] 13 14 优点: 保留了关键信息 15 缺点: 摘要本身消耗 token,可能丢失细节 16 17策略三: RAG 检索 18 把历史对话存入向量库,需要时检索 19 问到相关话题 → 从向量库搜索相关历史 → 注入上下文 20 (RAG 是什么?见模块一 1.1.4;完整实现见模块五 5.1) 21 22 优点: 不丢失信息,按需检索 23 缺点: 实现复杂,检索可能不准 24 25策略四: 混合方案(推荐) 26 最近 5 轮 → 完整保留(短期记忆) 27 更早的 → 压缩为摘要 28 关键事实 → 存入长期记忆(向量库/图谱) 29
2.4 工具(Tools):Agent 的执行中心
工具的本质
1工具 = 一个函数 + 描述信息 2 3┌─────────────────────────────────┐ 4│ 工具定义 │ 5│ │ 6│ 名称: search_web │ 7│ 描述: "在互联网上搜索信息" │ ← LLM 靠这个决定是否使用 8│ 参数: │ 9│ - query (string, 必填) │ ← LLM 生成这个参数值 10│ - max_results (int, 可选) │ 11│ 返回: 搜索结果列表 │ 12│ │ 13│ 实际执行: │ 14│ String searchWeb(String query) │ ← 你写的代码 15│ // 调用搜索 API │ 16│ return results; │ 17└─────────────────────────────────┘ 18
常见工具类型
1Agent 工具箱: 2│ 3├── 信息获取类 4│ ├── 网络搜索 (Google/Bing API) 5│ ├── 文档检索 (RAG 向量搜索) 6│ ├── 数据库查询 (SQL 执行) 7│ └── API 调用 (天气/股票/新闻) 8│ 9├── 执行操作类 10│ ├── 代码执行 (Java/Python/Shell) 11│ ├── 文件操作 (读/写/修改) 12│ ├── 发送消息 (邮件/Slack/钉钉) 13│ └── 系统操作 (创建任务/更新状态) 14│ 15├── 计算分析类 16│ ├── 数学计算 (计算器) 17│ ├── 数据分析 (Java Stream/Apache Commons Math) 18│ └── 图表生成 (JFreeChart/ECharts) 19│ 20└── 交互类 21 ├── 浏览器操作 (Browser Use) 22 ├── 屏幕操作 (Computer Use) 23 └── 人工确认 (Human-in-the-loop) 24
工具设计的好与坏
1// ❌ 差的工具设计 2var badTool = """ 3{ 4 "name": "do_stuff", 5 "description": "执行各种操作", 6 "parameters": { 7 "type": "object", 8 "properties": { 9 "action": {"type": "string"}, 10 "target": {"type": "string"}, 11 "option1": {"type": "string"}, 12 "option2": {"type": "string"}, 13 "option3": {"type": "string"}, 14 "option4": {"type": "string"} 15 } 16 } 17} 18"""; 19// 问题: 名字太模糊、描述不清楚、参数含义不明、参数太多 20 21// ✅ 好的工具设计 22var goodTool = """ 23{ 24 "name": "search_user_orders", 25 "description": "根据用户ID查询该用户的订单列表。返回订单号、状态、金额、时间。", 26 "parameters": { 27 "type": "object", 28 "properties": { 29 "user_id": { 30 "type": "string", 31 "description": "用户的唯一标识符,如 'U12345'" 32 }, 33 "status": { 34 "type": "string", 35 "enum": ["pending", "shipped", "delivered", "cancelled"], 36 "description": "按订单状态筛选(可选)" 37 }, 38 "limit": { 39 "type": "integer", 40 "description": "返回结果数量上限,默认10", 41 "default": 10 42 } 43 }, 44 "required": ["user_id"] 45 } 46} 47"""; 48// 优点: 名称精确、描述详细、参数含义清楚、只有必要的才 required 49
3 推理策略——Agent 如何思考
3.1 ReAct 模式(深入理解)
ReAct(Reasoning + Acting)是最经典、最常用的 Agent 推理模式。
完整的 ReAct 循环:
1用户: "帮我了解竞品公司 XYZ 最近的融资情况和主要产品" 2 3===== 第 1 轮 ===== 4Thought: 用户想了解 XYZ 公司的融资和产品信息。 5 我需要先搜索该公司的基本信息。 6Action: search_web(query="XYZ公司 融资") 7Observation: "XYZ 公司于 2024 年 3 月完成 B 轮融资 5000 万美元, 8 由红杉资本领投..." 9 10===== 第 2 轮 ===== 11Thought: 已获得融资信息。现在需要搜索他们的产品信息。 12Action: search_web(query="XYZ公司 主要产品") 13Observation: "XYZ 公司旗下有三款产品: ProductA(协作工具)、 14 ProductB(项目管理)、ProductC(文档工具)..." 15 16===== 第 3 轮 ===== 17Thought: 我已经收集到了融资和产品两方面的信息, 18 可以综合整理回答用户了。 19Action: [无需调用工具] 20Answer: "XYZ 公司融资情况: 21 - 2024年3月完成B轮融资5000万美元 22 - 领投方: 红杉资本 23 24 主要产品: 25 - ProductA: 协作工具 26 - ProductB: 项目管理 27 - ProductC: 文档工具" 28
ReAct 的 Prompt 模板:
1var REACT_PROMPT = """ 2你是一个助手,可以使用工具来回答问题。 3 4可用工具: 5%s 6 7请使用以下格式: 8 9Thought: 分析当前情况,决定下一步 10Action: 工具名称 11Action Input: 工具参数(JSON格式) 12Observation: [工具返回的结果会自动填入这里] 13 14... (可以重复 Thought/Action/Observation 多次) 15 16当你认为已经有足够的信息来回答时: 17Thought: 我已经有了足够的信息 18Final Answer: 最终回答 19 20开始! 21 22用户问题: %s 23""".formatted(toolsDescription, userQuestion); 24
Java 实现一个简单的 ReAct Agent:
1import com.fasterxml.jackson.databind.ObjectMapper; 2import com.fasterxml.jackson.databind.node.ObjectNode; 3import java.util.*; 4 5// 使用模块 1 中的 ClaudeClient(已定义 chat / chatWithTools / stream 方法) 6 7// 定义工具 8var toolsMap = Map.<String, java.util.function.Function<Map<String, String>, String>>of( 9 "search_web", args -> "搜索结果: 关于'" + args.get("query") + "'的信息...", 10 "calculator", args -> { 11 // 注意: 生产环境中应使用安全的表达式引擎 12 try { 13 var engine = new javax.script.ScriptEngineManager() 14 .getEngineByName("JavaScript"); 15 return String.valueOf(engine.eval(args.get("expression"))); 16 } catch (Exception e) { 17 return "计算错误: " + e.getMessage(); 18 } 19 } 20); 21 22var toolsSchema = """ 23[ 24 { 25 "name": "search_web", 26 "description": "搜索互联网获取信息", 27 "input_schema": { 28 "type": "object", 29 "properties": { 30 "query": {"type": "string", "description": "搜索关键词"} 31 }, 32 "required": ["query"] 33 } 34 }, 35 { 36 "name": "calculator", 37 "description": "计算数学表达式", 38 "input_schema": { 39 "type": "object", 40 "properties": { 41 "expression": {"type": "string", "description": "数学表达式"} 42 }, 43 "required": ["expression"] 44 } 45 } 46] 47"""; 48 49/** 一个简单的 ReAct Agent */ 50String runAgent(String userMessage, int maxSteps) { 51 var objectMapper = new ObjectMapper(); 52 var tools = objectMapper.readTree(toolsSchema); 53 54 var messages = new ArrayList<Message>(); 55 messages.add(new Message("user", userMessage)); 56 57 var system = "你是一个有用的助手,可以使用工具来回答问题。"; 58 59 for (int step = 0; step < maxSteps; step++) { 60 // 1. 调用 LLM(使用 ClaudeClient 的 chatWithTools) 61 var response = client.chatWithTools(system, messages, tools); 62 var stopReason = response.get("stop_reason").asText(); 63 var content = response.get("content"); 64 65 // 2. 检查是否需要调用工具 66 if ("tool_use".equals(stopReason)) { 67 // 将 assistant 回复加入消息列表 68 messages.add(new Message("assistant", content.toString())); 69 70 var toolResults = new ArrayList<Map<String, Object>>(); 71 for (var block : content) { 72 if ("tool_use".equals(block.get("type").asText())) { 73 var funcName = block.get("name").asText(); 74 var funcArgs = objectMapper.convertValue( 75 block.get("input"), 76 new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() {} 77 ); 78 var toolId = block.get("id").asText(); 79 80 System.out.printf(" [Step %d] 调用工具: %s(%s)%n", step + 1, funcName, funcArgs); 81 82 // 3. 执行工具 83 var result = toolsMap.get(funcName).apply(funcArgs); 84 System.out.printf(" [Step %d] 工具结果: %s%n", step + 1, 85 result.substring(0, Math.min(100, result.length()))); 86 87 // 4. 把工具结果传回 LLM 88 toolResults.add(Map.of( 89 "type", "tool_result", 90 "tool_use_id", toolId, 91 "content", result 92 )); 93 } 94 } 95 messages.add(new Message("user", objectMapper.writeValueAsString(toolResults))); 96 } else { 97 // LLM 没有调用工具,说明它准备直接回答 98 for (var block : content) { 99 if ("text".equals(block.get("type").asText())) { 100 return block.get("text").asText(); 101 } 102 } 103 } 104 } 105 return "Agent 达到最大步数限制"; 106} 107 108// 使用 109var answer = runAgent("北京和上海的面积分别是多少?哪个更大?", 5); 110System.out.println("\n最终回答: " + answer); 111
3.2 CoT(思维链)的深入应用
Zero-shot CoT
1// 只需要在 prompt 末尾加一句话就能大幅提升推理准确率 2 3// ❌ 直接问(准确率低) 4var directPrompt = "一个书架有 3 层,每层放 8 本书。小明拿走了 5 本,又放回 2 本。现在有多少本书?"; 5 6// ✅ 加上 CoT 引导(准确率大幅提升) 7var cotPrompt = """ 8一个书架有 3 层,每层放 8 本书。小明拿走了 5 本,又放回 2 本。现在有多少本书? 9请一步步思考。"""; 10 11// LLM 的回答: 12// 1. 书架有 3 层,每层 8 本,总共 3 × 8 = 24 本 13// 2. 小明拿走 5 本: 24 - 5 = 19 本 14// 3. 又放回 2 本: 19 + 2 = 21 本 15// 答: 现在有 21 本书 16
CoT 在 Agent 决策中的应用
1var AGENT_SYSTEM_PROMPT = """ 2你是一个智能助手。在选择工具前,请先进行推理分析。 3 4推理格式: 5<thinking> 61. 用户的核心需求是什么? 72. 需要哪些信息才能满足需求? 83. 哪个工具最适合获取这些信息? 94. 需要什么参数? 10</thinking> 11 12然后调用合适的工具。"""; 13 14// 这样 Agent 的工具选择准确率会显著提高 15// 因为它在"行动"之前先"想清楚"了 16
3.3 Plan-and-Execute 策略详解
1Plan-and-Execute 的核心思想: 2把"规划"和"执行"分成两个独立阶段 3 4┌─────────────────────────────────────────┐ 5│ Planner(规划者) │ 6│ │ 7│ 输入: 用户目标 │ 8│ 输出: 完整的步骤计划 │ 9│ │ 10│ "帮我写一个 Python 爬虫" │ 11│ → Plan: │ 12│ 1. 确定目标网站和数据 │ 13│ 2. 分析网页结构 │ 14│ 3. 编写爬虫代码 │ 15│ 4. 测试运行 │ 16│ 5. 处理异常和边界情况 │ 17└──────────────────┬──────────────────────┘ 18 │ 19 ▼ 20┌─────────────────────────────────────────┐ 21│ Executor(执行者) │ 22│ │ 23│ 逐步执行计划中的每一步 │ 24│ │ 25│ Step 1: search_web("Python 爬虫 教程") │ 26│ Step 2: analyze_html(url) │ 27│ Step 3: write_code(spec) │ 28│ Step 4: execute_code(code) │ 29│ Step 5: fix_errors(errors) │ 30│ │ 31│ 如果某步失败 → 通知 Planner 重新规划 │ 32└─────────────────────────────────────────┘ 33
Plan-and-Execute vs ReAct 的选择:
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 简单查询 | ReAct | 一两步就能完成,不需要提前规划 |
| 明确的多步任务 | Plan-and-Execute | 步骤可预见,先规划效率更高 |
| 探索性任务 | ReAct | 不确定需要什么信息,边做边看 |
| 复杂项目 | Plan-and-Execute + ReAct | 大计划用 P&E,每步执行用 ReAct |
3.4 Reflection(反思)策略详解
1Reflection 的核心: Agent 执行后评估自己的输出,发现问题后改进 2 3┌──────────┐ ┌──────────┐ ┌──────────┐ 4│ Generator│ ──→ │ Evaluator│ ──→ │ Reflector│ 5│ (执行) │ │ (评估) │ │ (反思) │ 6│ │ │ │ │ │ 7│ 生成代码 │ │ 跑测试 │ │ 分析错误 │ 8│ 写回答 │ │ 检查质量 │ │ 找出原因 │ 9│ │ │ 打分 │ │ 提出改进 │ 10└──────────┘ └──────────┘ └─────┬────┘ 11 ↑ │ 12 └──────────────────────────────────┘ 13 改进后重试 14
代码生成中的 Reflection 示例:
1/** 带反思能力的代码生成 Agent */ 2String codeAgentWithReflection(String task, int maxRetries) { 3 String code = null; 4 String reflection = null; 5 6 for (int attempt = 0; attempt < maxRetries; attempt++) { 7 // 1. 生成代码 8 if (attempt == 0) { 9 code = generateCode(task); 10 } else { 11 code = generateCode(task, code, reflection); 12 } 13 14 // 2. 执行代码并检查结果 15 var result = executeCode(code); 16 17 if (result.success()) { 18 return code; // 成功了,直接返回 19 } 20 21 // 3. 反思: 分析失败原因 22 reflection = client.chat( 23 "你是一个代码审查专家。", 24 List.of(new Message("user", """ 25 任务: %s 26 生成的代码: %s 27 错误信息: %s 28 29 请分析: 30 1. 代码为什么出错? 31 2. 具体哪一行有问题? 32 3. 应该如何修复? 33 """.formatted(task, code, result.error()))) 34 ); 35 36 System.out.printf("第 %d 次尝试失败,反思: %s%n", attempt + 1, reflection); 37 } 38 39 return null; // 多次尝试都失败 40} 41
4 Agent 循环的完整生命周期
把所有概念串起来,一个 Agent 处理请求的完整过程:
1用户发起请求 2│ 3▼ 4┌──────────────────────────────────────────────────┐ 5│ Phase 1: 理解与规划 │ 6│ │ 7│ ① 检索长期记忆(用户历史、偏好) │ 8│ ② LLM 理解用户意图 │ 9│ ③ 判断是否需要规划(简单任务直接执行) │ 10│ ④ 如需规划 → 分解为步骤列表 │ 11└───────────────────────┬──────────────────────────┘ 12 │ 13 ▼ 14┌──────────────────────────────────────────────────┐ 15│ Phase 2: 执行循环(ReAct Loop) │ 16│ │ 17│ for each step in plan: │ 18│ ① Thought: 当前步骤需要什么?用什么工具? │ 19│ ② Action: 调用选定的工具 │ 20│ ③ Observation: 获取工具执行结果 │ 21│ ④ 更新工作记忆(记录中间结果) │ 22│ ⑤ 判断: 是否需要调整计划? │ 23│ - 如果结果不符预期 → Reflection → Re-plan │ 24│ - 如果正常 → 继续下一步 │ 25└───────────────────────┬──────────────────────────┘ 26 │ 27 ▼ 28┌──────────────────────────────────────────────────┐ 29│ Phase 3: 总结与输出 │ 30│ │ 31│ ① 综合所有中间结果 │ 32│ ② 生成最终回答 │ 33│ ③ 更新长期记忆(保存有价值的信息) │ 34│ ④ 返回结果给用户 │ 35└──────────────────────────────────────────────────┘ 36
5 实战练习
练习 1: 手动模拟 ReAct 循环
1/** 2 * 目标: 不用任何框架,纯用 API 实现一个 ReAct Agent 3 * 功能: 能搜索天气和做数学计算 4 */ 5 6import com.fasterxml.jackson.databind.ObjectMapper; 7import com.fasterxml.jackson.databind.JsonNode; 8import java.util.*; 9import java.util.function.Function; 10 11// 使用模块 1 中的 ClaudeClient 和 Message 12 13public class ReactAgentDemo { 14 15 private static final ObjectMapper objectMapper = new ObjectMapper(); 16 17 // 模拟工具(实际中你会调用真正的 API) 18 static String getWeather(Map<String, String> args) { 19 var city = args.get("city"); 20 var weatherData = Map.of( 21 "北京", "晴,25°C,湿度30%", 22 "上海", "多云,28°C,湿度65%", 23 "广州", "雷阵雨,32°C,湿度80%" 24 ); 25 return weatherData.getOrDefault(city, "未找到 " + city + " 的天气数据"); 26 } 27 28 static String calculate(Map<String, String> args) { 29 try { 30 // 注意: 生产环境中不要用 ScriptEngine,这里仅为演示 31 var engine = new javax.script.ScriptEngineManager() 32 .getEngineByName("JavaScript"); 33 return String.valueOf(engine.eval(args.get("expression"))); 34 } catch (Exception e) { 35 return "计算错误: " + e.getMessage(); 36 } 37 } 38 39 // 工具注册 40 static final Map<String, Function<Map<String, String>, String>> availableTools = Map.of( 41 "get_weather", ReactAgentDemo::getWeather, 42 "calculate", ReactAgentDemo::calculate 43 ); 44 45 static final String toolsForApi = """ 46 [ 47 { 48 "name": "get_weather", 49 "description": "获取指定城市的实时天气信息", 50 "input_schema": { 51 "type": "object", 52 "properties": { 53 "city": {"type": "string", "description": "城市名称"} 54 }, 55 "required": ["city"] 56 } 57 }, 58 { 59 "name": "calculate", 60 "description": "计算数学表达式,如 '2+3*4' 或 '100/7'", 61 "input_schema": { 62 "type": "object", 63 "properties": { 64 "expression": {"type": "string", "description": "数学表达式"} 65 }, 66 "required": ["expression"] 67 } 68 } 69 ] 70 """; 71 72 /** ReAct Agent 实现 */ 73 static String reactAgent(ClaudeClient client, String question, int maxSteps) 74 throws Exception { 75 var tools = objectMapper.readTree(toolsForApi); 76 var messages = new ArrayList<Message>(); 77 messages.add(new Message("user", question)); 78 79 var system = "你是一个有用的助手。请使用提供的工具来回答用户问题。"; 80 81 System.out.println("\n[思考] 问题: " + question + "\n"); 82 83 for (int step = 0; step < maxSteps; step++) { 84 var response = client.chatWithTools(system, messages, tools); 85 var stopReason = response.get("stop_reason").asText(); 86 var content = response.get("content"); 87 88 // 如果 LLM 决定直接回答(不调用工具) 89 if (!"tool_use".equals(stopReason)) { 90 for (var block : content) { 91 if ("text".equals(block.get("type").asText())) { 92 var answer = block.get("text").asText(); 93 System.out.println("[完成] 最终回答: " + answer); 94 return answer; 95 } 96 } 97 } 98 99 // 处理工具调用 100 messages.add(new Message("assistant", content.toString())); 101 102 var toolResults = new ArrayList<Map<String, Object>>(); 103 for (var block : content) { 104 if ("tool_use".equals(block.get("type").asText())) { 105 var funcName = block.get("name").asText(); 106 var funcArgs = objectMapper.convertValue( 107 block.get("input"), 108 new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() {} 109 ); 110 var toolId = block.get("id").asText(); 111 112 System.out.printf(" [工具] Step %d: %s(%s)%n", step + 1, funcName, funcArgs); 113 114 // 执行工具 115 var result = availableTools.get(funcName).apply(funcArgs); 116 System.out.printf(" [结果] %s%n", result); 117 118 // 工具结果传回 LLM 119 toolResults.add(Map.of( 120 "type", "tool_result", 121 "tool_use_id", toolId, 122 "content", result 123 )); 124 } 125 } 126 messages.add(new Message("user", objectMapper.writeValueAsString(toolResults))); 127 } 128 129 return "达到最大步数限制"; 130 } 131 132 // 测试 133 public static void main(String[] args) throws Exception { 134 var client = new ClaudeClient(); 135 reactAgent(client, "北京和上海今天哪个城市更热?温差是多少度?", 10); 136 } 137} 138
本模块学习检查清单
- 能清晰解释 Agent 和 ChatBot 的 3 个核心区别
- 能画出 Agent 的四大组成部分架构图
- 理解 ReAct、CoT、Plan-and-Execute、Reflection 四种推理策略的区别和适用场景
- 理解三种记忆(短期/长期/工作记忆)的区别
- 理解工具调用的完整流程
- 能手写一个简单的 ReAct Agent(使用 API + 工具调用)
《核心概念层——深入理解 Agent 是什么》 是转载文章,点击查看原文。
