意图识别:放主 Agent、做 Skill,还是单独一个 Agent?
上一篇写完那 9 个 Agent 之后,有个问题我一直没正面回答。
那篇里我把「意图路由」单独拎出来占了一个位置,理由是"它要的是快和准,不能带业务上下文"。写完自己再看,这个理由其实只说了一半——它解释了为什么路由要轻,没解释为什么它不能是主 Agent 的一个思考步骤,也不能是一个 Skill。
这两个问法更狠。因为如果你答不上来,那"单独拆一个 Agent"就只是个习惯,不是个决策。
这篇把这个问题想清楚。顺带回答一个反方向的问题:意图识别和问题改写,这两件事该不该拆成两个 Agent。
一、先把三个方案摆清楚
同一个需求——判断用户这句话想干什么——有三种放法:
- A. 放进主 Agent:在系统提示里写"你首先要判断用户意图,然后……",意图识别成为主 Agent 推理的一部分。
- B. 做成 Skill:把意图分类的规则、意图清单、边界案例写成一个 SKILL.md,主 Agent 按需加载。
- C. 单独一个 Agent:独立的路由 Agent,输入会话历史和当前 query,输出结构化的意图。
看起来是"放哪里"的问题,其实是"你要的是能力还是结构"的问题。这个区分是全文的关键,先说清楚。
Anthropic 在 Skills 的官方说明里给了两句话,我觉得是这个话题最准确的边界:
Use Skills when: You want capabilities that any Claude instance can load and use. Skills are like training materials.
Use subagents when: You need complete, self-contained agents designed for specific purposes that handle workflows independently. Subagents are like specialized employees with their own context and tool permissions.
Skill 是训练材料,谁都能加载;Agent 是有自己上下文和工具权限的专门员工。
而技术上的分界线只有一条,也是官方原文:
Skills do not create a new context window. They add instructions and files into the existing conversation when relevant.
Skill 不创建新的上下文窗口,它把内容塞进当前对话。 这一条决定了后面所有的推论。
二、为什么不放进主 Agent
理由按我觉得的硬度排序。
1. 它是每一轮必跑的,但主 Agent 不是每轮都需要
客服会话里,用户第二轮的"那我这个订单能退吗"需要改写才能查;但"好的谢谢"、"嗯"这种,根本不需要走完整推理链。
放进主 Agent 之后,你没法区分这两种情况——意图识别变成了主 Agent 推理的一部分,每轮都付全价。独立出来之后,你可以在路由层就识别出"闲聊/确认/结束语"这类意图,直接走快通路,不进主流程。
2. 用主模型做分类,是浪费
意图识别本质上是个分类任务。意图种类有限、输出是枚举值、不需要业务知识、不需要工具。这活儿小模型就能干,甚至一个 fine-tune 过的 BERT 分类器都能干得比大模型好。
放在主 Agent 里,你被迫用主模型做分类——等于请一个架构师去贴发票。
这不是能力问题,是单价问题。而这个单价是每一轮对话都要付的。
3. 没有独立的挂载点
这一条我觉得是最实际的。独立成一个环节之后,你可以单独给它加:
- 缓存:高频 query 的意图是可以缓存的。"怎么退货"、"退货政策"、"能退吗"——这三个的意图 100% 是同一个。客服场景的 query 分布高度集中,缓存命中率会高得超出预期。
- 降级:意图服务超时或挂了,fallback 到"沿用上一轮意图"或"转人工",主流程不至于死。
- 限流和熔断:这是入口,可以单独保护。
- 换模型:分类模型换掉,只影响分类准确率这一个指标。
放进主 Agent,上面四件事一件都做不了——它没有独立的边界,你没法只针对它做操作。
4. 没法单独评测
意图识别是少数几个能拿到确定性指标的环节:给你 2000 条标注数据,算出准确率、召回率、混淆矩阵,哪个意图容易误判一目了然。
塞进主 Agent 之后,你只能测端到端。用户答错了,你说不清是意图判错了、检索召回了错的、还是推理跑偏了。我在上一篇里说过,能定位的问题才是能修的问题。
5. 指令越多,每条指令的遵从率越低
系统提示里既有"你要先判断意图",又有业务规则、回复风格、工具使用、合规要求。这些指令共享同一个注意力预算,条数越多,每条被严格执行的概率越低。
这个现象和上一篇讲的工具数量是同一个道理——选择空间变大,判断质量下降。你可以自己测:拿 20 条固定指令跑一次,再加 20 条跑一次,对比单条指令的遵从率。
意图识别要求的是"冷静、客观、按枚举值分类",而客服回复要求的是"共情、耐心、口语化"。这是两种相反的心智模式,硬塞进一个 prompt,两边都会打折。
6. 输出需要确定性结构
意图识别的输出应该是一个干净的枚举值加置信度:{intent: "refund", confidence: 0.92}。这个字段要被后面的代码消费——路由、限权、埋点全靠它。
独立的 Agent 可以强制 JSON schema 输出、加校验、失败重试。放在主 Agent 的推理过程里,意图只是它"想了一下"的中间产物,你拿不到一个可靠的结构化字段。
三、为什么不做成 Skill
这个更值得说,因为 Skill 看起来是个很优雅的答案——按需加载、省上下文、可复用。
1. Skill 是可选的,意图识别是必经的
这是最根本的一条。
Skill 的机制是:Agent 读到 description,判断"这次用不用得上"。这对于"分析退款异常"、"生成周报"这类能力完全合适——漏了顶多少做个功能。
但意图识别没有"用不上"的情况。用户每说一句话,你必须知道他要干什么,否则整个流程不知道往哪走。
把它做成 Skill,等于让模型决定"我这次要不要做意图识别"。问题是,这个决定本身就是意图识别。这是个死循环。
2. 必经环节不能靠模型的自觉
Skill 的触发依赖模型读 description 后自己判断相关性。这是概率性的,不是架构保证的。
对于可选能力,偶尔漏一次无所谓。对于流程入口,漏一次就是整个方向走错——用户要退货,被当成咨询,回复驴唇不对马嘴。
架构级的保证和 prompt 级的请求,是两种可靠性的东西。 入口环节要的是前者。
3. Skill 不隔离上下文(关键)
回到开头那条官方原文:Skill 不创建新上下文,它把内容加进当前对话。
这意味着什么?意味着意图识别的推理过程会留在主上下文里,继续影响后面的每一步。
独立 Agent 给下游的是一个结论——intent=refund。Skill 给主 Agent 的是一整套思考材料:"用户说了'那我这个订单能退吗',结合上一轮提到的退货政策,考虑到他之前问过物流……"这些中间推理会一直挂在上下文里,成为后续推理的"已知事实"。
这正是我在上一篇里花大篇幅讲的上下文污染。用 Skill 承载意图识别,等于花力气省了 token,却把最需要隔离的东西留在了共享上下文里。
顺便说一句,我自己那篇 Agent Skills 的文章里贴过 Skill 加载器的实现,最后一步是这样的:
<code class="language-java">return chain.nextCall(req.mutate().system(s -> s + """
## 当前激活的技能:%s
%s
""".formatted(decision.name(), skillRegistry.loadFull(decision.name())))
.build());
</code>
看清楚了——直接拼进系统提示词。加载完之后,Skill 的内容和主 Agent 的其他指令没有任何区别,都在同一个上下文里。这不是 Skill 的设计缺陷,它就是这么设计的,Skill 本来就不是用来做隔离的。
4. Skill 同样没有独立挂载点
第三条里说的缓存、降级、限流、换模型,Skill 一样给不了。它不是独立进程,不是独立服务,没有可以挂东西的边界。
5. 呼应一句我自己写过的话
我在那篇 Agent Skills 的文章结尾写过:"做 POC 可以,别把核心流程绑上去。"
当时这句话是就规范稳定性说的——Skill 规范那两个月改了两次,元数据必填项变过,allowed-tools 字段名变过。
这次想清楚意图识别的归属之后,我发现这句话还有第二层意思:就算规范完全稳定了,核心流程也不该用 Skill 承载。 因为 Skill 解决的是"能力按需加载",不是"结构隔离"。这两件事从来就不在一个层面上。
四、三种方案放一起看
| 维度 | A 放主 Agent | B 做 Skill | C 独立 Agent |
|---|---|---|---|
| 上下文隔离 | 无 | 无 | 有 |
| 可换小模型 | 不能 | 不能 | 能 |
| 可加缓存 | 不能 | 不能 | 能 |
| 可单独降级 | 不能 | 不能 | 能 |
| 可单独评测 | 不能 | 勉强 | 能 |
| 输出结构化 | 弱 | 弱 | 强(可强制 schema) |
| 省上下文 | — | 是(主要卖点) | — |
| 适合什么 | 意图 < 5 种、单轮为主 | 长尾可选能力 | 必经的入口环节 |
注意 B 那一列:它在"省上下文"上是赢的,其他几项基本全输。Skill 的价值就在省上下文,别指望它解决结构问题。
五、那 Skill 该放什么
不是"意图识别该不该做 Skill",而是"意图识别是结构,意图的处理知识是能力"。这两件事可以叠加。
具体来说:
- 意图识别本身 → 独立 Agent(结构,必经,需要隔离和限权)
- 每种意图的处理流程 → Skill(能力,可选,内容长)
举个例子。「退货政策咨询」这个意图的处理,可能要加载:哪些商品不支持 7 天无理由、特价商品怎么算、用过优惠券怎么退、赠品要不要退……这一套规则写下来几千字,而且只有命中这个意图时才需要。
这个就非常适合做成 Skill:路由 Agent 判定 intent=refund_policy → 处理 Agent 加载 refund-policy 这个 Skill → 按规则回答。
你看,结构和能力各归各位。官方也给了同样的组合建议:subagent 可以有自己的 Skills,两者是叠加关系。
一个判断方法:如果你描述这件事的时候用的是"先做什么后做什么",它是流程,是结构;如果你用的是"遇到这种情况要注意什么",它是经验,是能力。
六、反过来:意图识别和问题改写,该拆成两个吗
前面都在讲"为什么要拆"。但这个问题是反方向的。
问题改写(query rewrite)是把"那我这个订单能退吗"改写成"订单 2026090112345 是否支持退货"。它和意图识别常常被当成两件事,但我觉得不该拆,理由是:
它们共享同一份输入。 两件事都要读会话历史 + 当前 query。拆成两个 Agent,这份历史要加载两次、传两次,中间的"当前正在讨论哪个订单"这类状态要在两者之间同步。
它们的输出是互补的。 意图识别输出"要干什么",问题改写输出"具体干哪个"。下游拿到这两个字段才能干活,缺一个都走不下去。分开之后你还得再写一层合并逻辑。
它们需要同一种心智。 都是"冷静地理解这句话",不带业务判断,不带情绪。
它们都不需要工具。 纯语言理解任务,可以共用一个小模型,一次调用把两件事一起做了。
所以我的建议是把它们合并成一个 Query Understanding(查询理解)Agent,一次调用输出 {intent, rewritten_query, slots, confidence}。
这给上一篇的五问清单补了第六问:
这两件事共享同一份输入、输出互补吗? 如果是,别拆——拆开只会多一次调用、多一次状态同步、多一个出错点。
七、什么时候确实可以放进主 Agent
讲了这么多,得说实话:很多场景下放进主 Agent 完全没问题,别为了架构而架构。
- 意图种类少于 5 种,且语义距离大。 "查订单""退货""投诉""其他"——主 Agent 顺手就分了,不需要单独一层。
- 单轮为主。 没有多轮会话,也就没有"改写"这回事,意图识别的复杂度降一个数量级。
- 日均请求量很小。 成本不是约束,独立部署一套路由服务的运维成本反而更高。
- 你还在验证需求阶段。 这时候最重要的是快速改,不是架构正确。先写在一起,等意图种类涨到十几个、开始出现误判了再拆——拆的时机看的是痛点,不是规划。
最后一条我想强调:架构应该长在痛点上,不是长在图纸上。 我那 9 个 Agent 是"长出来的",这句话我上一篇说得有点自嘲,其实它没什么丢人的——只要长出来之后你想清楚了为什么,它就不比一开始就设计好的差。
八、判断标准
把上一篇的五问补成六问。下次要决定某件事放主 Agent、做 Skill、还是独立 Agent,按顺序问:
- 它是不是必经的? 是 → 不考虑 Skill(Skill 是可选的)。
- 它需要上下文隔离吗? 它的中间推理留在主上下文里会造成污染吗?是 → 独立 Agent。
- 它需要独立的挂载点吗? 缓存、降级、限流、换模型、单独埋点,有一样是刚需 → 独立 Agent。
- 它能单独评测吗? 能不能给它做一个单独的准确率指标?能且值得 → 独立 Agent。
- 它的内容长不长、是不是只在特定情况才需要? 是 → Skill。
- 它和其他事情共享输入、输出互补吗? 是 → 合并成同一个 Agent。
一句话收尾:Skill 给 Agent 加的是"能力",拆 Agent 给系统加的是"结构"。你要的是新能力还是新结构,先想清楚这个,放哪里就不是问题了。
写完这篇我才发现,上一篇里"意图路由"那一个格子,其实应该叫"查询理解"——它里面装了两件事。名字改了,位置不用动。这可能就是复盘的价值:不是推翻之前的决定,是给它一个更准确的说法。
[]