LangChain4j RAG

LangChain4j Easy RAG

RAG流程分为两个不同的阶段:索引和检索。LangChain4j 为这两个阶段提供了相应的工具。

索引阶段

在索引阶段,文档会被预处理,以便在检索阶段进行高效搜索。
这个过程会根据所采用的信息检索方法而有所不同。对于矢量搜索,通常包括以下步骤:清洗文档,添加额外的数据和元数据,将文档分割成小片段(又称分块),嵌入这些片段,并将它们存储在嵌入存储中(又称矢量数据库)。
索引阶段通常是离线的,这意味着用户不需要等待其完成。这可以通过例如每周周末定时任务来重新索引公司文档实现。负责索引的代码也可以是一个独立的应用,专门处理索引任务。
但是,在某些情况下,用户可能需要上传自己的自定义文档,以便LLM可以访问。这种情况下,索引应该在线进行,并且是主应用程序的一部分。

image-2025-03-15-1

检索阶段

检索阶段通常是在线发生的,当用户提交一个问题,需要使用索引文档来回答时。这个过程可能会根据所使用的信息检索方法而有所不同。对于向量搜索,这通常涉及到对用户的查询(问题)进行嵌入,并在嵌入存储中进行相似度搜索。然后,将相关的片段(原始文档的部分)注入到提示中,并发送给LLM(大型语言模型)。以下是检索阶段的简化流程图:

image-2025-03-15-2

RAG Flavours in LangChain4j

LangChain4j 提供了三种 RAG 的版本:
● Easy RAG:开始使用 RAG 的最简单方式
● Naive RAG:使用向量搜索实现的基本 RAG 实现
● Advanced RAG:一个模块化的 RAG 框架,允许进行附加步骤,如查询转换、从多个源检索以及重新排序。

Easy RAG

LangChain4j具有“简易RAG”功能,让用户能够轻松上手RAG。用户无需了解嵌入式的概念,选择向量库,寻找合适的嵌入模型,也不需要研究如何解析和分割文档等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.lixiang</groupId>
<artifactId>ex00560</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.0.0-beta3</version>
</dependency>
</dependencies>
</project>
1
2
3
4
5
package com.lixiang;

interface Assistant {
String chat(String userMessage);
}

LangChain4j Naive RAG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.lixiang</groupId>
<artifactId>ex00561</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-cohere</artifactId>
<version>1.0.0-beta3</version>
</dependency>
</dependencies>
</project>
1
2
3
4
5
package com.lixiang;

interface Assistant {
String chat(String userMessage);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.lixiang;

import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments;

import java.util.List;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;

/**
* 基于LangChain4j实现的Naive RAG(检索增强生成)示例
* 使用通义千问模型进行问答,支持从文档中检索相关信息
*/
public class NaiveRagDemo {

/**
* 主方法,创建助手并测试问答功能
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建RAG助手
Assistant assistant = createAssistant();

// 第一个问题
String input = "鲁智深做了什么?";
System.out.println("> " + input);
String chat = assistant.chat(input);
System.out.println("< " + chat);

// 第二个问题
input = "唐僧做了什么?";
System.out.println("> " + input);
chat = assistant.chat("里面的水浒传故事来自于哪个章节?");
System.out.println("< " + chat);
}

/**
* 创建RAG助手实例
* @return 配置好的助手实例
*/
private static Assistant createAssistant() {
// 1. 配置通义千问聊天模型
ChatLanguageModel chatLanguageModel = QwenChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // 通义千问API密钥
.modelName("qwen-plus") // 使用qwen-plus模型
.listeners(List.of(chatModelListener())) // 添加模型监听器
.build();

// 2. 配置通义千问嵌入模型(用于文本向量化)
QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770")
.modelName("text-embedding-v1") // 使用text-embedding-v1模型
.build();

// 3. 加载文档并处理
List<Document> documents = loadDocuments("D:/Temp/txt"); // 从指定目录加载文档
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>(); // 创建内存向量存储
DocumentSplitter splitter = DocumentSplitters.recursive(1000, 0); // 创建文档分割器(最大1000字符)

// 处理每个文档:分割、向量化、存储
documents.forEach(document -> {
List<TextSegment> segments = splitter.split(document); // 分割文档
List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); // 生成向量
System.out.println(embeddings.size()); // 打印向量数量
embeddingStore.addAll(embeddings, segments); // 存储向量和原始文本
});

// 4. 配置内容检索器
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore) // 设置向量存储
.embeddingModel(embeddingModel) // 设置嵌入模型
.maxResults(100) // 最大返回结果数
.minScore(0.7) // 最小相似度阈值
.build();

// 5. 配置聊天记忆(保存最近10条对话)
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

// 6. 构建RAG助手服务
return AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel) // 设置聊天模型
.contentRetriever(contentRetriever) // 设置内容检索器
.chatMemory(chatMemory) // 设置聊天记忆
.build();
}

