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_retries=2): 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_k=5): # query 改写 rewritten_query = llm_rewrite(query) # 向量检索 vector_results = vector_store.search(rewritten_query, top_k=20) # 关键词检索 keyword_results = es_client.search(rewritten_query, top_k=20) # 合并去重 merged = deduplicate(vector_results + keyword_results) # 重排序 reranked = reranker.rerank(query, merged, top_k=top_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_days=90): 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 到 N+M
在没有 MCP 之前,每接一个外部工具,我都要写一套适配代码。查订单要写一套,查物流要写一套,接内部 CRM 要写一套,接第三方客服系统又要写一套。每个工具的接口协议、鉴权方式、参数格式都不一样,维护成本极高。这就是典型的 N×M 问题:N 个 Agent 要对接 M 个工具,就需要 N×M 套适配代码。
MCP(Model Context Protocol)的核心价值就是把 N×M 变成 N+M。Agent 只需要实现一套 MCP 客户端,工具只需要实现一套 MCP 服务端,双方通过标准协议通信。这样 Agent 接新工具的时候,不需要改代码,只需要配置一下 MCP Server 的地址就行。
我第一次把内部工具全部迁移到 MCP 之后,最直观的感受是:接一个新工具的时间从两天变成了两个小时。以前要写适配层、写鉴权、写参数转换、写错误处理,现在只需要在 MCP Server 里注册一下工具,Agent 端配置一下连接信息就完事了。
4.2 MCP Server 的三种接入方式与选型建议
MCP Server 的接入方式主要有三种:stdio、SSE、Streamable HTTP。这三种方式各有适用场景,选错了会踩坑。
stdio 是最简单的方式,MCP Server 作为一个子进程运行,通过标准输入输出和 Agent 通信。这种方式适合本地工具,比如文件操作、本地数据库查询。优点是零网络开销,延迟极低;缺点是只能本机运行,没法分布式部署。
SSE(Server-Sent Events)是早期 MCP 支持的远程接入方式,通过 HTTP 长连接推送消息。这种方式适合远程工具,但 SSE 有一个问题:它是单向的,服务端只能推不能收,客户端发消息要走另一个 HTTP 请求。这就导致每次工具调用都要两次网络往返,延迟比较高。
Streamable HTTP 是目前推荐的方式,它把请求和响应都放在一个 HTTP 连接里,支持流式传输。这种方式兼顾了远程接入和低延迟,是目前生产环境的首选。
我的选型建议是:本地工具用 stdio,远程工具用 Streamable HTTP,SSE 除非有历史包袱否则不建议新项目使用。
| 接入方式 | 适用场景 | 延迟 | 部署复杂度 | 推荐度 |
|---|---|---|---|---|
| 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( name="query_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 设计不一定适合你的业务。唯一可靠的方法是:建立评估体系,快速迭代,用数据说话。踩过的坑多了,自然就知道路该怎么走了。