Spring AI 不同业务场景是否需要创建不同的ChatClient
Spring 的 Bean 管理 + 多个 ChatClient 实例如何区分。
假设你在 AiChatConfig 里面创建:
@Bean
public ChatClient tutorChatClient(...) {
...
}
@Bean
public ChatClient writingChatClient(...) {
...
}
@Bean
public ChatClient readingChatClient(...) {
...
}
那么 Spring 容器里就有 3 个 ChatClient Bean。
这时候如果你业务里直接:
private final ChatClient chatClient;
Spring 就不知道该注入哪一个。
所以核心就是:
给不同的 ChatClient 一个明确的 Bean 名称,然后在业务层使用
@Qualifier指定。
一、最直接的方式:@Qualifier
例如你的 AiChatConfig:
@Configuration
public class AiChatConfig {
@Bean
public ChatClient tutorChatClient(
ChatClient.Builder builder,
ChatMemory chatMemory
) {
return builder
.defaultSystem("""
你是一名专业的雅思AI教师。
负责帮助学生制定学习方案、分析成绩和回答学习问题。
""")
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
.build();
}
@Bean
public ChatClient writingChatClient(
ChatClient.Builder builder,
ChatMemory chatMemory
) {
return builder
.defaultSystem("""
你是一名专业的雅思写作老师。
负责分析学生作文并给出详细修改建议。
""")
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
.build();
}
@Bean
public ChatClient readingChatClient(
ChatClient.Builder builder,
ChatMemory chatMemory
) {
return builder
.defaultSystem("""
你是一名专业的雅思阅读老师。
负责分析阅读文章、题目和错题。
""")
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
.build();
}
}
现在 Spring 容器里:
tutorChatClient
writingChatClient
readingChatClient
三个 Bean。
二、业务层指定使用哪个
比如:
@Service
public class WritingAiService {
private final ChatClient writingChatClient;
public WritingAiService(
@Qualifier("writingChatClient")
ChatClient writingChatClient
) {
this.writingChatClient = writingChatClient;
}
public String analyze(String essay) {
return writingChatClient
.prompt()
.user(essay)
.call()
.content();
}
}
这里:
@Qualifier("writingChatClient")
就是告诉 Spring:
我要的不是随便一个
ChatClient,我要名字叫writingChatClient的那个。
三、Tutor 也是一样
@Service
public class TutorAiService {
private final ChatClient tutorChatClient;
public TutorAiService(
@Qualifier("tutorChatClient")
ChatClient tutorChatClient
) {
this.tutorChatClient = tutorChatClient;
}
public String chat(String message) {
return tutorChatClient
.prompt()
.user(message)
.call()
.content();
}
}
所以最终关系就是:
AiChatConfig
│
├── tutorChatClient
│ ↓
│ TutorAiService
│
├── writingChatClient
│ ↓
│ WritingAiService
│
└── readingChatClient
↓
ReadingAiService
四、如果使用 Lombok 要注意
如果你写:
@RequiredArgsConstructor
@Service
public class WritingAiService {
@Qualifier("writingChatClient")
private final ChatClient chatClient;
}
一般也可以通过构造器注入场景使用限定符,但为了让代码非常明确,我更推荐你在多 Bean 场景下直接写构造器:
public WritingAiService(
@Qualifier("writingChatClient")
ChatClient writingChatClient
) {
this.writingChatClient = writingChatClient;
}
尤其是你现在正在学习 Spring AI,这样最容易理解 Spring 到底做了什么。
五、其实还可以使用 @Primary
假设:
@Bean
public ChatClient tutorChatClient(...) {}
@Bean
public ChatClient writingChatClient(...) {}
@Bean
public ChatClient readingChatClient(...) {}
你可以指定:
@Primary
@Bean
public ChatClient tutorChatClient(...) {
...
}
那么:
private final ChatClient chatClient;
默认就会选择:
tutorChatClient
但是:
@Qualifier("writingChatClient")
依然可以强制选择:
writingChatClient
六、不过我不太推荐大量使用 @Primary
因为你的 AI 场景越来越多之后:
Tutor
Writing
Reading
Speaking
Planning
Knowledge
Exam
你会发现:
@Primary
并不能解决核心问题。
真正应该表达的是:
这个 Service 到底使用哪个 AI。
所以:
@Qualifier
更清晰。
七、但是这里还有一个更重要的问题
你刚才问的是:
如果都创建在
AiChatConfig里,那我在具体业务创建实例的时候怎么正确创建对应的实例?
这里其实有两种完全不同的架构。
方案一:多个 ChatClient Bean
就是我们刚才说的:
AiChatConfig
│
├── tutorChatClient
├── writingChatClient
├── readingChatClient
└── planningChatClient
业务:
TutorAiService
↓
@Qualifier("tutorChatClient")
WritingAiService
↓
@Qualifier("writingChatClient")
这个方案非常简单。
八、方案二:只创建一个 ChatClient
其实我更建议你不要为了不同业务场景,就一定创建多个 ChatClient。
因为:
ChatClient
本身并不是:
“一个 AI 专家实例”。
它更像:
对底层 ChatModel 的调用入口 + 一些默认配置。
真正变化的可能只是:
System Prompt
Advisor
Tool
Response Format
所以你完全可以:
@Bean
public ChatClient chatClient(
ChatClient.Builder builder
) {
return builder.build();
}
然后:
写作
chatClient
.prompt()
.system("你是一名雅思写作老师")
.user(essay)
.call();
阅读
chatClient
.prompt()
.system("你是一名雅思阅读老师")
.user(question)
.call();
学习规划
chatClient
.prompt()
.system("你是一名雅思学习规划老师")
.tools(studyPlanTools)
.user(message)
.call();
这时候:
一个 ChatClient
│
├── 写作场景
├── 阅读场景
├── 学习规划
└── AI Tutor
九、那么什么时候应该创建多个 ChatClient?
这个问题非常重要。
我给你一个简单判断。
如果只是 System Prompt 不一样
没必要创建多个 ChatClient。
直接:
.prompt()
.system(...)
如果只是 Tool 不一样
通常也可以:
.tools(...)
如果只是 Advisor 不一样
也可以:
.advisors(...)
如果默认能力完全不同
这时候可以创建多个 ChatClient。
例如:
TutorChatClient
Memory
RAG
ScoreTools
StudyPlanTools
WritingChatClient
Memory
WritingRAG
WritingCorrectionTools
这种情况下创建两个 ChatClient 就比较合理。
十、所以我比较推荐你的 IELTS 项目这样设计
不要:
ChatClient1
ChatClient2
ChatClient3
ChatClient4
ChatClient5
ChatClient6
而是:
ChatModel
│
↓
ChatClient
│
┌───────────────────┼───────────────────┐
↓ ↓ ↓
Tutor Writing Reading
│ │ │
Memory + RAG Memory + RAG Memory + RAG
ScoreTools WritingTools ReadingTools
绝大多数场景:
chatClient
.prompt()
.system(...)
.advisors(...)
.tools(...)
.call();
就够了。
十一、如果你真的有多个 ChatClient,我建议使用“配置类 + Qualifier”
例如:
@Configuration
public class AiChatConfig {
@Bean("tutorChatClient")
public ChatClient tutorChatClient(
ChatClient.Builder builder,
ChatMemory chatMemory
) {
return builder
.defaultSystem("你是一名雅思AI导师")
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
.build();
}
@Bean("writingChatClient")
public ChatClient writingChatClient(
ChatClient.Builder builder,
ChatMemory chatMemory
) {
return builder
.defaultSystem("你是一名雅思写作老师")
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
.build();
}
}
然后:
@Service
public class WritingAiService {
private final ChatClient chatClient;
public WritingAiService(
@Qualifier("writingChatClient")
ChatClient chatClient
) {
this.chatClient = chatClient;
}
}
十二、如果以后有十几个 ChatClient 呢?
这时候我反而不建议继续:
AiChatConfig.java
全部堆在一起。
可以拆成:
ai
├── config
│ ├── AiModelConfig.java
│ ├── TutorChatConfig.java
│ ├── WritingChatConfig.java
│ ├── ReadingChatConfig.java
│ └── PlanningChatConfig.java
例如:
@Configuration
public class WritingChatConfig {
@Bean("writingChatClient")
public ChatClient writingChatClient(
ChatClient.Builder builder,
ChatMemory chatMemory
) {
return builder
.defaultSystem("""
你是一名雅思写作专家。
""")
.defaultAdvisors(
MessageChatMemoryAdvisor
.builder(chatMemory)
.build()
)
.build();
}
}
这样比一个几百行的:
AiChatConfig.java
更容易维护。
十三、再进一步:如果你想让业务完全不知道 Bean 名称
可以再封装一层:
AiChatClientFactory
例如:
@Component
public class AiChatClientFactory {
private final ChatClient tutorChatClient;
private final ChatClient writingChatClient;
public AiChatClientFactory(
@Qualifier("tutorChatClient")
ChatClient tutorChatClient,
@Qualifier("writingChatClient")
ChatClient writingChatClient
) {
this.tutorChatClient = tutorChatClient;
this.writingChatClient = writingChatClient;
}
public ChatClient tutor() {
return tutorChatClient;
}
public ChatClient writing() {
return writingChatClient;
}
}
业务:
@Service
public class WritingAiService {
private final AiChatClientFactory factory;
public WritingAiService(AiChatClientFactory factory) {
this.factory = factory;
}
public String analyze(String essay) {
return factory
.writing()
.prompt()
.user(essay)
.call()
.content();
}
}
十四、但是这里我给你一个更重要的建议
不要为了“看起来高级”而创建大量 ChatClient。
你现在学习 Spring AI,最好把这几个层次分清楚:
ChatModel
↓
ChatClient
↓
Prompt
↓
Advisor
↓
Tool
其中:
ChatModel
负责:
真正调用大模型。
ChatClient
负责:
提供一个方便的 AI 调用入口,并承载默认配置。
Prompt
负责:
这一次到底告诉 AI 什么。
Advisor
负责:
在调用过程中自动增加 Memory、RAG 等能力。
Tool
负责:
允许 AI 调用你的业务系统。
所以很多时候:
一个 ChatClient
+
不同 Prompt
+
不同 Advisor
+
不同 Tool
已经能够覆盖大量业务。
十五、你可以按照这个标准决定
| 情况 | 推荐 |
|---|---|
| System Prompt 不同 | 一个 ChatClient |
| 用户消息不同 | 一个 ChatClient |
| Tool 不同 | 一个 ChatClient + .tools() |
| RAG 不同 | 一个 ChatClient + .advisors() |
| Memory 不同 | 可以使用不同 Advisor/Memory |
| 模型不同 | 多个 ChatClient / Model 配置 |
| 默认 Tool 完全不同 | 可以多个 ChatClient |
| 默认 Advisor 完全不同 | 可以多个 ChatClient |
| 模型参数完全不同 | 可以多个 ChatClient |
最后给你一个最实用的架构
如果是你现在的 IELTS 项目,我建议:
ChatModel
│
↓
ChatClient
│
┌──────────────┼──────────────┐
↓ ↓ ↓
Prompt Advisor Tool
│ │ │
│ ┌───┴───┐ ┌──┴────┐
│ ↓ ↓ ↓ ↓
│ Memory RAG Score StudyPlan
│
┌────┼────────────┐
↓ ↓ ↓
写作 阅读 学习规划
也就是说,先不要把“不同业务 = 不同 ChatClient”画等号。
真正合理的是:
一个 ChatClient
+
场景化 Prompt
+
场景化 Advisor
+
场景化 Tools
只有当某个场景的默认能力、模型、参数、工具体系都和其他场景明显不同时,再创建:
@Bean("writingChatClient")
@Bean("tutorChatClient")
然后通过:
@Qualifier("writingChatClient")
注入。
这也是为什么你前面看到我说的:
chatClient
.prompt()
.system(...)
.advisors(...)
.tools(...)
.call();
其实并不是“每个业务都创建一个 ChatClient”,而是一个 ChatClient 可以通过 Prompt / Advisor / Tool 在调用时组装成不同的 AI 能力。
更多推荐

所有评论(0)