SpringBoot整合LangChain4j
引入依赖
<!--langchain4j起步依赖-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
配置大模型
在配置文件application.yml中配置大模型
各类参数可通过服务商官方文档查询
SpringBoot 将自动为 OpenAiChatModel 注入参数
langchain4j:
open-ai:
chat-model:
base-url: https://api.deepseek.com # 接口地址
api-key: xxxxxxxxx # 秘钥
model-name: deepseek-chat # 模型名称
log-requests: true # 请求日志记录
log-responses: true # 响应日志记录
快速开始
通过配置文件已经自动为对话模型注入了参数,在 Controller 层中尝试直接调用
@RestController
public class ChatController {
@Autowired
private OpenAiChatModel model;
@RequestMapping("/chat")
public String chat(String userMessage){
return model.chat(userMessage);
}
}
AI Service
AI Service 封装 model 对象与会话记忆、RAG知识库、Tools工具等部件,使得每次调用对话方法时不需要手动依次装配
基本使用
引入AI Service 依赖
<!--AiServices依赖-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
声明 AI Service 接口
public interface DemoService {
String demoChat(String userMessage);
}
为接口创建代理对象
通过配置类构建代理对象
@Configuration
public class CommonAiConfig {
@Autowired
private OpenAiChatModel chatModel;
@Bean
public DemoService demoService() {
return AiServices.builder(DemoService.class)
.chatModel(chatModel)
.build();
}
}
在 Controller 中注入并使用
直接注入Service对象使用即可
@RestController
public class ChatController {
@Resource
private DemoService demoService;
@RequestMapping("/chat-service")
public String chatService(String userMessage) {
return demoService.demoChat(userMessage);
}
}
@AiService注解
在 Service 接口上添加 @AiService 注解,自动为 service 接口装配,也可以指定手动装配
若使用注解完成装配,则不需要再编写配置类进行代理对象的构建
手动装配:
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,// 手动装配
chatModel = "openAiChatModel"
)
public interface DemoService {
String demoChat(String userMessage);
}
自动装配:
@AiService
public interface DemoService {
String demoChat(String userMessage);
}
流式调用
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.1.0-beta7</version>
</dependency>
配置流式调用模型
在配置文件中新增流式模型的配置参数
SpringBoot 自动注入 OpenAiStreamingChatModel 对象
langchain4j:
open-ai:
streaming-chat-model:
base-url: https://api.deepseek.com
api-key: sk-0802c3e7747d4baf820656e5122dc293
model-name: deepseek-chat
log-requests: true # 请求日志记录
log-responses: true # 响应日志记录
流式接口
在 @AiService 注解中指定流式调用模型openAiStreamingChatModel,将接口返回类型改为Flux<String>
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,// 手动装配
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel"
)
public interface DemoService {
Flux<String> demoChat(String userMessage);
}
修改 Controller 中的返回类型
添加produces = "text/html;charset=utf-8"防止编码错误
@RestController
public class ChatController {
@Resource
private DemoService demoService;
@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chatService(String userMessage) {
return demoService.demoChat(userMessage);
}
}
消息注解
注意系统提示词与用户提示词对于 AI 模型的影响能力具有实质的区别。系统提示词影响权重更高
@SystemMessage
在Service接口前使用,设置系统提示词
1.直接指定提示词:
@SystemMessage("系统指定提示词")
Flux<String> demoChat2(String userMessage);
2.通过fromResource指定文件名:
首先在 resouces 目录下新建 prompt 目录存放提示词,再

