
在LLM(大语言模型)时代,如何让AI既拥有通用能力又具备专业知识?RAG技术给出了完美答案。本文将基于Spring AI生态,深入剖析RAG的核心原理、实现细节与优化策略。
在深入了解RAG之前,我们需要先认识传统LLM的局限性:
缺陷类型 | 具体表现 | RAG解决方案 |
|---|---|---|
知识截止 | 模型知识有截止日期,无法获取最新信息 | 实时检索外部知识库 |
幻觉问题 | 自信地生成看似合理但实际错误的内容 | 基于检索到的真实信息生成 |
上下文限制 | 长文本处理能力有限 | 只检索最相关的上下文片段 |
领域专业度 | 通用模型缺乏垂直领域深度知识 | 外挂专业领域知识库 |
比喻理解:如果将LLM比作一个"高中毕业生",那么:
RAG的工作流程可以分为两大阶段:离线索引(Indexing) 和 在线检索生成(Retrieval & Generation)。
RAG论⽂:https://arxiv.org/pdf/2312.10997

中文版本:

详细流程说明:
什么是Embedding? Embedding是将文本、图像等转换为高维向量的技术。语义相似的文本在向量空间中的距离更近。
例如,⼆维空间中的向量可以表示为 (𝑥,𝑦),即表示从原点 (0,0) 到点 (𝑥,𝑦) 的有向线段

Embeddings"这个多少钱" → [0.1, 0.5, -0.7, ..., 0.25]
"这个什么价格" → [0.12, 0.4, -0.3, ..., 0.3] ← 距离近(语义相似)
"给我报个价" → [0.15, 0.3, -0.2, ..., 0.3] ← 距离近(语义相似)
"我想要这个" → [0.8, -0.1, 0.4, ..., -0.1] ← 距离远(语义不同)
相似度计算方法:
具体选哪种方法?

