一个基于 Spring AI 的轻量级 Agent 实践项目
Sagent:一个基于 Spring AI 的轻量级 Agent 实践项目
随着大模型应用从简单问答逐渐走向业务系统,单纯调用一次聊天接口已经很难满足实际
需求。一个完整的 AI 应用通常还要具备知识检索、数据库访问、工具调用、会话记忆和
任务分发等能力。
Sagent 是一个用于学习和验证这些能力的轻量级项目。它基于 Spring Boot 和
Spring AI 构建,通过 OpenRouter 调用大语言模型,同时集成了本地 RAG、H2 数据库、
Tool Calling 和多轮会话记忆。
项目没有追求复杂的框架封装,而是用尽量清晰的结构展示一个 Agent 应用是如何从
用户问题出发,完成分类、执行和结果生成的。
项目能做什么
Sagent 将用户消息分为三种类型:
| 类型 | 处理方式 | 典型问题 |
|---|---|---|
| 普通聊天 | 直接调用大模型回答 | “帮我写一段产品介绍” |
| RAG 检索 | 从本地知识库检索后回答 | “项目使用哪个 JDK 版本?” |
| 数据库查询 | 调用数据库工具查询后回答 | “数据库里有多少个产品?” |
用户不需要手动选择模式。系统会先调用大模型判断问题类型,再把请求交给对应的处理
模块。
除了三类核心能力,项目还提供:
- 多轮会话记忆
- RAG 来源展示
- 路由类型和分类理由展示
- 本地 ONNX Embedding 模型
- Vue 2 + Element UI 聊天页面
- Windows、macOS 和 Linux 运行支持
整体架构
Sagent 的核心流程可以概括为:
整个项目由五个主要部分组成:
ChatController接收用户请求和会话 ID。AgentService负责组织分类和执行流程。MessageClassifier调用大模型判断消息类型。- 不同的
AgentHandler执行聊天、RAG 或数据库查询。 ChatMemory保存多轮会话消息。
这种结构将路由、执行和结果返回分离开,后续增加新的 Agent 类型时,不需要把所有
逻辑都堆在一个 Service 中。
大模型消息分类
传统程序通常使用关键词判断问题类型,例如看到“价格”就进入数据库查询,看到
“文档”就进入知识库检索。
这种方式实现简单,但面对自然语言时容易失效:
帮我看看最便宜的那个还有没有货。
刚才提到的新闻为什么会引起关注?
把数据库中的商品情况总结一下。
这些问题包含指代、上下文和不同表达方式,单纯依靠关键词很难稳定分类。
Sagent 使用大模型完成路由判断。分类提示词定义了三种可选类型,并要求模型返回
结构化结果:
public record RouteDecision(
AgentType type,
String reason
) {
}
模型返回的结果类似:
{
"type": "RAG",
"reason": "用户询问本地知识库中的项目配置"
}
Spring AI 会把模型结果转换成 Java 对象,AgentService 再根据 type 找到对应的
Handler。
这种做法的优势是:
- 能理解自然语言和上下文
- 不需要维护大量关键词规则
- 路由结果可以直接映射为 Java 类型
- 分类原因可以展示在页面上,便于调试
如果分类模型没有返回有效结果,项目会降级到普通聊天,避免整个请求直接失败。
普通聊天
普通聊天是最直接的处理分支。
当消息被分类为 CHAT 后,ChatAgentHandler 会将系统提示词和用户消息发送给
OpenRouter,再把模型回答封装成统一的 AgentResponse 返回。
适合进入该流程的问题包括:
- 日常问答
- 文案生成
- 翻译和总结
- 不需要访问本地知识或业务数据的任务
项目使用 OpenRouter 的 OpenAI 兼容接口,因此 Spring AI 可以通过 OpenAI
ChatModel 直接调用,不需要为 OpenRouter 编写单独的客户端。
RAG 知识库检索
大模型掌握的是训练阶段获得的知识,它并不知道项目内部文档、企业资料或刚刚发布的
内容。RAG 的作用是在模型回答前,先从指定知识库中找到相关资料。
Sagent 的 RAG 流程如下:
本地 Embedding
项目内嵌了 all-MiniLM-L6-v2 ONNX 模型,并通过 Spring AI Transformers 在 JVM
中运行。
Embedding 会把文本转换成向量。语义越接近的文本,其向量距离通常越近。用户提问
后,系统计算问题向量,再从知识文档中找出最相似的内容。
Embedding 完全在本地运行,因此:
- 不需要安装 Ollama
- 不需要额外的 Embedding API Key
- 不需要 Python 环境
- 下载代码后可以直接通过 IDEA 启动
向量检索与回答
项目启动时会加载 src/main/resources/knowledge 中的 Markdown 文档,将文档向量
保存到 SimpleVectorStore。
用户提出知识问题后,系统先检索相关文档,再把“用户问题 + 检索结果”一起发送给
聊天模型。最终响应还会返回命中的文件名,方便用户判断答案依据。
当前知识库包含项目说明、Agent 路由说明以及 NASA、CERN、WHO 等英文新闻,可用于
测试中文和英文知识问答。
数据库 Tool Calling
当用户询问产品数量、名称、价格或库存时,请求会进入数据库处理分支。
Sagent 没有让大模型直接生成并执行 SQL,而是把经过控制的 Java 方法注册为 Tool:
listProducts
findProductsByName
findProductsByMaxPrice
findProductById
countProducts
每个 Tool 都有名称、用途和参数说明。大模型根据用户问题选择合适的方法,并生成
调用参数。
例如:
用户:查询价格不超过 70 元的产品。
大模型会选择 findProductsByMaxPrice,传入 70。Java Tool 使用 JdbcClient
查询 H2 数据库,然后模型根据查询结果组织自然语言回答。
流程可以概括为:
用户问题
↓
大模型选择 Tool 和参数
↓
Java Tool 查询 H2
↓
查询结果返回大模型
↓
生成最终回答
项目只暴露只读查询 Tool,没有提供新增、修改和删除方法。相比直接让模型执行任意
SQL,这种方式更容易控制权限、校验参数和审计操作。
多轮会话记忆
大模型接口本身不会自动记住上一轮消息。要实现多轮聊天,应用必须在调用模型时补充
历史消息。
Sagent 使用 Spring AI 的:
MessageWindowChatMemoryMessageChatMemoryAdvisor
ChatMemory 根据 conversationId 保存不同会话的消息,Advisor 则在模型调用前
自动加载历史,在调用完成后保存用户消息和模型回答。
例如:
第一轮:介绍一下 NASA 的那篇新闻。
第二轮:它为什么被重新分类?
第二轮虽然没有再次提到新闻名称,但模型可以根据同一个会话中的历史消息理解“它”
指代什么。
项目为每个会话保留最多 20 条消息。当前记忆存放在 JVM 内存中,应用重启后会清空。
消息分类器只读取历史,不会把分类结果写入正式会话。这样可以避免内部
RouteDecision 污染用户和助手之间的正常聊天记录。
Web 聊天页面
项目提供了一个基于 Vue 2 和 Element UI 的测试页面:
http://localhost:8080/chat.html
页面可以展示:
- 用户消息和模型回答
- 当前选择的 Agent 类型
- 大模型给出的分类原因
- RAG 命中的知识文件
- 请求耗时和错误状态
页面会生成并维护 conversationId。点击清空按钮时,不仅会清除页面消息,也会调用
后端接口删除服务端会话记忆,然后创建新的会话。
Vue、Element UI 和字体文件都已经放在项目静态资源中,不需要安装 Node.js,也不
需要单独执行前端构建。
技术选型
| 技术 | 用途 |
|---|---|
| JDK 21 | Java 运行环境 |
| Spring Boot 4.1.0 | Web、配置和依赖管理 |
| Spring AI 2.0.0 | ChatModel、Advisor、Embedding、Tool Calling |
| OpenRouter | 大模型接口 |
| Spring AI Transformers | 本地 ONNX Embedding |
| SimpleVectorStore | 内存向量检索 |
| H2 | 演示数据库 |
| JdbcClient | 参数化数据库查询 |
| Vue 2 + Element UI | Agent 测试页面 |
整个项目不依赖外部数据库、向量数据库或本地大模型服务,适合直接下载后学习和调试。
如何运行
准备 JDK 21、Maven 和 OpenRouter API Key。
Windows PowerShell:
$env:OPENROUTER_API_KEY = "你的真实Key"
$env:OPENROUTER_MODEL = "openrouter/free"
mvn spring-boot:run
macOS 或 Linux:
export OPENROUTER_API_KEY="你的真实Key"
export OPENROUTER_MODEL="openrouter/free"
mvn spring-boot:run
在 IDEA 中运行时,将 Project SDK 设置为 JDK 21,并在运行配置中添加
OPENROUTER_API_KEY。
项目启动后访问:
http://localhost:8080/chat.html
可以怎样测试
普通聊天:
你好,请用一句话介绍 Spring AI。
RAG:
OPENROUTER_API_KEY 在哪里配置?
Why was 1998 SH2 reclassified as a comet?
What does WHO recommend to reduce dementia risk?
数据库:
数据库里有多少个产品?
查询价格不超过 70 元的产品。
多轮会话:
第一轮:介绍一下 NASA 的那篇新闻。
第二轮:它为什么被重新分类?
项目的定位和扩展方向
Sagent 是一个学习和功能验证项目,不是完整的生产系统。它希望用较少的代码展示
一个 Agent 应用的主要组成部分,而不是提供开箱即用的企业级平台。
后续可以继续扩展:
- 将内存向量库替换为 PGvector、Qdrant、Milvus 或 Redis
- 将会话记忆保存到 JDBC 或 Redis
- 使用 SSE 实现流式回答
- 增加写操作审批和人工确认
- 增加 API 鉴权、限流和敏感信息过滤
- 为路由、检索和 Tool Calling 增加可观测性
- 接入真实业务数据库和企业知识库
总结
Sagent 串联了一个 AI Agent 应用中最常见的几项能力:
大模型路由
+
普通聊天
+
RAG 知识检索
+
数据库 Tool Calling
+
多轮会话记忆
它展示了 Spring AI 不只是一个聊天接口封装,还可以作为模型、知识、工具和业务代码
之间的连接层。
对于正在学习 Spring AI 的开发者,这个项目可以作为一个容易启动、容易理解,也
方便继续扩展的基础工程。

浙公网安备 33010602011771号