@SystemMessage(fromResource = "prompt/SystemPrompt1.txt")
Flux<String> demoChat3(String userMessage);
@UserMessage
在Service接口前使用,设置用户提示词
@SystemMessage(fromResource = "prompt/SystemPrompt1.txt")
@UserMessage("用户提示词xxxxxxxxxx")
Flux<String> demoChat3(String userMessage);
同样,用户提示词也可以使用fromResource指定文件名
占位符
1)接口只含单个参数:使用{{it}}放置于系统提示词或用户提示词中的指定位置站位,在调用接口前会自动将占位符替换为对应的参数
2)接口含有多个参数:需要使用@V注解为接口中的参数命名,同时将站位符中的“it”替换为注解新定义的名称
注意:对于一个接口,尽管@V注解可以用于@SystemMessage也可以用于@UserMessage,但只要使用了@V就必须使用@UserMessage注解,否则会发生 500 错误
@SystemMessage(fromResource = "prompt/SystemPrompt1.txt")
@UserMessage("用户提示词{{msg1}}xxxxxxx{{msg2}}")
Flux<String> demoChat4(@V("msg1") String userMessage1, @V("msg2") String userMessage2);
会话记忆
大模型不具有会话记忆功能,唯一的方法就是将之前的聊天记录和本次提问一起发送给大模型
会话记忆对象(存储于内存)
ChatMemory源码
public interface ChatMemory {
Object id();// 会话记忆存储对象的唯一标识
void add(ChatMessage var1);// 增加一条会话记录
default void add(ChatMessage... messages) {
if (messages != null && messages.length > 0) {
this.add((Iterable)Arrays.asList(messages));
}
}
default void add(Iterable<ChatMessage> messages) {
if (messages != null) {
messages.forEach(this::add);
}
}
List<ChatMessage> messages();// 获取所有会话记录
void clear();//清除所有会话记录
}
配置并使用会话记忆对象
编写会话记忆配置类,对会话记忆进行配置
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
}
}
为@AIService指定会话记忆对象
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,// 手动装配
chatModel = "openAiChatModel",
chatMemory = "chatMemoryDemo"// 指定会话记忆对象
)
public interface DemoService {
@SystemMessage("系统指定提示词")
Flux<String> demoChat(String userMessage);
}
会话记忆隔离
定义并使用会话记忆提供者
1.编写会话记忆提供者配置类:
不再需要定义和配置ChatMemory对象
@Configuration
public class ChatMemoryProviderConfig {
@Bean
public ChatMemoryProvider chatMemoryProvider() {
ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
@Override
public ChatMemory get(Object memoryId) {
return MessageWindowChatMemory
.builder()
.id(memoryId)
.maxMessages(20)
.build();
}
};
return chatMemoryProvider;
}
}
2.在AI Service中使用会话记忆提供者:
对于AI Service接口中的参数,若包含@MemoryId注解时,用户消息必须添加@UserMessage注解
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,// 手动装配
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel",
//chatMemory = "chatMemoryDemo",// 指定对话记忆对象
chatMemoryProvider = "chatMemoryProvider"
)
public interface DemoService {
Flux<String> demoChat(@MemoryId String memoryId,
@UserMessage String userMessage);
}
3.在 Controller 层中接收 memoryId:
@RestController
public class ChatController {
@Resource
private DemoService demoService;
@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chatService(String memoryId, String userMessage) {
return demoService.demoChat(memoryId, userMessage);
}
}
会话记忆持久化
MessageWindowChatMemory源码使用ChatMemoryStore进行存储,会话记忆存储在JVM的内存中
public class MessageWindowChatMemory implements ChatMemory {
private final Object id;
private final Integer maxMessages;
private final ChatMemoryStore store;
}
缓存
1)框架中含有RedisChatMemoryStore用于缓存对话消息,只需要配置Redis链接即可
public class RedisChatMemoryStore implements ChatMemoryStore {
private final JedisPooled client;
private final String keyPrefix;
private final Long ttl;
2)Redis链接配置
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")
@Data
public class RedisChatMemoryStoreConfig {
private String host;
private int port;
private String password;
private long ttl;
@Bean
public RedisChatMemoryStore redisChatMemoryStore() {
RedisChatMemoryStore.Builder builder = RedisChatMemoryStore.builder()
.host(host)
.port(port)
.password(password)
.ttl(ttl);
if (StrUtil.isNotBlank(password)) {
builder.user("default");
}
return builder.build();
}
}
3)创建chatMemory对象时为其chatMemoryStore组件添加redisChatMemoryStore
MessageWindowChatMemory chatMemory = MessageWindowChatMemory
.builder()
.id(appId)
.chatMemoryStore(redisChatMemoryStore)
.maxMessages(20)
.build();
持久化
使用MySQL存储每轮对话记忆即可
-- 对话历史表
create table chat_history
(
id bigint auto_increment comment 'id' primary key,
message text not null comment '消息',
messageType varchar(32) not null comment 'user/ai',
appId bigint not null comment '应用id',
userId bigint not null comment '创建用户id',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
INDEX idx_appId (appId), -- 提升基于应用的查询性能
INDEX idx_createTime (createTime), -- 提升基于时间的查询性能
INDEX idx_appId_createTime (appId, createTime) -- 游标查询核心索引
) comment '对话历史' collate = utf8mb4_unicode_ci;
结构化输出
基础实现
直接定义目标 POJO 类并用于AI Service接口的返回类型,LangChain4j 框架会自动要求 AI 返回 JSON 格式内容,并自动将内容填充到对象中
LangChain4j 框架支持多种类型,参考官方文档:[https://docs.langchain4j.info/tutorials/structured-outputs]
示例:
record Person(String firstName, String lastName) {}
enum Sentiment {
POSITIVE, NEGATIVE, NEUTRAL
}
interface Assistant {
Person extractPersonFrom(String text);
Sentiment extractSentimentFrom(String text);
}
稳定性优化
设置maxToken
LLM 通常设置有输出的最大 Token 数,若输出达到最大 Token 数时,JSON 格式的结果仍然没有输出完毕,则会发生截断,导致错误
示例:deepseek
langchain4j:
open-ai:
chat-model:
max-tokens: 8129 # 设置最大Tokes,降低 JSON 解析错误
配置 json 格式返回要求
LLM 的官方文档通常会给出配置方法(构建 chatModel 或编写配置文件时添加)
示例:deepseek
langchain4j:
open-ai:
chat-model:
strict-json-schema: true # 要求严格返回 JSON 格式字符串
response-format: json_object # 要求严格返回 JSON 格式字符串
为 POJO 类添加字段描述
使用@Description注解,为类和字段添加详细描述
示例:
/**
* HTML 生成结果
*/
@Data
@Description("生成 HTML 代码文件的结果")
public class HtmlCodeResult {
/**
* HTML 代码
*/
@Description("HTML代码")
private String htmlCode;
/**
* 描述
*/
@Description("生成代码的描述")
private String description;
}
系统提示词优化
prompt 严格规范LLM 的输出格式,精确到每一部分的格式,并要求以JSON格式返回字段
工具调用
工具定义
编写 AI 工具,使用@Tool描述方法,@P描述方法参数
@Slf4j
public class FileWriteTool {
@Tool("写入文件到指定路径")
public String writeFile(
@P("文件的相对路径")
String relativeFilePath,
@P("要写入文件的内容")
String content
) {
// 具体实现
}
}
工具装配
在构建 AI Service 时,使用.tools()方法将所有工具类对象装配
AiServices.builder(AiCodeGeneratorService.class)
.chatModel(chatModel)
.tools((Object[]) toolManager.getAllTools())// 绑定工具
.build();
稳定性优化
1.幻觉问题:
通过.hallucinatedToolNameStrategy()方法,处理 AI 出现幻觉调用不存在的工具的情况
2.无限循环:
通过.maxSequentialToolsInvocations()方法,设置工具调用次数上限,防止无限调用工具死循环
AiServices.builder(AiCodeGeneratorService.class)
.chatModel(chatModel)
.tools((Object[]) toolManager.getAllTools())// 绑定工具
.hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
toolExecutionRequest, "Error: there is no tool called " + toolExecutionRequest.name()
))// 当AI出现幻觉调用不存在的工具时的处理方法
.maxSequentialToolsInvocations(30)// 最多连续调用工具30次,防止无限循环
.build();
输入输出护轨
护轨
输入护轨:对用户提示词进行安全校验
输出护轨:对 AI 输出内容进行安全校验
实践
创建“输入护轨类”与“输出护轨”类,分别继承InputGuardrail接口与OutputGuardrail接口
输入:
/**
* 输入护轨
*/
public class PromptSafetyInputGuardrail implements InputGuardrail {
/**
* 审查逻辑
*
* @param userMessage 用户消息
* @return 是否通过
*/
@Override
public InputGuardrailResult validate(UserMessage userMessage) {
// 校验业务
}
}
输出:
/**
* AI 输出护轨
*/
public class RetryOutputGuardrail implements OutputGuardrail {
/**
* AI 输出校验
*
* @param responseFromLLM AI 响应内容
* @return 校验结果
*/
@Override
public OutputGuardrailResult validate(AiMessage responseFromLLM) {
// 校验业务
}
}
装配:
在创建AIService对象时装配
AiServices.builder(AiCodeGeneratorService.class)
.chatModel(chatModel)
.inputGuardrails(new PromptSafetyInputGuardrail())// 添加输入护轨
.outputGuardrails(new RetryOutputGuardrail())// 添加输出护轨,为了流式输出这里不使用
.build();
RAG知识库
原理
概念
RAG:检索增强生成。通过检索外部知识库的方式增强大模型的生成能力
向量数据库:以向量的形式存储外部知识
向量相似度:两个向量夹角的余弦值。两个向量的余弦相似度越高,说明向量对应的文本相似度越高
存储过程

