LangChain4j實(shí)戰(zhàn):模型參數(shù)配置、多模態(tài)、流式輸出、聊天記憶、提示詞工程全解析
LangChain4j實(shí)戰(zhàn):模型參數(shù)配置、多模態(tài)、流式輸出、聊天記憶、提示詞工程全解析
前提
后面用于演示的代碼環(huán)境為: JDK-21,apache-maven-3.6.2,spring-boot和langchain4j的版本如下面pom文件所示
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>21</java.version>
<!--Spring Boot-->
<spring-boot.version>3.5.3</spring-boot.version>
<!--LangChain4J-->
<langchain4j.version>1.7.1</langchain4j.version>
<!--LangChain4J community-->
<langchain4j-community.version>1.7.1-beta14</langchain4j-community.version>
</properties>
<dependencyManagement>
<dependencies>
<!--Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--LangChain4J-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--langchain4j-community-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>${langchain4j-community.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
模型參數(shù)配置
根據(jù)選擇的模型和提供商,可以調(diào)整很多參數(shù),這里以O(shè)penAI API的參數(shù)為例進(jìn)行講解
LangChain4j提供了模型構(gòu)建器,我們可以使用構(gòu)建器模式設(shè)置模型的參數(shù),由于參數(shù)很多這里重點(diǎn)講日志打印,監(jiān)聽(tīng)機(jī)制,重試機(jī)制,超時(shí)機(jī)制的參數(shù)配置

基本使用的模型配置
如果僅是使用模型,那么只需要設(shè)置基本參數(shù)-大模型請(qǐng)求地址,模型名稱,個(gè)人密鑰
@Configuration
public class LLMConfig {
@Bean
public ChatModel chatModelQwen() {
return OpenAiChatModel.builder()
//api-key
.apiKey(System.getenv("aliyunQwen-apiKey"))
//調(diào)用模型名
.modelName("qwen-plus")
//調(diào)用阿里云百煉平臺(tái)大模型的url
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
}
日志打印配置
通過(guò)模型配置參數(shù)實(shí)現(xiàn)打印請(qǐng)求大模型與大模型返回的日志
前提
需要有SLF4J日志后端依賴,調(diào)整全局日志打印級(jí)別并指定langchain4j包的日志打印級(jí)別,配置文件如下
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.8</version>
</dependency>
logging:
level:
# 設(shè)置全局日志級(jí)別為INFO
root: INFO
# 設(shè)置特定包的日志打印級(jí)別為DEBUG
dev.langchain4j: DEBUG
打印日志的模型配置
@Configuration
public class LLMConfig {
@Bean(value = "qwen")
public ChatModel chatModelQwen() {
return OpenAiChatModel.builder()
//api-key
.apiKey(System.getenv("aliyunQwen-apiKey"))
//調(diào)用模型名
.modelName("qwen-plus")
//調(diào)用阿里云百煉平臺(tái)大模型的url
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
//日志配置-打印請(qǐng)求日志
.logRequests(true)
//日志配置-打印返回日志
.logResponses(true)
.build();
}
}
監(jiān)聽(tīng)機(jī)制配置
大模型(ChatModel或StreamingChatModel)允許配置ChatModelListener來(lái)監(jiān)聽(tīng)事件,如向LLM發(fā)送請(qǐng)求時(shí),收到響應(yīng)時(shí),異常時(shí)
構(gòu)建監(jiān)聽(tīng)實(shí)現(xiàn)類
@Slf4j
public class TestChatModelListener implements ChatModelListener {
/**
* 向 LLM 發(fā)送的請(qǐng)求時(shí)的監(jiān)聽(tīng)事件
*
* @param requestContext The request context.
*/
@Override
public void onRequest(ChatModelRequestContext requestContext) {
// 使用Hutool工具庫(kù)中的id生成工具生成UUID
String uuid = IdUtil.simpleUUID();
requestContext.attributes().put("traceId", uuid);
log.info("[請(qǐng)求監(jiān)聽(tīng)] 請(qǐng)求參數(shù) requestContext: " + requestContext.attributes().toString());
}
/**
* 來(lái)自 LLM 的響應(yīng)時(shí)的監(jiān)聽(tīng)事件
*
* @param responseContext The response context.
*/
@Override
public void onResponse(ChatModelResponseContext responseContext) {
log.info("[響應(yīng)監(jiān)聽(tīng)] 返回結(jié)果 responseContext: " + responseContext.attributes().toString());
}
/**
* 錯(cuò)誤的監(jiān)聽(tīng)事件
*
* @param errorContext The error context.
*/
@Override
public void onError(ChatModelErrorContext errorContext) {
log.error("[異常監(jiān)聽(tīng)] 請(qǐng)求異常 errorContext: " + errorContext);
}
}
監(jiān)聽(tīng)機(jī)制的模型配置
@Configuration
public class LLMConfig {
@Bean(value = "qwen")
public ChatModel chatModelQwen() {
return OpenAiChatModel.builder()
//api-key
.apiKey(System.getenv("aliyunQwen-apiKey"))
//調(diào)用模型名
.modelName("qwen-plus-2025-04-28")
//調(diào)用阿里云百煉平臺(tái)大模型的url
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
//監(jiān)聽(tīng)配置
.listeners(List.of(new TestChatModelListener()))
.build();
}
}
重試機(jī)制配置
即調(diào)用失敗時(shí)的重試次數(shù),默認(rèn)是調(diào)用兩次

@Configuration
public class LLMConfig {
@Bean(value = "qwen")
public ChatModel chatModelQwen() {
return OpenAiChatModel.builder()
//api-key
.apiKey(System.getenv("aliyunQwen-apiKey"))
//調(diào)用模型名
.modelName("qwen-plus-2025-04-28")
//調(diào)用阿里云百煉平臺(tái)大模型的url
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
//重試配置
.maxRetries(3)
.build();
}
}
超時(shí)機(jī)制配置
即配置連接超時(shí)時(shí)間和讀超時(shí)時(shí)間,默認(rèn)是連接超時(shí)時(shí)間為15s,讀超時(shí)時(shí)間為60s
@Configuration
public class LLMConfig {
@Bean(value = "qwen")
public ChatModel chatModelQwen() {
return OpenAiChatModel.builder()
//api-key
.apiKey(System.getenv("aliyunQwen-apiKey"))
//調(diào)用模型名
.modelName("qwen-plus-2025-04-28")
//調(diào)用阿里云百煉平臺(tái)大模型的url
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
//請(qǐng)求超時(shí)配置
.timeout(Duration.ofSeconds(2))
.build();
}
}
多模態(tài)
大模型的能力不僅僅局限于文本生成,還可以應(yīng)用于視圖理解,圖片生成,語(yǔ)音識(shí)別,語(yǔ)音合成,視頻生成等等,這里主要演示LangChain4j調(diào)用大模型的視圖理解與圖片生成的能力
前提
首先需要選取具體視圖理解與圖片生成的大模型,下面是大模型的配置類與依賴pom文件示例
pom依賴
<dependencies>
<!--快速構(gòu)建一個(gè)基于 Spring MVC 的 Web 應(yīng)用程序而預(yù)置的一組依賴項(xiàng)的集合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--一個(gè)通過(guò)注解在編譯時(shí)自動(dòng)生成 Java 樣板代碼的庫(kù)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--hutool Java工具庫(kù)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.40</version>
</dependency>
<!--對(duì) Spring Boot 應(yīng)用程序進(jìn)行全面測(cè)試而預(yù)置的一組測(cè)試相關(guān)依賴項(xiàng)的集合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--LangChain4J openAI集成依賴-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!--LangChain4J 高級(jí)AI服務(wù)API依賴-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<!--DashScope-阿里云開(kāi)發(fā)的平臺(tái)與langchain4j集成-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope</artifactId>
</dependency>
</dependencies>
大模型配置類
@Configuration
public class LLMConfig {
@Bean
public ChatModel chatLanguageModel() {
return OpenAiChatModel.builder()
//api-key
.apiKey(System.getenv("aliyunQwen-apiKey"))
//調(diào)用模型名
.modelName("qwen3-vl-plus")
//調(diào)用阿里云百煉平臺(tái)大模型的url
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
/**
* 文本生成圖片模型-通義萬(wàn)相
*
* @return 通義萬(wàn)相模型
*/
@Bean
public WanxImageModel wanxImageModel() {
return WanxImageModel.builder()
.apiKey(System.getenv("aliyunQwen-apiKey"))
.modelName("wan2.5-t2i-preview")
.build();
}
}
視圖理解的控制層接口
視圖理解功能需要將圖片轉(zhuǎn)換為字節(jié)數(shù)組并通過(guò)base64編碼轉(zhuǎn)換為字符串后與文本提示詞一起傳輸給大模型進(jìn)行分析
@Slf4j
@RestController
@RequestMapping(value = "/image")
public class ImageModelController {
@Autowired
private ChatModel chatModel;
@Value("classpath:static/image/TencentQuarterlyReport.png")
private Resource resource;
/**
* 多模態(tài)調(diào)用,采用文本加圖片的形式進(jìn)行圖片理解
*
* @return 圖片理解內(nèi)容
* @throws IOException IO異常
*/
@GetMapping("/readImage")
public String readImageContent() throws IOException {
String modelResult = null;
//圖片轉(zhuǎn)碼,通過(guò)Base64編碼將圖片轉(zhuǎn)換為字符串
byte[] byteArray = resource.getContentAsByteArray();
String base64Str = Base64.getEncoder().encodeToString(byteArray);
//多模塊提示詞,既包含文本也包含圖片,同時(shí)發(fā)送給大模型進(jìn)行處理
UserMessage userMessage = UserMessage.from(
TextContent.from("從下面圖片中分析圖片中內(nèi)容,提煉出中心主旨"),
ImageContent.from(base64Str, "image/png")
);
//API調(diào)用
ChatResponse chatResponse = chatModel.chat(userMessage);
//解析響應(yīng)體,從ChatResponse中獲取AI大模型的回復(fù)
modelResult = chatResponse.aiMessage().text();
log.info("大模型返回結(jié)果為: {}", modelResult);
return modelResult;
}
}
圖片生成的控制層接口
圖片生成的輸入和文本生成的輸入類似,均是輸入提示詞,圖片生成大模型以通義萬(wàn)相為例會(huì)返回一個(gè)有效期24小時(shí)的圖像下載鏈接
@Slf4j
@RestController
@RequestMapping(value = "/image")
public class WanxImageController {
@Autowired
private WanxImageModel wanxImageModel;
/**
* 基于通義萬(wàn)相模型調(diào)用接口生成圖片
*
* @return 圖片URL
*/
@GetMapping(value = "/imageCreate/one")
public String imageCreateOne() {
Response<Image> response = wanxImageModel.generate("生成一幅水墨畫(huà)");
log.info("[生成圖片]生成圖片的url為:{}", response.content().url());
return response.content().url().toString();
}
/**
* 在不構(gòu)建模型配置的情況下通過(guò)dashscope依賴類生成圖片
*
* @return 圖片URL
*/
@GetMapping(value = "/imageCreate/two")
public String imageCreateTwo() {
String prompt = "一副典雅莊重的對(duì)聯(lián)懸掛于廳堂之中,房間是個(gè)安靜古典的中式布置,桌子上放著一些青花瓷,對(duì)聯(lián)上左書(shū)“義本生知人機(jī)同道善思新”,右書(shū)“通云賦智乾坤啟數(shù)高志遠(yuǎn)”, 橫批“智啟通義”,字體飄逸,中間掛在一著一副中國(guó)風(fēng)的畫(huà)作,內(nèi)容是岳陽(yáng)樓。";
Map<String, Object> parameters = new HashMap<>();
parameters.put("prompt_extend", true);
parameters.put("watermark", true);
ImageSynthesisParam param =
ImageSynthesisParam.builder()
.apiKey(System.getenv("aliyunQwen-apiKey"))
.model("qwen-image")
.prompt(prompt)
.n(1)
.size("1328*1328")
.parameters(parameters)
.build();
ImageSynthesis imageSynthesis = new ImageSynthesis();
ImageSynthesisResult result = null;
try {
log.info("---同步調(diào)用,請(qǐng)等待任務(wù)執(zhí)行----");
result = imageSynthesis.call(param);
} catch (ApiException | NoApiKeyException e) {
throw new RuntimeException(e.getMessage());
}
String jsonResult = JsonUtils.toJson(result);
log.info(jsonResult);
return jsonResult;
}
}
流式輸出
LLM一次生成一個(gè)標(biāo)記(token),因此很多LLM提供商提供了一種方式,可以逐個(gè)標(biāo)記地流式傳輸響應(yīng),而不是等待整個(gè)文本生成完畢。這顯著改善了用戶體驗(yàn),因?yàn)橛脩舨恍枰却粗臅r(shí)間,幾乎可以立即開(kāi)始閱讀響應(yīng)。
前提
流式輸出中用到了響應(yīng)時(shí)編程的核心類Flux
,因此需要簡(jiǎn)單講解一下Flux 類,以及需要的依賴和配置文件
Flux類
Flux是io.projectreactor響應(yīng)式編程庫(kù)的核心類,用于表示0到N個(gè)元素的異步序列,可以發(fā)射0個(gè),1個(gè)或多個(gè)元素,支持背壓(Backpressure)。
適用場(chǎng)景: 如處理數(shù)據(jù)庫(kù)多條記錄,消息隊(duì)列,實(shí)時(shí)時(shí)間流等。
pom依賴
<dependencies>
<!--快速構(gòu)建一個(gè)基于 Spring MVC 的 Web 應(yīng)用程序而預(yù)置的一組依賴項(xiàng)的集合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--一個(gè)通過(guò)注解在編譯時(shí)自動(dòng)生成 Java 樣板代碼的庫(kù)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--LangChain4J openAI集成依賴(低級(jí)API依賴)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!--LangChain4J 高級(jí)AI服務(wù)API依賴-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<!--LangChain4J 響應(yīng)式編程依賴(AI服務(wù)使用Flux)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<!--hutool Java工具庫(kù)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.40</version>
</dependency>
</dependencies>
配置文件
server:
servlet:
# 設(shè)置響應(yīng)的字符編碼,避免流式返回輸出亂碼
encoding:
charset: UTF-8
enabled: true
force: true
低階LLM API的流式輸出
對(duì)于ChatModel和LanguageModel接口,有相應(yīng)的StreamingChatLanguageModel和StreamingLanguageModel接口。這些接口有類似的API,但可以流式傳輸響應(yīng)。它們接受StreamingChatResponseHandler接口實(shí)現(xiàn)作為參數(shù)

public interface StreamingChatResponseHandler {
// 當(dāng)生成下一個(gè)部分文本響應(yīng)時(shí)
default void onPartialResponse(String partialResponse) {}
default void onPartialResponse(PartialResponse partialResponse, PartialResponseContext context) {}
// 當(dāng)生成下一個(gè)部分思考/推理文本時(shí)
default void onPartialThinking(PartialThinking partialThinking) {}
default void onPartialThinking(PartialThinking partialThinking, PartialThinkingContext context) {}
// 當(dāng)生成下一個(gè)部分工具調(diào)用時(shí)
default void onPartialToolCall(PartialToolCall partialToolCall) {}
default void onPartialToolCall(PartialToolCall partialToolCall, PartialToolCallContext context) {}
// 當(dāng)LLLM完成單個(gè)工具調(diào)用的流處理時(shí)
default void onCompleteToolCall(CompleteToolCall completeToolCall) {}
// 當(dāng)LLM完成生成時(shí)
void onCompleteResponse(ChatResponse completeResponse);
// d當(dāng)出現(xiàn)錯(cuò)誤時(shí)
void onError(Throwable error);
}
高階LLM API的流式輸出
可以直接使用Flux
/**
* 聲明式AI服務(wù)業(yè)務(wù)接口
*/
public interface ChatAssistant {
/**
* 普通對(duì)話接口
*
* @param prompt 提示詞
* @return 模型返回結(jié)果
*/
String chat(String prompt);
/**
* 流式返回對(duì)話接口
*
* @param prompt 提示詞
* @return 模型返回結(jié)果
*/
Flux<String> chatFlux(String prompt);
}
低階與高階LLM API流式輸出示例
大模型配置類
@Configuration
public class LLMConfig {
/**
* 普通對(duì)話模型配置
*
* @return chatLanguageModel
*/
@Bean(value = "qwen")
public ChatModel chatLanguageModelQwen() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("aliyunQwen-apiKey"))
.modelName("qwen3-next-80b-a3b-instruct")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean(value = "streamQwen")
public StreamingChatModel streamingChatLanguageModelQwen() {
return OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("aliyunQwen-apiKey"))
.modelName("qwen3-next-80b-a3b-instruct")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean(value = "chatAssistant")
public ChatAssistant chatAssistant(StreamingChatModel chatLanguageModel) {
return AiServices.create(ChatAssistant.class, chatLanguageModel);
}
}
流式輸出的控制層接口
@Slf4j
@RestController
@RequestMapping(value = "/streamChat")
public class StreamingChatController {
/**
* 低階的LLM API
*/
@Autowired
@Qualifier("streamQwen")
private StreamingChatModel streamQwen;
/**
* 高階的LLM API(自己封裝接口)
*/
@Autowired
@Qualifier("chatAssistant")
private ChatAssistant chatAssistant;
/**
* 低階LLM API 流式返回到頁(yè)面
* @param prompt 提示詞
* @return 異步序列
*/
@GetMapping(value = "/chatOne")
public Flux<String> chatOne(@RequestParam(value = "prompt", defaultValue = "你是誰(shuí)") String prompt) {
return Flux.create(emitter -> streamQwen.chat(prompt, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
emitter.next(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
emitter.complete();
}
@Override
public void onError(Throwable error) {
emitter.error(error);
}
}));
}
/**
* 低階LLM API 流式返回到后端
* @param prompt 提示詞
*/
@GetMapping(value = "/chatTwo")
public void chatTwo(@RequestParam(value = "prompt", defaultValue = "你是誰(shuí)") String prompt) {
streamQwen.chat(prompt, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.println(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println("==response over:" + completeResponse);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
}
/**
* 高階LLM API 直接調(diào)用封裝接口流式返回到頁(yè)面
* @param prompt 提示詞
* @return 異步序列
*/
@GetMapping(value = "/chatThree")
public Flux<String> chatThree(@RequestParam(value = "prompt", defaultValue = "你是誰(shuí)") String prompt) {
return chatAssistant.chatFlux(prompt);
}
}
聊天記憶
聊天記憶是指大模型在對(duì)話過(guò)程中,能夠記住,理解并利用之前交流過(guò)的內(nèi)容,來(lái)影響后續(xù)回答的能力。是大模型從一個(gè)"知識(shí)問(wèn)答工具"進(jìn)化為"智能對(duì)話伙伴"的關(guān)鍵技術(shù)。它通過(guò)巧妙地結(jié)合"短期記憶"(上下文窗口)和"長(zhǎng)期記憶"(外部知識(shí)庫(kù)),讓對(duì)話變得連貫、智能、個(gè)性化。
記憶與歷史的區(qū)別
歷史:
歷史保持用戶和AI之間的所有消息完整無(wú)缺。歷史是用戶在UI中看到的內(nèi)容。它代表實(shí)際對(duì)話內(nèi)容。
記憶:
記憶保存了一些信息,這些信息呈現(xiàn)給LLM,使其表現(xiàn)得就像"記住"了對(duì)話一樣。記憶與歷史截然不同。根據(jù)使用的記憶算法,它可以以各種方式修改歷史:淘汰一些消息,總結(jié)多條消息,總結(jié)單獨(dú)的消息,從消息中刪除不重要的細(xì)節(jié),向消息中注入額外信息等等。
注:當(dāng)前LangChain4j只提供"記憶",而不是"歷史"。
淘汰策略
采用淘汰策略的原因
- 為了適應(yīng)LLM的上下文窗口。LLM一次可以處理的令牌(token)數(shù)量是有上限的。在某些時(shí)候,對(duì)話可能會(huì)超過(guò)這個(gè)限制。在這種情況下,應(yīng)該淘汰一些消息。通常,最舊的消息會(huì)被淘汰,但如果需要,可以實(shí)現(xiàn)更復(fù)雜的算法。
- 控制成本。每個(gè)令牌(token)都有成本,使每次調(diào)用LLM的費(fèi)用逐漸增加。淘汰不必要的消息可以降低成本。
- 控制延遲。發(fā)送給LLM的令牌(token)越多,處理它們所需的時(shí)間就越長(zhǎng)。
LangChain4j提供的2種淘汰策略

- MessageWindowChatMemory:作為滑動(dòng)窗口運(yùn)行,保留最近的N條消息,并淘汰不再適合的舊消息。然而,由于每條消息可能包含不同數(shù)量的令牌,MessageWindowChatMemory主要用于快速原型設(shè)計(jì)。
- TokenWindowChatMemory: 同樣作為滑動(dòng)窗口運(yùn)行,但專注于保留最近的N個(gè)令牌(token),根據(jù)需要淘汰舊消息。消息是不可分割的。如果一條消息不適合,它會(huì)被完全淘汰。TokenWindowChatMemory需要TokenCountEstimator來(lái)計(jì)數(shù)每個(gè)ChatMessage中的令牌(Token)。
淘汰策略實(shí)現(xiàn)代碼示例
pom依賴
<dependencies>
<!--快速構(gòu)建一個(gè)基于 Spring MVC 的 Web 應(yīng)用程序而預(yù)置的一組依賴項(xiàng)的集合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--一個(gè)通過(guò)注解在編譯時(shí)自動(dòng)生成 Java 樣板代碼的庫(kù)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--LangChain4J openAI集成依賴(低級(jí)API依賴)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!--LangChain4J 高級(jí)AI服務(wù)API依賴-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<!--LangChain4J 響應(yīng)式編程依賴(AI服務(wù)使用Flux)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<!--hutool Java工具庫(kù)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.40</version>
</dependency>
</dependencies>
聲明式AI服務(wù)業(yè)務(wù)接口
構(gòu)建普通對(duì)話接口與帶有記憶緩存功能的對(duì)話接口,用于后續(xù)調(diào)用對(duì)比
普通對(duì)話接口
public interface ChatAssistant {
/**
* 普通對(duì)話接口
*
* @param prompt 提示詞
* @return 模型返回結(jié)果
*/
String chat(String prompt);
}
帶有記憶緩存功能的對(duì)話接口
public interface ChatMemoryAssistant {
/**
* 帶有記憶緩存的聊天接口
*
* @param userId 用戶id
* @param prompt 提示詞
* @return 大模型返回內(nèi)容
*/
String chatWithMemory(@MemoryId Long userId, @UserMessage String prompt);
}
大模型配置類
包含普通對(duì)話的AI服務(wù)實(shí)例與具備記憶功能不同淘汰策略的兩種AI服務(wù)實(shí)例
@Configuration
public class LLMConfig {
/**
* 普通對(duì)話模型配置
*
* @return chatLanguageModel
*/
@Bean(value = "qwen")
public ChatModel chatLanguageModelQwen() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("aliyunQwen-apiKey"))
.modelName("qwen3-next-80b-a3b-instruct")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
/**
* 基于低階模型實(shí)例構(gòu)建高階使用的AIServices實(shí)例
*
* @param chatModel 對(duì)話模型
* @return AIServices實(shí)例
*/
@Bean(value = "chat")
public ChatAssistant chatAssistant(ChatModel chatModel) {
return AiServices.create(ChatAssistant.class, chatModel);
}
/**
* 構(gòu)建高階AIServices實(shí)例(基于保留消息數(shù)的滑動(dòng)窗口)
*
* @param chatModel 對(duì)話模型
* @return AIServices實(shí)例
*/
@Bean(value = "chatMemoryWithMessageWindow")
public ChatMemoryAssistant chatMemoryWithMessageWindow(ChatModel chatModel) {
return AiServices.builder(ChatMemoryAssistant.class)
.chatModel(chatModel)
//根據(jù)memoryId構(gòu)建一個(gè)ChatMemory,保留最多100條消息
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
.build();
}
/**
* 構(gòu)建高階AIServices實(shí)例(基于保留Token數(shù)的滑動(dòng)窗口)
*
* @param chatModel 對(duì)話模型
* @return AIServices實(shí)例
*/
@Bean(value = "chatMemoryWithTokenWindow")
public ChatMemoryAssistant chatMemoryWithTokenWindow(ChatModel chatModel) {
// 構(gòu)建默認(rèn)的token分詞器
TokenCountEstimator openAiTokenizer = new OpenAiTokenCountEstimator("gpt-4");
return AiServices.builder(ChatMemoryAssistant.class)
.chatModel(chatModel)
//根據(jù)memoryId構(gòu)建一個(gè)ChatMemory,保留最多100條消息
.chatMemoryProvider(memoryId -> TokenWindowChatMemory.withMaxTokens(1000, openAiTokenizer))
.build();
}
}
提供大模型調(diào)用的控制層接口
@Slf4j
@RestController
@RequestMapping(value = "/memoryChat")
public class ChatMemoryController {
@Autowired
@Qualifier("chat")
private ChatAssistant chat;
@Autowired
@Qualifier("chatMemoryWithMessageWindow")
private ChatMemoryAssistant chatMemoryWithMessageWindow;
@Autowired
@Qualifier("chatMemoryWithTokenWindow")
private ChatMemoryAssistant chatMemoryWithTokenWindow;
/**
* 普通對(duì)話
*
* @return 調(diào)用結(jié)果
*/
@GetMapping(value = "/chatOne")
public String chatOne() {
String answerOne = chat.chat("你好,我的名字叫張三");
log.info("answerOne: {}", answerOne);
String answerTwo = chat.chat("我的名字叫什么");
log.info("answerTwo: {}", answerTwo);
return "success : " + DateUtil.now() + "<br> \n\n answerOne: " + answerOne + "<br> \n\n answerTwo: " + answerTwo;
}
/**
* 基于保留消息數(shù)的滑動(dòng)窗口淘汰策略的帶有記憶對(duì)話
*
* @return 調(diào)用結(jié)果
*/
@GetMapping(value = "/chatTwo")
public String chatTwo() {
chatMemoryWithMessageWindow.chatWithMemory(1L, "你好,我的名字叫小明");
String answerOne = chatMemoryWithMessageWindow.chatWithMemory(1L, "我的名字叫什么");
log.info("answerOne: {}", answerOne);
chatMemoryWithMessageWindow.chatWithMemory(3L, "你好,我的名字叫小紅");
String answerTwo = chatMemoryWithMessageWindow.chatWithMemory(3L, "我的名字叫什么");
log.info("answerTwo: {}", answerTwo);
return "chatMemoryWithMessageWindow success : " + DateUtil.now() + "<br> \n\n answerOne: " + answerOne + "<br> \n\n answerTwo: " + answerTwo;
}
/**
* 基于保留Token數(shù)的滑動(dòng)窗口淘汰策略的帶有記憶對(duì)話
*
* @return 調(diào)用結(jié)果
*/
@GetMapping(value = "/chatThree")
public String chatThree() {
chatMemoryWithTokenWindow.chatWithMemory(1L, "你好,我的名字叫小明");
String answerOne = chatMemoryWithTokenWindow.chatWithMemory(1L, "我的名字叫什么");
log.info("answerOne: {}", answerOne);
chatMemoryWithTokenWindow.chatWithMemory(5L, "你好,我的名字叫小紅");
String answerTwo = chatMemoryWithTokenWindow.chatWithMemory(5L, "我的名字叫什么");
log.info("answerTwo: {}", answerTwo);
return "chatMemoryWithTokenWindow success : " + DateUtil.now() + "<br> \n\n answerOne: " + answerOne + "<br> \n\n answerTwo: " + answerTwo;
}
}
聊天記憶持久化
默認(rèn)情況下,ChatMemory實(shí)現(xiàn)在內(nèi)存中存儲(chǔ)ChatMessage。如果需要持久化,可以實(shí)現(xiàn)自定義的ChatMemoryStore,將ChatMessage存儲(chǔ)在你選擇的任何持久化存儲(chǔ)中
通過(guò)redis實(shí)現(xiàn)聊天記憶持久化示例
由于聲明式AI服務(wù)業(yè)務(wù)接口和控制層接口與上面的代碼類似就不做展示了
pom依賴
<dependencies>
<!--快速構(gòu)建一個(gè)基于 Spring MVC 的 Web 應(yīng)用程序而預(yù)置的一組依賴項(xiàng)的集合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--一個(gè)通過(guò)注解在編譯時(shí)自動(dòng)生成 Java 樣板代碼的庫(kù)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--LangChain4J openAI集成依賴(低級(jí)API依賴)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!--LangChain4J 高級(jí)AI服務(wù)API依賴-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<!--LangChain4J 響應(yīng)式編程依賴(AI服務(wù)使用Flux)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<!--hutool Java工具庫(kù)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.40</version>
</dependency>
<!-- springboot 集成redis數(shù)據(jù)源依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
配置文件
server:
port: 18190
servlet:
# 設(shè)置響應(yīng)的字符編碼,避免流式返回輸出亂碼
encoding:
charset: UTF-8
enabled: true
force: true
spring:
data:
# redis配置文件
redis:
host: localhost
port: 6379
database: 0
connect-timeout: 10s
timeout: 10s
password: 123456
redis配置類
@Slf4j
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactor);
//設(shè)置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//設(shè)置value的序列化方式j(luò)son,使用GenericJackson2JsonRedisSerializer替換默認(rèn)序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
自定義的ChatMemoryStore類
@Component
public class RedisChatMemoryStore implements ChatMemoryStore {
public static final String CHAT_MEMORY_STORE_PREFIX = "CHAT_MEMORY:";
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 實(shí)現(xiàn)通過(guò)內(nèi)存ID從持久化存儲(chǔ)中獲取所有消息
* @param memoryId The ID of the chat memory.
* @return 消息集合
*/
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String messageValue = redisTemplate.opsForValue().get(CHAT_MEMORY_STORE_PREFIX + memoryId);
return ChatMessageDeserializer.messagesFromJson(messageValue);
}
/**
* 實(shí)現(xiàn)通過(guò)內(nèi)存ID更新持久化存儲(chǔ)中的所有消息
* @param memoryId The ID of the chat memory.
* @param messages List of messages for the specified chat memory, that represent the current state of the {@link ChatMemory}.
* Can be serialized to JSON using {@link ChatMessageSerializer}.
*/
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
redisTemplate.opsForValue().set(CHAT_MEMORY_STORE_PREFIX + memoryId,
ChatMessageSerializer.messagesToJson(messages));
}
/**
* 實(shí)現(xiàn)通過(guò)內(nèi)存ID刪除持久化存儲(chǔ)中的所有消息
* @param memoryId The ID of the chat memory.
*/
@Override
public void deleteMessages(Object memoryId) {
redisTemplate.delete(CHAT_MEMORY_STORE_PREFIX + memoryId);
}
}
大模型配置類
@Configuration
public class LLMConfig {
@Autowired
private RedisChatMemoryStore redisChatMemoryStore;
/**
* 普通對(duì)話模型配置
*
* @return chatModel
*/
@Bean(value = "qwen")
public ChatModel chatLanguageModelQwen() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("aliyunQwen-apiKey"))
.modelName("qwen3-next-80b-a3b-instruct")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean
public ChatPersistenceAssistant chatPersistenceAssistant(ChatModel chatModel) {
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(1000)
.chatMemoryStore(redisChatMemoryStore)
.build();
return AiServices.builder(ChatPersistenceAssistant.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.build();
}
}
提示詞工程
在日常使用大模型的過(guò)程中,我們常常會(huì)遇到這樣的場(chǎng)景:簡(jiǎn)單地向AI提問(wèn)"寫個(gè)故事",結(jié)果往往是一篇泛泛而談,缺乏方向的內(nèi)容;而如果我們換一種方式,給出"請(qǐng)寫一個(gè)關(guān)于小狗勇闖森林的童話故事,要正能量,用小學(xué)生能聽(tīng)懂的語(yǔ)言",AI的輸出便立刻貼近我們的預(yù)期。這種差異,正是普通提問(wèn)與提示詞(Prompt)之間的區(qū)別--前者往往模糊、隨意;后者則清洗、結(jié)構(gòu)化,包含任務(wù)目標(biāo)、角色設(shè)定、語(yǔ)氣風(fēng)格、格式要求等關(guān)鍵信息。
普通提問(wèn)就像與AI隨意聊天,期待它"猜出"我們的需求;而提示詞更像是一份精準(zhǔn)的"說(shuō)明書(shū)",讓AI明確"做什么、怎么做"。通過(guò)精心設(shè)計(jì)的提示詞,我們可以顯著提升AI生成的內(nèi)容的質(zhì)量和針對(duì)性,這就是提示詞工程的核心價(jià)值。
提示詞的演進(jìn)歷程
- 簡(jiǎn)單的純提示詞提問(wèn)問(wèn)題
- 引入占位符(Prompt Template)以動(dòng)態(tài)插入內(nèi)容
- 多角色消息: 將消息分為不同角色(如用戶、助手、系統(tǒng)等),設(shè)置功能邊界,增強(qiáng)交互的復(fù)雜性和上下文感知能力。
ChatMessage的4種類型
目前有五種類型的聊天消息,但其中一種CustomMessage僅支持Ollama,因此一般說(shuō)4種

