找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2308

积分

0

好友

306

主题
发表于 2 小时前 | 查看: 2| 回复: 0

本文基于 Spring AI 1.0 与 Spring Boot 3.3,完整演示如何从零搭建一个生产可用的电商客服 RAG(检索增强生成)系统。我们将覆盖 PDF 文档解析、向量数据库选型、双模式检索、实体提取与答案溯源等核心环节,并附带所有可运行的代码。

一、传统客服系统的瓶颈为何难解?

当用户咨询“双11买的口红拆封了,颜色不喜欢能退吗?”时,传统的关键词匹配系统常常给出“亲,您可以在订单页面申请售后哦~”这类无意义的回复,根本无法命中用户关心的核心政策条款。

电商客服场景的挑战主要体现在:

  • 文档分散:退货政策、活动规则、运费说明等往往分散在多个PDF或Word文件中,人工维护的知识库更新滞后。
  • 语义理解弱:“拆封能退吗”和“已使用能退吗”表达同一意图,但关键词匹配无法识别。
  • 答案无溯源:客服回答无法提供具体的条款来源,导致用户信任度低。
  • 业务边界模糊:系统难以区分业务咨询(如退货)与非业务问题(如天气)。

RAG 架构恰恰能解决这些问题。它通过“实体提取 → 向量检索 → 答案生成”的流程,将用户问题精准关联到知识库中的具体条款,并生成带有来源标注的可信回答。

二、技术选型:为何选择 Spring AI?

技术栈全景

  • 核心框架:Spring Boot 3.3.0,Spring AI 1.0.0-M1,Java 17+
  • 向量数据库:PostgreSQL + pgvector(生产推荐),Redis Stack(轻量级方案),ChromaDB(快速原型)
  • 文档解析:Apache PDFBox(文本提取),Spring AI Document Readers(结构化解析)
  • 大模型:阿里云百炼(DashScope)、OpenAI GPT-4,或本地部署的 Qwen/ChatGLM

Spring AI 的核心优势在于,它将这些组件(DocumentReader → VectorStore → ChatClient)无缝集成到了 Spring 生态中,大大降低了开发复杂人工智能应用的门槛。

三、项目搭建与配置

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
         http://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.3.0</version>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>ecommerce-rag-service</artifactId>
    <version>1.0.0</version>
    <name>电商智能客服 RAG 系统</name>

    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M1</spring-ai.version>
    </properties>

    <dependencies>
        <!-- Spring Boot 核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring AI 核心 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-dashscope</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>

        <!-- PDF 解析 -->
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>3.0.1</version>
        </dependency>

        <!-- 实体提取辅助 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>0.35.0</version>
        </dependency>

        <!-- 数据库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

        <!-- 工具类 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

应用配置文件 (application.yml)

spring:
  application:
    name: ecommerce-rag-service

  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen-max
          temperature: 0.7
      embedding:
        options:
          model: text-embedding-v2

    vectorstore:
      pgvector:
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 1536  # text-embedding-v2 的维度

  datasource:
    url: jdbc:postgresql://localhost:5432/ecommerce_rag
    username: postgres
    password: ${DB_PASSWORD}

  jpa:
    hibernate:
      ddl-auto: update

# 自定义配置
rag:
  document:
    upload-dir: /data/documents
    supported-types: pdf,docx,txt
    chunk-size: 500      # 文本分块大小(字符)
    chunk-overlap: 50    # 分块重叠(减少上下文丢失)

  retrieval:
    top-k: 4             # 检索最相似文档数
    similarity-threshold: 0.6  # 相似度阈值
    business-filter: true    # 启用业务过滤器

  entity:
    extraction-enabled: true
    model: qwen-turbo    # 实体提取使用轻量模型

四、核心模块一:文档解析与向量化

1. PDF 文档读取与章节分割
我们使用 PDFBox 进行文本提取,并特别针对电商政策文档的章节结构(如“第 X 章”)进行智能分割,以提升后续检索的精度。

