LangChain4j学习与实践

初识LangChain4j

快速上手

1.引入依赖

xml 复制代码
<properties>
    <langchain4j.version>1.0.0-beta1</langchain4j.version>
</properties>

<dependencies>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>${langchain4j.version}</version>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
        <version>${langchain4j.version}</version>
    </dependency>
</dependencies>

如果出现依赖下载不了请检查Maven的远程仓库中是否存在langchain4j依赖, 本人用的远程仓库:repo1.maven.org/maven2/

2.示例

使用OpenAi

ini 复制代码
OpenAiChatModel model = OpenAiChatModel.builder()
        .baseUrl("http://langchain4j.dev/demo/openai/v1")
        .apiKey("demo")
        .modelName("gpt-4o-mini")
        .build();
String answer = model.chat("Say 'Hello World'");
System.out.println(answer); // Hello World

使用Deepseek

ini 复制代码
// DeepSeek使用的接口和OpenAI的接口一样,所以可以直接使用OpenAiChatModel
OpenAiChatModel model = OpenAiChatModel.builder()
        .baseUrl("https://api.deepseek.com")
        .apiKey("api key")
        .modelName("deepseek-chat")
        .build();
String answer = model.chat("你是谁");
System.out.println(answer);

使用通义千问

引入通义千问的依赖
xml 复制代码
<!--通义千问-->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-dashscope</artifactId>
    <version>${langchain4j.version}</version>
</dependency>

测试通义千问

java 复制代码
/**
 * 描述:测试通义千问
 *
 * @author xuzili
 * @date 2025/05/22
 */

@Test
public void testQw() {
    QwenChatModel model = QwenChatModel.builder()
            .apiKey("sk-62f684f1658144a4b3b6f7e9bee9497b")
            .modelName("qwen-max")
            .build();
    String answer = model.chat("你是谁");
    System.out.println(answer);
}

ollama

Ollama 是一个开源的本地大语言模型运行框架,专为在本地机器上便捷部署和运行大型语言模型LLM)而设计。 Ollama 支持多种操作系统,包括 macOS、Windows、Linux 以及通过 Docker 容器运行。 Ollama 提供对模型量化的支持,可以显著降低显存要求,使得在普通家用计算机上运行大型模型成为可能。

安装部署

ollama国内加速镜像: ollama | newbe

使用

以安装deepseek-r1:1.5b模型为例,在官网中找到对应模型名称

使用ollama run deepseek-r1:1.5b命令安装到本地

在Java程序中使用Ollama

引入Ollama依赖

xml 复制代码
<!--        ollama-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-ollama</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

测试Ollama

ollama默认端口为11434

java 复制代码
/**
 * 描述:测试ollama
 *
 * @author xuzili
 * @date 2025/05/22
 */

@Test
public void testOllama() {
    OllamaChatModel model = OllamaChatModel.builder()
            .baseUrl("https://127.0.0.1:11434")
            .modelName("deepseek-r1:1.5b")
            .build();
    String answer = model.chat("你是谁");
    System.out.println(answer);
}

文生图

需要选择支持文生图的模型,这里使用wanx2.1-t2i-plus模型

java 复制代码
/**
 * 描述:测试通义千问的图片生成
 *
 * @author xuzili
 * @date 2025/05/22
 */

@Test
public void testQwImage() {
    WanxImageModel wanxImageModel = WanxImageModel.builder()
            .modelName("wanx2.1-t2i-plus")
            .apiKey("api key")
            .build();
    Response<Image> response = wanxImageModel.generate("美女");
    System.out.println(response.content().url());
}

Spring Boot集成Langchain4j和通义千问百炼平台

1、创建Spring Boot项目

这里就不记录项目的创建过程了,贴一下pom文件

