Agent 的本质:ReAct 循环与 MCP 协议

问题场景:LLM 能回答问题,但不会主动查天气、调 API、执行多步任务——你需要 Agent。很多人以为 Agent 就是"更聪明的 ChatGPT",但 Agent 的本质是一个不到 50 行的 while 循环:思考→行动→观察→再思考。这篇文章把循环拆开给你看,再对比 Function Calling 和 MCP 两种工具调用协议的优劣。
30秒速览:ReAct 循环 = Thought(分析当前状态,决定下一步做啥)→ Action(调用工具执行)→ Observation(看工具返回的结果,决定下一步)→ 循环直到任务完成。核心代码不到 50 行,却能支撑从单工具查天气到多 Agent 协作的所有场景。Function Calling vs MCP——FC 是"告诉模型我能做什么"(OpenAI 定义的 API 协议,传 tools 数组→模型返回 tool_calls→你执行),MCP 是"告诉模型去哪找能做什么的东西"(Anthropic 定义的标准化协议,Server 独立进程,JSON-RPC 通信,支持工具发现→调用→结果返回完整链路)。选型口诀:单工具+单模型→FC 够用;多工具+多模型切换→MCP 是标准答案。
跟其他 Agent 文章不同:这篇不讲哲学不讲概念——直接从 ReAct 循环的 50 行 while 代码讲起,跑通后对比 FC 和 MCP 的架构差异,最后给出从单 Agent 到多 Agent 的演进路径。每一步都有可运行的代码。

系列第 3 篇(共 14 篇)。


⚠️ 时效性提示:本文基于 2026 年 7 月的技术状态撰写。大模型版本迭代迅速(通常 3-6 个月一次大版本更新),文中涉及的模型名称、API 端点及性能基准数据请以各厂商最新公告为准。建议重点关注文章中的架构原理与设计决策——这些内容具有更长的时效性。

一、Agent 的本质

三种 AI 能力的递进:

纯 LLM RAG Agent
能力 仅靠训练数据回答 LLM + 检索外部知识 LLM + 调用工具 + 多步推理
能做什么 回答问题 基于文档回答 执行任务(查数据库、调 API、操作文件)
例子 “什么是 MVCC” “根据知识库文档回答锁升级过程” “查表找出交易量最大的客户,生成 Excel 报表”

Agent 的三个核心能力:

  • 规划:把复杂任务拆成可执行的步骤
  • 工具调用:自主决定何时调用什么工具
  • 反馈循环:观察工具结果 → 调整下一步计划 → 继续执行

二、ReAct 模式深入

ReAct(Reasoning + Acting)是当前最主流的 Agent 推理范式:

循环结构(Thought → Action → Observation → 回到 Thought):

用户:"出 5 道 Java 多线程的单选题,难度中等"

→ Thought: 需要先从知识库检索多线程相关内容
→ Action: call rag_search(query="Java 多线程 知识点")
→ Observation: 返回 12 个文本片段(synchronized/ReentrantLock/线程池/volatile/AQS...)

→ Thought: 有足够的知识点,可以出题了。结合 LLM 知识出 5 道中等难度单选题。
→ Action: call exam_generation(
    topics=["synchronized","ReentrantLock","线程池","volatile","AQS"],
    questionType="单选", count=5, difficulty="中等"
  )
→ Observation: 生成了 5 道题目

→ Thought: 题目生成完成,检查一下是否覆盖了要求的 5 个主题
→ Final Answer: (输出 5 道题目,标注每题来源)

和传统编程的本质区别

  • 传统:人思考 → 人写代码 → 人检查
  • Agent:AI 思考 → AI 调工具 → AI 看结果 → AI 调整 → AI 输出

ReAct 的进化版

  • Plan-and-Execute:先一次性规划出完整步骤(Plan),再逐步执行(Execute),执行中发现问题就调整 Plan。适合复杂多步骤任务
  • Reflexion:执行失败后不只重试,而是先反思"为什么失败"→ 把反思记入上下文,下次自动避免同类错误

三、Function Calling vs MCP

两者的关系不是对立,而是演进:

维度 Function Calling MCP
定义者 各厂商各自定义(OpenAI/Anthropic 格式不同) Anthropic 提出的开放标准,社区共建
工具描述 写在每次 API 请求的 tools 参数里 Server 启动时通过 tools/list 动态提供
工具发现 应用代码硬编码工具列表 连接到 Server 时自动发现可用工具
新增工具 改代码 + 重新部署 启动新 MCP Server 即可(热插拔)
生态 厂商锁定 社区共建,已有 10000+ Server

PrismAI 为什么 V1 用 Function Calling

  • 工具集固定(RAG 检索/AI 搜索/题库查询/代码沙箱),不会频繁变动
  • Spring AI 的 ToolCallback 机制开发效率高,AgentTool 接口注册到 AgentToolRegistry 后自动适配
  • 个人项目单机部署,MCP 每个 Server 独立进程增加运维负担

V2 改造方向

当前(FC):Agent → @Tool 注解的 Java 方法 → 直接调用

