系列:AgentScope 2.0 学习笔记 · 第 008 篇
难度:进阶
适合谁:客服 Agent 已经能跑、但觉得「一个 Agent 干所有活」不对劲的开发者
前置:建议先读 007《双剑合璧——工具 + RAG,做一个会查会答的客服》
阅读收获:用意图路由把「单兵客服」拆成一队,各司其职
运行环境:WSL Ubuntu-24.04 + Python 3.11+ + AgentScope 2.x


你见过这种「一根筋」的客服机器人吗?用户说「我用了你们家产品过敏了,要退货」,它张口就来「那换这款试试吧,这款更温和」——人家来退货,它还在推销。反过来,用户说「我想买瓶面霜」,它先问「您是要退货吗」。

这不是模型笨,是让一个人干了两个人的活。真实公司里,销售和售后是两个部门、两套话术、两种考核——你见过让同一个柜员既卖货又处理投诉的吗?有,但一定两个都干不好。这一篇,就是把「全包型单兵客服」拆成一队:一个路由器负责判断来的是谁,导购 Agent 和售后 Agent 各接各的活。


一、007 的客服,其实是个「全包型单兵」

007 篇,我们拼出了一个会查会答的客服。但它有个隐藏的问题:它什么都得会。

用户来咨询买什么 → 它答;用户来投诉过敏要退货 → 它也得答。可「导购」和「售后」是两种截然不同的活儿:

  • 导购要热情:了解需求、推荐产品、促成下单;
  • 售后要克制:先安抚、再处理、复杂转人工。

让一个 Agent 既热情推销、又冷静安抚,就像让一个人既当销售又当客服——顾此失彼。用户说「过敏了」,它可能还热情推荐「那换这款试试」;用户说「想买」,它可能先问「您是要退货吗」。

这就是「全包型单兵」的天花板。破局的办法,就是一分为多


二、一分为多:让专业的 Agent 干专业的活

思路很朴素:与其让一个 Agent 什么都懂,不如让每个 Agent 只懂一件事

  • 导购 Agent「小帮」:只管推荐、报价、搭配;
  • 售后 Agent「小护」:只管安抚、退换、转人工。

每个 Agent 的人设(system_prompt)都聚焦一件事,回答自然更专业、更不容易串味。

这个思路,本质上就是把真实公司的组织架构搬进代码:销售部一个 Agent、售后部一个 Agent,各有一套「岗位说明书」(人设卡),各守各的红线。你在真实业务里怎么分工,Agent 就怎么分工——这是多 Agent 设计最容易上手的入口:先从组织架构图里抄。 销售和售后是两大部门,所以你第一步就拆出这两个。

但拆成多个 Agent 后,冒出个新问题:用户进线,先找谁? 这就是「意图路由」要解决的——先判断用户意图,再派给对的 Agent。


三、意图路由:规则快,LLM 兜底

意图路由的核心,是一个「分流器」:读用户的话,判断是「导购」还是「售后」,然后派单。

怎么判断?两层:

第一层:规则(关键词)。 用户的话里有「多少钱」「推荐」「买」→ 导购;有「过敏」「退货」「投诉」→ 售后。关键词命中就完事,快、省成本(不调模型)。

第二层:LLM(兜底)。 规则判断不了的模糊话(「我最近皮肤状态不太好」),交给模型判断意图。

为什么「规则优先、LLM 兜底」?因为规则零成本、零延迟、可解释;LLM 灵活、能理解语义。两者结合,既快又准——这正是系列里反复出现的「LLM 决策 + 规则校验」双层架构,在路由场景的落地。

这里要特别强调「规则优先」的顺序——别图省事每次都上 LLM。路由是用户每句话都要经过的,如果每句都调一次模型,就是「用大炮打蚊子」:又慢又贵。真实客服一天几万次进线,规则层能挡掉七八成,省下的就是真金白银。成本意识,要从架构的第一笔开始算。

顺带说一句:路由的「意图」不止导购/售后两类。真实系统里,你可以再分出「查订单」「转人工」「闲聊」等更多意图,每加一类就加一个专职 Agent 和一组关键词。路由的骨架不变,变的只是意图清单和对应的 Agent 列表——这就是多 Agent 架构的扩展方式。


