聊聊langchain4j的RAG

本文主要研究一下langchain4j的RAG

概述

RAG(Retrieval-Augmented Generation)即檢索增強生成,它通過檢索來獲取相關信息,注入到prompt,然后用增強的prompt然后輸入給LLM讓LLM在回答的時候能夠利用檢索到信息,從而降低幻覺。常見的信息檢索方法包括:全文(關鍵詞)搜索、向量搜索(語義搜索)、混合搜索。目前langchain4j以向量搜索為主(例如通過Qdrant等向量數據庫構建高效檢索系統),后續會擴展支持全文搜索及混合搜索(目前Azure AI Search支持,詳細見AzureAiSearchContentRetriever)。

RAG可以分為兩步:索引、檢索。

索引

索引階段可以對文檔進行預處理以便在檢索階段實現高效搜索,這一步不同的檢索方法有所不同。對于向量搜索,通常包括:

  • 清理文檔:去除噪音數據,統一格式
  • 使用額外數據及元數據增強:增加文檔來源、時間戳、作者等輔助信息
  • 分塊:將長文檔分割為更小的語義單元,以適配嵌入模型的上下文窗口限制
  • 向量化:使用嵌入模型將文本塊轉換為向量
  • 向量存儲:存儲到向量數據庫

索引階段通常是離線進行的,這意味著不需要終端用戶等待其完成。例如,可以通過一個定時任務(cronjob)每周在周末重新索引公司內部文檔。負責索引的代碼也可以是一個獨立的應用程序,專門處理索引任務。
在某些情況下,終端用戶可能希望上傳自定義文檔以使其能夠被大型語言模型(LLM)訪問。在這種情況下,索引應在線進行,并成為主應用程序的一部分。

檢索

檢索階段通常在線上進行,當用戶提交一個問題時,該問題需要使用索引過的文檔來回答。這一過程可能會根據所使用的信息檢索方法而有所不同。對于向量搜索,這通常涉及將用戶的查詢(問題)嵌入到向量表示中,并在嵌入存儲庫中執行相似性搜索。然后,將相關段落(原始文檔的片段)注入到提示中,并發送給大型語言模型(LLM)。

實現

LangChain4j 提供了三種RAG(Retrieval-Augmented Generation,檢索增強生成)的實現方式:

  • Easy RAG:這是最簡單的方式,適合初學者快速上手。用戶只需將文檔丟給LangChain4j,無需了解嵌入、向量存儲、正確的嵌入模型選擇以及如何解析和拆分文檔等復雜內容。這種方式非常適合快速體驗 RAG 功能。
  • Naive RAG:這是一種基礎的RAG實現方式(使用向量搜索),主要通過簡單的索引、檢索和生成過程完成任務。它通常涉及將文檔拆分為片段,并使用向量搜索進行檢索。然而,Naive RAG存在一些局限性,例如檢索的相關性較差、生成的答案可能不連貫或重復。
  • Advanced RAG:這是一種模塊化的RAG框架,允許添加額外的步驟,如查詢轉換、從多個來源檢索以及重排序(reranking)。Advanced RAG通過引入更高級的技術(如語義分塊、查詢擴展與壓縮、元數據過濾等)來提高檢索質量和生成答案的相關性。

Easy RAG

pom.xml

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-easy-rag</artifactId>
    <version>1.0.0-beta2</version>
</dependency>

example

    public void testEasyRag() {
        String dir = System.getProperty("user.home") + "/Downloads/rag";
        List<Document> documents = FileSystemDocumentLoader.loadDocuments(dir);
        log.info("finish load document");
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        EmbeddingStoreIngestor.ingest(documents, embeddingStore);
        log.info("finish inject to embedding store");
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatLanguageModel(chatModel)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
                .contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
                .build();
        String answer = assistant.chat("How to do Easy RAG with LangChain4j?");
        log.info("answer:{}", answer);
    }

輸出如下:

