每年的双十一、618 大促期间,电商平台的客服系统面临着前所未有的挑战。凌晨零点秒杀开启的瞬间,并发咨询量可能在几分钟内从几百飙升至数万,传统的人工客服团队根本无法承接。更糟糕的是,促销规则复杂多变——满减叠加、限时折扣、会员专享价、新用户礼包——客服人员需要实时记忆数十种活动细节,响应速度和准确率都难以保证。

本文将从一个真实的电商场景出发,手把手教你搭建一套完整的 RAG(Retrieval-Augmented Generation)系统,实现基于商品知识库和促销规则的智能问答。整个流程涵盖:文档解析、文本向量化、向量检索、大模型生成四大核心环节,并深度集成 HolySheep AI 的 Embedding 和 Chat API,国内直连延迟低于 50ms,汇率仅 ¥7.3=$1,远低于官方定价。

一、RAG 系统整体架构

在开始编码之前,我们需要先明确整个系统的数据流向:

二、环境准备与依赖安装

pip install openai tiktoken faiss-cpu pypdf python-docx langchain pydantic

我们选择 Faiss 作为向量数据库(支持 CPU 快速检索),LangChain 负责文本分割和 Prompt 组装。核心依赖只需要两个 API 客户端,均通过 HolySheep AI 提供,无需魔法上网。

import os
from openai import OpenAI

HolySheep API 配置

os.environ["HOLYSHEEP_API_KEY"] = "YOUR_HOLYSHEEP_API_KEY" os.environ["HOLYSHEEP_BASE_URL"] = "https://api.holysheep.ai/v1"

初始化客户端

client = OpenAI( api_key=os.environ["HOLYSHEEP_API_KEY"], base_url=os.environ["HOLYSHEEP_BASE_URL"] )

三、文档解析:从多格式到纯文本

电商的促销文档通常散落在各个部门的共享盘里,格式五花八门——运营团队用 Word 撰写活动方案,技术团队用 Excel 维护商品 SKU,财务部门用 PDF 输出对账单。我们先写一个统一的解析器:

import json
from pathlib import Path
from typing import List, Union
from pypdf import PdfReader
from docx import Document

def parse_document(file_path: str) -> List[str]:
    """
    统一解析多种文档格式,返回文本段落列表
    支持: .pdf, .docx, .txt
    """
    path = Path(file_path)
    suffix = path.suffix.lower()
    
    if suffix == ".pdf":
        return _parse_pdf(file_path)
    elif suffix == ".docx":
        return _parse_docx(file_path)
    elif suffix == ".txt":
        return _parse_txt(file_path)
    else:
        raise ValueError(f"暂不支持的文件格式: {suffix}")

def _parse_pdf(file_path: str) -> List[str]:
    """解析 PDF,提取每页文本"""
    reader = PdfReader(file_path)
    paragraphs = []
    for page in reader.pages:
        text = page.extract_text()
        if text.strip():
            paragraphs.append(text.strip())
    return paragraphs

def _parse_docx(file_path: str) -> List[str]:
    """解析 Word 文档,提取段落"""
    doc = Document(file_path)
    return [para.text.strip() for para in doc.paragraphs if para.text.strip()]

def _parse_txt(file_path: str) -> List[str]:
    """解析纯文本,按行分割"""
    with open(file_path, "r", encoding="utf-8") as f:
        return [line.strip() for line in f if line.strip()]

使用示例

if __name__ == "__main__": docs = parse_document("promotion_rules_2024.pdf") print(f"解析完成,共 {len(docs)} 个段落")

四、文本分块与向量化

文档解析完成后,我们需要将长文本切分为适合检索的小块(Chunk)。分块策略直接影响检索效果:太大则引入噪声,太小则丢失上下文。LangChain 提供了多种文本分割器,这里使用 RecursiveCharacterTextSplitter,按段落、句子递归切分:

from langchain.text_splitter import RecursiveCharacterTextSplitter

def chunk_text(paragraphs: List[str], chunk_size: int = 500, chunk_overlap: int = 50) -> List[str]:
    """
    文本分块
    
    Args:
        paragraphs: 原始段落列表
        chunk_size: 每个块的目标字符数
        chunk_overlap: 块之间的重叠字符数(保留上下文连续性)
    """
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separators=["\n\n", "\n", "。", "!", "?", " "]
    )
    
    full_text = "\n\n".join(paragraphs)
    chunks = text_splitter.split_text(full_text)
    return chunks

调用 HolySheep Embedding API 获取向量

def get_embeddings(texts: List[str], model: str = "text-embedding-3-small") -> List[List[float]]: """ 批量获取文本向量(使用 HolySheep API) 价格参考(2026年): - text-embedding-3-small: $0.02/1K tokens - text-embedding-3-large: $0.13/1K tokens """ response = client.embeddings.create( model=model, input=texts ) return [item.embedding for item in response.data]

五、构建向量索引与检索

向量数据库的选择上,生产环境推荐 Milvus、Pinecone 等云服务,这里为了演示方便使用 Faiss(Facebook 开源的向量索引库,支持毫秒级召回):

import faiss
import numpy as np

class VectorStore:
    def __init__(self, dimension: int = 1536):
        self.dimension = dimension
        self.index = faiss.IndexFlatL2(dimension)  # L2 距离索引
        self.chunks = []  # 存储原始文本块
    
    def add_vectors(self, vectors: List[List[float]], texts: List[str]):
        """批量添加向量和对应文本"""
        vectors_array = np.array(vectors).astype('float32')
        self.index.add(vectors_array)
        self.chunks.extend(texts)
    
    def search(self, query_vector: List[float], top_k: int = 3) -> List[dict]:
        """
        向量相似度检索
        
        Returns:
            List[dict]: 包含 text 和 score 的结果列表
        """
        query_array = np.array([query_vector]).astype('float32')
        distances, indices = self.index.search(query_array, top_k)
        
        results = []
        for dist, idx in zip(distances[0], indices[0]):
            if idx < len(self.chunks):
                results.append({
                    "text": self.chunks[idx],
                    "score": float(dist)
                })
        return results

