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=退费

相关推荐
用户3521802454752 小时前
MCP极简入门:node+idea运行简单的MCP服务和MCP客户端
node.js·ai编程
每天开心3 小时前
一文教你掌握事件机制
前端·javascript·ai编程
每天开心5 小时前
深入理解 CSS 选择器:从基础到高级
css·html·ai编程
俞乾6 小时前
Context Engineering(上下文工程)是 AI Agent 成功的关键吗?
openai·ai编程
kingchen7 小时前
稳定的Claude Code渠道,白嫖100刀
ai编程
量子位7 小时前
“英伟达显卡就是一坨 ”!博主 6000 字檄文怒批:烧接口、缺单元、驱动变砖还威胁媒体
ai编程
运器1237 小时前
【一起来学AI大模型】算法核心:数组/哈希表/树/排序/动态规划(LeetCode精练)
开发语言·人工智能·python·算法·ai·散列表·ai编程
量子位7 小时前
谁是余家辉?“年薪 1 亿美元”AI 研究员,中科大少年班天才,吴永辉的嫡系弟子
aigc·ai编程
qiyue7710 小时前
AI编程专栏(一)- 评估AI编程工具对编程语言支持情况
前端·ai编程
我爱一条柴ya11 小时前
【AI大模型】线性回归:经典算法的深度解析与实战指南
人工智能·python·算法·ai·ai编程