大家好!今天咱们来聊聊如何在普通spring项目 中集成langchain4j,一步步构建一个功能完整的ai对话应用。
为啥要强调“普通spring项目”?因为网上绝大多数教程都是spring boot的,但现实中很多老项目还是基于xml配置的spring mvc,或者出于各种原因没法用spring boot。咱们今天就专门给这部分同学写一份能直接用的教程。
废话不多说,直接开整!
一、项目概览
咱们要实现的5个功能,从简单到复杂层层递进:
| 序号 | 功能 | 说明 |
|---|---|---|
| 1 | 普通ai对话接口 | 最简单的“一问一答” |
| 2 | 带提示词的对话接口 | 通过systemmessage设定ai角色 |
| 3 | 保留对话轮次的接口 | 单会话内记住上下文 |
| 4 | 保留会话的接口 | 多会话隔离,每个会话独立记忆 |
| 5 | rag + tool calling + 保留会话及消息 | 综合实战,企业级能力 |
会话和会话消息会持久化到mysql的两张表中,重启服务也不丢数据。
技术栈:
- spring 5.x(普通spring,非boot)
- langchain4j 0.35.0
- mysql 8.0
- mybatis 3.5.x
- jdk 17+(langchain4j 0.36+要求jdk 17,咱们用0.35.0兼容性更好)
二、项目结构
先看一眼整体结构,心里有个数:
langchain4j-spring-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── config/
│ │ │ │ ├── springconfig.java # spring配置(java config)
│ │ │ │ └── langchain4jconfig.java # langchain4j相关bean配置
│ │ │ ├── controller/
│ │ │ │ └── chatcontroller.java # 对话接口控制器
│ │ │ ├── service/
│ │ │ │ ├── chatservice.java # 对话服务接口
│ │ │ │ └── chatserviceimpl.java # 对话服务实现
│ │ │ ├── memory/
│ │ │ │ └── mysqlchatmemorystore.java # mysql持久化记忆存储
│ │ │ ├── rag/
│ │ │ │ ├── documentloader.java # 文档加载器
│ │ │ │ └── ragservice.java # rag服务
│ │ │ ├── tool/
│ │ │ │ └── mathtool.java # 工具类(数学计算)
│ │ │ ├── entity/
│ │ │ │ ├── chatsession.java # 会话实体
│ │ │ │ └── chatmessage.java # 消息实体
│ │ │ └── mapper/
│ │ │ ├── chatsessionmapper.java
│ │ │ └── chatmessagemapper.java
│ │ ├── resources/
│ │ │ ├── spring/
│ │ │ │ └── applicationcontext.xml # spring xml配置(可选)
│ │ │ ├── mapper/
│ │ │ │ ├── chatsessionmapper.xml
│ │ │ │ └── chatmessagemapper.xml
│ │ │ └── config.properties # 配置文件
│ │ └── webapp/
│ │ └── web-inf/
│ │ └── web.xml
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── test/
│ └── chatservicetest.java
三、依赖配置(pom.xml)
普通spring项目没有spring boot的starter自动配置,所以咱们需要手动引入所有依赖:
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelversion>4.0.0</modelversion>
<groupid>com.example</groupid>
<artifactid>langchain4j-spring-demo</artifactid>
<version>1.0.0</version>
<packaging>war</packaging>
<properties>
<project.build.sourceencoding>utf-8</project.build.sourceencoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring.version>5.3.27</spring.version>
<langchain4j.version>0.35.0</langchain4j.version>
</properties>
<dependencies>
<!-- ============ spring 核心依赖 ============ -->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-context</artifactid>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-webmvc</artifactid>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-jdbc</artifactid>
<version>${spring.version}</version>
</dependency>
<!-- ============ langchain4j 核心 ============ -->
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j-core</artifactid>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j</artifactid>
<version>${langchain4j.version}</version>
</dependency>
<!-- ============ openai 集成(用官方sdk方式) ============ -->
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j-open-ai</artifactid>
<version>${langchain4j.version}</version>
</dependency>
<!-- ============ 嵌入模型(rag需要) ============ -->
<dependency>
<groupid>dev.langchain4j</groupid>
<artifactid>langchain4j-embeddings</artifactid>
<version>${langchain4j.version}</version>
</dependency>
<!-- ============ mybatis + mysql ============ -->
<dependency>
<groupid>org.mybatis</groupid>
<artifactid>mybatis</artifactid>
<version>3.5.13</version>
</dependency>
<dependency>
<groupid>org.mybatis</groupid>
<artifactid>mybatis-spring</artifactid>
<version>2.1.1</version>
</dependency>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
<version>8.0.33</version>
</dependency>
<dependency>
<groupid>com.alibaba</groupid>
<artifactid>druid</artifactid>
<version>1.2.18</version>
</dependency>
<!-- ============ json 序列化 ============ -->
<dependency>
<groupid>com.fasterxml.jackson.core</groupid>
<artifactid>jackson-databind</artifactid>
<version>2.15.2</version>
</dependency>
<!-- ============ 日志 ============ -->
<dependency>
<groupid>org.slf4j</groupid>
<artifactid>slf4j-log4j12</artifactid>
<version>1.7.36</version>
</dependency>
<!-- ============ servlet api ============ -->
<dependency>
<groupid>javax.servlet</groupid>
<artifactid>javax.servlet-api</artifactid>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- ============ 工具类 ============ -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!-- ============ 测试 ============ -->
<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-test</artifactid>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalname>langchain4j-demo</finalname>
<plugins>
<plugin>
<groupid>org.apache.maven.plugins</groupid>
<artifactid>maven-war-plugin</artifactid>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>小贴士:langchain4j 0.35.0 支持 jdk 8+,如果你们项目还在用 jdk 8,用这个版本没问题。但新项目建议直接上 jdk 17+,后面升级框架也方便。
四、配置文件
4.1 config.properties
在 src/main/resources/config.properties 中配置模型参数和数据库参数:
# ============ openai 配置 ============ openai.api.key=sk-your-api-key-here openai.base.url=https://api.openai.com/v1 openai.model.name=gpt-4o-mini openai.temperature=0.7 openai.max.tokens=2048 # ============ 数据库配置 ============ db.driver=com.mysql.cj.jdbc.driver db.url=jdbc:mysql://localhost:3306/langchain4j_demo?usessl=false&servertimezone=asia/shanghai&characterencoding=utf8 db.username=root db.password=123456 db.max.active=20 db.initial.size=5 # ============ 对话记忆配置 ============ chat.memory.max.messages=20
4.2 数据库表结构
执行以下sql创建两张表:
-- 创建数据库
create database if not exists langchain4j_demo
default character set utf8mb4
default collate utf8mb4_unicode_ci;
use langchain4j_demo;
-- ============ 会话表 ============
create table if not exists chat_session (
id varchar(64) primary key comment '会话id',
session_name varchar(255) default null comment '会话名称(可选)',
user_id varchar(64) default null comment '用户id(用于多用户隔离)',
created_at datetime default current_timestamp comment '创建时间',
updated_at datetime default current_timestamp on update current_timestamp comment '更新时间',
is_active tinyint(1) default 1 comment '是否活跃',
index idx_user_id (user_id),
index idx_created_at (created_at)
) engine=innodb default charset=utf8mb4 collate=utf8mb4_unicode_ci comment='ai对话会话表';
-- ============ 消息表 ============
create table if not exists chat_message (
id bigint auto_increment primary key comment '消息id',
session_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 null comment '工具名称(如果是工具调用)',
tool_execution_id varchar(100) default null comment '工具执行id',
created_at datetime default current_timestamp comment '创建时间',
index idx_session_id (session_id),
index idx_created_at (created_at),
foreign key (session_id) references chat_session(id) on delete cascade
) engine=innodb default charset=utf8mb4 collate=utf8mb4_unicode_ci comment='对话消息表';设计说明:langchain4j 的 chatmemory 存储的是“记忆”(可能经过裁剪、摘要),而咱们这张 chat_message 表存的是完整的“历史记录”。两件事分开做:chat_message 存全量历史供用户查询,chatmemory 存裁剪后的上下文供模型使用。这就是“历史”和“记忆”的区别。
五、spring配置
普通spring项目我们采用 java config + xml混合 的方式,既保留xml的灵活性,又享受java config的类型安全。
5.1 springconfig.java - 核心spring配置
package com.example.config;
import com.alibaba.druid.pool.druiddatasource;
import org.apache.ibatis.session.sqlsessionfactory;
import org.mybatis.spring.sqlsessionfactorybean;
import org.mybatis.spring.mapper.mapperscannerconfigurer;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.componentscan;
import org.springframework.context.annotation.configuration;
import org.springframework.context.annotation.propertysource;
import org.springframework.core.env.environment;
import org.springframework.core.io.support.pathmatchingresourcepatternresolver;
import org.springframework.jdbc.datasource.datasourcetransactionmanager;
import org.springframework.transaction.platformtransactionmanager;
import org.springframework.transaction.annotation.enabletransactionmanagement;
import org.springframework.web.servlet.config.annotation.enablewebmvc;
import org.springframework.web.servlet.view.internalresourceviewresolver;
import javax.sql.datasource;
import java.util.objects;
import java.util.properties;
/**
* spring核心配置类
* 相当于applicationcontext.xml的java版本
*/
@configuration
@enablewebmvc
@enabletransactionmanagement
@componentscan(basepackages = {"com.example"})
@propertysource(value = {"classpath:config.properties"}, ignoreresourcenotfound = false)
public class springconfig {
/**
* 配置druid数据源
*/
@bean
public datasource datasource(environment env) {
druiddatasource datasource = new druiddatasource();
datasource.setdriverclassname(env.getproperty("db.driver"));
datasource.seturl(env.getproperty("db.url"));
datasource.setusername(env.getproperty("db.username"));
datasource.setpassword(env.getproperty("db.password"));
datasource.setmaxactive(integer.parseint(objects.requirenonnull(env.getproperty("db.max.active"))));
datasource.setinitialsize(integer.parseint(objects.requirenonnull(env.getproperty("db.initial.size"))));
// druid监控配置(生产环境建议开启)
properties props = new properties();
props.setproperty("druid.stat.mergesql", "true");
props.setproperty("druid.stat.slowsqlmillis", "5000");
datasource.setconnectproperties(props);
return datasource;
}
/**
* 配置mybatis sqlsessionfactory
*/
@bean
public sqlsessionfactory sqlsessionfactory(datasource datasource) throws exception {
sqlsessionfactorybean factorybean = new sqlsessionfactorybean();
factorybean.setdatasource(datasource);
factorybean.settypealiasespackage("com.example.entity");
factorybean.setmapperlocations(
new pathmatchingresourcepatternresolver().getresources("classpath:mapper/*.xml")
);
// 配置mybatis设置
org.apache.ibatis.session.configuration configuration = new org.apache.ibatis.session.configuration();
configuration.setmapunderscoretocamelcase(true); // 下划线转驼峰
configuration.setlogimpl(org.apache.ibatis.logging.log4j.log4jimpl.class);
factorybean.setconfiguration(configuration);
return factorybean.getobject();
}
/**
* mapper扫描
*/
@bean
public mapperscannerconfigurer mapperscannerconfigurer() {
mapperscannerconfigurer scanner = new mapperscannerconfigurer();
scanner.setbasepackage("com.example.mapper");
scanner.setsqlsessionfactorybeanname("sqlsessionfactory");
return scanner;
}
/**
* 事务管理器
*/
@bean
public platformtransactionmanager transactionmanager(datasource datasource) {
return new datasourcetransactionmanager(datasource);
}
/**
* 视图解析器(如果你需要返回jsp页面)
*/
@bean
public internalresourceviewresolver viewresolver() {
internalresourceviewresolver resolver = new internalresourceviewresolver();
resolver.setprefix("/web-inf/views/");
resolver.setsuffix(".jsp");
return resolver;
}
}
5.2 langchain4jconfig.java - langchain4j专属配置
package com.example.config;
import com.example.memory.mysqlchatmemorystore;
import dev.langchain4j.memory.chat.messagewindowchatmemory;
import dev.langchain4j.model.chat.chatlanguagemodel;
import dev.langchain4j.model.openai.openaichatmodel;
import dev.langchain4j.service.aiservices;
import dev.langchain4j.store.memory.chat.chatmemorystore;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.core.env.environment;
import java.time.duration;
/**
* langchain4j 配置类
* 手动创建所有langchain4j相关的bean
*/
@configuration
public class langchain4jconfig {
/**
* 创建chatlanguagemodel - 与大模型对话的底层api
*
* chatlanguagemodel是langchain4j中与llm交互的低级api,
* 提供最大的能力和灵活性
*/
@bean
public chatlanguagemodel chatlanguagemodel(environment env) {
return openaichatmodel.builder()
.apikey(env.getproperty("openai.api.key"))
.baseurl(env.getproperty("openai.base.url"))
.modelname(env.getproperty("openai.model.name"))
.temperature(double.parsedouble(env.getproperty("openai.temperature", "0.7")))
.maxtokens(integer.parseint(env.getproperty("openai.max.tokens", "2048")))
.timeout(duration.ofseconds(60))
.logrequests(true) // 开发阶段开启,方便调试
.logresponses(true)
.build();
}
/**
* 创建chatmemorystore - mysql持久化存储
*
* 默认情况下chatmemory将消息存储在内存中。
* 如果需要持久化,可以实现自定义chatmemorystore
*/
@bean
public chatmemorystore chatmemorystore() {
return new mysqlchatmemorystore();
}
/**
* 创建默认的chatmemory(用于单会话场景)
* 使用滑动窗口,保留最近n条消息
*/
@bean
public messagewindowchatmemory defaultchatmemory(chatmemorystore chatmemorystore) {
return messagewindowchatmemory.builder()
.id("default-session") // 固定id,所有请求共享同一个记忆
.maxmessages(20) // 保留最近20条消息
.chatmemorystore(chatmemorystore)
.build();
}
}
5.3 web.xml(如果是servlet容器部署)
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xsi:schemalocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>langchain4j spring demo</display-name>
<!-- 配置spring上下文 -->
<context-param>
<param-name>contextclass</param-name>
<param-value>org.springframework.web.context.support.annotationconfigwebapplicationcontext</param-value>
</context-param>
<context-param>
<param-name>contextconfiglocation</param-name>
<param-value>com.example.config.springconfig</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.contextloaderlistener</listener-class>
</listener>
<!-- 配置dispatcherservlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.dispatcherservlet</servlet-class>
<init-param>
<param-name>contextclass</param-name>
<param-value>org.springframework.web.context.support.annotationconfigwebapplicationcontext</param-value>
</init-param>
<init-param>
<param-name>contextconfiglocation</param-name>
<param-value>com.example.config.springconfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!-- 字符编码过滤器 -->
<filter>
<filter-name>encodingfilter</filter-name>
<filter-class>org.springframework.web.filter.characterencodingfilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceencoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>六、实体类与mapper
6.1 chatsession.java
package com.example.entity;
import lombok.data;
import java.time.localdatetime;
/**
* 会话实体
*/
@data
public class chatsession {
private string id; // 会话id(uuid)
private string sessionname; // 会话名称
private string userid; // 用户id
private localdatetime createdat;
private localdatetime updatedat;
private integer isactive; // 1-活跃 0-已删除
}
6.2 chatmessage.java
package com.example.entity;
import lombok.data;
import java.time.localdatetime;
/**
* 消息实体
*/
@data
public class chatmessage {
private long id;
private string sessionid;
private string role; // user / assistant / system / tool
private string content;
private string toolname;
private string toolexecutionid;
private localdatetime createdat;
}
6.3 chatsessionmapper.java
package com.example.mapper;
import com.example.entity.chatsession;
import org.apache.ibatis.annotations.param;
import java.util.list;
public interface chatsessionmapper {
/** 插入会话 */
int insert(chatsession session);
/** 根据id查询 */
chatsession selectbyid(@param("id") string id);
/** 根据用户id查询所有活跃会话 */
list<chatsession> selectbyuserid(@param("userid") string userid);
/** 更新会话 */
int update(chatsession session);
/** 逻辑删除会话 */
int deletebyid(@param("id") string id);
}
6.4 chatmessagemapper.java
package com.example.mapper;
import com.example.entity.chatmessage;
import org.apache.ibatis.annotations.param;
import java.util.list;
public interface chatmessagemapper {
/** 插入消息 */
int insert(chatmessage message);
/** 批量插入消息 */
int batchinsert(@param("list") list<chatmessage> messages);
/** 根据会话id查询所有消息(按时间升序) */
list<chatmessage> selectbysessionid(@param("sessionid") string sessionid);
/** 根据会话id查询最近n条消息 */
list<chatmessage> selectrecentbysessionid(@param("sessionid") string sessionid,
@param("limit") int limit);
/** 删除会话的所有消息 */
int deletebysessionid(@param("sessionid") string sessionid);
}
6.5 mapper xml
chatsessionmapper.xml:
<?xml version="1.0" encoding="utf-8" ?>
<!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.chatsessionmapper">
<resultmap id="baseresultmap" type="com.example.entity.chatsession">
<id column="id" property="id"/>
<result column="session_name" property="sessionname"/>
<result column="user_id" property="userid"/>
<result column="created_at" property="createdat"/>
<result column="updated_at" property="updatedat"/>
<result column="is_active" property="isactive"/>
</resultmap>
<insert id="insert" parametertype="com.example.entity.chatsession">
insert into chat_session (id, session_name, user_id, is_active)
values (#{id}, #{sessionname}, #{userid}, #{isactive})
</insert>
<select id="selectbyid" resultmap="baseresultmap">
select * from chat_session
where id = #{id} and is_active = 1
</select>
<select id="selectbyuserid" resultmap="baseresultmap">
select * from chat_session
where user_id = #{userid} and is_active = 1
order by updated_at desc
</select>
<update id="update" parametertype="com.example.entity.chatsession">
update chat_session
<set>
<if test="sessionname != null">session_name = #{sessionname},</if>
<if test="isactive != null">is_active = #{isactive},</if>
</set>
where id = #{id}
</update>
<update id="deletebyid">
update chat_session set is_active = 0
where id = #{id}
</update>
</mapper>chatmessagemapper.xml:
<?xml version="1.0" encoding="utf-8" ?>
<!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.chatmessagemapper">
<resultmap id="baseresultmap" type="com.example.entity.chatmessage">
<id column="id" property="id"/>
<result column="session_id" property="sessionid"/>
<result column="role" property="role"/>
<result column="content" property="content"/>
<result column="tool_name" property="toolname"/>
<result column="tool_execution_id" property="toolexecutionid"/>
<result column="created_at" property="createdat"/>
</resultmap>
<insert id="insert" parametertype="com.example.entity.chatmessage" usegeneratedkeys="true" keyproperty="id">
insert into chat_message (session_id, role, content, tool_name, tool_execution_id)
values (#{sessionid}, #{role}, #{content}, #{toolname}, #{toolexecutionid})
</insert>
<insert id="batchinsert" parametertype="list">
insert into chat_message (session_id, role, content, tool_name, tool_execution_id)
values
<foreach collection="list" item="item" separator=",">
(#{item.sessionid}, #{item.role}, #{item.content}, #{item.toolname}, #{item.toolexecutionid})
</foreach>
</insert>
<select id="selectbysessionid" resultmap="baseresultmap">
select * from chat_message
where session_id = #{sessionid}
order by created_at asc
</select>
<select id="selectrecentbysessionid" resultmap="baseresultmap">
select * from chat_message
where session_id = #{sessionid}
order by created_at desc
limit #{limit}
</select>
<delete id="deletebysessionid">
delete from chat_message where session_id = #{sessionid}
</delete>
</mapper>七、mysql持久化记忆存储(核心)
这是整个教程最关键的部分——实现 chatmemorystore 接口,把对话记忆持久化到mysql。
langchain4j 的 chatmemorystore 接口有三个方法:
getmessages(object memoryid)- 根据记忆id获取消息列表updatemessages(object memoryid, list<chatmessage> messages)- 更新消息列表deletemessages(object memoryid)- 删除消息
package com.example.memory;
import com.example.entity.chatmessage;
import com.example.mapper.chatmessagemapper;
import com.example.mapper.chatsessionmapper;
import com.fasterxml.jackson.core.jsonprocessingexception;
import com.fasterxml.jackson.databind.objectmapper;
import com.fasterxml.jackson.datatype.jsr310.javatimemodule;
import dev.langchain4j.data.message.aimessage;
import dev.langchain4j.data.message.chatmessagedeserializer;
import dev.langchain4j.data.message.chatmessageserializer;
import dev.langchain4j.data.message.systemmessage;
import dev.langchain4j.data.message.usermessage;
import dev.langchain4j.store.memory.chat.chatmemorystore;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.component;
import java.time.localdatetime;
import java.util.arraylist;
import java.util.list;
import java.util.uuid;
/**
* mysql持久化的chatmemorystore实现
*
* 将langchain4j的chatmessage序列化为json存储在mysql中
*
* 注意:这里存储的是"记忆"(可能经过裁剪),而chat_message表存的是完整"历史"
*/
@slf4j
@component
public class mysqlchatmemorystore implements chatmemorystore {
@autowired
private chatmessagemapper chatmessagemapper;
@autowired
private chatsessionmapper chatsessionmapper;
private final objectmapper objectmapper;
public mysqlchatmemorystore() {
this.objectmapper = new objectmapper();
this.objectmapper.registermodule(new javatimemodule());
}
/**
* 根据memoryid获取消息列表
* memoryid就是sessionid
*/
@override
public list<dev.langchain4j.data.message.chatmessage> getmessages(object memoryid) {
string sessionid = memoryid.tostring();
log.debug("从mysql加载记忆: sessionid={}", sessionid);
// 从数据库查询该会话的所有消息
list<chatmessage> dbmessages = chatmessagemapper.selectbysessionid(sessionid);
if (dbmessages == null || dbmessages.isempty()) {
return new arraylist<>();
}
// 将数据库记录转换为langchain4j的chatmessage
list<dev.langchain4j.data.message.chatmessage> messages = new arraylist<>();
for (chatmessage dbmsg : dbmessages) {
try {
// 用langchain4j提供的序列化工具反序列化
// 注意:我们存储的是完整的消息json,可以直接反序列化
string json = dbmsg.getcontent();
// 如果是简单文本,需要包装成对应的消息类型
dev.langchain4j.data.message.chatmessage msg = deserializemessage(dbmsg);
if (msg != null) {
messages.add(msg);
}
} catch (exception e) {
log.error("反序列化消息失败: id={}", dbmsg.getid(), e);
}
}
log.debug("加载了 {} 条记忆", messages.size());
return messages;
}
/**
* 更新消息列表(全量替换)
*/
@override
public void updatemessages(object memoryid, list<dev.langchain4j.data.message.chatmessage> messages) {
string sessionid = memoryid.tostring();
log.debug("更新记忆: sessionid={}, 消息数={}", sessionid, messages.size());
// 先删除旧消息
chatmessagemapper.deletebysessionid(sessionid);
if (messages == null || messages.isempty()) {
return;
}
// 批量插入新消息
list<chatmessage> dbmessages = new arraylist<>();
for (dev.langchain4j.data.message.chatmessage msg : messages) {
chatmessage dbmsg = new chatmessage();
dbmsg.setsessionid(sessionid);
dbmsg.setrole(getrolename(msg));
dbmsg.setcontent(serializemessage(msg));
dbmsg.setcreatedat(localdatetime.now());
dbmessages.add(dbmsg);
}
if (!dbmessages.isempty()) {
chatmessagemapper.batchinsert(dbmessages);
}
}
/**
* 删除消息
*/
@override
public void deletemessages(object memoryid) {
string sessionid = memoryid.tostring();
log.debug("删除记忆: sessionid={}", sessionid);
chatmessagemapper.deletebysessionid(sessionid);
}
// ============ 辅助方法 ============
/**
* 序列化chatmessage为json
* langchain4j提供了chatmessageserializer工具类
*/
private string serializemessage(dev.langchain4j.data.message.chatmessage message) {
try {
return chatmessageserializer.messagetojson(message);
} catch (exception e) {
log.error("序列化消息失败", e);
// 降级方案:只存文本
return "{\"text\":\"" + message.text() + "\"}";
}
}
/**
* 从数据库记录反序列化为chatmessage
*/
private dev.langchain4j.data.message.chatmessage deserializemessage(chatmessage dbmsg) {
try {
// 尝试用langchain4j的序列化工具反序列化
return chatmessagedeserializer.messagefromjson(dbmsg.getcontent());
} catch (exception e) {
log.warn("反序列化失败,尝试简单文本解析: {}", e.getmessage());
// 降级方案:根据角色构造简单消息
string role = dbmsg.getrole();
string content = dbmsg.getcontent();
if ("user".equals(role)) {
return usermessage.from(content);
} else if ("system".equals(role)) {
return systemmessage.from(content);
} else if ("assistant".equals(role)) {
return aimessage.from(content);
}
return null;
}
}
/**
* 获取消息角色名称
*/
private string getrolename(dev.langchain4j.data.message.chatmessage message) {
if (message instanceof usermessage) {
return "user";
} else if (message instanceof systemmessage) {
return "system";
} else if (message instanceof aimessage) {
return "assistant";
} else {
return "unknown";
}
}
/**
* 创建新会话
*/
public string createsession(string userid, string sessionname) {
string sessionid = uuid.randomuuid().tostring();
com.example.entity.chatsession session = new com.example.entity.chatsession();
session.setid(sessionid);
session.setuserid(userid);
session.setsessionname(sessionname != null ? sessionname : "新对话");
session.setisactive(1);
chatsessionmapper.insert(session);
return sessionid;
}
}
八、功能1:普通ai对话接口
最简单的“一问一答”,不保留任何上下文。
chatservice.java
package com.example.service;
/**
* 对话服务接口
*/
public interface chatservice {
/**
* 功能1:普通对话 - 单次问答,无上下文
*/
string simplechat(string usermessage);
/**
* 功能2:带提示词的对话
*/
string chatwithprompt(string systemprompt, string usermessage);
/**
* 功能3:保留对话轮次的对话(单会话)
*/
string chatwithmemory(string usermessage);
/**
* 功能4:多会话对话(每个会话独立记忆)
*/
string chatwithsession(string sessionid, string usermessage);
/**
* 功能5:综合 - rag + tool calling + 会话持久化
*/
string chatwithragandtools(string sessionid, string usermessage);
}
chatserviceimpl.java - 功能1实现
package com.example.service;
import dev.langchain4j.data.message.aimessage;
import dev.langchain4j.data.message.usermessage;
import dev.langchain4j.model.chat.chatlanguagemodel;
import dev.langchain4j.model.chat.request.chatrequest;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
/**
* 对话服务实现
*/
@slf4j
@service
public class chatserviceimpl implements chatservice {
@autowired
private chatlanguagemodel chatmodel;
// ================================================================
// 功能1:普通ai对话接口 - 最简单的"一问一答"
// ================================================================
@override
public string simplechat(string usermessage) {
log.info("【功能1】普通对话: {}", usermessage);
try {
// 直接用chatlanguagemodel的便捷方法
string response = chatmodel.chat(usermessage);
log.info("【功能1】响应: {}", response);
return response;
} catch (exception e) {
log.error("对话失败", e);
return "抱歉,ai服务暂时不可用:" + e.getmessage();
}
}
}
chatcontroller.java - 功能1接口
package com.example.controller;
import com.example.service.chatservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.web.bind.annotation.*;
import java.util.hashmap;
import java.util.map;
@restcontroller
@requestmapping("/chat")
public class chatcontroller {
@autowired
private chatservice chatservice;
// ================================================================
// 功能1:普通ai对话接口
// ================================================================
@postmapping("/simple")
public map<string, object> simplechat(@requestbody map<string, string> request) {
string message = request.get("message");
map<string, object> result = new hashmap<>();
if (message == null || message.trim().isempty()) {
result.put("code", 400);
result.put("message", "消息不能为空");
return result;
}
try {
string response = chatservice.simplechat(message);
result.put("code", 200);
result.put("data", response);
} catch (exception e) {
result.put("code", 500);
result.put("message", e.getmessage());
}
return result;
}
}
测试:
curl -x post http://localhost:8080/api/chat/simple \
-h "content-type: application/json" \
-d '{"message": "你好,请介绍一下自己"}'九、功能2:带提示词的对话接口
通过 systemmessage 设定ai的角色和行为。
chatserviceimpl.java - 功能2实现
// ================================================================
// 功能2:带提示词的对话接口
// ================================================================
@override
public string chatwithprompt(string systemprompt, string usermessage) {
log.info("【功能2】带提示词对话 - 系统提示: {}, 用户: {}", systemprompt, usermessage);
try {
// 创建systemmessage和usermessage
systemmessage systemmsg = systemmessage.from(systemprompt);
usermessage usermsg = usermessage.from(usermessage);
// 发送消息列表
chatresponse response = chatmodel.chat(systemmsg, usermsg);
string answer = response.aimessage().text();
log.info("【功能2】响应: {}", answer);
return answer;
} catch (exception e) {
log.error("带提示词对话失败", e);
return "抱歉,ai服务暂时不可用:" + e.getmessage();
}
}
chatcontroller.java - 功能2接口
// ================================================================
// 功能2:带提示词的对话接口
// ================================================================
@postmapping("/with-prompt")
public map<string, object> chatwithprompt(@requestbody map<string, string> request) {
string systemprompt = request.get("systemprompt");
string usermessage = request.get("message");
map<string, object> result = new hashmap<>();
if (usermessage == null || usermessage.trim().isempty()) {
result.put("code", 400);
result.put("message", "消息不能为空");
return result;
}
// 如果没传系统提示词,给个默认的
if (systemprompt == null || systemprompt.trim().isempty()) {
systemprompt = "你是一个友好的ai助手,请用简洁、清晰的语言回答问题。";
}
try {
string response = chatservice.chatwithprompt(systemprompt, usermessage);
result.put("code", 200);
result.put("data", response);
} catch (exception e) {
result.put("code", 500);
result.put("message", e.getmessage());
}
return result;
}
测试:
curl -x post http://localhost:8080/api/chat/with-prompt \
-h "content-type: application/json" \
-d '{"systemprompt": "你是一位资深java架构师,请用专业但不失通俗的方式回答问题", "message": "什么是微服务"}'十、功能3:保留对话轮次的接口
用 chatmemory 在单会话内记住上下文。
chatserviceimpl.java - 功能3实现
@autowired
private messagewindowchatmemory defaultchatmemory; // 从配置中注入
// ================================================================
// 功能3:保留对话轮次的对话(单会话)
// ================================================================
@override
public string chatwithmemory(string usermessage) {
log.info("【功能3】带记忆对话: {}", usermessage);
try {
// 将用户消息加入记忆
defaultchatmemory.add(usermessage.from(usermessage));
// 获取当前所有记忆中的消息
list<dev.langchain4j.data.message.chatmessage> history = defaultchatmemory.messages();
log.debug("当前记忆中有 {} 条消息", history.size());
// 发送完整的历史消息给模型
chatresponse response = chatmodel.chat(history);
string answer = response.aimessage().text();
// 将ai的回复也加入记忆
defaultchatmemory.add(aimessage.from(answer));
log.info("【功能3】响应: {}", answer);
return answer;
} catch (exception e) {
log.error("带记忆对话失败", e);
return "抱歉,ai服务暂时不可用:" + e.getmessage();
}
}
chatcontroller.java - 功能3接口
// ================================================================
// 功能3:保留对话轮次的接口
// ================================================================
@postmapping("/with-memory")
public map<string, object> chatwithmemory(@requestbody map<string, string> request) {
string message = request.get("message");
map<string, object> result = new hashmap<>();
if (message == null || message.trim().isempty()) {
result.put("code", 400);
result.put("message", "消息不能为空");
return result;
}
try {
string response = chatservice.chatwithmemory(message);
result.put("code", 200);
result.put("data", response);
} catch (exception e) {
result.put("code", 500);
result.put("message", e.getmessage());
}
return result;
}
测试(连续对话):
# 第一轮
curl -x post http://localhost:8080/api/chat/with-memory \
-h "content-type: application/json" \
-d '{"message": "我叫张三"}'
# 第二轮 - ai应该还记得你叫张三
curl -x post http://localhost:8080/api/chat/with-memory \
-h "content-type: application/json" \
-d '{"message": "我叫什么名字?"}'十一、功能4:保留会话的接口
多会话隔离,每个会话有独立的记忆。通过 @memoryid 注解实现用户/会话隔离。
chatserviceimpl.java - 功能4实现
@autowired
private chatmemorystore chatmemorystore;
// 会话记忆缓存(生产环境建议用更健壮的缓存)
private final map<string, messagewindowchatmemory> sessionmemorycache = new concurrenthashmap<>();
// ================================================================
// 功能4:多会话对话(每个会话独立记忆)
// ================================================================
@override
public string chatwithsession(string sessionid, string usermessage) {
log.info("【功能4】会话对话: sessionid={}, message={}", sessionid, usermessage);
try {
// 获取或创建该会话的记忆
messagewindowchatmemory memory = sessionmemorycache.computeifabsent(sessionid, id -> {
log.info("创建新会话记忆: {}", id);
return messagewindowchatmemory.builder()
.id(id)
.maxmessages(20) // 保留最近20条
.chatmemorystore(chatmemorystore) // mysql持久化
.build();
});
// 添加用户消息
memory.add(usermessage.from(usermessage));
// 获取历史并发送
list<dev.langchain4j.data.message.chatmessage> history = memory.messages();
chatresponse response = chatmodel.chat(history);
string answer = response.aimessage().text();
// 添加ai回复
memory.add(aimessage.from(answer));
log.info("【功能4】响应: {}", answer);
return answer;
} catch (exception e) {
log.error("会话对话失败", e);
return "抱歉,ai服务暂时不可用:" + e.getmessage();
}
}
/**
* 创建新会话
*/
public string createnewsession(string userid, string sessionname) {
if (chatmemorystore instanceof mysqlchatmemorystore) {
return ((mysqlchatmemorystore) chatmemorystore).createsession(userid, sessionname);
}
return uuid.randomuuid().tostring();
}
chatcontroller.java - 功能4接口
// ================================================================
// 功能4:保留会话的接口
// ================================================================
@postmapping("/session")
public map<string, object> chatwithsession(@requestbody map<string, string> request) {
string sessionid = request.get("sessionid");
string message = request.get("message");
map<string, object> result = new hashmap<>();
if (message == null || message.trim().isempty()) {
result.put("code", 400);
result.put("message", "消息不能为空");
return result;
}
// 如果没有传sessionid,自动创建
if (sessionid == null || sessionid.trim().isempty()) {
sessionid = chatservice.createnewsession("default-user", null);
result.put("sessionid", sessionid);
}
try {
string response = chatservice.chatwithsession(sessionid, message);
result.put("code", 200);
result.put("data", response);
result.put("sessionid", sessionid);
} catch (exception e) {
result.put("code", 500);
result.put("message", e.getmessage());
}
return result;
}
/**
* 获取会话历史
*/
@getmapping("/session/{sessionid}/history")
public map<string, object> getsessionhistory(@pathvariable string sessionid) {
map<string, object> result = new hashmap<>();
try {
list<com.example.entity.chatmessage> messages =
chatmessagemapper.selectbysessionid(sessionid);
result.put("code", 200);
result.put("data", messages);
} catch (exception e) {
result.put("code", 500);
result.put("message", e.getmessage());
}
return result;
}
测试(多会话隔离):
# 会话a
curl -x post http://localhost:8080/api/chat/session \
-h "content-type: application/json" \
-d '{"sessionid": "session-a", "message": "我叫张三"}'
curl -x post http://localhost:8080/api/chat/session \
-h "content-type: application/json" \
-d '{"sessionid": "session-a", "message": "我叫什么?"}'
# 会话b - 独立记忆,不会混淆
curl -x post http://localhost:8080/api/chat/session \
-h "content-type: application/json" \
-d '{"sessionid": "session-b", "message": "我叫什么?"}'十二、功能5:综合 - rag + tool calling + 保留会话及消息
这是终极功能,结合了rag(检索增强生成)和tool calling(工具调用)。
12.1 工具类 - mathtool.java
package com.example.tool;
import dev.langchain4j.agent.tool.tool;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.component;
/**
* 数学工具类
* 使用@tool注解标记可被ai调用的方法
*/
@slf4j
@component
public class mathtool {
/**
* 加法运算
*/
@tool("计算两个数字的和")
public double add(double a, double b) {
log.info("工具调用: add({}, {})", a, b);
return a + b;
}
/**
* 减法运算
*/
@tool("计算两个数字的差")
public double subtract(double a, double b) {
log.info("工具调用: subtract({}, {})", a, b);
return a - b;
}
/**
* 乘法运算
*/
@tool("计算两个数字的乘积")
public double multiply(double a, double b) {
log.info("工具调用: multiply({}, {})", a, b);
return a * b;
}
/**
* 除法运算
*/
@tool("计算两个数字的商")
public double divide(double a, double b) {
log.info("工具调用: divide({}, {})", a, b);
if (b == 0) {
throw new illegalargumentexception("除数不能为0");
}
return a / b;
}
/**
* 平方根
*/
@tool("计算一个数字的平方根")
public double sqrt(double x) {
log.info("工具调用: sqrt({})", x);
if (x < 0) {
throw new illegalargumentexception("不能计算负数的平方根");
}
return math.sqrt(x);
}
}
12.2 rag服务 - 文档加载与检索
package com.example.rag;
import dev.langchain4j.data.document.document;
import dev.langchain4j.data.document.splitter.documentsplitters;
import dev.langchain4j.data.segment.textsegment;
import dev.langchain4j.model.embedding.embeddingmodel;
import dev.langchain4j.model.openai.openaiembeddingmodel;
import dev.langchain4j.store.embedding.embeddingstore;
import dev.langchain4j.store.embedding.inmemory.inmemoryembeddingstore;
import lombok.extern.slf4j.slf4j;
import org.springframework.beans.factory.annotation.value;
import org.springframework.stereotype.service;
import javax.annotation.postconstruct;
import java.util.list;
/**
* rag服务 - 检索增强生成
*/
@slf4j
@service
public class ragservice {
@value("${openai.api.key}")
private string apikey;
@value("${openai.base.url}")
private string baseurl;
private embeddingstore<textsegment> embeddingstore;
private embeddingmodel embeddingmodel;
@postconstruct
public void init() {
// 初始化嵌入模型
this.embeddingmodel = openaiembeddingmodel.builder()
.apikey(apikey)
.baseurl(baseurl)
.modelname("text-embedding-ada-002")
.build();
// 初始化内存向量存储(生产环境建议用pgvector或milvus)
this.embeddingstore = new inmemoryembeddingstore<>();
// 加载知识文档
loadknowledgedocuments();
}
/**
* 加载知识文档
* 实际项目中可以从文件、数据库、网络等加载
*/
private void loadknowledgedocuments() {
log.info("加载知识文档...");
// 示例知识库 - 实际项目中从文件读取
string knowledge = """
langchain4j是一个java框架,用于构建大语言模型应用。
它提供了统一的api来集成各种llm,包括openai、通义千问、claude等。
rag(检索增强生成)是一种技术,通过检索外部知识来增强大模型的回答能力。
工具调用(tool calling)允许大模型调用外部函数来执行特定任务。
chatmemory是langchain4j中管理对话历史的组件。
""";
// 创建文档并分块
document document = document.from(knowledge);
list<textsegment> segments = documentsplitters.recursive(500, 50)
.split(document);
// 嵌入并存储
list<dev.langchain4j.data.embedding.embedding> embeddings =
embeddingmodel.embedall(segments).content();
for (int i = 0; i < segments.size(); i++) {
embeddingstore.add(embeddings.get(i), segments.get(i));
}
log.info("知识文档加载完成,共 {} 个分块", segments.size());
}
/**
* 检索相关文档片段
*/
public list<textsegment> searchrelevant(string query, int maxresults) {
log.info("rag检索: {}", query);
// 将查询转换为向量
dev.langchain4j.data.embedding.embedding queryembedding =
embeddingmodel.embed(query).content();
// 相似度检索
list<dev.langchain4j.store.embedding.embeddingmatch<textsegment>> matches =
embeddingstore.findrelevant(queryembedding, maxresults);
return matches.stream()
.map(dev.langchain4j.store.embedding.embeddingmatch::embedded)
.tolist();
}
/**
* 构建rag增强的提示词
*/
public string buildragprompt(string userquery, list<textsegment> segments) {
stringbuilder context = new stringbuilder();
for (int i = 0; i < segments.size(); i++) {
context.append("【文档").append(i + 1).append("】")
.append(segments.get(i).text())
.append("\n");
}
return """
请基于以下参考信息回答用户的问题。
如果参考信息中没有相关内容,请如实告知用户。
=== 参考信息 ===
%s
=== 用户问题 ===
%s
请给出清晰、准确的回答。
""".formatted(context.tostring(), userquery);
}
}
12.3 综合服务实现
// ================================================================
// 功能5:综合 - rag + tool calling + 会话持久化
// ================================================================
@autowired
private mathtool mathtool;
@autowired
private ragservice ragservice;
@autowired
private chatmessagemapper chatmessagemapper;
@override
public string chatwithragandtools(string sessionid, string usermessage) {
log.info("【功能5】综合对话: sessionid={}, message={}", sessionid, usermessage);
try {
// ===== 第一步:rag检索 =====
list<textsegment> relevantdocs = ragservice.searchrelevant(usermessage, 3);
string ragprompt = ragservice.buildragprompt(usermessage, relevantdocs);
log.debug("rag增强后的提示词: {}", ragprompt);
// ===== 第二步:获取会话记忆 =====
messagewindowchatmemory memory = sessionmemorycache.computeifabsent(sessionid, id -> {
log.info("创建新会话记忆: {}", id);
return messagewindowchatmemory.builder()
.id(id)
.maxmessages(20)
.chatmemorystore(chatmemorystore)
.build();
});
// ===== 第三步:构建带工具调用的请求 =====
// 使用aiservices高级api,自动处理工具调用循环
// 这里演示手动处理工具调用的方式
// 先添加用户消息
memory.add(usermessage.from(ragprompt));
// 获取工具规范
list<toolspecification> toolspecs = toolspecifications.toolspecificationsfrom(
mathtool.getclass()
);
// 发送请求
chatrequest request = chatrequest.builder()
.messages(memory.messages())
.parameters(chatrequestparameters.builder()
.toolspecifications(toolspecs)
.build())
.build();
chatresponse response = chatmodel.chat(request);
aimessage aimessage = response.aimessage();
// ===== 第四步:处理工具调用 =====
// 检查ai是否请求调用工具
if (aimessage.hastoolexecutionrequests()) {
log.info("ai请求调用工具: {}", aimessage.toolexecutionrequests());
// 执行工具
list<toolexecutionresultmessage> toolresults = new arraylist<>();
for (toolexecutionrequest toolrequest : aimessage.toolexecutionrequests()) {
object result = executetool(toolrequest);
toolresults.add(toolexecutionresultmessage.from(toolrequest, string.valueof(result)));
}
// 将ai的工具调用请求和工具执行结果加入记忆
memory.add(aimessage);
for (toolexecutionresultmessage resultmsg : toolresults) {
memory.add(resultmsg);
}
// 再次调用模型,获取最终回答
chatresponse finalresponse = chatmodel.chat(memory.messages());
string finalanswer = finalresponse.aimessage().text();
// 保存最终回答到记忆
memory.add(aimessage.from(finalanswer));
log.info("【功能5】最终响应: {}", finalanswer);
return finalanswer;
}
// ===== 第五步:没有工具调用,直接返回 =====
string answer = aimessage.text();
memory.add(aimessage.from(answer));
log.info("【功能5】响应: {}", answer);
return answer;
} catch (exception e) {
log.error("综合对话失败", e);
return "抱歉,ai服务暂时不可用:" + e.getmessage();
}
}
/**
* 执行工具调用
*/
private object executetool(toolexecutionrequest toolrequest) {
string toolname = toolrequest.name();
map<string, object> arguments = toolrequest.arguments();
log.info("执行工具: {}, 参数: {}", toolname, arguments);
switch (toolname) {
case "add":
return mathtool.add(
((number) arguments.get("a")).doublevalue(),
((number) arguments.get("b")).doublevalue()
);
case "subtract":
return mathtool.subtract(
((number) arguments.get("a")).doublevalue(),
((number) arguments.get("b")).doublevalue()
);
case "multiply":
return mathtool.multiply(
((number) arguments.get("a")).doublevalue(),
((number) arguments.get("b")).doublevalue()
);
case "divide":
return mathtool.divide(
((number) arguments.get("a")).doublevalue(),
((number) arguments.get("b")).doublevalue()
);
case "sqrt":
return mathtool.sqrt(
((number) arguments.get("x")).doublevalue()
);
default:
throw new illegalargumentexception("未知工具: " + toolname);
}
}
12.4 chatcontroller.java - 功能5接口
// ================================================================
// 功能5:综合 - rag + tool calling + 会话持久化
// ================================================================
@postmapping("/advanced")
public map<string, object> advancedchat(@requestbody map<string, string> request) {
string sessionid = request.get("sessionid");
string message = request.get("message");
map<string, object> result = new hashmap<>();
if (message == null || message.trim().isempty()) {
result.put("code", 400);
result.put("message", "消息不能为空");
return result;
}
if (sessionid == null || sessionid.trim().isempty()) {
sessionid = chatservice.createnewsession("default-user", "高级对话");
result.put("sessionid", sessionid);
}
try {
string response = chatservice.chatwithragandtools(sessionid, message);
result.put("code", 200);
result.put("data", response);
result.put("sessionid", sessionid);
} catch (exception e) {
result.put("code", 500);
result.put("message", e.getmessage());
}
return result;
}
测试:
# rag知识问答
curl -x post http://localhost:8080/api/chat/advanced \
-h "content-type: application/json" \
-d '{"sessionid": "adv-session-1", "message": "什么是rag?"}'
# 工具调用 - 数学计算
curl -x post http://localhost:8080/api/chat/advanced \
-h "content-type: application/json" \
-d '{"sessionid": "adv-session-1", "message": "请计算 12345 乘以 6789 等于多少"}'
# 多轮对话 - 看ai是否记住上下文
curl -x post http://localhost:8080/api/chat/advanced \
-h "content-type: application/json" \
-d '{"sessionid": "adv-session-1", "message": "刚才我们计算了什么?"}'十三、常见问题与避坑指南
13.1 依赖版本兼容性
langchain4j 0.36.0 开始要求 jdk 17,如果项目还在用 jdk 8,记得用 0.35.0 版本。
13.2 chatmemory vs 历史记录
这是一个很容易混淆的概念:
- 历史记录(history) :完整的对话记录,用户能看到的所有消息
- 记忆(memory) :经过裁剪后喂给模型的消息,可能只保留最近n条
咱们的 chat_message 表存的是完整历史,chatmemorystore 存的是记忆。两者可以共用一张表,也可以分开。
13.3 api key 安全
千万别把 api key 硬编码在代码里!用环境变量或者配置文件管理。
13.4 工具调用的循环
当 ai 请求调用工具时,需要执行工具并将结果返回给 ai,ai 可能再次请求调用工具。生产环境需要处理多轮工具调用循环。
13.5 内存缓存 vs 数据库
sessionmemorycache 用的是 concurrenthashmap,生产环境建议替换为 redis 或 caffeine 等更健壮的缓存方案。
十四、总结
到这里,咱们就从零到一完成了 langchain4j + 普通spring项目的完整集成,实现了:
- 普通ai对话 - 最简单的问答
- 带提示词的对话 - 通过 systemmessage 控制ai角色
- 保留对话轮次 - 单会话上下文记忆
- 保留会话 - 多会话隔离,每个会话独立记忆
- 综合 rag + tool calling + 会话持久化 - 企业级能力
关键点回顾:
- 普通spring项目需要手动配置所有 bean,不像 spring boot 那样自动配置
chatmemorystore是实现持久化的核心接口,实现它就能把记忆存到任何地方- rag 通过检索增强大模型的知识
- tool calling 让 ai 能调用外部函数
以上就是spring项目集成langchain4j的保姆级教程的详细内容,更多关于spring集成langchain4j的资料请关注代码网其它相关文章!
发表评论