Contents

企业知识库问答系统(2.LangChain4j的AIService和RAG使用)

企业知识库问答系统(2.LangChain4j的AIService和RAG使用)

过程

上一篇文章已经成功接入了通义大模型完成了简单对话的测试。现在使用LangChain4j的进阶功能AIService。

1.编写用于创建AIService的接口

在这里指定使用的模型为我们之前创建的模型bean名称,@AIService注解会自动创建QwenChatService代理对象注册到Spring

@AiService(wiringMode = EXPLICIT, chatModel = "myQwenChatModel")
public interface QwenChatService {
    String chat(@UserMessage String message);
}

2.调用

创建测试,直接使用这个Service

@Slf4j
@SpringBootTest
public class QwenChatServiceTest {
    @Autowired
    private QwenChatService qwenChatService;
    @Test
    void chatUseService(){
        log.info(qwenChatService.chat("你是谁"));
    }
}

输出如下

2025-11-13T14:29:03.415+08:00  INFO 25884 --- [aiLearning] [           main] c.h.a.ai.service.QwenChatServiceTest     : 我是Qwen,由阿里云开发的预训练语言模型。我的目的是帮助用户获得准确、有用的信息,并且支持多轮对话。有什么我可以帮到你的吗?

3.省略模型定义

之前使用AIService注解指定了myQwenChatModel模型,这是因为不指定会报错有myQwenChatModel和qwenChatModel两个模型可以使用。因为在项目启动时,langchain自动把配置文件中的qwen模型注册为了qwenChatModel,所以,我们可以注释调自定义的模型,这样AIService注解也不用指定了。

@AiService
public interface QwenChatService {
    String chat(@UserMessage String message);
}

4.引入RAG功能

首先编写RagService,用于文档解析,向量数据存储,向量结果查询

@Configuration
public class myRagService {
    @Autowired
    private EmbeddingModel qwenEmbeddingModel;

    @Autowired
    private EmbeddingStore<TextSegment> embeddingStore;

    @Bean
    public ContentRetriever contentRetriever() {
        // ------ RAG ------
        // 1. 加载文档
        List<Document> documents = FileSystemDocumentLoader.loadDocuments("src/main/resources/docs");
        // 2. 文档切割:将每个文档按每段进行分割,最大 1000 字符,每次重叠最多 200 个字符
        DocumentByParagraphSplitter paragraphSplitter = new DocumentByParagraphSplitter(1000, 200);
        // 3. 自定义文档加载器
        EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
                .documentSplitter(paragraphSplitter)
                // 为了提高搜索质量,为每个 TextSegment 添加文档名称
                .textSegmentTransformer(textSegment -> TextSegment.from(
                        textSegment.metadata().getString("file_name") + "\n" + textSegment.text(),
                        textSegment.metadata()
                ))
                // 使用指定的向量模型
                .embeddingModel(qwenEmbeddingModel)
                .embeddingStore(embeddingStore)
                .build();
        // 加载文档
        ingestor.ingest(documents);
        // 4. 自定义内容查询器
        ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(qwenEmbeddingModel)
                .maxResults(5) // 最多 5 个检索结果
                .minScore(0.75) // 过滤掉分数小于 0.75 的结果
                .build();
        return contentRetriever;
    }
}

5.创建带RAG的ChatService

我们原来使用@AIService创建了chatService,这个注解虽然好用,但是只能创建基础对话模型。现在我们需要手动创建,注释调这个注解。

//@AiService
public interface QwenChatService {
    String chat(@UserMessage String message);
}

public class QwenChatServiceFactory {
    @Autowired
    private ChatModel myQwenChatModel;

    @Autowired
    private ContentRetriever contentRetriever;

    @Bean
    public QwenChatService qwenChatService() {
        // 构造 AI Service
        QwenChatService aiCodeHelperService = AiServices.builder(QwenChatService.class)
                .chatModel(myQwenChatModel)
                .contentRetriever(contentRetriever) // RAG 检索增强生成
                .build();
        return aiCodeHelperService;
    }
}

这里使用AiServices.builder手动创建,引入我们之前创建的myQwenChatModel和RAG的contentRetriever。返回的qwenChatService这个bean可以当作service使用。

6.测试

为了更好的查看rag引用情况,这里修改chatService的返回

//@AiService
public interface QwenChatService {
    Result<String> chat(@UserMessage String message);
}

同时修改测试

@Slf4j
@SpringBootTest
public class QwenChatServiceTest {
    @Autowired
    private QwenChatService qwenChatService;
    @Test
    void chatUseService(){
        Result<String> result = qwenChatService.chat("怎么学习 Java?有哪些常见面试题?");
        System.out.println(result.content());
        System.out.println(result.sources());//获取引用的文档
    }
}

输出结果如下(截取最后的部分内容),可以看到引用了文档

#### 学习建议

1)坚持:初学一门语言时,一定要持续学习,不能中断!

2)实践:想要学好编程,一定要多敲代码!建议先跟着书上的例子敲一遍代码,然后试着自主编写代码,并完成课后练习。

3)万事开头难:不理解代码也没关系,可以学习 Debug 后,一行一行地打断点执行,查看程序的执行过程。千万不要觉得麻烦,养成习惯后真的能节省很多重复学习的时间。

#### 经典面试题

1. 为什么重写 equals 还要重写 hashcode?
2. == 和 equals 比较的区别
3. 为啥有时会出现 4.0 - 3.6 = 0.40000001 这种现象?
4. final 关键字的作用
5. 介绍 Java 的集合类
6. ArrayList 和 LinkedList 的区别" metadata = {absolute_directory_path=D:\SOFT\aiLearning\src\main\resources\docs, index=0, file_name=Java 编程学习路线.md} }, metadata = {EMBEDDING_ID=5400b60b-0666-4b6d-9a85-3b62032fad42, SCORE=0.855345466124221} }]