V2(MCP):Agent → MCP Client → MCP Server(RAG检索) → 向量库
                               → MCP Server(题库)     → MySQL
                               → MCP Server(沙箱)     → Docker

改造后的价值:

  • 工具热插拔:新增"简历评估服务"作为独立 MCP Server,不停机上线
  • 工具复用:RAG 检索 MCP Server 可被其他应用直接复用
  • 协议演进意义:“V1 用 FC 快速落地,V2 改造 MCP 开放生态”

Function Calling vs MCP = 各厂商的私有充电口 vs USB-C。前者够用但换手机得换线,后者一次适配到处能用。关键是知道什么时候用什么——初期 FC 快速验证,规模上来切 MCP。


四、多 Agent 协作模式

单 Agent 能解决大部分问题,但当任务规模超过单一上下文的承载能力时,需要多 Agent 协作。

四种经典模式

模式 结构 适合场景 例子
顺序流水线 A→B→C 串行 步骤有明确依赖关系 需求→设计→编码→测试→审查
辩论模式 多 Agent 独立分析→互相质疑→收敛 高风险决策 技术方案评审、安全审查
投票/集成 多 Agent 独立输出→汇总比较 有多个可行解的开放问题 Bug 根因分析、SQL 优化方案
分层委派 主 Agent 拆任务→N 个子 Agent→汇总 大型跨模块任务 项目框架升级

顺序流水线——Code Review 是最典型的例子:Agent A 做 lint 检查和代码风格校验,输出修改建议;Agent B 对修改后的代码做安全扫描(SQL 注入、XSS);Agent C 生成可读性报告,最终交由人类审查。每一步的输出是下一步的输入,串行推进。主要风险:误差传播——上游 Agent 的一处误判会逐级放大,到流水线末端可能已偏离原始意图。对策是在每个环节保留原始输入,让下游 Agent 能回溯对比,而非只看上一环的输出。

辩论模式——适合架构决策这类没有标准答案的场景。例如:Agent A 提出"用 Redis Cluster 做分布式缓存",Agent B 扮演反方质疑"数据量大时集群扩容有脑裂风险,是否考虑 Redis + 本地 Caffeine 两级缓存?“双方交替发言,直到观点收敛或达到预设轮次上限。主要风险:无限辩论——两个 Agent 各执一词、反复纠缠细节却不收敛。对策是设置最大辩论轮次(通常 3-5 轮),并定义明确的收敛判据(如"剩余分歧不影响可执行性”),超时由人拍板。

投票/集成——Bug 根因分析场景:同一个生产异常,3 个 Agent 分别从日志堆栈、数据库慢查询、近期代码变更三个维度独立分析,各自输出一份根因假设和证据链,最后由汇总 Agent(或人)对比三份结论,取交集最高的原因为主方向。主要风险:群体盲从——如果三个 Agent 基于相似训练数据得出结论,投票只是把同一个偏见复读了三次,并非真正的多角度验证。对策是给各 Agent 分配不同的分析视角和工具集,确保输入多样化。

分层委派——大型重构场景:主 Agent(协调者)收到"将单体应用拆分为微服务"的需求,拆出用户模块、订单模块、支付模块三个子任务,分别委派给三个子 Agent 独立完成。每个子 Agent 在自己的限界上下文内分析代码、生成重构方案,主 Agent 汇总后检查模块间接口兼容性。主要风险:子 Agent 幻觉不被察觉——子 Agent 可能虚构了不存在的 API 或错误理解了业务逻辑,主 Agent 若不做校验直接汇总,错误就会进入最终方案。对策是主 Agent 对子 Agent 的输出做交叉验证(如检查引用的类和方法是否真实存在于代码库中),关键模块由人复审。

关键原则:人永远是总指挥——任务的拆解边界、验收标准、最终决策权在人手上。多 Agent 协作的价值是放大人的决策带宽,而非替代人的判断。AI 是执行者,不是决策者。


五、语义缓存与降级策略

两个容易被忽视但实际价值极大的工程手段。

语义缓存

  • 不是缓存 key-value 字符串,是缓存"语义相似的问题"
  • “synchronized 原理” = “Java 内置锁的工作机制” → 语义相同 → 缓存命中
  • 相似度阈值 ≥ 0.95 直接返回,不走 LLM
  • BYOK 模式下,每省一次 LLM 调用 = 帮用户省钱

降级策略(PrismAI 的三级降级):

RAG 检索
  ├── 返回结果充足 → 基于知识库生成,标注来源
  ├── 返回结果不足 → 降级到 LLM 训练数据 + 提示"以下答案基于通用知识,非您的资料"
  └── RAG 不可用(异常)
      ├── LLM 训练数据 → 降级兜底
      ├── 两者均不可用 → 提示用户"服务暂不可用,请稍后重试"
      └── 绝不让用户看到 500 / 空白页

六、实战:PrismAI Agent + ReAct 代码走读

PrismAI 的 Agent 实现完整展示了如何用 Spring AI 构建 ReAct 循环。

架构设计

mindforge-agent 模块(独立 Maven 模块,不依赖 mindforge-service)

