小崔看完上节手动 RAG 代码后吐槽:"每个查询都要写四步,这也太麻烦了。"白歌笑了笑:"所以说框架存在的意义就是消灭样板代码——一个 Advisor 搞定全部。"
QuestionAnswerAdvisor 详解
定义与作用
QuestionAnswerAdvisor 是 Spring AI 内置的 RAG Advisor,自动完成"检索 → 拼上下文 → 注入 Prompt"全流程。只需将 VectorStore 传入构造器并注册到 ChatClient,每次调用自动执行 RAG。
new QuestionAnswerAdvisor(vectorStore)
// 一行代码启动 RAG,背后是三阶段自动化
对比手动实现:
| 维度 | 手动 RAG | QuestionAnswerAdvisor |
|---|---|---|
| 检索 | 手动调 VectorStore | 自动执行 |
| Prompt 拼接 | 手写字符串模板 | 内置默认模板 + 可自定义 |
| 上下文注入 | 手动 String.format() | Advisor 链中自动注入 |
| 与 Advisors 配合 | 需手动管理顺序 | 自然融入 Advisor 洋葱模型 |
核心原理:自动注入流程
图释:QuestionAnswerAdvisor 在 adviseRequest 阶段拦截请求,自动检索 VectorStore → 拼接上下文 → 注入 Prompt,然后交给 ChatModel 生成回答。
完整示例一:最简 RAG 实现
场景说明
孔蓝要求"在现有聊天接口上加上公司知识库,不要改太多代码"。
关键代码
@Configuration
public class RagConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder,
VectorStore vectorStore) {
return builder
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
.build();
}
}
@RestController
public class StudentSystemController {
private final ChatClient chatClient;
public StudentSystemController(ChatClient chatClient) {
this.chatClient = chatClient;
}
// 一句代码接入 RAG
@GetMapping("/ask")
public String ask(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
运行结果
GET /ask?question=飞翔科技的办公时间是几点到几点?
响应:
「根据飞翔科技员工手册第二章:
- 标准办公时间:9:00 - 18:00(午休 12:00 - 13:30)
- 弹性工作制部门:核心时段 10:00 - 16:00 需在岗
- 加班需提前报备并由部门主管审批」
关键观察:Controller 代码与普通的 ChatClient 调用没有任何区别,但回答却基于了公司内部文档——这就是 Advisor 无侵入集成的优势。
完整示例二:自定义 RAG Prompt 模板
场景说明
高英发现默认 Prompt 模板不符合运营场景的需求——她需要 AI 回复时附带文档页码来源。
关键代码
@Configuration
public class CustomRagConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder,
VectorStore vectorStore) {
QuestionAnswerAdvisor advisor = new QuestionAnswerAdvisor(vectorStore);
// 自定义 Prompt 模板
advisor.setPromptTemplate("""
你是飞翔科技的管理助手。
【必须严格遵守以下规则】
1. 仅基于以下文档片段回答问题
2. 每条回答末尾标注来源页码(如 [员工手册-P23])
3. 如果文档中没有相关信息,回复「此问题超出我的知识范围」
相关文档:
{context}
用户问题:{question}
回答:
""");
return builder
.defaultAdvisors(advisor)
.build();
}
}
运行结果
GET /ask?question=怎么申请报销?
响应:
「报销流程如下:
1. 填写《费用报销单》并附发票原件
2. 直属主管签字审批
3. 财务部审核(3 个工作日内)
4. 金额 >5000 元需副总加签
[员工手册-P47]」
↑ 附带页码来源,高英可以直接验证
易错场景与面试考点
易错场景一:VectorStore 为 null 导致 NPE
// ❌ 错误:VectorStore 未配置,注入为 null
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultAdvisors(new QuestionAnswerAdvisor(null)) // NPE!
.build();
}
问题分析
VectorStore 需要通过 PGVector 等具体实现来提供。如果不引入 spring-ai-pgvector-store-spring-boot-starter 依赖或未配置连接,VectorStore Bean 不会创建。
// ✅ 正确:先确保 PGVector 配置正确
// application.yml
spring:
ai:
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536
initialize-schema: true
datasource:
url: jdbc:postgresql://localhost:5432/vectordb
username: postgres
password: ${PG_PASSWORD}
面试高频题
Q1:QuestionAnswerAdvisor 与 Advisor 链的协作方式是什么?
QuestionAnswerAdvisor 实现了 RequestResponseAdvisor 接口,在 adviseRequest 阶段从 VectorStore 检索文档并注入 Prompt。它可以与其他 Advisor(如 ChatMemoryAdvisor)组合使用,执行顺序由注册顺序决定。
Q2:如何自定义 RAG Prompt 模板中的占位符?
默认模板使用 {context} 和 {question} 两个占位符。通过 advisor.setPromptTemplate() 传入自定义模板字符串,框架会自动将检索结果填充到 {context},将用户问题填充到 {question}。
本章小结
- QuestionAnswerAdvisor 自动完成 RAG 三步:检索 → 拼接 → 注入
- 通过
defaultAdvisors注册后,所有请求自动 RAG 增强 - 支持自定义 Prompt 模板(含 {context} 和 {question} 占位符)
- 务必确保 VectorStore 已正确配置,否则抛出 NPE