# Agent 里的 Skill 依赖关系应该怎么设计
一句话导读:Skill 不是一堆互不相关的 Prompt 或函数。复杂 Agent 里的 Skill 会形成依赖图,需要显式声明、自动排序、版本治理、隔离运行和回归测试。
当 Agent 系统变复杂后,Skill 很快不再是几个孤立能力。
比如:
生成报告 Skill
-> 依赖读取数据 Skill
-> 依赖生成图表 Skill
-> 依赖摘要生成 Skill
发送邮件 Skill
-> 依赖鉴权 Skill
-> 依赖联系人查询 Skill
-> 依赖附件生成 Skill
2
3
4
5
6
7
8
9
如果这些依赖只靠人脑记、靠文档写、靠 Prompt 约定,系统很快会变成一团胶水:
不知道谁依赖谁
不知道应该先加载哪个
底层 Skill 一改,上层一片炸
循环依赖上线后才发现
测试只测了当前 Skill,没测依赖它的上游
2
3
4
5
所以 Skill 依赖不是一个文档问题,而是一个工程治理问题。
# 一、先定义什么是 Skill 依赖
Skill 依赖指的是:
一个 Skill 的执行,需要另一个 Skill 的能力、输出、资源或约束。
常见依赖有几类。
# 1. 数据依赖
一个 Skill 需要另一个 Skill 的输出。
比如:
生成报告 Skill
-> 依赖读取销售数据 Skill 的输出
2
# 2. 能力依赖
一个 Skill 内部需要调用另一个能力。
比如:
报销判断 Skill
-> 依赖员工身份查询 Skill
-> 依赖预算查询 Skill
-> 依赖制度检索 Skill
2
3
4
# 3. 运行时依赖
多个 Skill 依赖同一个基础服务。
比如:
订单查询 Skill
退款查询 Skill
工单创建 Skill
-> 都依赖鉴权 Skill
2
3
4
# 4. 版本依赖
上层 Skill 依赖某个版本范围的底层 Skill。
比如:
report-generator@2.x
-> depends on chart-generator@^1.4.0
2
# 5. 隐式依赖
最危险的是隐式依赖。
比如某个 Skill 的 Prompt 里写:
请参考订单查询规则。
但系统里没有机器可读的依赖声明。
这种依赖人能看懂,系统看不懂。
后续就没法自动加载、排序、检测和测试。
# 二、第一步:依赖必须显式声明
Skill 依赖治理的第一步,是把隐式依赖变成显式依赖。
每个 Skill 都应该有元数据。
比如:
name: refund-status
version: 1.3.0
description: 查询并解释退款状态
dependencies:
- name: order-query
version: "^1.2.0"
type: required
- name: payment-query
version: "^2.0.0"
type: required
- name: ticket-create
version: "^1.0.0"
type: optional
tools:
- refund.getStatus
- order.getRecent
inputs:
- userId
- orderId?
outputs:
- refundStatus
- estimatedArrival
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
重点不是 YAML 格式,而是这件事:
依赖关系必须机器可读。
只有机器可读,后面才能:
自动加载
自动排序
自动检测循环依赖
自动计算影响范围
自动跑回归测试
2
3
4
5
# 三、第二步:用依赖图管理 Skill
一旦每个 Skill 都声明了依赖,就可以把系统抽象成一张有向图。
比如:
refund-response
-> refund-status
-> order-query
-> auth
refund-response
-> payment-query
-> auth
refund-response
-> ticket-create
-> auth
2
3
4
5
6
7
8
9
10
11
12
这张图可以用来做几件事。
# 1. 推导加载顺序
底层依赖先加载,上层 Skill 后加载。
auth
-> order-query
-> refund-status
-> refund-response
2
3
4
# 2. 推导调用顺序
如果某个 Skill 的输入依赖另一个 Skill 的输出,系统可以自动知道先后关系。
# 3. 计算影响范围
如果 auth 改了,可以知道哪些 Skill 受影响:
order-query
payment-query
refund-status
ticket-create
refund-response
2
3
4
5
# 4. 检测循环依赖
如果出现:
A -> B -> C -> A
系统应该在注册期或构建期直接报错。
不要等运行时炸。
# 四、第三步:拓扑排序和循环依赖检测
依赖图最常见的处理方式是拓扑排序。
如果依赖图是 DAG,也就是有向无环图,就可以得到一个合法顺序:
auth
order-query
payment-query
refund-status
refund-response
2
3
4
5
如果无法排序,说明有循环依赖。
循环依赖要尽早拦截。
比如:
report-generator
-> chart-generator
-> report-generator
2
3
这通常说明 Skill 边界设计有问题。
解决方式不是硬让它们互相调用,而是重新拆边界:
report-generator
chart-generator
-> 都依赖 shared-data-model
2
3
或者用事件解耦:
report-generator 发布 ReportRequested
chart-generator 监听事件生成图表
report-generator 再汇总结果
2
3
简单原则:
同步互调容易形成循环依赖。
共享底层能力或事件驱动更容易解耦。
2
# 五、第四步:区分强依赖和弱依赖
不是所有依赖都一样。
可以分成:
required dependency
optional dependency
runtime dependency
dev dependency
2
3
4
# 强依赖
没有它,Skill 不能运行。
比如:
退款状态解释 Skill
必须依赖退款查询 Skill。
2
# 弱依赖
有它更好,没有也能降级。
比如:
退款超时场景可以依赖工单创建 Skill。
如果工单系统不可用,仍然可以回复用户转人工。
2
# 运行时依赖
只有走到某个分支才需要。
比如:
只有退款超时才需要 ticket-create。
# 开发依赖
只在测试或评估时需要。
比如:
mock-payment-service
eval-dataset-loader
2
依赖类型不同,加载策略和失败策略也不同。
# 六、第五步:语义化版本管理
Skill 也应该有版本。
推荐用 SemVer:
MAJOR.MINOR.PATCH
比如:
refund-status@1.4.2
版本语义可以这样定义:
PATCH:
修 bug,不改变输入输出契约。
MINOR:
增加兼容能力,比如新增可选字段。
MAJOR:
破坏性变更,比如输入输出结构改变。
2
3
4
5
6
7
8
上层 Skill 不应该随便写死某个版本:
dependencies:
- name: refund-status
version: "1.4.2"
2
3
更适合声明范围:
dependencies:
- name: refund-status
version: "^1.4.0"
2
3
这样兼容升级可以自动接入。
破坏性升级需要显式处理。
# 七、第六步:懒加载和按需装配
Skill 多了以后,不应该所有 Skill 启动时一次性全加载。
全量加载的问题是:
启动慢
内存占用高
依赖错误影响面大
低频 Skill 拖累高频路径
2
3
4
更好的方式是:
高频核心 Skill 预加载。
低频长尾 Skill 懒加载。
运行到对应 Intent 或 Capability 时,再解析依赖链。
2
3
比如:
订单查询、退款查询、物流查询
-> 高频,预加载
合同审查、复杂报表生成、异常申诉
-> 低频,懒加载
2
3
4
5
懒加载不是越多越好。
它有首次调用延迟。
所以取舍要看:
QPS
延迟要求
Skill 体积
依赖链深度
失败影响面
2
3
4
5
# 八、第七步:沙箱隔离,控制故障传播
一个 Skill 出错,不应该拖垮整个 Agent。
尤其是复杂垂类 Agent:
退款查询失败
不应该导致整个客服 Agent 崩掉。
2
可以做几层隔离:
超时控制
异常捕获
资源限制
权限限制
工具白名单
降级策略
2
3
4
5
6
比如:
ticket-create Skill 失败
-> 不影响 refund-status Skill
-> 回复用户“当前无法自动创建工单,可以转人工处理”
2
3
这叫故障边界。
Skill 依赖越复杂,越需要明确故障边界。
# 九、第八步:依赖注入,降低耦合
Skill 内部不要写死依赖。
不要这样:
import { refundStatusSkill } from "../refund-status";
export async function run(input) {
const status = await refundStatusSkill.run(input);
}
2
3
4
5
更好的方式是通过接口注入:
type RefundStatusProvider = {
getStatus(input: RefundInput): Promise<RefundState>;
};
export function createRefundResponseSkill(deps: {
refundStatus: RefundStatusProvider;
ticket?: TicketProvider;
}) {
return {
async run(input) {
const status = await deps.refundStatus.getStatus(input);
return generateResponse(status);
},
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
好处是:
方便替换实现
方便单元测试 mock
方便灰度新版本
避免代码层硬耦合
2
3
4
依赖声明解决系统级依赖关系。
依赖注入解决代码级耦合问题。
两个都需要。
# 十、第九步:变更后的影响分析和回归测试
Skill 依赖治理最容易被忽略的是测试闭环。
如果修改了一个底层 Skill:
auth
order-query
payment-query
refund-status
2
3
4
系统应该自动知道哪些上层 Skill 受影响,并触发相关测试。
比如:
refund-status 改了
-> 跑 refund-response 测试
-> 跑 overdue-refund 测试
-> 跑 ticket-create 集成测试
-> 跑相关 Eval Set
2
3
4
5
这可以通过依赖图做影响分析。
简单流程:
Skill changed
-> 找到所有反向依赖
-> 收集受影响测试
-> 跑回归
-> 阻止不兼容变更上线
2
3
4
5
这一步最能体现工程成熟度。
因为它解决的是:
改一个底层能力,炸一片上层能力。
# 十一、运行时应该怎么处理循环依赖
循环依赖不要留到运行时处理。
最好在:
注册期
构建期
发布前检查
2
3
直接拦截。
如果发现:
A depends on B
B depends on C
C depends on A
2
3
应该失败,并提示依赖链。
比如:
Circular dependency detected:
report-generator -> chart-generator -> report-generator
2
如果业务上确实需要双向协作,通常说明:
Skill 边界切错了。
可以考虑:
抽出共享底层 Skill
改成事件驱动
改成工作流节点编排
让上层 Workflow 负责协调,而不是 Skill 互相调用
2
3
4
# 十二、和垂类 Agent 的关系
垂类 Agent 里 Skill 依赖尤其常见。
比如报销 Agent:
报销判断 Skill
-> 员工身份 Skill
-> 部门政策 Skill
-> 预算查询 Skill
-> 订单识别 Skill
-> 风险检查 Skill
2
3
4
5
6
如果这些依赖不治理,系统会很难维护。
更推荐的方式是:
Intent 识别用户要做什么
Business Resolver 查询业务事实
Capability Router 选择能力
Skill Registry 解析依赖
Workflow 编排执行顺序
LLM 组织最终回复
2
3
4
5
6
也就是说:
不要让 Skill 之间随便互相调用。
让上层 Router / Workflow 根据依赖图统一编排。
2
# 十三、和 MCP 的关系
MCP 是工具接入协议。
Skill 可以依赖 MCP Tool。
比如:
name: refund-status
tools:
- refund.getStatus
- order.getRecent
2
3
4
但 MCP 不负责 Skill 依赖治理。
MCP 解决的是:
Agent 怎么调用外部工具。
Skill 依赖治理解决的是:
能力模块之间如何声明、加载、排序、隔离、升级和测试。
所以关系是:
Skill Registry 管 Skill。
MCP Registry 管工具。
Skill 可以引用 MCP Tool。
Workflow / Harness 负责装配它们。
2
3
4
不要把 MCP 当成 Skill 依赖系统。
# 十四、面试回答模板
如果面试官问:
如果 Agent 里有很多 Skill,Skill 之间还有依赖关系,你怎么设计?
可以这样答:
我会把 Skill 依赖当成工程治理问题,而不是靠文档约定。
第一,每个 Skill 都要有结构化元数据,显式声明依赖的 Skill、版本范围、依赖类型、输入输出和需要的工具。这样依赖关系机器可读。
第二,系统把 Skill 依赖构建成有向图,用拓扑排序推导加载和执行顺序,并在注册期或构建期检测循环依赖,避免运行时才炸。
第三,引入语义化版本管理,上层声明依赖范围,兼容升级自动接入,破坏性变更通过主版本号和回归测试拦截。
第四,加载策略上区分高频核心 Skill 和低频长尾 Skill。核心 Skill 预加载,长尾 Skill 懒加载,按 Intent 或 Capability 触发时再解析依赖链。
第五,运行时用沙箱隔离和超时、降级机制控制故障传播,同时用依赖注入降低代码层耦合,方便替换、灰度和测试。
最后,底层 Skill 变更时通过依赖图做影响分析,自动触发所有上游 Skill 的回归测试和相关 Eval Set,防止改一个炸三个。
2
3
4
5
6
7
8
9
10
11
12
13
这个回答的主线是:
隐式变显式
人工变自动
耦合变隔离
变更有回归
2
3
4
# 十五、总结
Skill 依赖治理可以记成九步:
1. 显式声明依赖
2. 建 Skill 依赖图
3. 拓扑排序推导顺序
4. 循环依赖提前检测
5. 区分强依赖和弱依赖
6. 用 SemVer 做版本管理
7. 高频预加载,长尾懒加载
8. 沙箱隔离和依赖注入
9. 变更后做影响分析和回归测试
2
3
4
5
6
7
8
9
真正成熟的 Agent 系统,不会把 Skill 当成散落的 Prompt 片段。
它会把 Skill 当成可注册、可依赖、可版本化、可测试、可隔离的能力模块。
一句话总结:
Skill 越多,越不能靠人脑维护依赖;必须用依赖图、版本、隔离和测试,把 Skill 从胶水代码治理成工程资产。