企业知识库问答系统(2.LangChain4j的AIService和RAG使用)
Contents
企业知识库问答系统(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} }]