UserMessage
這是來(lái)自用戶的消息。用戶可以是應(yīng)用程序的最終用戶,也可以是應(yīng)用程序本身。
UserMessage包含的屬性為:
contents: 消息內(nèi)容。根據(jù)LLM支持的模式,它可以包含一個(gè)文本或其他形式。
name: 用戶名。并不是所有的模型提供者都支持它。
attributes: 附加屬性。這些屬性不發(fā)送到模型,但是存儲(chǔ)在聊天記憶(ChatMemory)當(dāng)中。
AiMessage
該消息是由AI生成的,是對(duì)已發(fā)送消息的響應(yīng)。
AiMessage包含的屬性為:
text: 文本內(nèi)容。
thinking: 思考/推理內(nèi)容。
toolExecutionRequests: 執(zhí)行工具的請(qǐng)求。
attributes: 額外的屬性,通常是特定于提供的程序的。
ToolExecutionResultMessage
ToolExecutionResultMessage是ToolExecutionRequest的結(jié)果。
SystemMessage
來(lái)自系統(tǒng)的消息。通常,作為開(kāi)發(fā)人員應(yīng)該定義此消息的內(nèi)容。通常,你會(huì)在這里寫一些說(shuō)明,說(shuō)明LLM在這次對(duì)話中的角色,它應(yīng)該如何表現(xiàn),以什么方式回答,等等。LLM被訓(xùn)練得比其他類型的消息更關(guān)注SystemMessage,所以要小心,最后不要讓最終用戶自由地定義或注入一些輸入到SystemMessage。通常,它位于對(duì)話的開(kāi)始。
CustomMessage
這是一個(gè)自定義消息,可以包含任意屬性。此消息類型目前僅支持Ollama。
提示詞工程示例:打造專業(yè)的限定能力范圍的AI助手
本示例通過(guò)三種方式來(lái)體現(xiàn)多消息角色的使用
pom依賴
<dependencies>
<!--快速構(gòu)建一個(gè)基于 Spring MVC 的 Web 應(yīng)用程序而預(yù)置的一組依賴項(xiàng)的集合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--一個(gè)通過(guò)注解在編譯時(shí)自動(dòng)生成 Java 樣板代碼的庫(kù)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--LangChain4J openAI集成依賴(低級(jí)API依賴)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!--LangChain4J 高級(jí)AI服務(wù)API依賴-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<!--LangChain4J 響應(yīng)式編程依賴(AI服務(wù)使用Flux)-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<!--hutool Java工具庫(kù)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.40</version>
</dependency>
</dependencies>
聲明式AI服務(wù)業(yè)務(wù)接口
設(shè)計(jì)為計(jì)算機(jī)領(lǐng)域助手
public interface ComputerChatAssistant {
@SystemMessage("你是一位專業(yè)的計(jì)算機(jī)領(lǐng)域知識(shí)顧問(wèn),只回答計(jì)算機(jī)領(lǐng)域相關(guān)的問(wèn)題" +
"輸出限制: 禁止回答非計(jì)算機(jī)領(lǐng)域的問(wèn)題,直接返回'我只能回答計(jì)算機(jī)領(lǐng)域相關(guān)的問(wèn)題'")
@UserMessage("請(qǐng)回答以下問(wèn)題: {{question}} , 字?jǐn)?shù)控制在{{length}}以內(nèi)")
String chat(@V("question") String question, @V("length") int length);
@SystemMessage("你是一位專業(yè)的計(jì)算機(jī)領(lǐng)域知識(shí)顧問(wèn),只回答計(jì)算機(jī)領(lǐng)域相關(guān)的問(wèn)題" +
"輸出限制: 禁止回答非計(jì)算機(jī)領(lǐng)域的問(wèn)題,直接返回'我只能回答計(jì)算機(jī)領(lǐng)域相關(guān)的問(wèn)題'")
String chat(ComputerPrompt prompt);
}
提示詞業(yè)務(wù)實(shí)體類
@Data
@AllArgsConstructor
@NoArgsConstructor
@StructuredPrompt("根據(jù){{language}}語(yǔ)言, 解答以下問(wèn)題: {{question}}")
public class ComputerPrompt {
private String language;
private String question;
}
大模型配置類
@Configuration
public class LLMConfig {
@Bean(value = "qwen")
public ChatModel chatModelQwen() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("aliyunQwen-apiKey"))
.modelName("qwen3-next-80b-a3b-instruct")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean(value = "computerChatAssistant")
public ComputerChatAssistant computerChatAssistant(ChatModel chatModel) {
return AiServices.create(ComputerChatAssistant.class, chatModel);
}
}
提供大模型調(diào)用的控制層接口
@Slf4j
@RestController
@RequestMapping(value = "/chatPrompt")
public class ChatPromptController {
@Autowired
@Qualifier("computerChatAssistant")
private ComputerChatAssistant computerChatAssistant;
@Autowired
@Qualifier("qwen")
private ChatModel qwen;
/**
* 方式一:@SystemMessage+@UserMessage+@V
*
* @return 大模型返回結(jié)果
*/
@GetMapping(value = "/chatMethod1")
public String chatMethod1() {
String answer1 = computerChatAssistant.chat("計(jì)算機(jī)由什么組成", 1000);
log.info("answer1: {}", answer1);
String answer2 = computerChatAssistant.chat("蘋果富含哪些營(yíng)養(yǎng)", 1000);
log.info("answer2: {}", answer2);
return "success :" + DateUtil.now() + "<br> \n\n answer1: " + answer1 + "<br> \n\n answer2: " + answer2;
}
/**
* 方式二:@SystemMessage + 帶有@StructuredPrompt的業(yè)務(wù)實(shí)體類
*
* @return
*/
@GetMapping(value = "/chatMethod2")
public String chatMethod2() {
ComputerPrompt computerPrompt = new ComputerPrompt();
computerPrompt.setLanguage("Java");
computerPrompt.setQuestion("數(shù)據(jù)基本類型有哪些");
String answer = computerChatAssistant.chat(computerPrompt);
log.info("answer: {}", answer);
return "success :" + DateUtil.now() + "<br> \n\n answer: " + answer;
}
/**
* 方式三: PromptTemplate+Prompt
*
* @return
*/
@GetMapping(value = "/chatMethod3")
public String chatMethod3() {
String role = "金融專家";
String question = "股票如何選購(gòu)";
//1.構(gòu)建提示詞模版
PromptTemplate promptTemplate = PromptTemplate.from("你是一個(gè){{role}}助手,回答以下內(nèi)容{{question}}");
//2.由提示詞模版生成提示詞
Prompt prompt = promptTemplate.apply(Map.of("role", role, "question", question));
//3.提示詞生成UserMessage
UserMessage userMessage = prompt.toUserMessage();
//4.調(diào)用大模型
ChatResponse chatResponse = qwen.chat(userMessage);
log.info("chatResponse: {}", chatResponse);
return "success :" + DateUtil.now() + "<br> \n\n chatResponse: " + chatResponse.aiMessage().text();
}
}
參考資料
https://docs.langchain4j.dev/tutorials/model-parameters
https://docs.langchain4j.dev/tutorials/logging
https://docs.langchain4j.dev/tutorials/observability
https://bailian.console.aliyun.com/?tab=doc#/doc/?type=model&url=2848513
https://docs.langchain4j.dev/tutorials/response-streaming
https://docs.langchain4j.dev/tutorials/chat-memory
https://docs.langchain4j.dev/tutorials/chat-and-language-models

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