1. 项目概述:基于Spring AI的RAG知识库问答机器人实战
作为一名长期从事企业级应用开发的工程师,我最近完整实践了一个基于Spring AI框架的RAG知识库问答机器人项目。这个项目完美展示了如何将前沿的大语言模型技术与传统Java生态相结合,构建一个能理解专业文档的智能问答系统。不同于简单的API调用示例,这个项目实现了从文档上传、文本处理到智能问答的完整闭环,特别适合想要快速上手AI应用开发的Java开发者。
RAG(检索增强生成)技术的核心价值在于它解决了大语言模型的三个关键痛点:
- 知识更新滞后 - 通过实时检索外部知识库获取最新信息
- 幻觉问题 - 严格基于检索内容生成回答,减少编造
- 上下文窗口限制 - 只注入与问题最相关的文档片段
在医疗、法律、金融等专业领域,这种能"引经据典"的问答系统尤其有价值。比如医生可以上传最新医学指南,律师可以导入案例法条,系统都能基于这些专业文档给出准确回答。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构与核心组件
2.1 整体架构设计
这个问答机器人的架构可以分为三个主要层次:
-
数据预处理层:
- 文档解析(支持PDF/Word/TXT)
- 文本分块与向量化
- 向量存储与管理
-
核心服务层:
- 问题理解与查询重写
- 向量相似度检索
- 大模型交互与答案生成
-
应用接口层:
- 文件上传API
- 流式问答API
- 管理控制台
mermaid复制graph TD
A[用户提问] --> B[查询理解]
C[上传文档] --> D[文档解析]
D --> E[文本分块]
E --> F[向量化]
F --> G[向量存储]
B --> H[向量检索]
G --> H
H --> I[上下文构建]
I --> J[提示词工程]
J --> K[大模型调用]
K --> L[答案生成]
L --> M[流式返回]
注意:实际项目中我们移除了对Mermaid图表的依赖,改用文字描述架构,既保持专业性又避免技术债
2.2 关键技术选型
Spring AI生态
- spring-ai-advisors-vector-store:向量存储基础组件
- spring-ai-tika-document-reader:通用文档解析
- spring-ai-pdf-document-reader:专业PDF处理
- spring-ai-rag:RAG核心功能包
中文处理增强
- HanLP:中文分词与词性标注
- 自定义文本量化器:适配中文语义的向量化方案
大模型接入
- 智谱AI(GLM-4-Flash):性价比高的中文大模型
- 设计上支持快速切换其他模型(如OpenAI)
3. 核心实现细节
3.1 文档处理流水线
文档处理是RAG系统的基石,我们的流水线包含以下关键步骤:
- 文档解析:
java复制// PDF文档解析示例
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(
new ByteArrayResource(file.getBytes()),
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPagesPerDocument(1)
.build());
List<Document> documents = pdfReader.read();
- **智能分块策略:
java复制public List<String> splitText(String text) {
// 优先在句子边界分割(中文句号、感叹号等)
String[] sentences = text.split("(?<=。)|(?<=!)|(?<=?)|(?<=\\n\\n)");
// 滑动窗口算法合并短句
List<String> chunks = new ArrayList<>();
StringBuilder currentChunk = new StringBuilder();
for (String sentence : sentences) {
if (currentChunk.length() + sentence.length() <= maxChunkSize) {
currentChunk.append(sentence);
} else {
chunks.add(currentChunk.toString());
currentChunk = new StringBuilder(
sentence.substring(Math.max(0, sentence.length() - overlapSize))
);
}
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString());
}
return chunks;
}
- 向量化方案对比:
| 方案类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 大模型Embedding | 语义理解强 | 需要API调用、有成本 | 生产环境 |
| HanLP分词+TF-IDF | 纯本地运算 | 语义捕捉较弱 | 开发测试 |
| 混合方案 | 平衡成本效果 | 实现复杂 | 过渡阶段 |
3.2 检索增强实现
核心检索逻辑通过Spring AI的RetrievalAugmentationAdvisor实现:
java复制RetrievalAugmentationAdvisor.builder()
.queryTransformers(
RewriteQueryTransformer.builder()
.chatClientBuilder(builder.build().mutate())
.build()
)
.queryAugmenter(
ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build()
)
.documentRetriever(
VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build()
)
.build()
关键参数说明:
- similarityThreshold:0.5的阈值平衡了召回率与准确率
- topK:限制返回的文档片段数量(控制上下文长度)
- allowEmptyContext:允许无检索结果时直接回答(降级策略)
3.3 提示词工程
系统提示词模板设计要点:
- 明确角色定位:"你是一个严格基于文档的问答助手"
- 设定回答规则:"当答案不在文档中时,必须明确声明"
- 结构化输出要求:"分点列举关键信息"
- 风格指导:"使用专业但易懂的表达方式"
text复制给定上下文信息,请回答用户问题。遵守以下规则:
1. 仅使用提供的上下文信息
2. 保持回答简洁专业
3. 对不确定的内容明确说明
4. 避免"根据上下文"这类冗余表述
上下文:
---------------------
{question_answer_context}
---------------------
问题:{query}
4. 性能优化实践
4.1 向量检索优化
-
分层检索策略:
- 第一层:关键词快速筛选(BM25算法)
- 第二层:语义相似度精排(余弦相似度)
-
缓存机制:
java复制@Cacheable(value = "documentCache", key = "#md5")
public void processDocument(String md5, Document document) {
// 处理逻辑
}
- 批量操作:
java复制vectorStore.add(documents.stream()
.filter(doc -> !persistMd5.contains(doc.getMetadata().get("md5")))
.toList());
4.2 流式响应实现
使用Spring WebFlux实现答案的渐进式返回:
java复制@GetMapping(path = "/chat/{chatId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> qaGet(@PathVariable String chatId,
@RequestParam String question) {
return qaBolt.ask(chatId, question, Collections.emptyList())
.map(content -> content.replace("\n", "<br/>"));
}
前端通过EventSource监听:
javascript复制const eventSource = new EventSource(`/api/chat/${chatId}?question=${encodeURIComponent(question)}`);
eventSource.onmessage = (event) => {
document.getElementById('answer').innerHTML += event.data;
};
5. 生产环境考量
5.1 安全防护
- 文档上传安全:
yaml复制servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
- 内容过滤:
java复制String sanitizedInput = input.replaceAll("<script>.*?</script>", "")
.replaceAll("\\b(ALTER|DROP|DELETE)\\b", "");
- 访问控制:
java复制@PreAuthorize("#chatId == authentication.name")
public Flux<String> ask(String chatId, String question) {
// ...
}
5.2 监控指标
核心监控维度:
- 文档处理耗时(P99 < 2s)
- 问答响应时间(P95 < 5s)
- 检索准确率(>80%)
- 大模型调用成本($/request)
Prometheus配置示例:
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
6. 扩展与演进
6.1 进阶功能路线
-
多模态支持:
- 图像OCR识别
- 表格数据提取
-
混合检索策略:
- 结合关键词与向量搜索
- 查询意图分类
-
反馈学习:
- 答案质量评分
- 检索结果优化
6.2 架构演进方向
mermaid复制graph LR
A[单体应用] --> B[微服务拆分]
B --> C[独立向量数据库]
C --> D[分布式文档处理]
D --> E[多模型路由]
再次说明:实际项目中应避免使用Mermaid,此处仅为示意
具体演进步骤:
- 将向量存储迁移至专用数据库(如Milvus)
- 文档处理拆分为独立服务
- 实现多模型路由策略
- 增加异步处理队列
7. 踩坑实录
7.1 中文处理陷阱
问题现象:
- 长中文文档分块后语义断裂
- 专有名词被错误分割
解决方案:
- 调整HanLP分词配置:
java复制Segment segment = HanLP.newSegment()
.enableCustomDictionary(true)
.enablePartOfSpeechTagging(true);
- 增加专业词典:
code复制腾讯 nz 1000
Spring AI nz 1500
7.2 向量相似度波动
问题现象:
- 相同问题每次检索结果不一致
- 边界相似度文档时有时无
优化措施:
- 引入分数标准化:
java复制double normalizedScore = (score - threshold) / (1 - threshold);
- 增加检索结果后处理:
java复制List<Document> results = rawResults.stream()
.sorted(Comparator.comparing(Document::getScore).reversed())
.distinct(d -> d.getMetadata().get("chunk_id"))
.limit(topK)
.collect(Collectors.toList());
8. 项目实践建议
对于想要尝试这个项目的开发者,我的实操建议是:
-
分阶段实施:
- 第一阶段:跑通基础流程(2-3天)
- 第二阶段:优化中文处理(1周)
- 第三阶段:完善生产特性(2周)
-
硬件配置:
- 开发环境:8核CPU/16GB内存(可运行完整流程)
- 测试环境:16核CPU/32GB内存(压力测试)
- 生产环境:根据负载动态扩展
-
调试技巧:
java复制logging:
level:
org.springframework.ai: DEBUG
com.git.hui: TRACE
这个项目最让我惊喜的是Spring AI对传统Java开发者非常友好,不需要掌握Python生态就能快速构建AI应用。特别是在企业环境中,能直接复用现有的Java基础设施和运维体系,大大降低了AI应用的落地门槛。
