惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
罗磊的独立博客
H
Help Net Security
I
Intezer
G
Google Developers Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
Troy Hunt's Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
U
Unit 42
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
J
Java Code Geeks
S
Security Affairs
T
The Blog of Author Tim Ferriss
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
D
Docker
The GitHub Blog
The GitHub Blog
F
Full Disclosure
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs
腾讯CDC
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
T
Threatpost
D
DataBreaches.Net
Recent Announcements
Recent Announcements
博客园 - 三生石上(FineUI控件)
MongoDB | Blog
MongoDB | Blog
博客园 - 【当耐特】
L
LINUX DO - 最新话题
Google Online Security Blog
Google Online Security Blog
S
Schneier on Security
S
Securelist
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Help Net Security
Help Net Security
P
Proofpoint News Feed
Project Zero
Project Zero
S
SegmentFault 最新的问题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 叶小钗

Liu Zijian's Blog | 一个技术博客

使用Certbot自动续签HTTPS证书 使用Filebeat采集Nginx日志到ES Python的协程 Python中的异常 Python中的类和对象 Python的函数 Python的数据结构,推导式、迭代器和生成器 Spring AI集成多模态模型 LangChain4j多模态 LangChain Tools工具使用 Python中的模块和包 Python全局环境和虚拟环境(venv) LangChain Prompt提示词工程 LangChain4j Tools工具使用 基于Dify搭建AI智能体应用 LangChain4j RAG检索增强生成 Spring AI实现MCP Server Spring AI集成MCP Client Spring AI使用知识库增强对话功能 Spring AI实现一个智能客服 Spring AI实现一个简单的对话机器人 实现MinIO数据的每日备份 自己实现一个DNS服务 简单理解AI智能体 大模型和大模型应用 LangChain开篇 LangChain4j开篇 一个解析Excel2007的POI工具类 DataPermissionInterceptor源码解读 TenantLineInnerInterceptor源码解读 BaseMultiTableInnerInterceptor源码解读 Spring AI开篇 SQL解析工具JSQLParser 芋道源码解读之多租户 芋道源码解读之数据权限 芋道源码解读开篇 Java实现将数据导出为Word文档 OA系统的天数该怎样计算 安装MySQL8 安装MySQL5.7 RockyLinux9环境下编译MySQL8 MySQL字符集及底层原理 Java实现LDAP登录 Docker Compose IPv4和IPv6 使用虚拟机安装一个K8s集群 使用GraalVM原生编译打包SpringBoot工程 Nginx防止目录穿越 Java线程的状态 Nginx防盗链设置 使用python将excel表格转换为SQL INSERT Redis的公共操作命令 Redis数据结构之Bitfleid Redis数据结构之Bitmap Redis数据结构之GEO Redis数据结构之Hash Redis数据结构之HyperLogLog Redis数据结构之List Redis数据结构之Set Redis数据结构之Stream Redis数据结构之String Redis数据结构之ZSet 使用python压缩图片 利用Python实现Hexo站点的持续集成 Nginx设置HTTPS监听 firewalld防火墙工具的使用 Linux信号(signal)机制 MySQL5.7x 主从复制 用IP自签发一个HTTPS证书 基于Hexo实现一个静态的个人博客 RockyLinux9环境下编译MySQL5.7 Docker离线安装 MySQL数据定义语言 Docker与联合文件系统 Docker的网络 Docker的镜像操作 MySQL存储过程 MyBatis-Plus开篇 MySQL变量 MySQL视图 MySQL事务 MySQL插入修改和删除 MySQL查询 MySQL系统命令 Docker的容器操作 Docker的安装和配置 Docker容器数据卷 浅谈OAuth2.0授权原理 JVM开篇 浅谈Linux(Unix)的I/O模型 一个通用的CloseableHttpClient工厂类 JUC可重入锁ReentrantLock JUC读写锁ReadWriteLock Java的单例 Java泛型 Java8的新特性 最近最少使用算法(LRU) MySQL函数 SpringBoot配置和启动 volatile作用分析
LangChain4j Prompt提示词工程
Liu Zijian · 2025-11-04 · via Liu Zijian's Blog | 一个技术博客

