.NET+AI | Agent Skills |从 SKILL.md 到 AIContext:MAF Agent Skills 的分层设计与工程化扩展

张开发
2026/4/11 20:03:17 15 分钟阅读

分享文章

.NET+AI | Agent Skills |从 SKILL.md 到 AIContext:MAF Agent Skills 的分层设计与工程化扩展
以下内容选自我精心打造的《.NETAI | 智能体开发进阶》课程如需系统学习不妨阅读原文了解详情。前言Microsoft Agent Framework后文简称 MAF 发布 1.0 后关于 Agent Skills 的官方支持还在不断增强中在看 MAF 官方仓库里关于 Agent Skills 的实现时我有一个非常强烈的感受很多开发者第一次接触 Agent Skills容易把它理解成“给大模型增加一份 SKILL.md 说明书”。这个理解不能说错但如果只停留在这里就很容易低估它的价值。因为顺着 MAF 的源码继续往下看你会发现它做的事情远不止于“读取一个技能目录”这么简单。它实际上是在做一件更有工程意味的事情把 Skill 从一个静态内容包抽象成 Agent Runtime 中可发现、可加载、可读取、可执行、可治理的能力单元。这也是为什么我越来越觉得Agent Skills 真正值得关注的不只是它作为一个开放标准本身而是像 MAF 这样的框架开始把它往工程化运行时里真正落地了。所以这篇文章我不准备再从“如何编写一个 SKILL.md”这种入门视角展开而是想换一个角度顺着 MAF 的源码来回答几个更关键的问题1. MAF 到底是如何抽象 Agent Skills 的2. 它为什么没有把 Skill 仅仅处理成一堆 Markdown 文件3. 它相比标准 agentskills.io 协议工程化上又做了哪些扩展4. 如果放到真实业务里比如多租户电商客服助理这套抽象到底有什么价值如果你也在做 .NET AI Agent 的工程落地我觉得这几个问题是值得认真拆一拆的。一、先看全貌MAF Agent Skills 的五层架构在进入源码细节之前我们先看一张总体架构图。我个人非常喜欢先看全貌再钻细节。因为如果没有一个整体坐标系后面看到 AgentSkillsSource、AgentSkillsProvider、Builder、Filter、Deduplicate 这些类时很容易只记住类名却不明白它们为什么会被这样设计。先上图协议与内容层 agentskills.io SKILL.md Frontmatter / References / Scripts统一技能模型层AgentSkillAgentSkillFrontmatterAgentSkillResourceAgentSkillScript技能来源层AgentFileSkillsSource技能来源层AgentInMemorySkillsSource代码技能层 AgentInlineSkill / AgentClassSkill自定义来源层AgentSkillsSource组装治理层AgentSkillsProviderBuilderAggregatingFilteringDeduplicating运行时注入层 AgentSkillsProvider : AIContextProviderPrompt 注入available_skillsTool: load_skillTool: read_skill_resourceTool: run_skill_script这张图基本回答了一个核心问题一个放在目录里的 SKILL.md是如何一步步变成 Agent 运行时中的上下文和工具能力的从左到右看MAF 对 Agent Skills 的处理大致分成 5 层1. 协议与内容层负责承接 agentskills.io 标准定义的目录结构与内容规范。2. 统一技能模型层把文件、代码、内存中的 Skill 全部收敛为统一的对象抽象。3. 技能来源层解决 Skill 从哪里来不再局限于文件系统。4. 组装治理层解决 Skill 如何被组合、过滤、去重、审批和治理。5. 运行时注入层最终把 Skill 转换为 AIContext 的 Instructions 和 Tools进入 Agent Runtime。后面我们就按这五层一层层拆。二、第一层协议与内容层——MAF 先对齐标准再谈扩展先说结论MAF 对 Agent Skills 的设计并不是另起炉灶而是明确建立在 agentskills.io 的 progressive disclosure渐进式信息披露模型之上。在 AgentSkillsProvider 的注释里已经写得很直白了它实现的就是 Agent Skills 的四阶段模式1. Advertise先把 skill 的名称和描述注入到系统提示中。2. Load当模型判断某个 skill 相关时再通过 load_skill 工具加载完整技能内容。3. Read resources如果需要更详细的补充资料再按需读取 references / assets 中的资源。4. Run scripts如果 skill 中定义了 scripts则再按需执行脚本。也就是说在 MAF 的认知里Skill 从来就不是“一次性全量塞进上下文”的内容包而是一个分阶段暴露的信息单元。这一点很重要。因为一旦接受这个前提你就会明白为什么一个 Skill 目录会被拆成• SKILL.md• references/• assets/• scripts/以及为什么 frontmatter 只放最小发现信息而不是把所有内容都堆进去。在文件技能实现 AgentFileSkillsSource 中也能看到对标准目录结构的显式承接• 默认脚本目录scripts• 默认资源目录references、assets• 默认脚本扩展名.py、.js、.sh、.ps1、.cs、.csx• 默认资源扩展名.md、.json、.yaml、.yml、.csv、.xml、.txt这说明 MAF 并不是简单“支持 SKILL.md”而已它是把标准中的目录语义直接映射到了运行时行为上。换句话说在 agentskills.io 中目录结构是规范在 MAF 中目录结构开始变成运行时协议的一部分。这一层解决的是什么问题它解决的是“Skill 如何被标准化描述和渐进式披露”的问题。如果没有这一层后面的统一抽象和运行时注入都没有共同基础。三、第二层统一技能模型层——Skill 的本体不是文件而是对象如果说第一层解决的是“怎么描述 Skill”那么第二层解决的就是“框架内部如何理解 Skill”。这里 MAF 的设计非常关键它没有把 Skill 绑定死在文件系统上而是先抽象出统一对象模型。核心对象可以概括成四个1. AgentSkill统一的 Skill 抽象本体。2. AgentSkillFrontmatterSkill 的发现元数据比如 name、description、compatibility 等。3. AgentSkillResourceSkill 的资源抽象对应 references/assets 这类按需读取内容。4. AgentSkillScriptSkill 的脚本抽象对应 scripts 目录或者代码中的可执行能力。这层抽象背后其实有一个非常重要的设计意图Skill 的本体不是文件而是能力对象文件只是它的一种载体。这一点看似简单实则非常关键。因为一旦 Skill 被抽象成对象MAF 就可以自然支持多种来源• 文件技能• 内存技能• 代码技能• 自定义远程技能而不需要上层运行时关心“这个 Skill 到底来自 SKILL.md 还是来自一段 C# 代码”。从职责上看这四个对象其实对应了 Skill 的三个核心维度第一Frontmatter 负责“发现”模型在最开始不需要知道全部内容只需要知道有哪些技能、技能大概做什么。第二Content 负责“完整指令”当决定使用某个 Skill 时再获取完整内容。第三Resources / Scripts 负责“知识扩展与动作执行”资源提供补充资料脚本提供实际可执行能力。所以如果让我用一句话概括这一层我会这样说在 MAF 里一个 AgentSkill 本质上是 Prompt、Knowledge、Action 的统一容器。这就和很多人理解的“Skill 一段提示词模板”已经完全不是一回事了。这一层解决的是什么问题它解决的是“如何用统一对象模型承载不同来源、不同形态的 Skill 能力”的问题。四、第三层技能来源层——为什么工程里必须有 Source 抽象如果说统一模型层解决的是“Skill 是什么”那接下来就要解决另一个更加工程化的问题Skill 从哪里来这就是 AgentSkillsSource 抽象存在的意义。MAF 给出的基类非常直接AgentSkillsSource 是所有技能来源的统一抽象核心方法只有一个GetSkillsAsync()看起来很简单但这个抽象的价值其实非常大。因为它把“技能的供给方式”和“技能的使用方式”彻底解耦了。目前从源码看MAF 至少已经支持了几类典型来源。1. 文件来源AgentFileSkillsSource这是最贴近 agentskills.io 标准的一种来源。它会递归扫描目录中的 SKILL.md解析 frontmatter并进一步发现资源和脚本。这里有几个细节很值得注意• 搜索深度默认限制为 2 层• 使用 frontmatter 正则提取 YAML 区块• 会校验资源和脚本扩展名• 会校验路径安全防止 path traversal 和 symlink escape这说明文件来源并不只是“遍历目录 读文件”而是已经开始带有生产级的输入边界控制。2. 内存来源AgentInMemorySkillsSource这个就很有意思了。它意味着 Skill 可以完全不来自磁盘而是来自运行时内存对象集合。这对于以下场景很有价值• 通过配置中心下发技能• 按租户动态生成技能• 对某些技能做临时裁剪或拼装• 测试环境中快速注入技能3. 代码技能AgentInlineSkill / AgentClassSkill这是我觉得 MAF 很有工程味道的地方。它没有把 Skill 死死绑定在 Markdown 上而是允许用 C# 代码定义 Skill。其中• AgentInlineSkill 更适合快速代码式构建可以直接添加资源和脚本委托。• AgentClassSkill 更适合封装成面向对象的能力类适合复杂技能和可维护性更高的场景。也就是说MAF 在这一层已经完成了一个很重要的跨越它把 agentskills.io 解决的“技能如何描述”进一步延展成了“技能如何供给”。而这正是从标准走向平台能力的第一步。如果再往前想一步你会发现 AgentSkillsSource 这个抽象其实已经为很多企业场景留好了口子• 从数据库中拉取租户技能• 从 Git 仓库同步技能• 从对象存储加载版本化技能• 从配置平台动态下发技能• 从 SaaS 平台按用户订阅装配技能所以 Source 抽象看起来普通实际上是整个工程化扩展能力的起点。这一层解决的是什么问题它解决的是“Skill 如何被供给”的问题。而 agentskills.io 标准更多解决的是“Skill 如何被描述”的问题。五、第四层组装治理层——Builder 才是 MAF 工程化价值最浓的一层如果让我选 MAF Agent Skills 这套设计里最有工程味的一层我会选 Builder。因为很多框架做到这里可能就停在“我能扫描目录帮你加载 skills”了。但 MAF 显然没有停。它继续往前走了一步开始思考另一个更接近真实项目的问题当 Skill 来自多个来源、多个层级、多个租户、多个团队时应该如何装配和治理于是就有了 AgentSkillsProviderBuilder。从源码看这个 Builder 提供的能力包括• UseFileSkill / UseFileSkills• UseSkill / UseSkills• UseSource• UsePromptTemplate• UseScriptApproval• UseFileScriptRunner• UseLoggerFactory• UseFilter• UseOptions• Build表面看这是一个 Fluent Builder。但如果只把它看成“链式调用写起来更优雅”那还是低估它了。它真正做的是把 Skill 的装配过程管道化。Build 的内部流水线很清楚1. 先把多个来源解析出来2. 如果有多个来源就用 AggregatingAgentSkillsSource 聚合3. 如果配置了 Filter就做过滤4. 最后统一用 DeduplicatingAgentSkillsSource 去重5. 再交给 AgentSkillsProvider 注入运行时这套过程可以用一张图表示File SkillsAggregateInline SkillsCustom SourceFilterDeduplicateAgentSkillsProvider这里面最值得展开说的有三个点。1. AggregateSkill 集合是“装配结果”不是“目录扫描结果”这一点特别重要。在真实系统里Skill 往往不会只来自一个目录。你可能同时有• 平台默认技能• 团队共享技能• 租户自定义技能• 当前会话动态技能如果框架没有 Aggregate 这一层Skill 就只能是一个孤立目录中的内容集合而不能成为真正的能力注册表。2. FilterSkill 暴露是需要治理的Builder 支持 UseFilter这意味着并不是发现到的所有 Skill 都会暴露给 Agent。这件事在企业里非常关键。因为很多 Skill 的存在并不代表它对当前用户、当前租户、当前环境可见。比如• 某个租户不该看到平台内部技能• 某些敏感技能只允许人工审核后启用• 不同角色的客服只能看到自己的处理技能• 测试技能不能进入生产环境Filter 让 Skill 暴露从“静态存在”变成了“策略决策”。3. Deduplicate重名冲突不是异常而是常态Builder 最后统一做去重而且遵循 first occurrence wins。这件事乍看普通但其实是多来源系统里非常实用的一个策略点。因为一旦进入企业场景重名冲突几乎是必然的• 平台有默认 refund-skill• 某个大客户想自定义 refund-skill• 某个环境有临时 patch 版 refund-skill如果框架没有显式的去重策略这些技能在运行时的表现会非常不可控。所以我会认为Builder 这一层真正的价值不是“好用”而是“可治理”。这一层解决的是什么问题它解决的是“多个 Skill 来源如何被组合、筛选、去重和治理”的问题。这也是 MAF 相对 agentskills.io 标准最明显的工程化扩展之一。六、第五层运行时注入层——Skill 如何进入 Prompt 与 Tool 系统前面几层都还是“准备工作”真正到了运行时最关键的问题变成了这些 Skill 最终是如何进入 Agent 的答案在 AgentSkillsProvider。从源码看AgentSkillsProvider 继承自 AIContextProvider这个定位非常关键。因为它说明 MAF 并不是把 Skill 当成外挂配置而是把它纳入 AIContext 的构建链路中。它最终生成的是一个 AIContext核心包括两部分1. Instructions把 Skill 的摘要信息注入到系统提示中。2. Tools把 load_skill、read_skill_resource、run_skill_script 等工具暴露给模型。也就是说Skill 在运行时会同时影响两件事• Prompt• Tooling而这恰恰是 progressive disclosure 能落地的关键。先看默认提示模板大意非常清楚• 你拥有一组 skills• 这些 skills 提供领域知识和专门能力• 当任务与 skill 匹配时按顺序• load_skill• 按需读取资源• 按需执行脚本• 只在需要时加载需要的内容这不是简单“告诉模型有几个 skills”。而是在系统提示里直接教模型如何使用 Skill 机制。这一步非常像什么像是在给 Agent 注入一个“技能使用协议”。所以 MAF 真正高明的地方在于它不是单纯提供了一堆工具而是把“如何以渐进方式使用 Skill”这个行为规范一起注入给了模型。整个运行链路可以用下面这张时序图表示ScriptResourceSkill SourceAgentSkillsProviderAgentUserScriptResourceSkill SourceAgentSkillsProviderAgentUser发起任务注入 available_skillsload_skill(tenant-a-order-service)加载完整 Skill返回 Skill 内容read_skill_resource(tenant-a-order-service, refund-policy)读取资源返回资料run_skill_script(tenant-a-order-service, query-order-status, args)执行脚本返回执行结果这张图背后反映出 MAF 在运行时上至少做对了三件事。第一只先暴露最小信息不是把所有 Skill 全文灌入上下文而是先暴露技能名和描述。第二把“完整内容”和“执行动作”工具化让 Skill 变成可按需拉取和调用的运行时能力而不是静态 Prompt 附件。第三把 Skill 正式接入 AIContextProvider 体系这意味着 Skill 不再是“外挂插件”而是 Agent 上下文构建的一等公民。这一层解决的是什么问题它解决的是“Skill 如何真正进入 Agent Runtime并以 Prompt Tool 的方式被模型按需使用”的问题。七、第六层安全与边界——为什么这套实现更像生产级框架很多时候一套能力是不是能进生产看的不是它“能不能跑起来”而是它“边界处理得怎么样”。从这个角度看MAF 在 Agent Skills 上的设计明显已经不是“玩具级支持”了。在 AgentFileSkillsSource 中可以看到不少安全与边界控制设计。1. 搜索深度限制目录递归搜索不是无限下探而是限制在最多 2 层。这能避免扫描范围失控也减少技能发现的不可预期性。2. Frontmatter 校验Skill 文件不是随便一段 Markdown 就能过。需要匹配 frontmatter 结构并校验关键字段。3. 扩展名白名单资源和脚本并不是目录里有什么就加载什么而是通过默认白名单和可配置扩展名来控制。这一步实际上是在做“文件级能力边界”。4. 路径逃逸防护源码里显式提到了对 path traversal 和 symlink escape 的检查。这意味着框架已经意识到一旦 Skill 支持读取资源、执行脚本目录边界就是安全边界。5. Script Approval GateBuilder 中还支持 UseScriptApproval。这个设计非常有代表性因为它表明 MAF 并没有把脚本执行当成“默认无脑放开”的能力而是把它视作需要审批和治理的动作。我个人非常认同这种设计。因为从 AI Agent 的工程化视角看越是可执行能力越不能只谈灵活性而必须谈约束。一句话总结就是标准定义了 scripts 能做什么而 MAF 进一步定义了 scripts 在什么边界内才能做。这一层解决的是什么问题它解决的是“Skill 在生产环境里如何被安全发现、受控读取和谨慎执行”的问题。八、从 agentskills.io 到 MAF到底扩展了什么到这里我们其实可以回到一个更高层的问题既然 agentskills.io 已经定义了 Skill 的格式和渐进式信息披露那 MAF 到底额外做了什么我觉得可以直接用一张对比来说明。1. agentskills.io 更关注“协议”它解决的是• Skill 长什么样• SKILL.md 怎么写• 目录结构怎么组织• 模型应该如何渐进式获取信息2. MAF 更关注“运行时与工程化”它解决的是• Skill 如何被统一建模• Skill 如何来自多个来源• Skill 如何被聚合、过滤、去重• Skill 如何注入 AIContext• Skill 如何通过工具按需读取和执行• Skill 如何被安全审批和边界控制如果用一句更凝练的话来概括我会这样说agentskills.io 提供的是技能协议MAF 提供的是技能运行时。这两者并不是替代关系而是上下层关系。协议解决互通问题运行时解决落地问题。而一个标准真正有生命力往往恰恰不在于它被“支持”了多少次而在于是否有框架愿意把它推进到运行时核心链路里。从这个意义上说MAF 对 Agent Skills 的价值不是“又支持了一个新规范”而是把 Skill 从文件格式真正推进成了 .NET Agent Runtime 里的能力机制。九、案例收尾多租户电商客服助理Skill 如何真正跑起来说完抽象我们再回到一个更贴近业务的问题这套设计放到真实项目里到底有什么用这里我用一个很典型的场景来收尾多租户电商客服助理。假设我们要做一个服务多个电商租户的客服 Agent。平台上同时有三类知识和能力第一类平台公共技能所有租户共用比如• customer-service-baseline• customer-risk-control它们负责统一客服语气、服务规范、人工升级规则、风险沟通边界。第二类租户定制技能每个租户有自己独立的规则比如 tenant-a、tenant-b、tenant-c 分别拥有自己的• 订单查询技能• 物流处理技能• 退款退货技能• 活动优惠技能第三类动态运行时技能某些特殊活动、临时促销、节假日规则可能需要在运行时按租户临时注入。如果没有 Skill 抽象这种系统很容易退化成什么样通常就是把所有租户规则、所有业务 SOP、所有查询接口说明尽可能塞进一大段 Prompt。这样做的问题非常明显1. 上下文冗长所有租户知识一起塞token 成本高而且噪声大。2. 难以隔离tenant-a 的规则可能污染 tenant-b 的决策。3. 难以治理哪条规则来自平台、哪条来自租户、哪条来自临时活动很难追踪。4. 难以执行订单查询、优惠券校验、售后工单创建最终还是要调用外部动作单靠 Prompt 不够。而 MAF 的这套 Skill 抽象正好可以把这些问题逐一拆开。1. 用 Source 解决“技能分层供给”平台公共技能来自/skills/platform租户技能来自/skills/{tenantId}动态技能来自内存或自定义 Source这意味着 Skill 的供给天然可以分层。2. 用 Builder 解决“多来源装配”伪代码大致可以是这样var provider new AgentSkillsProviderBuilder() .UseFileSkills(./skills/platform, scriptRunner: platformRunner) .UseFileSkills($./skills/{tenantId}, scriptRunner: tenantRunner) .UseFilter(skill skill.Frontmatter.Name.StartsWith(customer-) || skill.Frontmatter.Name.StartsWith(${tenantId}-)) .UseScriptApproval() .Build();这里面有几个关键点• 平台技能和租户技能可以同时装配• Filter 控制当前租户只看到自己的技能• Deduplicate 可以处理平台默认技能和租户覆盖技能的命名冲突• ScriptApproval 让外部动作执行受控3. 用 progressive disclosure 解决“按需加载”比如某个租户用户发来一条消息“这笔订单为什么还没发货如果今天还发不出去能不能给我补偿”这时候Agent 并不需要提前知道这个租户全部退款规则、全部活动规则、全部物流规则。它只需要先看到 available_skills然后按需走下面的路径第一步load_skill(tenant-a-order-service)读取订单服务相关完整技能说明。第二步read_skill_resource(tenant-a-order-service, warehouse-sla)读取仓库 SLA 或物流时效说明。第三步run_skill_script(tenant-a-order-service, query-order-status, args)查询真实订单状态。第四步如果涉及补偿再加载退款或售后相关 Skill•load_skill(tenant-a-refund-service)•read_skill_resource(tenant-a-refund-service, aftersales-sop)•run_skill_script(tenant-a-refund-service, check-refund-eligibility, args)这整个过程其实正好就是前面五层架构在业务中的一次完整落地• 协议层定义 Skill 包结构• 模型层统一 Skill 抽象• 来源层提供平台技能 租户技能• Builder 负责聚合、过滤、去重、审批• Provider 负责把能力注入运行时并通过工具按需加载与执行所以在这个场景下Skill 不再只是“提示词模板”而真正变成了业务能力单元• 规则可以封装• 知识可以读取• 动作可以执行• 可见范围可以治理• 不同租户可以隔离• 平台能力可以复用如果再把这个判断说得更直接一点在多租户电商客服场景里Skill 的本质已经不是一份给模型看的说明书而是一种把平台规则、租户知识、实时查询能力与安全边界封装起来的运行时能力模块。十、总结顺着 MAF 的源码一路看下来我觉得它对 Agent Skills 做的最重要的一件事不是“支持了 agentskills.io 协议”而是把这件事做到了运行时。它先承认标准用 agentskills.io 的目录结构和渐进式信息披露做基础再通过统一对象模型把 Skill 从文件提升为能力抽象接着通过 Source 抽象解决供给问题通过 Builder 解决装配和治理问题最后再通过 AgentSkillsProvider把 Skill 作为 AIContext 的一部分正式注入 Agent Runtime。所以回到文章开头那个问题MAF 中的 Agent Skills到底是不是“多了一份 SKILL.md”我的答案很明确不是。它真正补齐的是这样一条链路从 Skill 的标准化描述到 Skill 的统一建模再到 Skill 的多来源供给、运行时注入、按需执行与安全治理。如果说 agentskills.io 解决的是“Skill 应该如何被标准化描述”那么 MAF 真正推进的是“Skill 如何在 .NET Agent Runtime 中被工程化供给、装配、注入与治理”。而这件事的意义可能比“支持一个新协议”要大得多。因为 Agent Skills 的价值从来不只是多了一个 SKILL.md而是组织知识、业务规则与执行能力终于开始以标准化、可组合、可治理的方式进入 Agent Runtime。如果你也在做 .NET Agent 的工程化落地我认为这恰恰是最值得关注的方向之一。

更多文章