在 AI 应用里,把一段能力封装成 Skill,还是交给独立的 Sub-Agent,并不只是命名问题。这个选择会直接影响系统的上下文管理、调试难度、复用方式和长期维护成本。Azure 架构实践提出的核心判断标准很务实:优先考虑可复用性、简单性和长期可维护性,而不是默认把所有复杂任务都拆成多个 Agent。
Skill 和 Sub-Agent 解决的是不同问题
可以把 Skill 理解为主 Agent 可直接调用的一项受控能力。它通常有清晰输入和输出,执行边界稳定,不需要独立维护长期上下文。例如:
- 查询订单状态
- 生成 SQL 并执行只读查询
- 从文档中提取结构化字段
- 按固定规则检查代码或配置
Sub-Agent 则更接近一个被委派任务的独立执行者。它可以拥有自己的提示词、工具集合、上下文和执行循环,适合需要多步规划、反复验证或专业隔离的任务。例如,一个代码审查 Sub-Agent 可能需要读取多个文件、运行测试、分析差异,再汇总风险。
两者的关键差异不是“能力强弱”,而是自治程度。Skill 通常完成一次边界明确的调用;Sub-Agent 需要在目标约束下自行决定下一步行动。
先问三个工程问题
1. 能力是否具有稳定、可复用的接口
如果多个工作流都会以相同参数调用这项能力,而且结果格式可以稳定定义,优先封装为 Skill。稳定接口便于测试、版本管理和替换实现。
例如,“根据客户 ID 查询最近五笔订单”很适合做成 Skill。调用方不必知道数据库结构,也不需要为它创建一个能够自主规划的 Agent。
2. 任务是否真的需要独立规划
只有当任务必须分解步骤、选择工具、检查中间结果并根据结果调整计划时,Sub-Agent 的额外复杂度才可能合理。
如果主 Agent 已经知道应该调用哪个工具、传入什么参数,以及如何消费结果,再增加一层 Sub-Agent 往往只会引入更多提示词、消息传递和失败状态。
3. 团队能否承担它的运行与维护成本
Sub-Agent 不只是多一个模型调用。工程上还要处理:
- 独立上下文的构造和裁剪
- 工具权限与数据访问边界
- 超时、重试和循环终止条件
- 多 Agent 之间的追踪与日志关联
- 模型升级后的回归测试
- Token、延迟和并发成本
如果这些机制没有明确收益,保持单 Agent 加 Skills 的结构通常更容易维护。
一个可执行的选择器
下面是一个可以直接运行的最小决策示例。它不是 Azure 指南的原始 API 或官方评分公式,而是根据“复用、简单和维护性优先”的原则整理出的工程化实践。团队可以修改阈值,并把实际生产指标接入评分过程。
将代码保存为 choose_component.py,然后运行 python choose_component.py:
from dataclasses import dataclass
from enum import Enum
class Component(str, Enum):
FUNCTION = "plain function or deterministic workflow"
SKILL = "skill"
SUB_AGENT = "sub-agent"
@dataclass(frozen=True)
class TaskProfile:
reusable: bool
needs_planning: bool
needs_isolated_context: bool
chooses_between_tools: bool
deterministic: bool
def choose_component(task: TaskProfile) -> Component:
# 不需要模型推理时,普通函数或确定性工作流更简单。
if task.deterministic and not task.needs_planning:
return Component.FUNCTION
autonomy_signals = sum(
[
task.needs_planning,
task.needs_isolated_context,
task.chooses_between_tools,
]
)
# 多个自治信号同时存在时,独立 Agent 才更可能值得。
if autonomy_signals >= 2:
return Component.SUB_AGENT
# 可复用且边界明确的模型能力适合封装为 Skill。
if task.reusable:
return Component.SKILL
return Component.FUNCTION
examples = {
"extract_invoice_fields": TaskProfile(
reusable=True,
needs_planning=False,
needs_isolated_context=False,
chooses_between_tools=False,
deterministic=False,
),
"investigate_repository_failure": TaskProfile(
reusable=False,
needs_planning=True,
needs_isolated_context=True,
chooses_between_tools=True,
deterministic=False,
),
"validate_email_address": TaskProfile(
reusable=True,
needs_planning=False,
needs_isolated_context=False,
chooses_between_tools=False,
deterministic=True,
),
}
for name, profile in examples.items():
print(f"{name}: {choose_component(profile).value}")
预期输出如下:
extract_invoice_fields: skill
investigate_repository_failure: sub-agent
validate_email_address: plain function or deterministic workflow
这个例子还强调了一个容易忽略的选项:并非所有能力都需要 Skill 或 Agent。格式校验、权限判断、字段转换等确定性逻辑,应优先使用普通代码。让模型执行可由规则准确完成的任务,会增加成本和不确定性。
把组件边界写进契约
无论最终选择 Skill 还是 Sub-Agent,都应该定义可测试的输入输出。可以这样实践:为每个组件维护一份简短契约。
name: investigate_repository_failure
kind: sub-agent
objective: "定位测试失败的根因,并给出带证据的修复建议"
inputs:
required:
- repository_path
- failing_command
outputs:
schema:
root_cause: string
evidence: array
suggested_changes: array
tools:
allowed:
- read_file
- search_code
- run_tests
limits:
max_steps: 12
timeout_seconds: 180
can_modify_files: false
对于 Skill,同一份契约通常可以更窄:减少可用工具,取消自主步骤,并要求稳定的结构化结果。契约越清楚,调用方越不依赖内部提示词,后续替换模型或实现方式也越容易。
采用前的检查清单
准备引入 Sub-Agent 时,可以快速检查以下问题:
- 普通函数、工作流或现有 Skill 是否已经足够?
- 任务是否至少包含两个明确的自治需求,例如规划、工具选择和上下文隔离?
- 是否定义了停止条件、超时和最大步骤数?
- 是否限制了它能访问的工具、数据和写操作?
- 是否能单独评估其结果质量、延迟和成本?
- 出错时,日志能否还原主 Agent 与 Sub-Agent 之间的调用链?
更稳妥的演进路径是从普通代码开始,在需要模型能力时提升为 Skill,只有在独立规划带来可测量收益时再拆出 Sub-Agent。架构的目标不是让 Agent 数量更多,而是让每个组件拥有足够且不过量的自治能力。