从ReAct到MCP-Java写AI-Agent
从 ReAct 到 MCP:用 Java 写 AI Agent 的正确姿势
大模型应用真正落地企业,靠的不是 ChatGPT 式的对话框,而是能调系统、查数据、写工单的 Agent。本文用 Spring AI 1.0 从零搭建一个企业级 Agent,并对比 LangChain4j 的差异。读完你能拿到一份可直接跑通的工程模板。
一、先看效果:3 行自然语言搞定原本要点 5 个菜单的事
一位 HR 想知道:
“把张伟上个月请假超过 2 天的记录导一份给我,顺便算下他剩余年假。”
传统做法:登录 HR 系统 → 选员工 → 切到请假明细 → 筛选日期 → 看年假余额。至少 5 次点击。
接上 Agent 之后,控制台输出是这样的:
[Thinking] 需要分两步:先查请假记录,再查年假余额
[Tool Call] queryLeaveRecords(employee="张伟", startDate="2026-04-01", endDate="2026-04-30", minDays=2)
[Tool Result] [{"date":"2026-04-08","days":3,"type":"事假"}]
[Tool Call] getAnnualLeaveBalance(employee="张伟")
[Tool Result] {"total":15, "used":7, "remaining":8}
[Final Answer] 张伟上月共有 1 条超过 2 天的请假记录:4 月 8 日起事假 3 天。
当前年假总额 15 天,已用 7 天,剩余 8 天。
这就是 Agent——它不是更大的模型,而是给模型加了"手脚":一个能感知(Observation)、能思考(Thought)、能执行(Action)的循环。
下面我们一步步把它造出来。
二、Agent 的核心:把 ReAct 循环讲清楚
任何 Agent 框架,剥到底层都是同一张图:
这就是 2022 年 Princeton 提出的 ReAct 框架:Reasoning + Acting 交替进行。
OpenAI 后来把它工程化为 Function Calling,再演化为今天的 Tool Use。Anthropic 在 2024 年底进一步抽象出 MCP(Model Context Protocol),把工具变成了可热插拔的服务。Spring AI 1.0 同时支持这三层。
记住一句话:Agent = LLM + 工具 + 循环控制。框架做的事,就是把这个循环写好,让你只关心工具本身。
三、Java 生态两强对比:Spring AI vs LangChain4j
国内 Java 团队在 2026 年做 Agent,绕不开这两个选择:
| 维度 | Spring AI 1.0 | LangChain4j 1.x |
|---|---|---|
| 设计哲学 | Spring 风格,声明式、依赖注入 | LangChain 风格,Builder + 接口代理 |
| Agent 定义 | @Tool + ChatClient |
@Tool + AiServices 接口 |
| 记忆机制 | ChatMemory + Advisor |
ChatMemory 内建 |
| 国产模型支持 | 通过 OpenAiChatModel 兼容 DeepSeek、通义、Kimi |
同左,社区扩展更激进 |
| MCP 支持 | 官方 spring-ai-mcp-client |
社区实现 |
| 学习曲线 | 熟悉 Spring 的人 1 小时上手 | 偏向 Python 经验迁移 |
怎么选? 如果项目是 Spring Boot 单体或微服务(绝大多数国内中后台都是),选 Spring AI——它和 @Configuration、@Bean、ConfigurationProperties 浑然一体。如果是新项目想贴近 Python 社区生态,选 LangChain4j。
下文以 Spring AI 1.0 为主线,末尾会给一段 LangChain4j 对照代码。
四、动手:30 分钟搭一个"HR 助手 Agent"
4.1 项目依赖
pom.xml 关键部分:
<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.0</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 用 OpenAI 协议兼容层接 DeepSeek/通义/Kimi 都行 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 对话记忆 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
4.2 配置文件
application.yml:
spring:
ai:
openai:
base-url: https://api.deepseek.com # 国内首选,便宜稳定
api-key: ${DEEPSEEK_API_KEY}
chat:
options:
model: deepseek-chat
temperature: 0.2 # Agent 任务温度要低
小坑:很多新人把
temperature留默认值 0.7,结果工具参数老飘。Agent 类任务建议压到 0.0–0.3。
4.3 定义工具
这是 Agent 最核心的部分。把你已有的业务方法直接标注成工具:
@Component
public class HrTools {
private final HrApiClient hrApi; // 你现成的 Feign / RestClient
public HrTools(HrApiClient hrApi) {
this.hrApi = hrApi;
}
@Tool(description = "查询指定员工在某段时间内的请假记录。"
+ "minDays 用于过滤天数下限,0 表示不过滤。")
public List<LeaveRecord> queryLeaveRecords(
@ToolParam(description = "员工姓名或工号") String employee,
@ToolParam(description = "起始日期,格式 yyyy-MM-dd") String startDate,
@ToolParam(description = "结束日期,格式 yyyy-MM-dd") String endDate,
@ToolParam(description = "最小天数") int minDays) {
return hrApi.listLeaves(employee, startDate, endDate).stream()
.filter(r -> r.days() >= minDays)
.toList();
}
@Tool(description = "获取员工当前年假余额")
public LeaveBalance getAnnualLeaveBalance(
@ToolParam(description = "员工姓名或工号") String employee) {
return hrApi.balanceOf(employee);
}
}
注意三个关键点:
description不是注释,是 Prompt。模型靠它判断什么时候调用。写得越清楚,调用越准。- 参数描述要具体到格式。日期写明
yyyy-MM-dd,否则模型可能给你"上个月"这种字符串。 - 返回值用 Record 或 POJO 都行,Spring AI 会自动序列化成 JSON 给模型。
4.4 装配 ChatClient
@Configuration
public class AgentConfig {
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
}
@Bean
public ChatClient hrAgent(ChatClient.Builder builder,
ChatMemory memory,
HrTools hrTools) {
return builder
.defaultSystem("""
你是一名企业 HR 助手。
- 涉及员工数据时,必须通过工具查询,不要凭记忆作答。
- 日期一律用 yyyy-MM-dd 格式。
- 回答前请先在心里列出步骤,但只向用户输出最终结论。
""")
.defaultTools(hrTools)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build())
.build();
}
}
System Prompt 决定 Agent 的"人格"和"行为边界"。我习惯写明:
- 必须用工具的场景(防止幻觉)
- 格式约束(日期、货币、单位)
- 思考方式(让模型内部 CoT,但不啰嗦输出)
4.5 暴露接口
@RestController
@RequestMapping("/api/hr")
public class HrAgentController {
private final ChatClient hrAgent;
public HrAgentController(ChatClient hrAgent) {
this.hrAgent = hrAgent;
}
@PostMapping("/ask")
public Map<String, String> ask(@RequestBody AskRequest req) {
String reply = hrAgent.prompt()
.user(req.question())
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, req.sessionId()))
.call()
.content();
return Map.of("answer", reply);
}
record AskRequest(String sessionId, String question) {}
}
启动项目,调用:
curl -X POST http://localhost:8080/api/hr/ask \
-H "Content-Type: application/json" \
-d '{"sessionId":"u-1001","question":"张伟上个月请假超过2天的记录有哪些?顺便看看年假还剩多少"}'
至此,一个能调用真实业务系统的 Agent 已经跑起来了。整个工程不到 200 行代码。
五、生产化:四个绕不开的工程问题
Demo 跑通离上线还很远。下面是我在多个企业项目里反复踩过的坑。
5.1 工具调用要做幂等与超时
LLM 会出现"循环调用同一个工具"或"参数微调后重试"的情况。所有写操作工具必须:
@Tool(description = "提交请假申请")
public String submitLeave(@ToolParam String requestId, /* ... */) {
// 用 requestId 做幂等键
if (cache.exists("leave:" + requestId)) {
return "该申请已提交,无需重复";
}
// ...
}
读操作也建议加超时:
@Bean
public ChatClient hrAgent(ChatClient.Builder builder, ...) {
return builder
.defaultOptions(OpenAiChatOptions.builder()
.toolCallReturnDirect(false)
.build())
// ...
.build();
}
5.2 Token 成本:会话越长越烧钱
MessageWindowChatMemory(20) 听起来够用,但一旦工具返回 JSON 比较大,20 条消息就能把上下文撑到几万 token。
实战做法:
- 工具返回值在送给模型前做摘要(比如只保留 5 条最相关记录)。
- 用
VectorStoreChatMemoryAdvisor替代滑窗,把历史向量化存储,按需检索。 - 接 DeepSeek 这种带 上下文缓存 的厂商,重复 system prompt 部分能省 80% 费用。
5.3 防止"工具地狱":分层 Agent
当工具数量超过 20 个,模型选错工具的概率快速上升。解决方案是分层:
RouterAgent (判断意图)
├── HrAgent (HR 工具 8 个)
├── FinanceAgent (财务工具 6 个)
└── ItOpsAgent (运维工具 10 个)
Spring AI 里实现很简单——把每个子 Agent 包装成一个 @Tool 暴露给 Router:
@Tool(description = "处理一切人力资源相关问题:请假、考勤、薪酬、年假")
public String askHr(@ToolParam String question) {
return hrAgent.prompt().user(question).call().content();
}
5.4 可观测:必须打全链路日志
Agent 的故障定位比普通接口难十倍。最少要记三类日志:
- 每一轮 LLM 输入输出(含 token 数)
- 每一次工具调用的参数与返回
- 整条会话的 trace_id
Spring AI 1.0 提供了 ChatModelObservationConvention,能直接接 Micrometer + SkyWalking。生产环境务必打开。
六、进阶:用 MCP 把工具"插件化"
到目前为止,工具都是写在自己工程里的。但企业里更常见的是:HR 系统是 A 团队的、ERP 是 B 团队的、报销系统是外采的。每接一个就改 Agent 代码?不现实。
MCP(Model Context Protocol) 解决的就是这个问题。它把工具变成独立的"工具服务",通过标准协议暴露。Agent 想用哪个工具,连过去拿就行——就像 USB-C 之于外设。
Spring AI 1.0 接 MCP 服务只需一行配置:
spring:
ai:
mcp:
client:
sse:
connections:
hr-server:
url: http://hr-mcp.internal:8090
finance-server:
url: http://finance-mcp.internal:8091
然后注入 ToolCallbackProvider:
@Bean
public ChatClient agent(ChatClient.Builder builder,
ToolCallbackProvider mcpTools) {
return builder.defaultToolCallbacks(mcpTools).build();
}
新增一个业务系统?让对方部署一个 MCP Server 即可,Agent 端零改动。这是 2026 年企业 Agent 架构的主流方向。
七、对照:同样的功能用 LangChain4j 怎么写
为了完整性,给一段等价代码:
interface HrAssistant {
@SystemMessage("你是一名企业 HR 助手...")
String chat(@MemoryId String sessionId, @UserMessage String question);
}
HrAssistant assistant = AiServices.builder(HrAssistant.class)
.chatLanguageModel(OpenAiChatModel.builder()
.baseUrl("https://api.deepseek.com")
.apiKey(System.getenv("DEEPSEEK_API_KEY"))
.modelName("deepseek-chat")
.build())
.tools(new HrTools(hrApi))
.chatMemoryProvider(sid -> MessageWindowChatMemory.withMaxMessages(20))
.build();
String reply = assistant.chat("u-1001", "张伟上个月...");
LangChain4j 把 Agent 抽象成接口代理,写起来更紧凑,但少了 Spring 的依赖注入弹性。两个框架我都用过,结论:团队里 Spring 经验越深,越应该选 Spring AI。
八、写在最后
一年前做大模型应用,主战场还是"Prompt 调到天荒地老"。今天,焦点已经转移到:
- 工具怎么设计才能让模型用得准(描述比代码更重要)
- 多 Agent 怎么协作才能不打架(分层路由)
- 工具怎么解耦才能横向扩张(MCP)
Spring AI 1.0 已经把这些能力做成了 Spring 开发者熟悉的形态。门槛真正消失的不是模型能力,而是工程化路径。
如果你正在评估"要不要在公司项目里引入 Agent",我的建议是:先挑一个查询场景(报表、知识库、流水),用本文模板花一周做个 POC。跑通后你会发现,最难的不是模型,而是把业务接口梳理成"模型能理解的样子"。
转载请注明出处。文中观点基于作者在多个 AI 项目中的实践,仅供参考。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)