基于 Spring AI 和 Redis 向量库的智能对话系统实践

基于 Spring AI 和 Redis 向量库的智能对话系统实践

在当前大模型技术飞速发展的背景下,构建一个功能强大、可扩展的 AI 助手系统成为许多开发者的目标。本文将分享我们基于 Spring AI 框架,结合 Redis 作为向量数据库,实现的一个多模态、支持文档检索与文件上传的智能对话系统。系统具备图片识别、文件解析、中英文翻译、深度思考等核心能力,并实现了灵活的多模型管理和切换。

界面预览

技术架构概览

整个系统采用前后端分离架构,后端基于 Spring Boot + Spring AI 构建,前端为简洁的 Web 界面。核心技术栈如下:

  • 核心框架 : Spring AI - 提供了与大语言模型(LLM)交互的标准化接口。
  • 向量数据库 : Redis (通过 spring-ai-redis 实现) - 用于存储和检索文档的嵌入向量(Embeddings),实现基于内容的语义搜索。
  • 模型服务: 自行部署的 Qwen 大模型(通过 vLLM 推理服务),以及阿里百炼提供的视觉模型和向量模型。
  • 文件处理: 使用 Apache Tika 解析多种格式的文档(PDF, Word, PPT 等),并利用 OCR 技术处理图片中的文字。
  • 通信协议 : 使用 SSE (Server-Sent Events) 实现流式响应,提供流畅的聊天体验。
  • 状态管理: 利用 Redis 缓存会话记忆和图片识别结果,提升性能。

核心功能实现

1. 多模型管理与切换

系统支持管理多个不同类型的 AI 模型,并允许用户在聊天过程中自由切换。

复制代码
// 在配置界面展示所有模型
@GetMapping("/models")
public List<ModelVO> listModels() {
    return modelService.listByCondition(new ModelQuery());
}

用户可以通过下拉菜单选择不同的模型进行对话,如 /data/model/Qwen3-30B-A3B(本地大模型)、qwen3-max(阿里百炼Max大模型)等。

2. 图片与文件上传预览及提问

系统支持用户上传图片和文件,并能对其进行分析和问答。

图片识别

当用户上传图片时,系统会自动调用多模态模型(Multi-Modal Model)来识别图片内容,并将其描述文本作为上下文传递给主语言模型。

复制代码
/**
 * 处理图片:调用多模态模型识别图片内容
 */
private String processImagesWithMultiModal(List<String> fileUrls, String conversationId) {
    if (CollectionUtils.isEmpty(fileUrls)) {
        return "没有找到图片文件";
    }

    // 分类文件,只处理图片
    FileClassificationResult classification = classifyFiles(fileUrls);
    List<String> imageUrls = classification.getImageUrls();

    if (imageUrls.isEmpty()) {
        return "";
    }

    // 获取可用的多模态模型
    AiModel multiModalModel = getMultiModalModel();
    if (multiModalModel == null) {
        log.warn("未找到可用的多模态模型,跳过图片识别");
        return "未找到可用的多模态模型";
    }

    StringBuilder imageDescriptions = new StringBuilder();
    AIParams multiModalParams = mergeParams(multiModalModel, null);

    for (int i = 0; i < imageUrls.size(); i++) {
        String imageUrl = imageUrls.get(i);
        try {
            // 使用 Redis 缓存图片识别结果,避免重复请求
            String cacheKey = IMAGE_CACHE_PREFIX + conversationId + ":" + extractFileNameFromUrl(imageUrl);
            String cachedDescription = stringRedisTemplate.opsForValue().get(cacheKey);

            if (StrUtil.isNotBlank(cachedDescription)) {
                imageDescriptions.append("图片").append(i + 1).append(": ");
                imageDescriptions.append(cachedDescription);
            } else {
                // 调用多模态模型进行识别
                byte[] imageBytes = ossService.readBytes(imageUrl);
                UserMessage visionMessage = UserMessage.builder()
                        .text("请描述这张图片的内容。")
                        .media(new Media(MimeTypeUtils.IMAGE_PNG, new ByteArrayResource(imageBytes)))
                        .build();

                String imageDescription = llmHandler.blockChatWithVision(visionMessage, multiModalParams);

                // 将结果缓存到 Redis(5分钟有效)
                stringRedisTemplate.opsForValue().set(cacheKey, imageDescription, 5, TimeUnit.HOURS);
                imageDescriptions.append("图片").append(i + 1).append(": ");
                imageDescriptions.append(imageDescription.trim()).append("\n");
            }
        } catch (Exception e) {
            log.error("图片识别失败: {}", imageUrl, e);
            String fileName = extractFileNameFromUrl(imageUrl);
            imageDescriptions.append("图片").append(i + 1).append(": ").append(fileName).append("\n");
        }
    }

    return imageDescriptions.toString();
}