未完待续

引言

之前,使用Spring AI对接大模型实现了对话机器人的功能:Spring AI实现一个简单的对话机器人,spring-boot与langchain4j整合可以实现同样的功能。

spring-boot与langchain4j整合,可以采用集成底层API(popular integrations)的方式,也有集成高层API(declarative AI Services)的方式,这里先后使用底层和高层API进行集成和测试。

1.底层API实现对话

引入spring-boot 3.5.4,langchain4j-bom。截至目前,官网上langchain4j-bom的最高版本是1.8.0,均需要jdk17+

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.4</version>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>1.8.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<repositories>
    <repository>
        <name>Central Portal Snapshots</name>
        <id>central-portal-snapshots</id>
        <url>https://central.sonatype.com/repository/maven-snapshots/</url>
        <releases>
            <enabled>false</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

以对接OpenAI及支持该协议的大模型为例,添加底层API依赖langchain4j-open-ai-spring-boot-starter

<dependencies>

    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

</dependencies>

1.1 阻塞式ChatModel

使用OpenAI协议对接DeepSeek大模型,更多详细的模型参数介绍见:https://docs.langchain4j.dev/tutorials/model-parameters

langchain4j:
  open-ai:
    chat-model:
      base-url: https://api.deepseek.com
      api-key: ${OPEN_API_KEY}
      model-name: deepseek-reasoner
      log-requests: true
      log-responses: true
      return-thinking: true


server:
  port: 8080

logging:
  level:
    dev.langchain4j: debug #需要设置日志级别

有些配置项不支持填写在配置文件,因此还可以通过配置类进行配置

package org.example.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LangChainConfig {

    public ChatModel chatModel() {
      
        return OpenAiChatModel.builder()
                .baseUrl("https://api.deepseek.com")
                .apiKey(System.getProperty("OPEN_API_KEY"))
                .modelName("deepseek-reasoner")
                .maxRetries(3)
                .logRequests(true)
                .logResponses(true)
                .returnThinking(true)
                .build();
    }
}

然后可以直接使用ChatModel实现Prompt对话,并返回消耗的Token数,ChatModel是一种阻塞式的API,需要等待大模型回复完成将结果一次性返回

package org.example.controller;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.output.TokenUsage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("chat")
@Slf4j
public class ChatController {

    @Resource
    private ChatModel chatModel;

    @GetMapping("chat")
    public String chat(String msg) {

        List<ChatMessage> messages = Arrays.asList(
                SystemMessage.from("你是一个数学老师,用简单易懂的方式解释数学概念。"),
                UserMessage.from(msg)
        );

        ChatResponse chatResponse = chatModel.chat(messages);
        TokenUsage tokenUsage = chatResponse.tokenUsage();
        log.info("token usage: {}", tokenUsage);

        return chatResponse.aiMessage().text();

    }


}

1.2 流式StreamingChatModel

StreamingChatModel是一种非阻塞式的API,不需要等待大模型回复完成将结果一次性返回,而是实时返回大模型生成的片段,直到全部返回。

pom.xml中新增支持流式返回的依赖

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-reactor</artifactId>
</dependency>

配置文件application.yml需要新增流式的streaming-chat-model配置

langchain4j:
  open-ai:
    streaming-chat-model:
      base-url: https://api.deepseek.com
      api-key: ${OPEN_API_KEY}
      model-name: deepseek-reasoner
      log-requests: true
      log-responses: true
      return-thinking: true

同样可以通过配置类进行配置

package org.example.config;

import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LangChainConfig {

    @Bean
    public StreamingChatModel chatModel() {
        return OpenAiStreamingChatModel.builder()
                .baseUrl("https://api.deepseek.com")
                .apiKey(System.getProperty("OPEN_API_KEY"))
                .modelName("deepseek-reasoner")
                .logRequests(true)
                .logResponses(true)
                .returnThinking(true)
                .build();
    }
}

