开源一个能自己配的 RAG 智能客服系统:装完点运行,剩下全在网页上搞定

简介: 这是一款开箱即用的智能客服RAG系统:无需改配置、不跑脚本、不用重启,全部管理操作(换模型、调参数、传知识库、切向量库)均在网页后台完成,改完即生效。纯Python+FastAPI后端,原生JS前端零依赖,MIT协议可商用

这个项目的设计目标只有一句:装完依赖点一下运行,剩下全在网页上配。

不改配置文件,不跑初始化脚本,不重启服务。换厂商、换向量库、调 RAG 参数、
上传知识库,全部在管理后台点几下完成,改完立即生效。


一、效果预览

1.1 模型配置:选厂商就自动填好

15 项厂商预设。选中厂商后 Base URL 和默认模型自动填充,协议下拉按该厂商实际
支持的协议动态生成。填完 Key 点「测试连接」,直接看到模型的真实回复。
ai_kefu_model.png

右边还有个「🔄」按钮 —— 点它会调厂商的 /models 接口,拉取你这个账号真正能用的
模型列表
,而不是让你对着文档猜模型名。拉不到就自动回退到内置推荐列表,不会卡住。

1.2 RAG 设置:参数不用猜,能预览

ai_kefu_rag.png

Top-K、相似度阈值、切分策略都在这里。关键是那个「🔍 预览切分效果」按钮 ——
选一份已上传的文档或直接粘贴文本,立刻看到会被切成多少片、每片多长、
归属哪个章节
,不用真的入库消耗 embedding 配额。

调参数这件事,能看到结果和纯靠猜,完全是两种体验。

1.3 向量库:三选一,只显示相关字段

ai_kefu_xl.png

Chroma(默认,本地文件零部署)/ Qdrant / Milvus 三选一。选中哪个就只显示它需要的
字段 —— 选 Chroma 不会让你看到一堆 Qdrant 的连接参数。

Milvus 支持 Milvus Lite:Base URI 填个 .db 文件路径就能跑,不用装 etcd 和 minio。

1.4 客服信息:改一次,所有站点生效

ai_kefu_config.png

名称、头像、欢迎语、联系方式。这里改完,所有已经嵌入到各个网站的组件都会同步 ——
因为组件启动时会自己来读这份配置,不需要你去改每个站点的嵌入代码。

1.5 聊天预览:后台里直接测

ai_kefu_view.png

配完不用切页面,就在后台里问。答案里的 Markdown 会正常渲染(加粗、列表、代码块),
文章链接是可点击的超链接而不是一串纯文本。

1.6 嵌入指南:三种方式,一键复制

ai_kefu_jieru.png

<script> 标签 / iframe / 小程序 web-view,代码直接复制。

1.7 装到网站上是什么样

右下角一个浮动按钮,点开就是聊天窗口:
ai_kefu_yanshi.png

嵌入代码就这么几行:

<script src="https://你的域名/widget/customer-service.js?v=1"></script>
<script>
  CustomerService.init({
     apiUrl: 'https://你的域名' });
</script>

组件是单文件、零依赖、免构建的原生 JS,19KB。不用 npm,不用打包,
丢到任何页面都能跑。


二、源码介绍

2.1 整体结构

99 个文件 · 后端 Python 6,548 行 · 前端 2,823 行 · 24 个 API 端点

intelligent-customer-service/
├── backend/
│   ├── run.py                    ← 一键启动(PyCharm 右键 Run)
│   └── src/
│       ├── api/          (7)     admin · chat · config · documents · models · sessions
│       ├── services/     (6)     document · rag · llm · embedding · session
│       ├── adapters/     (5)     OpenAI / Anthropic 协议适配
│       ├── vector_store/ (6)     Chroma / Qdrant / Milvus
│       ├── embeddings/   (3)     向量化
│       ├── parsers/      (6)     txt · md · docx · xlsx · pdf
│       ├── prompts/              RAG 约束提示词
│       ├── core/                 配置 · 数据库 · 厂商注册表 · 异常
│       └── utils/                结构感知切分 · SSE · 哈希
└── frontend/
    ├── admin/index.html          管理后台(2,037 行,无框架)
    └── widget/customer-service.js 嵌入组件(786 行,零依赖)

