本文基于 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 抽象了向量存储、大模型调用等复杂细节,让开发者能更专注于业务逻辑的实现。
后续可深入探索的方向:
- 多模态检索:结合商品图片进行视觉+文本的跨模态检索。
- 主动学习与反馈闭环:收集用户对答案的满意度(如点赞/点踩),用于持续优化检索策略和提示词。
- 知识图谱融合:将离散的政策条款构建成关联的知识图谱,支持更复杂的逻辑推理查询。
- 流式输出与渐进式答案呈现:改善用户体验。
本实战项目展示了 Spring AI 在构建企业级智能应用方面的强大潜力。随着 Spring 生态在Java开发者社区的广泛普及,Spring Boot与 AI 的结合无疑会催生出更多创新的应用场景。希望本文能为你在云栈社区的技术探索之路提供有价值的参考。项目完整代码可根据文中配置自行构建与测试。