流式API是由StreamingChatModel类来实现,在web环境下,需要配合Spring的Flux来使用,在下面方法回调触发时调用相应的Flux的方法,像Spring AI那样将Flux对象返回。

  • onPartialResponse 实时返回大模型生成的片段,调用sink.next()实时输出到浏览器
  • onPartialThinking 实时返回大模型推理过程,调用sink.next()实时输出到浏览器
  • onCompleteResponse 大模型生成完成,调用sink.complete()结束流的输出,还可以对消耗的token进行统计
  • onError 出错,记录错误信息,调用sink.complete()结束流的输出
package org.example.controller;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;

import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.*;
import dev.langchain4j.model.output.TokenUsage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("chat")
@Slf4j
public class StreamController {

    @Resource
    private StreamingChatModel streamingChatModel;

    @GetMapping(value = "streaming", produces = "text/html; charset=utf-8")
    public Flux<String> streaming(String msg) {

        List<ChatMessage> messages = Arrays.asList(
                SystemMessage.from("你是一个数学老师,用简单易懂的方式解释数学概念。"),
                UserMessage.from(msg)
        );

        return Flux.create(sink -> {
            streamingChatModel.chat(messages, new StreamingChatResponseHandler() {

                @Override
                public void onPartialResponse(PartialResponse partialResponse, PartialResponseContext context) {
                    sink.next(partialResponse.text());
                }

                @Override
                public void onPartialThinking(PartialThinking partialThinking) {
                    sink.next("<span style='color:red;'>" + partialThinking.text() + "</span>");
                }

                @Override
                public void onCompleteResponse(ChatResponse completeResponse) {
                    TokenUsage tokenUsage = completeResponse.tokenUsage();
                    log.info("token usage: {}", tokenUsage);
                    sink.complete();
                }

                @Override
                public void onError(Throwable error) {
                    error.printStackTrace();
                    sink.complete();
                }
            });
        });

    }
}

2.高层API实现对话

使用高层API,需要在底层API基础上,额外引入这个依赖

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-spring-boot-starter</artifactId>
</dependency>

2.1 阻塞式对话

新建一个接口,将调用大模型的方法声明在里面,方法的第一个参数默认就是UserMessage

package org.example.ai;

public interface AiAssistant {

    String chat(String prompt);

}

langchain4j提供了一些消息注解对高级API接口内方法进行设定

  • @SystemMessage 指明系统提示词,可以从类路径下读取文本文件
  • @UserMessage 预先指明用户提示词的固定部分,也可以从类路径下读取文本文件,会和后续调用方法时传入的用户提示词进行拼接替换,因此需要通过{{it}}的固定写法对用户传入的提示词进行占位,如果不想写成{{it}},则需要@V注解更换展位的字符
package org.example.ai;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import reactor.core.publisher.Flux;

public interface AiAssistant {

    // 系统提示词
    @SystemMessage("你是一个数学老师,用简单易懂的方式解释数学概念。")
    // @SystemMessage(fromResource = "1.txt") 基于工程类路径查找
    Flux<String> teacher(String prompt);

    // 用户提示词
    @UserMessage("你是一个数学老师,用简单易懂的方式解释数学概念。{{it}}")
    //@UserMessage(fromResource = "1.txt") 基于工程类路径查找
    Flux<String> check(String prompt);


    @UserMessage("你是一个数学老师,用简单易懂的方式解释数学概念。{{msg}}")
    Flux<String> chat3(@V("msg") String prompt);
}

配置类中,通过AiServices类将刚刚定义的AiAssistant注入容器,并注入之前定义好的ChatModel对象到AiAssistant

package org.example.config;

import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.service.AiServices;
import org.example.ai.AiAssistant;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LangChainConfig {

    @Bean
    public AiAssistant aiAssistant(ChatModel chatModel) {
        return AiServices.builder(AiAssistant.class)
                .chatModel(chatModel)
                .build();
    }

}


然后直接注入AiAssistant到对应类,并调用方法即可

package org.example.controller;

import jakarta.annotation.Resource;
import org.example.ai.AiAssistant;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("high-chat")
public class HighChatController {

    @Resource
    private AiAssistant aiAssistant;