2025-03-14T09:55:02.003Z  INFO 3181 --- [           main] com.example.AppTest                      : finish load document
2025-03-14T09:55:06.116Z  INFO 3181 --- [           main] ai.djl.util.Platform                     : Found matching platform from: jar:file:/root/.m2/repository/ai/djl/huggingface/tokenizers/0.31.1/tokenizers-0.31.1.jar!/native/lib/tokenizers.properties
2025-03-14T09:55:06.118Z  WARN 3181 --- [           main] a.d.huggingface.tokenizers.jni.LibUtils  : No matching cuda flavor for linux-x86_64 found: cu117/sm_75.
2025-03-14T09:55:06.282Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Loaded the following document splitter through SPI: dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter@55053f81
2025-03-14T09:55:07.076Z  WARN 3181 --- [           main] a.d.h.tokenizers.HuggingFaceTokenizer    : maxLength is not explicitly specified, use modelMaxLength: 512
2025-03-14T09:55:07.077Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Loaded the following embedding model through SPI: dev.langchain4j.model.embedding.onnx.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel@631bc9f4
2025-03-14T09:55:07.077Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Starting to ingest 1 documents
2025-03-14T09:55:07.294Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Documents were split into 33 text segments
2025-03-14T09:55:07.294Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Starting to embed 33 text segments
2025-03-14T09:55:08.209Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Finished embedding 33 text segments
2025-03-14T09:55:08.209Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Starting to store 33 text segments into the embedding store
2025-03-14T09:55:08.211Z DEBUG 3181 --- [           main] d.l.s.embedding.EmbeddingStoreIngestor   : Finished storing 33 text segments into the embedding store
2025-03-14T09:55:08.212Z  INFO 3181 --- [           main] com.example.AppTest                      : finish inject to embedding store
2025-03-14T09:55:24.098Z  INFO 3181 --- [           main] com.example.AppTest                      : answer:Okay, here’s a guide on how to do Easy RAG with LangChain4j, based on the provided information:
**1. Getting Started (General Approach)**
The easiest way to get started with RAG using LangChain4j is through the "Easy RAG" feature. This eliminates the need to configure embeddings, vector stores, or document parsing yourself.
**2. Dependencies**
Import the `langchain4j-easy-rag` dependency:
<dependency>
   <groupId>dev.langchain4j</groupId>
   <artifactId>langchain4j-easy-rag</artifactId>
   <version>1.0.0-beta2</version>
</dependency>
**3. Loading Documents**
Load your documents using `FileSystemDocumentLoader`:
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j/documentation");
**4. Easy RAG in Action**
*   **No Configuration Needed:**  Simply point the `FileSystemDocumentLoader` to your documents. LangChain4j handles everything under the hood.
*   **Retrieval:** LangChain4j uses Apache Tika to detect document types and parse them.
*   **Output:** `result.content()` provides the retrieved content. `result.sources()` provides a list of the sources.
**5. Quarkus Specifics**
"If you are using Quarkus, there is an even easier way to do Easy RAG. Please read Quarkus documentation." (This suggests you'll need to consult the Quarkus documentation for specific instructions, likely involving simplified configuration).
**6. Streaming and the `Assistant` Interface**
*   When streaming, you can use `onRetrieved()` to handle retrieved content.
*   The `Assistant` interface defines the `chat()` method, which can be used to interact with the RAG system.
*   Example:
Assistant assistant = ...; // Instantiate your assistant
String answer = assistant.chat("How to do Easy RAG with LangChain4j?")
    .onRetrieved((List<Content> sources) -> {
        // Process the retrieved sources here
        System.out.println("Retrieved Sources: " + sources);
    })
    .onPartialResponse(...)
    .onCompleteResponse(...)
    .onError(...)
    .start();
**7. RAG Flavors**
LangChain4j offers three RAG flavors:
*   **Easy RAG:** The simplest, quickest way to get started.
*   **Naive RAG:** A basic implementation using vector search.
*   **Advanced RAG:** A modular framework for more customization (query transformation, multiple sources, re-ranking, etc.).
**Important Note:**  Easy RAG will likely produce lower quality results than a more tailored RAG setup.  It's a good starting point for learning and prototyping.
---    
**Disclaimer:** This answer is based solely on the provided text.  It doesn't include details about specific libraries, configurations, or error handling beyond what's mentioned in the text.  You'll need to consult the LangChain4j documentation and the Quarkus documentation for a complete understanding.

小結

RAG(Retrieval-Augmented Generation)即檢索增強生成,它通過檢索來獲取相關信息,注入到prompt,然后用增強的prompt然后輸入給LLM讓LLM在回答的時候能夠利用檢索到信息,從而降低幻覺。LangChain4j 提供了三種RAG(Retrieval-Augmented Generation,檢索增強生成)的實現方式:Easy RAG、Naive RAG、Advanced RAG。

doc

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容