xml 复制代码
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xzl</groupId>
    <artifactId>langchain4j_springboot_2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>langchain4j_springboot_2</name>
    <description>langchain4j_springboot_2</description>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>3.0.2</spring-boot.version>
        <langchain4j.version>1.0.0-beta2</langchain4j.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-community-bom</artifactId>
                <version>${langchain4j.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.xzl.Langchain4jSpringboot2Application</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2、 使用普通对话

使用前需要在application.properties中配置对话的模型和api key,这里使用的是qwen-max模型

ini 复制代码
langchain4j.community.dashscope.chat-model.api-key = api key
langchain4j.community.dashscope.chat-model.model-name=qwen-max

具体使用

less 复制代码
/**
 * 描述:
 *
 * @author: xuzili
 * @date: 2025/05/22
 */

@RequestMapping("/ai")
@RestController
public class ChatController {

    private static final Logger log = LogManager.getLogger(ChatController.class);
    @Autowired
    private QwenChatModel qwenChatModel;

    @Autowired
    private QwenStreamingChatModel qwenStreamingChatModel;
    @RequestMapping("chat")
    public String chat(@RequestParam String question) {
        return qwenChatModel.chat(question);
    }
}

测试普通对话
http://localhost:8080/ai/chat?question=你是谁

3、使用流式对话

需要在application.properties中配置流式对话的模型和api key,这里使用的是qwen3-32b模型

ini 复制代码
langchain4j.community.dashscope.chat-model.api-key = api key
langchain4j.community.dashscope.chat-model.model-name=qwen3-32b

还需要使用Web Flux,在pom 文件中添加此依赖

xml 复制代码
<!--使用ai的流式对话需要使用webflux接收响应并返回给前端        -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

具体使用

typescript 复制代码
@RequestMapping(value = "streamChat",produces = "text/stream;charset=UTF-8")
public Flux<String> streamChat(@RequestParam(defaultValue = "你是谁") String question) {
    // http://localhost:8080/ai/streamChat
    Flux<String> result = Flux.create(sink -> {
        qwenStreamingChatModel.chat(question, new StreamingChatResponseHandler() {
            @Override
            public void onPartialResponse(String s) {
                sink.next(s);

            }

            @Override
            public void onCompleteResponse(ChatResponse chatResponse) {
                sink.complete();

            }

            @Override
            public void onError(Throwable throwable) {
                throwable.printStackTrace();
                sink.error(throwable);

            }
        });
    });
    return result;
}

测试流式对话

访问:http://localhost:8080/ai/streamCha, 结果会持续不断的输出,然后直到结束

4、记忆对话(多轮对话)

之前的测试,ai只能知道单次对话的内容,不知道之前的对话内容,所以ai并没有结合上下文去思考问题。比如第一次对话,给ai说我叫张三,第二次对话,问ai我叫什么,ai不会正确回答出你叫张三。所以就需要使用记忆对话功能。

具体使用

主要是通过qwenChatModel.chat()方法将第一次和AI对话的消息和回答,在第二次对话中设置,这样ai就知道第一次对话的内容了,就可以结合上下文进行回答。

ini 复制代码
/**
 * 描述:记忆对话
 *
 * @return {@link String }
 * @author xuzili
 * @date 2025/05/25
 */

@RequestMapping("memoryChat")
public String memoryChat() {
    // http://localhost:8080/ai/memoryChat

    UserMessage userMessage1 = UserMessage.userMessage( "你好,我是张三");
    ChatResponse response1 = qwenChatModel.chat(userMessage1);
    AiMessage aiMessage1 =response1.aiMessage();// 大模型的第一次响应
    System.out.println(aiMessage1.text());
    System.out.println("----");
    //下面一行代码是重点
    ChatResponse response2 = qwenChatModel.chat(userMessage1, aiMessage1, UserMessage.userMessage("我叫什么"));
    AiMessage aiMessage2 = response2.aiMessage();// 大模型的第二次响应
     System.out.println(aiMessage2.text());

    return aiMessage2.text();
}

测试记忆对话
http://localhost:8080/ai/memoryChat

5、ChatMemory方式实现记忆对话

在上面的例子中,会发现手动维护和管理ChatMessage是很麻烦的。 因此,LangChain4j提供了ChatMemory抽象以及多种开箱即用的实现。

引入Lanchain4j的核心依赖

xml 复制代码
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>${langchain4j.version}</version>
</dependency>

配置

封装自定义的对话助手,在其中设置ChatMemory用来保存对话记录

kotlin 复制代码
package com.xzl.config;

import com.fasterxml.jackson.core.TokenStreamFactory;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiConfig {
    /**
     * 描述:
     *
     * @author: xuzili
     * @date: 2025/05/25
     */

    public interface Assistant {
        String chat(String message);
        TokenStream stream(String message);
    }

    @Bean
    public Assistant assistant(ChatLanguageModel chatLanguageModel, StreamingChatLanguageModel streamingChatLanguageModel){
        ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
        // 创建对话助手,
        return AiServices.builder(Assistant.class)
                .chatLanguageModel(chatLanguageModel)
                .streamingChatLanguageModel(streamingChatLanguageModel)
                .chatMemory(chatMemory)
                .build();
    }
}

具体使用对话助手

less 复制代码
@Autowired
private AiConfig.Assistant  assistant;

/**
 * 描述:使用对话助手实现普通对话
 *
 * @param message 消息
 * @return {@link String }
 * @author xuzili
 * @date 2025/05/25
 */

@RequestMapping("assistantChat")
public String assistantChat(@RequestParam(defaultValue = "我叫张三") String message) {
    // http://localhost:8080/ai/assistantChat
    return assistant.chat(message);
}

/**
 * 描述:使用对话助手实现流式对话
 *
 * @param message 消息
 * @return {@link Flux }<{@link String }>
 * @author xuzili
 * @date 2025/05/25
 */

@RequestMapping(value = "assistantStreamChat",produces = "text/stream;charset=UTF-8")
public Flux<String> assistantStreamChat(@RequestParam(defaultValue = "我是谁") String message) {
    // http://localhost:8080/ai/assistantStreamChat
    TokenStream stream = assistant.stream(message);
    return Flux.create(sink -> {
        stream.onPartialResponse(sink::next)
                .onCompleteResponse(c -> sink.complete())
                .onError(sink::error)
                .start();
    });
}

测试

首先访问localhost:8080/ai/assistantChat,告诉ai我叫张三:

然后访问localhost:8080/ai/assistantStreamChat,询问ai我叫什么:

可以看到ai知道我叫张三,说明记忆对话成功

6、function-call的使用

  1. ​定义​​:

    • 当用户提出一个需要具体数据或执行操作的请求时(例如查询天气、计算数学公式、翻译文本),模型会通过"函数调用"触发预定义的外部功能(如API),并将结果返回给用户。
  2. ​工作原理​​:

    • ​意图识别​:模型分析用户输入,判断是否需要调用外部功能。
    • ​函数匹配​:从已注册的函数库中找到最匹配的工具(例如通过自然语言理解映射到特定API)。
    • ​参数解析​:从用户输入中提取函数所需的参数。
    • ​执行与返回​:调用工具获取结果,并将结果整合到回复中。

典型应用场景​

  1. ​数据查询​​:

    • 调用政务API获取重名的人数。
    • 示例:用户问"武汉有多少叫张三的?",模型调用政务API后返回结果。

创建ToolService

kotlin 复制代码
package com.xzl.service;


import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Service;

/**
 * 描述:
 *
 * @author: xuzili
 * @date: 2025/05/27
 */
@Service
public class ToolService {

    /**
     * 描述:测试tool,
     * @Tool告诉ai什么时候调用此方法
     * @P("姓名") 告诉ai需要提取的参数
     *
     *
     * @param name 名称
     * @return {@link Integer }
     * @author xuzili
     * @date 2025/05/27
     */

    @Tool("武汉有多少个名字")
    public Integer getNameCount(@P("姓名") String name) {
        return 10;
    }
}

将ToolService注册到对话助手中

在之前定义的对话助手中增加红色部分内容

使用

less 复制代码
/**
 * 描述:测试function-call
 *
 * @param message 消息
 * @return {@link String }
 * @author xuzili
 * @date 2025/05/27
 */

@RequestMapping(value = "tool")
public String assistantTool(@RequestParam String message) {
    // http://localhost:8080/ai/tool?message=武汉有多少个叫张三的
   return assistant.chat(message);
}

测试function-call

访问:http://localhost:8080/ai/tool?message=武汉有多少个叫张三的 可以看到我们询问ai武汉有多少个叫张三的,ai回答了10个

7、预设角色(系统消息SystemMessage)

基础大模型是没有目的性的,你聊什么给什么,所以在有些场景下我们需要给大模型预设一个角色,保证大模型基于预设角色回答问题;下面告诉ai他是一个叫做蔡徐坤的人,喜欢唱、跳、rap、篮球,在问他喜欢干什么;

使用@SystemMessage注解定义预设角色

在之前的Assistant接口中增加systemMessageChat接口

java 复制代码
/**
 * 描述:测试系统消息
 *
 * @param userMessage 用户消息
 * @return {@link String }
 * @author xuzili
 * @date 2025/06/01
 */

@SystemMessage("你是一个叫做蔡徐坤的人,喜欢唱、跳、rap、篮球")
String systemMessageChat(String userMessage);

测试SystemMessageChat

localhost:8080/ai/systemMessageChat?message=你喜欢干什么?

可以看到ai希望预设的角色,并基于这个角色做出相应的回答。

8、使用SystemMessage实现智能票务助手

但是如果我们开发的是一个智能票务助手, 我需要他以一个票务助手的角色跟我对话,并且在我跟他说"退票"的时候,让大模型一定要告诉我"车次"和"姓舌",这样我才能去调用业务方法(假设有一个业务方法,需要根据车子和姓名才能查询具体车票),进行退票。

在之前的Assistant接口中增加customerServiceChat方法

less 复制代码
/**
 * 描述:航空公司客服
 * 当接口方法中有两个或以上的参数,需要使用 @UserMessage注解标记告诉 langchain4j,该参数是用户消息,而不是一个参数。
 *
 * @param userMessage 用户消息
 * @return {@link String }
 * @author xuzili
 * @date 2025/06/01
 */

@SystemMessage(""春秋"航空公司的客户聊天支持代理。请以友好、乐于助人且愉快的方式来回复。 您正在通过在线聊天系统与客户互动 " +
        "在提供有关预订或取消预订的信息之前,您必须始终从用户处获取以下信息:预订号、客户姓名" +
        "请讲中文。" +
        "今天的日期是{{current_date}}")
TokenStream customerServiceChat(@UserMessage String userMessage, @V("current_date") String currentDate);

在之前的ToolService类中增加refundTicket方法

less 复制代码
@Tool("退票")
public String refundTicket(@P("姓名") String name,@P("预订号") String number) {
    System.out.println("退票申请已受理");

    return name + "您好"  + number + "的退票申请已受理";
}

在之前的ChatController中增加

less 复制代码
/**
 * 描述:测试system message
 *
 * @param message 消息
 * @return {@link String }
 * @author xuzili
 * @date 2025/05/27
 */

@RequestMapping(value = "customerServiceChat",produces = "text/stream;charset=UTF-8")
public Flux<String> customerServiceChat(@RequestParam String message) {
    // http://localhost:8080/ai/customerServiceChat?message=你好

    TokenStream stream = assistant.customerServiceChat(message, String.valueOf(LocalDate.now()));
    return Flux.create(sink -> {
        stream.onPartialResponse(sink::next)
                .onCompleteResponse(c -> sink.complete())
                .onError(sink::error)
                .start();
    });
}

测试

先打招呼 询问退票

办理退票

控制台也打印了退票日志

9、RAG向量检索增强

为什么需要RAG

传统的语言模型,比如 GPT-3,虽然在生成文本方面表现出色,但它们有一个显著的局限性:它们依赖于预训练的参数,无法动态访问外部知识。这意味着这些模型在处理实时信息、领域特定知识或罕见实体时表现不佳。举个例子,在问答任务中,模型可能会生成不准确或过时的答案,因为它无法访问最新的数据。就像你问一个朋友"今天天气怎么样?",但他只能告诉你去年的天气情况,显然这样的信息对你来说毫无用处。这种局限性在需要精确答案的场景中尤为明显。例如,在医疗领域,医生可能需要最新的研究数据来做出诊断,而传统的语言模型无法提供这些信息。

什么是 RAG?

RAG(Retrieval Augmented Generation)检索增强生成,即大模型LLM在回答问题或生成文本时,会先从大量的文档中检索出相关信息,然后基于这些检索出的信息进行回答或生成文本,从而可以提高回答的质量,而不是任由LLM来发挥。RAG技术使得开发者没有必要为每个特定的任务重新训练整个大模型,只需要外挂上相关知识库就可以,即可为模型提供额外的信息输入,提高回答的准确性。

简单来说,RAG 是一种在发送给 LLM 之前,从你的数据中找到并注入相关信息片段到提示中的方法。 这样 LLM 将获得相关信息,并能够使用这些信息回复, 这会降低产生幻觉的概率。

  • 全文(关键词)搜索。这种方法使用 TF-IDF 和 BM25 等技术, 通过匹配查询(例如,用户提问的内容)中的关键词与文档数据库进行搜索。 它根据每个文档中这些关键词的频率和相关性对结果进行排名。
  • 向量搜索,也称为"语义搜索"。 文本文档使用嵌入模型转换为数字向量。 然后根据查询向量和文档向量之间的余弦相似度 或其他相似度/距离度量找到并排序文档, 从而捕捉更深层次的语义含义。
  • 混合搜索。结合多种搜索方法(例如,全文 + 向量)通常可以提高搜索的有效性。

文本向量化

简单来说就是将人类可读的文本转化为ai可读的信息,向量就是一组基于文本含义生成的特征值;

scss 复制代码
/**
 * 描述:测试文本向量化
 *
 * @author xuzili
 * @date 2025/06/07
 */

@Test
void testTextVectorization() {
    QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder()
            .apiKey("key")
            .build();
    Response<Embedding> embed = embeddingModel.embed("我叫真爱坤,喜欢唱、跳、篮球");
    // 打印向量坐标
    System.out.println(embed);
    // 打印向量的坐标数量
    System.out.println(embed.content().vector().length);
}

打印结果

向量检索

就是将需要查询的问题,转化为向量,然后使用这个查询向量到向量数据库中查询,会根据查询向量做相似性分析,并得到一个分数,分数越高说明相似性越高,也就代表对应的候选向量最满足检索需求;

ini 复制代码
/**
 * 描述:测试向量检索
 *
 * @author xuzili
 * @date 2025/06/07
 */

@Test
void testVectorSearch() {
    // 创建向量模型
    QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder()
            .apiKey("sk-62f684f1658144a4b3b6f7e9bee9497b")
            .build();
    // 创建向量数据库
    InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();

    // 创建一个新的文本片段
    TextSegment trueKunTextSegment = TextSegment.from("我叫真爱坤,喜欢唱、跳、篮球");
    // 将文本向量化
    Response<Embedding> trueKunEmbedding = embeddingModel.embed(trueKunTextSegment);
    // 将向量添加到向量数据库中
    embeddingStore.add(trueKunEmbedding.content(), trueKunTextSegment);

    TextSegment falseKunTextSegment = TextSegment.from("我叫假爱坤,喜欢干饭、摸鱼");
    Response<Embedding> falseKunEmbedding = embeddingModel.embed(falseKunTextSegment);
    embeddingStore.add(falseKunEmbedding.content(),falseKunTextSegment);

    Response<Embedding> queryEmbedding = embeddingModel.embed("真爱坤喜欢干什么?");

    // 创建向量检索
    EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
            .queryEmbedding(queryEmbedding.content())
            .maxResults(1)
            .build();

    // 执行向量检索
    EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest);
    // 输出结果
    searchResult.matches()
            .forEach(embeddingMatch -> {
                // 输出分数
                System.out.println(embeddingMatch.score());
                // 输出向量
                System.out.println(embeddingMatch.embedding());
                // 输出文本
                System.out.println(embeddingMatch.embedded().text());
            });

}

