Java8+Spring Boot + Vue + Langchain4j 实现阿里云百炼平台 AI 流式对话对接

1. 引言

在本文中,我们将介绍如何使用 Spring BootVue.jsLangchain4j,实现与 阿里云百炼平台 的 AI 流式对话对接。通过结合这些技术,我们将创建一个能够实时互动的 AI 聊天应用。

这是一个基于 Spring Boot + Vue.js + Langchain4j 的智能对话系统,实现了类似 ChatGPT 的流式交互体验。系统通过 阿里云百炼 qwen-max 大模型 提供 AI 能力,支持多轮对话记忆(Redis 存储)、本地知识库检索(RAG)和实时流式响应(SSE 协议)。前端 Vue.js 动态渲染对话内容,后端 Spring Boot 处理业务逻辑,Langchain4j 简化大模型集成,具有低延迟、易扩展的特点,适用于智能客服、知识问答等场景。

在线预览地址
https://www.coderman.club

对话记忆功能
在这里插入图片描述
代码编写效果图
在这里插入图片描述
预览效果

2. 技术栈概述

  • Spring Boot:后端框架,用于处理与阿里云百炼平台的 API 对接与业务逻辑。
  • Vue.js:前端框架,用于实现与用户的实时交互和流式数据展示。
  • Langchain4j:Java 库,用于简化与文本生成模型的交互。
  • 阿里云百炼平台:为智能对话提供 NLP 模型和 API,支持流式对话功能。

3. 阿里云百炼平台接入准备

3.1 注册阿里云百炼账号

首先,注册一个阿里云账号并访问百炼平台,获取相关的 API 密钥

3.2 创建对话模型

在阿里云百炼平台上创建并配置一个 AI 对话模型,为后续的对接做好准备。

3.3 获取 API 文档

参考阿里云的 API 文档,获取具体的接口信息和请求参数。

4. 后端实现 - Spring Boot 与阿里云百炼平台对接

4.1 创建 Spring Boot 项目

使用 Spring Initializr 创建一个基础的 Spring Boot 项目,配置相关依赖。

 <dependency>
     <groupId>dev.langchain4j</groupId>
     <artifactId>langchain4j</artifactId>
     <version>${langchain4j.version}</version>
 </dependency>

 <dependency>
     <groupId>dev.langchain4j</groupId>
     <artifactId>langchain4j-dashscope</artifactId>
     <version>${langchain4j.version}</version>
 </dependency>

4.2 编写Controller代码

@Api(value = "AI接口", tags = "AI接口")
@RestController
@Slf4j
@RequestMapping(value = "/common/chat")
public class ChatController {
    
    

    @Resource
    private ChatService chatService;

