LangChain4j-RAG
RAG检索增强生成(retrieval-augmented-generation)
向量文本化
//向量模型
QwenEmbeddingModel model = QwenEmbeddingModel.builder()
.apiKey("xxxx")
.build();
Response<Embedding> embed = model.embed("你好");
System.out.println(embed.content());
System.out.println(embed.content().vector().length);//向量维度
向量检索
//embedding阶段
//内存的向量数据库
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore();
//向量模型
QwenEmbeddingModel model = QwenEmbeddingModel.builder()
.apiKey("xxxx")
.build();
//向量化
TextSegment segment1 = TextSegment.from("""
预定航班:
- 通过我们的网站或移动应用程序预定
- 预定时需要全额付款
- 确保个人信息(姓名、id)的准确性,会产生25的费用
""");
Embedding embedding1 = model.embed(segment1).content();
TextSegment segment2 = TextSegment.from("""
取消预定:
- 最晚在航班起飞前48小时取消
- 取消费用:50
- 退款将在7个工作日内处理
""");
Embedding embedding2 = model.embed(segment2).content();
//存储到向量数据库
store.add(embedding1,segment1);
store.add(embedding2,segment2);
//数据检索阶段
//查询内容向量化
Embedding queryEmbedding = model.embed("退票多少钱").content();
//构建查询条件
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(1)//最多查询几条数据
// .minScore(0.8)//最小分数
.build();
//从向量数据库中查询
EmbeddingSearchResult<TextSegment> result = store.search(request);
//查询结果
result.matches().forEach(embeddingMatch -> {
System.out.println(embeddingMatch.score());//相似度分数,最高1
System.out.println(embeddingMatch.embedded().text());//检索到的文本
});
检索增强
将检索出来的向量携带到大模型中
langchain4j中使用AiServices创建动态代理来简化操作
//embedding阶段
//内存的向量数据库
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore();
//向量模型
QwenEmbeddingModel model = QwenEmbeddingModel.builder()
.apiKey("xxxx")
.build();
//向量化
TextSegment segment1 = TextSegment.from("""
预定航班:
- 通过我们的网站或移动应用程序预定
- 预定时需要全额付款
- 确保个人信息(姓名、id)的准确性,会产生25的费用
""");
Embedding embedding1 = model.embed(segment1).content();
TextSegment segment2 = TextSegment.from("""
取消预定:
- 最晚在航班起飞前48小时取消
- 取消费用:50
- 退款将在7个工作日内处理
""");
Embedding embedding2 = model.embed(segment2).content();
//存储到向量数据库
store.add(embedding1,segment1);
store.add(embedding2,segment2);
//检索增强
//查询模型
QwenChatModel qw = QwenChatModel.builder()
.apiKey("xxxxx")
.modelName("qwen-max")
.build();
EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)//向量数据库
.embeddingModel(model)//向量化模型
.maxResults(5)//最大条数
.minScore(0.6)//最低分数
.build();
//生成代理
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(qw)
.contentRetriever(retriever)
.build();
System.out.println(assistant.chat("退票多少钱"));
文档解析器
如果要开发一个知识库系统,这些资料可能在各种文件中,比如word、txt、pdf、image、html等等,所以langchain4j提供了不同的文档解析器
TextDocumentParser
:可用解析纯文本格式(txt、html、md等),来自langchain4j模块ApachePdfBoxDocumentParser
:可以解析pdf文件,来自langchain4j-document-parser-apache-pdfboxApachePoiDocumentParser
:可以解析Office文件,来自langchain4j-document-parser-apache-poiApacheTikaDocumentParser
:可以自动检测和解析所以的文件格式,来自langchain4j-document-parser-apache-tika模块
使用:
//classpath
Document document = ClassPathDocumentLoader.loadDocument("rag/test01.txt", new TextDocumentParser());
System.out.println(document.text());
//绝对路径
Document document = FileSystemDocumentLoader.loadDocument("D:\\IDEAProject\\langchain-demo\\src\\main\\resources\\img\\test01.txt");
System.out.println(document);
文档拆分器
由于文本读取过来后,还需要分成一段一段的片段(分块chunk),分块是蔚来更好的拆分语义单元,这样在后面可以更精确的进行语义相似性检索,也可以避免llm的token限制
langchain4j分割器
分割器 | 能力 |
---|---|
DocumentByCharacterSplitter | 无符号分割器(按照字数) |
DocumentByRegexSplitter | 正则表达式分割 |
DocumentByParagraphSplitter | 删除大段空白内容 |
DocumentByLineSplitter | 删除单个换行符周围的空白 |
DocumentByWordSplitter | 删除连续的空白字符 |
DocumentBySentenceSplitter | 按句子分割 |
使用:
Document document = ClassPathDocumentLoader.loadDocument("rag/test01.txt", new TextDocumentParser());
DocumentByCharacterSplitter splitter = new DocumentByCharacterSplitter(
50,//每段最长字数
5);//自然语义最大重叠数,避免断句(这个分割器会忽略这个参数)
List<TextSegment> split = splitter.split(document);//对文档进行分割
split.forEach(System.out::println);
整合springboot
导入依赖
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0.0-beta1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>1.0.0-beta1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置文件
langchain4j:
community:
dashscope:
chat-model:
api-key: xxxx
model-name: qwen-max
embedding-model:
api_key: xxxx
配置bean
//向量数据库
@Bean
public EmbeddingStore embeddingStore(){
return new InMemoryEmbeddingStore();
}
@Bean
public Assistant assistant(ChatLanguageModel chatLanguageModel,
StreamingChatLanguageModel streamingChatLanguageModel,
QwenEmbeddingModel qwenEmbeddingModel) {
//记忆对话
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
//内容检索器
EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore())
.embeddingModel(qwenEmbeddingModel)
.maxResults(5)
.minScore(0.6)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.streamingChatLanguageModel(streamingChatLanguageModel)
.chatMemory(chatMemory)
.contentRetriever(retriever)//指定检索器
.build();
return assistant;
}
//初始化数据
@Bean
public CommandLineRunner commandLineRunner(QwenEmbeddingModel qwenEmbeddingModel){
return args -> {
//获取文档数据
Document document = ClassPathDocumentLoader.loadDocument("rag/test01.txt", new TextDocumentParser());
//分割
DocumentByLineSplitter splitter = new DocumentByLineSplitter(500, 200);
List<TextSegment> textSegments = splitter.split(document);
//向量化
List<Embedding> embeddingList = qwenEmbeddingModel.embedAll(textSegments).content();
//存入向量数据库
embeddingStore().addAll(embeddingList, textSegments);
};
}
controller
@Autowired
private AssistantUnique assistantUnique;
@RequestMapping("assistant/chat")
public String assistantChat(@RequestParam String message){
return assistant.chat(message);
}