/**
* 创建聊天模型监听器,用于监控模型请求、响应和错误
* @return 配置好的监听器实例
*/
public static ChatModelListener chatModelListener() {
ChatModelListener listener = new ChatModelListener() {
// 请求开始时触发
@Override
public void onRequest(ChatModelRequestContext requestContext) {
System.out.println(">>>>>>>>>>>>>>>>>>>>");
System.out.println("onRequest(): " + requestContext.chatRequest());
}

// 收到响应时触发
@Override
public void onResponse(ChatModelResponseContext responseContext) {
System.out.println("<<<<<<<<<<<<<<<<<<<<");
System.out.println("onResponse(): " + responseContext.chatResponse());
}

// 发生错误时触发
@Override
public void onError(ChatModelErrorContext errorContext) {
System.out.println("onError(): " + errorContext.error());
System.out.println("-------------------");
}
};
return listener;
}
}

LangChain4j Advance RAG:查询路由

1
2
3
4
5
package com.lixiang;

interface Assistant {
String chat(String userMessage);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package com.lixiang;

import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.RetrievalAugmentor;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.query.router.LanguageModelQueryRouter;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
/**
* 实际项目中,数据通常分散在各种地方,比如:
* - 公司内部文档(Confluence)
* - 项目代码(Git仓库)
* - 用户数据库
* - 产品搜索引擎
* 做RAG时,我们可能有多个数据源和检索器
* 如果每个问题都去查所有数据源,既慢又浪费资源
*
* 解决方案是"智能路由":
* 根据问题类型自动选择最合适的数据源
* 具体实现方式有:
* - 按规则路由(比如根据用户权限)
* - 按关键词路由(问题含特定词就走特定检索器)
* - 按语义相似度路由
* - 让AI模型自己决定
*/

/**
* 高级RAG(检索增强生成)演示类
* 使用通义千问模型进行问答,支持从多个文档源检索相关信息
* 实现了基于语言模型的路由查询功能
*/
public class AdvanceRagDemo {

/**
* 主方法,创建RAG助手并测试问答功能
* @param args 命令行参数
*/
public static void main(String[] args) {
// 创建RAG助手实例
Assistant assistant = createAssistant();

// 测试关于四大名著的问题
String input = "水浒传中鲁智深做了什么?";
System.out.println("> " + input);
String chat = assistant.chat(input);
System.out.println("< " + chat);

// 测试关于LangChain4j的问题
input = "为我介绍下什么是LangChain4j?";
System.out.println("> " + input);
chat = assistant.chat(input);
System.out.println("< " + chat);
}

/**
* 创建RAG助手实例
* @return 配置好的助手实例
*/
private static Assistant createAssistant() {
// 1. 配置通义千问聊天模型
ChatLanguageModel chatLanguageModel = QwenChatModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770") // API密钥
.modelName("qwen-plus") // 使用qwen-plus模型
.listeners(List.of(chatModelListener())) // 添加模型监听器
.build();

// 2. 配置通义千问嵌入模型(用于文本向量化)
QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder()
.apiKey("sk-937aee4c3e654d04b84634d363f5a770")
.modelName("text-embedding-v1") // 使用text-embedding-v1模型
.build();

// 3. 创建不同领域的内容检索器
ContentRetriever fourGreatClassicsRetriever = createFourGreatClassicsRetriever(embeddingModel); // 四大名著检索器
ContentRetriever langChain4jRetriever = createLangChain4jRetriever(embeddingModel); // LangChain4j文档检索器

// 4. 配置查询路由器,根据问题类型选择不同的检索器
Map<ContentRetriever, String> retrieverToDescription = new HashMap<>();
retrieverToDescription.put(fourGreatClassicsRetriever, "红楼梦、水浒传、西游记、三国演义节选");
retrieverToDescription.put(langChain4jRetriever, "LangChain4j学习文档");
QueryRouter queryRouter = new LanguageModelQueryRouter(chatLanguageModel, retrieverToDescription);

// 5. 构建检索增强器
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter) // 设置查询路由器
.build();

// 6. 构建RAG助手服务
return AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel) // 设置聊天模型
.retrievalAugmentor(retrievalAugmentor) // 设置检索增强器
.build();
}