打印结果

10、知识库RAG实战

创建知识库对话方法

在Assisant接口中增加knowledgeBaseChat方法

java 复制代码
/**
 * 描述:知识库问答
 *
 * @param message 消息
 * @return {@link TokenStream }
 * @author xuzili
 * @date 2025/06/07
 */

TokenStream knowledgeBaseChat(String message);

创建一个向量数据库(EmbeddingStore)

java 复制代码
/**
 * 描述:创建一个基于内存的向量数据库
 *
 * @return {@link EmbeddingStore }<{@link TextSegment }>
 * @author xuzili
 * @date 2025/06/07
 */

@Bean
public EmbeddingStore<TextSegment> createEmbeddingStore(){
    return new InMemoryEmbeddingStore<>();
}

创建向量检索器(EmbeddingStoreContentRetriever)

改造创建对话助手的方法

scss 复制代码
/**
 * 描述:创建ai助手
 *
 * @param chatLanguageModel 对话模型
 * @param streamingChatLanguageModel 流式对话模型
 * @param toolService function call工具服务
 * @param embeddingModel 向量模型
 * @param embeddingStore 向量数据库
 * @return {@link Assistant }
 * @author xuzili
 * @date 2025/06/07
 */

@Bean
public Assistant assistant(ChatLanguageModel chatLanguageModel,
                           StreamingChatLanguageModel streamingChatLanguageModel,
                           ToolService toolService,
                           EmbeddingModel embeddingModel,
                           EmbeddingStore<TextSegment> embeddingStore

){
    ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
    // 创建向量检索器
    EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
            .embeddingModel(embeddingModel)
            .embeddingStore(embeddingStore)
            .maxResults(5)
            .minScore(0.6)
            .build();
    // 创建对话助手,
    return AiServices.builder(Assistant.class)
            // 添加function call工具服务
            .tools(toolService)
            .chatLanguageModel(chatLanguageModel)
            .streamingChatLanguageModel(streamingChatLanguageModel)
            .chatMemory(chatMemory)
            .contentRetriever(contentRetriever)
            .build();
}

