公司动态
智能客服系统的工程复盘:FAQ 匹配 + 大模型兜底的双层架构
智能客服系统的工程复盘FAQ 匹配 大模型兜底的双层架构一、个性化深度引言去年年底接了一个智能客服项目需求看起来并不复杂替代人工客服回答80%的常见问题。产品经理给的方案是用大模型直接对话一行代码调用 API 就行。上线两周问题清单拉到了三页。高峰期延迟超过8秒——GPT走了一遍推理用户等的脾气上来了。更致命的是幻觉——问我的订单退款了吗模型给出了一个漂亮的退款金额和日期数字全是编的。20%的用户追问后得到正确答案但这个先骗你再纠正的体验比直接说我不知道更糟糕。事后复盘发现核心矛盾大模型善长开放域对话但客服场景需要的是精确信息召回。FAQ 匹配精度高但覆盖率有限大模型覆盖率高但稳定性差——两者恰好互补。双层架构的思路就是在这里成型的第一层 FAQ 精确匹配解决80%高频问题第二层大模型兜底处理长尾和复杂问题。这篇文章复盘一下这个工程实践。二、个性化原理剖析双层智能客服架构的设计要点是让 FAQ 层和大模型层形成互补而非简单串联FAQ 层的核心指标是命中率而不是准确率——没命中可以走大模型兜底命中但答错才真正影响体验。所以工程上把召回优先级放在精度前面Embedding 召回 top-50Reranker 从50个候选中选 top-5。大模型层的核心指标是事实一致性而不是流畅度——客服场景漂亮而错误的回答比丑陋而正确的回答更危险。事实性校验是一道必须过的关卡。反馈闭环容易被忽视但至关重要。用户问过但 FAQ 没覆盖的问题应该在24小时内分析并补充到 FAQ 库。这个自动化能力决定了系统能否持续进化——没有反馈闭环的系统上线即巅峰之后只会越来越差。三、个性化代码实践双层客服的检索引擎实现import numpy as np from dataclasses import dataclass, field from typing import List, Tuple, Optional from enum import Enum import asyncio class MatchLevel(Enum): 匹配等级——设计原因三档比二值判断更精准 EXACT exact # 精确匹配置信度 0.9 HIGH_SIM high_sim # 高相似度0.7-0.9 LOW_SIM low_sim # 低相似度需大模型兜底 dataclass class FAQItem: FAQ条目——设计原因结构化字段便于索引和更新 faq_id: str question: str answer: str category: str keywords: List[str] field(default_factorylist) hit_count: int 0 # 命中次数用于热度排序 last_hit: float 0.0 # 最后命中时间戳 dataclass class SearchResult: 检索结果——设计原因统一返回结构上层不用区分来源 content: str score: float level: MatchLevel is_from_faq: bool source_id: str class BilingualQASystem: 双层智能客服系统——设计原因类组织便于注入不同检索/生成组件 # FAQ置信度阈值——设计原因基于线上数据回测确定不是拍脑袋 FAQ_HIGH_THRESHOLD 0.85 # 高于此值直接返回FAQ FAQ_LOW_THRESHOLD 0.65 # 低于此值强制走大模型 # 0.65-0.85之间FAQ RAG双重验证 def __init__(self, embedding_model, reranker_model, llm_client): self.embedding_model embedding_model self.reranker reranker_model self.llm llm_client self.faq_index {} # faq_id - embedding vector async def build_faq_index(self, faq_items: List[FAQItem]): 构建FAQ向量索引——设计原因异步批量embedding10k条FAQ也要秒级完成 questions [item.question for item in faq_items] # 批量embedding——设计原因单条调用是N次网络RTT批量是一次 embeddings await self.embedding_model.encode_batch(questions) for item, emb in zip(faq_items, embeddings): self.faq_index[item.faq_id] { item: item, vector: emb } print(fFAQ索引构建完成共{len(self.faq_index)}条) async def search(self, query: str, top_k: int 5) - List[SearchResult]: 双层检索主流程——设计原因统一入口便于A/B测试不同策略 # 阶段一FAQ向量检索 query_emb await self.embedding_model.encode(query) faq_candidates self._faq_vector_search(query_emb, top_k50) if not faq_candidates: # FAQ完全没召回——设计原因极端情况直接走RAG不做无用功 return await self._rag_search(query) # 阶段二Reranker精排——设计原因BM25/reranker比纯向量相似度准得多 reranked await self.reranker.rerank( query, [(c.item.question, c.score) for c in faq_candidates] ) best reranked[0] best_score best[1] # 阶段三按置信度分级处理 if best_score self.FAQ_HIGH_THRESHOLD: # 高置信度直接返回FAQ——设计原因减少大模型调用降低延迟和成本 return [SearchResult( contentbest[0].item.answer, scorebest_score, levelMatchLevel.EXACT, is_from_faqTrue, source_idbest[0].item.faq_id )] elif best_score self.FAQ_LOW_THRESHOLD: # 中等置信度FAQ RAG双重验证——设计原因宁可多算一步也不能给错答案 faq_result best[0].item.answer rag_results await self._rag_search(query) # 用大模型验证FAQ答案和RAG结果的一致性 verified await self._verify_consistency(query, faq_result, rag_results) return verified else: # 低置信度直接用RAG——设计原因FAQ没把握就全交给大模型 return await self._rag_search(query) def _faq_vector_search(self, query_emb: np.ndarray, top_k: int) - List[Tuple[FAQItem, float]]: FAQ向量检索——设计原因余弦相似度top-k简单够用 results [] for faq_id, data in self.faq_index.items(): sim np.dot(query_emb, data[vector]) / ( np.linalg.norm(query_emb) * np.linalg.norm(data[vector]) ) results.append((data[item], float(sim))) results.sort(keylambda x: x[1], reverseTrue) return results[:top_k] async def _rag_search(self, query: str) - List[SearchResult]: RAG检索——设计原因独立方法便于替换底层检索引擎 # 文档检索 大模型生成 docs await self._retrieve_documents(query) context \n\n.join([d[content] for d in docs]) prompt f根据以下知识库内容回答用户问题。 知识库内容{context} 用户问题{query} 要求如果知识库中没有相关信息明确回复暂未找到相关信息。 response await self.llm.generate(prompt) # 事实性校验——设计原因防止模型编造信息 is_consistent await self._fact_check(response, docs) if not is_consistent: return [SearchResult( content抱歉我暂时无法确认这个信息已转接人工客服。, score0.0, levelMatchLevel.LOW_SIM, is_from_faqFalse, source_idfallback )] return [SearchResult( contentresponse, score0.8, levelMatchLevel.LOW_SIM, is_from_faqFalse, source_idllm_rag )] async def _verify_consistency(self, query: str, faq_answer: str, rag_results: List[SearchResult]) - List[SearchResult]: 一致性验证——设计原因FAQ和RAG矛盾时以RAG为准因为RAG有文档依据 if not rag_results: return [SearchResult( contentfaq_answer, score0.75, levelMatchLevel.HIGH_SIM, is_from_faqTrue, source_idfaq_fallback )] # 用轻量模型做一致性判断——设计原因不需要GPT-4Roberta-BASE足够 prompt f判断以下两段回答是否一致 回答AFAQ: {faq_answer} 回答BRAG: {rag_results[0].content} 只回复一致或不一致。 result await self.llm.generate(prompt) if 一致 in result: return [SearchResult( contentfaq_answer, score0.8, levelMatchLevel.HIGH_SIM, is_from_faqTrue, source_idfaq_verified )] else: return rag_results # RAG优先 async def _retrieve_documents(self, query: str) - List[dict]: # 文档检索占位——实际按需实现 return [{content: 示例文档内容, score: 0.9}] async def _fact_check(self, response: str, docs: List[dict]) - bool: # 事实校验占位——实际按需实现 return True # 使用示意 async def main(): system BilingualQASystem(None, None, None) faq_list [ FAQItem(001, 如何退款, 登录后在订单详情页点击申请退款。, 售后), ] await system.build_faq_index(faq_list) results await system.search(怎么退货) print(f检索结果: {results[0].content})代码的核心设计是三层置信度分级高置信度直接回复省成本、低延迟中等置信度双重验证FAQ RAG交叉校验低置信度全部交给大模型文档检索。这比简单的FAQ不够就走大模型要稳定得多。四、个性化边界权衡FAQ覆盖率 vs 维护成本FAQ 库越大命中率越高但维护成本也指数增长。发现一个规律前500条 FAQ 能覆盖85%的流量加到2000条只提升到92%。与其维护一个5000条的臃肿 FAQ 库不如把精力放在 FAQ 质量和大模型 RAG 的效果优化上。Reranker延迟 vs 召回精度Cross-encoder reranker 效果很好但处理50个候选的推理延迟在200-500ms。高峰期这个延迟需要优化降候选到20个精度损失约3%但延迟减半或者用更轻量的 reranker 模型。一致性校验的阈值设置太严格——大量正确回答被误判为不一致用户收到太多转人工太宽松——不一致被放过幻觉风险增加。经过A/B测试在精确信息场景订单查询、政策咨询设置严格阈值在建议类场景产品推荐、使用技巧放宽阈值分场景差异化配置效果最好。五、总结双层智能客服架构通过 FAQ 精确匹配与大模型 RAG 兜底的分层设计兼顾了高频场景的低延迟精确性与长尾场景的高覆盖灵活性。Embedding 向量检索做粗排、Reranker 做精排的策略在召回率和延迟间取得平衡。三层置信度分级实现了 FAQ 直达、双重验证、RAG 兜底的差异化处理路径。实施中需管理 FAQ 覆盖率与维护成本、Reranker 延迟与精度、一致性校验阈值敏感度三者间的权衡。反馈闭环是系统持续进化的关键机制。