
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_docscall_tool工具调用intent 参数tool_resultsgenerate_answer生成回答上述所有messages confidencecheck_escalation转人工判断confidence 情绪need_humanhuman_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 聚合做成可视化的对话轨迹图运营同学可以直接看到一次会话在图上走了哪些节点、在哪一步出了问题。这个工具做出来之后迭代效率会提升一个量级。这套多智能体客服系统我从零搭到稳定运行前后迭代了大概三个月最大的体会是架构的复杂度要花在可观测、可干预、可回退上而不是花在让模型更聪明上。模型能力会随着版本更新自然提升但一个职责清晰、交接规范、兜底扎实的架构才是系统能长期稳定跑下去的根本。转人工阈值、轮次上限、置信度阈值这几个旋钮上线后一定要留出运营可调的接口因为真实业务的变化速度永远比你写代码的速度快。