99 lines
6.2 KiB
Markdown
99 lines
6.2 KiB
Markdown
---
|
||
id: scenario.customer-service-kb
|
||
title: 客服知识库问答:从文档接入到带引用的流式回答、权限、评测和转人工
|
||
type: scenario
|
||
domain: cross
|
||
stack: [rag, vector-db, elasticsearch, fastapi, vue, redis, data-pipeline, agent-ops]
|
||
tags: [RAG, 混合检索, 权限, 流式输出, 评测, 转人工, 引用]
|
||
level: intermediate
|
||
prerequisites: [ai.rag.index, python.fastapi.index]
|
||
est_hours: 3
|
||
related: [ai.rag.scenario.permission-aware-kb, backend.elasticsearch.scenario.kb-search-sync, frontend.vue.scenario.ai-chat-stream, ai.agent-ops.scenario.agent-quality-gate]
|
||
week: [5, 6, 13, 15]
|
||
---
|
||
|
||
# 客服知识库问答:从文档接入到带引用的流式回答、权限、评测和转人工
|
||
|
||
> 跨技术栈的综合场景,串联前端、后端、AI 和运维。BOSS直聘 2026-10-23 的调研中,Agent(22 个岗位)、RAG(17 个)是出现最多的要求,这是最常见的落地形态,也适合作为作品集项目。
|
||
|
||
## 业务背景与问题
|
||
|
||
一家 SaaS 公司的客服每天处理大量重复问题(退货、发票、账号、错误码)。要做一个 AI 客服:
|
||
|
||
1. 回答必须基于公司文档,不能编造,要给出引用来源;
|
||
2. 不同客户(租户)、不同部门的文档权限不同;
|
||
3. 文档经常更新,更新后很快生效;
|
||
4. 回答不了或用户不满意时转人工,并把上下文交给坐席;
|
||
5. 需要证明效果:上线前和每次改动后都要有数据。
|
||
|
||
## 约束
|
||
|
||
- 首 token 延迟 2 秒以内,回答流式输出。
|
||
- 模型调用有成本预算。
|
||
- 数据不能出公司(或只能使用指定的模型服务)。
|
||
- 团队:1 名前端、1 名后端、你(全栈 + AI)。
|
||
|
||
## 方案设计
|
||
|
||
| 层 | 方案 | 专题 |
|
||
| --- | --- | --- |
|
||
| 文档接入 | 连接器增量拉取 → 解析(PDF / Word / 网页,表格单独处理)→ 按标题层级分块 → 嵌入 → 写入;outbox + 队列 + 幂等消费者,每日对账 | [数据管道](../topics/backend/data-pipeline/index.md)、[RAG 切分](../topics/ai/rag/01-pipeline-chunking.md) |
|
||
| 检索 | ES BM25 + 向量 kNN 两路召回,RRF 融合,重排取前 5;租户和部门权限作为 filter | [ES 混合检索](../topics/backend/elasticsearch/scenarios/kb-search-sync.md)、[权限感知知识库](../topics/ai/rag/scenarios/permission-aware-kb.md) |
|
||
| 生成 | 只依据资料回答、资料不足时拒答,输出带引用编号;结构化输出(答案、引用、置信度、是否建议转人工) | [提示词与结构化输出](../topics/ai/prompt-engineering/index.md) |
|
||
| 接口 | FastAPI SSE 流式接口,事件:`citations`、`token`、`done`、`error`;断开时取消生成 | [FastAPI 流式接口](../topics/python/fastapi/scenarios/streaming-chat-api.md) |
|
||
| 前端 | Vue 对话组件,增量渲染 Markdown(安全清洗),引用可点击看原文,“没解决”按钮转人工 | [Vue AI 对话流](../topics/frontend/vue/scenarios/ai-chat-stream.md)、[AI 应用安全](../topics/cs/web-security/scenarios/ai-app-security.md) |
|
||
| 缓存与限流 | 通用问题语义缓存(按租户隔离、文档更新时失效);按用户限流 | [Redis](../topics/backend/redis/index.md) |
|
||
| 转人工 | 置信度低 / 用户点击 / 反复追问时创建工单,附对话摘要和检索资料 | [工单系统](ticket-system.md) |
|
||
| 评测与监控 | 标注集(问题 → 应引用文档 → 参考答案),CI 中跑召回率@K、引用正确率、拒答正确率;线上记录 Bad Case、成本、延迟 | [Agent 质量门禁](../topics/ai/agent-ops/scenarios/agent-quality-gate.md) |
|
||
|
||
## 关键代码
|
||
|
||
接口骨架(示意;完整实现见第 5~6 周项目和各专题示例):
|
||
|
||
```python
|
||
@app.post("/chat")
|
||
async def chat(req: ChatRequest, user: User = Depends(current_user)):
|
||
async def events():
|
||
hits = await retrieve(req.question, acl=user.acl, k=20) # 两路召回 + 权限过滤
|
||
top = await rerank(req.question, hits, k=5)
|
||
if not top or top[0].score < MIN_SCORE:
|
||
await create_ticket(user, req, reason="no_context")
|
||
yield sse("done", {"answer": "抱歉,资料中没有找到相关内容,已为你转人工。", "handoff": True})
|
||
return
|
||
yield sse("citations", [c.public() for c in top]) # 先发引用,前端先展示来源
|
||
async for token in llm.stream(build_prompt(req.question, top, history=req.history)):
|
||
yield sse("token", {"text": token})
|
||
yield sse("done", {"handoff": False})
|
||
return StreamingResponse(events(), media_type="text/event-stream")
|
||
```
|
||
|
||
评测门禁(CI 中运行,比基线差则失败):
|
||
|
||
```python
|
||
report = evaluate(dataset="eval/kb_v3.jsonl", retriever=retrieve, generator=answer)
|
||
assert report.recall_at_5 >= baseline.recall_at_5
|
||
assert report.citation_accuracy >= 0.9
|
||
assert report.refusal_accuracy >= baseline.refusal_accuracy # 该拒答的要拒答
|
||
```
|
||
|
||
## 取舍
|
||
|
||
| 决策 | 选择 | 放弃的 | 原因 |
|
||
| --- | --- | --- | --- |
|
||
| 检索 | 混合检索 + 重排 | 只用向量 | 错误码、型号等精确词召回差 |
|
||
| 框架 | 主流程手写,部分组件用 LangChain | 全部用框架 | 便于调试和讲清原理 |
|
||
| 低代码 | Dify 做 POC,正式版写代码 | 直接上线 Dify | 权限、评测、定制 UI 受限,见 [POC 到生产](../topics/ai/lowcode/scenarios/poc-to-production.md) |
|
||
| 缓存 | 只缓存无个人信息的通用问题 | 所有问题都缓存 | 防止串数据 |
|
||
|
||
## 易错点
|
||
|
||
- 没有评测集,靠感觉调参。
|
||
- 权限只在检索后过滤,导致结果不足或泄露。
|
||
- 文档删除、权限变更没有同步。
|
||
- 回答渲染成 HTML 时没有清洗。
|
||
- 转人工时没有带上下文,用户要从头再说一遍。
|
||
|
||
## 面试怎么讲
|
||
|
||
> “这是我做的客服知识库问答。文档通过增量同步进 ES,同时存文本和向量;查询时 BM25 和向量两路召回、RRF 融合、重排取前 5,租户和部门权限作为 filter。生成只依据资料、带引用编号,资料不足就拒答并转人工。接口是 FastAPI 的 SSE,先发引用再流式发答案。我维护了一份评测集,CI 里跑召回率、引用正确率和拒答正确率,任何改动比基线差就不合并。”(数字请用你自己项目的实测结果。)
|