完整示例:构建促销知识库索引

def build_promotion_index(documents: List[str]): """构建促销规则知识库""" # 1. 分块 chunks = chunk_text(documents) print(f"分块完成: {len(chunks)} 个文本块") # 2. 向量化 embeddings = get_embeddings(chunks) print(f"向量化完成: {len(embeddings)} 个向量") # 3. 构建索引 dimension = len(embeddings[0]) store = VectorStore(dimension=dimension) store.add_vectors(embeddings, chunks) print(f"索引构建完成: {store.index.ntotal} 条记录") return store

六、检索增强生成:组装 Prompt 并调用 LLM

检索环节完成后,我们需要将召回的文档片段作为上下文,组装成结构化的 Prompt。好的 Prompt 设计能显著提升回答质量,尤其是处理促销规则这类需要精确数字的场景:

def retrieve_and_generate(query: str, vector_store: VectorStore, model: str = "gpt-4o-mini"):
    """
    RAG 核心流程:检索 + 生成
    
    Args:
        query: 用户问题
        vector_store: 向量数据库
        model: 使用的 LLM 模型
    
    Returns:
        str: 生成的答案
    """
    # 1. 将用户问题向量化
    query_embedding = get_embeddings([query])[0]
    
    # 2. 检索相关文档
    relevant_docs = vector_store.search(query_embedding, top_k=3)
    
    # 3. 组装上下文
    context = "\n\n".join([doc["text"] for doc in relevant_docs])
    
    # 4. 构建 Prompt(Few-shot 设计,提升格式一致性)
    system_prompt = """你是一个专业的电商促销客服。请根据【上下文】中的促销规则回答用户问题。

要求:
1. 只使用【上下文】中提供的信息,不要编造
2. 如果信息不足以回答,明确说明"暂无相关规则"
3. 涉及金额、折扣比例的数字必须精确
4. 回答要简洁有条理,突出关键信息
"""
    
    user_prompt = f"""【上下文】
{context}

【用户问题】
{query}

【回答】"""
    
    # 5. 调用 HolySheep Chat API 生成回答
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ],
        temperature=0.3,  # 促销规则回答需要低随机性
        max_tokens=500
    )
    
    return response.choices[0].message.content

使用示例

if __name__ == "__main__": # 模拟从 PDF 解析的促销文档 sample_docs = [ "双十一满减规则:全场商品满300减50、满500减100、满1000减250,可与店铺优惠券叠加使用", "限时折扣专区:11月11日 0:00-2:00,部分商品5折起售,每个用户限购2件", "会员专享:PLUS会员可额外领取满200减30专属券,与满减活动叠加,上不封顶" ] # 构建索引 index = build_promotion_index(sample_docs) # 测试问答 question = "我是 PLUS 会员,双十一当天买了600元的商品,能优惠多少?" answer = retrieve_and_generate(question, index) print(f"问题: {question}\n回答: {answer}")

七、性能优化:应对大促并发高峰

双十一零点洪峰对系统的并发处理能力是严峻考验。以下是几项关键优化:

from functools import lru_cache
from concurrent.futures import ThreadPoolExecutor
import asyncio

class AsyncRAGPipeline:
    """异步 RAG 流水线,支持高并发"""
    
    def __init__(self, vector_store: VectorStore):
        self.vector_store = vector_store
        self.embedding_cache = {}
    
    @lru_cache(maxsize=10000)
    def cached_embedding(self, text_hash: str, text: str) -> List[float]:
        """向量缓存:基于文本哈希去重"""
        return get_embeddings([text])[0]
    
    async def batch_retrieve(self, queries: List[str]) -> List[List[dict]]:
        """批量异步检索"""
        loop = asyncio.get_event_loop()
        with ThreadPoolExecutor(max_workers=20) as executor:
            tasks = [
                loop.run_in_executor(executor, self._sync_search, q)
                for q in queries
            ]
            results = await asyncio.gather(*tasks)
        return results
    
    def _sync_search(self, query: str) -> List[dict]:
        embedding = self.cached_embedding(hash(query), query)
        return self.vector_store.search(embedding, top_k=3)
    
    async def batch_generate(self, queries: List[str], model: str = "gpt-4o-mini") -> List[str]:
        """批量异步生成"""
        contexts = await self.batch_retrieve(queries)
        
        async def generate_one(query: str, context: List[dict]) -> str:
            prompt = f"【上下文】\n{' '.join([c['text'] for c in context])}\n\n【问题】{query}"
            response = client.chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": prompt}],
                max_tokens=300
            )
            return response.choices[0].message.content
        
        tasks = [
            generate_one(q, c) 
            for q, c in zip(queries, contexts)
        ]
        return await asyncio.gather(*tasks)

八、生产部署架构建议

单个 Python 进程难以支撑数万 QPS 的并发咨询,建议采用以下微服务架构:

九、常见报错排查

1. API Key 配置错误导致 401 Unauthorized

错误信息AuthenticationError: Incorrect API key provided

排查步骤

2. 向量维度不匹配导致 Faiss 索引报错

错误信息RuntimeError: Error adding vectors: inconsistent dimension

排查步骤

3. 模型响应超时或限流 429

错误信息RateLimitError: Rate limit reached for model

排查步骤