4. 使用SpringBoot快速集成LangChain4j, 实现AI的丝滑调用

1. 简介

前几章简单测试了一下LangChain4J的特性, 本章使用SpringBoot快速集成LangChain4J, 实现丝滑调用大模型, 往期内容传送门

  1. LangChain4j 初识,想使用Java开发AI应用?

  2. LangChain4j-AIServices,原来调用AI这么简单?

  3. LangChain4j-RAG,实现简单的text-sql功能

LangChain4J官方提供了SpringBoot Starter, 本章就使用Starter进行快速集成.

2. 环境信息

使用SDK版本信息如下:

Java: 21
SpringBoot: 3.4.5
LangChain4j: 1.0.1
LLM: (使用在线的百炼(阿里)平台)
	embedding模型: text-embedding-v3  
	chat模型: qwen-plus
PGVector(postgresql版本的向量数据库, 文章最后有相关的docker-compose): 0.8.0-pg17 

3. Maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ldx</groupId>
    <artifactId>langchain-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>langchain-test</name>
    <description>langchain-test</description>

    <properties>
        <java.version>21</java.version>
        <guava.version>33.0.0-jre</guava.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-bom</artifactId>
                <version>1.0.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
			  <!-- 这里使用的是OpenAI Starter, 因为百炼平台的模型支持OpenAI, 如果本地使用Ollama部署的模型, 导入其对应的Starter即可 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
        </dependency>
			  <!-- 该包实现了AI Services的自动注入, 如果想自己声明AI Services去掉该依赖即可 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-spring-boot-starter</artifactId>
        </dependency>

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

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>

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

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4. 配置信息

4.1 application.yml

通过在配置文件中声明模型信息 即可实现对应模型的自动注入

langchain4j:
  open-ai:
    # 普通聊天模型
    chat-model:
      api-key: ${LLM_API_KEY}
      model-name: qwen-plus
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
    # 流式相应模型  
    streaming-chat-model:
      api-key: ${LLM_API_KEY}
      model-name: qwen-plus
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
    # 向量模型  
    embedding-model:
      api-key: ${LLM_API_KEY}
      model-name: text-embedding-v3
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1

4.2 配置类

主要声明了

  1. 聊天记忆提供类, 关联了记忆存储对象
  2. 向量存储对象,这里使用的是pgvector
  3. 内容检索器(RAG-检索实现)
package com.ldx.langchaintest.config;

import com.ldx.langchaintest.service.PersistentChatMemoryStore;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.pgvector.DefaultMetadataStorageConfig;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * config
 *
 * @author ludangxin
 * @date 2025/5/15
 */
@Configuration
public class AiConfiguration {

    /**
     * 聊天记忆 提供者
     *
     * @param persistentChatMemoryStore 对话内容持久化对象
     * @return 对话记忆 provider
     */
    @Bean
    public ChatMemoryProvider jdbcChatMemoryProvider(PersistentChatMemoryStore persistentChatMemoryStore) {
        return memoryId -> MessageWindowChatMemory
                .builder()
                .id(memoryId)
                // 这里使用了自定义的会话存储对象, 可以通过其实现对话过程内容的持久化
                // 本地测试的话可以使用 InMemoryChatMemoryStore对象实现内存存储
                .chatMemoryStore(persistentChatMemoryStore)
                .maxMessages(5)
                .build();
    }
  