/**
* 创建四大名著内容检索器
* @param embeddingModel 嵌入模型
* @return 配置好的内容检索器
*/
private static ContentRetriever createFourGreatClassicsRetriever(QwenEmbeddingModel embeddingModel) {
// 创建文档分割器(最大1000字符)
DocumentSplitter splitter = DocumentSplitters.recursive(1000, 0);

// 从指定目录加载四大名著文档
List<Document> fourGreatClassicsDocuments = loadDocuments("D:/Temp/txt");
// 创建内存向量存储
EmbeddingStore<TextSegment> fourGreatClassicsEmbeddingStore = new InMemoryEmbeddingStore<>();

// 处理每个文档:分割、向量化、存储
fourGreatClassicsDocuments.forEach(document -> {
List<TextSegment> segments = splitter.split(document); // 分割文档
List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); // 生成向量
System.out.println(embeddings.size()); // 打印向量数量
fourGreatClassicsEmbeddingStore.addAll(embeddings, segments); // 存储向量和原始文本
});

// 构建并返回内容检索器
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(fourGreatClassicsEmbeddingStore) // 设置向量存储
.embeddingModel(embeddingModel) // 设置嵌入模型
.maxResults(100) // 最大返回结果数
.minScore(0.7) // 最小相似度阈值
.build();
}

/**
* 创建LangChain4j内容检索器
* @param embeddingModel 嵌入模型
* @return 配置好的内容检索器
*/
private static ContentRetriever createLangChain4jRetriever(QwenEmbeddingModel embeddingModel) {
// 创建文档分割器(最大1000字符)
DocumentSplitter splitter = DocumentSplitters.recursive(1000, 0);

// 从指定目录加载LangChain4j文档
List<Document> langChain4jDocuments = loadDocuments("D:/Temp/langchain4j");
// 创建内存向量存储
EmbeddingStore<TextSegment> langChain4jEmbeddingStore = new InMemoryEmbeddingStore<>();

// 处理每个文档:分割、向量化、存储
langChain4jDocuments.forEach(document -> {
List<TextSegment> segments = splitter.split(document); // 分割文档
List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); // 生成向量
System.out.println(embeddings.size()); // 打印向量数量
langChain4jEmbeddingStore.addAll(embeddings, segments); // 存储向量和原始文本
});

// 构建并返回内容检索器
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(langChain4jEmbeddingStore) // 设置向量存储
.embeddingModel(embeddingModel) // 设置嵌入模型
.maxResults(100) // 最大返回结果数
.minScore(0.7) // 最小相似度阈值
.build();
}

/**
* 创建聊天模型监听器
* @return 配置好的监听器实例
*/
public static ChatModelListener chatModelListener() {
ChatModelListener listener = new ChatModelListener() {
// 请求开始时触发
@Override
public void onRequest(ChatModelRequestContext requestContext) {
System.out.println(">>>>>>>>>>>>>>>>>>>>");
System.out.println("onRequest(): " + requestContext.chatRequest());
}

// 收到响应时触发
@Override
public void onResponse(ChatModelResponseContext responseContext) {
System.out.println("<<<<<<<<<<<<<<<<<<<<");
System.out.println("onResponse(): " + responseContext.chatResponse());
}

// 发生错误时触发
@Override
public void onError(ChatModelErrorContext errorContext) {
System.out.println("onError(): " + errorContext.error());
System.out.println("-------------------");
}
};
return listener;
}
}