当前位置: 代码网 > it编程>编程语言>Java > Spring项目集成Langchain4J的保姆级教程

Spring项目集成Langchain4J的保姆级教程

2026年08月05日 Java 我要评论
大家好!今天咱们来聊聊如何在普通spring项目 中集成langchain4j,一步步构建一个功能完整的ai对话应用。为啥要强调“普通spring项目”?因为网上绝大多数教程都

大家好!今天咱们来聊聊如何在普通spring项目 中集成langchain4j,一步步构建一个功能完整的ai对话应用。

为啥要强调“普通spring项目”?因为网上绝大多数教程都是spring boot的,但现实中很多老项目还是基于xml配置的spring mvc,或者出于各种原因没法用spring boot。咱们今天就专门给这部分同学写一份能直接用的教程。

废话不多说,直接开整!

一、项目概览

咱们要实现的5个功能,从简单到复杂层层递进:

序号功能说明
1普通ai对话接口最简单的“一问一答”
2带提示词的对话接口通过systemmessage设定ai角色
3保留对话轮次的接口单会话内记住上下文
4保留会话的接口多会话隔离,每个会话独立记忆
5rag + 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项目的完整集成,实现了:

  1. 普通ai对话 - 最简单的问答
  2. 带提示词的对话 - 通过 systemmessage 控制ai角色
  3. 保留对话轮次 - 单会话上下文记忆
  4. 保留会话 - 多会话隔离,每个会话独立记忆
  5. 综合 rag + tool calling + 会话持久化 - 企业级能力

关键点回顾:

  • 普通spring项目需要手动配置所有 bean,不像 spring boot 那样自动配置
  • chatmemorystore 是实现持久化的核心接口,实现它就能把记忆存到任何地方
  • rag 通过检索增强大模型的知识
  • tool calling 让 ai 能调用外部函数

以上就是spring项目集成langchain4j的保姆级教程的详细内容,更多关于spring集成langchain4j的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com