    @GetMapping("chat")
    public String chat(String msg) {
        return aiAssistant.chat(msg);

    }
}

实际上,高层API可以使用接口类加注解的方式进行配置,通过@AiService注解标注为操作大模型的接口类,会直接被实例化,无需在配置类中再去通过AiServices.builder进行实例化

package org.example.ai;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;

@AiService(
        //如需手动配置模型,需要设置属性:AiServiceWiringMode.EXPLICIT
        wiringMode = AiServiceWiringMode.EXPLICIT,
        //如需手动配置模型,要指定具体使用哪个模型,例如:chatModel = "deepseek"
        chatModel = "chatModel"
)
public interface AiAssistant {

    
    String chat(String prompt);

}

2.2 流式对话

  1. 同底层API的流式一样,也要引入langchain4j-reactor依赖
  2. 同样需要先将一个StreamingChatModel的对象注入容器
  3. @AiService注解中大模型属性名使用streamingChatModel,然后调用StreamAssistant的方法即可,Controller中可以直接将Flux对象返回
package org.example.ai;

import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;

@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT,
        streamingChatModel = "streamingChatModel"
)
public interface StreamAssistant {
    
    Flux<String> chat(String prompt);
}
@Resource
private StreamAssistant streamAssistant;

@GetMapping(value = "chat", produces = "text/html; charset=utf-8")
public Flux<String> chat(String msg) {
    return streamAssistant.chat(msg);

}

3.对话记忆ChatMemory

关于会话记忆的概念等,已经在:Spring AI实现一个简单的对话机器人一文中讲到。

先明确langchain4j中的两个概念,记忆和历史

  • 历史(History) 历史记录会完整保存用户与人工智能之间的所有消息。历史记录就是用户在用户界面中看到的内容,它代表了实际发生过的所有对话。

  • 记忆(Memory) 保留一些信息,这些信息会呈现给LLM,使其表现得好像“记住”了对话。记忆与历史记录截然不同。根据所使用的内存算法,它可以以各种方式修改历史记录:例如,删除一些消息、汇总多条消息、汇总单个消息、移除消息中不重要的细节、向消息中注入额外信息(用于RAG算法)或指令(用于结构化输出)等等。

langchain4j目前仅提供记忆管理,不提供历史记录管理。如需要保留完整的历史记录,要手动操作。

langchain4j通过ChatMemory实现记忆缓存,因为一段长对话含有的信息很多,如果不加以修剪,会产生很多冗余,甚至超过一次对话的Token大小限制,因此langchain4j对ChatMemory设计了两种实现:

  • MessageWindowChatMemory 一个比较简单的实现,作为一个滑动窗口,只保留最近的N多个记录
  • TokenWindowChatMemory 保留最近的N多个Token,通过TokenCountEstimator计算会话的令牌数

3.1 底层API实现对话记忆

这里以MessageWindowChatMemory为例,配置类中新增配置

package org.example.config;

import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LangChainConfig {

    /**
     * 采用内存存储
     */
    @Bean
    public ChatMemoryStore chatMemoryStore() {
        return new InMemoryChatMemoryStore();
    }

    /**
     * ChatMemoryProvider类,每次根据不同对话ID生成专属的ChatMemory对象
     */
    @Bean
    public ChatMemoryProvider chatMemoryProvider () {
        return new ChatMemoryProvider() {
            @Override
            public ChatMemory get(Object id) {
                return MessageWindowChatMemory.builder()
                        .id(id)
                        .maxMessages(1000)
                        .chatMemoryStore( chatMemoryStore() )
                        .build();
            }
        };
    }

}

存储会话采用的InMemoryChatMemoryStore仅仅将会话保存到内存,用于测试,实际场景应该将会话持久化保存到数据库中,因此实际项目中需要自行实现一个ChatMemoryStore接口的实现类来保存会话内容

Controller中,注入ChatMemoryProvider对象,将和大模型的对话改造升级为支持记忆的

每次对话,将用户提问和大模型回答都进行保存,关联到同一个会话ID