package com.example.ecommercerag.reader;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.Metadata;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Component
@Slf4j
public class PdfDocumentReader {
    public List<Document> readByChapter(File pdfFile) throws IOException {
        List<Document> chapters = new ArrayList<>();
        try (PDDocument document = Loader.loadPDF(pdfFile)) {
            PDFTextStripper stripper = new PDFTextStripper();
            String fullText = stripper.getText(document);
            // 按章节标题分割(匹配"第 X 章"、"X."等模式)
            String[] chapterPatterns = {
                "(?m)^第 [一二三四五六七八九十]+章.*$",
                "(?m)^\\d+\\..*$",
                "(?m)^[A-Z]\\..*$"
            };
            List<String> chapterTexts = splitByPattern(fullText, chapterPatterns);
            for (int i = 0; i < chapterTexts.size(); i++) {
                String chapterText = chapterTexts.get(i);
                if (chapterText.trim().length() < 50) {  // 过滤过短章节
                    continue;
                }
                Metadata metadata = Metadata.builder()
                        .with("filename", pdfFile.getName())
                        .with("chapterIndex", i + 1)
                        .with("documentType", "ecommerce_policy")
                        .with("documentId", UUID.randomUUID().toString())
                        .build();
                chapters.add(new Document(chapterText, metadata));
            }
        }
        log.info("解析完成,共提取 {} 个章节", chapters.size());
        return chapters;
    }
    private List<String> splitByPattern(String text, String[] patterns) {
        // 实现细节:根据正则表达式模式切分文本
        // ... (代码省略)
    }
}

2. 向量化存储服务
文本分块后,使用 Spring AI 的 EmbeddingModel 和 VectorStore 接口,将文档块转换为向量并存入数据库/中间件/技术栈(如 pgvector)。

package com.example.ecommercerag.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
public class DocumentEmbeddingService {
    private final EmbeddingModel embeddingModel;
    private final VectorStore vectorStore;
    private final EcommerceTextSplitter textSplitter;

    @Transactional
    public void ingestDocument(Document document) {
        log.info("开始文档向量化入库:{}", document.getMetadata().get("filename"));
        // 1. 文本分块
        List<Document> chunks = textSplitter.split(document);
        log.info("文档分块完成,共 {} 个块", chunks.size());
        // 2. 存入向量数据库
        vectorStore.add(chunks);
        log.info("文档入库完成");
    }
}

五、核心模块二:双模式检索策略

针对不同复杂度的问题,我们设计了两种检索模式:

  • 模式一:精准条款匹配。适用于查询明确政策条款,如“7天无理由退货的条件是什么?”。直接进行向量相似度检索,返回最相关的 Top-K 条款,延迟低。
  • 模式二:复杂场景增强。适用于多条件组合场景,如“双11买的口红拆封能退吗?”。先提取实体(商品、时间、诉求),再基于实体信息进行多轮检索增强,最后交由大模型推理生成带溯源的答案。

复杂场景增强 Advisor 实现

package com.example.ecommercerag.advisor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.Message;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
@Slf4j
public class EcommerceAugmentationAdvisor implements RetrievalAugmentationAdvisor {
    private final VectorStore vectorStore;
    private final EntityExtractionService entityExtractor;

    private static final String AUGMENTATION_PROMPT = """
        你是一名资深电商客服专家,请根据检索到的知识库内容回答用户问题。
        【回答规范】
        1. 答案必须基于检索内容,禁止编造
        2. 标注信息来源(文档名 + 章节)
        3. 如信息不足,明确告知并建议人工客服
        4. 语气专业且友好
        【检索结果】
        {retrieved_documents}
        【用户问题】
        {user_question}
        【提取的实体】
        {extracted_entities}
        """;

