如何处理 Skill 之间的依赖关系?比如 Skill A 的输出是 Skill B 的输入。
面试官:“当 Skill A 的输出是 Skill B 的输入时,你在 Agent 里怎么处理这种依赖关系?”
候选人:
“这个场景太常见了,比如‘查询天气’的输出(温度、湿度)是‘穿衣建议’的输入。处理这种依赖,核心原则是:Skill 之间不直接耦合,而是通过 Agent 核心维护的全局上下文,以声明式参数映射来串联。 我分机制、设计、异常处理三层来说明。
一、核心机制:统一的上下文对象 + 依赖声明
每个任务开始时,Agent 编排引擎会创建一个 TaskContext 对象,它就像一个贯穿整个任务生命周期的全局临时数据库。所有 Skill 执行时都从上下文里拿输入,结果写回上下文。这样就解耦了 Skill 之间的直接依赖。
具体实现上,每个 Skill 的元信息里需要声明:
-
输入参数及其来源:参数可以从哪里来(用户直接输入、上下文中由某个 Skill 产生、默认值)。
-
输出声明:返回哪些字段,这些字段写入上下文后,用什么键名供后续 Skill 引用。
例如,Skill B 声明它的参数 temperature 来源于 Skill A 的输出字段 current_temp。元信息片段:
// Skill B 的参数定义
"parameters": [
{
"name": "temperature",
"type": "number",
"required": true,
"source": {
"type": "context",
"key": "weather_report.current_temp",
"provided_by": "weather_query"
}
}
]
而 Skill A 的输出元信息会声明自己贡献的上下文键:
// Skill A 的输出声明
"output": {
"provides": {
"weather_report.current_temp": "number",
"weather_report.humidity": "number"
}
}
这样,Agent 的编排引擎在构建任务 DAG 时,自动识别依赖关系:B 依赖 A 的输出,所以 A 必须先于 B 执行。这种声明式的方式让 Skill 开发者不需要写任何调用代码,只需要定义“我要什么”和“我能给什么”。
二、运行时注入:自动参数映射与类型转换
当编排引擎调度执行时,按照 DAG 拓扑顺序依次激活 Skill。对于 Skill B,引擎会检查它的所有参数,发现 source.type == "context",就去 TaskContext 里查找 weather_report.current_temp。找到后注入值。如果有多个候选值(比如多个 Skill 都提供了类似的键),根据 Skill 版本、置信度等挑选最优值。
同时,为了避免类型不匹配,系统内置了一个类型适配器层。例如 A 输出的是字符串 "26",B 需要整数,上下文映射时自动尝试类型转换;如果转换失败,交给异常处理。
如果某个参数在上下文里找不到值,并且不是必需的,就用默认值;如果是必需的,Agent 有两种策略:
-
暂停执行,生成澄清问题反问用户(“我没有拿到当前温度,你告诉我一下?”)。
-
如果有替代 Skill 可以提供该值,动态插入调用一个 Skill C 来补齐缺失数据。
三、异常处理与补偿:上游失败时下游怎么办?
这是依赖处理中最棘手的部分。编排引擎需要为每个 DAG 节点定义失败传播策略:
-
强依赖:B 强依赖 A 的输出。A 失败,B 直接标记为
skipped,整个任务失败或进入补偿流程。 -
弱依赖 / 可降级:B 声明了 A 的输出是“最好有”,但不是必需。A 失败后,B 使用默认值或降级逻辑继续执行(比如天气查不到,穿衣建议用本地缓存的季节数据)。
-
重试:A 失败后,编排引擎先对 A 重试(次数、退避策略),重试成功则 B 正常执行。
在元信息里,Skill B 可以为每个参数指定 required 和 fallback_value,以及 on_missing 策略(ask_user / use_fallback / fail)。这样依赖处理的决策就下沉到了配置层,不需要硬编码。