1)首先文本 Document 首先被文本分割器 Text Splitter 分割为文本片段 Segment
2)接着文本片段 Segment 被嵌入模型 Embdding Model 转化为向量片段 Embddings
3)最终文本片段 Segment 与 向量片段一起存储入向量数据库
检索过程

1)首先用户询问 Query 被嵌入模型 Embdding Model 转化为询问向量片段 Query Embdding
2)接着询问向量片段 Query Embdding进入向量数据库 Embdding Store 中检索出相关文本片段 Relevant Segment
3)最终用户询问 Query 与相关文本片段 Relevant Segment 一同交予大模型处理
快速开始(easy-RAG)
引入依赖
简易的内存向量数据库
<!--RAG-easy-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.1.0-beta7</version>
</dependency>
存储
1)创建向量数据库操作对象ingestor,同时将创建的向量数据库embeddingStore交予操作对象
2)将读入的文档交予ingestor,将自动执行文档的切分、向量化、存储
/**
* 向量数据库操作对象
*/
@Configuration
public class EmbeddingStoreConfig {
/**
* 配置向量数据库操作对象
*
* @return 向量数据库操作对象 Bean
*/
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
// 1. 加载文档进内存(若为 null 则替换为空列表,避免后续 NPE)
List<Document> documentList = ClassPathDocumentLoader.loadDocuments("content");
// 2. 构建向量数据库操作对象(使用接口类型声明)
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
// 3. 构建 EmbeddingStoreIngestor 并在有文档时执行切分/向量化/存储
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(embeddingStore)
.build();
// 执行
ingestor.ingest(documentList);
// 4. 返回对象,交由 IOC 管理
return embeddingStore;
}
}
检索
构建向量数据库检索对象contentRetriever,同时设定部分参数
/**
* 向量数据库检索对象
*/
@Configuration
public class ContentRetrieverConfig {
/**
* 向量数据库操作对象
*/
@Resource
private EmbeddingStore<TextSegment> store;
/**
* 配置向量数据库检索对象
* @return 向量数据库检索对象 Bean
*/
@Bean
public ContentRetriever contentRetriever() {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)// 添加向量数据库操作对象
.minScore(0.5)// 最小匹配程度
.maxResults(3)// 最大结果条数
.build();
}
}
装配 AI Service 接口
将向量数据库检索对象contentRetriever装配至 AI Service 接口
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,// 手动装配
chatModel = "openAiChatModel",
contentRetriever = "contentRetriever"// 配置向量数据库检索对象
)
public interface DemoService {
Flux<String> demoChat(@MemoryId String memoryId,
@UserMessage String userMessage);
}
核心 API
结构
EmbeddingStoreIngestor已经封装了具体的操纵,开发者只需选择装配时选择哪种文档分割器、向量模型、向量数据库操作对象