    @Override
    public List<Message> augment(List<Message> messages) {
        String userQuestion = extractUserQuestion(messages);
        // 1. 实体提取
        EcommerceEntities entities = entityExtractor.extract(userQuestion);
        log.info("提取实体:{}", entities);
        // 2. 构建增强检索查询
        String enhancedQuery = buildEnhancedQuery(userQuestion, entities);
        // 3. 执行检索
        List<Document> retrievedDocs = retrieveDocuments(enhancedQuery, entities);
        // 4. 构建增强后的提示词
        String context = formatRetrievedDocuments(retrievedDocs);
        String augmentedPrompt = AUGMENTATION_PROMPT
                .replace("{retrieved_documents}", context)
                .replace("{user_question}", userQuestion)
                .replace("{extracted_entities}", entities.toString());
        return List.of(new Message(augmentedPrompt));
    }
    // ... (其他辅助方法)
}

六、核心模块三:电商领域实体提取

实体定义与提取服务
我们定义了 EcommerceEntities 类来封装商品类型、购买时间、诉求、地区、商品状态等关键信息。实体提取服务利用大模型的少样本学习(Few-shot)能力,从用户问题中精准抽取这些信息。

package com.example.ecommercerag.service;
import com.example.ecommercerag.entity.EcommerceEntities;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class EntityExtractionService {
    private final ChatClient chatClient;
    private final ObjectMapper objectMapper;

    private static final String EXTRACTION_PROMPT = """
        请从以下用户问题中提取电商领域实体,以 JSON 格式返回。
        【提取字段】
        {
          "productType": "商品类型(如口红、手机、生鲜)",
          "category": "商品分类 (PERISHABLE/COSMETICS/DIGITAL/FURNITURE/VIRTUAL/CUSTOMIZED/GENERAL)",
          "purchaseTime": "购买时间(如双 11、618、昨天)",
          "requestType": "需求类型(如退货、换货、价保、改地址)",
          "region": "地区(如新疆、西藏、港澳台)",
          "orderStatus": "订单状态(未发货/已发货/已签收)",
          "productCondition": "商品状态(未拆封/已拆封/已使用/有质量问题)"
        }
        【要求】
        1. 只返回 JSON,不要其他内容
        2. 无法确定的字段填 null
        3. category 必须从枚举值中选择
        【用户问题】
        {user_question}
        【JSON 输出】
        """;

    public EcommerceEntities extract(String userQuestion) {
        try {
            String prompt = EXTRACTION_PROMPT.replace("{user_question}", userQuestion);
            String response = chatClient.prompt().user(prompt).call().content();
            // 清理响应(去除可能的 markdown 标记)
            response = cleanJsonResponse(response);
            return objectMapper.readValue(response, EcommerceEntities.class);
        } catch (Exception e) {
            log.error("实体提取失败:{}", userQuestion, e);
            return EcommerceEntities.builder().build();
        }
    }
}

七、核心模块四:业务过滤器与答案溯源

1. 非业务问题过滤
为了避免系统资源被“今天天气怎么样?”这类无关问题消耗,我们引入了一个轻量级过滤器。它使用一个简化的 LLM 调用,判断问题是否属于电商业务范畴。

2. 答案溯源
这是建立用户信任的关键。我们设计了 CitedAnswer 类,在返回答案的同时,附上其所依据的文档名称、具体章节、原文片段以及相似度得分。

package com.example.ecommercerag.service;
import lombok.Builder;
import lombok.Data;
import org.springframework.ai.document.Document;
import java.util.List;

@Data
@Builder
public class CitedAnswer {
    private String answer;
    private List<Citation> citations;
    private double confidence;
    private boolean suggestHumanAgent;

    @Data
    @Builder
    public static class Citation {
        private String documentName;
        private String section;
        private String originalText;
        private double similarityScore;
    }

