text
ai
├── config
│ ├── AiConfig.java
│ ├── ChatMemoryConfig.java
│ └── VectorStoreConfig.java
│
├── advisor
│ ├── MemoryAdvisor.java
│ └── RagAdvisor.java
│
└── rag
├── KnowledgeImporter.java
└── KnowledgeSearchService.java
这几个类不是 Spring AI 强制要求你必须这样创建。
这是我们为了让一个真实项目:
text
配置
业务
RAG
Memory
Advisor
彼此解耦,而设计出来的一种比较合理的目录结构。
下面我就按照一个完整的 IELTS AI Tutor 项目,把每个类到底干什么、为什么需要、里面应该放什么代码、它们之间怎么调用讲清楚。
一、先看整体关系
先不要急着看代码。
这几个类实际上分成 3 层:
text
Spring AI
│
┌────────────┼────────────┐
↓ ↓ ↓
Config Advisor RAG
│ │ │
│ │ ├── KnowledgeImporter
│ │ │
│ │ └── KnowledgeSearchService
│ │
│ ├── MemoryAdvisor
│ │
│ └── RagAdvisor
│
├── AiConfig
├── ChatMemoryConfig
└── VectorStoreConfig
简单说:
config
负责:
"这些 AI 基础设施怎么创建?"
advisor
负责:
"AI 请求发送之前/过程中,要自动增加什么能力?"
rag
负责:
"我们的知识怎么导入、怎么搜索?"
二、先从 AiConfig.java 开始
这是整个 AI 模块最基础的配置之一。
例如:
java
@Configuration
public class AiConfig {
@Bean
public ChatClient chatClient(
ChatClient.Builder builder
) {
return builder.build();
}
}
看起来非常简单。
但是它实际上解决了一个重要问题:
text
ChatClient
到底由谁创建?
三、为什么要自己配置 ChatClient?
你当然可以直接:
java
private final ChatClient chatClient;
但是 Spring 必须知道:
这个
ChatClientBean 从哪里来?
所以:
java
@Bean
public ChatClient chatClient(
ChatClient.Builder builder
) {
return builder.build();
}
告诉 Spring:
帮我创建一个 ChatClient,并放到 Spring IOC 容器。
以后:
java
@Service
public class AiTutorService {
private final ChatClient chatClient;
public AiTutorService(ChatClient chatClient) {
this.chatClient = chatClient;
}
}
Spring 自动注入。
四、AiConfig 不只是创建 ChatClient
实际项目里可以在这里配置:
text
默认 System Prompt
默认 Advisor
默认 Tool
例如:
java
@Configuration
public class AiConfig {
@Bean
public ChatClient chatClient(
ChatClient.Builder builder,
ChatMemory chatMemory
) {
return builder
.defaultSystem("""
你是一名专业的雅思AI教师。
你的任务是帮助学生学习雅思。
""")
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
.build();
}
}
这样:
java
chatClient
.prompt()
.user("我雅思阅读5.5怎么办?")
.call();
自动拥有:
text
默认 System Prompt
+
Memory
五、但是我更建议把 AiConfig 控制得简单一点
如果项目越来越大:
java
AiConfig
里面不要什么都塞。
例如不要最终变成:
java
@Configuration
public class AiConfig {
// ChatClient
// Memory
// Redis
// VectorStore
// RAG
// Tools
// Prompt
// Embedding
// ...
}
这样很快就变成:
AIConfig 巨石类。
所以我们才拆出:
text
AiConfig
ChatMemoryConfig
VectorStoreConfig
六、ChatMemoryConfig.java
这个类专门负责:
AI 对话记忆怎么创建、怎么存储。
七、先理解 ChatMemory
例如用户:
text
用户:
我雅思阅读5.5。
AI:
你的阅读目前基础已经...
下一句话:
text
用户:
那我应该重点练什么?
AI 必须知道:
text
"那"
指的是:
text
阅读5.5
所以需要保存历史:
text
User:
我雅思阅读5.5。
Assistant:
...
User:
那我应该重点练什么?
这就是:
text
ChatMemory
八、ChatMemory 最简单的配置
可以先使用内存实现:
java
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
}
这样:
text
Spring Boot
↓
ChatMemory
↓
内存
但是这个只适合:
text
开发
测试
Demo
因为:
text
应用重启
↓
Memory丢失
九、生产环境为什么通常用 Redis?
你的项目本身就很适合 Redis。
架构:
text
Vue
↓
Spring Boot
↓
ChatClient
↓
ChatMemory
↓
Redis
例如:
text
chat:memory:user:10001:conversation:abc123
里面保存:
json
[
{
"role": "user",
"content": "我雅思阅读5.5"
},
{
"role": "assistant",
"content": "..."
}
]
十、这里有一个非常重要的概念
Conversation ID
例如:
text
conversationId = "abc123"
用户第一次:
text
POST /ai/chat
conversationId=abc123
message=我雅思阅读5.5
保存:
text
abc123
↓
消息1
消息2
下一次:
text
conversationId=abc123
message=那我应该重点练什么?
系统根据:
text
abc123
找到历史。
十一、MemoryConfig 主要负责什么?
你可以把它理解成:
text
ChatMemoryConfig
│
├── Memory实现
│
├── 存储方式
│
├── 消息数量
│
└── conversation管理
而:
text
MemoryAdvisor
负责:
把 Memory 接入 ChatClient。
这两个不要混。
十二、MemoryConfig 和 MemoryAdvisor 的区别
非常重要。
ChatMemoryConfig
负责:
text
"Memory是什么?"
例如:
text
Redis
MemoryAdvisor
负责:
text
"什么时候使用Memory?"
例如:
text
每次调用AI之前:
读取历史
AI调用完成:
保存当前消息
所以:
text
ChatMemoryConfig
↓
ChatMemory
↓
MemoryAdvisor
↓
ChatClient
↓
LLM
十三、MemoryAdvisor.java
这个类的职责就是:
把 ChatMemory 接入 AI 调用流程。
如果你使用 Spring AI 自带的:
java
MessageChatMemoryAdvisor
其实你甚至可以不自己写:
text
MemoryAdvisor.java
例如直接:
java
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
所以:
MemoryAdvisor.java是项目架构中的一个"业务封装类",不是 Spring AI 强制要求的类。
十四、为什么还要自己封装 MemoryAdvisor?
假设你希望统一处理:
text
conversationId
userId
历史消息数量
异常处理
日志
你可以自己封装:
java
@Component
public class MemoryAdvisor {
private final ChatMemory chatMemory;
public MemoryAdvisor(ChatMemory chatMemory) {
this.chatMemory = chatMemory;
}
// 自己的统一逻辑
}
例如:
text
用户ID
+
Conversation ID
+
ChatMemory
组合成你自己的上下文体系。
十五、接下来是 VectorStoreConfig
这个是 RAG 的核心基础设施配置。
先理解:
text
VectorStore
它负责:
保存向量,并进行相似度搜索。
十六、为什么需要 VectorStore?
假设你有:
text
10000篇雅思文章
5000份课程资料
100000道题目
用户问:
判断题为什么不能根据常识判断?
你不可能:
text
把所有资料发给AI
所以:
text
用户问题
↓
VectorStore
↓
找最相关的5条
↓
交给LLM
十七、VectorStoreConfig 负责什么?
它主要负责:
text
VectorStore
↓
Embedding Model
↓
Vector Database
例如:
text
Spring AI
↓
VectorStore
↓
PGVector
或者:
text
Spring AI
↓
VectorStore
↓
Milvus
或者其他 Spring AI 支持的向量存储。
十八、为什么还需要 EmbeddingModel?
因为:
text
文字
不能直接进行向量相似度搜索。
需要:
text
文字
↓
Embedding Model
↓
向量
例如:
text
"如何提高雅思阅读速度"
变成:
text
[0.123, 0.521, -0.421, ...]
所以:
text
VectorStoreConfig
通常会涉及:
text
EmbeddingModel
+
VectorStore
十九、一个概念上的配置
例如:
java
@Configuration
public class VectorStoreConfig {
@Bean
public VectorStore vectorStore(
EmbeddingModel embeddingModel
) {
return new SomeVectorStore(
embeddingModel
);
}
}
具体实现取决于你选择:
text
PGVector
Milvus
Redis
Elasticsearch
Qdrant
Chroma
...
这里不要死记某个具体 Bean,因为不同 Spring AI 版本和不同向量数据库 Starter 的自动配置方式会不同。
你真正需要记的是:
text
EmbeddingModel
↓
VectorStore
↓
RAG
二十、接下来是 RagAdvisor.java
这个和 MemoryAdvisor 非常像。
它的职责:
把 RAG 检索能力接入 ChatClient。
例如:
text
用户:
什么是NOT GIVEN?
普通:
text
User
↓
LLM
RAG:
text
User
↓
RAG Advisor
↓
VectorStore
↓
找到相关资料
↓
加入上下文
↓
LLM
二十一、RagAdvisor具体干什么?
比如:
java
@Component
public class RagAdvisor {
private final VectorStore vectorStore;
public RagAdvisor(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public List<Document> search(String query) {
return vectorStore.similaritySearch(
SearchRequest.builder()
.query(query)
.topK(5)
.build()
);
}
}
它主要负责:
text
接收问题
↓
查询 VectorStore
↓
返回相关 Document
二十二、但这里有一个重要区别
严格来说:
text
RagAdvisor
也可以直接使用 Spring AI 已经提供的 RAG Advisor。
也就是说,你不一定要自己写:
text
RagAdvisor.java
Spring AI 已经提供了相应的 RAG 抽象和实现。
我们自己创建:
text
RagAdvisor.java
主要是为了:
text
业务封装
自定义检索
权限过滤
metadata过滤
不同知识库
日志
二十三、例如你有两个知识库
text
IELTS
├── 阅读知识库
├── 写作知识库
└── 口语知识库
用户问:
如何提高阅读判断题?
你不能让它搜索:
text
所有知识库
而应该:
text
knowledgeType = READING
然后:
java
SearchRequest.builder()
.query(query)
.filterExpression(
"knowledgeType == 'READING'"
)
.topK(5)
.build();
这时候自己封装 RagAdvisor 就非常有意义。
二十四、接下来是 KnowledgeImporter
这个类非常容易被忽略。
它负责:
把你的原始知识资料导入向量数据库。
比如:
text
PDF
Word
Markdown
数据库
网页
课程资料
最终:
text
原始资料
↓
读取
↓
切分
↓
Document
↓
Embedding
↓
VectorStore
二十五、为什么需要切分?
例如一个 PDF:
text
雅思阅读技巧大全.pdf
可能有:
text
300页
你不能直接:
text
PDF
↓
一个Document
因为太大。
通常需要:
text
PDF
↓
Document
↓
Chunk
例如:
text
Chunk 1
雅思阅读主旨题...
Chunk 2
判断题...
Chunk 3
NOT GIVEN...
Chunk 4
匹配题...
二十六、KnowledgeImporter 的完整职责
你可以把它理解成:
text
KnowledgeImporter
│
├── 读取文件
│
├── 文档解析
│
├── 文本切分
│
├── 添加Metadata
│
├── Embedding
│
└── 写入VectorStore
二十七、一个具体例子
假设:
text
/resources/knowledge/reading/not-given.md
内容:
text
NOT GIVEN题主要考察...
Importer:
java
@Component
public class KnowledgeImporter {
private final VectorStore vectorStore;
public KnowledgeImporter(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public void importKnowledge(String content) {
Document document = new Document(content);
document.getMetadata().put(
"subject",
"reading"
);
vectorStore.add(
List.of(document)
);
}
}
最终:
text
Document
│
├── content
│
└── metadata
└── subject=reading
进入:
text
VectorStore
二十八、Metadata 非常重要
这是以后做企业级 RAG 必须掌握的东西。
例如:
text
Document
不仅保存:
text
content
还可以保存:
json
{
"subject": "reading",
"type": "course",
"courseId": 1001,
"teacherId": 2001,
"level": "6.5",
"language": "en"
}
这样用户:
阅读 6 分应该学习什么?
可以过滤:
text
subject = reading
level = 6.0
再做语义搜索。
二十九、最后一个:KnowledgeSearchService
这个类和 KnowledgeImporter 正好相反。
KnowledgeImporter
负责:
写进去
KnowledgeSearchService
负责:
查出来
所以:
text
KnowledgeImporter
↓
VectorStore
↑
KnowledgeSearchService
三十、KnowledgeSearchService 具体做什么?
例如:
java
@Service
public class KnowledgeSearchService {
private final VectorStore vectorStore;
public KnowledgeSearchService(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public List<Document> search(String query) {
return vectorStore.similaritySearch(
SearchRequest.builder()
.query(query)
.topK(5)
.build()
);
}
}
调用:
java
List<Document> docs =
knowledgeSearchService.search(
"为什么NOT GIVEN不能根据常识判断?"
);
得到:
text
Document 1
Document 2
Document 3
Document 4
Document 5
三十一、KnowledgeSearchService 和 RagAdvisor 又有什么区别?
这个一定要分清。
KnowledgeSearchService
负责:
"怎么搜索知识库?"
例如:
java
search(query)
RagAdvisor
负责:
"什么时候搜索,以及搜索结果怎么加入 AI 请求?"
所以:
text
用户问题
↓
RagAdvisor
↓
KnowledgeSearchService
↓
VectorStore
↓
Documents
↓
Prompt
↓
LLM
三十二、整个 RAG 链路终于完整了
现在把:
text
VectorStoreConfig
KnowledgeImporter
KnowledgeSearchService
RagAdvisor
串起来:
text
【知识入库】
│
PDF / Word / Markdown
↓
KnowledgeImporter
↓
Document切分
↓
Embedding
↓
VectorStore
│
│
↓
【用户提问】
│
↓
ChatClient
│
↓
RagAdvisor
│
↓
KnowledgeSearchService
│
↓
VectorStore
│
↓
相关Documents
│
↓
Prompt增强
│
↓
LLM
这就是完整的 RAG。
三十三、这几个类之间的关系
现在你应该能看懂整个结构:
text
ai
│
├── config
│ │
│ ├── AiConfig
│ │ ↓
│ │ 创建ChatClient
│ │
│ ├── ChatMemoryConfig
│ │ ↓
│ │ 创建ChatMemory
│ │ ↓
│ │ Redis
│ │
│ └── VectorStoreConfig
│ ↓
│ EmbeddingModel
│ ↓
│ VectorStore
│
├── advisor
│ │
│ ├── MemoryAdvisor
│ │ ↓
│ │ ChatMemory
│ │
│ └── RagAdvisor
│ ↓
│ KnowledgeSearchService
│
└── rag
│
├── KnowledgeImporter
│ ↓
│ 文档 → VectorStore
│
└── KnowledgeSearchService
↓
VectorStore → Documents
三十四、如果按照真实项目,我会稍微调整你的目录
你之前的:
text
ai
├── config
├── advisor
└── rag
已经可以。
但随着项目变大,我更推荐:
text
ai
├── config
│ ├── AiModelConfig.java
│ ├── ChatClientConfig.java
│ ├── ChatMemoryConfig.java
│ └── VectorStoreConfig.java
│
├── advisor
│ ├── MemoryAdvisor.java
│ ├── RagAdvisor.java
│ └── SecurityAdvisor.java
│
├── prompt
│ ├── reading-analysis.st
│ ├── study-plan.st
│ └── tutor.st
│
├── memory
│ └── ChatMemoryService.java
│
├── rag
│ ├── KnowledgeImporter.java
│ ├── KnowledgeSearchService.java
│ ├── DocumentProcessor.java
│ └── DocumentChunker.java
│
├── tool
│ ├── ScoreTools.java
│ ├── WrongQuestionTools.java
│ └── StudyPlanTools.java
│
└── service
└── AiTutorService.java
这样职责更加清晰。
三十五、最重要的是不要把这些类理解成"Spring AI规定的固定模板"
这是我特别想提醒你的。
例如:
text
AiConfig.java
ChatMemoryConfig.java
VectorStoreConfig.java
MemoryAdvisor.java
RagAdvisor.java
KnowledgeImporter.java
KnowledgeSearchService.java
这些类名都是我们自己定义的。
Spring AI 真正提供的是各种:
text
ChatClient
ChatModel
ChatMemory
Advisor
VectorStore
EmbeddingModel
Document
Tool
以及对应的实现和 Starter。
我们创建这些 Java 类,是为了把 Spring AI 的能力组织进我们自己的项目。
所以你应该理解为:
text
Spring AI提供能力
↓
我们设计项目结构
↓
Config负责组装
↓
Service负责业务
↓
Advisor负责AI调用增强
↓
RAG负责知识库
↓
Tool负责业务能力
三十六、最终你可以这样理解这几个类
| 类 | 核心职责 | 一句话理解 |
|---|---|---|
AiConfig |
创建/组装 ChatClient | AI总入口怎么配置 |
ChatMemoryConfig |
创建 ChatMemory | AI记忆存哪里 |
VectorStoreConfig |
创建 VectorStore | AI知识库存哪里 |
MemoryAdvisor |
将 Memory 接入调用链 | 每次AI调用怎么带历史 |
RagAdvisor |
将 RAG 接入调用链 | 每次AI调用怎么查知识 |
KnowledgeImporter |
知识入库 | 资料怎么放进向量库 |
KnowledgeSearchService |
知识检索 | 怎么从向量库找资料 |
最终就是:
text
ChatClient
│
┌───────┴───────┐
↓ ↓
MemoryAdvisor RagAdvisor
↓ ↓
ChatMemory KnowledgeSearch
↓ ↓
Redis VectorStore
↑
│
KnowledgeImporter
其中最关键的一条链你要记住:
text
KnowledgeImporter
↓
VectorStore
↓
KnowledgeSearchService
↓
RagAdvisor
↓
ChatClient
↓
LLM
这条链搞明白之后,你对 Spring AI RAG 的整体架构基本就真正入门了。