从“能跑”到“能用”:我在智能客服 RAG 项目中踩过的两个生产级问题
从“能跑”到“能用”:我在智能客服 RAG 项目中踩过的两个生产级问题
最近在做一个智能客服 RAG 项目时,我发现很多网上的 Demo 都停留在“能跑通”的阶段,但真正落地到业务场景后,会遇到很多生产环境问题。
比如:
- LLM 服务突然 503,整个客服系统直接不可用
- 知识库文档更新后,向量库还是旧数据
- 删除 PDF 后,ChromaDB 里仍然残留旧向量
- 重复导入知识库后,向量数据越来越乱
这些问题在本地 Demo 里不明显,但一旦进入真实业务环境,就会直接影响用户体验。
这篇文章记录一下我在智能客服 RAG 项目里做的两个比较有价值的优化:
- LLM Fallback 兜底机制
- 知识库增量管理与文件同步机制
项目技术栈:
- LangChain
- ChromaDB
- DeepSeek
- 通义千问
- MinerU
- Python
一、LLM 服务不稳定?我给 RAG 做了 Fallback 兜底机制
1. 问题背景
一开始项目只接了 DeepSeek。
本地测试没问题,但实际调用时,经常会出现:
503 Service Unavailable
Service is too busy
特别是高峰期,模型接口不稳定。
如果整个系统只依赖一个 LLM,那么:
主模型挂了 = 整个客服系统挂了
这是生产环境里绝对不能接受的。
2. 我的解决方案:Fallback 兜底机制
核心思想很简单:
主模型失败
↓
自动切换备用模型
↓
保证服务不中断
整体架构:
用户请求
↓
┌─────────────────────┐
│ 主模型 DeepSeek │
└──────────┬──────────┘
│ 失败
↓
┌─────────────────────┐
│ 备用模型 通义千问 │
└──────────┬──────────┘
│ 成功
↓
返回结果
3. 核心代码实现
核心逻辑其实就是:
- try 主模型
- except 捕获异常
- 自动切换备用模型
代码如下:
def chat(messages: list, temperature: float = 0.7) -> str:
"""
与 LLM 对话(支持 Fallback 机制)
"""
try:
# 主模型
primary_llm = get_llm(
temperature,
provider="primary"
)
result = _call_llm(primary_llm, messages)
print("[LLM] 主模型调用成功")
return result
except Exception as e:
print(f"[LLM] 主模型失败: {e}")
# 未开启 fallback
if not LLM_FALLBACK_ENABLED:
raise
print("[LLM] 触发 Fallback")
try:
# 备用模型
fallback_llm = get_llm(
temperature,
provider="fallback"
)
result = _call_llm(
fallback_llm,
messages
)
print("[LLM] 备用模型调用成功")
return result
except Exception:
raise Exception("主备模型全部失败")
4. 这里面真正重要的,不是 try-catch
很多人会觉得:
“这不就是 try-catch 吗?”
但真正重要的是:
生产级服务降级思维
因为:
服务降级 > 服务崩溃
即使备用模型:
- 更慢
- 更贵
- 效果稍差
也比:
系统完全不可用
强得多。
5. 我还做了哪些生产级优化?
(1)配置化管理
LLM_FALLBACK_ENABLED = True
LLM_PRIMARY_TIMEOUT = 30
LLM_FALLBACK_TIMEOUT = 60
这样可以动态控制:
- 是否开启兜底
- 超时时间
- 主备模型策略
(2)调用状态追踪
{
"provider": "fallback",
"model": "qwen-plus",
"fallback_triggered": True
}
方便:
- 日志监控
- 故障排查
- 分析模型稳定性
(3)预留熔断机制
后面还可以继续扩展:
连续失败 N 次
↓
暂停主模型调用
↓
定时恢复检测
这其实已经接近微服务里的:
- 熔断
- 降级
- 高可用设计
了。
二、知识库最难的,其实不是“导入”,而是“同步”
1. 我发现网上很多 RAG Demo 都有一个问题
很多教程都是:
python import.py
然后:
PDF → 向量化 → ChromaDB
看起来没问题。
但真实项目里会出现:
- PDF 更新了怎么办?
- PDF 删除了怎么办?
- 重复导入怎么办?
- 多目录怎么办?
- 同名文件怎么办?
如果不处理:
向量库会越来越脏
2. 我最后采用了“声明式同步”思路
我把整个知识库管理,类比成 Git 工作流。
3. 整体架构设计
data/raw/
↓
data/processed/
↓
ChromaDB
对应关系:
| 模块 | 类比 |
|---|---|
| raw | Git 工作目录 |
| processed | 暂存区 |
| ChromaDB | 仓库 |
| import_record.json | 提交记录 |
用户只需要在工作区放PDF,然后点击一下,系统自动同步。
三、我实现了四种状态自动检测
1. 新增文件
PDF 存在
JSON 无记录
处理:
解析 → 向量化 → 写入记录
2. 文件更新
通过:
文件修改时间(mtime)
自动检测。
代码:
def is_file_updated(filename, file_path):
current_mtime = get_file_mtime(file_path)
record_mtime = f.get("file_mtime", "")
return current_mtime > record_mtime
如果文件被替换:
删除旧向量
重新导入
3. 文件删除
如果:
JSON 有记录
但 PDF 不存在
系统自动:
删除 Markdown
删除向量
删除记录
4. 无变化
直接跳过。
这样整个知识库:
只处理变化部分
不需要每次全量重建。
四、我重点解决了“幂等性”问题
这是很多 RAG 项目容易忽略的。
什么叫幂等性?
简单理解:
重复运行结果一致
比如:
python import_knowledge.py
python import_knowledge.py
python import_knowledge.py
不会:
- 重复向量
- 重复 chunk
- 数据污染
我的解决方案
每次导入前:
collection.delete(
where={"source": source}
)
先删旧数据。
再插入:
collection.add(...)
这样:
永远只有一份最新数据
五、我还解决了“同名文件冲突”问题
很多知识库系统都有:
同名文件覆盖
问题。
比如:
实验一/说明书.pdf
实验二/说明书.pdf
我的解决方案
source 使用:
完整相对路径
例如:
实验一/说明书/说明书.md
chunk id:
ids = [
f"{source_name}_chunk_{i:04d}"
]
这样:
全局唯一
不会冲突。
六、最后的效果
现在整个知识库管理已经变成:
用户只负责放文件
系统自动同步
支持:
- 新增
- 更新
- 删除
- 重试失败文件
- 全量重建
- 增量同步
命令:
python import_knowledge.py
python import_knowledge.py --force
python import_knowledge.py --reimport "文件名"
python import_knowledge.py --reimport-failed
python import_knowledge.py --status
七、我对 RAG 项目最大的一个理解变化
以前做 RAG:
重点一直放在:
- Prompt
- Embedding
- Chunk
- Recall
但真正做项目后发现:
工程化能力
才是真正拉开差距的地方
因为:
Demo 能跑 ≠ 系统能用
真正上线后:
- 高可用
- 数据同步
- 幂等性
- 熔断降级
- 可观测性
这些问题才是核心。
八、总结
这次项目里,我主要做了两件事:
1. LLM Fallback 机制
解决:
模型服务不稳定
实现:
主模型失败自动切换备用模型
核心价值:
服务降级,而不是服务崩溃
2. 知识库增量同步机制
解决:
知识库数据混乱
实现:
自动检测新增/更新/删除
核心价值:
让知识库真正可维护
九、后续优化方向
目前还准备继续优化:
- 多级 Fallback
- 熔断恢复机制
- 混合检索(BM25 + 向量)
- Rerank
- 知识库版本管理
- Agent 化客服系统
- LangGraph 工作流
如果你也在做 RAG 项目,希望这篇文章能给你一些工程化方向上的参考。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)