实战客服 RAG 01:搭建项目骨架和知识源

先理解:为什么第一篇不急着写检索

客服 RAG 的第一步不是选模型,而是把边界定清楚:它回答什么、拒绝什么、从哪里找答案、答错以后谁负责。很多 RAG Demo 看起来很快,是因为它绕过了这些问题;真实上线时,问题会集中爆发在知识来源不清、风险边界不清、字段格式不统一上。

本篇的目标是打地基。你会先准备 FAQ 和政策文档,再定义 ChunkAnswer 两个核心数据结构。后续入库、检索、API、评测都会围绕这两个结构展开。这样做的好处是:你以后替换向量库、换 Embedding 模型、接入 LLM,都不需要推翻整个项目。

这一步做错会发生什么

如果一开始没有统一 Schema,后面常见的问题是:入库脚本里叫 source,API 里叫 doc_id,评测脚本里又叫 reference。项目越写越乱,最后每次改字段都要全局搜索。

如果一开始没有写风险边界,客服机器人很容易回答“可以退款”“一定换新”这类高风险承诺。技术上看只是生成了一句话,业务上可能就是一次投诉。

本篇完成后的判断标准

你不需要得到一个智能机器人,只需要得到一个可继续扩展的项目骨架。判断标准是:原始知识放在哪里很清楚,索引将来放在哪里很清楚,业务对象字段含义很清楚。

这个系列会做出一个最小可用客服 RAG:知识入库、向量检索、带引用回答、拒答/转人工、离线评测、FastAPI 接口。

本篇成品

text
customer-rag/
  data/raw/faq.csv
  data/raw/policy.md
  src/config.py
  src/schema.py
  requirements.txt

初始化

bash
mkdir -p customer-rag/data/raw customer-rag/src
cd customer-rag
python -m venv .venv
source .venv/bin/activate

requirements.txt

txt
fastapi==0.115.0
uvicorn==0.30.6
pydantic==2.8.2
chromadb==0.5.5
sentence-transformers==3.0.1
python-dotenv==1.0.1
bash
pip install -r requirements.txt

准备 FAQ

data/raw/faq.csv

csv
question,answer,product,doc_type,effective_from
耳机进水能保修吗,液体浸入通常不属于免费保修范围,需要售后检测后确认维修方案,headphone,faq,2026-04-01
发票怎么开,订单完成后可在订单详情页申请电子发票,all,faq,2026-04-01
超过七天还能退货吗,超过七天通常不支持无理由退货,如存在质量问题可申请售后检测,all,faq,2026-04-01

准备政策

data/raw/policy.md

md
# 售后政策 2026.04

## 七天无理由退货
用户签收商品后 7 天内,商品未拆封、未损坏、配件齐全,可申请无理由退货。

## 免费保修边界
非人为性能故障,在保修期内可申请免费检测和维修。液体浸入、摔落、私自拆修不属于免费保修范围。

## 高风险承诺
客服机器人不得承诺一定退款、一定换新、一定赔偿。涉及金额补偿的问题必须转人工。

配置和 Schema

src/config.py

python
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parents[1]
RAW_DIR = BASE_DIR / "data" / "raw"
INDEX_DIR = BASE_DIR / "data" / "index"
COLLECTION_NAME = "customer_support"
EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"

src/schema.py

python
from pydantic import BaseModel

class Chunk(BaseModel):
    id: str
    text: str
    source: str
    product: str = "all"
    doc_type: str
    effective_from: str
    risk_level: str = "low"

class Answer(BaseModel):
    answer: str
    sources: list[str]
    handoff: bool
    reason: str | None = None

验收

bash
python - <<'PY'
from src.config import RAW_DIR
from src.schema import Chunk
print(RAW_DIR.exists())
print(Chunk(id="1", text="ok", source="faq", doc_type="faq", effective_from="2026-04-01"))
PY

看到 TrueChunk(...) 即可进入下一篇。

实战客服 RAG 02:清洗、分块并写入向量库
MCP 生态全景:社区 Server、最佳实践和下一步