LangChain4j实战:模型参数配置、多模态、流式输出、聊天记忆、提示词工程全解析

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提供了模型构建器,我们可以使用构建器模式设置模型的参数,由于参数很多这里重点讲日志打印,监听机制,重试机制,超时机制的参数配置

1

基本使用的模型配置

如果仅是使用模型,那么只需要设置基本参数-大模型请求地址,模型名称,个人密钥

@Configuration
public class LLMConfig {
    @Bean
    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")
                .build();
    }
}

日志打印配置

通过模型配置参数实现打印请求大模型与大模型返回的日志

前提

需要有SLF4J日志后端依赖,调整全局日志打印级别并指定langchain4j包的日志打印级别,配置文件如下

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.5.8</version>
</dependency>
logging:
  level:
    # 设置全局日志级别为INFO
    root: INFO
    # 设置特定包的日志打印级别为DEBUG
    dev.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.
     */
    @Override
    public void onRequest(ChatModelRequestContext requestContext) {
        // 使用Hutool工具库中的id生成工具生成UUID
        String uuid = IdUtil.simpleUUID();
        requestContext.attributes().put("traceId", uuid);
        log.info("[请求监听] 请求参数 requestContext: " + requestContext.attributes().toString());
    }

    /**
     * 来自 LLM 的响应时的监听事件
     *
     * @param responseContext The response context.
     */
    @Override
    public void onResponse(ChatModelResponseContext responseContext) {
        log.info("[响应监听] 返回结果 responseContext: " + responseContext.attributes().toString());
    }

    /**
     * 错误的监听事件
     *
     * @param errorContext The error context.
     */
    @Override
    public 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();
    }
}

重试机制配置

即调用失败时的重试次数,默认是调用两次

2

@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 {
    @Bean
    public 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 通义万相模型
     */
    @Bean
    public 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 {
    @Autowired
    private 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 {
    @Autowired
    private 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-8
      enabled: true
      force: true

低阶LLM API的流式输出

对于ChatModel和LanguageModel接口,有相应的StreamingChatLanguageModel和StreamingLanguageModel接口。这些接口有类似的API,但可以流式传输响应。它们接受StreamingChatResponseHandler接口实现作为参数

3

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() {
            @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 = "你是谁") 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 直接调用封装接口流式返回到页面
     * @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种淘汰策略

4

  • 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: 18190
  servlet:
    # 设置响应的字符编码,避免流式返回输出乱码
    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);
        //设置key序列化方式string
        redisTemplate.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:";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 实现通过内存ID从持久化存储中获取所有消息
     * @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);
    }

    /**
     * 实现通过内存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}.
     */
    @Override
    public 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.
     */
    @Override
    public void deleteMessages(Object memoryId) {
        redisTemplate.delete(CHAT_MEMORY_STORE_PREFIX + memoryId);
    }
}
大模型配置类
@Configuration
public class LLMConfig {
    @Autowired
    private 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();
    }

    @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();
    }

}

提示词工程

在日常使用大模型的过程中,我们常常会遇到这样的场景:简单地向AI提问"写个故事",结果往往是一篇泛泛而谈,缺乏方向的内容;而如果我们换一种方式,给出"请写一个关于小狗勇闯森林的童话故事,要正能量,用小学生能听懂的语言",AI的输出便立刻贴近我们的预期。这种差异,正是普通提问与提示词(Prompt)之间的区别--前者往往模糊、随意;后者则清洗、结构化,包含任务目标、角色设定、语气风格、格式要求等关键信息。

普通提问就像与AI随意聊天,期待它"猜出"我们的需求;而提示词更像是一份精准的"说明书",让AI明确"做什么、怎么做"。通过精心设计的提示词,我们可以显著提升AI生成的内容的质量和针对性,这就是提示词工程的核心价值。

提示词的演进历程

  1. 简单的纯提示词提问问题
  2. 引入占位符(Prompt Template)以动态插入内容
  3. 多角色消息: 将消息分为不同角色(如用户、助手、系统等),设置功能边界,增强交互的复杂性和上下文感知能力。

ChatMessage的4种类型

目前有五种类型的聊天消息,但其中一种CustomMessage仅支持Ollama,因此一般说4种

5

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.提示词生成UserMessage
        UserMessage 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

posted @ 2025-11-05 16:25  柯南。道尔  阅读(25)  评论(0)    收藏  举报