package org.example.controller;

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.*;
import dev.langchain4j.model.output.TokenUsage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("memory-chat")
@Slf4j
public class MemoryController {


    @Resource
    private StreamingChatModel streamingChatModel;

    @Resource
    private ChatMemoryProvider chatMemoryProvider;

    @GetMapping(value = "streaming", produces = "text/html; charset=utf-8")
    public Flux<String> streaming(String msg, String msgId) {

        // 将问题保存到当前对话记忆
        ChatMemory chatMemory = chatMemoryProvider.get(msgId);
        chatMemory.add(UserMessage.from(msg));

        return Flux.create(sink -> {
            streamingChatModel.chat(chatMemory.messages(), new StreamingChatResponseHandler() {

                @Override
                public void onPartialResponse(PartialResponse partialResponse, PartialResponseContext context) {
                    sink.next(partialResponse.text());
                }

                @Override
                public void onPartialThinking(PartialThinking partialThinking) {
                    sink.next("<span style='color:red;'>" + partialThinking.text() + "</span>");
                }

                @Override
                public void onCompleteResponse(ChatResponse completeResponse) {
                    TokenUsage tokenUsage = completeResponse.tokenUsage();
                    log.info("token usage: {}", tokenUsage);

                    // 大模型回答完毕,将大模型的回答也添加进当前对话记忆
                    AiMessage aiMessage = completeResponse.aiMessage();
                    chatMemory.add(aiMessage);

                    sink.complete();
                }

                @Override
                public void onError(Throwable error) {
                    error.printStackTrace();
                    sink.complete();
                }
            });
        });

    }

}

3.2 高层API实现对话记忆

高层API实现对话记忆,首先接口类的方法要标注一个消息ID@MemoryId String msgId,其次接口方法如果不止一个参数则需要将用户提示词通过@UserMessage注解标注。然后@AiService注解通过属性chatMemoryProvider = "chatMemoryProvider"关联我们之前在配置类声明的chatMemoryProvider对象

package org.example.ai;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;

@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT,
        streamingChatModel = "streamingChatModel",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface StreamAssistant {

    @SystemMessage("你是一个数学老师,用简单易懂的方式解释数学概念。")
    Flux<String> chat(@UserMessage String prompt, @MemoryId String msgId);


}

4.监听

langchain4j允许对大模型调用和返回等进行监控(Observability),并适时触发回调方法,详见:https://docs.langchain4j.dev/tutorials/observability/

新建一个监听器,名字叫MyChatModelListener,实现langchain4j的ChatModelListener接口方法,在发送请求前,得到响应后以及调用出错时触发回调去执行一些代码,而且支持在之间传输自定义的属性,因此可以生成一个traceId供我们调试某次对话。

package org.example.listener;

import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Component
@Slf4j
public class MyChatModelListener implements ChatModelListener {

    @Override
    public void onRequest(ChatModelRequestContext requestContext) {
        String traceId = UUID.randomUUID().toString();
        requestContext.attributes().put("traceId", traceId);
        log.info("********** 请求参数 {} {} ********", traceId, requestContext);
    }

    @Override
    public void onResponse(ChatModelResponseContext responseContext) {
        Object traceId = responseContext.attributes().get("traceId");
        log.info("********** 响应结果 {} {} ********", traceId, responseContext);
    }

    @Override
    public void onError(ChatModelErrorContext errorContext) {
        log.info("********** 请求异常 {}  ********", errorContext );
    }
}

配置类中其他地方都不变,只需要新配置一个listeners属性即可

@Resource
private ChatModelListener chatModelListener;

@Bean
public ChatModel streamingChatModel() {

    return OpenAiChatModel.builder()
            .baseUrl("https://api.deepseek.com/")
            .apiKey(System.getProperty("OPEN_API_KEY"))
            .modelName("deepseek-reasoner")
            .logRequests(true)
            .logResponses(true)
            .returnThinking(true)
            .listeners(Collections.singletonList(chatModelListener))
            .build();
}

如果采用yml配置大模型,MyChatModelListener注入容器后会自动生效,无需在yml中配置,也无法配置

该功能在未来版本可能会有变化