目錄
一、前言
二、LangChain4j 介紹
2.1 什么是LangChain4j
2.2 LangChain4j 主要特點
2.3?Langchain4j 核心組件
三、RAG介紹
3.1 什么是RAG
3.2 RAG工作流程
3.2.1 補充說明
3.3 Embedding模型
3.3.1 RAG實際使用步驟
3.3.2 什么是Embedding
3.3.3 Embedding 技術優缺點
3.3.4 Embedding 技術在大模型中的價值
四、LangChain4j整合RAG操作實戰
4.1 前置準備
4.1.1 導入核心依賴
4.1.2 搭建pgVector向量數據庫
4.1.3 添加配置信息
4.1.4 Document Loader介紹
4.2 基于本地內存作為向量數據庫操作過程
4.2.1 添加配置信息
4.2.2 添加測試文檔
4.2.3 配置InMemoryEmbeddingStore
4.2.4 增加測試接口
4.2.5 效果測試
4.3 基于pgVector作為向量數據庫操作過程
4.3.1 增加配置類
4.3.2 增加pgVector配置類
4.3.3 準備幾個文檔
4.3.4 提供文檔加載接口
4.3.5 提供一個對話接口
4.3.6 文檔加載優化補充
五、寫在文末
一、前言
盡管AI大模型(如GPT-4、DeepSeek等)在自然語言處理任務中表現出色,但它們仍然存在一些局限性,而RAG(Retrieval-Augmented Generation,檢索增強生成)技術可以很好地彌補這些不足。舉例來說,企業或個人都希望擁有一款屬于自己的AI助手,能夠幫自己隨時解決一些特定場景或特定領域的問題,在這種場景下,AI大模型中的RAG技術就派上用場了,簡單來說,它就是一款可以問你量身打造的大模型知識庫,當你需要某個知識的時候為你提供更貼合實際業務場景的回答。
二、LangChain4j 介紹
2.1 什么是LangChain4j
LangChain4j作為一款專注于AI大模型集成的開源庫,近年來受到了廣泛關注。它旨在為開發者提供一種簡單且高效的方式來接入和利用各種AI大模型,從而提升應用程序的智能化水平。LangChain4j的核心優勢在于其高度的靈活性和易用性,使得開發者可以在不改變現有架構的前提下,快速實現AI功能的集成。
開發者文檔地址:Introduction | LangChain4j
git地址:GitCode - 全球開發者的開源社區,開源代碼托管平臺
2.2 LangChain4j 主要特點
LangChain4j 主要具備如下特點:
-
模塊化設計
-
LangChain4j 采用模塊化架構,允許開發者根據需要選擇和使用特定功能,如模型集成、數據加載、鏈式調用等。
-
-
多模型支持:
-
支持多種 LLM 提供商,如 OpenAI、Hugging Face 等,方便切換和集成不同模型。
-
-
鏈式調用:
-
提供鏈式調用功能,允許將多個任務串聯,如文本生成后自動進行情感分析。
-
-
數據加載與處理:
-
內置多種數據加載器和處理器,支持從不同來源加載數據并進行預處理。
-
-
擴展性好
-
提供豐富的 API 和擴展點,開發者可以自定義組件以滿足特定需求。
-
-
社區活躍
-
擁有活躍的社區和詳細的文檔,便于開發者獲取支持和學習。
-
2.3?Langchain4j 核心組件
Langchain4j 的強大之處正是在于其內置了豐富的可以做到開箱即用的組件能力,相比Spring AI,Langchain4j 的組件更加豐富,在使用的時候也更靈活,下面是一張關于Langchain4j 的組件全景圖。
三、RAG介紹
3.1 什么是RAG
RAG(Retrieval-Augmented Generation)的核心思想是:將傳統的信息檢索(IR)技術與現代的生成式大模型(如chatGPT)結合起來。
具體來說,RAG模型在生成答案之前,會首先從一個大型的文檔庫或知識庫中檢索到若干條相關的文檔片段。再將這些檢索到的片段作為額外的上下文信息,輸入到生成模型中,從而生成更為準確和信息豐富的文本。
3.2 RAG工作流程
RAG的工作原理可以分為以下幾個步驟:
-
接收請求:首先,系統接收到用戶的請求(例如提出一個問題)。
-
信息檢索(R):系統從一個大型文檔庫中檢索出與查詢最相關的文檔片段。這一步的目標是找到那些可能包含答案或相關信息的文檔。
-
生成增強(A):將檢索到的文檔片段與原始查詢一起輸入到大模型(如chatGPT)中,注意使用合適的提示詞,比如原始的問題是XXX,檢索到的信息是YYY,給大模型的輸入應該類似于:請基于YYY回答XXXX。
-
輸出生成(G):大模型基于輸入的查詢和檢索到的文檔片段生成最終的文本答案,并返回給用戶。
3.2.1 補充說明
第2步驟中的信息檢索,不一定必須使用向量數據庫,可以是關系型數據庫(如MySQL,PG)、全文搜索引擎(如Elasticsearch, ES),非關系數據庫(Mongodb,Redis等)
- 大模型應用場景廣泛使用向量數據庫的原因是:在大模型RAG的應用場景中,主要是要查詢相似度高的某幾個文檔,而不是精確的查找某一條(MySQL、ES擅長)。
- 相似度高的兩個文檔,可能不包含相同的關鍵詞。例如,句子1: "他很高興。" 句子2: "他感到非常快樂。" 雖然都是描述【他】很開心快樂的心情,但是不包含相同的關鍵詞;
- 包含相同的關鍵詞的兩個文檔可能完全沒有關聯,例如:句子1: "他喜歡蘋果。" 句子2: "蘋果是一家大公司。" 雖然都包含相同的關鍵詞【蘋果】,但兩句話的相似度很低。
3.3 Embedding模型
3.3.1 RAG實際使用步驟
基于上面的描述,在實際落地RAG應用的時候,通常的思路是,大模型+本地(私有化)向量數據庫,簡單來說分為下面兩步:
-
搭建私有化的向量數據庫,可以是PG,es等,或者使用云上的付費向量數據庫;
-
基于企業自身的文檔資料,或其他業務資料,將這些原始的資料加載到私有向量數據庫;
-
應用問題或知識檢索時,大模型能力驅動,參考私有的向量數據庫文檔資料綜合回答,這樣給出的答案更符合企業的實際情況;
3.3.2 什么是Embedding
在上一步中,將本地的各類文檔加載到向量數據庫的過程中,向量數據庫完成這一步驟的關鍵技術中,Embedding 技術在其中承擔著非常重要的角色。
Embedding 技術是一種將高維數據映射到低維空間的方法,通常用于將離散的、非連續的數據轉換為連續的向量表示,以便于計算機進行處理。
3.3.3 Embedding 技術優缺點
Embedding 技術優點:
- 語義信息捕捉
- Embedding 技術能夠捕捉數據的語義信息,使得相似的數據在嵌入空間中更接近,有助于模型更好地理解數據之間的關系。
- 維度約減
- Embedding 技術將高維數據映射到低維空間,減少了計算和內存需求,提高了模型的效率。
- 上下文感知
- 嵌入向量通常是上下文感知的,可以考慮數據點與其周圍數據點的關系,這對于自然語言處理等任務非常有用。
- 可訓練
- 嵌入向量通常是可訓練的,可以與模型一起訓練,從而適應特定任務和數據集。
- 泛化能力
- 適當訓練的嵌入可以提高模型的泛化能力,從而使其能夠處理新數據和未知情況。
Embedding 技術缺點:
- 數據依賴性:
- Embedding 技術的性能高度依賴于訓練數據的質量和多樣性。如果訓練數據不足或不具代表性,嵌入可能不準確。
- 維度選擇:
- 選擇適當的嵌入維度可以是挑戰性的,太低的維度可能喪失信息,太高的維度可能增加計算成本。
- 過擬合:
- 嵌入可以過度擬合訓練數據,特別是在小數據集上。這可能導致模型在未見過的數據上表現不佳。
- 計算復雜性:
- 在訓練嵌入時,可能需要大量的計算資源和時間,尤其是對于大規模數據集和高維度嵌入。
- 可解釋性差:
- 嵌入向量通常是抽象的,難以解釋。這使得難以理解模型為什么做出特定的預測或推薦。
3.3.4 Embedding 技術在大模型中的價值
大模型在文本生成上的優勢很明顯,但是大模型的特性也帶來的一定的弱勢;
-
訓練數據不實時(如ChatGPT是基于2021年9月之前的數據訓練),重新訓練成本過高,不現實。
-
輸入文本長度有限制,通常限制在幾千到數萬個tokens之間。
-
無法訪問不能公開的文檔。
OpenAI發布了一篇文檔,說明如何基于embedding使用兩步搜索的方式來解決GPT無法處理長文本和最新數據的問題,即 RAG 技術。所以說Embedding是RAG應用落地的核心技術。其在很大程度上決定了搜索相關文本的質量以及影響大模型的輸出效果。
下面是一張關于LangChain4j目前支持的嵌入模型
四、LangChain4j整合RAG操作實戰
下面通過實際案例完整演示下如何在SpringBoot項目中使用LangChain4j完成RAG的完整使用過程。
4.1 前置準備
4.1.1 導入核心依賴
創建一個springboot工程,并導入下面的核心依賴
<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><langchain.version>1.0.0-beta1</langchain.version><springboot.version>3.2.4</springboot.version>
</properties><dependencies><!-- JSON 處理 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-tika-document-reader</artifactId><version>1.0.0-M3</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.35</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java --><dependency><groupId>com.alibaba</groupId><artifactId>dashscope-sdk-java</artifactId><version>2.16.9</version></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>${langchain.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.32</version> <!-- 使用最新版本 --></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-web-search-engine-searchapi</artifactId><version>${langchain.version}</version></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-pgvector</artifactId><version>${langchain.version}</version></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-easy-rag</artifactId><version>${langchain.version}</version></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${springboot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-bom</artifactId><version>${langchain.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository>
</repositories><build><finalName>boot-docker</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>
4.1.2 搭建pgVector向量數據庫
向量數據庫的選擇有很多,比如pgVector,es,Milvus,Weaviate,Redis Stack,Chroma等,這里選擇pgVector,首先使用docker命令快速部署一個pgVector服務
docker run -d --name pgvector -p 5432:5432 -v /root/pgv/data:/var/lib/postgresql/data -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres pgvector/pgvector:pg17
4.1.3 添加配置信息
在工程的配置文件中,添加如下信息,在下面的配置文件中,為了后面使用向量數據庫的能力,主要包括下面幾塊核心的配置信息:
-
langchain4j核心配置,基于langchain4j連接大模型的能力;
-
這里選擇了阿里云百煉大模型平臺中的qwen-max這個大模型,apikey也是這個平臺的;
-
選擇的模型,建議直接使用那些支持嵌入模型的;
-
-
pgvector配置;
-
本地的文檔將會加載到pgvector的向量數據庫中進行存儲;
-
server:port: 8081langchain4j:community:dashscope:chat-model:api-key: 你的阿里云百煉平臺的apikeymodel-name: qwen-maxpgvector:database: springaihost: pgVector的服務器地址port : 5432user: postgrespassword: postgrestable: my_tablespring:ai:dashscope:api-key: sk-你的阿里云百煉平臺的apikeyembedding:options:model: text-embedding-v2
4.1.4 Document Loader介紹
用于讀取文件內容并寫入到向量數據庫中,在這里Langchain4j中集成了apache tika, 能夠從許多種文件格式中提取文本內容。像功能比較強大的 Apache tika,入口:Introduction | LangChain4j
tika 內置以下幾種讀取文件的方式:
-
FileSystemDocumentLoader : 根據文件絕對路徑來讀取
-
ClassPathDocumentLoader : 根據classpath來讀取
-
UrlDocumentLoader : 根據提供的url來讀取
其他一些loader示例
Document Loaders | LangChain4j
4.2 基于本地內存作為向量數據庫操作過程
如果是為了快速驗證效果,或者開發做測試,可以使用基于本地內存的方式進行模擬測試,在Langchain4j中提供了 InMemoryEmbeddingStore 這個對象,可用于作為內存級的向量數據庫使用,參考下面的執行步驟。
4.2.1 添加配置信息
在配置文件中添加下面的配置信息
-
embedding-model 一定要配置,這里選擇的是阿里云百煉大平臺中的支持向量數據庫的大模型;
server:port: 8081langchain4j:community:dashscope:chat-model:api-key: 阿里云百煉平臺apikeymodel-name: qwen-maxembedding-model:api-key: 阿里云百煉平臺apikeymodel: multimodal-embedding-v1#model-name: text-embedding-v2spring:ai:dashscope:api-key: 阿里云百煉平臺apikeyembedding:options:model: multimodal-embedding-v1#model: text-embedding-v2
4.2.2 添加測試文檔
在工程的resources目錄下增加一個用于加載到向量數據庫的txt文檔,內容如下:
4.2.3 配置InMemoryEmbeddingStore
增加一個自定義配置類,將InMemoryEmbeddingStore 配置進去
@Configuration
@RequiredArgsConstructor
public class AssistantConfig {final ChatLanguageModel chatLanguageModel;@Beanpublic EmbeddingStore<TextSegment> initEmbeddingStore() {return new InMemoryEmbeddingStore<>();}@Beanpublic Assistant assistant(SearchApiWebSearchEngine engine, EmbeddingStore<TextSegment> embeddingStore){return AiServices.builder(Assistant.class).chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(15)).contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore)).tools(new WebSearchTool(engine)).chatLanguageModel(chatLanguageModel).build();}}
4.2.4 增加測試接口
增加兩個接口,加載文檔到向量數據庫的接口,和用于對話的接口,參考下面的代碼
package com.congge.langchain4j;import com.congge.service.Assistant;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.splitter.DocumentByLineSplitter;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("/api/rag")
@RequiredArgsConstructor
public class RagController {final ChatLanguageModel chatLanguageModel;final EmbeddingStore<TextSegment> embeddingStore;final EmbeddingModel embeddingModel;/*** 加載文件到向量數據庫* localhost:8081/api/rag/load** @return*/@GetMapping("/load")public Object load() {List<Document> documents = FileSystemDocumentLoader.loadDocuments("E:\\code-self\\boot-consist\\src\\main\\resources\\file");EmbeddingStoreIngestor.ingest(documents, embeddingStore);;return "success";}final Assistant assistant;/*** 聊天對話* localhost:8081/api/rag/high/chat?message=AI去中心化與AI倫理成為焦點是什么** @return*/@GetMapping("/high/chat")public Object highChat(@RequestParam(value = "message") String message) {return assistant.chat( message);}}
4.2.5 效果測試
調用第一個接口進行文檔加載
再調用第二個接口進行對話,不難看出,本次回答問題的時候明顯是參考了本地加載的文檔中的內容
4.3 基于pgVector作為向量數據庫操作過程
下面來看如何在代碼中進行整合集成,參考下面的步驟。
4.3.1 增加配置類
該配置類主要設置一些初始化的內置對象,配置到全局的bean中,提供其他地方直接調用
package com.congge.config;import com.congge.langchain4j.Calculator;
import com.congge.langchain4j.HighLevelCalculator;
import com.congge.service.Assistant;
import com.congge.util.DateCalculator;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import dev.langchain4j.web.search.WebSearchTool;
import dev.langchain4j.web.search.searchapi.SearchApiWebSearchEngine;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@RequiredArgsConstructor
public class AssistantConfig {final ChatLanguageModel chatLanguageModel;@Beanpublic Assistant assistant(SearchApiWebSearchEngine engine, EmbeddingStore<TextSegment> embeddingStore){return AiServices.builder(Assistant.class).chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(15)).contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore)).tools(new WebSearchTool(engine)).chatLanguageModel(chatLanguageModel).build();}}
4.3.2 增加pgVector配置類
由于本次使用的是pgVector作為向量數據庫,因此需要覆蓋默認的向量數據庫,如下:
1)讀取配置文件中的pg配置類
@Configuration
@ConfigurationProperties(prefix = "pgvector")
@Data
public class PgConfig {private String host;private int port;private String database;private String user;private String password;private String table;}
2)自定義EmbeddingStore覆蓋默認
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@RequiredArgsConstructor
public class EmbeddingStoreInit {final PgConfig pgConfig;@Beanpublic EmbeddingStore<TextSegment> initEmbeddingStore() {return PgVectorEmbeddingStore.builder().table(pgConfig.getTable()).dropTableFirst(true).createTable(true).host(pgConfig.getHost()).port(pgConfig.getPort()).user(pgConfig.getUser()).password(pgConfig.getPassword()).dimension(384).database(pgConfig.getDatabase()).build();}
}
4.3.3 準備幾個文檔
在本地隨機提供幾個文檔,可以是txt,或者md、PDF等文件
4.3.4 提供文檔加載接口
提供第一個接口,用于加載本地文檔到向量數據庫
final EmbeddingStore<TextSegment> embeddingStore;/*** 加載文件到向量數據庫* localhost:8081/api/rag/load** @return*/
@GetMapping("/load")
public Object load() {List<Document> documents = FileSystemDocumentLoader.loadDocuments("E:\\code-self\\boot-consist\\src\\main\\resources\\file");EmbeddingStoreIngestor.ingest(documents, embeddingStore);;return "success";
}
接口執行成功后,連接pg數據庫之后,可以看到本地的文檔數據成功被加載到表中進行存儲了
4.3.5 提供一個對話接口
像上文那樣再提供一個對話接口,用于后面測試檢索
final Assistant assistant;/*** 聊天對話* localhost:8081/api/rag/high/chat?message=勒布朗·詹姆斯獲得過哪些榮譽** @return*/
@GetMapping("/high/chat")
public Object highChat(@RequestParam(value = "message") String message) {return assistant.chat( message);
}
基于上一步文檔加載接口之后,調用一下對話接口
4.3.6 文檔加載優化補充
在上面的加載文檔接口中,我們使用了 EmbeddingStoreIngestor.ingest(documents, embeddingStore) 這個方法,對讀取的文檔進行向量數據庫的加載,即沒有對內部的參數進行特殊的配置,其實來說,在實際使用過程中,為了更合理的對拆分后存儲到向量數據庫中的文本長度進行控制,以兼顧存儲容量和檢索的效果,需要適當的做一些參數設置。
此處主要是用于對需要寫入向量數據庫的文本進行一定策略上的切分,可以讓rag的檢索效果更好,如果需要自定義Spliter策略的話可以實現DocumentSplitter接口
以下 是langchain4j內置提供的一些切分策略:
-
DocumentByCharacterSplitter 基于指定的字符進行切分
-
DocumentByLineSplitter 基于行切分
-
DocumentByParagraphSplitter 基于段落切分,默認的切分方式
-
DocumentByRegexSplitter 基于正則進行切分
-
DocumentBySentenceSplitter 基于語義進行切分,需要依賴語義切分的模型
-
DocumentByWordSplitter 基于單詞進行切分
常用的拆分參數:
-
maxSegmentSizeInChars : 每個文本段最大的長度
-
maxOverlapSizeInChars :兩個段之間重疊的數量
在上面的參數設置中,需要結合特定的向量數據庫類型進行適配,如下這個參數:
下面是一個具體的自定義的文檔切分參數后的測試接口
final ChatLanguageModel chatLanguageModel;final EmbeddingStore<TextSegment> embeddingStore;final EmbeddingModel embeddingModel;/*** 加載文件到向量數據庫* localhost:8081/api/rag/load** @return*/@GetMapping("/load")public Object load() {List<Document> documents = FileSystemDocumentLoader.loadDocuments("E:\\code-self\\boot-consist\\src\\main\\resources\\file");EmbeddingStoreIngestor.builder().embeddingStore(embeddingStore).embeddingModel(embeddingModel).documentSplitter(new DocumentByLineSplitter(30,20)).build().ingest(documents);;return "success";}
重新調用一下文檔加載接口,調用成功后,不難發現,這一次設置并進行文檔拆分存儲之后,文檔的片段長度更小了;
五、寫在文末
本文通過較大的篇幅詳細介紹了基于Langchain4j結合目前主流的AI大模型實現RAG的完整過程,并通過實際案例進行了演示,希望對看到的同學有用,本篇到此結束,感謝觀看。