ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

客服Agent从能跑到扛造:Tool、RAG、MCP与Eval的48关工程实践

客服Agent从能跑到扛造:Tool、RAG、MCP与Eval的48关工程实践 1. 从能跑到扛造客服 Agent 的 48 关到底在考什么做客服 Agent 的人大概都有过这种体验Demo 阶段顺风顺水用户问我的订单到哪了Agent 调个订单查询 Tool返回结果完美。然后一上生产问题像开闸一样涌出来——用户问我上周买的那件红色连衣裙能不能换成蓝色Agent 懵了因为它不知道该先查订单、再查库存、再判断换货政策用户问你们家那个什么什么政策来着Agent 检索出一堆不相关的文档用户连续追问五轮之后Agent 开始胡言乱语把上一轮的上下文和这一轮的问题搅在一起。这就是我说的渡劫。客服 Agent 从能跑到扛造中间隔着的不是一两个 Prompt 优化而是一整套工程体系的搭建。我把这个过程拆成了 48 个关卡覆盖从 Tool 设计、RAG 检索、MCP 协议集成到 Eval 评估体系的完整链路。每一关都是真实踩过的坑每一关背后都有具体的失败案例。先说说为什么是客服场景。客服是所有 Agent 落地场景里最刁钻的一个它要求多轮对话的上下文保持、要求工具调用的准确性、要求知识检索的时效性、要求回复的合规性还要求在高并发下保持稳定。你把这五个要求拆开看每一个都对应着 Agent 工程里的一个核心难题。所以客服 Agent 能跑通基本上其他场景的 Agent 也不会差太远。这篇文章适合谁看如果你正在做 Agent 开发尤其是客服、售前咨询、售后支持这类场景那这篇内容基本就是一份避坑地图。如果你刚接触 Agent想了解 Tool、RAG、MCP、Eval 这些概念在实际项目里怎么落地那这篇也能给你一个完整的工程视角。我不会只讲概念每个环节都会给出具体的做法、参数和踩坑经验。48 关不可能在一篇文章里全部展开我会挑其中最关键的几个维度深入讲Tool 的设计边界与调用可靠性、RAG 在客服场景下的检索瓶颈与优化、MCP 协议如何统一工具接入、以及 Eval 体系怎么建才能真的发现问题。这四个维度基本覆盖了客服 Agent 从开发到上线的核心链路。2. Tool 不是越多越好客服 Agent 的工具设计边界2.1 为什么 Tool 数量超过 15 个之后准确率开始崩刚开始做客服 Agent 的时候我的思路很简单用户可能问什么我就做什么 Tool。查订单、查物流、查库存、查优惠券、查会员等级、查退换货政策、查发票、查售后进度……一口气做了二十多个 Tool。结果上线第一天就发现Agent 选错 Tool 的概率高得离谱。用户问我的发票什么时候能开好Agent 调了查订单的 Tool用户问退款到哪了Agent 调了查物流的 Tool。后来我做了个统计发现当 Tool 数量超过 15 个之后Agent 的 Tool 选择准确率从 92% 掉到了 67%。原因不复杂LLM 在选 Tool 的时候本质上是在做一次语义匹配Tool 越多语义空间越拥挤相似的 Tool 描述之间就会互相干扰。比如查订单状态和查售后进度这两个 Tool描述如果不仔细区分模型很容易搞混。我的做法是做了三层收敛。第一层是合并同类项把查订单状态查订单详情查订单物流合并成一个query_orderTool通过参数区分查询维度。第二层是加前置路由用一个轻量的意图分类模型先判断用户意图属于哪个大类再只把该类下的 Tool 暴露给 Agent。第三层是动态 Tool 加载根据对话上下文只加载相关的 Tool 子集。这三层做完之后Tool 数量从 23 个降到了 9 个核心 Tool选择准确率回到了 94% 以上。提示Tool 的数量控制不是拍脑袋决定的。我的经验值是单次对话轮次中暴露给 Agent 的 Tool 不超过 12 个超过这个数就要考虑做路由或分组。2.2 Tool 描述怎么写才能让 Agent 不选错Tool 的描述description是 Agent 选择 Tool 的唯一依据但很多人写描述的时候特别随意。我见过最离谱的一个描述是查询订单相关信息这种描述等于没写。Agent 看到这个描述只能靠猜。一个好的 Tool 描述应该包含四个要素功能边界这个 Tool 能做什么、输入约束参数的类型和取值范围、输出格式返回什么结构的数据、使用场景什么情况下应该调用这个 Tool。举个例子{ name: query_order, description: 根据订单号或用户ID查询订单信息。当用户询问订单状态、订单详情、物流进度时调用此工具。不适用于查询售后工单进度售后问题请使用 query_after_sale 工具。, parameters: { order_id: { type: string, description: 订单号格式为 ORD 开头的 12 位字符串 }, user_id: { type: string, description: 用户ID当用户未提供订单号时使用 }, query_type: { type: string, enum: [status, detail, logistics], description: 查询类型status 查状态detail 查详情logistics 查物流 } } }注意最后那句不适用于查询售后工单进度这就是在描述里主动划清边界。实测下来加了这句之后Agent 把售后问题误调到订单查询 Tool 的概率下降了 40% 以上。还有一个技巧是在描述里加反例。比如当用户询问退款时如果指的是订单退款进度用此工具如果指的是售后工单的退款用 query_after_sale。这种正反对比的描述方式对 LLM 的区分能力提升非常明显。2.3 Tool 调用失败后的重试与降级策略Tool 调用失败是常态不是异常。网络超时、下游服务限流、参数格式错误这些都会导致 Tool 调用失败。如果 Agent 遇到失败就直接把错误信息抛给用户体验会非常差。我的做法是给每个 Tool 配置一套重试与降级策略。重试策略包括超时重试最多 2 次间隔 500ms、参数修正重试如果失败原因是参数格式错误让 LLM 根据错误信息修正参数后重试一次。降级策略包括如果 Tool 连续失败 3 次自动切换到备用 Tool 或返回兜底话术。这里有个细节重试的时候不要把原始错误信息直接喂给 LLM因为有些错误信息包含技术细节比如堆栈信息LLM 可能会把这些信息泄露给用户。我的做法是先把错误信息做一层脱敏和归类比如把Connection timeout after 3000ms归类为服务暂时不可用再把归类后的信息给 LLM 做决策。def call_tool_with_retry(tool_name, params, max_retries2): for attempt in range(max_retries 1): try: result tool_registry[tool_name].invoke(params) return {success: True, data: result} except TimeoutError: if attempt max_retries: time.sleep(0.5 * (attempt 1)) continue return {success: False, error: SERVICE_UNAVAILABLE} except ParamError as e: if attempt 0: params llm_fix_params(tool_name, params, str(e)) continue return {success: False, error: PARAM_INVALID}这套机制上线之后Tool 调用的用户感知失败率从 8% 降到了 1.2% 以下。3. RAG 在客服场景的瓶颈检索不准比不检索更可怕3.1 客服知识库的特殊性为什么通用 RAG 方案直接套会翻车通用 RAG 方案通常是文档切块、向量化、存向量库、检索 Top-K、拼 Prompt。这套流程在通用问答场景下能跑但在客服场景下会翻车。原因有三个。第一客服知识库的文档结构高度非结构化。产品手册、FAQ、政策文档、工单记录这些内容的格式千差万别。FAQ 是问答对政策文档是条款列表工单记录是对话流水。用统一的切块策略处理这些文档切出来的块要么太碎丢失上下文要么太大引入噪声。第二客服问题对时效性要求极高。用户问你们现在的退换货政策是什么如果 RAG 检索到的是一年前的旧政策那比不检索还糟糕。通用 RAG 方案通常不处理文档的时效性权重检索的时候新旧文档一视同仁。第三客服场景下的 query 通常很短且口语化。那个退货怎么弄、我买错了能换吗这种 query 和文档里的书面表达之间有很大的语义鸿沟。直接用向量相似度检索召回率会很低。我踩过最惨的一次坑是用户问七天无理由退货从哪天开始算RAG 检索到了一篇讲退货流程的文档Agent 根据这篇文档回答了一堆退货步骤但完全没有回答从哪天开始算这个核心问题。用户直接炸了。3.2 混合检索 重排序把召回率从 61% 拉到 89% 的实操路径纯向量检索在客服场景下的召回率大概在 60% 左右这个数字是我在三个不同项目的测试集上反复验证过的。要提升召回率必须上混合检索。混合检索的核心思路是向量检索负责语义匹配关键词检索负责精确匹配两路结果合并后做重排序。具体做法是向量检索用 embedding 模型把 query 和文档块都向量化用余弦相似度检索 Top-20。关键词检索用 BM25 或 Elasticsearch 的 match 查询检索 Top-20。合并去重两路结果合并按文档 ID 去重。重排序用一个 cross-encoder 重排序模型比如 bge-reranker对合并后的结果做精排取 Top-5。这套流程听起来简单但有几个关键参数需要调。向量检索的 Top-K 我一般设 20关键词检索的 Top-K 也设 20合并后大概有 25-30 个不重复的结果重排序后取 Top-5 给 LLM。重排序模型的选择很关键我试过 bge-reranker-base 和 bge-reranker-large在客服场景下 large 版本的效果明显更好但延迟会增加 80ms 左右。如果对延迟敏感可以用 base 版本加一个轻量的规则过滤。还有一个容易被忽略的点是查询改写。用户的原始 query 往往很短直接拿去检索效果不好。我的做法是在检索之前加一步 query 改写用 LLM 把用户的口语化 query 改写成更适合检索的形式。比如那个退货怎么弄改写成退货流程 退货条件 退货步骤。这一步能把召回率再提升 8-10 个百分点。def hybrid_retrieve(query, top_k5): # query 改写 rewritten_query llm_rewrite(query) # 向量检索 vector_results vector_store.search(rewritten_query, top_k20) # 关键词检索 keyword_results es_client.search(rewritten_query, top_k20) # 合并去重 merged deduplicate(vector_results keyword_results) # 重排序 reranked reranker.rerank(query, merged, top_ktop_k) return reranked这套方案上线后我在测试集上测到的召回率是 89%比纯向量检索的 61% 提升了 28 个百分点。当然延迟也从 120ms 增加到了 350ms 左右这个 trade-off 在客服场景下是值得的。3.3 知识库时效性管理怎么让 Agent 不引用过期政策客服知识库的时效性问题本质上是一个文档版本管理问题。我的做法是给每个文档块打上三个时间戳生效时间、失效时间、最后更新时间。检索的时候先根据当前时间过滤掉已失效的文档块然后在重排序阶段给新文档更高的权重。具体实现上我在向量库的 metadata 里加了这三个字段检索的时候用 filter 做时间过滤。重排序的时候用一个简单的时间衰减函数来调整分数def time_decay_score(base_score, last_updated, half_life_days90): days_since_update (now() - last_updated).days decay 0.5 ** (days_since_update / half_life_days) return base_score * (0.7 0.3 * decay)这个衰减函数的含义是文档每过 90 天时效性权重减半但最低保留 70% 的基础分数。这样既不会让旧文档完全消失又能让新文档获得明显的排序优势。还有一个实操经验是政策类文档一定要做结构化。不要把一整篇政策文档直接切块而是把每一条政策拆成独立的条目每个条目单独存一个文档块带上生效时间和失效时间。这样检索的时候粒度更细准确率更高。我做过对比结构化之后的政策问答准确率比非结构化高了 22%。4. MCP 协议接入让工具生态从手搓变成插拔4.1 MCP 到底解决了什么问题从 N×M 到 NM在没有 MCP 之前每接一个外部工具我都要写一套适配代码。查订单要写一套查物流要写一套接内部 CRM 要写一套接第三方客服系统又要写一套。每个工具的接口协议、鉴权方式、参数格式都不一样维护成本极高。这就是典型的 N×M 问题N 个 Agent 要对接 M 个工具就需要 N×M 套适配代码。MCPModel Context Protocol的核心价值就是把 N×M 变成 NM。Agent 只需要实现一套 MCP 客户端工具只需要实现一套 MCP 服务端双方通过标准协议通信。这样 Agent 接新工具的时候不需要改代码只需要配置一下 MCP Server 的地址就行。我第一次把内部工具全部迁移到 MCP 之后最直观的感受是接一个新工具的时间从两天变成了两个小时。以前要写适配层、写鉴权、写参数转换、写错误处理现在只需要在 MCP Server 里注册一下工具Agent 端配置一下连接信息就完事了。4.2 MCP Server 的三种接入方式与选型建议MCP Server 的接入方式主要有三种stdio、SSE、Streamable HTTP。这三种方式各有适用场景选错了会踩坑。stdio 是最简单的方式MCP Server 作为一个子进程运行通过标准输入输出和 Agent 通信。这种方式适合本地工具比如文件操作、本地数据库查询。优点是零网络开销延迟极低缺点是只能本机运行没法分布式部署。SSEServer-Sent Events是早期 MCP 支持的远程接入方式通过 HTTP 长连接推送消息。这种方式适合远程工具但 SSE 有一个问题它是单向的服务端只能推不能收客户端发消息要走另一个 HTTP 请求。这就导致每次工具调用都要两次网络往返延迟比较高。Streamable HTTP 是目前推荐的方式它把请求和响应都放在一个 HTTP 连接里支持流式传输。这种方式兼顾了远程接入和低延迟是目前生产环境的首选。我的选型建议是本地工具用 stdio远程工具用 Streamable HTTPSSE 除非有历史包袱否则不建议新项目使用。接入方式适用场景延迟部署复杂度推荐度stdio本地工具极低低高SSE远程工具旧中中低Streamable HTTP远程工具低中高4.3 MCP 工具注册的坑参数 schema 不一致导致的调用失败MCP 工具注册的时候最容易踩的坑是参数 schema 不一致。MCP 协议要求工具用 JSON Schema 描述参数但很多现有工具的接口文档和 JSON Schema 之间是有 gap 的。我遇到过一个典型案例一个查询订单的 MCP 工具参数 schema 里定义order_id是 string 类型但实际调用的时候下游服务要求 order_id 必须是数字。Agent 按照 schema 传了字符串下游服务直接报错。这种问题在 MCP 层面不会报错因为 schema 校验通过了但实际调用会失败。解决这个问题的办法是在 MCP Server 里加一层参数转换和校验。不要直接把下游服务的接口暴露成 MCP 工具而是在中间加一层适配把 schema 定义和实际参数格式对齐。具体做法是mcp_tool( namequery_order, description查询订单信息, parameters{ order_id: {type: string, description: 订单号} } ) def query_order(order_id: str): # 参数转换字符串转数字 try: order_id_int int(order_id) except ValueError: return {error: 订单号格式不正确} # 调用下游服务 result downstream_service.query(order_id_int) return result这层适配看起来简单但能避免大量因为参数格式不一致导致的调用失败。我的经验是MCP 工具注册的时候一定要用真实的调用场景做一遍端到端测试不要只看 schema 校验通过就完事。5. Eval 体系没有评估的 Agent 就是在裸奔5.1 客服 Agent 的评估维度拆解从单轮准确率到多轮任务完成率做 Agent 评估最容易犯的错误是只看单轮准确率。单轮准确率高不代表 Agent 好用因为客服场景下大部分问题都是多轮对话。用户问我的订单到哪了Agent 回答请提供订单号用户提供订单号Agent 查询后回答已发货预计明天到达。这是一个三轮对话单轮准确率只能衡量每一轮的回答质量没法衡量整个任务是否完成。我的评估体系包含四个维度单轮回答准确率、多轮任务完成率、工具调用准确率、用户满意度。这四个维度分别对应不同的评估方法。单轮回答准确率用人工标注的测试集来测每个测试用例包含一个 query 和一个标准答案用 LLM-as-Judge 或者人工来打分。多轮任务完成率用模拟用户来测让一个 LLM 扮演用户和 Agent 进行多轮对话最后判断任务是否完成。工具调用准确率用日志分析来测统计 Agent 调用的 Tool 和预期 Tool 的一致率。用户满意度用线上反馈来测包括点赞点踩、转人工率、对话时长等指标。5.2 用 LLM-as-Judge 做自动化评估Prompt 设计和打分校准LLM-as-Judge 是目前最实用的自动化评估方法但用不好会引入很大的偏差。我踩过的坑包括Judge 模型给分太宽松、Judge 模型对格式敏感、Judge 模型和人工打分的一致性低。解决这些问题的关键是Prompt 设计和打分校准。Prompt 设计上我用的模板是这样的你是一个客服质量评估专家。请根据以下标准对客服回答进行打分 评分标准 - 5分回答完全正确信息完整语气友好 - 4分回答基本正确但信息有少量缺失或语气稍显生硬 - 3分回答部分正确但存在明显的信息错误或遗漏 - 2分回答大部分错误但至少没有误导用户 - 1分回答完全错误或包含误导性信息 用户问题{query} 客服回答{answer} 标准答案{reference} 请先分析客服回答和标准答案的差异然后给出 1-5 分的评分。 输出格式{score: 分数, reason: 分析}打分校准上我会定期抽一批样本做人工标注然后对比 LLM-as-Judge 的打分和人工打分的一致性。如果一致性低于 80%就调整 Prompt 或者换一个 Judge 模型。我试过用 GPT-4 和 Claude 做 Judge在客服场景下 Claude 的一致性略好一些但差距不大。还有一个技巧是多 Judge 投票。用两个不同的模型分别打分如果分差超过 1 分就触发人工复核。这样能把评估的准确率再提升 5-8 个百分点。5.3 线上灰度与 A/B 测试怎么用真实流量验证 Agent 效果离线评估做得再好也不能完全代表线上效果。线上灰度是必须的。我的做法是新版本 Agent 先切 5% 的流量跑一周对比核心指标任务完成率、转人工率、用户满意度和旧版本的差异。如果指标没有明显下降再逐步扩大到 20%、50%、100%。A/B 测试的关键是指标定义要清晰。我用的核心指标是任务完成率用户问题是否在对话中被解决用转人工率来反向衡量。平均对话轮次完成一个任务平均需要几轮对话轮次越少越好。工具调用成功率Tool 调用成功次数 / 总调用次数。用户满意度对话结束后的点赞点踩比例。这四个指标里我最关注的是转人工率。因为转人工意味着 Agent 没能解决问题这是最直接的失败信号。我的经验是转人工率每降低 1 个百分点用户满意度大概能提升 2-3 个百分点。6. 48 关之后客服 Agent 的工程化心得6.1 哪些关卡是必须过的哪些可以绕48 关听起来很多但实际上有些关卡是必须过的有些可以绕。必须过的关卡包括Tool 描述规范化、混合检索、MCP 协议接入、Eval 体系搭建。这四关不过Agent 根本没法上生产。可以绕的关卡包括多模态 RAG如果客服场景不涉及图片和视频、GraphRAG如果知识库结构不复杂、Agent 并发优化如果初期流量不大。这些关卡可以等业务量上来之后再补。我的建议是先把必须过的关卡过掉让 Agent 能跑起来然后再根据实际瓶颈逐步优化。不要一开始就追求大而全那样很容易陷入过度工程的陷阱。6.2 从 48 关里挑出的三条最高优先级经验如果只能从 48 关里挑三条最重要的经验我会选这三条第一Tool 描述要写到傻瓜都能看懂的程度。Agent 不是人它没有常识所有的判断都基于你给的描述。描述写得越清楚Agent 选错的概率越低。第二RAG 的召回率比精确率更重要。在客服场景下宁可多召回一些不相关的文档也不要漏掉相关文档。因为漏掉相关文档会导致 Agent 回答错误而多召回不相关文档最多是让 Agent 的回答稍微啰嗦一点。第三Eval 要贯穿开发全流程不是上线前才做。每次改 Prompt、改 Tool、改检索策略都要跑一遍评估集看看指标有没有下降。没有评估的优化就是盲人摸象。6.3 后续还可以扩展的方向客服 Agent 的工程化还有很多可以深挖的方向。比如多 Agent 协作用一个路由 Agent 把不同类型的用户问题分发给不同的专业 Agent每个专业 Agent 只负责一个领域这样可以进一步降低单个 Agent 的复杂度。再比如Agent 的可观测性把 Agent 的每一步决策都记录下来包括 Tool 选择、检索结果、Prompt 内容这样出问题的时候可以快速定位。还有一个方向是Agent 的自我进化。用线上积累的对话数据做持续微调让 Agent 越来越懂业务。这个方向目前还在探索阶段但我觉得是未来的趋势。我在实际项目里最大的体会是客服 Agent 的工程化没有银弹每一个环节都需要根据具体业务场景去调。别人的参数不一定适合你别人的 Tool 设计不一定适合你的业务。唯一可靠的方法是建立评估体系快速迭代用数据说话。踩过的坑多了自然就知道路该怎么走了。
返回列表