创建向量检索器EmbeddingStoreContentRetriever contentRetriever

scss 复制代码
// 创建向量检索器
EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
        .embeddingModel(embeddingModel)
        .embeddingStore(embeddingStore)
        .maxResults(5)
        .minScore(0.6)
        .build();

将向量检索器注入到对话助手中

scss 复制代码
.contentRetriever(contentRetriever)

初始化知识库数据

typescript 复制代码
/**
 * 描述:初始化知识库
 *
 * @param embeddingStore 向量数据库
 * @param embeddingModel 向量模型
 * @return {@link CommandLineRunner }
 * @author xuzili
 * @date 2025/06/07
 */

@Bean
public CommandLineRunner initKnowledgeBase(EmbeddingStore<TextSegment> embeddingStore,EmbeddingModel embeddingModel){
    return args -> {
        // 创建文档
        Document document = ClassPathDocumentLoader.loadDocument("document/document.txt");
        System.out.println(document.text());

        // 文档分割
        // 文本读取过来后还需要分成一段一段的片段(分块chunk),分块是为了更好地拆分语义单元,
        // 这样在后面可以更精确地进行语义相似性检索,也可以避免LLM的Token限制。
        DocumentBySentenceSplitter splitter = new DocumentBySentenceSplitter(
                // 每个分块的长度
                100,
                // 重叠的长度
                20);
        // 分词器会先按照分词规则拆分,然后按照分块长度拆分,最后按照重叠长度进行重叠
        List<TextSegment> textSegments = splitter.split(document);
        System.out.println(textSegments);
        // 将文档向量化
        Response<List<Embedding>> listResponse = embeddingModel.embedAll(textSegments);
        //  将向量添加到向量数据库中
        embeddingStore.addAll(listResponse.content(), textSegments);

    };
}

