1. 引言
在本文中,我们将介绍如何使用 Spring Boot、Vue.js 和 Langchain4j,实现与 阿里云百炼平台 的 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