2.2 多厂商适配:一个接口,两种协议

市面上的模型接口分两大派:OpenAI 风格(/chat/completions)和 Anthropic 风格
/messages)。后者把 system 放在顶层字段而不是消息列表里,流式响应的事件结构
也不一样。

BaseProvider(async + SSE)
  ├── OpenAIStyleProvider     → POST {base}/chat/completions
  └── AnthropicStyleProvider  → POST {base}/messages
build_provider() 按 protocol 字段分发

一个踩坑记录:Anthropic 官方用 x-api-key 认证,但很多"兼容 Anthropic 协议"的
国内网关实际要 Authorization: Bearer。我最初只发前者,火山方舟直接返回 401。
现在两个头都发 —— 各家忽略自己不用的那个,兼容性问题就消失了。

def _headers(self) -> dict:
    key = self.config.api_key or ""
    return {
   
        "Content-Type": "application/json",
        "x-api-key": key,                      # Anthropic 官方
        "Authorization": f"Bearer {key}",      # 兼容层网关
        "anthropic-version": self.version,
    }

2.3 结构感知切分:让检索命中的片段"知道自己是谁"

这是我花时间最多、收益也最直观的一块。

问题:常规做法是按固定字数切(比如每 500 字一刀)。这会产生两种坏片段:

  1. 一个片段横跨三个不相关章节 → 它的向量是几个主题的模糊平均值,谁都能弱匹配
  2. 一个片段从「Q3:可以接入多个模型吗」的中间开始 → 丢了【常见问题】这个上下文,
    模型不知道这段话在讲什么

做法:先按结构切块,再打包成片段,打包时不跨标题,并给每片带上标题路径。

能识别的结构:

类型 示例
CJK 方括号章节 【保修条款】
Markdown 标题 # 手册 / ## 安装(支持层级 手册 > 安装
中文章节 第一章 总则
Q&A 对 Q1:… / A:…(问答绝不分离)
Excel 工作表 ## Sheet: 价格表
PDF 页 ## Page 3

编号列表如 1. 整机保修期为 12 个月。 不会被误判成标题 ——
判断规则里排除了含句末标点的行。

实测对比(595 字的示例文档):

固定窗口 结构感知
片段数 2 6
问「保修期」Top-1 得分 0.719 0.791
Top-1 命中内容 混杂大片段 精准的【保修条款】章节

2.4 回答策略:一个开关,两种人格

默认是严格 RAG:只依据知识库回答,检索不到就明确说"抱歉,知识库中没有相关信息"。
好处是每句话都能追溯到你上传的文档。

但有时你希望它更像个通用助手。所以加了个开关:

关闭(默认) 开启
知识库有相关内容 依据资料回答 依据资料回答,不足时补全
知识库没有 明确拒答 用模型自身知识回答
可追溯性 ❌ 用户分不清来源
适用 价格、政策、承诺 通用问答、技术咨询

这里有个不那么显然的技术细节。 我最初的实现是"检索零命中时才切换到自主回答",
结果开关完全不生效 —— 因为中文 embedding 的特性是:即使内容完全无关,
相似度也有 0.65 左右
。而 similarity_threshold 必须保持低值(0.5),
否则「登录页」这种短查询会检索不到任何东西。

于是我实测了 12 个问题的分数分布:

站内问题: 0.813 0.827 0.833 0.836 0.850 0.859 0.905
站外问题: 0.649 0.665 0.680 0.702 0.711
间隔 +0.102  → 分界线取 0.76

据此引入 relevance_threshold = 0.76,区分"检索到了"和"检索到了答案"。
9 个路由测试全部正确:5 个站内问题走知识库,4 个站外问题走自主回答。

2.5 入库的可靠性:断点续传 + 自适应限流

大知识库入库会遇到两类现实问题,都不是写代码时能预料的:

① 厂商单批上限不统一。 火山方舟的 embedding 接口单次最多 10 条,
OpenAI 是 2048。写死成 10 会拖慢所有人。

做法是从错误信息里解析上限并自动降批