文档加载器Document Loader
文档加载器:用于将磁盘或网络中的数据加载进程序
部分加载器对象:
FileSystemDocumentLoader,根据本地磁盘绝对路径加载
ClassPathDocumentLoader,相对于类路径加载
UrlDocumentLoader,根据url路径加载
文档解析器Document Parser
文档解析器:用于解析使用文档加载器加载进内存的内容,把非纯文本数据转化成纯文本
部分解析器对象:
TextDocumentParser,解析纯文本格式的文件
ApachePdfBoxDocumentParser,解析pdf格式文件
ApacheTikaDocumentParser (默认),几乎可以解析所有格式的文件
尽管默认文件解析器能够解析几乎所有的文件,但对于特定文件如PDF文件,默认解析能力不如专门的PDF解析器
使用:
1)引入依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
<version>1.1.0-beta7</version>
</dependency>
2)直接在读取文件方法后new出解析对象即可
List<Document> documentList = ClassPathDocumentLoader.loadDocuments("content",new ApachePdfBoxDocumentParser());
文本分割器Document Splitter
文本分割器:将一个大文档切割成一个个小片段
部分分割器对象:
DocumentByParagraphSplitter,按照段落分割文本
DocumentSplotters.recursive(...)(默认),递归分割器,优先按照段落划分,而后依次是按照行、句、词分割
使用方式:
1)构建文本分割器对象:设置每个片段最大字符数量,两相邻片段之间重叠字符个数
两相邻片段之间重叠字符个数:
目的:提高两个相邻片段的关联性,使得划分后不会出现断章取义问题
操作:将上一片段的后一小部分添加至下一片段的首部;将下一片段的前一小部分添加至上一片段的尾部
2)设置文本分割器对象
// 构建文本分割器,并分别设置片段长度与重复字符长度
DocumentSplitter documentSplitter = DocumentSplitters.recursive(500, 100);
// 3. 构建 EmbeddingStoreIngestor 并在有文档时执行切分/向量化/存储
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(embeddingStore)
.documentSplitter(documentSplitter)
.build();
向量模型EmbeddingModel
向量模型:把文档分割后的片段和用户输入内容向量化
1.配置向量模型信息:
langchain4j:
open-ai:
embedding-model:
base-url: xxxx
api-key: xxxx
model-name: xxxx
log-requests: true # 请求日志记录
log-responses: true # 响应日志记录
max-segments-per-batch: 10 # 每次发送的最大片段数量
2.设置EmbeddingModel:
分别注入向量模型并装配入向量数据库操作对象与检索对象
1)操作对象:
/**
* 向量数据库操作对象
*/
@Configuration
public class EmbeddingStoreConfig {
@Resource
private EmbeddingModel embeddingModel;
/**
* 配置向量数据库操作对象
*
* @return 向量数据库操作对象 Bean
*/
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingModel(embeddingModel)
.build();
return embeddingStore;
}
}
1)检索对象
/**
* 向量数据库检索对象
*/
@Configuration
public class ContentRetrieverConfig {
@Resource
private EmbeddingModel embeddingModel;
/**
* 向量数据库操作对象
*/
@Resource
private EmbeddingStore<TextSegment> store;
/**
* 配置向量数据库检索对象
* @return 向量数据库检索对象 Bean
*/
@Bean
public ContentRetriever contentRetriever() {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)// 添加向量数据库操作对象
.minScore(0.5)// 最小匹配程度
.maxResults(3)// 最大结果条数
.embeddingModel(embeddingModel)
.build();
}
}
向量数据库操作对象EmbeddingStore(持久化向量数据库)
向量数据库操作对象:用户操作向量数据库(添加、检索)
注意:若要进行持久化,则不在使用InMemoryEmbeddingStore(操作内存数据库)
示例:
1)引入RedisEmbeddingStore依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-redis-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
2)配置
langchain4j:
community:
redis:
host: xxxx
port: 6379
3)注入并使用
操作对象:
注意已经使用外部向量数据库后,不在需要为操作对象添加Bean,防止每次运行都在此将全部数据向量化
/**
* 向量数据库操作对象
*/
@Configuration
public class EmbeddingStoreConfig {
@Resource
private EmbeddingModel embeddingModel;
@Resource
private RedisEmbeddingStore redisEmbeddingStore;
/**
* 配置向量数据库操作对象
*
* @return 向量数据库操作对象 Bean
*/
//@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
// 1. 加载文档进内存(若为 null 则替换为空列表,避免后续 NPE)
List<Document> documentList = ClassPathDocumentLoader.loadDocuments("content");
// 2. 构建 EmbeddingStoreIngestor 并在有文档时执行切分/向量化/存储
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(redisEmbeddingStore)// 装配向量数据库操作对象
.embeddingModel(embeddingModel)// 装配向量模型
.build();
// 执行
ingestor.ingest(documentList);
// 3. 返回对象,交由 IOC 管理
return redisEmbeddingStore;
}
}
检索对象:
/**
* 向量数据库检索对象
*/
@Configuration
public class ContentRetrieverConfig {
@Resource
private EmbeddingModel embeddingModel;
@Resource
private RedisEmbeddingStore redisEmbeddingStore;
/**
* 配置向量数据库检索对象
* @return 向量数据库检索对象 Bean
*/
@Bean
public ContentRetriever contentRetriever() {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(redisEmbeddingStore)// 添加向量数据库操作对象
.minScore(0.5)// 最小匹配程度
.maxResults(3)// 最大结果条数
.embeddingModel(embeddingModel)// 装配向量模型
.build();
}
}

顶尖