简介:一套基于大模型实现的智能客服对话系统项目,以 SpringAI 作为连接大模型与项目框架的中间件,面向希望快速上手大模型应用开发、了解集成方案的开发者与学习者。资源包以 zip 压缩形式提供,共两千零五十二个文件,容量约五十七兆,主要包含 JavaScript 前端逻辑脚本、Markdown 说明文档、Java 后端服务代码、JSON 与 XML 配置数据以及 properties、yaml、sql 等辅助文件,目录结构清晰,涵盖前端交互、后端逻辑与模型接入等多个层次,便于按模块研读。目前已有 114 人学习下载,内容覆盖前端页面、后端服务、大模型接入与对话管理、数据库脚本等,读者可据此搭建完整客服对话原型,并重点学习如何连接大模型与项目框架、如何处理多轮对话上下文、如何实现个性化服务。整体上是一份结合工程实践与理论说明的参考资源,适合用于课程设计、毕业设计或企业级客服系统的前期预研。
1. 项目概述与核心思路
1.1 为什么选这个方向
大模型在国内技术圈真正火起来之后,我一直在琢磨到底什么场景最落地。智能客服是我个人认为目前大模型技术落地性价比最高的方向之一:需求真实存在、客户付费意愿明确、效果可量化评估,而且技术栈足够深——从模型部署到向量检索再到服务架构,每一层都有值得抠的细节。
先解释下为什么传统客服方案不够用。传统基于规则或FAQ匹配的客服系统,核心问题是泛化能力差:用户问法稍微变一下,比如“我密码忘了怎么办”和“登录不上去了要怎么搞”,明明是一个意图,关键词匹配却可能完全对不上。大模型天然具备语义理解能力,可以端到端处理这类口语化、多样化表达,这也是我选择基于大模型重构智能客服的原因。
1.2 系统能做什么,适合谁参考
本文要拆解的这个系统,核心能力包括三个维度:多轮对话理解与应答、基于企业私有知识库的检索问答、以及面向真实生产环境的高并发服务支撑。简单说,就是让机器人既能听明白用户的复杂表达,又能从自己的知识库里找到正确答案,还能扛得住线上的流量压力。
这套方案适合三类人看:一是公司里正在做客服系统技术选型或重构的工程师,二是想入门大模型应用开发、特别是RAG(检索增强生成)方向的同学,三是需要给业务方展示技术落地方案的架构师。文中涉及的环境准备、模型部署、代码实现和问题排查,都是我自己从零跑通后沉淀下来的,可以直接抄作业。
2. 整体架构设计:不是简单接个API
2.1 模块划分与职责边界
整套系统我拆成了五个核心模块:接入层、对话管理模块、知识库检索模块、大模型推理引擎、以及运营管理与日志分析模块。这里重点说下为什么这么拆,因为边界划分直接决定了后续系统的可维护性。
接入层负责对接各个渠道——网页H5、小程序、企业微信、钉钉,统一转换成内部标准的消息协议。对话管理模块是大脑,负责判断当前用户意图是否需要查知识库、是否有上下文会话、是否要转人工。知识库检索模块的作用是把用户问题先在知识库里做一次向量相似度检索,把命中的相关文档片段作为上下文交给大模型。大模型推理引擎是核心执行者,负责把对话上下文、检索结果、系统提示词拼装成最终的自然语言回复。运营管理模块则收集所有对话日志,用于后续的效果评测和badcase分析。
这套设计有个直接的好处:模型可以随时换,推理引擎不依赖具体某个大模型产品;知识库可以随时更新,不用重新训练模型;接入新渠道只需要改接入层。这是我在设计之初就确定的优先级——业务侧的需求变化远比模型升级频繁,解耦是第一原则。
2.2 为什么选RAG而不是微调
这是我做技术选型时纠结最久的问题。微调适合改变模型的行为风格、注入特定领域的表达习惯,但它的缺点是:更新知识要重新训练、成本高、周期长。RAG则在每次请求时动态检索最新知识库内容,可以做到当天更新知识库、当天生效。
智能客服这个场景,知识库是高频变更的——产品新功能上线、优惠活动变化、常见问题更新,几乎每天都有。如果用微调方案,每次更新都意味着一次训练任务,成本完全不可接受;而RAG方案只需要增量更新向量库,几分钟完成。所以我的选择很明确:以RAG为知识扩展的主路径,微调只作为可选的后期优化手段。
顺带说下RAG的局限,这样大家心里有数。RAG的检索质量上限决定了最终回答质量的上限,如果文档切分不合理或者知识库里本身没有相关内容,大模型再强也答不对。所以整个项目里,知识库的构建工作量其实比模型部署还大,这个后面详细展开。
2.3 模型部署方案:本地私有化 vs API调用
大模型的部署方式,我对比了三条路线:开源模型本地部署、商用大模型API调用、以及本地部署为主+云端大模型兜底。最终落地选择了混合路线,但主导逻辑是本地为主。
本地私有化部署的好处是数据不出内网,对很多企业来说这是合规刚需——客服会话数据包含用户手机号、订单信息,直接调外部API风险很大。我用Ollama作为推理运行时部署了Qwen系列模型,选Qwen的原因是中文能力扎实、开源协议友好、生态完善,相关部署资料丰富。
硬件方面,实测8GB显存可以跑Qwen2.5-7B的量化版本,日常问答够用;如果要做更复杂的推理,建议上24GB以上显存或者用vLLM做高并发推理服务。这里有个细节:我用Ollama做单机快速验证,但生产环境一般切到vLLM部署,因为vLLM支持连续批处理和PagedAttention,吞吐量比Ollama高一个量级,具体对比我放在后面的评测部分。
3. 知识库构建与检索调优
3.1 原始数据加工:把文档变成模型能读懂的格式
这是整个项目里最磨人、但也最容易被忽视的一环。很多团队把精力全放在模型选型上,结果知识库没建好,出来的效果跟直接问大模型没什么区别。知识库加工的核心步骤是:清洗、切分、向量化。
清洗阶段要做的是去噪和规范化,比如去掉文档里的页眉页脚、特殊符号、错别字,统一编码格式。这一步看似简单,但直接影响后续切分质量。切分阶段是技术含量最高的,我之前踩过的坑是“无脑按固定字数切”——比如每500字一段,结果把一句话、一个表格或者一个逻辑单元生生劈成两半,检索时召回的都是碎片,大模型拼不出完整答案。
调整后的切分策略是:优先按章节、段落和语义边界切。对非结构化文本,用滑动窗口+重叠区间的方式保证上下文连续;对结构化数据,比如表格类的产品参数,考虑保留原格式转成Markdown表格再切。Q&A格式的FAQ文档,我按“一问一答”为单位切,而不是按字数切,这样检索时一次命中、答案完整。
3.2 向量化与检索策略
向量化就是把文本映射成高维向量。文本切好后,我使用Embedding模型对每个文档片段做向量化,存入向量数据库。向量数据库的选择上,我测试了Milvus和Chroma,前者功能强但部署偏重,后者轻量但适合小规模验证。生产环境我推荐Milvus或者Qdrant,社区活跃、性能稳定。
检索策略不能只做一次向量相似度。我把检索设计成“向量召回+重排”两层:第一层用向量相似度召回Top50候选片段,第二层用BM25和向量得分做融合重排,选出Top5。实践下来,这种混合检索比单纯向量检索的准确率提升明显,特别是对专业术语和精确匹配的场景,BM25能补上向量检索的短板。
这里补充一个容易被忽略的点:检索回来的片段顺序也很关键。我控制在2~4个片段,按相关性降序拼接,放入Prompt的上下文区域,并在每个片段前标注来源。这样大模型知道每个信息的出处,回复时能更准确地引用具体来源,也便于后续做引用溯源。
3.3 提示词工程:约束大模型不说胡话
很多人的Prompt是“你是一个客服,请回答用户的问题”,这太免不了幻觉。我对系统提示词做了三件事:明确身份和职责边界、规定回答格式与话术风格、设定不知道时的兜底回复。
关键是第三点。在System Prompt里明确写了:如果知识库提供的内容不足以回答用户问题,必须回答“这个问题我暂时无法准确回答,已为你转接人工客服”,并同时触发转人工逻辑。这是智能客服系统最重要的安全阀——宁可不答,不能瞎答。客服场景跟聊天机器人不同,一个错误回答可能直接导致客诉升级。
另外一个实用技巧:在Prompt里要求模型按结构化格式返回,比如同时返回“直接回复用户的文本”和“意图分类标签”,这样程序可以方便地解析回复内容,做后续的路由判断。JSON格式输出我建议作为正式选项,解析稳定且扩展性好。
4. 对话系统实现:从原型到可上线
4.1 环境准备与模型部署实操
先讲硬件最低配置。如果你只是想跑通流程做Demo,8GB显存的消费级显卡就够,比如RTX 3070/4060,跑Qwen2.5-7B的Q4量化版本没问题;如果要达到生产可用的响应速度和并发支撑,推荐至少24GB显存的RTX 4090或A10,或者直接上多卡推理方案。
软件环境我用的是Ubuntu 22.04 + Python 3.10 + CUDA 12.x。模型部署先用Ollama快速起来:
# 安装Ollama curl -fsSL https://ollama.com/install.sh | sh # 拉取Qwen2.5-7B-Instruct并启动服务 ollama pull qwen2.5:7b-instruct ollama serveOllama自带OpenAI兼容的API接口,端口默认为11434,调用方式:
curl http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5:7b-instruct", "messages": [{"role": "user", "content": "你好"}] }'生产环境我会切换到vLLM,因为Ollama在并发和吞吐上不够。vLLM部署同样很简洁:
pip install vllm python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --tensor-parallel-size 1 \ --port 8000两者API兼容,切换成本很低。
4.2 知识库代码实现:从文档到可检索向量
用LangChain搭RAG链路是目前效率最高的方式,虽然LangChain版本迭代快有不少坑,但核心链路已经很成熟。下面是我项目里精简后的关键代码逻辑:
from langchain_community.document_loaders import DirectoryLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Milvus # 1. 加载文档 loader = DirectoryLoader("./knowledge", glob="**/*.md") docs = loader.load() # 2. 按语义边界切分,chunk控制在300-500字 text_splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", " ", ""], length_function=len, ) chunks = text_splitter.split_documents(docs) # 3. 向量化并写入Milvus embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh-v1.5") vector_store = Milvus.from_documents( chunks, embeddings, collection_name="knowledge_base", connection_args={"host": "localhost", "port": "19530"}, )切分参数这里值得细说:chunk_size设400字符,overlap设50。如果chunk太大,检索召回的内容冗余多,浪费Prompt token且干扰模型聚焦;太小则信息不完整。overlap的作用是保证跨边界的信息不丢失,尤其是那段关键结论恰好落在两个chunk接缝处时,有overlap能大幅降低信息断裂概率。
Embedding模型我选了BAAI的bge-large-zh-v1.5,中文场景表现稳定,在C-MTEB评测里排名靠前,而且它支持为检索query和文档配置不同的指令前缀,能在不增加成本的情况下提升检索精度。这里要提醒,Embedding模型和生成模型是两回事,很多人误以为大模型自带的语义能力也能做Embedding,实际不是——两者必须分开部署,Embedding模型负责“找”,生成模型负责“说”。
4.3 会话管理与服务封装
智能客服必须支持多轮对话。用户说“那怎么退款”必须能关联到前文说的“订单取消”。我把会话管理分了两层:短期会话状态和长期用户画像。短期会话状态存最近N轮对话摘要,使用Redis存储,键为会话ID,值保存对话历史,超出窗口后做摘要压缩;长期用户画像存在MySQL/PG里,比如会员等级、历史工单,检索出来后也拼入Prompt。
服务封装用FastAPI是最顺手的。核心路由代码做了简化但逻辑完整:
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import redis, json app = FastAPI() redis_client = redis.Redis(host="localhost", port=6379, db=0) class ChatRequest(BaseModel): session_id: str user_message: str class ChatResponse(BaseModel): reply: str intent: str need_human: bool @app.post("/chat", response_model=ChatResponse) async def chat(req: ChatRequest): # 1. 获取会话历史 history = redis_client.get(f"session:{req.session_id}") messages = json.loads(history) if history else [] # 2. 检索知识库 query_vec = embeddings.embed_query(req.user_message) docs = vector_store.similarity_search_by_vector(query_vec, k=4) # 3. 拼接Prompt context = "\n".join([d.page_content for d in docs]) system_prompt = f"""你是智能客服小助手,请严格根据以下知识库内容回答问题。 知识库内容: {context} 如果知识库内容不足以回答问题,请明确回答无法回答并提示转人工。""" messages = messages + [{"role": "user", "content": req.user_message}] response = openai_client.chat.completions.create( model="qwen2.5:7b-instruct", messages=[{"role": "system", "content": system_prompt}] + messages, ) reply = response.choices[0].message.content # 4. 更新会话历史,保留最近8轮 messages.append({"role": "assistant", "content": reply}) if len(messages) > 16: messages = messages[-16:] redis_client.set(f"session:{req.session_id}", json.dumps(messages), ex=3600) return ChatResponse(reply=reply, intent="query", need_human=False)这里我特意加了Redis过期时间,默认1小时会话有效,既控制内存占用,也符合客服场景的实际:超过1小时用户还没说完话的概率极低,重新开始一轮会话更合理。保留最近8轮对话(16条消息)是经验值,太长会超出模型上下文窗口,且早期信息对当前问题的参考价值已经衰减。
5. 部署上线与性能调优
5.1 并发压力测试与性能瓶颈
线下逻辑跑通和真正上线之间,隔着一道性能鸿沟。我做了压测,并发50路请求时,Ollama的响应时间从单机的700ms飙升到3秒以上,这在客服场景根本不可接受。问题出在Ollama默认不启用连续批处理,请求全部排队等待推理。
切换到vLLM后情况完全不同。vLLM的Continuous Batching机制允许不同请求在同一批次内处理,GPU利用率大幅提升,并发50路时P95延迟稳定在1.2秒左右,同时吞吐量比Ollama高约3倍。如果你确定要用Ollama,也可以设置OLLAMA_NUM_PARALLEL环境变量提高并发,但效果远不如vLLM。
还有一个容易踩的坑是Embedding服务的并发能力。Embedding模型一般是小模型,推理速度很快,但如果你和生成模型共用GPU显存,会在高峰时抢占显存导致OOM。我最后把Embedding服务单独部署在CPU上,虽然单条延迟稍高(几十毫秒),但完全够用且不干扰主推理。
5.2 服务容错与降级策略
客服系统最怕的是大模型推理服务挂了,用户全量无法应答。所以必须设计降级策略。我的方案分三档:第一档是主链路——大模型+RAG正常服务;第二档是降级链路——当大模型推理超时或返回错误时,直接返回知识库中向量检索得分最高的FAQ标准答案;第三档是兜底——如果连向量库也挂了,返回统一话术“系统繁忙,请稍后再试或拨打人工热线”。
这个降级策略特别重要,因为大模型推理服务在高负载下确实会出现超时,而且概率不低。有了降级链路,用户至少能拿到一个标准回答,而不是看到接口报错。实测在推理服务抖动期间,降级链路把整体可用性从92%拉回了99.2%,这个差距在真实业务里就是几百个客诉的区别。
另外我强烈建议给大模型推理加超时控制,不要依赖默认的超时时间。我之前遇到过客户端请求一直挂着不返回,连接池被占满,导致后续所有请求排队的情况。在代码里给OpenAI客户端设置max_retries=1和timeout=15,超时后立即走降级链路,绝不让用户端卡死。
5.3 评测体系:效果好不好,得用数据说话
没有评测体系的AI系统等于盲人摸象。我搭建了一套半自动化的评测流程:业务方提供200条标准问答对作为测试集,覆盖高频问题、边缘问题和未知问题三类。每次修改Prompt或检索逻辑后,在这200条上跑一遍,统计准确率、兜底触发率、平均响应时间三个指标。
这里说的准确率不是人工肉眼判断,而是分两个维度:一是检索命中率,即标准答案是否在返回的Top5知识片段里,这个可以自动计算;二是生成内容是否与标准答案语义一致,我用另外一个更强的模型做裁判打分,同时保留人工抽检,防止裁判模型本身有偏好。
评测这套流程看着简单,但需要坚持做。我遇到过的情况是:某个Prompt改动在10条badcase上效果显著,理论上应该上线,结果全量测试发现另一个场景的准确率跌了8%。如果没跑全量评测就上线,这就是线上事故。评测体系在AI应用里的地位,不亚于代码仓库里的CI/CD,这是我从这个项目里最深的体会之一。
6. 常见问题避坑实录
6.1 上下文管理的坑
最典型的问题是上下文无限增长。用户聊了30轮之后,消息历史里可能有上万token,直接全部发给模型,不仅推理速度慢、费用高,而且模型在超长上下文里的注意力会分散,前面的关键信息反而“遗忘”。
我的处理方案是对话轮次裁剪+关键信息抽取两步走。保留最近6到8轮对话原文,更早的信息通过一个轻量级模型抽取“用户诉求摘要”和“已解决问题清单”,以压缩形式保留在Prompt里。经过这个方案,即使对话长达50轮,Prompt总长度依然可控,用户诉求也不丢。
6.2 检索召回不到相关内容的处理
知识库明明有相关内容,但检索不到,这在中文场景非常常见。原因一般是:用户问法的表达方式和知识库文档里的表述差异太大,比如用户说“退款几天到账”,文档里的原文是“退款时效为2-5个工作日”。单纯靠向量检索很难在语义空间里对齐这种差异。
我做了两个改进。第一,扩充知识库FAQ的“相似问法”字段,用大模型批量生成每个标准问题的10个常见变体问法,一并存入向量库,这样被准确命中的概率大幅提升;第二,融合BM25关键词检索,上述“退款”“到账”这类词,关键词匹配完全可以兜住。
6.3 模型幻觉的源头治理
虽然Prompt里写了“没有把握不要回答”,但模型有时还是会一本正经地胡说八道。根源在于:检索返回的文档片段里本身包含噪音,模型分不清哪些是可靠信息。我在检索阶段加了相关度阈值过滤:向量相似度低于阈值的片段直接丢弃,宁可让模型回答“不知道”,也不能把低质量片段作为上下文喂给它,因为低质量上下文产生的幻觉远比没有上下文更严重。
用了这个策略之后,系统在测试集上的幻觉发生率降低约60%,代价是兜底率提升了10%。但在客服场景里,多转几次人工的代价远小于给用户一个错误承诺——比如用户问“这个产品能保鲜多久”,模型答错成“永久保鲜”,这种错误会导致直接客诉甚至赔偿,完全不可接受。
6.4 项目经验总结
这个项目做完,我最想强调的一点是:智能客服系统的天花板,不在模型本身,而在知识库质量和评测闭环。模型选型可以照抄社区方案,Prompt技巧有大量公开资料,但知识库的语料加工、切分策略、检索阈值,每家的业务情况都不同,必须自己一遍遍调优和评测。
如果现在有人要从零开始做,我的建议是先用Ollama快速验证效果,确认业务方对回答质量满意后,再切换vLLM做性能优化;先解决“回答对不对”,再解决“扛不扛得住”。遇到badcase不要慌,逐个分析是检索问题还是生成问题,对症下药,效果提升肉眼可见。
技术上其实没有太多玄学,多测、多调、多积累评测数据,系统就一定能越跑越稳。
本文还有配套的精品资源,点击获取