Embedding batch of 13 rejected by vendor; shrinking batch size 32 → 10

只探测一次,之后记住。ARK 的报错文本 max 10, got 13 会被正则提取出 10
一次到位而不用反复折半。

顺带一个反直觉的实测结果:并发调 embedding 反而慢 60 倍
(100 个片段:串行 3.4 秒 vs 并发 4 路 211 秒)。ARK 对并行请求限制极严。
所以默认改回串行,并在代码注释里写明"实测慢 60 倍,别改回去"。

② 配额是长周期累计的。 我最初的重试策略是"最多 40 次、总共等 44 秒",
但实测 ARK 的配额需要 90 秒以上才恢复 —— 所以每次入库跑完一个批次就死掉,
用户得手动点。

改成按时间预算(10 分钟)而不是按次数,等待序列 1→2→4→8→16→20…秒。
配合进度持久化,实现了断点续传:中断后再点「入库」从上次位置继续,
已完成的片段不重复消耗配额。

模拟测试(每 25 请求耗尽、静默 10 秒恢复):

1287 片段一次跑完,耗时 112s,其间 28 次 429 全部自动消化

2.6 火山方舟的计费陷阱

这个值得单独说,因为用错会花钱

ARK 有两个产品,域名相同但路径不同:

产品 Base URL 模型名 计费
Coding Plan 包月套餐 /api/plan/v3/api/plan/v1 ark-code-latest 套餐内
豆包 按量付费 /api/v3 doubao-* 另外计费

我最初"去重"时只看域名相同就合并成一项 —— 这是个会让用户多花钱的错误。
现在拆成两个独立选项,包月套餐那项的说明里明确写着
⚠ 不要改成 /api/v3 —— 那是按量付费,会另外计费

还加了单元测试断言两者的 (base_url, protocol) 不冲突,防止以后又被合并回去。

2.7 测试:37 + 17

cd backend && python -m pytest src/tests/ -q     # 37 passed
cd frontend/widget && npm test                   # 17 项全部通过

后端 37 个测试覆盖厂商注册表去重、切分策略、向量库工厂分发、回答策略路由、
版本号解析等。

前端那 17 项断言值得单独说明为什么必要。 node -c 只做语法检查,
检不出「模板字符串被内部反引号截断」这类错误 —— 那会让整个组件在运行时抛错、
完全不工作,但静态检查一片绿。

所以自检脚本在真实 DOM 里用假 SSE 驱动完整渲染路径,断言链接可点击、
Markdown 正确渲染、引用标记被清除等。

写测试时我有个习惯:故意注入 bug 验证测试真的会失败。比如把版本比较从数值
退化成字符串比较(这会让 2.0 > 10.0),测试确实报了 3 项失败;把面板高度改掉,
尺寸守卫立刻拦住。能失败的断言才是有价值的断言。


三、其他实现细节

3.1 首次运行向导

打开管理后台,如果系统没初始化,顶部会出现横幅:

⚠️ 检测到系统未完成初始化
  • 客服配置未初始化
  [🚀 一键初始化]  [🔄 重新检测]

点一下完成建表、建目录、写默认配置。这是"不用跑脚本"这个目标的兑现方式。

顺带处理了一个容易被忽略的状态:如果入库过程中服务被重启,
数据库里会留下一条永远不会完成的 processing 记录。
现在列表查询会检测 15 分钟未更新的记录并标记为可重试,
而不是让用户对着一个假的"处理中"干等。

3.2 顶部工具栏

右上角三个常驻工具:

工具 说明
🟢 后端状态 每 15 秒自检。离线时悬停看完整错误(长报错不会撑破布局)
⬆︎ 检测更新 比对 GitHub 最新 Release,有新版时按钮出现红点
☕ 打赏支持 微信 / 支付宝 / QQ 赞赏码

检测更新只检测不改代码 —— 发现新版展示更新说明和更新命令,由你决定何时执行。
自动 git pull 会覆盖本地未提交的修改,还可能因依赖变更导致服务起不来,
不适合放在一个按钮后面。

3.3 密钥处理

API Key 存在数据库里(backend/data/app.db),页面上永不回显真实值,
只显示 ****1ef5。留空表示不修改,填了才替换。