四、规则层的经典坑:否定句

规则路由看着简单,其实有个坑,专门坑新手:否定句

用户说「我没有过敏,就是想买个洗面奶」——里面明明有「过敏」这个售后关键词,但用户其实是想买(导购)。

如果不处理,规则会傻乎乎地把「想买洗面奶」的用户丢给售后。所以规则层要加否定词过滤:关键词前面如果紧跟着「不、没有、没、无」这类否定词,就跳过这个关键词。

这个坑我在学习里真踩过,写代码时专门做了修复。你在写自己的路由时,一定记得加这一层——关键词匹配的朴素方案,永远过不了否定句这一关。

不过也要认清规则的边界:否定词过滤只管「关键词前紧邻的否定词」,管不了更复杂的转折和反问。比如「这款控油但不紧绷」里的「控油」是肯定语义、「但」只是转折;再比如「我不太清楚自己是不是过敏」这种绕弯子的话。这些复杂情况,正是交给 LLM 兜底层去处理的——规则管「快而准的常见情况」,LLM 管「规则的盲区」。


五、运行环境

  • 系统:WSL Ubuntu-24.04
  • Python:3.11+,虚拟环境 venv
  • 框架:AgentScope 2.x
  • API KeyDEEPSEEK_API_KEY(对话;本篇不涉及 Embedding,无需百炼)
wsl -d Ubuntu-24.04
cd /2026_Study/agentscope/
source venv/bin/activate

export DEEPSEEK_API_KEY="sk-xxxx"

python 008_multi_agent.py

六、完整代码(单文件自包含)

保存为 008_multi_agent.py。一个路由器 + 两个专职 Agent,对三类问题分别派单。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AgentScope 2.0 · 一分为多——意图路由与多 Agent 协作
  导购 Agent「小帮」+ 售后 Agent「小护」+ 双层意图路由(规则关键词 + LLM 兜底)。