3. 文档上传与向量化

用户可以上传 PDF、Word 等文档,系统会使用 Tika 工具解析其内容,并将其分块后转换为向量,存储到 Redis 向量库中。

复制代码
@PostMapping(value = "/tikaParseEmbeddingFileSse", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public SseEmitter tikaParseEmbeddingFileSse(@RequestPart("file") MultipartFile file) {
    AssertUtil.notNull(file, "请上传文件");
    String filename = file.getOriginalFilename();
    AssertUtil.notNull(filename, "请上传文件");

    // 区分图片和普通文件
    String fileSuffix = filename.substring(filename.lastIndexOf(".") + 1);
    if (fileSuffix.equals("jpg") || fileSuffix.equals("png") || fileSuffix.equals("jpeg")) {
        return embeddingFileService.ocrImage(file); // 图片使用OCR
    } else {
        return embeddingFileService.tikaParseEmbeddingFileSse(file); // 其他文件使用Tika解析
    }
}

embeddingFileService 内部会调用 documentParseService 解析文件,并最终调用 llmHandler.vectorizeAndStore() 方法将文本内容向量化并存入 Redis。

4. 基于文档的智能问答

当用户提出问题时,系统会先从 Redis 向量库中检索与问题最相关的文档片段,然后将这些片段作为上下文,连同用户的问题一起发送给 LLM 进行回答。

复制代码
/**
 * 检索最相关的文档
 */
public List<Document> retrieveRelevantDocuments(String query, List<String> documentIds, AIParams params) {
    RedisVectorStore vectorStore = getInitializedVectorStore(params);

    // 构建过滤器,只检索指定文档ID的内容
    Filter.Expression filter = null;
    if (!documentIds.isEmpty()) {
        filter = VectorFilterBuilder.buildDocumentIdFilter(documentIds);
    }

    SearchRequest request = SearchRequest.builder()
            .query(query)
            .topK(20) // 返回前20个最相关的文档
            .similarityThreshold(0.5) // 相似度阈值
            .filterExpression(filter)
            .build();

    List<Document> documents = vectorStore.similaritySearch(request);
    // 进一步过滤确保返回的文档属于目标列表
    return documents.stream()
            .filter(doc -> documentIds.contains(doc.getMetadata().get("document_id")))
            .collect(Collectors.toList());
}

5. 流式对话与深度思考

系统使用 SSE 实现流式输出,用户可以实时看到 AI 的生成过程。同时,支持"深度思考"模式,让 AI 在回答前进行更深入的推理。

复制代码
public void streamChatSse(String systemMsg, UserMessage message, String conversationId, Boolean enableThinking, AIParams params, SseEmitter emitter) {
    // ... 参数校验 ...

    AiModelOptions modelOptions = params.toModelOptions();
    ChatModel chatModel = AiModelFactory.createStreamingChatModel(modelOptions);

    ChatClient chatClient = ChatClient.builder(chatModel)
            .defaultAdvisors(
                    new SimpleLoggerAdvisor(),
                    new MessageChatMemoryAdvisor(chatMemory) // 记住聊天历史
            ).build();

    // 设置会话 ID,用于在 Redis 中存储聊天记忆
    Integer userId = Objects.requireNonNull(UserUtil.getUser()).getId();
    String memoryIdWithUserId = userId + ":u:" + conversationId;

    Prompt prompt = new Prompt(message);
    prompt.setEnableThinking(enableThinking); // 开启深度思考

    Disposable disposable = chatClient.prompt(prompt)
            .advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, memoryIdWithUserId))
            .system(systemMsg)
            .stream()
            .content()
            .takeWhile(chunk -> !chatSessionHandler.isCancelled(conversationId))
            .doOnNext(chunk -> {
                try {
                    MessageData messageData = new MessageData();
                    messageData.setMessage(chunk);
                    emitter.send(SseEmitter.event().data(SseResponse.chunk(conversationId, messageData)));
                } catch (IOException e) {
                    emitter.completeWithError(e);
                }
            })
            .doOnComplete(() -> {
                try {
                    emitter.send(SseEmitter.event().data(SseResponse.end()));
                    emitter.complete();
                } catch (IOException e) {
                    emitter.completeWithError(e);
                }
            })
            .doOnError(error -> {
                try {
                    emitter.send(SseEmitter.event().data(SseResponse.error("服务端错误: " + error.getMessage())));
                } catch (Exception ex) {
                    log.error("发送错误事件失败", ex);
                }
                emitter.completeWithError(error);
            })
            .subscribe();

    chatSessionHandler.registerSession(conversationId, disposable, emitter);
}