    @ApiModelProperty(value = "AI对话")
    @PostMapping(value = "/completion",produces = {
    
    MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter completion(@RequestBody ChatGptDTO chatGptDTO) {
    
    

        SseEmitter sseEmitter = new SseEmitter(5 * 60 * 1000L);

        this.chatService.completion(chatGptDTO, sseEmitter);
        sseEmitter.onTimeout(() -> {
    
    
            log.warn("SSE连接超时");
            sseEmitter.complete();
        });

        sseEmitter.onError(e -> {
    
    
            log.error("SSE连接错误", e);
            sseEmitter.complete();
        });
        return sseEmitter;
    }
}

4.2 编写Service代码

package com.coderman.admin.service.common.impl;

import com.alibaba.fastjson.JSONObject;
import com.coderman.admin.dto.common.ChatGptDTO;
import com.coderman.admin.service.common.Assistant;
import com.coderman.admin.service.common.ChatService;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.annotation.Resource;
import java.io.IOException;

@Service
@Slf4j
@Configuration
public class ChatServiceImpl implements ChatService {
    
    

    @Resource
    private Assistant assistant;

    /**
     * 调用大模型接口
     * @param chatGptDTO 参数
     * @param sseEmitter sse
     */
    public void completion(ChatGptDTO chatGptDTO, SseEmitter sseEmitter) {
    
    
        try {
    
    
            TokenStream stream = assistant.completion(chatGptDTO.getSessionId(), chatGptDTO.getPrompt());
            // 监听回调
            stream.onNext(result -> sendSseData(sseEmitter, result));
            stream.onError(throwable -> handleError(sseEmitter, throwable));
            stream.onComplete(response -> completeSse(sseEmitter));
            stream.start();
        } catch (Exception e) {
    
    
            throw new RuntimeException("completion error:"+e.getMessage());
        }
    }

    private void sendSseData(SseEmitter sseEmitter, String result) {
    
    
        try {
    
    
            JSONObject jsonObject =  new JSONObject();
            jsonObject.put("text",result);
            sseEmitter.send(jsonObject, MediaType.APPLICATION_JSON);
        } catch (IOException e) {
    
    
            log.warn("SSE 发送数据失败:{}", e.getMessage());
        }
    }

    private void handleError(SseEmitter sseEmitter, Throwable throwable) {
    
    
        log.error("SSE 发生错误:{}", throwable.getMessage());
        sseEmitter.completeWithError(throwable);
    }

    private void completeSse(SseEmitter sseEmitter) {
    
    
        try {
    
    
            sseEmitter.send("[DONE]");
            sseEmitter.complete();
        } catch (Exception e) {
    
    
            log.warn("SSE 关闭失败:{}", e.getMessage());
        }
    }
}

4.3 langchain4j配置类

package com.coderman.admin.config;

import com.coderman.admin.service.common.Assistant;
import com.coderman.api.constant.CommonConstant;
import com.coderman.service.util.DesUtil;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author :zhangyukang
 * @date :2025/04/02 11:12
 */
@Configuration
@Slf4j
public class ChatAiConfigure {
    
    

    @Resource
    private RedisProperties redisProperties;

    @Bean
    public Assistant assistant() {
    
    
        EmbeddingModel embeddingModel =  QwenEmbeddingModel.builder()
                .apiKey(this.getApiKey())
                .build();
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(embeddingModel)
                // 最相似的3个结果
                .maxResults(3)
                // 只找相似度在0.8以上的内容
                .minScore(0.8)
                .build();

        // 初始化知识库
        this.loadEmbedding(embeddingModel, embeddingStore);

        // 自定义存储方式
        RedisChatMemoryStore redisChatMemoryStore = RedisChatMemoryStore.builder()
                .port(redisProperties.getPort())
                .password(redisProperties.getPassword())
                .host(redisProperties.getHost())
                .build();
        // 构建模型
        StreamingChatLanguageModel model = QwenStreamingChatModel.builder()
                .apiKey(this.getApiKey())
                .modelName("qwen-max")
                .maxTokens(1024)
                .enableSearch(true)
                .build();
        // 构建服务
        return AiServices.builder(Assistant.class)
                .streamingChatLanguageModel(model)
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder()
                        .id(memoryId)
                        .maxMessages(10)
                        .chatMemoryStore(redisChatMemoryStore)
                        .build())
                .contentRetriever(contentRetriever)
                .build();
    }

    /**
     * 初始化RAG向量
     * @param embeddingModel 模型
     * @param embeddingStore 向量存储
     */
    private void loadEmbedding(EmbeddingModel embeddingModel, InMemoryEmbeddingStore<TextSegment> embeddingStore) {
    
    
        try {
    
    
            Document document = FileSystemDocumentLoader.loadDocument("/opt/rag.txt", new TextDocumentParser());
            List<TextSegment> textSegments = new RagDocumentSplitter().split(document);
            Response<List<Embedding>> response = embeddingModel.embedAll(textSegments);
            List<Embedding> embeddings = response.content();
            embeddingStore.addAll(embeddings, textSegments);
        } catch (Exception e) {
    
    
            log.warn("初始化知识库失败:{}", e.getMessage());
        }
    }

    private String getApiKey() {
    
    
        try {
    
    
            final String API_KEY_ENCRYPTED = "你的app秘钥";
            return DesUtil.decrypt(API_KEY_ENCRYPTED, CommonConstant.SECRET_KEY);
        } catch (Exception e) {
    
    
            log.error("API Key 解密失败", e);
            throw new RuntimeException("无法解密 API Key");
        }
    }
}

4.4 自定义AI对话接口类

package com.coderman.admin.service.common;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.UserMessage;

/**
 * @author :zhangyukang
 * @date :2025/04/02 11:14
 */
public interface Assistant {
    
    