新增知识库对话接口

less 复制代码
/**
 * 描述:测试RAG应用知识库
 *
 * @param message 消息
 * @return {@link String }
 * @author xuzili
 * @date 2025/05/27
 */

@RequestMapping(value = "knowledgeBaseChat",produces = "text/stream;charset=UTF-8")
public Flux<String> knowledgeBaseChat(@RequestParam String message) {
    // http://localhost:8080/ai/knowledgeBaseChat?message=退费
    TokenStream stream = assistant.knowledgeBaseChat(message);
    return Flux.create(sink -> {
        stream.onPartialResponse(sink::next)
                .onCompleteResponse(c -> sink.complete())
                .onError(sink::error)
                .start();
    });
}

测试知识库对话

http://localhost:8080/ai/knowledgeBaseChat?message=退费

相关推荐
Jooolin3 小时前
【编程史】Git是如何诞生的?这可并非计划之中...
linux·git·ai编程
爱喝喜茶爱吃烤冷面的小黑黑3 小时前
小黑一层层削苹果皮式大模型应用探索:langchain中智能体思考和执行工具的demo
python·langchain·代理模式
Jooolin4 小时前
【编程史】IDE 是谁发明的?从 punch cards 到 VS Code
ai编程·visual studio code·编译器
Lilith的AI学习日记5 小时前
什么是预训练?深入解读大模型AI的“高考集训”
开发语言·人工智能·深度学习·神经网络·机器学习·ai编程
程序员陆通5 小时前
Vibe Coding AI编程
ai编程
Jaising6666 小时前
JetBrains AI 打零工(一)——生产力工具与程序员的驾驭之道
ai编程·intellij idea
翔云1234568 小时前
2025年AI编程工具推荐
ai编程
用户28988180666429 小时前
如何定制个人智能体
ai编程·coze
大千AI9 小时前
LangChain Core架构解析:模块化设计与LCEL原语实现原理
langchain
Chatopera 研发团队9 小时前
智能体开发,实现自定义知识库,基于 LangChain,qwen 7b, ollama, chatopera | LLMs
langchain