一个必须说明的局限:目前只做了 base64 混淆,不是加密
生产环境应该换成 KMS / Vault。.gitignore 已经排除了这个数据库文件 ——
这一条我实测验证过:

git check-ignore -v backend/data/app.db   # 确认被忽略

四、源码获取

4.1 仓库地址

https://github.com/vfaner/intelligent-customer-service

MIT 协议,可自由商用、二次开发。

4.2 三步跑起来

① 装依赖

git clone https://github.com/vfaner/intelligent-customer-service.git
cd intelligent-customer-service/backend
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt

② 启动

PyCharm 用户:右键 backend/run.pyRun

命令行:

python run.py

启动后会打印:

==============================================================
  🤖 IntelligentCustomerService
==============================================================
  ⚙️  管理后台   http://localhost:8000/admin/     ← 从这里开始
  🪟  组件演示   http://localhost:8000/widget/demo/
  📖  API 文档   http://localhost:8000/docs
--------------------------------------------------------------
  ⚠  尚未配置对话模型 API Key
     → 打开管理后台「模型配置」填写即可,无需改文件
==============================================================

缺依赖时会提示当前解释器对应的安装命令(避免装到别的 Python 环境去),
也可以 python run.py --install 自动装。

③ 打开管理后台配置

浏览器访问 http://localhost:8000/admin/

顺序 做什么
1️⃣ 顶部若有黄色横幅 → 点「🚀 一键初始化」
2️⃣ 「模型配置」→ 选厂商 → 填 API Key → 「🔌 测试连接」→「💾 保存」
3️⃣ 同页「🧬 向量模型」→「⬆︎ 复用对话模型的 Key/URL」→「🔌 测试并探测维度」
4️⃣ 「知识文档」→ 拖入 txt/md/docx/xlsx/pdf → 自动解析入库
5️⃣ 「聊天预览」→ 直接提问验证

全程不需要编辑任何文件。

4.3 环境要求

项目 要求
Python 3.11+(已在 3.14.5 上验证)
数据库 SQLite(默认,零配置)或 MySQL
向量库 Chroma(默认,零部署)/ Qdrant / Milvus
模型 任意一个厂商的 API Key
Node.js 仅跑前端自检时需要,运行项目不需要

不需要 GPU —— 推理都在厂商侧。

4.4 部署到服务器

仓库里有 docs/DEPLOYMENT.md,包含 Docker Compose 和
systemd 两套方案、Nginx 配置、HTTPS 证书、备份恢复、升级回滚、10 条故障排查。

三个坑值得先知道:

  1. 只有 Nginx 该暴露公网。 应用绑 127.0.0.1:8000 —— 管理后台没有内置登录,
    直接对外等于把 API Key 配置页开放给所有人。文档里给了三种保护方式,
    最推荐 SSH 端口转发(完全不对外开放)。

  2. SSE 必须在 Nginx 关闭缓冲,否则回答不会逐字出现,而是等全部生成完才一次性蹦出来:

    location /api/chat/stream {
         
        proxy_pass http://127.0.0.1:8000;
        proxy_buffering off;
        proxy_cache off;
        chunked_transfer_encoding off;
    }
    
  3. iframe 嵌入要给够尺寸400 × 636(面板 360×540 + 按钮位 76 + 边距 20),
    background: transparent。给小了面板会被裁掉,不透明就会露出一块白底。

4.5 文档

文档 内容
README.md 中文总览、页面导览、配置项参考、FAQ
README.en.md English version
docs/API.md 24 个接口的详细说明
docs/DEPLOYMENT.md 服务器部署完整步骤
docs/EMBED_GUIDE.md 嵌入网站/小程序/桌面端

相关文章
人工智能 缓存 前端开发
11396 55
人工智能 JavaScript 开发工具
4395 13
开发工具 Swift git
1756 4
人工智能 Java BI
1089 1
人工智能 JavaScript 测试技术
1817 2
Web App开发 人工智能 API
893 1
缓存 JavaScript Shell
1967 3
人工智能 JavaScript 测试技术
902 4

热门文章

最新文章