你好!作为一名java开发,我也曾看着python那边的langchain生态眼馋。不过现在好了,langchain4j 让我们java开发者也能优雅地接入大模型了。下面我就把整套方案给你梳理出来,从零到一,保姆级,咱们直接开干!
一、项目初始化
1.1 技术栈版本(建议)
- jdk 17+(langchain4j 要求 jdk 17 起步)
- spring boot 3.x
- mysql 8.0+
- redis stack 7.x(必须开启 redisearch 模块,用于向量检索)
- maven 3.6+
1.2 核心依赖(pom.xml)
<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xsi:schemalocation="http://maven.apache.org/pom/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelversion>4.0.0</modelversion>
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version>3.4.5</version>
<relativepath/>
</parent>
<groupid>com.example</groupid>
<artifactid>langchain4j-springboot-demo</artifactid>
<version>1.0.0</version>
<properties>
<java.version>21</java.version>
<langchain4j.version>1.0.0-beta3</langchain4j.version>
</properties>
<!-- bom统一管理版本,防止依赖冲突 -->
<dependencymanagement>
<dependencies>
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j-bom</artifactid>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencymanagement>
<dependencies>
<!-- spring boot web -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- langchain4j 核心 -->
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j</artifactid>
</dependency>
<!-- langchain4j spring boot starter(声明式ai服务、rag、tools等) -->
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j-spring-boot-starter</artifactid>
</dependency>
<!-- openai兼容接口的spring boot starter(支持通义千问等) -->
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j-open-ai-spring-boot-starter</artifactid>
</dependency>
<!-- ========== redis 相关依赖(用于向量存储) ========== -->
<!-- spring boot redis starter(提供 redis 客户端) -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>
<!-- langchain4j redis 向量存储 spring boot starter -->
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j-community-redis-spring-boot-starter</artifactid>
<version>1.0.1-beta6</version>
</dependency>
<!-- mybatis-plus(orm框架,操作数据库) -->
<dependency>
<groupid>com.baomidou</groupid>
<artifactid>mybatis-plus-spring-boot3-starter</artifactid>
<version>3.5.6</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupid>com.mysql</groupid>
<artifactid>mysql-connector-j</artifactid>
<scope>runtime</scope>
</dependency>
<!-- lombok(简化代码) -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<optional>true</optional>
</dependency>
<!-- jackson(json序列化,langchain4j内部已包含,此处显式引入确保版本一致) -->
<dependency>
<groupid>com.fasterxml.jackson.core</groupid>
<artifactid>jackson-databind</artifactid>
</dependency>
</dependencies>
</project>小贴士:langchain4j-community-redis-spring-boot-starter 依赖了 jedis,如果和 spring-boot-starter-data-redis(默认用 lettuce)版本冲突,可以手动排除 jedis 或保持两者并存,实际测试中 lettuce 和 jedis 可以共存。
二、配置文件(application.yml)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/langchain4j_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.driver
# ============ redis 配置(用于向量存储) ============
data:
redis:
host: localhost
port: 6379
# password: your_redis_password # 如果有密码则配置
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
# langchain4j 配置
langchain4j:
open-ai:
chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 # 通义千问兼容openai接口
api-key: sk-your-api-key-here # 去阿里云百炼申请
model-name: qwen-max
log-requests: true
log-responses: true
# mybatis-plus配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.stdoutimpl
global-config:
db-config:
id-type: auto注意:rag 的向量检索依赖 redis stack(带 redisearch 模块)。如果用 docker,推荐命令:docker run -d --name redis-vector -p 6379:6379 -p 8001:8001 redis/redis-stack:latest。
三、数据库表设计
根据需求,我们需要两张表:会话表(conversation) 和 消息表(message)。
-- 创建数据库
create database if not exists langchain4j_db default character set utf8mb4;
use langchain4j_db;
-- 会话表:存储每个对话会话的元信息
create table conversation (
id bigint primary key auto_increment comment '主键id',
conversation_id varchar(64) not null unique comment '会话唯一标识(对外暴露)',
user_id varchar(64) not null comment '用户id(用于多用户隔离)',
title varchar(200) default '' comment '会话标题',
status tinyint default 1 comment '状态:1-活跃 0-已关闭',
created_at datetime default current_timestamp comment '创建时间',
updated_at datetime default current_timestamp on update current_timestamp comment '更新时间',
index idx_user_id (user_id),
index idx_conversation_id (conversation_id)
) engine=innodb default charset=utf8mb4 comment='对话会话表';
-- 消息表:存储每条对话消息
create table message (
id bigint primary key auto_increment comment '主键id',
conversation_id varchar(64) not null comment '所属会话id',
role varchar(20) not null comment '角色:user/assistant/system/tool',
content text not null comment '消息内容',
tool_name varchar(100) default '' comment '工具名称(仅tool角色时有值)',
tool_execution_id varchar(100) default '' comment '工具执行id',
created_at datetime default current_timestamp comment '创建时间',
index idx_conversation_id (conversation_id),
index idx_created_at (created_at)
) engine=innodb default charset=utf8mb4 comment='对话消息表';四、实体类与mapper
4.1 会话实体(conversation.java)
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.data;
import java.time.localdatetime;
@data
@tablename("conversation")
public class conversation {
@tableid(type = idtype.auto)
private long id;
@tablefield("conversation_id")
private string conversationid;
@tablefield("user_id")
private string userid;
private string title;
private integer status; // 1-活跃 0-已关闭
@tablefield("created_at")
private localdatetime createdat;
@tablefield("updated_at")
private localdatetime updatedat;
}
4.2 消息实体(message.java)
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.data;
import java.time.localdatetime;
@data
@tablename("message")
public class message {
@tableid(type = idtype.auto)
private long id;
@tablefield("conversation_id")
private string conversationid;
private string role; // user / assistant / system / tool
private string content;
@tablefield("tool_name")
private string toolname;
@tablefield("tool_execution_id")
private string toolexecutionid;
@tablefield("created_at")
private localdatetime createdat;
}4.3 mapper接口
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.basemapper;
import com.example.demo.entity.conversation;
import org.apache.ibatis.annotations.mapper;
@mapper
public interface conversationmapper extends basemapper<conversation> {
}
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.basemapper;
import com.example.demo.entity.message;
import org.apache.ibatis.annotations.mapper;
@mapper
public interface messagemapper extends basemapper<message> {
}
五、服务层(核心业务)
5.1 会话服务(conversationservice.java)
package com.example.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.lambdaquerywrapper;
import com.baomidou.mybatisplus.extension.service.impl.serviceimpl;
import com.example.demo.entity.conversation;
import com.example.demo.mapper.conversationmapper;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.service;
import java.util.list;
import java.util.uuid;
@slf4j
@service
public class conversationservice extends serviceimpl<conversationmapper, conversation> {
/**
* 创建新会话
*/
public conversation createconversation(string userid, string title) {
conversation conversation = new conversation();
conversation.setconversationid(uuid.randomuuid().tostring().replace("-", ""));
conversation.setuserid(userid);
conversation.settitle(title != null ? title : "新对话");
conversation.setstatus(1);
save(conversation);
log.info("创建会话成功:conversationid={}, userid={}", conversation.getconversationid(), userid);
return conversation;
}
/**
* 获取用户的会话列表
*/
public list<conversation> listbyuserid(string userid) {
lambdaquerywrapper<conversation> wrapper = new lambdaquerywrapper<>();
wrapper.eq(conversation::getuserid, userid)
.orderbydesc(conversation::getupdatedat);
return list(wrapper);
}
/**
* 关闭会话
*/
public boolean closeconversation(string conversationid, string userid) {
lambdaquerywrapper<conversation> wrapper = new lambdaquerywrapper<>();
wrapper.eq(conversation::getconversationid, conversationid)
.eq(conversation::getuserid, userid);
conversation conversation = getone(wrapper);
if (conversation == null) {
return false;
}
conversation.setstatus(0);
return updatebyid(conversation);
}
}
5.2 消息服务(messageservice.java)
package com.example.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.lambdaquerywrapper;
import com.baomidou.mybatisplus.extension.service.impl.serviceimpl;
import com.example.demo.entity.message;
import com.example.demo.mapper.messagemapper;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.service;
import java.util.list;
@slf4j
@service
public class messageservice extends serviceimpl<messagemapper, message> {
/**
* 保存一条消息
*/
public void savemessage(string conversationid, string role, string content) {
savemessage(conversationid, role, content, null, null);
}
/**
* 保存一条消息(含工具信息)
*/
public void savemessage(string conversationid, string role, string content,
string toolname, string toolexecutionid) {
message message = new message();
message.setconversationid(conversationid);
message.setrole(role);
message.setcontent(content);
message.settoolname(toolname != null ? toolname : "");
message.settoolexecutionid(toolexecutionid != null ? toolexecutionid : "");
save(message);
log.debug("保存消息成功:conversationid={}, role={}, content长度={}",
conversationid, role, content != null ? content.length() : 0);
}
/**
* 查询会话的所有消息(按时间升序)
*/
public list<message> listbyconversationid(string conversationid) {
lambdaquerywrapper<message> wrapper = new lambdaquerywrapper<>();
wrapper.eq(message::getconversationid, conversationid)
.orderbyasc(message::getcreatedat);
return list(wrapper);
}
}
六、mysql持久化的chatmemorystore实现
这一步是关键!我们需要实现 chatmemorystore 接口,让langchain4j的对话记忆能持久化到mysql。
package com.example.demo.store;
import com.example.demo.entity.message;
import com.example.demo.service.messageservice;
import dev.langchain4j.data.message.chatmessage;
import dev.langchain4j.data.message.chatmessagedeserializer;
import dev.langchain4j.data.message.chatmessageserializer;
import dev.langchain4j.memory.chatmemory;
import dev.langchain4j.memory.chat.messagewindowchatmemory;
import dev.langchain4j.store.memory.chat.chatmemorystore;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.component;
import java.util.arraylist;
import java.util.list;
/**
* 基于mysql的chatmemorystore实现
* 将对话记忆持久化到mysql,每个会话的消息以json数组形式存储
*/
@slf4j
@component
@requiredargsconstructor
public class mysqlchatmemorystore implements chatmemorystore {
private final messageservice messageservice;
/**
* 根据memoryid获取该会话的所有消息
* memoryid 对应 conversationid
*/
@override
public list<chatmessage> getmessages(object memoryid) {
string conversationid = memoryid.tostring();
log.debug("从mysql加载会话消息:conversationid={}", conversationid);
list<message> messages = messageservice.listbyconversationid(conversationid);
list<chatmessage> chatmessages = new arraylist<>();
for (message msg : messages) {
// 将数据库中的消息反序列化为chatmessage对象
string json = string.format(
"{\"role\":\"%s\",\"text\":\"%s\"}",
msg.getrole(),
msg.getcontent().replace("\"", "\\\"")
);
// 使用langchain4j内置的序列化工具
chatmessage chatmessage = chatmessagedeserializer.messagefromjson(json);
chatmessages.add(chatmessage);
}
return chatmessages;
}
/**
* 更新会话的所有消息(全量替换)
* langchain4j的chatmemory在每次对话后都会调用此方法
*/
@override
public void updatemessages(object memoryid, list<chatmessage> messages) {
string conversationid = memoryid.tostring();
log.debug("更新mysql会话消息:conversationid={}, 消息数={}", conversationid, messages.size());
// 简单起见:先删除该会话所有旧消息,再批量插入新消息
// 生产环境可优化为增量更新
messageservice.lambdaupdate()
.eq(message::getconversationid, conversationid)
.remove();
for (chatmessage msg : messages) {
string role = msg.type().name().tolowercase();
string content = msg.text();
messageservice.savemessage(conversationid, role, content);
}
}
/**
* 删除会话的所有消息
*/
@override
public void deletemessages(object memoryid) {
string conversationid = memoryid.tostring();
log.info("删除mysql会话消息:conversationid={}", conversationid);
messageservice.lambdaupdate()
.eq(message::getconversationid, conversationid)
.remove();
}
}
特别说明:上面的 getmessages 方法中,我用了简化的json反序列化方式。生产环境中建议使用 chatmessageserializer 和 chatmessagedeserializer 配合jackson来做完整的序列化/反序列化。
七、配置类:组装ai服务
package com.example.demo.config;
import com.example.demo.store.mysqlchatmemorystore;
import dev.langchain4j.memory.chatmemory;
import dev.langchain4j.memory.chat.messagewindowchatmemory;
import dev.langchain4j.model.chat.chatlanguagemodel;
import dev.langchain4j.service.aiservices;
import dev.langchain4j.service.memoryid;
import dev.langchain4j.service.systemmessage;
import dev.langchain4j.service.usermessage;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import java.util.function.function;
@slf4j
@configuration
@requiredargsconstructor
public class langchain4jconfig {
private final chatlanguagemodel chatmodel;
private final mysqlchatmemorystore memorystore;
/**
* 基础ai服务(无记忆)
*/
@bean
public simpleaiservice simpleaiservice() {
return aiservices.builder(simpleaiservice.class)
.chatmodel(chatmodel)
.build();
}
/**
* 带提示词模板的ai服务
*/
@bean
public promptaiservice promptaiservice() {
return aiservices.builder(promptaiservice.class)
.chatmodel(chatmodel)
.build();
}
/**
* 带会话记忆的ai服务(保留对话轮次)
* 使用messagewindowchatmemory,保留最近n条消息
*/
@bean
public memoryaiservice memoryaiservice() {
return aiservices.builder(memoryaiservice.class)
.chatmodel(chatmodel)
// 每个会话独立记忆,最多保留20条消息
.chatmemoryprovider(memoryid ->
messagewindowchatmemory.builder()
.id(memoryid)
.maxmessages(20)
.chatmemorystore(memorystore) // 持久化到mysql
.build()
)
.build();
}
/**
* rag + tool calling + 会话管理的综合ai服务
*/
@bean
public advancedaiservice advancedaiservice(
dev.langchain4j.rag.content.retriever.contentretriever contentretriever,
list<object> tools) {
var builder = aiservices.builder(advancedaiservice.class)
.chatmodel(chatmodel)
// 会话记忆:使用mysql持久化
.chatmemoryprovider(memoryid ->
messagewindowchatmemory.builder()
.id(memoryid)
.maxmessages(30)
.chatmemorystore(memorystore)
.build()
)
// rag检索增强
.contentretriever(contentretriever);
// 注册工具
if (tools != null && !tools.isempty()) {
builder.tools(tools.toarray());
}
return builder.build();
}
}
八、ai服务接口定义(声明式)
8.1 基础ai服务(普通对话)
package com.example.demo.service.ai;
import dev.langchain4j.service.aiservice;
import dev.langchain4j.service.usermessage;
@aiservice
public interface simpleaiservice {
/**
* 普通对话接口:用户说什么,ai回什么
*/
string chat(@usermessage string usermessage);
}
8.2 带提示词模板的ai服务
package com.example.demo.service.ai;
import dev.langchain4j.service.aiservice;
import dev.langchain4j.service.systemmessage;
import dev.langchain4j.service.usermessage;
import dev.langchain4j.service.v;
/**
* 带提示词模板的ai服务
* 通过@systemmessage设定角色,@v绑定变量
*/
@aiservice
public interface promptaiservice {
/**
* 带系统提示词的对话
* @param usermessage 用户输入
* @return ai响应
*/
@systemmessage("你是一位资深的java技术专家,擅长spring boot和微服务架构。请用专业且易懂的方式回答问题。")
string chat(@usermessage string usermessage);
/**
* 带变量的提示词模板
* @param name 用户名称
* @param question 用户问题
* @return ai响应
*/
@systemmessage("你是一位{{role}}专家")
@usermessage("你好{{name}},请回答:{{question}}")
string chatwithtemplate(@v("role") string role,
@v("name") string name,
@v("question") string question);
}
8.3 保留对话轮次的ai服务(带记忆)
package com.example.demo.service.ai;
import dev.langchain4j.service.aiservice;
import dev.langchain4j.service.memoryid;
import dev.langchain4j.service.usermessage;
/**
* 带会话记忆的ai服务
* 通过@memoryid实现多用户/多会话隔离
*/
@aiservice
public interface memoryaiservice {
/**
* 带记忆的对话
* @param conversationid 会话id(用于隔离不同会话的记忆)
* @param usermessage 用户输入
* @return ai响应
*/
string chat(@memoryid string conversationid, @usermessage string usermessage);
}
8.4 综合ai服务:rag + tool calling + 会话管理
package com.example.demo.service.ai;
import dev.langchain4j.service.aiservice;
import dev.langchain4j.service.memoryid;
import dev.langchain4j.service.systemmessage;
import dev.langchain4j.service.usermessage;
@aiservice
public interface advancedaiservice {
/**
* 综合对话接口:支持rag检索 + 工具调用 + 会话记忆
*
* @param conversationid 会话id(用于记忆隔离和持久化)
* @param usermessage 用户输入
* @return ai响应(包含检索增强和工具调用的结果)
*/
@systemmessage("""
你是一个智能助手,可以访问知识库和调用工具来帮助用户。
如果用户的问题涉及专业知识,请优先从知识库中检索相关信息。
如果需要实时数据或执行特定操作,请调用相应的工具。
回答要准确、简洁、友好。
""")
string chat(@memoryid string conversationid, @usermessage string usermessage);
}
九、rag配置(检索增强生成)- 基于redis向量存储
rag需要向量数据库的支持。这里我们选择 redis stack(带 redisearch 模块),相比 pgvector,redis 能提供亚毫秒级的向量检索速度,非常适合实时对话场景。
9.1 redis连接配置(可选,用于自定义)
如果默认的 redisconnectionfactory 自动配置不满足需求,可以手动配置:
package com.example.demo.config;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisstandaloneconfiguration;
import org.springframework.data.redis.connection.lettuce.lettuceconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.serializer.stringredisserializer;
@configuration
public class redisconfig {
@bean
public lettuceconnectionfactory redisconnectionfactory() {
redisstandaloneconfiguration config = new redisstandaloneconfiguration();
config.sethostname("localhost");
config.setport(6379);
// config.setpassword(redispassword.of("your_password"));
config.setdatabase(0);
return new lettuceconnectionfactory(config);
}
@bean
public redistemplate<string, object> redistemplate(lettuceconnectionfactory connectionfactory) {
redistemplate<string, object> template = new redistemplate<>();
template.setconnectionfactory(connectionfactory);
template.setkeyserializer(new stringredisserializer());
template.setvalueserializer(new stringredisserializer());
return template;
}
}
9.2 rag核心配置类
package com.example.demo.config;
import dev.langchain4j.community.store.embedding.redis.redisembeddingstore;
import dev.langchain4j.data.document.document;
import dev.langchain4j.data.document.loader.filesystemdocumentloader;
import dev.langchain4j.data.document.parser.textdocumentparser;
import dev.langchain4j.data.document.splitter.documentsplitters;
import dev.langchain4j.data.segment.textsegment;
import dev.langchain4j.model.embedding.embeddingmodel;
import dev.langchain4j.rag.content.retriever.contentretriever;
import dev.langchain4j.rag.content.retriever.embeddingstorecontentretriever;
import dev.langchain4j.store.embedding.embeddingstore;
import dev.langchain4j.store.embedding.embeddingstoreingestor;
import jakarta.annotation.postconstruct;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.connection.lettuce.lettuceconnectionfactory;
import java.nio.file.path;
import java.nio.file.paths;
import java.util.list;
@slf4j
@configuration
@requiredargsconstructor
public class ragconfig {
private final embeddingmodel embeddingmodel;
private final redisconnectionfactory redisconnectionfactory;
/**
* 配置 redis 作为向量存储
*
* redis stack 必须安装 redisearch 模块才能支持向量搜索
* docker 部署命令:
* docker run -d --name redis-vector -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
*/
@bean
public embeddingstore<textsegment> embeddingstore() {
// 从 redisconnectionfactory 中获取连接信息
lettuceconnectionfactory factory = (lettuceconnectionfactory) redisconnectionfactory;
string host = factory.gethostname();
int port = factory.getport();
string password = factory.getpassword();
int database = factory.getdatabase();
log.info("初始化 redisembeddingstore:host={}, port={}, database={}", host, port, database);
return redisembeddingstore.builder()
.host(host)
.port(port)
.password(password != null ? password : "")
.database(database)
// 索引名称,用于在 redis 中标识向量索引
.indexname("knowledge_vectors")
// 向量维度,必须与 embeddingmodel 的输出维度一致
// 通义千问 text-embedding-v4 的维度是 1536
.dimension(1536)
// 距离度量类型:cosine(余弦相似度)、euclidean(欧氏距离)、ip(内积)
.distancemetric(redis.embedding.distancemetric.cosine)
.build();
}
/**
* 配置内容检索器
*/
@bean
public contentretriever contentretriever(embeddingstore<textsegment> embeddingstore) {
return embeddingstorecontentretriever.builder()
.embeddingstore(embeddingstore)
.embeddingmodel(embeddingmodel)
.maxresults(3) // 最多检索 3 条相关片段
.minscore(0.7) // 最低相似度阈值
.build();
}
/**
* 启动时加载知识文档到 redis 向量库
*/
@postconstruct
public void loadknowledgedocuments(embeddingstore<textsegment> embeddingstore) {
try {
// 从 resources/knowledge 目录加载文档
path path = paths.get("src/main/resources/knowledge");
list<document> documents = filesystemdocumentloader.loaddocuments(
path,
new textdocumentparser()
);
if (documents.isempty()) {
log.warn("未找到知识文档,跳过加载");
return;
}
// 分割文档并存入 redis 向量库
embeddingstoreingestor ingestor = embeddingstoreingestor.builder()
.documentsplitter(documentsplitters.recursive(500, 0))
.embeddingmodel(embeddingmodel)
.embeddingstore(embeddingstore)
.build();
ingestor.ingest(documents);
log.info("知识文档加载完成,共 {} 个文档已存入 redis", documents.size());
} catch (exception e) {
log.error("加载知识文档到 redis 失败", e);
}
}
}
十、工具类定义(tool calling)
package com.example.demo.tool;
import dev.langchain4j.agent.tool.tool;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.component;
import java.time.localdatetime;
import java.time.format.datetimeformatter;
/**
* 天气查询工具
* 演示tool calling的基本用法
*/
@slf4j
@component
public class weathertool {
@tool("查询指定城市的当前天气信息")
public string getweather(string city) {
log.info("调用天气工具:city={}", city);
// 模拟天气数据(实际可调用第三方api)
string[] weathers = {"晴", "多云", "小雨", "阴天"};
string weather = weathers[(int) (math.random() * weathers.length)];
int temperature = 15 + (int) (math.random() * 20);
return string.format("【%s】当前天气:%s,温度:%d℃",
city, weather, temperature);
}
}
/**
* 计算器工具
*/
@slf4j
@component
public class calculatortool {
@tool("计算两个数字的和")
public double sum(double a, double b) {
log.info("调用计算工具:{} + {}", a, b);
return a + b;
}
@tool("计算两个数字的差")
public double subtract(double a, double b) {
log.info("调用计算工具:{} - {}", a, b);
return a - b;
}
@tool("计算两个数字的乘积")
public double multiply(double a, double b) {
log.info("调用计算工具:{} × {}", a, b);
return a * b;
}
}
/**
* 时间工具
*/
@slf4j
@component
public class datetimetool {
@tool("获取当前日期和时间")
public string getcurrentdatetime() {
log.info("调用时间工具");
return localdatetime.now().format(
datetimeformatter.ofpattern("yyyy-mm-dd hh:mm:ss")
);
}
}
十一、controller接口层
package com.example.demo.controller;
import com.example.demo.entity.conversation;
import com.example.demo.service.conversationservice;
import com.example.demo.service.ai.*;
import io.swagger.v3.oas.annotations.operation;
import io.swagger.v3.oas.annotations.parameter;
import io.swagger.v3.oas.annotations.tags.tag;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.hashmap;
import java.util.list;
import java.util.map;
@slf4j
@restcontroller
@requestmapping("/api/chat")
@requiredargsconstructor
@tag(name = "ai对话接口", description = "langchain4j集成spring boot演示")
public class chatcontroller {
private final simpleaiservice simpleaiservice;
private final promptaiservice promptaiservice;
private final memoryaiservice memoryaiservice;
private final advancedaiservice advancedaiservice;
private final conversationservice conversationservice;
// ==================== 1. 普通ai对话接口 ====================
@getmapping("/simple")
@operation(summary = "普通对话", description = "无记忆、无提示词,最简单的ai对话")
public map<string, string> simplechat(
@requestparam @parameter(description = "用户输入") string prompt) {
log.info("普通对话请求:prompt={}", prompt);
string result = simpleaiservice.chat(prompt);
return map.of("response", result);
}
// ==================== 2. 带提示词的对话接口 ====================
@getmapping("/prompt")
@operation(summary = "带提示词对话", description = "使用@systemmessage设定ai角色")
public map<string, string> promptchat(
@requestparam @parameter(description = "用户输入") string prompt) {
log.info("带提示词对话请求:prompt={}", prompt);
string result = promptaiservice.chat(prompt);
return map.of("response", result);
}
@getmapping("/prompt/template")
@operation(summary = "带模板变量的提示词对话")
public map<string, string> prompttemplatechat(
@requestparam @parameter(description = "角色") string role,
@requestparam @parameter(description = "姓名") string name,
@requestparam @parameter(description = "问题") string question) {
log.info("模板对话请求:role={}, name={}, question={}", role, name, question);
string result = promptaiservice.chatwithtemplate(role, name, question);
return map.of("response", result);
}
// ==================== 3. 保留对话轮次的接口(带记忆) ====================
@postmapping("/memory")
@operation(summary = "带记忆对话", description = "同一conversationid会记住对话历史")
public map<string, string> memorychat(
@requestparam @parameter(description = "会话id") string conversationid,
@requestparam @parameter(description = "用户输入") string prompt) {
log.info("带记忆对话请求:conversationid={}, prompt={}", conversationid, prompt);
string result = memoryaiservice.chat(conversationid, prompt);
return map.of("response", result);
}
// ==================== 4. 保留会话的接口(会话管理) ====================
@postmapping("/conversation/create")
@operation(summary = "创建新会话")
public map<string, object> createconversation(
@requestparam @parameter(description = "用户id") string userid,
@requestparam(required = false) @parameter(description = "会话标题") string title) {
log.info("创建会话请求:userid={}, title={}", userid, title);
conversation conversation = conversationservice.createconversation(userid, title);
map<string, object> result = new hashmap<>();
result.put("conversationid", conversation.getconversationid());
result.put("title", conversation.gettitle());
result.put("createdat", conversation.getcreatedat());
return result;
}
@getmapping("/conversation/list")
@operation(summary = "获取用户会话列表")
public list<conversation> listconversations(
@requestparam @parameter(description = "用户id") string userid) {
log.info("获取会话列表:userid={}", userid);
return conversationservice.listbyuserid(userid);
}
@postmapping("/conversation/close")
@operation(summary = "关闭会话")
public map<string, boolean> closeconversation(
@requestparam @parameter(description = "会话id") string conversationid,
@requestparam @parameter(description = "用户id") string userid) {
log.info("关闭会话请求:conversationid={}, userid={}", conversationid, userid);
boolean result = conversationservice.closeconversation(conversationid, userid);
return map.of("success", result);
}
@postmapping("/conversation/chat")
@operation(summary = "在会话上下文中对话", description = "自动关联会话,保留完整对话历史")
public map<string, string> conversationchat(
@requestparam @parameter(description = "会话id") string conversationid,
@requestparam @parameter(description = "用户输入") string prompt) {
log.info("会话对话请求:conversationid={}, prompt={}", conversationid, prompt);
// 使用memoryaiservice,conversationid作为@memoryid
string result = memoryaiservice.chat(conversationid, prompt);
return map.of("response", result);
}
// ==================== 5. 综合接口:rag + tool calling + 会话管理 ====================
@postmapping("/advanced")
@operation(summary = "综合ai对话", description = "rag检索增强 + 工具调用 + 会话记忆持久化")
public map<string, string> advancedchat(
@requestparam @parameter(description = "会话id") string conversationid,
@requestparam @parameter(description = "用户输入") string prompt) {
log.info("综合对话请求:conversationid={}, prompt={}", conversationid, prompt);
long starttime = system.currenttimemillis();
string result = advancedaiservice.chat(conversationid, prompt);
long costtime = system.currenttimemillis() - starttime;
log.info("综合对话完成,耗时:{}ms", costtime);
map<string, string> response = new hashmap<>();
response.put("response", result);
response.put("costtime", costtime + "ms");
return response;
}
}
十二、spring boot启动类
package com.example.demo;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
@springbootapplication
public class langchain4jdemoapplication {
public static void main(string[] args) {
springapplication.run(langchain4jdemoapplication.class, args);
system.out.println("╔══════════════════════════════════════════════════════════╗");
system.out.println("║ 🚀 langchain4j + spring boot 集成成功! ║");
system.out.println("║ 📌 访问 http://localhost:8080/api/chat/simple 试试 ║");
system.out.println("║ 🧠 向量存储:redis stack (redisearch) ║");
system.out.println("╚══════════════════════════════════════════════════════════╝");
}
}
十三、接口测试示例
| 接口 | 方法 | 说明 | 示例 |
|---|---|---|---|
/api/chat/simple?prompt=你好 | get | 普通对话 | 返回ai基础回答 |
/api/chat/prompt?prompt=什么是微服务 | get | 带提示词 | ai以"java技术专家"身份回答 |
/api/chat/memory?conversationid=xxx&prompt=我叫张 | post | 带记忆 | ai记住你是谁 |
/api/chat/conversation/create?userid=user001&title=技术咨询 | post | 创建会话 | 返回conversationid |
/api/chat/conversation/chat?conversationid=xxx&prompt=继续刚才的话题 | post | 会话对话 | 基于历史上下文回答 |
/api/chat/advanced?conversationid=xxx&prompt=帮我查下今天的天气 | post | 综合接口 | rag+工具调用+记忆 |
十四、生产避坑指南(redis 特别版)
1. 会话记忆 vs 历史记录
langchain4j提供的chatmemory是服务于大模型的"短期记忆",用于拼接上下文。而"历史记录"是面向用户展示的完整对话流水,需要你手动维护到message表中。本文的方案中,mysqlchatmemorystore的updatemessages是全量替换,生产环境建议改成增量追加。
2. redis 向量维度必须匹配
redisembeddingstore.builder().dimension(1536) 中的维度必须与你使用的 embeddingmodel 输出维度一致。通义千问 text-embedding-v4 是 1536 维,如果用其他模型(如 openai text-embedding-ada-002 是 1536 维,text-embedding-3-small 是 1536 维),请相应调整。
3. redis stack 部署检查
redis 原生不支持向量搜索,必须安装 redisearch 模块。检查方法:连接 redis 后执行 module list,看是否包含 search。推荐直接使用 redis/redis-stack 镜像,一步到位。
4. 工具描述要清晰
@tool注解的value描述一定要写清楚,ai能否正确调用工具全看这个描述。
5. 会话隔离
使用@memoryid注解标识会话id,不同会话的记忆互不干扰。多用户场景下,建议用userid + conversationid组合作为记忆id。
6. 向量检索的实时性
redis 向量检索基于内存,速度极快,适合对延迟敏感的实时对话场景。但如果知识库非常大(超过百万级向量),需要考虑内存容量规划。
7. 版本兼容性(重要)
langchain4j从0.36.0起要求jdk 17。langchain4j-community-redis-spring-boot-starter 是社区模块,建议使用与 bom 版本匹配的版本(本文使用 1.0.1-beta6)。如果遇到 jedis/lettuce 版本冲突,可以在 pom.xml 中显式排除。
好了,整套方案就这些了。从最简单的 hello world 到综合的 rag+tool calling+会话管理,向量存储也换成了更轻量快速的 redis,该有的都有了。代码可以直接复制到项目里跑,有问题随时交流!
以上就是springboot集成langchain4j的保姆级教程分享的详细内容,更多关于springboot集成langchain4j的资料请关注代码网其它相关文章!
发表评论