基于 Spring Boot 4.0.8 + Spring AI 2.0.1 + DeepSeek,从零搭建一个覆盖 Tool Calling、MCP、RAG、ReAct、Skills、安全认证、弹性容错的 AI Agent 平台。115 个测试用例、五层架构、双环境 Profile,跟着做一遍,Spring AI 2.0 核心能力全部落地。

一、为什么需要这篇教程?
AI Agent 大火的 2026 年,Java 开发者用 Spring AI 搭建 Agent 平台时,往往会撞上三堵墙:
本文手把手拆解的这个项目(menghan-agent),开源地址:https://gitee.com/zhang-qing521/menghan-agent.git,就是为了把这三堵墙一次性推倒:在一个结构清晰的代码库里覆盖 Spring AI 全部核心能力,同时把平台化必备的安全认证、弹性容错、可观察性、持久化全部补齐。跟着做完,你得到的不是一个玩具 Demo,而是一个可以直接演进的 Agent 平台骨架。
技术栈一览:
| 框架 | Spring Boot 4.0.8 / Spring AI 2.0.1 / JDK 21+(已在 Java 25 验证) |
| 大模型 | DeepSeek(deepseek-chat / deepseek-reasoner) |
| Embedding | Transformers ONNX(all-MiniLM-L6-v2,384 维,首次运行自动下载) |
| 向量库 | dev: SimpleVectorStore 内存版;prod: PgVector(HNSW 索引) |
| 协议 | MCP(Model Context Protocol),Server + Client 双端,SSE 传输 |
| 安全 | Spring Security + API Key(常量时间比对 + IP 暴力破解防护) |
| 容错 | Resilience4j(限流 / 重试 / 熔断) |
| 文档 | springdoc-openapi(Swagger UI) |
| 可观察性 | Actuator + Micrometer Tracing(Brave) + Zipkin + 结构化日志 |
| 测试 | JUnit5 + Mockito + MockMvc + Testcontainers(115 个用例) |
二、平台架构:先把五层分包立起来
动手写代码前,先定好分层。平台严格按 Controller → Service → Config/Advisor → Tool/Infra → Domain 分包:
org.menghan.agent
├── config/ # AiConfig、AdvisorConfig、ChatMemoryConfig、VectorStoreConfig、SecurityConfig……
├── advisor/ # LoggingAdvisor、SafeGuardAdvisor、SkillAdvisor(拦截器链)
├── service/ # ChatService、AgentService、RagService、McpClientService
├── controller/ # AgentController、McpController、SkillController(带 Swagger 注解)
├── tool/ # WeatherTools、CalculatorTools、KnowledgeBaseTools、SkillTools(@Tool)
├── infra/ # McpServerTools、SkillRegistry、KnowledgeLoader、GlobalExceptionHandler
├── domain/ # dto(请求响应)+ model(WeatherForecast、Skill)
└── support/ # ApiResult 统一响应包装
分层的好处在平台化阶段会集中体现:LLM 相关能力全部收敛在 Service + Advisor + Tool 三层,Web 层和业务 POJO 完全不感知 AI 框架的存在,替换模型提供商、增删工具都只动一个包。
三、手把手搭建:十大核心模块
3.1 第一步:装配 ChatClient 与 Advisor 链
Spring AI 2.0 中,ChatClient 是所有交互的入口,而 Advisor(拦截器)是它的灵魂。在 AiConfig 中完成全量装配:
@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory,
VectorStore vectorStore,
LoggingAdvisor loggingAdvisor,
SafeGuardAdvisor safeGuardAdvisor,
SkillAdvisor skillAdvisor,
WeatherTools weatherTools,
CalculatorTools calculatorTools,
KnowledgeBaseTools knowledgeBaseTools,
SkillTools skillTools) {
// 会话记忆:20 条滑动窗口
MessageChatMemoryAdvisor memoryAdvisor =
MessageChatMemoryAdvisor.builder(chatMemory).build();
// RAG:每次请求前自动检索 topK=3 的文档注入上下文
Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.topK(3)
.build())
.build();
return ChatClient.builder(chatModel)
.defaultSystem(REACT_SYSTEM_PROMPT) // ReAct 系统提示
.defaultAdvisors(
safeGuardAdvisor, // order = HIGHEST_PRECEDENCE + 50 最外层:安全
loggingAdvisor, // order = HIGHEST_PRECEDENCE + 100 次外层:日志
skillAdvisor, // order = HIGHEST_PRECEDENCE + 150 技能注入
memoryAdvisor, // 多轮记忆
ragAdvisor) // 检索增强
// @Tool 对象 → ToolCallback[],一行完成工具注册
.defaultToolCallbacks(
ToolCallbacks.from(weatherTools, calculatorTools,
knowledgeBaseTools, skillTools))
.build();
}
这一步有几个 2.0 版本的关键变化,都是实际搭建时踩过的坑:
- 工具回调工具类是 org.springframework.ai.support.ToolCallbacks,包名与 1.x 不同;
- RAG 用的是 RetrievalAugmentationAdvisor,1.x 的 QuestionAnswerAdvisor 已被移除;
- ToolCallingAdvisor 不需要手动添加,DefaultChatClient 会自动把它接到链尾,驱动工具调用循环;
- 自定义 Advisor 的接口是 CallAdvisor / StreamAdvisor,老的 CallAroundAdvisor 已废弃。
3.2 第二步:用系统提示 + 工具循环实现 ReAct
Agent 平台的精髓在于自主决策。用一段系统提示引导模型遵循 ReAct 范式:
你是一个会自主规划的 AI Agent。对每个任务按 ReAct 循环:
1. Thought: 思考需要哪些信息或动作
2. Action: 调用合适的工具 (天气查询 / 计算器 / 本地知识库检索 / MCP 远程工具)
3. Observation: 观察工具返回结果
4. 重复 1-3 直到有充分依据
5. Final Answer: 给出最终答案
规则:
– 不要臆造未通过工具获取的事实性数据。
– 若需要多个信息, 分步调用多个工具。
– 工具调用完毕后, 综合观察结果给出最终答案。
然后在 AgentService 中把本地工具和远程 MCP 工具合并成一个工具池交给模型:
public String run(String task, String sessionId) {
List<ToolCallback> tools = new ArrayList<>(Arrays.asList(
ToolCallbacks.from(weatherTools, calculatorTools,
knowledgeBaseTools, skillTools)));
// 远程 MCP 工具(可选注入,client 关闭时为空列表)
for (ToolCallbackProvider provider : mcpProviders) {
tools.addAll(Arrays.asList(provider.getToolCallbacks()));
}
return chatClient.prompt()
.user(task)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
.toolCallbacks(tools.toArray(new ToolCallback[0]))
.call()
.content();
}
效果是什么?用户发一句"查一下上海的天气,再帮我算 45 加 55 等于多少",模型会自主决定:先调天气工具 → 观察结果 → 再调计算器 → 综合两次观察输出最终答案。整个循环不需要你写任何编排代码,ToolCallingAdvisor 会在"模型请求工具 → 执行工具 → 回填结果 → 再次请求模型"之间自动轮转,直到模型不再请求工具。
3.3 第三步:Advisor 洋葱模型,安全日志各就各位
自定义 Advisor 通过 getOrder() 控制位置,形成洋葱式拦截:
| SafeGuardAdvisor | HIGHEST_PRECEDENCE + 50 | 最外层,越狱/敏感关键词拦截 |
| LoggingAdvisor | HIGHEST_PRECEDENCE + 100 | 全链路入参、出参、耗时记录 |
| SkillAdvisor | HIGHEST_PRECEDENCE + 150 | 关键词命中后注入技能指令 |
| MessageChatMemoryAdvisor | — | 多轮会话记忆 |
| RetrievalAugmentationAdvisor | — | RAG 检索 |
把安全放在最外层,意味着任何被拦截的恶意请求根本不会进入日志、记忆、LLM 调用,既省 token 又防注入。
3.4 第四步:Skills —— Tool、RAG 之外的第三种能力
这是平台化过程中最有价值的一个模块。Agent 有三类知识:
- Tool(工具):可执行的动作,如"查天气";
- RAG(检索):静态事实知识,如文档片段;
- Skill(技能):程序性知识,即"做某类任务的标准工作流程",如"怎么做代码审查"“怎么优化 SQL”。
技能用 Markdown 文件定义(YAML frontmatter + 正文指令),平台上线后新增技能只需加文件、零代码改动、零重启发版:
—
name: code-review
description: 代码审查工作流
keywords: 代码审查, code review, review, CR
—
审查代码时按以下顺序检查:
1. 正确性:边界条件、空值、并发……
2. 安全性:输入校验、权限、注入……
3. 可维护性:命名、重复、圈复杂度……
SkillAdvisor 实现"被动注入"——用户输入命中关键词,自动把技能指令拼进系统提示。这里有一个 Spring AI 2.0.1 的大坑,搭建时务必注意:
// ⚠️ 注意:augmentSystemMessage(String) 是【替换】语义(mutate().text(…)),
// 直接传字符串会把原有系统提示整个覆盖掉!
// 必须用 Function 重载,先读原文本再拼接:
Prompt augmented = request.prompt().augmentSystemMessage(sm -> sm.mutate()
.text((sm.getText() == null ? "" : sm.getText()) + injection)
.build());
return request.mutate().prompt(augmented).build();
这种官方文档语焉不详的版本陷阱,恰恰是跟着完整项目搭建一遍才能避开的。此外还有"主动读取"路径:SkillTools.readSkill(name) 暴露为 @Tool,模型在 ReAct 循环中可以自己决定按名加载技能。
3.5 第五步:MCP —— 把工具能力标准化、跨进程化
MCP(Model Context Protocol)是 2025 年以来 AI 工具生态的事实标准。平台同时实现双端:
Server 端——用 @McpTool 注解,方法自动通过 /sse 端点暴露给 Claude Desktop 等外部客户端:
@Component
public class McpServerTools {
@McpTool(description = "获取当前服务器时间, 用于演示 MCP server 暴露工具")
public String serverTime() {
return LocalDateTime.now().toString();
}
@McpTool(description = "生成指定长度的随机字符串")
public String randomString(
@McpToolParam(description = "结果字符串长度, 范围 1-64", required = true) int length) {
int len = Math.max(1, Math.min(64, length));
return UUID.randomUUID().toString().replace("-", "").substring(0, len);
}
}
Client 端——消费远程 MCP Server 的工具,自动包装成 ToolCallbackProvider Bean,合入 3.2 节 AgentService 的工具池。本地工具和跨进程工具在模型眼里毫无差别,这就是协议标准化的威力:你的平台从此可以接入整个 MCP 工具生态。
3.6 第六步:RAG —— 双模式检索 + 双向量库
RAG 做了两条路径,覆盖不同场景:
- 被动 RAG:RetrievalAugmentationAdvisor 在每次请求前自动检索,模型无感知;
- 主动 RAG:KnowledgeBaseTools 是一个 @Tool,模型在需要时显式调用检索。
知识库在启动时由 KnowledgeLoader 从 classpath:data/*.md 加载切片入向量库。向量库按环境切换,这是平台能"开发零依赖、生产可持久化"的关键:
- dev:SimpleVectorStore 内存实现(@Profile("!prod")),clone 即跑;
- prod:PgVector,HNSW 索引 + cosine 距离 + 384 维,持久化到 PostgreSQL。
3.7 第七步:结构化输出与流式,两个一行 API
// 结构化:LLM 返回的 JSON 直接反序列化为 Java record,类型安全
public WeatherForecast structured(String city) {
return chatClient.prompt()
.user("查询 " + city + " 的天气, 请以结构化对象返回……")
.call()
.entity(WeatherForecast.class); // ← 关键 API
}
// 流式:Flux<String> 天然适配 SSE 打字机效果
public Flux<String> stream(String message, String sessionId) {
return chatClient.prompt()
.user(message)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
.stream()
.content();
}
WeatherForecast 是一个 record,包含城市、温度、天气状况、湿度、穿衣建议——非结构化文本到类型安全对象的转换,框架全包了。
3.8 第八步:生产安全 —— API Key 认证的四个细节
平台要对外提供服务,认证是绕不过去的。SecurityConfig 仅在 prod profile 激活,四个细节缺一不可:
另外无状态会话(STATELESS)、HSTS、CSP、X-Frame-Options: DENY 等安全响应头一应俱全;CSRF 禁用也附带了依据(无状态 API Key 认证不依赖 Cookie,无 CSRF 攻击向量,注释里直接贴了 Spring Security 官方文档链接)。
3.9 第九步:弹性容错 —— 三个注解守住 LLM 调用
LLM 调用慢、贵、还会抖。ChatService 上叠加 Resilience4j 三件套:
@RateLimiter(name = "llm-rate-limit") // 限流:20 次/秒
@Retry(name = "llm-retry") // 重试:3 次,指数退避 1s→2s→4s,仅网络/超时异常
@CircuitBreaker(name = "llm-circuit-breaker") // 熔断:50% 失败率开启,30s 后半开恢复
public String chat(String message, String sessionId) { ... }
一个容易写错的细节:流式接口不加 @Retry——Flux 的中间状态不可重放,重试会导致内容重复,所以流式方法只保留限流和熔断。
3.10 第十步:可观察性与测试保障
- Actuator:/actuator/health 内置 liveness/readiness 探针,直接对接 K8s;
- 分布式追踪:Micrometer Tracing(Brave) + Zipkin Reporter,采样率可配(默认 0.1);
- 结构化日志:logback-spring.xml 中 prod 环境输出 JSON;
- 测试:115 个用例覆盖 domain / tool / advisor / service / controller / infra / config 全部分层,另有 5 个 Testcontainers 集成测试用真实 PostgreSQL + PgVector 验证检索链路。纯单元测试不启动 Spring 上下文,3 秒跑完。
四、平台 API 一览
| POST | /api/chat | 多轮对话(20 条滑动窗口记忆) |
| POST | /api/agent | ReAct Agent 工具调用(本地 + MCP) |
| POST | /api/rag | RAG 知识库问答 |
| GET | /api/structured?city=北京 | 结构化输出 WeatherForecast |
| GET | /api/stream?message=你好&sessionId=s1 | SSE 流式输出 |
| GET | /api/skills、/api/skills/{name} | 技能列表 / 详情 |
| POST | /api/skills/match | 关键词匹配演示(不调 LLM,免 Key 验证) |
| GET/POST | /api/mcp/tools、/api/mcp/call | 远程 MCP 工具发现与调用 |
| GET | /swagger-ui.html | 在线接口文档(内置 X-API-Key 调试) |
五、三分钟把平台跑起来
# 1. 准备 JDK 21+、Maven 3.9+,申请 DeepSeek API Key
# https://platform.deepseek.com/api_keys
# 2. 设置 Key(Windows PowerShell)
$env:DEEPSEEK_API_KEY="sk-你的key"
# 3. 启动(默认 dev profile:内存向量库、免认证、免 PostgreSQL)
mvn spring-boot:run
# 4. 验证
curl http://localhost:8087/actuator/health
curl -X POST http://localhost:8087/api/chat `
-H "Content-Type: application/json" `
-d '{"message":"你好","sessionId":"s1"}'
生产部署切换 SPRING_PROFILES_ACTIVE=prod 即可:API Key 认证、PgVector 持久化、JSON 日志、CORS 白名单自动生效,项目自带 Dockerfile、docker-compose.yml(含 pgvector)和 DEPLOYMENT.md。
六、建议的动手路线
七、总结
跟着这套骨架搭完,你会发现一个 Agent 平台的核心并不神秘:
- 交互层:ChatClient + Advisor 洋葱链,横切关注点(安全、日志、记忆、检索)全部可插拔;
- 能力层:@Tool 本地工具 + MCP 远程工具 + Skill 程序性知识,三类能力统一编排;
- 知识层:RAG 双模式检索 + dev/prod 双向量库;
- 保障层:API Key 认证、Resilience4j 容错、Micrometer 追踪、115 个测试兜底。
AI 时代,Java 工程师不需要转 Python 才能做 Agent。Spring AI 2.0 已经把平台级能力准备妥当,差的只是一个把这些能力串起来、且认真对待生产细节的参考实现——希望这个项目能成为你搭建自己 Agent 平台的起点。
项目 MIT 协议开源,欢迎 Star、Fork、提 Issue 交流。你的每一个 ⭐ 都是作者继续更新的动力!
技术栈:Spring Boot 4.0.8 · Spring AI 2.0.1 · DeepSeek · MCP · PgVector · Resilience4j · Spring Security