    /**
     * 向量存储对象
     * 
     * @param embeddingModel 向量模型
     * @return 向量存储对象
     */
    public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) {
        return PgVectorEmbeddingStore
                .builder()
                .host("localhost")                           // 必需:PostgresSQL 实例的主机
                .port(5431)                                  // 必需:PostgresSQL 实例的端口
                .database("postgres")                        // 必需:数据库名称
                .user("root")                                // 必需:数据库用户
                .password("123456")                          // 必需:数据库密码
                .table("my_embeddings")                      // 必需:存储嵌入的表名
                .dimension(embeddingModel.dimension())       // 必需:嵌入的维度
                .metadataStorageConfig(DefaultMetadataStorageConfig.defaultConfig()) // 元数据存储配置
                .build();
    }

    /**
     * 内容检索器
     *
     * @param embeddingModel 向量模型
     * @return 内容检索器
     */
    @Bean
    public ContentRetriever contentRetriever(EmbeddingModel embeddingModel) {
        return EmbeddingStoreContentRetriever
                .builder()
                .embeddingStore(this.embeddingStore(embeddingModel))
                .embeddingModel(embeddingModel)
                .maxResults(10)
                .minScore(0.65)
                .build();
    }
}

4.3 聊天记忆持久化

实现了ChatMemoryStore接口, 这里测试使用的是map存储的, 生产环境中可以持久化到数据库中

chat过程中消息会通过ChatMemory调用ChatMemoryStore对聊天内容进行持久化/获取

package com.ldx.langchaintest.service;

import com.google.common.collect.ArrayListMultimap;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PersistentChatMemoryStore implements ChatMemoryStore {
    final ArrayListMultimap<Object, ChatMessage> messagesStore = ArrayListMultimap.create();

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        return messagesStore.get(memoryId);
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        messagesStore.put(memoryId, messages.getLast());
    }

    @Override
    public void deleteMessages(Object memoryId) {
        messagesStore.removeAll(memoryId);
    }
}

4.4 tools

package com.ldx.langchaintest.tools;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * tools
 *
 * @author ludangxin
 * @date 2025/5/15
 */
@Slf4j
@Component
public class SysTools {
    @Tool("根据用户的名称获取对应的code")
    public String getUserCodeByUsername(@P("用户名称") String username) {
        log.info("get user code by username:{}", username);
        if ("张铁牛".equals(username)) {
            return "003";
        }

        return "000";
    }
}

5. 核心源码

5.1 Ai Services

@AiService 将实现AiService的自动注入

wiringMode = EXPLICIT: 用户自己指定相关的bean

​ 缺省:wiringMode = AUTOMATIC: 项目启动时自动在环境中找对应的对象实现注入,如果有多个(比如:chatModel),启动报错

这里举了几种典型的场景 如

  1. 普通聊天 chat()
  2. 聊天记忆&流式输出 chatWithStream()
  3. 提取指定内容并将结果结构化 extractPerson()
  4. 提示词占位替换 mockUsername()
  5. rag text-sql chatWithSql()
package com.ldx.langchaintest.service;

import com.ldx.langchaintest.domain.Person;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;

import java.util.List;

import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;

/**
 * ai svc
 *
 * @author ludangxin
 * @date 2025/5/16
 */
@AiService(wiringMode = EXPLICIT,
        chatModel = "openAiChatModel",
        streamingChatModel = "openAiStreamingChatModel",
        chatMemoryProvider = "chatMemoryProvider",
        contentRetriever = "contentRetriever",
        tools = {"sysTools"})
public interface AiSqlAssistantService {
    String chat(String message);

    @SystemMessage("👉 将文本改写成类似小红书的 Emoji 风格")
    Flux<String> chatWithStream(@MemoryId String memoryId, @UserMessage String message);

    @SystemMessage("请在用户提供的文本中提取出人员信息")
    Person extractPerson(@UserMessage String message);

    @UserMessage("需要你帮我mock人员姓名, 帮我生成{{total}}个")
    List<String> mockUsername(@V("total") Integer total);

    @SystemMessage("你是一名sql分析专家 我会将sql相关的ddl给你, 需要你根据ddl生成合理且可执行的sql语句并返回")
    String chatWithSql(@MemoryId String memoryId, @UserMessage String message);
}

5.2 Controller

package com.ldx.langchaintest.controller;

