核心概念层——深入理解 Agent 是什么

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 + 工具调用)

相关推荐
woniu_maggie2 小时前
SAP Web Service日志监控:如何用SRT_UTIL快速定位接口问题
后端
唐骁虎3 小时前
Claude Code 全景架构指南——三大核心支柱及四大关键扩展组件
ai·架构·ai编程·claude code
一线大码3 小时前
Java 使用国密算法实现数据加密传输
java·spring boot·后端
Rust语言中文社区3 小时前
【Rust日报】用 Rust 重写的 Turso 是一个更好的 SQLite 吗?
开发语言·数据库·后端·rust·sqlite
南宫乘风4 小时前
Claude Code国内优化完全指南:Win零成本打造丝滑AI编程体验
ai编程
在屏幕前出油4 小时前
06. FastAPI——中间件
后端·python·中间件·pycharm·fastapi
wuqingshun3141595 小时前
说一下spring的bean的作用域
java·后端·spring
钟智强5 小时前
从2.7GB到481MB:我的Docker Compose优化实战,以及为什么不能全信AI
后端·docker
华科易迅6 小时前
Spring JDBC
java·后端·spring