LangChain4j实现RAG (检索增强生成)
🤖检索增强生成 (英语:
Retrieval-augmented generation
,RAG
) 是赋予生成式人工智能模型信息检索能力的技术。检索增强生成优化大型语言模型(LLM
) 的交互方式,让模型根据指定的一组文件回应用户的查询,并使用这些信息增强模型从自身庞大的静态训练数据中提取的信息。检索增强生成技术促使大型语言模型能够使用特定领域或更新后的信息。[1]应用案例,包括让聊天机器人访问公司内部资料,或来自权威来源的事实信息。(来自维基百科)
项目地址:github.com/l0sgAi/ai-c...
阅读本文前应该有的知识基础
- Java基础
- Java Web基础
- 数据库基础
- 包管理 (Maven、Gradle等)
- Spring基础、SSM整合、SpringBoot等
- docker、容器化基础
- Elasticsearch基础
总之,使用RAG, 可以提升AI对话的输出质量, 提高回答的准确性。
本文是我AI
学习笔记的第2部分,基于阿里的百炼云平台。如果你对相关知识不熟悉,请阅读我之前的文章,以获得最佳阅读体验:SpringBoot整合AI应用-流式对话。
向量数据库简介
注:构建知识库也可以使用阿里的百炼云平台,但是本文介绍的是本地知识库的搭建。如果你不想在本地构建知识库,请参考:百炼-0代码构建私有知识问答应用
💡 为什么RAG需要向量数据库?
原因 | 说明 |
---|---|
语义搜索 | 文本向量化后可表达其语义信息,向量数据库支持基于语义相似度的检索,优于关键词匹配。 |
高效相似度检索 | 支持大规模高维向量的相似度搜索 (如余弦相似度、内积),适合 RAG 通过 Query 检索相关文档片段。 |
支持 ANN 索引结构 | 使用 HNSW 、IVF 、Flat 等索引算法,可快速返回 Top-K 相似结果,满足 RAG 实时响应需求。 |
可扩展性强 | 能处理上亿级向量数据,适用于大型文档库的构建和实时更新。 |
支持元数据过滤 | 可结合元数据过滤(如文档类型、时间)进行更精准的文档召回。 |
📊 主流向量数据库对比(2025年,来自CHAT GPT 4o)
名称 | 开源 | 架构语言 | 索引类型 | 特点 | 使用场景 |
---|---|---|---|---|---|
FAISS (Meta) | ✅ | C++/Python | Flat, IVF, HNSW | 离线高效、轻量、嵌入式强 | 本地快速检索(适合研究原型) |
Milvus (Zilliz) | ✅ | C++/Go | IVF, HNSW, DiskANN | 分布式、高吞吐、云原生支持 | 企业级向量检索服务 |
Weaviate | ✅ | Go | HNSW | 支持向量+结构化数据+GraphQL | RAG、企业知识库 |
Qdrant | ✅ | Rust | HNSW | 快速部署、支持过滤器、Web UI | 轻量级生产部署、边缘计算 |
Pinecone | ❌(SaaS) | N/A | HNSW (定制) | 托管服务、可扩展性强、使用简单 | 无需部署、直接云上构建 RAG 系统 |
Vespa (Yahoo) | ✅ | Java | HNSW + ranking | 强结构化查询+文本检索 | 企业级搜索与推荐系统 |
ElasticSearch + dense_vector | ✅ | Java | Brute-force / ANN 插件 | 结合全文检索和向量搜索 | 一体化文档检索系统(传统 ES 用户首选) |
这里大家可以选择自己喜欢的向量数据库,我这里使用的是ElasticSearch
,因为之前装过了,使用比较方便,而且它的功能也足够强大。
安装ElasticSearch
安装
ElasticSearch
有多种方式,推荐使用docker
,我这里为了方便起见,在windows
上使用docker-compose
+dockerhub
部署Elasticsearch
和可视化界面工具Kibana
,大家也可以安装到自己的Linux
虚拟机和服务器上。😎如果你已经安装了Elasticsearch,请跳过这一步。
1. 拉取镜像
- 打开你的dockerHub,搜索
elasticsearch
和kibana
的镜像


