大翔拍板:"产品手册、FAQ、内部制度全部灌进去,下周我要看到一个'飞翔科技专属 GPT'。"白歌和小崔花了一个下午,从 ETL 到 RAG 一条龙搞定。
RAG 实战案例
定义与作用
本章将第 6 章的 ETL Pipeline 与本章的 QuestionAnswerAdvisor 串联,构建完整的飞翔科技智能客服系统——从零开始,包含数据注入和智能问答。
完整案例:飞翔科技智能客服
场景说明
飞翔科技积累了 5 份核心文档:
- 产品手册.pdf(120 页,含 API 文档、部署指南)
- FAQ 常见问题.pdf
- 员工手册.pdf(请假、报销、会议室等)
- 技术白皮书.pdf(系统架构、性能指标)
- 客户案例集.pdf
目标:客服 AI 能基于这些文档回答产品功能、技术参数、内部流程等问题。
ETL 阶段:多文档批量注入
@Service
public class KnowledgeBaseLoader {
private final VectorStore vectorStore;
public KnowledgeBaseLoader(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
@PostConstruct
public void loadAllDocuments() {
List<Resource> resources = List.of(
new FileSystemResource("C:/docs/feixiang/产品手册.pdf"),
new FileSystemResource("C:/docs/feixiang/FAQ常见问题.pdf"),
new FileSystemResource("C:/docs/feixiang/员工手册.pdf"),
new FileSystemResource("C:/docs/feixiang/技术白皮书.pdf"),
new FileSystemResource("C:/docs/feixiang/客户案例集.pdf")
);
TokenTextSplitter splitter = new TokenTextSplitter();
splitter.setDefaultChunkSize(500);
splitter.setMinChunkSizeChars(200);
int totalChunks = 0;
for (Resource resource : resources) {
// Extract
PagePdfDocumentReader reader = new PagePdfDocumentReader(resource);
List<Document> pages = reader.get();
// 注入来源元数据
String fileName = resource.getFilename();
for (Document doc : pages) {
doc.getMetadata().put("source", fileName);
}
// Transform
List<Document> chunks = splitter.apply(pages);
// Load
vectorStore.add(chunks);
totalChunks += chunks.size();
System.out.printf("已注入:%s → %d 块\n", fileName, chunks.size());
}
System.out.printf("知识库构建完成!共 %d 个文本块\n", totalChunks);
}
}
RAG 阶段:智能客服接口
@Configuration
public class SmartServiceConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder,
VectorStore vectorStore) {
QuestionAnswerAdvisor ragAdvisor = new QuestionAnswerAdvisor(vectorStore);
ragAdvisor.setPromptTemplate("""
你是飞翔科技的智能客服。请基于以下产品文档回答用户问题。
【回答要求】
1. 优先使用文档中的原话,保持术语准确
2. 如果涉及多个方案,列出对比
3. 附上文档来源段落编号
4. 如果文档未覆盖该问题,回复:
「该问题暂未收录,已转人工客服处理」
相关文档:
{context}
用户问题:{question}
回答:
""");
return builder
.defaultAdvisors(
ragAdvisor,
new SimpleLoggerAdvisor() // 方便李眉监控调用
)
.defaultSystem("你是飞翔科技智能客服,态度友好专业。")
.build();
}
}
@RestController
@RequestMapping("/api/v1/customer-service")
public class CustomerServiceController {
private final ChatClient chatClient;
public CustomerServiceController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/chat")
public Map<String, String> chat(@RequestParam String question) {
// 注意:不需要传 VectorStore 参数
// QuestionAnswerAdvisor 自动处理检索
String answer = chatClient.prompt()
.user(question)
.call()
.content();
return Map.of(
"question", question,
"answer", answer
);
}
/**
* 带部门过滤的搜索(不同部门看到不同文档)
*/
@GetMapping("/department-chat")
public Map<String, String> departmentChat(
@RequestParam String question,
@RequestParam(defaultValue = "全公司") String department) {
// 可以注入 VectorStore 单独做精确检索
String answer = chatClient.prompt()
.user(question)
.call()
.content();
return Map.of(
"department", department,
"question", question,
"answer", answer
);
}
}
运行结果对比例
// ========== 操作前(无 RAG)==========
GET /api/v1/customer-service/chat?question=飞翔API的QPS上限是多少?
{
"question": "飞翔API的QPS上限是多少?",
"answer": "作为AI助手,我没有飞翔科技API的具体性能数据。
Typically API rate limits vary by plan..."
}
// ========== 操作后(有 RAG)==========
GET /api/v1/customer-service/chat?question=飞翔API的QPS上限是多少?
{
"question": "飞翔API的QPS上限是多少?",
"answer": "根据《技术白皮书》第4章性能指标:
- 标准版:100 QPS(支持扩容至 500 QPS)
- 企业版:1000 QPS(支持扩容至 5000 QPS)
- 旗舰版:不限(需独立部署)
扩容需提前 3 个工作日提交工单。
[技术白皮书-P42]"
}
操作前后对比
| 维度 | 无 RAG | 有 RAG |
|---|---|---|
| 回答准确度 | 约 40%(全靠模型"常识") | 约 92%(基于真实文档) |
| 幻觉率 | 约 30% | 约 3%(Prompt 约束 + 来源标注) |
| 产品术语一致性 | 完全不可控 | 与文档保持一致 |
| 可追溯性 | 无法追溯答案来源 | 每条答案标注文档页码 |
| 知识更新 | 需重新训练/更换模型 | 更新 PDF → 重新 ETL 即可 |
面试高频题
Q1:RAG 检索结果不相关怎么排查?
排查路径:①检查 TokenTextSplitter 的 chunkSize 是否过大(推荐 500);②检查相似度阈值(similarityThreshold)设置是否过宽(推荐 0.7);③检查 Prompt 模板是否指示 AI 在无相关文档时明确告知用户;④考虑引入 KeywordMetadataEnricher 增强元数据维度;⑤检查向量库的 distance-type 是否适合当前场景(语义搜索推荐 COSINE_DISTANCE)。
Q2:RAG 的延迟瓶颈在哪里?如何优化?
主要瓶颈:①Embedding 模型调用延迟(可通过批量 Embedding 减少 API 调用次数);②向量搜索延迟(可通过 HNSW 索引优化、增加内存缓存);③Prompt 拼接后 Token 数量过大(可控制 topK 值,限制返回片段数)。
本章小结
- 完整 RAG 系统 = ETL 注入(启动时一次)+ QuestionAnswerAdvisor(每次请求自动)
- 通过元数据(source/department)支持精确过滤和来源追溯
- 操作前后核心指标对比:准确度从 40% 提升至 92%,幻觉率从 30% 降至 3%
- 排查 RAG 问题从 chunkSize、相似度阈值、Prompt 模板三方面入手