依赖配置(Maven):
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-openai</artifactId>
</dependency>
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.2.10</version>
</dependency>配置信息:
# OpenAI配置(用于Embedding和Chat)
spring.ai.openai.api-key=your-api-key
spring.ai.openai.embedding.api-key=your-api-key
spring.ai.openai.embedding.base-url=https://api.openai-hk.com
# Milvus配置
milvus.host=localhost
milvus.port=19530public void createCollection() {
List<FieldType> fieldTypes = Arrays.asList(
// 主键ID
FieldType.newBuilder()
.withName("id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(true)
.build(),
// 向量字段(1536维,对应text-embedding-3-small)
FieldType.newBuilder()
.withName("feature")
.withDataType(DataType.FloatVector)
.withDimension(1536)
.build(),
// 原始文本
FieldType.newBuilder()
.withName("instruction")
.withDataType(DataType.VarChar)
.withMaxLength(65535)
.build(),
// 答案内容
FieldType.newBuilder()
.withName("output")
.withDataType(DataType.VarChar)
.withMaxLength(65535)
.build()
);
CreateCollectionParam createParam = CreateCollectionParam.newBuilder()
.withCollectionName("springai_rag")
.withFieldTypes(fieldTypes)
.build();
client.createCollection(createParam);
// 创建IVF_FLAT索引,加速检索
CreateIndexParam indexParam = CreateIndexParam.newBuilder()
.withCollectionName("springai_rag")
.withFieldName("feature")
.withIndexType(IndexType.IVF_FLAT)
.withMetricType(MetricType.L2)
.build();
client.createIndex(indexParam);
}@Autowired
private EmbeddingModel embeddingModel;
public void insertData(List<FaqItem> items) {
for (FaqItem item : items) {
// 1. 文本向量化
float[] embeddings = embeddingModel.embed(item.getInstruction());
// 2. 构建插入参数
List<InsertParam.Field> fields = new ArrayList<>();
fields.add(new InsertParam.Field("feature",
Collections.singletonList(embeddings)));
fields.add(new InsertParam.Field("instruction",
Collections.singletonList(item.getInstruction())));
fields.add(new InsertParam.Field("output",
Collections.singletonList(item.getOutput())));
// 3. 插入Milvus
InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName("springai_rag")
.withFields(fields)
.build();
client.insert(insertParam);
}
}@Test
void ragTest() {
String userQuestion = "预算8000元以内,适合深度学习的笔记本电脑推荐";
// 1. 向量化查询
float[] queryEmbedding = embeddingModel.embed(userQuestion);
// 2. 向量检索Top-3
SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName("springai_rag")
.withMetricType(MetricType.L2)
.withTopK(3)
.withVectors(Collections.singletonList(queryEmbedding))
.withVectorFieldName("feature")
.withOutFields(Arrays.asList("instruction", "output"))
.build();
SearchResults results = client.search(searchParam).getData();
List<RowRecord> records = new SearchResultsWrapper(results).getRowRecords();
// 3. 构建上下文
StringBuilder context = new StringBuilder();
for (RowRecord record : records) {
context.append("Q: ").append(record.get("instruction")).append("\n");
context.append("A: ").append(record.get("output")).append("\n\n");
}
// 4. 构建增强Prompt
String enhancedPrompt = String.format("""
# 角色设定
你是资深数码产品顾问,擅长根据用户需求推荐笔记本电脑。
请严格基于以下商品库信息回答,不要推荐库中不存在的商品。
# 商品库信息(Top-3匹配结果):
%s
# 用户需求:
%s
# 回答要求:
1. 先分析用户需求(用途、预算、性能要求)
2. 从商品库中推荐1-2款最合适的,说明理由
3. 如果商品库中没有匹配项,请明确告知"暂无符合要求的商品"
4. 最后可简要补充选购建议(非强制)
""", context, userQuestion);
// 5. 调用LLM生成
String answer = chatClient.prompt(enhancedPrompt).call().content();
System.out.println(answer);
}文本分割是影响RAG效果的关键因素,常用策略对比:
分割策略 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
固定字符数 | 每N个字符切分 | 简单高效 | 可能切断语义 | 对上下文连续性要求不高的场景 |
滑动窗口 | 固定大小+重叠部分 | 保留上下文衔接 | 数据冗余,存储增加 | 需要连贯性的长文档 |
递归字符 | 按标点优先级切分(先句号→逗号→空格) | 保持语义完整 | 实现复杂 | 专业文档、论文 |
基于语义 | 使用NLP模型判断语义边界 | 最优语义保留 | 计算成本高 | 高精度要求的知识库 |
最佳实践:
1. 混合检索(Hybrid Search) 结合关键词检索(BM25)和向量检索,兼顾精确匹配和语义理解:
// 伪代码示例
List<Document> keywordResults = keywordSearch(query);
List<Document> vectorResults = vectorSearch(query);
List<Document> hybridResults = rerank(merge(keywordResults, vectorResults));2. 重排序(Reranking) 使用专门的交叉编码器(Cross-Encoder)模型对Top-K结果进行精排:
3. 查询重写(Query Rewriting) 对用户的模糊查询进行扩展和澄清:
基于《Seven Failure Points When Engineering a Retrieval Augmented Generation System》的总结:
痛点类别 | 具体表现 | 解决方案 |
|---|---|---|
Missing Content | 知识库中没有答案 | 设置兜底回复;持续扩充知识库 |
Missed Top Ranked | 正确答案未进入Top-K | 调整similarity阈值;引入重排序 |
Not in Context | 检索到内容但与问题无关 | 优化文本分割;提升向量模型质量 |
Wrong Format | 需要JSON但返回了文本 | Prompt中明确输出格式示例;使用结构化输出 |
Incomplete | 答案只覆盖问题部分 | 引导模型逐步思考(CoT);拆分复杂问题 |
Not Extracted | 上下文中有答案但模型未提取 | 优化Prompt模板;增强上下文标识 |
RAGAS(Retrieval-Augmented Generation Assessment)是评估RAG系统的标准框架,核心指标:
评估数据集格式:
{
"question": "预算8000元以内,适合深度学习的笔记本电脑推荐",
"contexts": [
"机械革命翼龙15 Pro配备RTX4060显卡,支持CUDA加速,售价7499元",
"联想拯救者Y7000P 2024款搭载i7-14650HX和RTX4060,售价8499元超出预算",
"轻薄本不适合深度学习,缺乏独立显卡"
],
"answer": "推荐机械革命翼龙15 Pro,配备RTX4060显卡支持CUDA加速,价格7499元符合预算",
"ground_truths": ["机械革命翼龙15 Pro,RTX4060,7499元"]
}选择RAG的场景:
选择Fine-tuning的场景:
参考资料:
通过本文,你应该已经掌握了RAG技术的完整链路:从文档切分、向量化存储,到相似度检索和Prompt增强。在实际项目中,建议先从简单的关键字检索+向量混合方案开始,逐步引入重排序、查询重写等优化策略。