    @SystemMessage(value = {
    
    
            "你是由小章鱼开发的AI智能助手,专注于高效、友好地帮助用户解决问题。",
            "你的核心能力包括:问题解答、信息查询、任务协助、建议提供等。",
            "当用户询问你的身份时,请明确回复:'我是由小章鱼开发的AI助手,很高兴为你服务!'",
            "回答时需保持专业且亲切,语言简洁易懂,避免冗长或模糊表述。",
            "如果遇到无法回答的问题,应礼貌说明并建议替代方案。",
            "始终以用户需求为核心,主动提供有价值的帮助。"
    })
    TokenStream completion(@MemoryId String sessionId, @UserMessage String userMessage);
}

4.5 使用Redis存储实现对话记忆

package com.coderman.admin.config;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.internal.ValidationUtils;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import redis.clients.jedis.*;

import java.util.ArrayList;
import java.util.List;

/**
 * 使用 Redis 存储聊天记录的 MemoryStore
 */
public class RedisChatMemoryStore implements ChatMemoryStore {
    
    

    private final JedisPool jedisPool;
    private final static String KEY_PREFIX = "chat_memory:";

    public RedisChatMemoryStore(String host, Integer port, String password) {
    
    
        // 配置 Redis 连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(10);
        poolConfig.setMaxIdle(5);
        poolConfig.setMinIdle(2);

        // 这里 JedisPool 支持传入密码
        this.jedisPool = new JedisPool(poolConfig, host, port, 2000, password);
    }

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
    
    
        try (Jedis jedis = jedisPool.getResource()) {
    
    
            String json = jedis.get(KEY_PREFIX+ toMemoryIdString(memoryId));
            return json == null ? new ArrayList<>() : ChatMessageDeserializer.messagesFromJson(json);
        }
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
    
    
        try (Jedis jedis = jedisPool.getResource()) {
    
    
            String json = ChatMessageSerializer.messagesToJson(ValidationUtils.ensureNotEmpty(messages, "messages"));
            String res = jedis.set(KEY_PREFIX + toMemoryIdString(memoryId), json);
            if (!"OK".equals(res)) {
    
    
                throw new RuntimeException("Redis set memory error: " + res);
            }
        }
    }

    @Override
    public void deleteMessages(Object memoryId) {
    
    
        try (Jedis jedis = jedisPool.getResource()) {
    
    
            jedis.del(KEY_PREFIX + toMemoryIdString(memoryId));
        }
    }

    private static String toMemoryIdString(Object memoryId) {
    
    
        if (memoryId == null || memoryId.toString().trim().isEmpty()) {
    
    
            throw new IllegalArgumentException("memoryId cannot be null or empty");
        }
        return memoryId.toString();
    }

    public static Builder builder() {
    
    
        return new Builder();
    }

    /**
     * Builder 模式,方便配置 Redis 连接
     */
    public static class Builder {
    
    
        private String host = "127.0.0.1";
        private Integer port = 6379;
        private String password = null;

        public Builder host(String host) {
    
    
            this.host = host;
            return this;
        }

        public Builder port(Integer port) {
    
    
            this.port = port;
            return this;
        }

        public Builder password(String password) {
    
    
            this.password = password;
            return this;
        }

        public RedisChatMemoryStore build() {
    
    
            return new RedisChatMemoryStore(host, port, password);
        }
    }
}

源码地址: https://github.com/zykzhangyukang/admin

猜你喜欢

转载自blog.csdn.net/qq_43257103/article/details/147042987
今日推荐