如何在Java中基於LangChain編寫大語言模型應用

作者 | Kumar Chandrakant
譯者 | 張衛濱
策劃 | 張衛濱
本文最初發表於 Baeldung 網站,由 InfoQ 中文站翻譯分享。
引   言
在本教程中,我們將會研究 LangChain 的細節,這是一個利用語言模型開發應用程式的框架。首先,我們會介紹有關語言模型的基本概念,這將對本教程有一定的輔助作用。
儘管 LangChain 主要提供了 Python 和 JavaScript/TypeScript 語言的版本,但是也有在 Java 中使用 LangChain 的可選方案。我們將會討論組成 LangChain 框架的構建基塊,然後在 Java 中進行實驗。
背   景
在深入探討為何需要一個框架來構建語言模型驅動的應用程式之前,我們必須首先了解什麼是語言模型,除此之外,我們還會介紹在使用語言模型時遇到的一些典型的複雜問題。
大語言模型
語言模型是自然語言的一個機率模型,可以生成一系列單詞的機率。大語言模型(LLM)是以規模龐大為特徵的一種語言模型。它們是人工智慧網路,可能會有數十億個引數。
LLM 通常會使用自監督和半監督學習技術,在大量無標記的資料上進行預訓練。然後,使用微調和提示工程等各種技術對預訓練模型進行調整,使其適用於特定的任務:
這些 LLM 能夠執行多種自然語言處理任務,如語言翻譯和內容摘要。它們還能完成內容建立等生成型任務。因此,它們在問題回答這樣的應用中極具價值。
幾乎所有主流的雲服務提供商都在它們的軟體產品中加入了大語言模型。例如,微軟 Azure 提供了 Llama 2 和 OpenAI GPT-4 等 LLM。Amazon Bedrock 提供了來自 AI21 Labs、Anthropic、Cohere、Meta 和 Stability AI 的模型。
提示工程
LLM 是在大量文字資料集上訓練的基礎模型。因此,它們可以捕獲人類語言內在的語法和語義。但是,我們必須對其進行調整,以便於讓其執行我們希望它們執行的特定任務。
提示工程是調整 LLM 最快的方法之一。這是一個構建文字的過程,文字可以被 LLM 解釋和理解。在這裡,我們使用自然語言文字來描述希望 LLM 執行的任務:
我們建立的提示有助於 LLM 執行上下文內的學習,這種學習是臨時性的。我們可以使用提示工程來促進 LLM 的安全使用,並構建新的能力,比如利用領域知識和外部工具來增強 LLM。
這是一個活躍的研究領域,新技術層出不窮。但是,像思路鏈(chain-of-thought)提示這樣的技術已經非常流行。該技術的理念是讓 LLM 在給出最終答案之前,透過一系列的中間步驟來解決問題。
詞嵌入
正如我們看到的,LLM 能夠處理大量的自然語言文字。如果我們將自然語言中的單詞表示為 詞嵌入(word embedding),那麼 LLM 的效能就會大大提高。這是一種 實值向量(real-valued vector),能夠對單詞的含義進行編碼
通常,詞嵌入是透過 Tomáš Mikolov 的 Word2vec 或斯坦福大學的 GloVe 等演算法生成的。GloVe 是一種無監督學習演算法,根據語料庫中全域性的單詞 – 單詞間共現(co-occurrence)統計資料進行訓練:
在提示工程中,我們將提示轉換為詞嵌入,使模型能夠更好地理解提示並做出響應。除此之外,它還有助於增強我們提供給模型的上下文,使它們能夠提供更多符合上下文的答案。
例如,我們可以從現有的資料集生成詞嵌入,並將其儲存到向量資料庫中。此外,我們還可以 使用使用者提供的輸入對該向量資料庫執行語義搜尋。然後,我們將搜尋結果作為模型的附加上下文。
使用 LangChain 的 LLM 技術棧
正如我們已經看到的,建立有效的提示是在所有應用中成功利用 LLM 的關鍵因素。這包括讓與語言模型的互動具有上下文感知的能力,並且能夠依靠語言模型進行推理。
為此,我們需要執行多項任務,包括建立提示模板、呼叫語言模型以及從多個數據源向語言模型提供使用者特定的資料。為了簡化這些任務,我們需要一個像 LangChain 這樣的框架,將其作為 LLM 技術棧的一部分:
在開發需要連結多個語言模型的應用,並且需要呼叫過去與語言模型互動生成的資訊時,該框架會非常有用。此外,還有一些更復雜的用例,涉及到將語言模型作為推理引擎。
最後,我們還可以執行日誌記錄、監控、流式處理以及其他必要的維護和故障排除任務。LLM 技術棧正在快速發展,以解決其中的許多問題。不過,LangChain 正在迅速成為 LLM 技術棧的重要組成部分。
面向 Java 的 LangChain
LangChain 在 2022 年以開源專案的形式啟動,並很快在社群的支援下贏得了廣泛關注。它最初由 Harrison Chase 用 Python 語言開發,很快就成為人工智慧領域發展最快的初創公司之一。
2023 年初,繼 Python 版本後,LangChain 又推出了 JavaScript/TypeScript 版本。它很快就變得非常流行,並開始支援多種 JavaScript 環境,如 Node.js、Web 瀏覽器、CloudFlare Worker、Vercel/Next.js、Deno 和 Supabase Edge 函式。
遺憾的是,目前還沒有適用於 Java/Spring 應用的 LangChain 官方 Java 版本。不過,有一個名為LangChain4j的 LangChain Java 社群版本。它適用於 Java 8 或更高版本,支援 Spring Boot 2 和 3。
LangChain 的各種依賴可以從 Maven 中央倉庫獲取。我們可能需要在應用程式中新增一個或多個依賴項,這取決於我們要使用的特性:
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>0.23.0</version></dependency>
例如,在本教程的後續章節中,我們還將需要一些依賴,以便於繼續支援與 OpenAI 模型整合、提供嵌入式支援以及類似 all-MiniLM-L6-v2 的句子轉換器(sentence-transformer)模型。
LangChain4j 的設計目標與 LangChain 相同,它提供了一個簡單而一致的抽象層以及眾多的實現。它已經支援多個語言模型供應商(如 OpenAI)和嵌入資料儲存商(如 Pinecone)。
不過,由於 LangChain 和 LangChain4j 都在快速發展,Python 或 JS/TS 版本支援的特性可能在 Java 版本中還不存在。不過,基本概念、一般結構和詞彙在很大程度上是相同的。
LangChain 的構建基塊
LangChain 以模組元件的形式為我們提供了多個有價值的提議。模組化元件提供了有用的抽象,以及一系列用於處理語言模型的實現。我們以 Java 為例,討論其中的一些模組。
模型 I/O
在使用任何語言模型時,我們都需要與之進行互動的能力。LangChain 提供了必要的構建基塊,比如模板化提示以及動態選擇和管理模型輸入的能力。同時,我們還可以使用輸出解析器從模型輸出中提取資訊:
提示模板是為語言模型生成提示的預定義配方,可能包括說明、few-shot 樣例和特定的上下文:
PromptTemplate promptTemplate = PromptTemplate .from("Tell me a {{adjective}} joke about {{content}}..");Map<String, Object> variables = new HashMap<>();variables.put("adjective", "funny");variables.put("content", "computers");Prompt prompt = promptTemplate.apply(variables);
在這裡,我們建立了一個可以接受多個變數的提示模板。這些變數是我們從使用者輸入中接收到的,並輸入到提示模板中。
LangChain 支援與兩種模型整合,即語言模型和聊天模型。聊天模型也由語言模型支援,但提供聊天的能力:
ChatLanguageModel model = OpenAiChatModel.builder() .apiKey(<OPENAI_API_KEY>) .modelName(GPT_3_5_TURBO) .temperature(0.3) .build();String response = model.generate(prompt.text());
在這裡,使用特定的 OpenAI 模型和相關的 API 秘鑰建立了一個聊天模型。我們可以透過免費註冊從 OpenAI 獲取 API 秘鑰。引數 temperature 用來控制模型輸出的隨機性。
最後,語言模型的輸出可能不夠結構化,不足以進行展示。LangChain 提供的輸出解析器可以幫助我們對語言模型的響應進行結構化處理,例如,以 Java 的 POJO 的形式從輸出中提取資訊。
記憶記憶體
通常,利用 LLM 的應用程式都會有一個對話介面。所有對話的一個重要方面就是要能夠參考對話早期引入的資訊。儲存過去互動資訊的能力被稱為記憶記憶體(memory):
LangChain 提供了為應用程式新增記憶記憶體的關鍵功能。我們需要從記憶記憶體中讀取資訊的能力,以增強使用者的輸入。然後,我們需要將當前執行的輸入和輸出寫入記憶記憶體的能力:
ChatMemory chatMemory = TokenWindowChatMemory .withMaxTokens(300, new OpenAiTokenizer(GPT_3_5_TURBO));chatMemory.add(userMessage("Hello, my name is Kumar"));AiMessage answer = model.generate(chatMemory.messages()).content();System.out.println(answer.text()); // Hello Kumar! How can I assist you today?chatMemory.add(answer);chatMemory.add(userMessage("What is my name?"));AiMessage answerWithName = model.generate(chatMemory.messages()).content();System.out.println(answer.text()); // Your name is Kumar.chatMemory.add(answerWithName);
在這裡,我們使用TokenWindowChatMemory實現了一個固定視窗的聊天記憶體,它允許我們讀寫與語言模型交換的聊天資訊。
LangChain 還提供了更復雜的資料結構和演算法,以便於從記憶記憶體中返回選定的資訊,而不是所有的資訊。例如,它支援返回過去幾條資訊的摘要,或者只返回與當前執行相關的資訊。
檢索
大語言模型通常是在大量的文字語料庫中訓練出來的。因此,它們在一般任務中相當高效,但是在特定領域任務中可能就沒有那麼有用了。因此,我們需要檢索相關的外部資料,並在生成步驟將其傳遞給語言模型。
這個過程被稱為“檢索增強生成(Retrieval Augmented Generation,RAG)”。它有助於將模型建立在相關的準確資訊之上,並讓我們深入瞭解模型的生成過程。LangChain 為建立 RAG 應用程式提供了必要的構建基塊:
首先,LangChain 提供了文件載入器,用於從儲存位置檢索文件。其次,還提供了用於準備文件的轉換器(transformer),以便於進一步的處理。例如,我們可以讓它將大文件拆分為小塊:
Document document = FileSystemDocumentLoader.loadDocument("simpson's_adventures.txt");DocumentSplitter splitter = DocumentSplitters.recursive(100, 0, new OpenAiTokenizer(GPT_3_5_TURBO));List<TextSegment> segments = splitter.split(document);
在這裡,我們使用FileSystemDocumentLoader從檔案系統載入文件。然後,使用OpenAiTokenizer將文件拆分為小塊。
為了提高檢索效率,通常會將文件轉換為嵌入內容,並將其儲存在向量資料庫中。LangChain 支援多種嵌入式提供商和方法,並且能夠與幾乎所有的向量儲存整合:
EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();List<Embedding> embeddings = embeddingModel.embedAll(segments).content();EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();embeddingStore.addAll(embeddings, segments);
在這裡,我們使AllMiniLmL6V2EmbeddingModel建立文件片段的嵌入內容。然後,我們將嵌入內容儲存在基於記憶體的向量儲存中。
現在,我們已經將外部資料以嵌入的形式儲存在向量儲存中了,並且做好準備進行檢索了。LangChain 支援多種檢索演算法,如簡單的語義搜尋以及複雜的複合檢索:
String question = "Who is Simpson?";//The assumption here is that the answer to this question is contained in the document we processed earlier.Embedding questionEmbedding = embeddingModel.embed(question).content();int maxResults = 3;double minScore = 0.7;List<EmbeddingMatch<TextSegment>> relevantEmbeddings = embeddingStore .findRelevant(questionEmbedding, maxResults, minScore);
我們建立了使用者問題的嵌入,然後使用問題嵌入從向量儲存中檢索相關的匹配項。現在,我們可以將檢索到的匹配項作為上下文進行傳送,將其新增到我們想要傳送給模型的提示中。
LangChain 的複雜應用
到目前為止,我們已經瞭解瞭如何使用單個元件建立基於語言模型的應用程式。LangChain 還提供了用於建立更復雜應用的元件。例如,我們可以使用鏈和代理來構建功能更強的自適應應用。
一般來講,應用程式需要按照特定順序呼叫多個元件。這就是 LangChain 中所說的鏈(chain)。它簡化了複雜應用程式的開發過程,使得除錯、維護和改進都變得更加容易。
它對於組合多個鏈以形成更復雜的應用程式也很有用,因為這些應用可能需要與多個語言模型互動。LangChain 提供了建立此類鏈的便捷方法,並提供了很多預構建的鏈:
ConversationalRetrievalChain chain = ConversationalRetrievalChain.builder() .chatLanguageModel(chatModel) .retriever(EmbeddingStoreRetriever.from(embeddingStore, embeddingModel)) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .promptTemplate(PromptTemplate .from("Answer the following question to the best of your ability: {{question}}\n\nBase your answer on the following information:\n{{information}}")) .build();
在這裡,我們使用了一個預構建的鏈ConversationalRetreivalChain,它允許我們使用聊天模型、檢索器、記憶記憶體和提示模板。現在,我們只需要使用該鏈就可以執行使用者查詢:
String answer = chain.execute("Who is Simpson?");
鏈自帶預設的記憶記憶體和提示模板。建立自定義鏈也很容易。建立鏈的功能使得複雜應用程式的模組化實現變得更加容易
代理
LangChain 還提供了更強大的結構,如代理(agent)。與鏈不同的是,代理使用語言模型作為推理引擎來決定採取哪些行動以及行動的順序。我們還可以讓代理訪問恰當的工具,以執行必要的行為。
在 LangChain4j 中,代理是作為 AI 服務的形式來使用的,可以用來宣告式地定義複雜的行為。我們看一下是否能夠以工具的形式為 AI 服務提供一個計算器,並使語言模型能夠執行運算。
首先,我們定義一個具有基本計算器功能的類,並使用自然語言描述每個功能,以便於模型理解:
publicclassAIServiceWithCalculator{staticclassCalculator{@Tool("Calculates the length of a string")intstringLength(String s){return s.length(); }@Tool("Calculates the sum of two numbers")intadd(int a, int b){return a + b; } }
然後,我們定義 AI 服務的介面,並以此為基礎進行構建。這個介面非常簡單,但是它也能描述更復雜的行為:
interface Assistant {String chat(String userMessage);}
現在,我們使用剛剛定義的介面和建立的工具,透過 LangChain4j 提供的構建器工廠建立一個 AI 服務:
Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(OpenAiChatModel.withApiKey(<OPENAI_API_KEY>)) .tools(new Calculator()) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .build();
就是這麼簡單!現在,我們可以開始向語言模型傳送一些包含計算的問題了:
String question = "What is the sum of the numbers of letters in the words \"language\" and \"model\"?";String answer = assistant.chat(question);System.out.prtintln(answer); // The sum of the numbers of letters in the words "language" and "model" is 13.
執行這段程式碼後,我們會發現語言模型現在可以執行計算了。
需要注意的是,語言模型在執行一些需要它們具有時間和空間的概念或執行復雜運算程式的任務時會遇到困難。不過,我們可以隨時透過為模型補充必要的工具來解決這個問題。
結  論
在本教程中,我們介紹了利用大語言模型建立應用程式的一些基本元素。此外,我們還討論了將 LangChain 這樣的框架作為開發此類應用程式的技術棧的一部分的價值。
基於此,我們探索了 LangChain4j 的一些核心要素,LangChain4j 是 LangChain 的 Java 版本。這些庫會在未來迅速發展。但是,它們也已讓開發由語言模型驅動的應用程式的過程變得更加成熟而有趣!
讀者可以在 GitHub 上檢視本文的完整原始碼。
宣告:本文為 AI前線翻譯整理,不代表平臺觀點,未經許可禁止轉載。
 會議推薦
AICon 2025 強勢來襲,5 月上海站、6 月北京站,雙城聯動,全覽 AI 技術前沿和行業落地。大會聚焦技術與應用深度融合,匯聚 AI Agent、多模態、場景應用、大模型架構創新、智慧資料基建、AI 產品設計和出海策略等話題。即刻掃碼購票,一同探索 AI 應用邊界!
今日薦文
谷歌史上最大收購:320 億美元買下了發現 DeepSeek 資料庫洩露的那家小企業
李開復:超大模型預訓練逐漸寡頭化,國內將收斂至 DeepSeek、阿里、位元組三家
史上最貴 API!比 DeepSeek-R1 貴千倍,OpenAI 高價勸退一批使用者!
英偉達軟硬體“雙拳出擊”:Blackwell Ultra、Rubin 晶片炸場,開源Dynamo讓R1 token生成暴漲40倍
你也「在看」嗎?👇

相關文章