先理解:为什么第一篇不急着写检索
客服 RAG 的第一步不是选模型,而是把边界定清楚:它回答什么、拒绝什么、从哪里找答案、答错以后谁负责。很多 RAG Demo 看起来很快,是因为它绕过了这些问题;真实上线时,问题会集中爆发在知识来源不清、风险边界不清、字段格式不统一上。
本篇的目标是打地基。你会先准备 FAQ 和政策文档,再定义 Chunk 和 Answer 两个核心数据结构。后续入库、检索、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/activaterequirements.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.1bash
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看到 True 和 Chunk(...) 即可进入下一篇。