AgentTool (接口)
  ├── getName()       → "rag_search"
  ├── getDescription() → "从用户知识库中检索..."(LLM 决策依据)
  ├── getParametersSchema() → JSON Schema(LLM 理解参数格式)
  └── execute(Map params)  → ToolResult(实际执行逻辑)

AgentToolRegistry (注册中心)
  ├── Spring 自动注入所有 AgentTool Bean
  ├── buildToolsDescription() → 生成工具列表文本,注入 System Prompt
  └── get(String name) → 按名查找工具

ReActLoop (循环调度器)
  ├── buildInitialMessages() → System Prompt + 对话历史 + 用户查询
  ├── buildToolCallbacks()  → AgentTool → Spring AI ToolCallback 适配
  └── execute() → ChatClient.prompt().tools(...).call().chatResponse()

关键设计:AgentTool → ToolCallback 适配ReActLoop.java:142-193):

// 将 AgentTool 适配为 Spring AI 2.0 ToolCallback
// 核心是三个动作:
// 1. 提供 ToolDefinition(名/描述/参数Schema)—— LLM 用来决定是否调用
// 2. 实现 call(String input) —— Spring AI 自动解析 LLM 的工具调用请求
// 3. 合并系统注入参数(_provider, _apiKey, _directionId)—— 这些是 LLM 不知道的
ToolCallback callback = new ToolCallback() {
    @Override
    public ToolDefinition getToolDefinition() {
        return DefaultToolDefinition.builder()
                .name(tool.getName())
                .description(tool.getDescription())
                .inputSchema(tool.getParametersSchema())
                .build();
    }

    @Override
    public String call(String input) {
        Map<String, Object> params = new HashMap<>(JSON.parseObject(input, Map.class));
        // 合并系统注入的额外参数
        if (context.getExtraToolParams() != null) {
            params.putAll(context.getExtraToolParams());
        }
        // 发送 SSE thinking 事件 → 前端展示"正在检索知识库..."
        emitThinking(context, 0, tool.getName(), "started", null);
        ToolResult result = tool.execute(params);
        emitThinking(context, 0, tool.getName(),
                result.isSuccess() ? "completed" : "failed", buildSummary(result));
        return result.isSuccess() ? result.getData() : "ERROR: " + result.getErrorMessage();
    }
};

ReActContextReActContext.java):

// 封装一次 Agent 调用所需的全部参数
ChatModel chatModel;                        // 由调用方通过 LlmChatRouter 获取
String systemPrompt;                        // 含工具列表 + 角色定义 + 行为规则
String userQuery;                           // 用户输入的原始问题
List<Message> history;                      // 对话历史(多轮对话)
Map<String, Object> extraToolParams;        // 系统注入参数(_provider, _apiKey 等)
Consumer<ThinkingEvent> thinkingCallback;   // SSE 事件回调
int maxIterations = 10;                     // 兜底:单次最多 10 步
int toolTimeoutSeconds = 30;                // 单次工具调用 30 秒超时

工具列表(已实现的 3 个 + 设计中的 3 个):

工具 功能
rag_search RagSearchTool 知识库语义检索
ai_search AiSearchTool 外部网络搜索(DuckDuckGo 免费兜底,按 Provider 路由)
question_bank_query QuestionBankQueryTool 历史题库查询
exam_generation (设计中) 根据检索结果生成考题
answer_evaluation (设计中) 评估用户答案,输出评分+建议
follow_up_decision (设计中) 从四维度判断是否继续追问

上下文窗口管理ContextWindowManager.java):

  • 会话超过 20 轮时,自动摘要前 10 轮
  • 摘要注入为 SystemMessage,后续轮次基于摘要继续
  • 摘要 LLM 调用失败时降级为简单截断(保留最近 10 轮)

核心要点回顾

  1. Agent 的本质 = 规划 + 工具调用 + 反馈循环。它是从"聊天机器人"到"能干活的系统"的关键跃升
  2. ReAct(Thought→Action→Observation) 是当前最主流的 Agent 推理范式,PrismAI 完整实现了这个循环
  3. FC vs MCP = 私有充电口 vs USB-C——初期 FC 快速验证,规模上来切 MCP
  4. 语义缓存是省钱最重要的手段——考试场景下高频问题重复率很高,缓存命中直接省掉 LLM 调用
  5. AgentTool → ToolCallback 的适配展示了如何将自定义工具接口接入 Spring AI 框架——中间通过系统注入参数解耦 Agent 层和 Service 层

上一篇:《RAG从入门到工程落地:文档切分+Embedding选型+CRAG纠错》 | 下一篇:《AI工程化实践:安全/成本/可观测性/部署全景复盘》
系列专栏AI专栏

💬 聊聊你的经历:你做过的 Agent 项目,工具调用用的是 Function Calling 还是 MCP?从单工具升级到多工具时遇到了什么问题?我先说:一开始用 FC 挺好的,工具多了以后 tools 数组膨胀、每个模型格式还不一样——换 MCP 之后标准化了,但 MCP Server 的进程管理又是个新坑。你呢?

如果这篇文章帮你看懂了 ReAct 循环的本质和 FC vs MCP 的选型逻辑,欢迎收藏+点赞 🙏

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