LangChain4j实战:模型参数配置、多模态、流式输出、聊天记忆、提示词工程全解析
前提
后面用于演示的代码环境为: 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>
模型参数配置
根据选择的模型和提供商,可以调整很多参数,这里以OpenAI API的参数为例进行讲解
LangChain4j提供了模型构建器,我们可以使用构建器模式设置模型的参数,由于参数很多这里重点讲日志打印,监听机制,重试机制,超时机制的参数配置

基本使用的模型配置
如果仅是使用模型,那么只需要设置基本参数-大模型请求地址,模型名称,个人密钥
@Configuration
public class LLMConfig {@Beanpublic ChatModel chatModelQwen() {return OpenAiChatModel.builder() //api-key.apiKey(System.getenv("aliyunQwen-apiKey"))//调用模型名.modelName("qwen-plus")//调用阿里云百炼平台大模型的url.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").build();}
}
日志打印配置
通过模型配置参数实现打印请求大模型与大模型返回的日志
前提
需要有SLF4J日志后端依赖,调整全局日志打印级别并指定langchain4j包的日志打印级别,配置文件如下
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.5.8</version>
</dependency>
logging:level:# 设置全局日志级别为INFOroot: INFO# 设置特定包的日志打印级别为DEBUGdev.langchain4j: DEBUG
打印日志的模型配置
@Configuration
public class LLMConfig {@Bean(value = "qwen")public ChatModel chatModelQwen() {return OpenAiChatModel.builder()//api-key.apiKey(System.getenv("aliyunQwen-apiKey"))//调用模型名.modelName("qwen-plus")//调用阿里云百炼平台大模型的url.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//日志配置-打印请求日志.logRequests(true)//日志配置-打印返回日志.logResponses(true).build();}
}
监听机制配置
大模型(ChatModel或StreamingChatModel)允许配置ChatModelListener来监听事件,如向LLM发送请求时,收到响应时,异常时
构建监听实现类
@Slf4j
public class TestChatModelListener implements ChatModelListener {/*** 向 LLM 发送的请求时的监听事件** @param requestContext The request context.*/@Overridepublic void onRequest(ChatModelRequestContext requestContext) {// 使用Hutool工具库中的id生成工具生成UUIDString uuid = IdUtil.simpleUUID();requestContext.attributes().put("traceId", uuid);log.info("[请求监听] 请求参数 requestContext: " + requestContext.attributes().toString());}/*** 来自 LLM 的响应时的监听事件** @param responseContext The response context.*/@Overridepublic void onResponse(ChatModelResponseContext responseContext) {log.info("[响应监听] 返回结果 responseContext: " + responseContext.attributes().toString());}/*** 错误的监听事件** @param errorContext The error context.*/@Overridepublic void onError(ChatModelErrorContext errorContext) {log.error("[异常监听] 请求异常 errorContext: " + errorContext);}
}
监听机制的模型配置
@Configuration
public class LLMConfig {@Bean(value = "qwen")public ChatModel chatModelQwen() {return OpenAiChatModel.builder()//api-key.apiKey(System.getenv("aliyunQwen-apiKey"))//调用模型名.modelName("qwen-plus-2025-04-28")//调用阿里云百炼平台大模型的url.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//监听配置.listeners(List.of(new TestChatModelListener())).build();}
}
重试机制配置
即调用失败时的重试次数,默认是调用两次

@Configuration
public class LLMConfig {@Bean(value = "qwen")public ChatModel chatModelQwen() {return OpenAiChatModel.builder()//api-key.apiKey(System.getenv("aliyunQwen-apiKey"))//调用模型名.modelName("qwen-plus-2025-04-28")//调用阿里云百炼平台大模型的url.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//重试配置.maxRetries(3).build();}
}
超时机制配置
即配置连接超时时间和读超时时间,默认是连接超时时间为15s,读超时时间为60s
@Configuration
public class LLMConfig {@Bean(value = "qwen")public ChatModel chatModelQwen() {return OpenAiChatModel.builder()//api-key.apiKey(System.getenv("aliyunQwen-apiKey"))//调用模型名.modelName("qwen-plus-2025-04-28")//调用阿里云百炼平台大模型的url.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//请求超时配置.timeout(Duration.ofSeconds(2)).build();}
}
多模态
大模型的能力不仅仅局限于文本生成,还可以应用于视图理解,图片生成,语音识别,语音合成,视频生成等等,这里主要演示LangChain4j调用大模型的视图理解与图片生成的能力
前提
首先需要选取具体视图理解与图片生成的大模型,下面是大模型的配置类与依赖pom文件示例
pom依赖
<dependencies><!--快速构建一个基于 Spring MVC 的 Web 应用程序而预置的一组依赖项的集合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--一个通过注解在编译时自动生成 Java 样板代码的库--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--hutool Java工具库--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.40</version></dependency><!--对 Spring Boot 应用程序进行全面测试而预置的一组测试相关依赖项的集合--><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 高级AI服务API依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!--DashScope-阿里云开发的平台与langchain4j集成--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-dashscope</artifactId></dependency></dependencies>
大模型配置类
@Configuration
public class LLMConfig {@Beanpublic ChatModel chatLanguageModel() {return OpenAiChatModel.builder()//api-key.apiKey(System.getenv("aliyunQwen-apiKey"))//调用模型名.modelName("qwen3-vl-plus")//调用阿里云百炼平台大模型的url.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").build();}/*** 文本生成图片模型-通义万相** @return 通义万相模型*/@Beanpublic WanxImageModel wanxImageModel() {return WanxImageModel.builder().apiKey(System.getenv("aliyunQwen-apiKey")).modelName("wan2.5-t2i-preview").build();}
}
视图理解的控制层接口
视图理解功能需要将图片转换为字节数组并通过base64编码转换为字符串后与文本提示词一起传输给大模型进行分析
@Slf4j
@RestController
@RequestMapping(value = "/image")
public class ImageModelController {@Autowiredprivate ChatModel chatModel;@Value("classpath:static/image/TencentQuarterlyReport.png")private Resource resource;/*** 多模态调用,采用文本加图片的形式进行图片理解** @return 图片理解内容* @throws IOException IO异常*/@GetMapping("/readImage")public String readImageContent() throws IOException {String modelResult = null;//图片转码,通过Base64编码将图片转换为字符串byte[] byteArray = resource.getContentAsByteArray();String base64Str = Base64.getEncoder().encodeToString(byteArray);//多模块提示词,既包含文本也包含图片,同时发送给大模型进行处理UserMessage userMessage = UserMessage.from(TextContent.from("从下面图片中分析图片中内容,提炼出中心主旨"),ImageContent.from(base64Str, "image/png"));//API调用ChatResponse chatResponse = chatModel.chat(userMessage);//解析响应体,从ChatResponse中获取AI大模型的回复modelResult = chatResponse.aiMessage().text();log.info("大模型返回结果为: {}", modelResult);return modelResult;}
}
图片生成的控制层接口
图片生成的输入和文本生成的输入类似,均是输入提示词,图片生成大模型以通义万相为例会返回一个有效期24小时的图像下载链接
@Slf4j
@RestController
@RequestMapping(value = "/image")
public class WanxImageController {@Autowiredprivate WanxImageModel wanxImageModel;/*** 基于通义万相模型调用接口生成图片** @return 图片URL*/@GetMapping(value = "/imageCreate/one")public String imageCreateOne() {Response<Image> response = wanxImageModel.generate("生成一幅水墨画");log.info("[生成图片]生成图片的url为:{}", response.content().url());return response.content().url().toString();}/*** 在不构建模型配置的情况下通过dashscope依赖类生成图片** @return 图片URL*/@GetMapping(value = "/imageCreate/two")public String imageCreateTwo() {String prompt = "一副典雅庄重的对联悬挂于厅堂之中,房间是个安静古典的中式布置,桌子上放着一些青花瓷,对联上左书“义本生知人机同道善思新”,右书“通云赋智乾坤启数高志远”, 横批“智启通义”,字体飘逸,中间挂在一着一副中国风的画作,内容是岳阳楼。";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("---同步调用,请等待任务执行----");result = imageSynthesis.call(param);} catch (ApiException | NoApiKeyException e) {throw new RuntimeException(e.getMessage());}String jsonResult = JsonUtils.toJson(result);log.info(jsonResult);return jsonResult;}
}
流式输出
LLM一次生成一个标记(token),因此很多LLM提供商提供了一种方式,可以逐个标记地流式传输响应,而不是等待整个文本生成完毕。这显著改善了用户体验,因为用户不需要等待未知的时间,几乎可以立即开始阅读响应。
前提
流式输出中用到了响应时编程的核心类Flux
,因此需要简单讲解一下Flux 类,以及需要的依赖和配置文件
Flux类
Flux是io.projectreactor响应式编程库的核心类,用于表示0到N个元素的异步序列,可以发射0个,1个或多个元素,支持背压(Backpressure)。适用场景: 如处理数据库多条记录,消息队列,实时时间流等。
pom依赖
<dependencies><!--快速构建一个基于 Spring MVC 的 Web 应用程序而预置的一组依赖项的集合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--一个通过注解在编译时自动生成 Java 样板代码的库--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--LangChain4J openAI集成依赖(低级API依赖)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId></dependency><!--LangChain4J 高级AI服务API依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!--LangChain4J 响应式编程依赖(AI服务使用Flux)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId></dependency><!--hutool Java工具库--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.40</version></dependency></dependencies>
配置文件
server:servlet:# 设置响应的字符编码,避免流式返回输出乱码encoding:charset: UTF-8enabled: trueforce: true
低阶LLM API的流式输出
对于ChatModel和LanguageModel接口,有相应的StreamingChatLanguageModel和StreamingLanguageModel接口。这些接口有类似的API,但可以流式传输响应。它们接受StreamingChatResponseHandler接口实现作为参数

public interface StreamingChatResponseHandler {// 当生成下一个部分文本响应时default void onPartialResponse(String partialResponse) {}default void onPartialResponse(PartialResponse partialResponse, PartialResponseContext context) {}// 当生成下一个部分思考/推理文本时default void onPartialThinking(PartialThinking partialThinking) {}default void onPartialThinking(PartialThinking partialThinking, PartialThinkingContext context) {}// 当生成下一个部分工具调用时default void onPartialToolCall(PartialToolCall partialToolCall) {}default void onPartialToolCall(PartialToolCall partialToolCall, PartialToolCallContext context) {}// 当LLLM完成单个工具调用的流处理时default void onCompleteToolCall(CompleteToolCall completeToolCall) {}// 当LLM完成生成时void onCompleteResponse(ChatResponse completeResponse);// d当出现错误时void onError(Throwable error);
}
高阶LLM API的流式输出
可以直接使用Flux
/*** 声明式AI服务业务接口*/
public interface ChatAssistant {/*** 普通对话接口** @param prompt 提示词* @return 模型返回结果*/String chat(String prompt);/*** 流式返回对话接口** @param prompt 提示词* @return 模型返回结果*/Flux<String> chatFlux(String prompt);
}
低阶与高阶LLM API流式输出示例
大模型配置类
@Configuration
public class LLMConfig {/*** 普通对话模型配置** @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 流式返回到页面* @param prompt 提示词* @return 异步序列*/@GetMapping(value = "/chatOne")public Flux<String> chatOne(@RequestParam(value = "prompt", defaultValue = "你是谁") String prompt) {return Flux.create(emitter -> streamQwen.chat(prompt, new StreamingChatResponseHandler() {@Overridepublic void onPartialResponse(String partialResponse) {emitter.next(partialResponse);}@Overridepublic void onCompleteResponse(ChatResponse completeResponse) {emitter.complete();}@Overridepublic void onError(Throwable error) {emitter.error(error);}}));}/*** 低阶LLM API 流式返回到后端* @param prompt 提示词*/@GetMapping(value = "/chatTwo")public void chatTwo(@RequestParam(value = "prompt", defaultValue = "你是谁") String prompt) {streamQwen.chat(prompt, new StreamingChatResponseHandler() {@Overridepublic void onPartialResponse(String partialResponse) {System.out.println(partialResponse);}@Overridepublic void onCompleteResponse(ChatResponse completeResponse) {System.out.println("==response over:" + completeResponse);}@Overridepublic void onError(Throwable error) {error.printStackTrace();}});}/*** 高阶LLM API 直接调用封装接口流式返回到页面* @param prompt 提示词 * @return 异步序列*/@GetMapping(value = "/chatThree")public Flux<String> chatThree(@RequestParam(value = "prompt", defaultValue = "你是谁") String prompt) {return chatAssistant.chatFlux(prompt);}}
聊天记忆
聊天记忆是指大模型在对话过程中,能够记住,理解并利用之前交流过的内容,来影响后续回答的能力。是大模型从一个"知识问答工具"进化为"智能对话伙伴"的关键技术。它通过巧妙地结合"短期记忆"(上下文窗口)和"长期记忆"(外部知识库),让对话变得连贯、智能、个性化。
记忆与历史的区别
历史:
历史保持用户和AI之间的所有消息完整无缺。历史是用户在UI中看到的内容。它代表实际对话内容。记忆:
记忆保存了一些信息,这些信息呈现给LLM,使其表现得就像"记住"了对话一样。记忆与历史截然不同。根据使用的记忆算法,它可以以各种方式修改历史:淘汰一些消息,总结多条消息,总结单独的消息,从消息中删除不重要的细节,向消息中注入额外信息等等。注:当前LangChain4j只提供"记忆",而不是"历史"。
淘汰策略
采用淘汰策略的原因
- 为了适应LLM的上下文窗口。LLM一次可以处理的令牌(token)数量是有上限的。在某些时候,对话可能会超过这个限制。在这种情况下,应该淘汰一些消息。通常,最旧的消息会被淘汰,但如果需要,可以实现更复杂的算法。
- 控制成本。每个令牌(token)都有成本,使每次调用LLM的费用逐渐增加。淘汰不必要的消息可以降低成本。
- 控制延迟。发送给LLM的令牌(token)越多,处理它们所需的时间就越长。
LangChain4j提供的2种淘汰策略

- MessageWindowChatMemory:作为滑动窗口运行,保留最近的N条消息,并淘汰不再适合的旧消息。然而,由于每条消息可能包含不同数量的令牌,MessageWindowChatMemory主要用于快速原型设计。
- TokenWindowChatMemory: 同样作为滑动窗口运行,但专注于保留最近的N个令牌(token),根据需要淘汰旧消息。消息是不可分割的。如果一条消息不适合,它会被完全淘汰。TokenWindowChatMemory需要TokenCountEstimator来计数每个ChatMessage中的令牌(Token)。
淘汰策略实现代码示例
pom依赖
<dependencies><!--快速构建一个基于 Spring MVC 的 Web 应用程序而预置的一组依赖项的集合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--一个通过注解在编译时自动生成 Java 样板代码的库--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--LangChain4J openAI集成依赖(低级API依赖)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId></dependency><!--LangChain4J 高级AI服务API依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!--LangChain4J 响应式编程依赖(AI服务使用Flux)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId></dependency><!--hutool Java工具库--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.40</version></dependency></dependencies>
声明式AI服务业务接口
构建普通对话接口与带有记忆缓存功能的对话接口,用于后续调用对比
普通对话接口
public interface ChatAssistant {/*** 普通对话接口** @param prompt 提示词* @return 模型返回结果*/String chat(String prompt);
}
带有记忆缓存功能的对话接口
public interface ChatMemoryAssistant {/*** 带有记忆缓存的聊天接口** @param userId 用户id* @param prompt 提示词* @return 大模型返回内容*/String chatWithMemory(@MemoryId Long userId, @UserMessage String prompt);
}
大模型配置类
包含普通对话的AI服务实例与具备记忆功能不同淘汰策略的两种AI服务实例
@Configuration
public class LLMConfig {/*** 普通对话模型配置** @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();}/*** 基于低阶模型实例构建高阶使用的AIServices实例** @param chatModel 对话模型* @return AIServices实例*/@Bean(value = "chat")public ChatAssistant chatAssistant(ChatModel chatModel) {return AiServices.create(ChatAssistant.class, chatModel);}/*** 构建高阶AIServices实例(基于保留消息数的滑动窗口)** @param chatModel 对话模型* @return AIServices实例*/@Bean(value = "chatMemoryWithMessageWindow")public ChatMemoryAssistant chatMemoryWithMessageWindow(ChatModel chatModel) {return AiServices.builder(ChatMemoryAssistant.class).chatModel(chatModel)//根据memoryId构建一个ChatMemory,保留最多100条消息.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100)).build();}/*** 构建高阶AIServices实例(基于保留Token数的滑动窗口)** @param chatModel 对话模型* @return AIServices实例*/@Bean(value = "chatMemoryWithTokenWindow")public ChatMemoryAssistant chatMemoryWithTokenWindow(ChatModel chatModel) {// 构建默认的token分词器TokenCountEstimator openAiTokenizer = new OpenAiTokenCountEstimator("gpt-4");return AiServices.builder(ChatMemoryAssistant.class).chatModel(chatModel)//根据memoryId构建一个ChatMemory,保留最多100条消息.chatMemoryProvider(memoryId -> TokenWindowChatMemory.withMaxTokens(1000, openAiTokenizer)).build();}
}
提供大模型调用的控制层接口
@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;/*** 普通对话** @return 调用结果*/@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;}/*** 基于保留消息数的滑动窗口淘汰策略的带有记忆对话** @return 调用结果*/@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数的滑动窗口淘汰策略的带有记忆对话** @return 调用结果*/@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;}
}
聊天记忆持久化
默认情况下,ChatMemory实现在内存中存储ChatMessage。如果需要持久化,可以实现自定义的ChatMemoryStore,将ChatMessage存储在你选择的任何持久化存储中
通过redis实现聊天记忆持久化示例
由于声明式AI服务业务接口和控制层接口与上面的代码类似就不做展示了
pom依赖
<dependencies><!--快速构建一个基于 Spring MVC 的 Web 应用程序而预置的一组依赖项的集合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--一个通过注解在编译时自动生成 Java 样板代码的库--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--LangChain4J openAI集成依赖(低级API依赖)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId></dependency><!--LangChain4J 高级AI服务API依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!--LangChain4J 响应式编程依赖(AI服务使用Flux)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId></dependency><!--hutool Java工具库--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.40</version></dependency><!-- springboot 集成redis数据源依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency></dependencies>
配置文件
server:port: 18190servlet:# 设置响应的字符编码,避免流式返回输出乱码encoding:charset: UTF-8enabled: trueforce: true
spring:data:# redis配置文件redis:host: localhostport: 6379database: 0connect-timeout: 10stimeout: 10spassword: 123456
redis配置类
@Slf4j
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactor);//设置key序列化方式stringredisTemplate.setKeySerializer(new StringRedisSerializer());//设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化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:";@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 实现通过内存ID从持久化存储中获取所有消息* @param memoryId The ID of the chat memory.* @return 消息集合*/@Overridepublic List<ChatMessage> getMessages(Object memoryId) {String messageValue = redisTemplate.opsForValue().get(CHAT_MEMORY_STORE_PREFIX + memoryId);return ChatMessageDeserializer.messagesFromJson(messageValue);}/*** 实现通过内存ID更新持久化存储中的所有消息* @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}.*/@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages) {redisTemplate.opsForValue().set(CHAT_MEMORY_STORE_PREFIX + memoryId,ChatMessageSerializer.messagesToJson(messages));}/*** 实现通过内存ID删除持久化存储中的所有消息* @param memoryId The ID of the chat memory.*/@Overridepublic void deleteMessages(Object memoryId) {redisTemplate.delete(CHAT_MEMORY_STORE_PREFIX + memoryId);}
}
大模型配置类
@Configuration
public class LLMConfig {@Autowiredprivate RedisChatMemoryStore redisChatMemoryStore;/*** 普通对话模型配置** @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();}@Beanpublic 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();}}
提示词工程
在日常使用大模型的过程中,我们常常会遇到这样的场景:简单地向AI提问"写个故事",结果往往是一篇泛泛而谈,缺乏方向的内容;而如果我们换一种方式,给出"请写一个关于小狗勇闯森林的童话故事,要正能量,用小学生能听懂的语言",AI的输出便立刻贴近我们的预期。这种差异,正是普通提问与提示词(Prompt)之间的区别--前者往往模糊、随意;后者则清洗、结构化,包含任务目标、角色设定、语气风格、格式要求等关键信息。
普通提问就像与AI随意聊天,期待它"猜出"我们的需求;而提示词更像是一份精准的"说明书",让AI明确"做什么、怎么做"。通过精心设计的提示词,我们可以显著提升AI生成的内容的质量和针对性,这就是提示词工程的核心价值。
提示词的演进历程
- 简单的纯提示词提问问题
- 引入占位符(Prompt Template)以动态插入内容
- 多角色消息: 将消息分为不同角色(如用户、助手、系统等),设置功能边界,增强交互的复杂性和上下文感知能力。
ChatMessage的4种类型
目前有五种类型的聊天消息,但其中一种CustomMessage仅支持Ollama,因此一般说4种

UserMessage
这是来自用户的消息。用户可以是应用程序的最终用户,也可以是应用程序本身。
UserMessage包含的属性为:
contents: 消息内容。根据LLM支持的模式,它可以包含一个文本或其他形式。
name: 用户名。并不是所有的模型提供者都支持它。
attributes: 附加属性。这些属性不发送到模型,但是存储在聊天记忆(ChatMemory)当中。
AiMessage
该消息是由AI生成的,是对已发送消息的响应。
AiMessage包含的属性为:
text: 文本内容。
thinking: 思考/推理内容。
toolExecutionRequests: 执行工具的请求。
attributes: 额外的属性,通常是特定于提供的程序的。
ToolExecutionResultMessage
ToolExecutionResultMessage是ToolExecutionRequest的结果。
SystemMessage
来自系统的消息。通常,作为开发人员应该定义此消息的内容。通常,你会在这里写一些说明,说明LLM在这次对话中的角色,它应该如何表现,以什么方式回答,等等。LLM被训练得比其他类型的消息更关注SystemMessage,所以要小心,最后不要让最终用户自由地定义或注入一些输入到SystemMessage。通常,它位于对话的开始。
CustomMessage
这是一个自定义消息,可以包含任意属性。此消息类型目前仅支持Ollama。
提示词工程示例:打造专业的限定能力范围的AI助手
本示例通过三种方式来体现多消息角色的使用
pom依赖
<dependencies><!--快速构建一个基于 Spring MVC 的 Web 应用程序而预置的一组依赖项的集合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--一个通过注解在编译时自动生成 Java 样板代码的库--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--LangChain4J openAI集成依赖(低级API依赖)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId></dependency><!--LangChain4J 高级AI服务API依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!--LangChain4J 响应式编程依赖(AI服务使用Flux)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId></dependency><!--hutool Java工具库--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.40</version></dependency></dependencies>
声明式AI服务业务接口
设计为计算机领域助手
public interface ComputerChatAssistant {@SystemMessage("你是一位专业的计算机领域知识顾问,只回答计算机领域相关的问题" +"输出限制: 禁止回答非计算机领域的问题,直接返回'我只能回答计算机领域相关的问题'")@UserMessage("请回答以下问题: {{question}} , 字数控制在{{length}}以内")String chat(@V("question") String question, @V("length") int length);@SystemMessage("你是一位专业的计算机领域知识顾问,只回答计算机领域相关的问题" +"输出限制: 禁止回答非计算机领域的问题,直接返回'我只能回答计算机领域相关的问题'")String chat(ComputerPrompt prompt);
}
提示词业务实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@StructuredPrompt("根据{{language}}语言, 解答以下问题: {{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);}
}
提供大模型调用的控制层接口
@Slf4j
@RestController
@RequestMapping(value = "/chatPrompt")
public class ChatPromptController {@Autowired@Qualifier("computerChatAssistant")private ComputerChatAssistant computerChatAssistant;@Autowired@Qualifier("qwen")private ChatModel qwen;/*** 方式一:@SystemMessage+@UserMessage+@V** @return 大模型返回结果*/@GetMapping(value = "/chatMethod1")public String chatMethod1() {String answer1 = computerChatAssistant.chat("计算机由什么组成", 1000);log.info("answer1: {}", answer1);String answer2 = computerChatAssistant.chat("苹果富含哪些营养", 1000);log.info("answer2: {}", answer2);return "success :" + DateUtil.now() + "<br> \n\n answer1: " + answer1 + "<br> \n\n answer2: " + answer2;}/*** 方式二:@SystemMessage + 带有@StructuredPrompt的业务实体类** @return*/@GetMapping(value = "/chatMethod2")public String chatMethod2() {ComputerPrompt computerPrompt = new ComputerPrompt();computerPrompt.setLanguage("Java");computerPrompt.setQuestion("数据基本类型有哪些");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 = "股票如何选购";//1.构建提示词模版PromptTemplate promptTemplate = PromptTemplate.from("你是一个{{role}}助手,回答以下内容{{question}}");//2.由提示词模版生成提示词Prompt prompt = promptTemplate.apply(Map.of("role", role, "question", question));//3.提示词生成UserMessageUserMessage userMessage = prompt.toUserMessage();//4.调用大模型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