从 0 到 1 搭建一套企业级 AI 智能客服平台:微服务 + RAG + 全链路可观测
一、为什么要自己造一套
做过客服系统的同学大概都有同感:市面上的 SaaS 客服要么黑盒、要么按坐席收费、要么 AI 能力只有"接个大模型 API"这一层。真落到企业里,问题往往卡在这几处:
- AI 回答不可控:模型张口就来,答错了没人知道是"检索没召回"还是"模型幻觉";
- 知识库只管上传,不管效果:文档传进去,检索召回率多少、哪些问题没命中,全是黑盒;
- 接入一个新行业就要改代码:电商要查物流,金融要查账,改完发版;
- 出问题定位不了:一条对话跨了 5 个服务,日志散在各处,只能靠
grep拼时间线。
所以我用 Spring Cloud + LangChain4j + Milvus 做了一套可私有化部署的智能客服平台(项目名「智语」),核心目标只有一句话:
AI 的每一步都要可配置、可回查、可降级。
下面按"看得见的部分(界面)→ 看不见的部分(架构与代码)→ 怎么跑起来"来展开。
二、核心特性一览
先把能力摊开,后面再看每块具体怎么落地。系统分五条主线:
🤖 AI 智能对话
- 基于大语言模型的智能问答,支持上下文会话管理;
- 意图识别与智能路由,自动判断用户需求;
- Function Call 工具调用,支持扩展 AI 能力;
- 多轮对话记忆管理,基于 Redis 持久化存储。
📚 知识库检索(RAG)
- 传统 FAQ 管理:FAQ 的增删改查、分类管理;
- RAG 检索增强生成:
- 支持多格式文档(PDF / TXT / DOCX / MD);
- 智能文本切片,
chunkSize与overlap可配置; - 向量化存储,Chroma 用于本地开发、Milvus 用于生产;
- 两阶段检索(向量召回 + Rerank 重排)提升精度;
- 多用户会话隔离与防幻觉机制,自定 Prompt 模板约束。
🎫 工单系统
智能工单创建与流转、状态跟踪、优先级管理及统计报表。
💬 实时通信
基于 WebSocket 的即时消息推送、在线客服聊天及连接状态管理。
👥 客户管理
完整的客户信息管理、标签与分组、客户画像及坐席账号管理。
这五条主线之上,还挂了知识图谱、多模态知识、行业包可插拔、全链路可观测这些增强能力——界面部分往下看,代码部分看第三章。
三、RAG 工作流程
RAG 是这个系统的核心链路,完整走一遍是 8 步:
① 用户提问
↓
② 文本向量化(Embedding,nomic-embed-text)
↓
③ 向量相似度检索(Chroma 开发 / Milvus 生产)
↓
④ Rerank 重排优化(BGE-Reranker 二次排序)
↓
⑤ 取回相关的文档片段
↓
⑥ 构建 Prompt(把检索到的上下文注入提示词)
↓
⑦ LLM 生成答案(qwen:7b 等)
↓
⑧ 返回用户
三个容易被忽略但决定效果的点:
- 为什么必须两步检索。纯向量召回看的是语义相近,Top-K 里经常混进"意思接近但答案不对"的片段。加一层 Rerank 交叉编码重排,把 query 和片段拼起来打分,精度提升明显,代价只是一次模型推理的延迟。
- 切片参数比模型更重要。
chunkSize太大,召回片段噪声多;太小,答案被切成两半。overlap就是为了不让关键句正好落在切口上。这两个参数在文档知识库里可配,不同行业的最优值不一样。 - 防幻觉靠约束,不靠祈祷。Prompt 模板里明确写"仅依据下列资料回答,资料未覆盖时回答未找到",再配合上一节的三态返回,模型才有"承认不知道"的出口。
四、先看图:系统长什么样
4.1 数据概览

指标卡 + 对话趋势 + 客户趋势 + 工单状态分布 + 坐席排行。
页面滚动动图:

4.2 C 端智能对话