运行环境:WSL Ubuntu-24.04 + Python 3.11+ + AgentScope 2.x
运行命令:python 008_multi_agent.py
"""

import os
import gc
import asyncio
import warnings

warnings.filterwarnings("ignore", category=DeprecationWarning)

from agentscope.agent import Agent
from agentscope.message import Msg
from agentscope.model import DeepSeekChatModel
from agentscope.credential import DeepSeekCredential


# 1. 两个专职 Agent 的人设
SALES_PROMPT = """
你叫【小帮】,是「敏辰」的专业护肤导购。
回答流程:先了解肤质需求 → 匹配产品 → 说明价格成分 → 关联推荐 → 红线检查。
⚠️ 红线:不承诺疗效、不贬低其他品牌。
""".strip()

AFTERSALES_PROMPT = """
你叫【小护】,是「敏辰」的售后客服。
回答流程:先安抚 → 了解情况 → 分级处理 → 确认满意 → 复杂转人工。
⚠️ 红线:不推卸责任、不过度承诺、人身伤害必须转人工。
""".strip()


# 2. 规则层:意图关键词 + 否定词过滤
SALES_KEYWORDS = ["推荐", "适合", "价格", "多少钱", "成分", "搭配", "买", "哪款", "肤质"]
AFTERSALES_KEYWORDS = ["过敏", "退货", "退款", "物流", "快递", "投诉", "换货", "破损", "质量"]

NEGATION_WORDS = ["不", "没有", "没", "无", "并非", "不会"]


def rule_route(question: str):
    """规则层:关键词命中判断意图。返回 'sales' / 'after_sales' / None。

    关键技巧:否定句过滤——「我没有过敏想买产品」里的「过敏」被「没有」否定,
    不应判为售后。关键词前紧邻否定词则跳过,继续找下一个。
    """
    for k in AFTERSALES_KEYWORDS:
        idx = question.find(k)
        while idx != -1:
            prefix = question[max(0, idx - 3):idx]
            if not any(n in prefix for n in NEGATION_WORDS):
                return "after_sales"
            idx = question.find(k, idx + len(k))
    for k in SALES_KEYWORDS:
        if k in question:
            return "sales"
    return None  # 规则判断不了,交给 LLM 层


# 3. LLM 层:兜底意图判断
async def llm_route(model, question: str) -> str:
    """LLM 层:规则无法判断时,用模型判断意图。"""
    router = Agent(
        name="意图路由",
        model=model,
        system_prompt=(
            "你是意图路由器。判断用户问题属于「导购」(咨询产品/推荐/价格)"
            "还是「售后」(退换/过敏/物流/投诉)。只回复 sales 或 after_sales。"
        ),
    )
    msg = Msg(name="user", content=[{"type": "text", "text": question}], role="user")
    response = await router.reply(msg)
    result = extract_text(response).strip().lower()
    return "after_sales" if "after" in result or "售后" in result else "sales"


# 4. 模型构建 + 收尾
def build_model():
    """对话模型(DeepSeek,stream=False 干净退出)。"""
    return DeepSeekChatModel(
        credential=DeepSeekCredential(api_key=os.environ["DEEPSEEK_API_KEY"]),
        model="deepseek-v4-flash",
        stream=False,
    )


def extract_text(response) -> str:
    for block in response.content:
        if hasattr(block, "type") and block.type == "text":
            return block.text
    return ""


async def cleanup(model):
    """收尾:逐个 await self.client.close()(干净退出三件套)。"""
    closed = 0
    client = getattr(model, "client", None)
    if client is not None:
        try:
            await client.close()
            closed += 1
        except Exception:
            pass
    if closed:
        print(f"\n🧹 已关闭 {closed} 个 HTTP 连接池")


# 5. 意图路由 + 多 Agent 协作演示
async def demo(model) -> None:
    sales_agent = Agent(name="小帮", model=model, system_prompt=SALES_PROMPT)
    after_agent = Agent(name="小护", model=model, system_prompt=AFTERSALES_PROMPT)

    test_questions = [
        "你们家的美白精华多少钱?",        # 导购:规则命中「多少钱」
        "我用了产品过敏了,想退货",        # 售后:规则命中「过敏/退货」
        "我最近皮肤状态不太好",            # 模糊:规则无法判断 → LLM 兜底
    ]

    print("=" * 60)
    print("  一分为多:意图路由 + 多 Agent 协作")
    print("=" * 60)

    for q in test_questions:
        print(f"\n{'─' * 60}")
        print(f"  👤 用户:{q}")

        intent = rule_route(q)
        route_source = "规则层"
        if intent is None:
            intent = await llm_route(model, q)
            route_source = "LLM 层"

        target = sales_agent if intent == "sales" else after_agent
        target_name = "小帮(导购)" if intent == "sales" else "小护(售后)"
        print(f"  🧭 路由:{route_source}{target_name}")

        msg = Msg(name="user", content=[{"type": "text", "text": q}], role="user")
        response = await target.reply(msg)
        print(f"  💬 回答:{extract_text(response)}")

    print(f"\n{'=' * 60}")
    print("  ✅ 意图路由 + 多 Agent 协作演示完成")
    print("=" * 60)


def check_keys():
    need = ["DEEPSEEK_API_KEY"]
    missing = [k for k in need if not os.environ.get(k)]
    if missing:
        print("❌ 缺少环境变量:" + ", ".join(missing))
        return False
    return True


async def main():
    if not check_keys():
        return

    model = build_model()

    try:
        await demo(model)
    finally:
        await cleanup(model)
        del model
        gc.collect()


if __name__ == "__main__":
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", DeprecationWarning)
        warnings.simplefilter("ignore", FutureWarning)
        warnings.simplefilter("ignore", PendingDeprecationWarning)
        asyncio.run(main())

七、代码拆解:三个关键点

① 人设决定分工。 两个 Agent 的 system_prompt 截然不同——小帮「先了解肤质→匹配→报价→关联」,小护「先安抚→了解→分级→转人工」。同一个模型,人设不同,就是两个不同的「岗位」。这印证了 003 篇那句话:换一张人设卡,就是换一个人。

② 规则优先、LLM 兜底。 rule_route() 先跑关键词,命中就直接返回,一次模型都不调;只有返回 None 时,才进 llm_route() 调模型。这个顺序不能反——如果每次都让 LLM 判断,路由就成了「用大炮打蚊子」,又慢又贵。

③ 否定词过滤是规则的「安全带」。 rule_route() 里那段 while 循环,专门处理「关键词前有否定词」的情况。没有它,规则路由在真实对话里会频繁翻车——因为「我没有过敏」和「我过敏了」只差一个字,意图却完全相反。


八、运行结果(真实输出)

下面是本文件在 WSL Ubuntu-24.04 里的真实运行结果。三类问题,三种路由路径,全部踩在点子上。

Case 1(导购) —— 规则层命中「多少钱」,零成本路由到小帮:

👤 用户:你们家的美白精华多少钱?
🧭 路由:规则层 → 小帮(导购)
💬 回答:在给您介绍价格之前,我需要先了解您的皮肤状况……请问您目前的肤质是
偏干、偏油还是混合型?这样我可以为您推荐最适合的美白精华方案。

Case 2(售后) —— 规则层命中「过敏/退货」,路由到小护:

👤 用户:我用了产品过敏了,想退货
🧭 路由:规则层 → 小护(售后)
💬 回答:非常理解您现在的心情,请先不要着急,我们一定会帮您妥善处理……
如果是严重的过敏反应,尤其是涉及面部、呼吸等,请务必立即就医。

Case 3(模糊) —— 规则层判断不了,LLM 层兜底,路由到小帮:

👤 用户:我最近皮肤状态不太好
🧭 路由:LLM 层 → 小帮(导购)
💬 回答:皮肤状态不好是很多原因的……方便具体说说吗?您是暗沉发黄、局部泛红、
还是干起皮、偏油闭口?

最后一行 🧹 已关闭 1 个 HTTP 连接池,程序干净退出、零收尾报错。

三个回答,三种味道,正是「一分为多」的成果

  1. 路由正确:前两问走「规则层」(零成本、零延迟),第三问走「LLM 层」(规则盲区,模型兜底)——两层分工清晰。
  2. 人设鲜明:导购「小帮」先问肤质、不盲目推贵的;售后「小护」先安抚、了解情况、严重过敏提醒就医。同一个模型,两张人设卡,就是两个截然不同的「岗位」——这正是 003 篇「换人设卡就是换人」的实战印证。
  3. 红线守住:售后那句「严重过敏请务必立即就医」,正是售后人设里「人身伤害必须转人工」这条红线的体现。

九、总结与下一步

这一篇,你把「单兵客服」拆成了「一支队伍」:

单兵(007)= 一个 Agent 全包,顾此失彼
一分为多(本篇)= 路由器 + 导购 + 售后,各司其职

核心就一句:让专业的 Agent 干专业的活,用意图路由做分流。 路由的秘诀是「规则快、LLM 兜底」,再加一层「否定词过滤」保平安。

但「分工」只是多 Agent 协作的第一课。分工之后,还有更进阶的玩法:让多个 Agent 对同一个问题各自作答、再投票汇总,用「多数人的智慧」压住单个模型的随机性。下一篇《AgentScope 2.0 学习笔记:集思广益——多 Agent 投票与结果汇总》,我们把这套「对答案」的机制搭起来。敬请期待。


十、写在最后:你的业务有几条线,就该有几个 Agent

这一篇讲的是「一分为多」,但它背后其实是一个更值钱的问题:怎么判断该拆成几个 Agent?

我的经验是八个字:跟着业务走,别跟着技术走。 你打开自家客服的组织架构图——有几个工种,就拆几个 Agent。销售一条线、售后一条线、订单一条线,就拆三个;再加上意图路由当「前台」,一个人力兜底当「总机转人工」,一套客服系统就成型了。拆多了维护累,拆少了串味,「业务里怎么分工,代码里就怎么分」是最不容易出错的尺度。

这也正是我做 Agent 落地项目时的习惯:先画组织架构,再写代码。如果你正在设计自己的客服/业务系统,卡在「该拆几个 Agent、路由怎么设计」这类问题上,欢迎来评论区讨论。下一篇「多 Agent 投票」见。


本文为「AgentScope 2.0 学习笔记」系列第 008 篇,代码已通过 py_compile 语法校验,运行环境见第五节。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