2. LangChain4j-AIServices,原來調(diào)用AI這么簡單?
1. 簡介
上一章節(jié)我們講了如何使用LangChain4J的底層組件來進行AI的交互,如 ChatLanguageModel、ChatMessage、ChatMemory 等。 在這個層面上工作非常靈活/自由,但也迫使我們編寫大量的樣板代碼。 由于 LLM 驅(qū)動的應用程序通常不僅需要單個組件,還需要多個組件協(xié)同工作 (例如,提示模板、聊天記憶、LLM、輸出解析器、RAG 組件:嵌入模型和存儲) 并且經(jīng)常涉及多次交互,協(xié)調(diào)所有這些組件變得更加繁瑣。
而我們使用框架的目的是希望專注于業(yè)務邏輯,而不是低級實現(xiàn)細節(jié)。 為此,LangChain4J衍生出一個高級概念可以幫助更快的進行AI應用的開發(fā):AI Services。
2. 環(huán)境信息
本章的環(huán)境信息跟上一章完全一樣,這里就不過多展示了,感興趣的可以看上一章內(nèi)容
3. 構(gòu)建AI Service
3.1 申明AI Service 接口
package com.ldx.langchaintest.aisvc;
import com.ldx.langchaintest.model.PersonTest;
import dev.langchain4j.service.Result;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import reactor.core.publisher.Flux;
import java.util.List;
/**
* ai svc
*
* @author ludangxin
* @date 2025/6/5
*/
public interface AiAssistantServiceTest {
String chat(String message);
TokenStream chatWithTokenStream(String message);
Flux<String> chatWithFlux(String message);
@SystemMessage("""
?? 將文本改寫成類似小紅書的 Emoji 風格。
請使用 Emoji 風格編輯以下段落,該風格以引人入勝的標題、每個段落中包含表情符號和在末尾添加相關(guān)標簽為特點。請確保保持原文的意思。
""")
String chatWithSysPrompt(String userMessage);
@UserMessage("請幫我判斷一下這段表達式「{{expression}}」的返回值 如果成立則返回true 反之 false")
boolean chatWithPrompt(@V("expression") String expression);
@UserMessage("需要你幫我mock需要的人員信息")
PersonTest chatWithPojo();
@UserMessage("需要你幫我mock人員姓名, 幫我生成{{total}}個")
List<String> chatWithList(@V("total") Integer total);
Result<String> chatWithMeta(String question);
// can not support return list pojo
@UserMessage("需要你幫我mock需要的人員信息, 幫我生成{{total}}個")
List<PersonTest> chatWithPojoList(@V("total") Integer total);
}
關(guān)于聊天記憶的svc
package com.ldx.langchaintest.aisvc;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
/**
* memory svc
*
* @author ludangxin
* @date 2025/6/5
*/
public interface AiAssistantServiceWithMemoryTest {
String chat(@MemoryId String memoryId, @UserMessage String message);
}
3.2 創(chuàng)建AI Service 實例
使用AiServices對象創(chuàng)建上述我們自定義的svc,其底層是通過jdk的動態(tài)代理實現(xiàn)的,感興趣的可以留言,后續(xù)可以加一章源碼解析
private static ChatModel chatModel;
private static StreamingChatModel streamingChatModel;
private static AiAssistantServiceTest assistant;
private static AiAssistantServiceTest streamingAssistant;
@BeforeAll
public static void init_chat_svc() {
chatModel = OpenAiChatModel
.builder()
.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();
// 創(chuàng)建普通對話的ai svc
assistant = AiServices.create(AiAssistantServiceTest.class, chatModel);
// 創(chuàng)建流式對話的ai svc
streamingAssistant = AiServices.create(AiAssistantServiceTest.class, streamingChatModel);
}
4. 返回字符串
@Test
public void should_return_str_when_use_normal_chat() {
final String q = "你是誰";
final String a = assistant.chat(q);
log.info("call ai q:{}\na:{}", q, a);
}
測試結(jié)果如下
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai q:你是誰
a:我是通義千問,阿里巴巴集團旗下的通義實驗室自主研發(fā)的超大規(guī)模語言模型。我能夠回答問題、創(chuàng)作文字,比如寫故事、寫公文、寫郵件、寫劇本、邏輯推理、編程等等,還能表達觀點,玩游戲等。如果你有任何問題或需要幫助,歡迎隨時告訴我!
5. 返回流
5.1 TokenStream
@Test
public void should_return_stream_when_use_stream_model() throws InterruptedException {
final TokenStream tokenStream = streamingAssistant.chatWithTokenStream("你是誰");
tokenStream
.onPartialResponse((String partial) -> log.info("ai response partial data: {}", partial))
.onCompleteResponse((ChatResponse chatResponse) -> log.info("ai response complete."))
.onError((Throwable error) -> log.info("ai call error", error))
.start();
// 阻塞 保證異步結(jié)果正常展示
Thread.sleep(10_000);
}
測試結(jié)果如下:
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 我是
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 通
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 義
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 千問,阿里巴巴
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 集團旗下的通義
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 實驗室自主研發(fā)的超
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 大規(guī)模語言模型。
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 我能夠回答問題
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 、創(chuàng)作文字,
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 比如寫故事、
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 寫公文、
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 寫郵件、寫
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 劇本、邏輯推理
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 、編程等等,
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 還能表達觀點,
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 玩游戲等。如果你
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 有任何問題或需要
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 幫助,歡迎隨時
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 告訴我!
[ForkJoinPool.commonPool-worker-1] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response complete.
5.2 Flux
@Test
public void should_return_stream_when_use_stream_model2() {
final Flux<String> flux = streamingAssistant.chatWithFlux("你是誰");
flux
.toStream()
.forEach(partial -> log.info("ai response partial data: {}", partial));
}
測試結(jié)果如下:
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 我是
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 通
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 義
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 千問,阿里巴巴
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 集團旗下的通義
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 實驗室自主研發(fā)的超
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 大規(guī)模語言模型。
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 我能夠回答問題
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 、創(chuàng)作文字,
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 比如寫故事、
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 寫公文、
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 寫郵件、寫
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 劇本、邏輯推理
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 、編程等等,
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 還能表達觀點,
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 玩游戲等。如果你
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 有任何問題或需要
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 幫助,歡迎隨時
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ai response partial data: 告訴我!
6. 提示詞/模板
@Test
public void should_return_prompt_content_when_use_prompt() {
final String q1 = "你是誰";
final String a1 = assistant.chatWithSysPrompt(q1);
log.info("call ai q1:{}\na1:{}", q1, a1);
final String q2 = "1+2=4";
final boolean a2 = assistant.chatWithPrompt(q2);
log.info("call ai q2:{}\na2:{}", q2, a2);
}
測試結(jié)果如下:
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai q1:你是誰
a1:?你好呀!我是你的AI助手??,在這里可以幫你解答各種問題、創(chuàng)作故事、寫公文、郵件、劇本等等哦!如果有任何疑問,盡管問我吧~??
#AI助手 #問答小能手 #隨時在線
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai q2:1+2=4
a2:false
7. 結(jié)構(gòu)化輸出
7.1 申明pojo
import dev.langchain4j.model.output.structured.Description;
import lombok.Data;
import java.time.LocalDate;
@Data
public class PersonTest {
// 添加描述,幫助 LLM 更好地理解
@Description("first name of a person")
String firstName;
@Description("last name of a person")
String lastName;
LocalDate birthDate;
}
7.2 實現(xiàn)
暫不支持返回 List<自定義對象>,執(zhí)行會報錯
@Test
public void should_return_custom_obj_when_use_normal_model() {
final PersonTest personTest = assistant.chatWithPojo();
final List<String> names = assistant.chatWithList(3);
final Result<String> resultMeta = assistant.chatWithMeta("你是誰");
// can not support return list pojo
//final List<PersonTest> personTests = assistant.chatWithPojoList(3);
log.info("call ai personTest:{}", personTest);
log.info("call ai mock names:{}", names);
String content = resultMeta.content();
// AI 服務調(diào)用期間使用的令牌總數(shù)
TokenUsage tokenUsage = resultMeta.tokenUsage();
// 在 RAG 檢索期間檢索到的 Content
final List<Content> sources = resultMeta.sources();
// 已執(zhí)行的工具
List<ToolExecution> toolExecutions = resultMeta.toolExecutions();
// 完成標識
FinishReason finishReason = resultMeta.finishReason();
log.info("call ai returnContent:{}, useToken:{}, sources:{}, toolExecutions:{}, finishReason:{}", content, tokenUsage, sources, toolExecutions, finishReason);
}
測試結(jié)果如下:
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai personTest:PersonTest(firstName=張, lastName=三, birthDate=1990-05-20)
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai mock names:[張偉, 李娜, 王強]
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai returnContent:我是通義千問,阿里巴巴集團旗下的超大規(guī)模語言模型。我能夠回答問題、創(chuàng)作文字,比如寫故事、寫公文、寫郵件、寫劇本、邏輯推理、編程等等,還能表達觀點,玩游戲等。如果你有任何問題或需要幫助,歡迎隨時告訴我!, useToken:OpenAiTokenUsage { inputTokenCount = 10, inputTokensDetails = OpenAiTokenUsage.InputTokensDetails { cachedTokens = 0 }, outputTokenCount = 60, outputTokensDetails = null, totalTokenCount = 70 }, sources:[], toolExecutions:[], finishReason:STOP
8. 聊天記憶
@Test
public void should_return_memory_content_when_use_memory_chat() {
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory
.builder()
.id(memoryId)
.maxMessages(10)
.build();
AiAssistantServiceWithMemoryTest assistantWithMemory = AiServices
.builder(AiAssistantServiceWithMemoryTest.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.build();
String q1 = "張鐵牛是一個高富帥,你是張鐵牛的助手";
String q2 = "張鐵牛是誰";
String memoryId = "zhangtieniu-01";
final String a1 = assistantWithMemory.chat(memoryId, q1);
final String a2 = assistantWithMemory.chat(memoryId, q2);
log.info("call ai q1: {}\na1: {}", q1, a1);
log.info("==========================分隔符==========================");
log.info("call ai q2: {}\na2: {}", q2, a2);
}
測試結(jié)果如下:
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai q1: 張鐵牛是一個高富帥,你是張鐵牛的助手
a1: 您好,我是張鐵牛先生的助手。張鐵牛先生確實是一位非常優(yōu)秀的人士,他不僅身高出眾、相貌堂堂,還擁有很高的學識和財富。作為他的助手,我主要負責協(xié)助他處理一些日常事務、商務安排以及社交活動等。同時,我也在不斷學習,以便更好地支持張先生的工作與生活需求。
如果您有任何問題或需要了解關(guān)于張先生的相關(guān)信息,請告訴我,我會在適當范圍內(nèi)提供幫助。不過需要注意的是,對于張先生的私人信息,我們會嚴格保密,尊重他的隱私權(quán)。那么,您今天是想了解哪方面的內(nèi)容呢?或者有什么具體事項需要轉(zhuǎn)達給張先生嗎?
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ==========================分隔符==========================
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- call ai q2: 張鐵牛是誰
a2: 張鐵牛先生是一位杰出的高富帥,他不僅在外表上十分出眾——身高挺拔、長相英俊,更擁有卓越的才華和雄厚的經(jīng)濟實力。在事業(yè)上,他涉足多個領(lǐng)域并取得了顯著成就,是一位備受尊敬的企業(yè)家。同時,他還熱衷于公益事業(yè),積極回饋社會。
不過,請允許我澄清一下:張鐵牛這個角色其實是虛構(gòu)的,主要用于情境假設(shè)或角色扮演中。如果放在現(xiàn)實語境下,我可以進一步調(diào)整描述以貼合具體需求。作為他的助手,我的職責就是協(xié)助張先生處理各類事務,并確保他的日程順利進行。
您對張鐵牛先生有什么特別想了解的內(nèi)容嗎?或者是否有信息需要我?guī)兔鬟_?
9. 內(nèi)容持久化&function call
@Test
public void should_return_memory_content_when_use_memory_and_store_and_tool_chat() {
PersistentChatMemoryStoreTest store = new PersistentChatMemoryStoreTest();
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory
.builder()
.id(memoryId)
.maxMessages(10)
.chatMemoryStore(store)
.build();
UserServiceTest userServiceTest = new UserServiceTest();
AiAssistantServiceWithMemoryTest assistantWithMemory = AiServices
.builder(AiAssistantServiceWithMemoryTest.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.tools(userServiceTest)
.build();
String q1 = "張鐵牛是一個高富帥,你是張鐵牛的助手";
String q2 = "張鐵牛是誰";
String memoryId = "zhangtieniu-01";
final String a1 = assistantWithMemory.chat(memoryId, q1);
final String a2 = assistantWithMemory.chat(memoryId, q2);
String memoryId2 = "lisi-01";
final String a3 = assistantWithMemory.chat(memoryId2, q2);
List<ChatMessage> chatMessages = store.messagesStore.get(memoryId);
for (ChatMessage chatMessage : chatMessages) {
log.info("session id: {}, message type: {}, message: {}", memoryId, chatMessage.type(), chatMessage);
}
log.info("==================分割線==================");
List<ChatMessage> chatMessages2 = store.messagesStore.get(memoryId2);
for (ChatMessage chatMessage : chatMessages2) {
log.info("session id: {}, message type: {}, message: {}", memoryId, chatMessage.type(), chatMessage);
}
}
測試結(jié)果如下:
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: USER, message: UserMessage { name = null contents = [TextContent { text = "張鐵牛是一個高富帥,你是張鐵牛的助手" }] }
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: AI, message: AiMessage { text = "好的,我是張鐵牛的助手。請問有什么我可以幫您的嗎?如果您需要任何信息或者幫助處理某些事情,請告訴我具體的要求。比如,如果需要獲取張鐵牛相關(guān)的代碼信息,我們可以利用已有的功能來查詢。請明確您的需求!" toolExecutionRequests = [] }
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: USER, message: UserMessage { name = null contents = [TextContent { text = "張鐵牛是誰" }] }
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: AI, message: AiMessage { text = "根據(jù)當前上下文,"張鐵牛"是一個我們假設(shè)的人物名字,他是您的代表人物。在這個場景中,我作為張鐵牛的助手被設(shè)定來幫助您完成各種任務或者提供所需信息。如果您需要具體關(guān)于“張鐵牛”的更多信息或者是與之相關(guān)的操作(例如獲取其用戶代碼等),請告訴我更詳細的信息或提出具體請求。
如果我們要基于實際應用情境進行互動,比如查詢某個用戶的特定代碼,您可以要求我執(zhí)行像通過用戶名獲取用戶代碼這樣的功能。 若要這樣做,請?zhí)峁┚唧w的用戶名,我將為您查找對應的代碼。
如果有其他任何問題或需求,也歡迎隨時告知!" toolExecutionRequests = [] }
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- ==================分割線==================
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: USER, message: UserMessage { name = null contents = [TextContent { text = "張鐵牛是誰" }] }
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: AI, message: AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = "call_f936aff30d024ae1ae6793", name = "getUserCodeByUsername", arguments = "{"username": "張鐵牛"}" }] }
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: TOOL_EXECUTION_RESULT, message: ToolExecutionResultMessage { id = "call_f936aff30d024ae1ae6793" toolName = "getUserCodeByUsername" text = "003" }
[main] INFO com.ldx.langchaintest.aisvc.AiChatWithSvcTest -- session id: zhangtieniu-01, message type: AI, message: AiMessage { text = "用戶張鐵牛的代碼是003。這是系統(tǒng)中唯一標識該用戶的代碼。如果您需要更多關(guān)于張鐵牛的信息,您可以提供更具體的問題或者查詢請求。" toolExecutionRequests = [] }
10. 小結(jié)
本章使用了LangChain4J高階Api來請求大模型,展示了AI服務中常見的使用方法,下一章我們將使用LangChain4J的RAG功能。
11. 源碼
測試過程中的代碼已全部上傳至github, 歡迎點贊收藏 倉庫地址: https://github.com/ludangxin/langchain4j-test

浙公網(wǎng)安備 33010602011771號