1. 为什么企业客服需要多智能体而不是一个"全能大模型"
很多团队第一次做智能客服,思路都很直接:接一个大模型 API,写一段系统提示词,把企业知识库塞进上下文,然后对外开一个对话框就上线了。我见过不下十个团队这么做,结果几乎一模一样——前两周演示效果惊艳,第三周开始被业务部门投诉,第六周项目进入"半停摆"状态。
问题不在于大模型不够聪明,而在于把客服这件事当成了一次问答。真实的客服场景里,用户的一句话背后往往同时牵扯好几件事:查订单状态、判断是否符合退款政策、安抚情绪、决定要不要转人工、记录工单。你让一个模型在一次推理里同时干完这些,它就会开始"幻觉式地自信"——该查数据库的时候它在编,该转人工的时候它在硬撑。
多智能体协作要解决的就是这个结构性矛盾。它的核心思路不是"让一个模型更强",而是把客服拆成几个职责单一的智能体,让它们像真实客服团队一样分工、交接、互相校验。这跟现实中的客服中心是一样的:一线接线员负责理解诉求,二线专员负责查系统和走流程,主管负责处理投诉和例外情况。你不会指望一个接线员同时干完这三件事,那为什么指望一个模型能做到?
1.1 单智能体方案在真实业务里会撞上的三堵墙
第一堵墙是上下文污染。当系统提示词里同时塞了退款政策、产品手册、话术规范、转人工规则,模型在长对话里会逐渐"忘记"哪条规则对应哪个场景。我实测过一个 8 轮以上的对话,模型把"7 天无理由"和"15 天质保"两个政策混着用,用户一问细节就露馅。
第二堵墙是工具调用的决策困难。单智能体面对十几个可用工具(查订单、查物流、建工单、发优惠券、转人工……),选择准确率会随工具数量上升而明显下降。这不是模型能力问题,是决策空间太大导致的固有难题。
第三堵墙是无法优雅地"认输"。客服系统最重要的能力之一,是知道自己什么时候该转人工。单智能体往往倾向于"再试一次",因为它被训练成要尽量回答问题,结果就是用户在死循环里被反复兜圈子,体验极差。
1.2 多智能体架构到底把什么问题变简单了
把上面三堵墙拆开看,多智能体的价值就清楚了。它把"一个大决策"拆成"一串小决策":路由智能体只负责判断意图属于哪一类,知识智能体只负责在指定知识域里检索,工具智能体只负责调用它被授权的那几个接口,转人工智能体只负责判断置信度和情绪。
每个智能体的提示词可以写得非常短、非常聚焦,工具集可以限制在 2 到 3 个,这样单点准确率会显著提升。更关键的是,每个环节都可以被单独观测和调试。当用户投诉"答非所问"时,你能明确知道是路由错了、检索错了、还是生成错了,而不是面对一个黑盒干瞪眼。
这也是为什么 LangGraph 这类框架会成为这类项目的首选。它本质上是一个带状态的图执行引擎,把每个智能体当成图上的一个节点,节点之间的边就是交接逻辑,整个对话的状态在图上流转。相比传统的链式调用,图结构天然支持条件分支、循环、人工介入点,这正好对应客服场景里的"如果……就转人工""如果信息不足就回退重问"。
2. 用 LangGraph 搭多智能体客服:节点怎么切、状态怎么设计
聊完为什么,进入怎么落地。这一节我按实际搭建顺序讲,从状态设计到节点划分,再到边和条件路由,每一步都说明为什么这么设计。
2.1 状态(State)是整个系统的骨架,先设计它
LangGraph 里最容易被低估的就是 State 的设计。很多人上来就写节点,结果写到一半发现节点之间要传的数据对不上,只能回头重构。我的建议是:先把 State 当成一份"对话工单"来设计,它要记录这次会话从头到尾所有需要跨节点共享的信息。
一个经过实战检验的客服 State 大致包含这几类字段:
from typing import TypedDict, Annotated, Literal from langgraph.graph.message import add_messages class CustomerServiceState(TypedDict): # 对话消息历史,用 add_messages 做增量合并 messages: Annotated[list, add_messages] # 路由结果:当前意图分类 intent: Literal["faq", "order", "refund", "complaint", "unknown"] # 用户身份,用于查订单等需要鉴权的操作 user_id: str | None # 检索到的知识片段 retrieved_docs: list[str] # 工具调用结果缓存 tool_results: dict # 置信度,低于阈值触发转人工 confidence: float # 是否需要人工介入 need_human: bool # 当前轮次,用于防止无限循环 turn_count: int这里有几个设计要点值得展开。messages用add_messages注解是 LangGraph 的标准做法,它保证多个节点往消息列表里追加内容时不会互相覆盖。intent用 Literal 限定取值范围,能在开发阶段就暴露拼写错误。turn_count是我强烈建议加的,没有轮次上限的多智能体系统迟早会陷入死循环,两个智能体互相觉得对方该处理,来回踢皮球。
confidence字段的引入是转人工逻辑的基础。它由生成节点在产出回答时一并给出,可以是模型自评,也可以是基于检索相似度的计算值。这个值不需要绝对准确,它的作用是提供一个可调节的阈值旋钮,让运营同学能根据实际投诉率去调松紧。
2.2 节点划分:宁可多切一刀,不要职责重叠
节点划分的原则是单一职责。我一般会切成这么几个节点,每个节点对应一个明确的动作:
| 节点名称 | 职责 | 输入 | 输出 |
|---|---|---|---|
| classify_intent | 意图分类 | 用户最新消息 | intent 字段 |
| retrieve_knowledge | 知识检索 | intent + 用户问题 | retrieved_docs |
| call_tool | 工具调用 | intent + 参数 | tool_results |
| generate_answer | 生成回答 | 上述所有 | messages + confidence |
| check_escalation | 转人工判断 | confidence + 情绪 | need_human |
| human_handoff | 人工交接 | 完整状态 | 工单记录 |
这里要特别说classify_intent和check_escalation为什么要分开。很多人的直觉是把转人工判断塞进生成节点里,让模型自己决定。但实测下来,让生成节点同时负责"好好回答"和"判断该不该放弃"是矛盾的,模型会倾向于前者。把转人工判断独立成一个节点,用独立的提示词和明确的规则(置信度阈值 + 情绪关键词 + 轮次上限),判断质量会稳定得多。
call_tool节点内部其实还可以再细分,比如订单查询、物流查询、退款申请各是一个子图。LangGraph 支持子图嵌套,当某个业务域的工具特别多时,把它封装成一个子图是很好的隔离手段。
2.3 条件边:让流程真正"活"起来的地方
节点是死的,边才是活的。LangGraph 的条件边(conditional edge)决定了状态在图上怎么流转。客服系统里最关键的几条条件边:
第一条是从classify_intent出发的路由。如果意图是 FAQ,走知识检索;如果是订单类,走工具调用;如果分类置信度本身就低,直接跳到转人工判断。这条边是整个系统的分流器,它的准确率直接决定后续所有环节的效率。
第二条是从generate_answer出发的质检边。生成完回答后,判断 confidence 是否低于阈值、是否命中敏感词、是否连续两轮被用户否定。任意一条命中就转向check_escalation。
第三条是从check_escalation出发的兜底边。如果need_human为真,走人工交接;否则回到对话主循环等待用户下一句。这里必须加一个turn_count上限判断,超过就强制转人工,防止死循环。
def route_by_intent(state: CustomerServiceState) -> str: if state["confidence"] < 0.5: return "check_escalation" mapping = { "faq": "retrieve_knowledge", "order": "call_tool", "refund": "call_tool", "complaint": "check_escalation", } return mapping.get(state["intent"], "check_escalation") graph.add_conditional_edges( "classify_intent", route_by_intent, { "retrieve_knowledge": "retrieve_knowledge", "call_tool": "call_tool", "check_escalation": "check_escalation", } )这段代码里有个细节:分类置信度低的时候直接跳过后续环节去转人工判断,而不是硬着头皮往下走。这是省成本的关键——一次错误的工具调用可能触发真实的退款操作,代价远高于直接转人工。
3. 智能体之间的协作协议:交接、校验与冲突消解
多智能体系统最容易翻车的地方不是单个智能体不够强,而是它们之间怎么交接。这一节专门讲协作层面的坑。
3.1 交接时到底该传什么:全量状态还是摘要
新手常见的做法是把整个 State 原封不动传给下一个节点,简单粗暴。但这样做的后果是上下文越来越长,模型注意力被稀释,而且容易把上一个节点的中间推理过程当成事实。
我的做法是分层传递:结构化字段(intent、user_id、tool_results)全量传,因为它们短且精确;对话历史传摘要加最近 N 轮原文,而不是全部原文。摘要由专门的节点生成,只保留"用户诉求、已确认事实、待解决问题"三要素。
这个摘要节点本身也是一个智能体,它的提示词可以很简单:"请从以下对话中提取用户的核心诉求、已经确认的事实、以及尚未解决的问题,用三句话概括。"实测下来,这个摘要能把长对话的 token 消耗压到原来的三分之一,同时几乎不损失关键信息。
3.2 工具调用结果的校验:别信模型说"调用成功了"
这是血泪教训。早期版本里,工具节点调用完接口后直接把结果丢给生成节点,生成节点有时候会"美化"结果——接口返回的是"订单不存在",它生成出来变成"您的订单正在处理中"。用户拿着这个回答去投诉,才发现系统在撒谎。
解决办法是在工具节点和生成节点之间加一道结果校验。校验逻辑不复杂:检查返回结构是否符合预期 schema、关键字段是否为空、错误码是否被正确识别。校验不通过就不进入生成环节,直接走异常分支。
def validate_tool_result(result: dict, expected_schema: dict) -> bool: for key, expected_type in expected_schema.items(): if key not in result: return False if not isinstance(result[key], expected_type): return False if result.get("error_code") not in (None, 0, "0"): return False return True校验失败时,不要急着让模型重试。先判断是参数问题还是系统问题:参数问题(比如用户没提供订单号)可以回问用户;系统问题(接口超时、权限不足)应该直接转人工,因为重试大概率还是失败。
3.3 两个智能体意见冲突时听谁的
多智能体系统里,冲突是常态。检索智能体说"知识库里没有相关内容",生成智能体却想编一个答案;情绪识别说"用户很生气",路由智能体却判断"这是普通咨询"。
处理冲突的原则是优先级明确 + 可追溯。我一般设定这样的优先级:安全规则 > 人工判断 > 工具事实 > 检索结果 > 模型生成。也就是说,当生成智能体想编答案但检索结果为空时,以检索结果为准,走"未找到"分支;当情绪识别判定为高愤怒值时,无论路由结果如何都提升转人工优先级。
这个优先级表要写进代码里,而不是靠提示词让模型自己权衡。凡是能用确定性代码表达的规则,就不要交给模型判断,这是多智能体系统稳定性的核心心法。
4. 转人工这件事,做不好前面全白搭
智能客服转人工,看起来是个小功能,实际上是整个系统体验的生死线。用户对智能客服最大的怨气就来自"绕圈子不给转人工"。这一节专门讲怎么把转人工做扎实。
4.1 转人工的触发条件要分层设计
不要只有一个"置信度低于阈值就转人工"的规则。真实场景里,转人工的触发条件至少分四层:
第一层是硬规则触发,比如用户直接说"转人工""找客服""投诉",这类关键词命中就立即转,不要犹豫,不要试图再回答一轮。用户明确要求转人工时还继续用机器人回复,是最招骂的行为。
第二层是置信度触发,连续两轮回答的置信度都低于阈值,或者用户连续两次否定回答("不是这个""你没听懂"),就转。
第三层是情绪触发,检测到愤怒、威胁投诉、提及监管等信号,提升转人工优先级。
第四层是轮次触发,单次会话超过 N 轮仍未解决,强制转人工。这个 N 我一般设 6 到 8,具体看业务复杂度。
4.2 转人工不是"甩锅",交接信息要完整
很多系统的转人工就是弹一句"正在为您转接人工客服",然后人工坐席接起来一脸懵,什么上下文都没有,用户得从头再讲一遍。这种转人工等于没转。
正确的做法是生成一份结构化的交接单,包含:用户身份、核心诉求、已确认事实、已尝试的方案、当前情绪状态、建议处理方向。这份交接单在人工坐席的工作台上直接展示,坐席扫一眼就能接上话。
def build_handoff_ticket(state: CustomerServiceState) -> dict: return { "user_id": state["user_id"], "intent": state["intent"], "summary": state.get("conversation_summary", ""), "confirmed_facts": state.get("tool_results", {}), "attempted_solutions": state.get("attempted", []), "emotion": state.get("emotion", "neutral"), "turn_count": state["turn_count"], "suggested_action": state.get("suggested_action", ""), }这份交接单的价值在于,它把智能体这一轮"白干"的工作变成了人工坐席的起点,用户感知到的是"这个系统懂我",而不是"又要重讲一遍"。
4.3 转人工后的状态回传:闭环才算完整
人工处理完之后,结果要能回写到系统里。一方面用于统计(哪些问题智能体搞不定,是优化提示词的依据),另一方面用于训练(这些真实的人工处理记录是微调数据的金矿)。
我一般会在人工工单关闭时,把"问题类型 + 人工解决方案 + 是否可自动化"三个字段回写。积累几百条之后,你会发现有些高频问题其实是可以自动化的,只是当初提示词没写好;也会发现有些问题确实必须人工,那就别硬撑,把转人工阈值调松一点。
5. 知识检索与提示词工程:让回答有据可依
多智能体架构搭好了,回答质量的天花板其实由知识检索和提示词决定。这一节讲这两块的实战细节。
5.1 检索不是"向量搜索一把梭"
很多人做知识库检索就是文档切块、向量化、相似度 top-k。这套在 FAQ 场景勉强能用,但在客服场景经常翻车,因为客服问题往往需要精确匹配 + 语义匹配结合。
我的做法是混合检索:先用关键词(BM25 或倒排索引)召回一批,再用向量检索召回一批,两批结果做融合排序。关键词检索能抓住"订单号""退款""发票"这类精确术语,向量检索能抓住"我买的东西还没到"这种口语化表达。两者互补,召回率明显提升。
切块策略也很关键。客服知识库的文档往往有清晰的层级(政策 > 条款 > 细则),切块时要保留层级路径,让每个块都知道自己属于哪个政策下的哪一条。检索命中后,把层级路径一起喂给生成节点,模型就能准确引用"根据《退款政策》第 3.2 条"。
5.2 提示词要写"边界",不只是写"任务"
写客服提示词,新手只写"你是一个客服助手,请友好地回答用户问题"。这种提示词等于没写。真正有用的提示词必须包含边界条件:
- 什么情况下必须说"我不确定",而不是猜
- 什么情况下必须引用知识库原文,而不是转述
- 什么情况下必须拒绝回答(比如涉及账户安全、法律建议)
- 回答长度限制、语气要求、禁止使用的表达
我习惯把提示词分成三段:角色与能力边界、回答规范、输出格式。输出格式那段尤其重要,因为下游节点要解析生成结果里的 confidence 字段,格式不稳定会直接导致解析失败。
5.3 上下文工程:把对的信息在对的时机喂进去
上下文工程(Context Engineering)是这两年从提示词工程里分化出来的概念,核心是动态决定每一轮该给模型看什么。客服场景里,同一个模型在不同轮次需要的上下文完全不同:第一轮需要意图分类的指引,中间轮需要工具结果,最后一轮需要转人工的判断依据。
我的做法是给每个节点配一个独立的上下文组装函数,只组装这个节点需要的信息。这样既省 token,又避免无关信息干扰判断。比如classify_intent节点只需要最近一轮用户消息加意图分类的 few-shot 示例,完全不需要历史对话和知识库。
6. 上线之后才会暴露的那些坑
架构、协作、转人工、检索都讲完了,最后聊聊上线后才会遇到的问题。这些是文档里不会写、只有真跑起来才知道的东西。
6.1 冷启动阶段的"答非所问"高峰
系统刚上线的前两周,答非所问率会明显高于稳定期。原因不是模型不行,而是真实用户的问题分布和你准备的测试集完全不一样。你测了一百个"标准问法",用户上来问的是"我昨天那个事儿咋样了"。
应对办法是上线初期把转人工阈值调得很松,宁可多转人工,也不要让用户被错误回答激怒。同时密集收集转人工的工单,从中提炼真实问法,补充到测试集和 few-shot 示例里。一般两周到一个月,答非所问率会降到可接受水平。
6.2 多轮对话里的"记忆漂移"
长对话到第 8 轮以后,模型对前面确认过的事实的记忆会开始漂移。用户明明说过"我要退的是那件蓝色的",到后面模型可能记成"红色的"。这不是模型缺陷,是长上下文固有的注意力衰减。
解决办法是关键事实显式化。在 State 里维护一个confirmed_facts字典,每当用户确认一个关键信息(订单号、商品、金额),就写进去,并在后续每轮生成时把这份事实清单放在提示词靠前的位置。用结构化的方式对抗注意力衰减,比指望模型自己记住靠谱得多。
6.3 成本控制:不是所有请求都值得用大模型
多智能体系统跑起来之后,token 消耗会比你想象的高,因为一次用户提问可能触发四五个节点的模型调用。我的经验是分级处理:简单 FAQ 用便宜的小模型甚至规则匹配,复杂问题才走完整的多智能体流程。
具体做法是在入口加一个轻量分类器,判断问题复杂度。简单问题直接走单节点快速通道,复杂问题才进主图。实测下来,这个分流能省掉 40% 到 60% 的 token 成本,而用户体验几乎无差别。
6.4 观测与迭代:没有日志就没有优化
最后强调一点,多智能体系统必须每个节点都打日志:输入、输出、耗时、置信度、走了哪条边。这些日志是你后续优化的唯一依据。没有日志,你连"系统为什么答错"都说不清,更别提优化了。
我一般会把日志按会话 ID 聚合,做成可视化的对话轨迹图,运营同学可以直接看到一次会话在图上走了哪些节点、在哪一步出了问题。这个工具做出来之后,迭代效率会提升一个量级。
这套多智能体客服系统我从零搭到稳定运行,前后迭代了大概三个月,最大的体会是:架构的复杂度要花在"可观测、可干预、可回退"上,而不是花在"让模型更聪明"上。模型能力会随着版本更新自然提升,但一个职责清晰、交接规范、兜底扎实的架构,才是系统能长期稳定跑下去的根本。转人工阈值、轮次上限、置信度阈值这几个旋钮,上线后一定要留出运营可调的接口,因为真实业务的变化速度,永远比你写代码的速度快。