    public String formatWithCitations() {
        StringBuilder sb = new StringBuilder();
        sb.append(answer).append("\n\n");
        if (citations != null && !citations.isEmpty()) {
            sb.append("📄 **信息来源**:\n");
            for (int i = 0; i < citations.size(); i++) {
                Citation cite = citations.get(i);
                sb.append(String.format("[%d] 《%s》- %s (相似度:%.0f%%)\n",
                        i + 1,
                        cite.getDocumentName(),
                        cite.getSection(),
                        cite.getSimilarityScore() * 100));
            }
        }
        if (suggestHumanAgent) {
            sb.append("\n⚠️ 以上信息仅供参考,如需进一步帮助请联系人工客服");
        }
        return sb.toString();
    }
}

八、服务集成与 API 暴露

RAG 服务主入口
EcommerceRagService 作为统一入口,集成了前述所有模块。其工作流程为:业务过滤 → 实体提取 → 问题分类(选择检索模式)→ 调用对应 Advisor 获取答案 → 构建带溯源的 CitedAnswer 返回。

REST API 设计

package com.example.ecommercerag.controller;
import com.example.ecommercerag.service.CitedAnswer;
import com.example.ecommercerag.service.EcommerceRagService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/rag")
@RequiredArgsConstructor
@CrossOrigin(origins = "*")
public class EcommerceRagController {
    private final EcommerceRagService ragService;

    @PostMapping("/chat")
    public ResponseEntity<CitedAnswer> chat(@RequestBody ChatRequest request) {
        CitedAnswer answer = ragService.answer(request.getQuestion());
        return ResponseEntity.ok(answer);
    }

    @Data
    public static class ChatRequest {
        private String question;
    }
}

API 调用与响应示例

curl -X POST http://localhost:8080/api/v1/rag/chat \
  -H "Content-Type: application/json" \
  -d '{
    "question": "双 11 买的口红拆封了,能退货吗?"
  }'

响应将是一个结构化的 JSON,包含了带引用的答案、置信度以及是否建议转人工的标识。

九、生产环境考量

1. 性能优化与缓存
对高频、通用的政策问答(如“退货政策”),可以使用 Redis 对问题和答案进行哈希缓存,显著降低响应延迟和 LLM 调用成本。

2. 容器化部署
使用 Docker Compose 可以轻松部署整个服务栈。

# docker-compose.yml 示例
version: '3.8'
services:
  rag-service:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
      - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/ecommerce_rag
    depends_on:
      - postgres

  postgres:
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_DB=ecommerce_rag
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data

十、总结与展望

本文通过一个完整的电商客服案例,详细拆解了基于 Spring AI 构建 RAG 系统的全流程。核心技术点包括:面向领域的文档解析与分块、双模式自适应检索、基于 LLM 的精准实体提取以及增强可信度的答案溯源机制。

这套方案的优势在于其开箱即用深度集成。Spring AI 抽象了向量存储、大模型调用等复杂细节,让开发者能更专注于业务逻辑的实现。

后续可深入探索的方向:

  1. 多模态检索:结合商品图片进行视觉+文本的跨模态检索。
  2. 主动学习与反馈闭环:收集用户对答案的满意度(如点赞/点踩),用于持续优化检索策略和提示词。
  3. 知识图谱融合:将离散的政策条款构建成关联的知识图谱,支持更复杂的逻辑推理查询。
  4. 流式输出与渐进式答案呈现:改善用户体验。

本实战项目展示了 Spring AI 在构建企业级智能应用方面的强大潜力。随着 Spring 生态在Java开发者社区的广泛普及,Spring Boot与 AI 的结合无疑会催生出更多创新的应用场景。希望本文能为你在云栈社区的技术探索之路提供有价值的参考。项目完整代码可根据文中配置自行构建与测试。




上一篇:深入解析Linux内核页面迁移:从系统调用到底层实现原理
下一篇:干货解析:开源AI教学平台OpenMAIC如何实现40%完课率,重塑个性化学习
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-18 07:45 , Processed in 0.475949 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表