6. 翻译与深度思考功能

系统内置了简单的翻译功能,可以快速将中文翻译成英文,反之亦然。

复制代码
// 在前端页面上,用户可以选择"翻译"按钮
// 系统会调用类似以下逻辑
if (requestType.equals("translate")) {
    String result = translate(text, sourceLang, targetLang);
    return result;
}

"深度思考"功能则通过设置 enableThinking=true 来触发,这会改变 LLM 的行为,使其在生成答案前进行更长时间的内部推理。

关键代码解析

AIChatHandler 类

AIChatHandler 是核心业务逻辑处理类,负责协调各个服务。

  • mergeParams: 合并全局模型参数和具体对话的参数,确保配置正确。
  • streamChatSse: 主要的流式聊天入口,支持传入文档ID和文件URL。
  • processImagesWithMultiModal: 调用多模态模型识别图片内容。
  • buildDocumentContextSafely: 从向量库检索相关文档内容。
  • buildUserMessageWithFiles: 构建包含文件信息的用户消息。

LLMHandler 类

LLMHandler 是与 Spring AI 框架交互的桥梁。

  • blockChat: 阻塞式调用 LLM。
  • blockChatWithVision: 支持图片输入的阻塞式调用。
  • streamChatSse: 实现流式对话的核心方法。
  • vectorizeAndStore: 将文本内容向量化并存储到 Redis。
  • retrieveRelevantDocuments: 从 Redis 向量库检索相关文档。

总结与展望

本项目成功构建了一个功能全面的智能对话系统,充分利用了 Spring AI 的强大能力和 Redis 作为向量数据库的高效性。系统不仅支持基础的文本聊天,还集成了图片识别、文档解析、语义检索等高级功能,极大地提升了用户体验。

未来可以进一步优化的方向包括:

  1. 引入 RAG(检索增强生成):更精细地控制检索和生成的流程。
  2. 增加工具调用(Tool Calling):让 AI 能够调用外部 API 完成复杂任务。
  3. 优化向量存储:探索更高效的向量索引算法。
  4. 增强安全与权限控制:对敏感数据和操作进行更严格的保护。

通过不断迭代和优化,这样的 AI 助手系统将成为企业知识管理、客户服务和个人学习的强大工具。

相关推荐
lambo mercy7 小时前
无监督学习
人工智能·深度学习
sunfove8 小时前
致暗夜行路者:科研低谷期的自我心理重建
人工智能
oMcLin8 小时前
如何在 AlmaLinux 9 上配置并优化 Redis 集群,支持高并发的实时数据缓存与快速查询?
数据库·redis·缓存
GAOJ_K8 小时前
丝杆模组精度下降的预警信号
人工智能·科技·机器人·自动化·制造
lusasky8 小时前
Claude Code 2.1.2最佳实战
人工智能
●VON8 小时前
跨模态暗流:多模态安全攻防全景解析
人工智能·学习·安全·von
柯南小海盗8 小时前
从“会聊天的AI”到“全能助手”:大语言模型科普
人工智能·语言模型·自然语言处理
焦耳热科技前沿8 小时前
中科大EMA:3秒焦耳热一步合成双功能催化剂用于甲醇氧化协同高效制氢
大数据·人工智能·自动化·能源·材料工程
向量引擎小橙8 小时前
推理革命与能耗:AI大模型应用落地的“冰山成本”与破局之路
大数据·人工智能·深度学习·集成学习