Spring AI 的一些配置类怎么去配置

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 必须知道:

这个 ChatClient Bean 从哪里来?

所以:

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 的整体架构基本就真正入门了。

相关推荐
AI的探索之旅1 小时前
97 个 OpenCV 实例(九):形态学操作,腐蚀膨胀与开闭运算
人工智能·opencv·计算机视觉
经济视野1 小时前
输入户型图自动生成装修方案的软件有哪些
人工智能
richard_first1 小时前
第8章 Feed Forward Network(FFN 网络)
网络·人工智能·深度学习·机器学习·transformer
pjj198541 小时前
opencv-基于 SIFT 特征和 FLANN 匹配器的图像特征点匹配与可视化
人工智能·opencv·计算机视觉
Wang's Blog1 小时前
Vibe Coding一人即团队系列2: AI驱动的全链路产品开发实践
人工智能
飞哥数智坊1 小时前
豆包工作上线:功能超多,但我更关注这3个地方
人工智能
Csvn1 小时前
第 4 章 提示工程
人工智能·aigc
小白的成长路程1 小时前
AI搜索里,品牌被悄悄降级了
人工智能·geo
这就是佬们吗1 小时前
AI 的记忆进化史:从金鱼记忆到学会做梦
人工智能·python·embedding