一个基于 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 的核心流程可以概括为:

flowchart TD U["用户 / chat.html"] --> API["ChatController"] API --> AS["AgentService"] AS --> MC["MessageClassifier"] MC --> LLM["OpenRouter 大模型"] LLM --> RD{"RouteDecision"} RD -->|"CHAT"| CH["ChatAgentHandler"] RD -->|"RAG"| RH["RagAgentHandler"] RD -->|"DATABASE"| DH["DatabaseAgentHandler"] RH --> VS["本地 Embedding + SimpleVectorStore"] DH --> TOOL["ProductDatabaseTools + H2"] CH --> MEM["ChatMemory"] RH --> MEM DH --> MEM CH --> RESULT["AgentResponse"] RH --> RESULT DH --> RESULT RESULT --> U

整个项目由五个主要部分组成:

  1. ChatController 接收用户请求和会话 ID。
  2. AgentService 负责组织分类和执行流程。
  3. MessageClassifier 调用大模型判断消息类型。
  4. 不同的 AgentHandler 执行聊天、RAG 或数据库查询。
  5. 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 流程如下:

flowchart LR Q["用户问题"] --> E1["Embedding"] E1 --> SEARCH["向量相似度检索"] DOC["knowledge/*.md"] --> E2["文档 Embedding"] E2 --> STORE["SimpleVectorStore"] STORE --> SEARCH SEARCH --> CONTEXT["相关文档上下文"] CONTEXT --> MODEL["问题 + 上下文交给大模型"] MODEL --> ANSWER["生成回答并返回来源"]

本地 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 的:

  • MessageWindowChatMemory
  • MessageChatMemoryAdvisor

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 的开发者,这个项目可以作为一个容易启动、容易理解,也
方便继续扩展的基础工程。

项目地址

https://github.com/hdwang123/sagent

posted @ 2026-07-19 10:45  追极  阅读(19)  评论(0)    收藏  举报