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