1. LangChain4j 初識,想使用Java開發AI應用?
1. 簡介
LangChain4j 是一個基于 Java 的開源框架,用于開發 人工智能驅動的應用程序,尤其是涉及 大語言模型(LLM)交互 的場景。它的設計目標是簡化開發者與大語言模型的集成過程,提供一套工具和組件來處理復雜的 LLM 應用邏輯,例如對話管理、提示工程、工具調用等。
核心功能與特點
- 大語言模型集成
- 支持多種 LLM 接入方式,包括:
- 本地運行的開源模型(如 Llama 2、ChatGLM 等)。
- 第三方 API 模型(如 OpenAI 的 GPT 系列、Anthropic 的 Claude 等)。
- 通過統一的接口抽象,降低模型切換的成本。
- 支持多種 LLM 接入方式,包括:
- 提示工程工具
- 提供模板化的提示構建器,幫助開發者結構化輸入(如填充變量、管理上下文歷史)。
- 支持動態組合提示鏈(Prompt Chain),例如根據用戶問題逐步調用不同的提示模板。
- 對話狀態管理
- 維護多輪對話的上下文,支持記憶管理(如設置上下文窗口大小、選擇性遺忘舊信息)。
- 可集成外部知識庫(如向量數據庫)實現長期記憶。
- 工具調用能力
- 支持調用外部工具(如計算器、數據庫查詢、API 接口等),并將工具返回結果整合到 LLM 的回答中。
- 提供工具調用的決策邏輯(如判斷何時需要調用工具、如何解析工具返回結果)。
- 鏈式流程編排
- 通過 Chain 機制編排多個組件(如提示生成、工具調用、結果處理),形成復雜的工作流。
- 典型場景:問答系統中先調用搜索引擎獲取實時數據,再用 LLM 生成回答。
- 擴展性與生態
- 基于 Java 生態,可輕松與 Spring框架集成。
- 支持自定義組件(如自定義提示策略、工具適配器),靈活適配業務需求。
2. 話不多說,直接展示
本章主要通過單元測試的方式展示LangChain4j的各項功能,后續會出通過LangChain4j Starter的方式快速集成SpringBoot。
使用SDK版本信息如下:
Java: 21
SpringBoot: 3.4.5
LangChain4j: 1.0.1
AI 模型主要使用的是阿里的百煉平臺免費的token,需要ApiKey的可以自行去申請, 平臺地址如下:
https://bailian.console.aliyun.com/?tab=model#/model-market
3. Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ldx</groupId>
<artifactId>langchain-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>langchain-test</name>
<description>langchain-test</description>
<properties>
<java.version>21</java.version>
<langchain4j.version>1.0.1</langchain4j.version>
<guava.version>33.0.0-jre</guava.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4. 構建模型對象
// 普通的對話模型
private static ChatModel chatModel;
// 流式對話的模型(可以模擬gpt的打字機效果)
private static StreamingChatModel streamingChatModel;
@BeforeAll
public static void init_chat_model() {
chatModel = OpenAiChatModel
.builder()
// apikey 通過環境變量的方式注入,大家可以使用自己的apikey
.apiKey(System.getenv("LLM_API_KEY"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
streamingChatModel = OpenAiStreamingChatModel
.builder()
.apiKey(System.getenv("LLM_API_KEY"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
5. 返回字符串
@Test
public void should_return_str_when_use_normal_chat() {
String q = "你是誰";
String content = chatModel.chat(q);
log.info("call ai q: {}\na: {}", q, content);
}
測試結果如下:
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- call ai q: 你是誰
a: 我是通義千問,阿里巴巴集團旗下的超大規模語言模型。我能夠回答問題、創作文字,比如寫故事、公文、郵件、劇本等,還能進行邏輯推理、編程,甚至表達觀點和玩游戲。我在多國語言上都有很好的掌握,能為你提供多樣化的幫助。有什么我可以幫到你的嗎?
6. 返回流
這里使用flux對象接收流式返回的結果
如果想流式的返回給前端,也可以使用SSE的方式返回(代碼注釋的部分)
@Test
public void should_return_stream_when_use_stream_model() {
Sinks.Many<String> sinks = Sinks
.many()
.multicast()
.onBackpressureBuffer();
Flux<String> flux = sinks.asFlux();
StreamingChatResponseHandler streamingChatResponseHandler = new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String s) {
sinks.tryEmitNext(s);
}
@Override
public void onCompleteResponse(ChatResponse chatResponse) {
sinks.tryEmitComplete();
}
@Override
public void onError(Throwable throwable) {
sinks.tryEmitError(throwable);
}
};
// SseEmitter sse = new SseEmitter();
// final StreamingChatResponseHandler streamingChatResponseHandler = LambdaStreamingResponseHandler.onPartialResponseAndError(s -> {
// try {
// log.info("ai response stream data: {}", s);
// sse.send(s);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }, e -> sse.complete());
streamingChatModel.chat("你是誰", streamingChatResponseHandler);
flux
.toStream()
.forEach(partial -> log.info("ai response stream data: {}", partial));
}
測試結果如下:
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 我是
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 通
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 義
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 千問,阿里巴巴
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 集團旗下的通義
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 實驗室自主研發的超
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 大規模語言模型。
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 我能夠回答問題
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 、創作文字,
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 比如寫故事、
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 寫公文、
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 寫郵件、寫
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 劇本、邏輯推理
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 、編程等等,
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 還能表達觀點,
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 玩游戲等。如果你
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 有任何問題或需要
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 幫助,歡迎隨時
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ai response stream data: 告訴我!
7. 提示詞/模板
@Test
public void should_return_prompt_content_when_use_prompt() {
// 申明系統提示詞
// SystemMessage systemMessage = Prompt.from("你是一名java專家,請協助用戶解決相應的專業性問題").toSystemMessage();
// 申明提示詞模板
final PromptTemplate promptTemplate = new PromptTemplate("""
?? 將文本改寫成類似小紅書的 Emoji 風格。
請使用 Emoji 風格編輯以下段落,該風格以引人入勝的標題、每個段落中包含表情符號和在末尾添加相關標簽為特點。請確保保持原文的意思。
用戶的提問信息如下:
{{question}}
""");
final UserMessage userMessage = promptTemplate
.apply(Map.of("question", "你是誰"))
.toUserMessage();
ChatResponse chatResponse = chatModel.chat(userMessage);
String content = chatResponse
.aiMessage()
.text();
log.info("call ai q: {}\na: {}", userMessage.singleText(), content);
}
測試結果如下:
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- call ai q: ?? 將文本改寫成類似小紅書的 Emoji 風格。
請使用 Emoji 風格編輯以下段落,該風格以引人入勝的標題、每個段落中包含表情符號和在末尾添加相關標簽為特點。請確保保持原文的意思。
用戶的提問信息如下:
你是誰
a: ?你是誰?來認識一下我吧!??
嗨,親愛的朋友們!我是通義千問,阿里巴巴集團旗下的超大規模語言模型??。我可以通過學習海量文本數據,幫你回答問題、創作文字,甚至玩游戲哦~是不是很酷呢???
如果你有任何問題或需要幫助,盡管告訴我!我會盡力為你提供支持和支持??。讓我們一起開啟有趣的探索之旅吧!??
#人工智能 #聊天機器人 #新知探索 #科技生活
8. 聊天記憶
中心邏輯其實就是:將最近的聊天內容存儲起來,然后一股腦扔給AI??
@Test
public void should_return_memory_content_when_use_memory_chat() {
String id = "zhangtieniu_01";
String q1 = "你是誰";
ChatMemory chatMemory = MessageWindowChatMemory
.builder()
// 會話隔離(不同用戶的聊天信息互不干擾)
.id(id)
// 最大存儲最近的5條聊天內容(存儲太多影響性能&token)
.maxMessages(5)
.build();
// 將聊天內容放入記憶對象中
chatMemory.add(UserMessage.from(q1));
// SystemMessage 始終保存在messages中 且占用maxMessage名額
chatMemory.add(SystemMessage.from("""
?? 將文本改寫成類似小紅書的 Emoji 風格。
請使用 Emoji 風格編輯以下段落,該風格以引人入勝的標題、每個段落中包含表情符號和在末尾添加相關標簽為特點。請確保保持原文的意思。
"""));
final ChatResponse chatResponse = chatModel.chat(chatMemory.messages());
// 將ai的返回結果放入記憶對象中
chatMemory.add(chatResponse.aiMessage());
String q2 = "我剛剛問了啥";
chatMemory.add(UserMessage.from(q2));
final ChatResponse chatResponse2 = chatModel.chat(chatMemory.messages());
log.info("call ai q1: {}\na1: {}", q1, chatResponse
.aiMessage()
.text());
log.info("==========================分隔符==========================");
log.info("call ai q2: {}\na2: {}", q2, chatResponse2
.aiMessage()
.text());
}
測試結果如下:
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- call ai q1: 你是誰
a1: ?? 你好呀,讓我來介紹一下自己! ?? 我是通義千問,阿里巴巴集團旗下的超大規模語言模型。我不僅能陪你聊天,還能幫你寫故事、郵件、劇本等等,甚至可以表達觀點、玩游戲呢!????
?? 無論你想聊生活中的小確幸 ?? 還是工作學習中的難題 ??,我都會盡力幫助你!希望我能成為你的貼心小伙伴~ ??
#人工智能 #聊天伙伴 #創作助手 #日常分享
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- ==========================分隔符==========================
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- call ai q2: 我剛剛問了啥
a2: ?? 呃... 讓我查查!哦對!你剛剛問的是:“我是誰?” 這個問題讓我有機會用小紅書風格重新介紹自己呢! ??
? 作為通義千問,我最喜歡的就是通過對話幫助別人啦!如果你還有其他問題或者需要靈感,隨時可以問我哦~??
#回憶對話 #人工智能 #問答時間 #互動分享
9. 聊天內容持久化
9.1 store handler
這里簡單的使用map存儲會話內容
package com.ldx.langchaintest.store;
import com.google.common.collect.ArrayListMultimap;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import java.util.List;
public class PersistentChatMemoryStoreTest implements ChatMemoryStore {
public final ArrayListMultimap<Object, ChatMessage> messagesStore = ArrayListMultimap.create();
@Override
public List<ChatMessage> getMessages(Object memoryId) {
return messagesStore.get(memoryId);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
messagesStore.put(memoryId, messages.getLast());
}
@Override
public void deleteMessages(Object memoryId) {
messagesStore.removeAll(memoryId);
}
}
9.2 實現
@Test
public void should_return_memory_content_when_use_store_chat() {
String id = "zhangtieniu_01";
String q1 = "張鐵牛是一個高富帥,你是張鐵牛的助手";
PersistentChatMemoryStoreTest store = new PersistentChatMemoryStoreTest();
ChatMemory chatMemory = MessageWindowChatMemory
.builder()
.id(id)
.chatMemoryStore(store)
.maxMessages(5)
.build();
chatMemory.add(UserMessage.from(q1));
final ChatResponse chatResponse = chatModel.chat(chatMemory.messages());
chatMemory.add(chatResponse.aiMessage());
String q2 = "張鐵牛是誰";
chatMemory.add(UserMessage.from(q2));
final ChatResponse chatResponse2 = chatModel.chat(chatMemory.messages());
chatMemory.add(chatResponse2.aiMessage());
// 獲取當前會話的存儲內容并打印
List<ChatMessage> chatMessages = store.messagesStore.get(id);
for (ChatMessage chatMessage : chatMessages) {
log.info("session id: {}, message type: {}, message: {}", id, chatMessage.type(), chatMessage);
}
}
測試結果如下:
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- session id: zhangtieniu_01, message type: USER, message: UserMessage { name = null contents = [TextContent { text = "張鐵牛是一個高富帥,你是張鐵牛的助手" }] }
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- session id: zhangtieniu_01, message type: AI, message: AiMessage { text = "您好,我是張鐵牛先生的助手。張鐵牛先生確實是一位優秀的人士,他不僅外貌出眾、家境優渥,而且非常有才華。作為他的助手,我會幫助他處理各種事務,確保他的生活和工作都井井有條。如果您有任何需要幫忙的事情,或者想了解張鐵牛先生的相關信息,只要是我職責范圍內的,我都會盡力提供幫助。請問有什么我可以為您服務的呢?" toolExecutionRequests = [] }
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- session id: zhangtieniu_01, message type: USER, message: UserMessage { name = null contents = [TextContent { text = "張鐵牛是誰" }] }
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- session id: zhangtieniu_01, message type: AI, message: AiMessage { text = "張鐵牛先生是一位非常杰出的人物。他出身于一個成功的企業家庭,擁有優越的教育資源和廣泛的商業人脈。除了在商業領域的卓越成就外,他還以陽光、正直的形象受到周圍人的喜愛。
作為一位“高富帥”,張鐵牛先生不僅注重個人修養,還熱衷于公益事業,經常參與慈善活動來回饋社會。同時,他對生活充滿熱情,興趣愛好廣泛,比如健身、旅行以及收藏藝術品等。
不過,請允許我提醒您,雖然他是公眾眼中的完美人物,但他更希望被當作普通人來尊重,注重隱私保護。如果您有關于他的正面問題或需要安排相關事務,我很樂意為您提供幫助!" toolExecutionRequests = [] }
10. function call
10.1 user svc
聲明一個自定義的user svc, 讓ai去調用我們的業務方法
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
public class UserServiceTest {
@Tool("根據用戶的名稱獲取對應的code")
public String getUserCodeByUsername(@P("用戶名稱") String username) {
if ("張鐵牛".equals(username)) {
return "003";
}
return "000";
}
}
10.2 實現
@Test
public void should_return_func_content_when_use_function_call() {
final String q = "張鐵牛的code是多少";
List<ChatMessage> chatMessages = new ArrayList<>();
chatMessages.add(UserMessage.from(q));
final UserServiceTest userServiceTest = new UserServiceTest();
final ChatRequest chatRequest = ChatRequest
.builder()
.messages(UserMessage.from(q))
// 將工具類注入到上下文中
.toolSpecifications(ToolSpecifications.toolSpecificationsFrom(userServiceTest))
.build();
final ChatResponse chatResponse = chatModel.chat(chatRequest);
final AiMessage aiMessage = chatResponse.aiMessage();
chatMessages.add(aiMessage);
String a = aiMessage.text();
// 在響應結果中判斷是否有tool請求
if (aiMessage.hasToolExecutionRequests()) {
// 遍歷tool req
for (ToolExecutionRequest toolExecutionRequest : aiMessage.toolExecutionRequests()) {
// 申明執行器 其實就是通過tool name 反射調用userServiceTest的方法
ToolExecutor userToolExecutor = new DefaultToolExecutor(userServiceTest, toolExecutionRequest);
final String uuid = UUID
.randomUUID()
.toString();
final String executeResult = userToolExecutor.execute(toolExecutionRequest, uuid);
log.info("execute user tool, name: {}, param:{}, result: {} ", toolExecutionRequest.name(), toolExecutionRequest.arguments(), executeResult);
final ToolExecutionResultMessage toolExecutionResultMessages = ToolExecutionResultMessage.from(toolExecutionRequest, executeResult);
// 將tool執行的結果 放入上下文
chatMessages.add(toolExecutionResultMessages);
}
// 再次請求ai
a = chatModel
.chat(chatMessages)
.aiMessage()
.text();
}
log.info("call ai q:{}\na:{}", q, a);
}
測試結果如下:
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- execute user tool, name: getUserCodeByUsername, param:{"username": "張鐵牛"}, result: 003
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- call ai q:張鐵牛的code是多少
a:根據您的問題,張鐵牛的代碼是 **003**。如果還有其他相關信息需要補充或查詢,請告訴我!
11. dynamic function call
因為有的時候我們使用已有的svc時,我們可能無法直接給目標方法標注對應的注解讓AI來識別方法(比如第三方包里的方法)
所以就需要動態的創建tool的描述類,如下
@Test
public void should_return_func_content_when_use_dynamic_function_call() {
final String q = "張鐵牛的code是多少";
final List<ChatMessage> chatMessages = new ArrayList<>();
chatMessages.add(UserMessage.from(q));
final UserServiceTest userServiceTest = new UserServiceTest();
final ToolSpecification toolSpecification = ToolSpecification
.builder()
.name("getUserCodeByUsername")
.description("根據用戶的名稱獲取對應的code")
.parameters(JsonObjectSchema
.builder()
.addStringProperty("username", "用戶姓名")
.build())
.build();
final ChatRequest chatRequest = ChatRequest
.builder()
.messages(UserMessage.from(q))
.toolSpecifications(toolSpecification)
.build();
final ChatResponse chatResponse = chatModel.chat(chatRequest);
AiMessage aiMessage = chatResponse.aiMessage();
chatMessages.add(aiMessage);
String a = aiMessage.text();
if (aiMessage.hasToolExecutionRequests()) {
for (ToolExecutionRequest toolExecutionRequest : aiMessage.toolExecutionRequests()) {
ToolExecutor userToolExecutor = new DefaultToolExecutor(userServiceTest, toolExecutionRequest);
final String uuid = UUID
.randomUUID()
.toString();
final String executeResult = userToolExecutor.execute(toolExecutionRequest, uuid);
log.info("execute user tool, name: {}, param:{}, result: {} ", toolExecutionRequest.name(), toolExecutionRequest.arguments(), executeResult);
final ToolExecutionResultMessage toolExecutionResultMessages = ToolExecutionResultMessage.from(toolExecutionRequest, executeResult);
chatMessages.add(toolExecutionResultMessages);
}
a = chatModel
.chat(chatMessages)
.aiMessage()
.text();
}
log.info("call ai q:{}\na:{}", q, a);
}
測試結果如下:
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- execute user tool, name: getUserCodeByUsername, param:{"username": "張鐵牛"}, result: 003
[main] INFO com.ldx.langchaintest.lowapi.AiChatTest -- call ai q:張鐵牛的code是多少
a:根據您的問題,張鐵牛的代碼是 **003**。如果還有其他相關信息需要補充,請告訴我!
12. 小結
本章使用了LangChain4j一些比較底層的(原生的)api來請求大模型, 展示了AI服務中常見的使用方法,下一章我們將使用LangChain4j的AI Service功能來展示LangChain4j高階用法。
13. 源碼
測試過程中的代碼已全部上傳至github, 歡迎點贊收藏 倉庫地址: https://github.com/ludangxin/langchain4j-test

浙公網安備 33010602011771號