这里使用的版本是8.15.5
,推荐使用8.6+
的版本
验证安装,打开powershell
或cmd
,输入:
arduino
docker image ls -a
如果看到如下输出,说明镜像拉取成功:
REPOSITORY TAG IMAGE ID CREATED SIZE
elasticsearch 8.15.5 e983a5cfa418 6 months ago 1.27GB
kibana 8.15.5 e76560fb8141 6 months ago 1.14GB
当然,你也可以在docker-hub
中直接查看。
2. 使用docker-compose运行容器
编写docker-compose.yml
yml
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.15.5
container_name: elasticsearch
environment:
- node.name=es-node
- cluster.name=es-cluster
- discovery.type=single-node
- bootstrap.memory_lock=true
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- ./es-data:/usr/share/elasticsearch/data
ports:
- 9200:9200
- 9300:9300
kibana:
image: kibana:8.15.5
container_name: kibana
depends_on:
- elasticsearch
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- 5601:5601
volumes:
es-data:
其中的参数可以自行调整,包括设置内存选项和端口号。
注意:数据文件会储存在docker-compose.yml
目录下的es-data
中。
执行docker-compose.yml
执行命令:
bash
cd ${你的docker-compose.yml所在的绝对目录}
docker-compose up -d
如果输出类似于:
erlang
Creating network "docker_default" ...
Creating volume "docker_es-data" ...
Creating elasticsearch ...
Creating kibana ...
说明安装成功了
验证容器运行情况
执行命令:
docker ps
这个命令会列出所有运行中的容器,输出类似于:
bash
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
15ee1a203ec9 kibana:8.15.5 "/bin/tini -- /usr/l..." About an hour ago Up 22 seconds 0.0.0.0:5601->5601/tcp kibana
cc9990df199d elasticsearch:8.15.5 "/bin/tini -- /usr/l..." About an hour ago Up 23 seconds 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch
如果STATUS
为Up
,说明容器正在运行,也可以在docker-hub
中查看容器运行情况。
整合Elasticsearch与LangChain4j
我们首先需要引入Maven
依赖:
xml
<!-- OpenAI compatible API (支持 Qwen 百炼 OpenAI 模式) -->
<!-- https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-open-ai -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>0.35.0</version>
</dependency>
<!--langchain4j-es集成-->
<!-- https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-elasticsearch -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-elasticsearch</artifactId>
<version>0.35.0</version>
</dependency>
<!--ES客户端-->
<!-- https://mvnrepository.com/artifact/co.elastic.clients/elasticsearch-java -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.15.5</version> <!--这里选择和你的实际ES版本匹配的版本-->
</dependency>
测试Elastcsearch的文本向量嵌入和搜索
参考的文章:
RAG阶段
RAG
流程分为两个不同的阶段:索引和检索。LangChain4j
为这两个阶段提供了相应的工具。
索引
在索引阶段,文档会进行预处理,以便在检索阶段进行有效搜索。
此过程可能因所使用的信息检索方法而异。对于向量搜索,这通常涉及清理文档、使用附加数据和元数据丰富文档、将其拆分为更小的段(也称为分块)、嵌入这些段,最后将其存储在嵌入存储(也称为向量数据库)中。
索引阶段通常离线进行,这意味着最终用户无需等待其完成。例如,有一个 cron
作业,该作业每周周末重新索引一次公司内部文档。负责索引的代码也可以是一个单独的应用程序,专门处理索引任务。
但是,在某些情况下,最终用户可能希望上传其自定义文档,以便 LLM
可以访问。在这种情况下,索引应该在线进行,并作为主申请的一部分。
简单来说,要实现
RAG
,我们需要一份文档,通过文本分词器提取片段、使用嵌入文本模型储存到我们的向量储存 (Elasticsearch
)中。
编写测试类:
java
@Slf4j
@SpringBootTest
public class RagTest {
@Autowired
private AiConfigMapper aiConfigMapper;
@Test
public void startEmbedding() throws IOException {
// 也可以从数据库读取API Key
AiConfig aiConfig = aiConfigMapper.selectByPrimaryKey(1);
// 构建嵌入模型,使用openAI标准
OpenAiEmbeddingModel model = OpenAiEmbeddingModel.builder()
// 这里换成自己的API Key
.apiKey(aiConfig.getApiKey())
// 即为 "https://dashscope.aliyuncs.com/compatible-mode/v1"
.baseUrl(aiConfig.getApiDomain())
// 需要使用QWEN的文本向量模型 支持的维度2048、1536、1024(默认)、768、512、256、128、64
.modelName("text-embedding-v4")
.dimensions(1024) // 直接指定向量维度
.build();
// 检查模型的生成维数
log.info("model: {}", model.dimension());
// 构建测试用文本
TextSegment game1 = TextSegment.from(
"电子游戏:METAL GEAR SOLID V: THE PHANTOM PAIN由小岛秀夫制作,但是由于黑心企业KONAMI的介入," +
"后期开发资金不足,是一个半成品," +
"即便如此,它也在国际上获得了良好的声誉,获奖无数。" +
"在2025年6月5日,它的在线人数为993人。",
Metadata.from("gameName", "METAL GEAR SOLID V: THE PHANTOM PAIN")
);
Embedding embedding1 = model.embed(game1.text()).content();
TextSegment game2 = TextSegment.from(
"电子游戏:MONSTER HUNTER: WILDS在刚发售的时候销量还不错,但是后期的发展却不太理想,"
+ "在2025年6月5日,即使刚刚更新了游戏内容不久,同时在线人数只有1.2万左右。",
Metadata.from("gameName", "MONSTER HUNTER: WILDS")
);
Embedding embedding2 = model.embed(game2.text()).content();
/*
* 显然,上面的这种方法不适用于更大的数据集,
* 因为这个数据存储系统会把所有内容都存储在内存中,
* 而我们的服务器内存有限。因此,我们可以将嵌入存储到 Elasticsearch 中,
* 现在的解决方案只是测试用
* */
// 初始化Elasticsearch实例
// 1. 创建 RestClient(无认证、无 SSL)
// TODO 如果在设置里打开了认证,需要配置认证信息
RestClient restClient = RestClient.builder(
// 这里换成自己的ES服务器地址,如果是本地部署,直接localhost即可
new HttpHost("192.168.200.132", 9200)
).build();
// 2. 使用 Jackson 映射器创建 Transport 层
RestClientTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
// 3. 创建 Elasticsearch Java 客户端
ElasticsearchClient client = new ElasticsearchClient(transport);
// 4. 使用ES储存文本向量
ElasticsearchEmbeddingStore store = ElasticsearchEmbeddingStore.builder()
.indexName("games") // 这里换成自己的ES索引名
.restClient(restClient)
.build();
// 5. 向ES存储文本向量
store.add(embedding1, game1);
store.add(embedding2, game2);
// 搜索相似向量-搜索
String question = "电子游戏:MONSTER HUNTER: WILDS在6月5日的同时在线人数";
Embedding questionAsVector = model.embed(question).content();
// 搜索相似向量-搜索结果
EmbeddingSearchResult<TextSegment> result = store.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(questionAsVector)
.build());
// 打印匹配结果
result.matches().forEach(m -> log.info("{} - score [{}]",
m.embedded().metadata().getString("gameName"), m.score()));
}
}
检查我们的文本向量嵌入结果:
- 打开
Kibana
,浏览器输入网址:http://{你的服务器地址}:5601
,如果你是本地部署,直接访问:你的本地kibana
主页面应该是这样的:
- 点击左上角三个横杠,选择"
Dev Tools
" (开发者工具)

