Embedding 与 RAG
大约 2 分钟
第十一章:Embedding 与 RAG:把知识库接进来
11.1 RAG 的目标:减少幻觉,而不是“让模型更聪明”
RAG 的核心流程是:
- 文档切片
- 用 embedding 生成向量
- 存入向量库(EmbeddingStore/Vector DB)
- 查询时相似度检索 TopK
- 把 TopK 片段拼进 prompt,再让模型回答
你真正要控制的不是“检索算法”,而是三件事:
- 上下文总长度(别把大段资料喂进去)
- 片段来源(让答案可追溯)
- 检索阈值(相似度太低就别硬答)
11.2 最小可运行:InMemoryEmbeddingStore + OpenAI Embedding
11.2.1 依赖(示例)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>1.12.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
</dependencies>11.2.2 代码:构建检索器并回答
package com.example.langchain4j.rag;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import java.util.List;
public class RagInMemoryExample {
public static void main(String[] args) {
String apiKey = System.getenv("OPENAI_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("请先设置环境变量 OPENAI_API_KEY");
}
ChatLanguageModel chatModel = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.temperature(0.1)
.build();
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-small")
.build();
EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
List<TextSegment> segments = List.of(
TextSegment.from("开票规则:订单完成后 30 天内可申请电子发票。"),
TextSegment.from("退款规则:7 天无理由,原路退回,1~3 个工作日到账。"),
TextSegment.from("物流规则:默认顺丰,48 小时内发货。")
);
for (TextSegment segment : segments) {
store.add(embeddingModel.embed(segment).content(), segment);
}
var retriever = EmbeddingStoreContentRetriever.builder()
.embeddingModel(embeddingModel)
.embeddingStore(store)
.maxResults(3)
.build();
String question = "开票多久还能申请?";
var contents = retriever.retrieve(question);
String context = contents.stream()
.map(c -> c.textSegment().text())
.reduce((a, b) -> a + "\n---\n" + b)
.orElse("");
String answer = chatModel.chat("""
你是企业知识库助手。
规则:只根据【上下文】回答;如果上下文没有答案,请说“不确定”。
【上下文】
%s
【问题】
%s
""".formatted(context, question));
System.out.println(answer);
}
}11.3 生产落地建议
- 文档切片要有标题/来源(链接/文档编号),方便追溯
- TopK 不宜太大:通常 3~8 就够,关键是控制总长度
- 低相似度要拒答:宁可说不确定,也不要硬答
- 工具与 RAG 组合:RAG 不足时再调用工具(第 9、14 章会组合)
11.4 本章小结
你已经做出了最小可运行的 RAG:embedding → 向量存储 → 检索 → 拼上下文 → 生成回答。下一章我们做体验升级:流式输出(SSE),同时把取消、断连、心跳这些工程问题一次讲清楚。
