ChatClient 是 Spring AI 中用于与大语言模型(LLM)交互的核心 API。它采用了流式(Fluent)API 设计,非常类似于 Spring 生态中的 WebClient 或 RestClient。
以下是一个基于 Spring AI 最新特性的使用 Demo,涵盖了依赖配置、基础调用以及带参数的复杂调用。
1. 依赖配置 (Maven)
确保你的 pom.xml 中包含了 Spring AI 的相关依赖。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
2. 基础使用 Demo
在 Spring 中,推荐通过 ChatClient.Builder 来创建实例。你可以直接在 Service 或 Controller 中注入并使用。
代码实现
@RestController
public class ChatController {
private final ChatClient chatClient;
// 通过构造函数注入 Builder 并创建 ChatClient
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.call()
.content(); // 直接获取字符串内容
}
@GetMapping("/chat-complex")
public Map<String, Object> complexChat(@RequestParam String topic) {
return this.chatClient.prompt()
.system("你是一个专业的科普作家,请用幽默的语气回答。")
.user(u -> u.text("请解释什么是 {topic}").param("topic", topic))
.call()
.entity(new ParameterizedTypeReference<Map<String, Object>>() {});
// 自动将输出解析为 Map 或 POJO
}
}
3. 运行输出示例
场景 A:简单对话
请求: GET /chat?message=你是谁?
输出:
"你好!我是由 Google 训练的大语言模型。我可以帮你回答问题、编写代码、翻译文本等。有什么我可以帮你的吗?"
场景 B:结构化与系统角色 (Complex Chat)
请求: GET /chat-complex?topic=量子纠缠
输出 (JSON格式):
{
"topic": "量子纠缠",
"explanation": "想象一下你有两只神奇的袜子,当你给左脚穿上红色袜子时,远在宇宙另一端的右脚袜子瞬间也变成了红色!这就是量子纠缠,爱因斯坦曾称之为‘幽灵般的超距作用’。",
"funny_note": "如果快递也能有这种速度,我就再也不给差评了。"
}
4. 核心 API 关键点
为了让你更好地理解 ChatClient 的工作流,可以参考下面的逻辑结构:
prompt(): 开始构建一个请求。system(String): 设置系统提示词(设定 AI 的“人设”)。user(String): 设置用户输入。call(): 阻塞式调用,等待完整响应。stream(): 流式调用,返回Flux<String>(适用于打字机效果)。entity(): 强大的解析器,可以直接把 AI 的文本回复转成 Java 对象(POJO)。
在 Spring AI 的 ChatClient 体系中,prompt() 之后的链式调用非常灵活。/chat-complex 这个示例方法展示了 Fluent API 的核心能力。
为了更深入理解,我们可以将 ChatClient 的参数配置分为四个维度:消息角色 (Roles)、模板替换 (Templating)、配置参数 (Options) 和 输出处理 (Output)。
1. 消息角色参数 (Messaging Roles)
这是构建对话的基础,决定了 AI 的行为逻辑。
.system(String text): 定义 AI 的“人格”。例如:"你是一个只用 SQL 回答问题的数据库专家"。.user(String text): 用户的具体指令或问题。.messages(List<Message> messages): 如果你需要传入一段完整的历史对话上下文(Context),可以使用此方法。
2. 动态模板与参数 (Prompt Templates)
ChatClient 支持类似 SQL 占位符的操作,避免了手动拼接字符串的繁琐和风险。
.user(u -> u.text(template).param("key", value)):template: 包含占位符的字符串,如"请帮我翻译这段话到 {language}: {text}"。.param(): 自动将{language}和{text}替换为实际变量。
3. 模型运行配置 (Chat Options)
你可以针对单次请求覆盖全局配置。
.options(ChatOptions options):Temperature: 控制随机性(0.0 趋向稳定,1.0 趋向发散)。TopP / TopK: 采样策略参数。Model: 临时切换模型(例如从gpt-3.5切换到gpt-4o)。
4. 响应转换参数 (Response Transformation)
这是 ChatClient 最强大的地方,它能将 AI 的模糊文本直接“序列化”为 Java 对象。
.call(): 触发阻塞式同步调用。.stream(): 触发流式响应(返回Flux<String>)。.entity(Class<T> type): 将结果自动转为 POJO 类。.entity(new ParameterizedTypeReference<List<MyBean>>() {}): 处理复杂的泛型集合(如返回一个对象列表)。
完整参数示例代码
@GetMapping("/chat-ultra")
public ProjectInfo ultraChat(@RequestParam String projectName) {
return this.chatClient.prompt()
// 1. 系统约束
.system("你是一个高级架构师,请输出 JSON 格式的架构方案")
// 2. 动态模板
.user(u -> u.text("为项目 {name} 生成一份技术栈建议")
.param("name", projectName))
// 3. 运行参数定制
.options(OpenAiChatOptions.builder()
.withTemperature(0.7)
.withModel("gpt-4-turbo")
.build())
// 4. 自动类型转换
.call()
.entity(ProjectInfo.class);
}
输出结果预测 (ProjectInfo 对象)
如果 ProjectInfo 类包含 name, language, database 等字段,输出将是:
ProjectInfo[name="电商中台", language="Java/Spring Boot", database="PostgreSQL/Redis"]
在 Spring AI 中,要实现对话记忆(Memory),我们通常使用 Advisor(顾问/建议者)。这是 ChatClient 最优雅的设计之一,它能自动处理对话历史的存储和读取,不需要你手动去维护一个 List<Message>。
以下是如何在 ChatClient 中加入 ChatMemoryAdvisor 的完整 Demo。
1. 基础配置:引入内存存储
首先,你需要一个存储对话的地方。最简单的做法是使用内存级别的 InMemoryChatMemory。
@Configuration
public class AiConfig {
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
}
2. 使用 Advisor 的 Controller 示例
我们在创建 ChatClient 时,通过 .defaultAdvisors() 加入记忆功能。这样,AI 就能“记住”上一次你说了什么。
@RestController
public class MemoryChatController {
private final ChatClient chatClient;
public MemoryChatController(ChatClient.Builder builder, ChatMemory chatMemory) {
this.chatClient = builder
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) // 核心:加入记忆顾问
.build();
}
@GetMapping("/memo")
public String memoChat(@RequestParam String chatId, @RequestParam String message) {
return this.chatClient.prompt()
.user(message)
// 通过 ADVISOR_CHAT_ID_VALUE 指定这是哪一个人的对话 ID
.advisors(a -> a.param(AbstractChatMemoryAdvisor.CHAT_ID_SIZE, chatId))
.call()
.content();
}
}
3. 对话输出演练
记忆功能的效果通常体现在“多轮对话”中:
第一轮对话
请求: GET /memo?chatId=user123&message=你好,我是张三
输出: > "你好,张三!很高兴认识你。有什么我可以帮你的吗?"
第二轮对话(AI 记住了名字)
请求: GET /memo?chatId=user123&message=我还记得我叫什么吗?
输出: > "当然记得,你刚才告诉我你叫张三。"
4. Advisor 的工作原理
Advisor 就像是请求发送前后的一个“拦截器”:
- 发送前:它从
ChatMemory中根据chatId捞出最近的几条历史记录,塞进 Prompt 里。 - 回复后:它自动把 AI 的回复和你的新问题存回
ChatMemory。
进阶提示:持久化存储
如果你的应用重启了,InMemoryChatMemory 的数据会丢失。在生产环境下,Spring AI 支持将记忆存储在 Redis、Cassandra 或 JDBC 数据库中。你只需要更换 ChatMemory 的 Bean 实现即可:
// 伪代码示例:使用 Redis 存储
@Bean
public ChatMemory chatMemory(RedisTemplate redisTemplate) {
return new RedisChatMemory(redisTemplate);
}
如何利用 Advisor 实现“检索增强生成(RAG)”,让 AI 读取你私有的 PDF 或文档库
太棒了,这正是 Spring AI 最具威力的部分。RAG (Retrieval Augmented Generation) 让 AI 不再只是“纸上谈兵”,而是能根据你提供的私有文档(如 PDF、Markdown、Word)来回答问题。
在 Spring AI 中,实现 RAG 同样是通过 QuestionAnswerAdvisor 来完成的。
1. RAG 的核心组件
要让 AI 读懂你的文档,需要以下三个角色协同工作:
VectorStore:向量数据库(如 PGVector, Redis, Pinecone),用于存储文档的“特征”。Resource:你的原始文档(PDF 或文本)。QuestionAnswerAdvisor:拦截用户提问,先去数据库搜资料,再把资料喂给 AI。
2. 简易 RAG 代码实现
第一步:初始化向量数据库并导入文档
在实际应用中,你通常会有一个专门的 Service 来解析并上传文档。
@Service
public class DocumentService {
private final VectorStore vectorStore;
public DocumentService(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public void loadDocs(Resource pdfResource) {
// 1. 加载并拆分文档(防止单次输入太长)
TikaDocumentReader reader = new TikaDocumentReader(pdfResource);
TokenTextSplitter splitter = new TokenTextSplitter();
// 2. 写入向量数据库(此过程会自动调用 Embedding 模型将文字转为向量)
vectorStore.accept(splitter.apply(reader.get()));
}
}
第二步:在 ChatClient 中配置 Advisor
现在,你的 AI 就拥有了“查资料”的能力。
@RestController
public class RagController {
private final ChatClient chatClient;
public RagController(ChatClient.Builder builder, VectorStore vectorStore) {
this.chatClient = builder
// 核心:注入 QA 顾问,它会自动根据用户问题去 vectorStore 检索
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
.build();
}
@GetMapping("/ask-docs")
public String ask(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.call()
.content();
}
}
3. RAG 运行原理与输出
当用户发送请求时,底层发生的事情如下:
运行输出示例
假设你上传了一份关于“公司 2026 年报”的 PDF。
请求: GET /ask-docs?message=公司去年的利润是多少?
AI 的思考过程(隐式):
- 检索:从向量数据库中找到相关段落:“...2025年度净利润为 1.2 亿人民币...”
- 增强:将该段落作为上下文,重写 Prompt 给 LLM。
- 生成:输出最终答案。
最终输出:
"根据您提供的 2026 年报资料,公司在 2025 年度的净利润为 1.2 亿人民币,较前一年增长了 15%。"
4. 为什么选择这种方式?
- 解耦:你不需要在业务逻辑里写“搜索”代码。
- 低成本:只在需要时检索相关片段,而不是把整个 PDF 发给 AI,节省 Token。
- 动态更新:只要往
VectorStore里新增文档,AI 的知识库就会实时更新。
总结:你的 ChatClient 技能树
目前我们已经解锁了:
- Fluent API:优雅的链式调用。
- Structured Output:自动转 POJO。
- Memory Advisor:多轮对话记忆。
- QA Advisor (RAG):基于私有知识库回答。