SpringBoot整合LangChain4j实现RAG (检索增强生成)

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 索引结构 使用 HNSWIVFFlat 等索引算法,可快速返回 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,搜索elasticsearchkibana的镜像

这里使用的版本是8.15.5,推荐使用8.6+的版本

验证安装,打开powershellcmd,输入:

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

如果STATUSUp,说明容器正在运行,也可以在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的文本向量嵌入和搜索

参考的文章:

LangChain4j 使用 Elasticsearch 作为嵌入存储

RAG(检索增强生成)

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()));
    }
}

检查我们的文本向量嵌入结果:

  1. 打开Kibana ,浏览器输入网址:http://{你的服务器地址}:5601,如果你是本地部署,直接访问:你的本地kibana

主页面应该是这样的:

  1. 点击左上角三个横杠,选择"Dev Tools" (开发者工具)
  1. 进入开发者控制台,输入查询的DSL即可
python 复制代码
# 查询你的文本向量
GET /games/_search # games换成你的索引名称
{
  "query": {
    "match_all": {}
  },
  "size": 10
}

如下图所示,点击运行按钮,能够在控制台右侧看到储存的向量数据:

👆🤓恭喜你,你已经将查询所需要的文本向量化,存入了Elasticsearch中!🥳🥳🥳


检索

检索阶段通常在线发生,当用户提交需要使用索引文档来回答的问题时。

此过程可能因所使用的信息检索方法而异。对于向量搜索,这通常涉及嵌入用户的查询(问题)并在嵌入存储中执行相似性搜索。然后将相关片段(原始文档的片段)注入提示并发送到 LLM。

简单来说,检索阶段通过嵌入文本模型,根据问题在向量数据库中查询对应的文本片段,并添加到上下文中,以增强AI问答体验。

以下是检索阶段的简化图:

LangChain4j 提供三种 RAG:

  • Easy RAG:开始使用 RAG 的最简单方法
  • 朴素 RAG:使用向量搜索的 RAG 基本实现
  • 高级 RAG:模块化 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应用中,提升问答体验;希望这篇文章能帮到大家!如果喜欢的话请点赞收藏,如有任何的问题和建议,也欢迎在评论区讨论!😎

相关推荐
Yxh181377845541 分钟前
抖去推--短视频矩阵系统源码开发
人工智能·python·矩阵
取酒鱼食--【余九】34 分钟前
rl_sar实现sim2real的整体思路
人工智能·笔记·算法·rl_sar
Jamence1 小时前
多模态大语言模型arxiv论文略读(111)
论文阅读·人工智能·语言模型·自然语言处理·论文笔记
归去_来兮1 小时前
图神经网络(GNN)模型的基本原理
大数据·人工智能·深度学习·图神经网络·gnn
爱吃饼干的熊猫1 小时前
PlayDiffusion上线:AI语音编辑进入“无痕时代”
人工智能·语音识别
SelectDB技术团队1 小时前
Apache Doris + MCP:Agent 时代的实时数据分析底座
人工智能·数据挖掘·数据分析·apache·mcp
Leinwin1 小时前
微软推出SQL Server 2025技术预览版,深化人工智能应用集成
人工智能·microsoft
CareyWYR2 小时前
每周AI论文速递(2506202-250606)
人工智能
点云SLAM2 小时前
PyTorch 中contiguous函数使用详解和代码演示
人工智能·pytorch·python·3d深度学习·contiguous函数·张量内存布局优化·张量操作