- 进入开发者控制台,输入查询的DSL即可
python
# 查询你的文本向量
GET /games/_search # games换成你的索引名称
{
"query": {
"match_all": {}
},
"size": 10
}
如下图所示,点击运行按钮,能够在控制台右侧看到储存的向量数据:

👆🤓恭喜你,你已经将查询所需要的文本向量化,存入了Elasticsearch中!🥳🥳🥳
检索
检索阶段通常在线发生,当用户提交需要使用索引文档来回答的问题时。
此过程可能因所使用的信息检索方法而异。对于向量搜索,这通常涉及嵌入用户的查询(问题)并在嵌入存储中执行相似性搜索。然后将相关片段(原始文档的片段)注入提示并发送到 LLM。
简单来说,检索阶段通过嵌入文本模型,根据问题在向量数据库中查询对应的文本片段,并添加到上下文中,以增强AI问答体验。
LangChain4j 提供三种 RAG:
✔本文会在测试中简单介绍检索的一般步骤,如果需要更进阶的应用,可以自己继续深入研究❤ 。
编写检索阶段的测试
新增maven
依赖:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.35.0</version> <!-- 确保与你使用的版本一致 -->
</dependency>
定义Assistant
接口:
java
public interface Assistant {
@SystemMessage("You are a helpful assistant.")
String chat(@UserMessage String userMessage);
}
@SystemMessage
注解的作用是在用户问题前面添加一段额外的提示词,以满足不同应用场景的要求。
测试方法:
java
@Test
public void startQuery(){
// 也可以从数据库读取API Key
AiConfig aiConfig = aiConfigMapper.selectByPrimaryKey(1);
// 构建嵌入模型,使用openAI标准
OpenAiEmbeddingModel model = OpenAiEmbeddingModel.builder()
// 这里换成自己的API Key
.apiKey(aiConfig.getApiKey())
// 注意:这里的url和流式对话的时候不一样,需要使用compatible-mode
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
// 需要使用QWEN的文本向量模型 支持的维度2048、1536、1024(默认)、768、512、256、128、64
.modelName("text-embedding-v4")
.dimensions(1024) // 直接指定向量维度
.build();
// 初始化Elasticsearch实例
// 1. 创建 RestClient(无认证、无 SSL)
// TODO 如果在设置里打开了认证,需要配置认证信息
RestClient restClient = RestClient.builder(
// 这里换成自己的ES服务器地址,如果是本地部署,直接localhost即可
new HttpHost("192.168.200.132", 9200)
).build();
// 2. 使用 Jackson 映射器创建 Transport 层
RestClientTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
// 3. 创建 Elasticsearch Java 客户端
ElasticsearchClient client = new ElasticsearchClient(transport);
// 4. 获取ES中对应的索引储存
ElasticsearchEmbeddingStore store = ElasticsearchEmbeddingStore.builder()
.indexName("games") // 这里换成自己的ES索引名
.restClient(restClient)
.build();
// 5. 创建内容检索器
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(model)
.maxResults(5)
.minScore(0.75)
.build();
// 构建对话模型 (非流式返回)
OpenAiChatModel myChatModel = OpenAiModelBuilder.fromAiConfigWithoutStream(aiConfig);
// 建立基于RAG的对话模型
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(myChatModel) // 如阿里百炼的 ChatModel 封装
.contentRetriever(contentRetriever) // 你刚刚创建好的带 EmbeddingStore 的 ContentRetriever
.build();
// 给出答案
String question = "请问在2025年6月5日,MONSTER HUNTER: WILDS和METAL GEAR SOLID V: THE PHANTOM PAIN的在线人数分别是多少?";
String answer = assistant.chat(question);
log.info("答案-RAG:{}",answer);
}
模型构建部分:
这里请填写你自己的模型参数,或者从数据库查询
java
public class OpenAiModelBuilder {
/** 构建流式对话参数*/
public static OpenAiStreamingChatModel fromAiConfigByLangChain4j(AiConfig config) {
return OpenAiStreamingChatModel.builder()
.apiKey(config.getApiKey())
.baseUrl(config.getApiDomain()) // 如 https://dashscope.aliyuncs.com/compatible-mode/v1
.modelName(config.getModelId()) // 如 qwen-turbo-latest
.temperature(config.getTemperature()) // 可从 config 拓展配置
.topP(config.getSimilarityTopP())
.maxTokens(config.getMaxContextMsgs())
.apiKey(config.getApiKey())
.build();
}
/** 构建非流式对话参数*/
public static OpenAiChatModel fromAiConfigWithoutStream(AiConfig config) {
return OpenAiChatModel.builder()
.apiKey(config.getApiKey())
.baseUrl(config.getApiDomain()) // 如 https://dashscope.aliyuncs.com/compatible-mode/v1
.modelName(config.getModelId()) // 如 qwen-turbo-latest
.temperature(config.getTemperature()) // 可从 config 拓展配置
.topP(config.getSimilarityTopP())
.maxTokens(config.getMaxContextMsgs())
.apiKey(config.getApiKey())
.build();
}
}
运行测试,结果:
bash
DruidDataSource : {dataSource-1} inited
com.losgai.ai.RagTest :
答案-RAG:在2025年6月5日,MONSTER HUNTER: WILDS的在线人数约为1.2万,
而METAL GEAR SOLID V: THE PHANTOM PAIN的在线人数为993人。
DruidDataSource : {dataSource-1} closing ...
2025-06-07T17:07:39.009+08:00 INFO 16864 --- [ai] [ionShutdownHook]
DruidDataSource : {dataSource-1} closed
进程已结束,退出代码为 0
总结
🤯还记得我们之前向量化嵌入的文本吗?
java
// 构建测试用文本
TextSegment game1 = TextSegment.from(
"电子游戏:METAL GEAR SOLID V: THE PHANTOM PAIN由小岛秀夫制作,但是由于黑心企业KONAMI的介入," +
"后期开发资金不足,是一个半成品," +
"即便如此,它也在国际上获得了良好的声誉,获奖无数。" +
"在2025年6月5日,它的在线人数为993人。",
Metadata.from("gameName", "METAL GEAR SOLID V: THE PHANTOM PAIN")
);
Embedding embedding1 = model.embed(game1.text()).content();
TextSegment game2 = TextSegment.from(
"电子游戏:MONSTER HUNTER: WILDS在刚发售的时候销量还不错,但是后期的发展却不太理想,"
+ "在2025年6月5日,即使刚刚更新了游戏内容不久,同时在线人数只有1.2万左右。",
Metadata.from("gameName", "MONSTER HUNTER: WILDS")
);
Embedding embedding2 = model.embed(game2.text()).content();
✅大模型提供的回复和我们之前提供的文本信息一模一样!说明它已经成功从我们的向量数据库检索出了数据,并应用到回复中。
实际应用上,我们也可以将上面的方法改造为流式输出,并在输入框新增一个"选择知识库"的选项。(即存放向量数据的索引)因为如果一个索引中的数据过于庞大,向量数据的检索效果也会随之降低,这时候就需要一些其它的手段,提升检索的准确性了,通过不同的索引,对不同种类的知识库进行分别查询就是其中一个简单有效的做法。具体请查阅文档:高级 RAG
对照实验
作为对比,同样的问题,不使用RAG
的结果 (即不使用.contentRetriever
)的结果:
java
// 构建对话模型 (非流式返回)
OpenAiChatModel myChatModel = OpenAiModelBuilder.fromAiConfigWithoutStream(aiConfig);
// 建立基于RAG的对话模型
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(myChatModel) // 如阿里百炼的 ChatModel 封装ContentRetriever
.build();
// 给出答案
String question = "请问在2025年6月5日,MONSTER HUNTER: WILDS和METAL GEAR SOLID V: THE PHANTOM PAIN的在线人数分别是多少?";
String answer = assistant.chat(question);
log.info("答案-RAG:{}",answer);
运行测试,结果:
bash
DruidDataSource : {dataSource-1} inited
RagTest: 答案-RAG:截至我知识库的更新时间(2024年10月),我无法提供2025年6月5日《MONSTER HUNTER: WILDS》和《METAL GEAR SOLID V: THE PHANTOM PAIN》的在线人数数据,因为这些数据是实时变化的,并且需要依赖游戏平台(如Steam、PlayStation Network、Xbox Live等)的实时统计。
不过,我可以提供一些背景信息:
### 1. **《MONSTER HUNTER: WILDS》**
- 这是一款由Capcom开发的新作,于2025年发布(根据官方消息)。
- 由于是新游戏,上线初期的在线人数可能会较高,但具体数字需查看实时数据平台(如Steam Charts、Metacritic、GameSpot等)。
- 你可以通过以下网站查看实时在线人数:
- [Steam Charts](https://steamcharts.com/)
- [GamingOnLinux](https://gamingonlinux.com/)
- 或者游戏内的在线状态。
### 2. **《METAL GEAR SOLID V: THE PHANTOM PAIN》**
- 这款游戏已于2015年发布,目前处于"长尾期"。
- 在线人数已经大幅下降,但仍有部分玩家在进行多人模式或社区活动。
- 由于它不再有官方服务器维护,可能只有少数玩家在使用第三方服务器或本地联机。
---
### 建议你查看的资源:
- **Steam Charts**:可以查看《MONSTER HUNTER: WILDS》的实时在线人数。
- **PlayStation/PC平台的在线状态**:部分平台会显示游戏的在线人数。
- **Reddit 或 Discord 社区**:有时玩家会分享实时数据或讨论在线情况。
如果你需要的是**历史数据**(比如2025年6月5日前的某个时间点),请明确说明,我可以尝试给出更具体的参考。
.DruidDataSource : {dataSource-1} closing ...
.DruidDataSource : {dataSource-1} closed
进程已结束,退出代码为 0
不难看出,此时的AI
是不知道2025-06-05
的两款游戏在线人数的。
扩展:基于RAG进行流式输出
引入依赖-分别集成SpringBoot
和反应式组件:
xml
<!--SpringBoot集成-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>0.35.0</version>
</dependency>
<!--反应式组件-->
<!-- https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-reactor -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>0.35.0</version>
</dependency>
编写测试方法:
java
@Test
public void startQueryWithRAG() throws InterruptedException {
// 也可以从数据库读取API Key
AiConfig aiConfig = aiConfigMapper.selectByPrimaryKey(1);
CountDownLatch countDownLatch = new CountDownLatch(1);
// 构建嵌入模型,使用openAI标准
OpenAiEmbeddingModel model = OpenAiEmbeddingModel.builder()
// 这里换成自己的API Key
.apiKey(aiConfig.getApiKey())
// 即为 "https://dashscope.aliyuncs.com/compatible-mode/v1"
.baseUrl(aiConfig.getApiDomain())
// 需要使用QWEN的文本向量模型 支持的维度2048、1536、1024(默认)、768、512、256、128、64
.modelName("text-embedding-v4")
.dimensions(1024) // 直接指定向量维度
.build();
// 初始化Elasticsearch实例
// 1. 创建 RestClient(无认证、无 SSL)
// TODO 如果在设置里打开了认证,需要配置认证信息
RestClient restClient = RestClient.builder(
// 这里换成自己的ES服务器地址,如果是本地部署,直接localhost即可
new HttpHost("192.168.200.132", 9200)
).build();
// 2. 使用 Jackson 映射器创建 Transport 层
RestClientTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
// 3. 创建 Elasticsearch Java 客户端
ElasticsearchClient client = new ElasticsearchClient(transport);
// 4. 获取ES中对应的索引储存
ElasticsearchEmbeddingStore store = ElasticsearchEmbeddingStore.builder()
.indexName("games") // 这里换成自己的ES索引名
.restClient(restClient)
.build();
// 5. 创建内容检索器
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(model)
.maxResults(5)
.minScore(0.75)
.build();
OpenAiStreamingChatModel myChatModelStream = OpenAiModelBuilder.fromAiConfigByLangChain4j(aiConfig);
Assistant assistantStream = AiServices.builder(Assistant.class)
.streamingChatLanguageModel(myChatModelStream) // 如阿里百炼的 ChatModel 封装
.contentRetriever(contentRetriever) // 你刚刚创建好的带 EmbeddingStore 的 ContentRetriever
.build();
// 给出答案
String question = "请问在2025年6月5日,MONSTER HUNTER: WILDS和METAL GEAR SOLID V: THE PHANTOM PAIN的在线人数分别是多少?";
Flux<String> responseFlux = assistantStream.chat(question);
// 订阅 Flux 实现流式输出(控制台输出或 SSE 推送)
responseFlux.subscribe(
token -> log.info("输出token:{}", token), // 每个token响应
error -> {
log.error("出错:", error);
countDownLatch.countDown(); // 停止倒计时
}, // 错误处理
() -> log.info("\n回答完毕!") // 流结束
);
// 阻塞主线程最多60s 等待结果
countDownLatch.await(60, TimeUnit.SECONDS);
}
🎉输出结果:

💎基于上面文章的内容,我们还可以尝试把RAG
检索封装为服务,集成到您的Web
应用中,提升问答体验;希望这篇文章能帮到大家!如果喜欢的话请点赞收藏,如有任何的问题和建议,也欢迎在评论区讨论!😎