用户侧没有侧边栏,顶栏是吉祥物+问候语,主区域是对话流。三个功能在这页闭环:
- 转人工:分「已指派坐席」与「排队中」两种态渲染,没坐席时不会借一位在线坐席来"假装接通";
- 引用来源:消息下方列出命中的知识条目(
#id 问题); - 富媒体附件:点选、拖拽、粘贴三种上传入口,图片可直接放大预览。
4.3 坐席工作台(三栏)

左边"我的会话"、中间对话、右边客户快照(客户编号、联系方式、最近订单、最近工单)。坐席不用来回切页面查客户历史,这是客服系统里最容易被低估的效率点。
4.4 知识库:问答 + 文档 + 图谱 + 多模态
问答管理(含向量化状态、未命中转问、批量向量化):

语义检索实测动图(输入问题 → 走 RAG → 返回答复与引用条目):

注意动图里返回的引用是 #1 如何退款?——回答必须能溯源到具体知识条目,这是让运营敢用 AI 的前提。
文档知识库:

知识图谱(三元组构建 + 节点/关系管理):

多模态知识(图片 / 音频 / 视频统一入库与标注):

4.5 模型管理:能力与供应商解耦

按能力(对话 / 意图识别 / 向量 / 重排 / 视觉 / 多模态)分类注册模型,每个能力可以挂不同的供应商:Ollama 本地、DashScope、DeepSeek、任意 OpenAI 兼容端点。业务代码只认"能力",不认"厂商"。
意图配置与填槽配置:


4.6 工单:列表与详情

工单详情弹窗动图(列表只放关键列,完整字段走详情接口):

4.7 开放接入:行业包可插拔

这是整套系统里设计得最"克制"的一块:内核不认识任何行业。电商包、金融包只是连接器与工具模板,开关一开就有"查物流/退款/查账"能力,关掉就退回通用客服形态。同一套代码可以同时给两家不同行业的客户用。
配套还有回调通知、卡片模板、出站安全、提示词包、接口导入、Widget 白标:


4.8 运维:日志、链路、健康
运维总览:

一条对话跨了网关、坐席服务、知识库、开放平台,所以必须有全链路视角。点"调用树"能看到服务 → 类 → 方法三层展开:

调用树还能一键复制成纯文本,贴到工单里给同事看。日志查询按 requestId / traceId 反查:

4.9 系统管理
用户、角色、菜单、租户、技能组:



五、看不见的部分:架构与关键代码
5.1 技术栈
| 层次 | 技术选型 |
|---|---|
| 后端 | Java 17、Spring Boot 3.2.0、Spring Cloud Gateway、MyBatis Plus 3.5.5 |
| 数据 | MySQL 8.0+、Redis 6.0+、Milvus(生产)/ Chroma(开发) |
| AI/ML | LangChain4j 0.32.0、Ollama、Embedding 向量化、BGE-Reranker 重排 |
| 模型 | 通义千问、ChatGLM、qwen:7b、nomic-embed-text(本地可通过 Ollama 拉起) |
| 通信 | WebSocket |
| 前端 | Vue 3.x、Vite 4.x、Element Plus、Axios、Vue Router 4.x |
5.2 微服务拓扑
| 模块 | 职责 | 端口 |
|---|---|---|
ai-cs-gateway |
网关:路由、负载均衡、鉴权、限流、请求 ID | 8080 |
ai-cs-websocket |
实时消息推送(WebSocket) | 8081 |
ai-cs-agent |
AI 对话编排:意图、填槽、Function Call、流式输出 | 8082 |
ai-cs-knowledge |
知识库:FAQ、文档切片、向量检索、RAG、图谱、多模态 | 8083 |
ai-cs-base-service |
基础域:客户、坐席、租户、权限、配置 | 8084 |
ai-cs-workorder |
工单与工作流 | 8085 |
ai-cs-open |
开放平台:连接器、工具、Webhook、卡片 | 8086 |
ai-cs-ops |
运维控制台后端 | 8087 |
ai-cs-job |
定时任务 | 8088 |
数据层分工:MySQL 存主数据,Redis 承担缓存与会话记忆,Chroma / Milvus 承担向量存储。
一次对话的典型链路(即运维页"调用树"里能展开的那条,截图内为演示数据):
gateway 校验登录态 → 转发
ai-cs-agent 加载会话上下文 → 叠加行业提示词 → 识别意图 → 填槽
ai-cs-knowledge RAG 检索命中问答第 2 条
ai-cs-open 调用查询物流工具(首包超时 → 重试 → 成功)
ai-cs-agent 调用对话模型生成 → 流式写出片段 → 落库
ai-cs-open 触发出站回调:会话进展
5.3 RAG 的三态返回,而不是"有/无"
这是我改得最值钱的一处设计。早期 semanticSearch 只返回字符串,上层无法区分"知识库里真没有"和"模型服务挂了",于是模型故障时用户看到的是"抱歉没有找到相关信息"——运营以为要补知识,实际是服务挂了。
现在固定三态:
public RagSearchResultDTO semanticSearch(String question, String tenantCode,
String sessionId, String industryPrompt) {
if (!StringUtils.hasText(question)) {
return RagSearchResultDTO.miss(MISS_REPLY);
}
String tenant = KnowledgeFaqService.normalizeTenant(tenantCode);
try {
List<MilvusHit> hits = retrieve(tenant, question);
List<KnowledgeFaq> faqs = loadPublishedFaqs(tenant, hits);
if (faqs.isEmpty()) {
// 未命中:记录问题供运营补知识,topScore 帮助判断「差一点命中」
missService.record(tenant, question, sessionId, topScore(hits));
return RagSearchResultDTO.miss(MISS_REPLY);
}
List<String> contextDocs = faqs.stream().map(this::toContextDoc).toList();
String reply = generate(question, contextDocs, industryPrompt);
return RagSearchResultDTO.hit(reply, buildCitations(hits, faqs));
} catch (Exception e) {
// 模型/向量任何一环不可用都归 UNAVAILABLE,让上层走「服务不可用」而不是「没查到」
log.warn("RAG 语义检索不可用 tenant={} question({})", tenant, maskQuestion(question), e);
return RagSearchResultDTO.unavailable("知识库检索暂不可用");
}
}
三个细节值得单独说:
- 向量命中后一定回表:
loadPublishedFaqs按 faqId 查库,只保留"启用 + 已发布"的条目。这样即使删除失败留下了脏向量,也不会答出已下线的知识。 - 未命中要落库:把问题、租户、
topScore记进cs_knowledge_miss,运营在后台能看到"哪些问题没人答",还能一键"转问"补成新问答。 - 异常不等于未命中:
UNAVAILABLE走另一套话术,并且日志里问题会脱敏(maskQuestion)。
5.4 意图识别的三级容错
意图是"配置优先、代码兜底"的典型场景:租户在后台自己加意图,不用发版。
public IntentDTO getIntent(String userMsg, String tenantCode) {
if (!StringUtils.hasText(userMsg)) return fallbackConsult();
try {
// 1. 拉租户启用的意图清单,失败返回空列表(不抛异常)
List<IntentConfigDTO> intents = loadEnabledIntents(tenantCode);
String intentList = buildIntentList(intents);
// 2. 清单非空 → 定点替换默认清单;为空 → 用默认五类模板
String prompt = StringUtils.hasText(intentList)
? buildPromptWithIntents(intentList, userMsg)
: PromptConst.fill(PromptConst.INTENT_PROMPT, userMsg);
String raw = modelRouter.chatForType(ModelTypeEnum.INTENT.getCode(), messages, null);
return normalize(raw, intents);
} catch (Exception e) {
// 模型调用或解析异常,统一降级为「咨询」并打上 llmDegraded=true
return fallbackConsult();
}
}
降级后为什么不直接退化成"闲聊":llmDegraded=true 是给上层看的信号,AiAgentService 见到它会返回"系统繁忙"。如果把"模型故障"伪装成"正常闲聊",监控和调用方就都失去感知了。
5.5 权限:两套东西,别混为一谈
前端权限分两层,一开始我混在一起,结果"菜单藏了但手敲地址能进":
- 档位(platform / admin / super):管入口隔离与粗粒度准入;
- 岗位角色码(AGENT / SUPERVISOR / KNOWLEDGE_OPS / ADMIN / SUPER_ADMIN):管"侧栏看到哪些菜单、能进哪些路径"。
一个账号可挂多个角色码,权限取并集。路由守卫里两道判断:
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) return next('/login')
if (token && to.meta.requiresAuth) {
const { ok, redirect } = checkAccess(to.path) // 档位:普通用户只能进聊天/帮助中心
if (!ok) {
if (isSuperOnlyPath(to.path)) ElMessage.warning('该功能仅超级管理员可用')
return next(redirect)
}
// 岗位角色:菜单里看不到的路径同样不放行,与侧栏共用 config/menus.js 那份矩阵
if (!canVisitPath(to.path, getRoleCodes())) {
ElMessage.warning('当前岗位没有该功能的权限')
return next(homeAfterLogin('admin'))
}
}
next()
})
关键点:菜单过滤与路由拦截必须共用同一份角色矩阵(config/menus.js),否则迟早出现"菜单里没有、但地址栏能进"的漏网之鱼。
还有一个体感细节:不同岗位登录后落地页不同。坐席落工作台、知识运营落知识库、主管与管理员落数据概览。否则低权限账号一登录就被自己的首页权限挡一次,用户会以为"登录失败"。
六、怎么跑起来
环境:JDK 17+、Maven 3.6+、MySQL 8.0+、Redis 6.0+、Node.js、Ollama,向量库 Chroma(开发)或 Milvus(生产)。
想走完整 AI 链路就得把 Ollama 和向量库拉起来;只浏览界面则不需要。
# 1. 拉代码(国内推荐 Gitee,海外可用 GitHub)
git clone https://gitee.com/huangrenhui/ai-cs-parent.git
# 或:git clone https://github.com/huangrenhui/ai-cs-parent.git
cd ai-cs-parent
# 2. 初始化数据库
mysql -uroot -p < database/init.sql
# 3. RAG 前置服务:拉模型 + 起向量库
ollama pull qwen:7b
ollama pull nomic-embed-text
docker run -d -p 8000:8000 -v ./chroma:/chroma/chroma chromadb/chroma
# 4. 编译并启动微服务
mvn clean install -DskipTests
mvn -pl ai-cs-gateway spring-boot:run # 其余服务同理,或按需逐个启动
# 5. 前端(独立工程,不在 Maven modules 里)
cd ai-cs-frontend
npm install
npm run dev # http://localhost:5173
七、部署与安全
7.1 部署架构
| 环境 | 形态 |
|---|---|
| 开发 | 本地直接跑,各服务连本机 MySQL / Redis / Chroma |
| 生产 | Nginx 负载均衡 → Gateway 集群 → 微服务集群 + 各自数据集群 |
7.2 安全设计
- 网关统一鉴权,业务服务不直接暴露;
- 密码 BCrypt 加密存储,不落明文;
- 全站 HTTPS;
- 防 SQL 注入(MyBatis Plus 参数化 + 入参校验);
- XSS / CSRF 防护。
7.3 性能与可观测
- 性能:Redis 缓存热点数据、数据库索引、HikariCP 连接池、耗时操作异步化、列表全量分页、向量检索参数调优;
- 监控:Spring Boot Actuator 暴露健康与指标,ELK 做日志分析,链路追踪(Sleuth / Zipkin),关键指标配置告警。
八、适用场景与项目亮点
8.1 用在哪儿
- 企业客服中心:替代传统 IVR + 人工的排队模式,AI 先接住 80% 的重复问题;
- 技术支持系统:把产品手册、FAQ、历史工单喂进知识库,做技术自助问答;
- 内部知识管理:企业内部文档检索,找制度、找流程不用再问同事;
- 在线教育平台:课程咨询、报名答疑、教务常见问题自动应答。
8.2 亮点
- 微服务架构:按域拆分,服务独立扩展,单点故障不扩散;
- RAG 真正落地:两阶段检索 + 可配切片 + 回表校验,不是"接个向量库就算 RAG";
- 文档版本管理:版本创建、查询、回退、基于 MD5 与文件大小的对比,以及内容去重;
- 多用户会话隔离:租户与用户维度双重隔离,会话记忆不串;
- 高可扩展:模型按能力注册,行业包与工具可插拔,加能力不改内核。
九、这套东西的边界
- 知识库检索目前以 FAQ 为主,文档切片链路有完整实现但尚未与 FAQ 双路召回合并(代码里留了明确的扩展点注释);
- 知识图谱支持三元组构建与查询,生产级图数据库(Neo4j / Nebula)是可选项,默认轻量实现;
- Flowable 流程引擎默认未启用,工单流转当前走状态机,流程编排页属于待接通的演示配置;
- AI 工具目录内置部分是前端 catalog,自定义 HTTP 工具的落库接口仍是内存桩,接生产需要补持久化;
ai-cs-ops是控制台后端,指标采集走 Micrometer + Prometheus,告警通道对接需要按企业实际情况补;- 多租户是"数据隔离 + 配置隔离",不是物理隔离,合规要求高的场景要单独评估。
把"没做的"写清楚,比把"做了的"吹得多更可信。
十、写在最后
这套系统做下来,最大的体会是:AI 客服的难点不在模型,而在"模型之外"——知识有没有召回、回答能不能溯源、出了故障怎么降级、跨服务怎么定位。
如果你也在做类似的东西,有三条经验可以直接拿走:
- 检索必须回表校验,别让脏向量答出下线知识;
- 异常与未命中必须分开,否则监控和运营都会被误导;
- 演示环境要能独立跑起来,一个 Mock 开关省下的沟通成本远超实现成本。
仓库(MIT 协议,欢迎 Star / Issue):
文档入口:docs/文档说明.md(架构、接口、数据库、部署、测试都在里面)。
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【码猿手】。

浙公网安备 33010602011771号