import com.ldx.langchaintest.domain.Person;
import com.ldx.langchaintest.service.AiSqlAssistantService;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.loader.ClassPathDocumentLoader;
import dev.langchain4j.data.document.splitter.DocumentByRegexSplitter;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingStore;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.util.List;

/**
 * ai controller
 *
 * @author ludangxin
 * @date 2025/5/16
 */
@Slf4j
@RestController
@RequestMapping("/ai/chat")
@RequiredArgsConstructor
public class AiServiceController {
    private final EmbeddingModel embeddingModel;

    private final EmbeddingStore<TextSegment> embeddingStore;

    private final AiSqlAssistantService aiSqlAssistantService;

    @GetMapping("/test")
    public String test() {
        return aiSqlAssistantService.chat("你是谁");
    }
  
    @GetMapping
    public String chat(@RequestParam String userMessage) {
        return aiSqlAssistantService.chat(userMessage);
    }

    @GetMapping(value = "/{id}/stream/memory", produces = "text/stream;charset=utf-8")
    public Flux<String> streamMemory(@PathVariable String id, @RequestParam String userMessage) {
        final Flux<String> chatResponse = aiSqlAssistantService.chatWithStream(id, userMessage);
        return chatResponse
                .doOnNext(partial -> log.info("chat stream partial data:{}", partial))
                .doOnError(e -> log.error("stream output error", e))
                .doOnComplete(() -> log.info("chat stream complete"));
    }

    @GetMapping("/extract/person")
    public Person extractPerson(@RequestParam String userMessage) {
        return aiSqlAssistantService.extractPerson(userMessage);
    }

    @GetMapping("/mock/username")
    public List<String> mockUsername(@RequestParam(defaultValue = "0") Integer total) {
        return aiSqlAssistantService.mockUsername(total);
    }

    @GetMapping(value = "/embedding")
    public String aiEmbedding() throws IOException {
        final Document document = ClassPathDocumentLoader.loadDocument("student_ddl.sql");
        // 创建 SQL 感知的文档分割器
        DocumentSplitter splitter = new DocumentByRegexSplitter(";",";",
                2000,    // 最大片段长度
                100     // 重叠长度
        );
        final List<TextSegment> textSegments = splitter.split(document);
        final Response<List<Embedding>> embedResult = embeddingModel.embedAll(textSegments);
        final List<Embedding> content = embedResult.content();
        embeddingStore.addAll(content, textSegments);
        return "success";
    }

    @GetMapping(value = "/{id}/sql/generate")
    public String aiEmbedding(@PathVariable String id, @RequestParam String userMessage) {
        return aiSqlAssistantService.chatWithSql(id, userMessage);
    }
}

6. 测试

6.1 测试chat

6.2 测试tool

6.3 测试流式&聊天记忆

第一次会话:

后端日志如下

第二次会话:

6.4 测试抽取用户信息

6.5 测试mock

6.6 测试embedding

数据库中信息如下:

6.7 测试text2sql

7. 小结

本章通过使用SpringBoot实现快速集成LangChain4J, 通过简单的配置实现了AI的调用, 总体使用感受还不错, 虽然是刚发布的正式版但是整体的集成、方法调用都挺丝滑的, 到这里这关于LangChain4J的全部内容已经完结了, 后续会出个SpringAI正式版的体验对比,感兴趣的可以关注下.

8. 源码

测试过程中的代码已全部上传至github, 欢迎点赞收藏 仓库地址: https://github.com/ludangxin/langchain4j-test

PGVector

version: '3'
services:
  pgvector:  
    container_name: pgvector
    restart: "no" 
    image: pgvector/pgvector:0.8.0-pg17
    privileged: true
    ports:
      - 5431:5432
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: 123456
      PGDATA: /var/lib/postgresql/data/
    volumes:
      - ./data:/var/lib/postgresql/data/
posted @ 2025-06-20 00:54  张铁牛  阅读(2057)  评论(2)    收藏  举报