乐于分享
好东西不私藏

.NET+AI | Agent Skills | 强类型 Agent Skills 如此优雅,助你轻松业务系统 Skills 化

.NET+AI | Agent Skills | 强类型 Agent Skills 如此优雅,助你轻松业务系统 Skills 化

以下内容选自我精心打造的《.NET+AI | 智能体开发进阶》课程,如需系统学习,不妨阅读原文了解详情。

前两篇我们分别聊了 Inline Skill 和 File-based Skill。

如果说 Inline Skill 解决的是“先把能力接进来”,File-based Skill 解决的是“怎么把能力沉淀成资产”,那么到了 Class-based Skill,问题就变成了:

当一个 Skill 已经足够稳定,甚至准备进入系统内部长期维护时,能不能用更清晰、更可治理的方式来组织它?

这正是 AgentClassSkill<T> 出现的意义。

它的重点,不是再换一种写法,而是把 Skill 从“可复用资产”进一步推进到“可治理模块”。

一、为什么 File-based 之后,还需要 Class-based Skill?

File-based Skill 已经解决了很多问题,比如结构化、分发、共享、跨项目复用。

但它更偏“外部化能力资产”。

而在真实项目里,总有一些 Skill 会逐步稳定下来,变成系统内部的核心能力。比如订单查询、物流换算、售后规则解释、风控校验。这类能力往往不只是“能共享”就够了,还会进一步要求:

  • • 更清晰的类型约束
  • • 更自然的单元测试
  • • 更稳定的重构支持
  • • 更容易接入 DI、日志、安全、审批

这时候,Class-based Skill 的价值就体现出来了。

它不是把 Skill 做成一个文件目录,而是直接把 Skill 做成一个强类型 C# 类。

二、AgentClassSkill<T> 到底带来了什么?

Class-based Skill 最核心的变化,是把 Skill 的关键组成重新收拢到一个类型里。

比如在物流换算场景里,一个最小但有业务意义的 Skill 可以这样写:

#pragma warning disable MAAI001using System.ComponentModel;using System.Text.Json;internal sealed class UnitConverterClassSkill : AgentClassSkill<UnitConverterClassSkill>{    public override AgentSkillFrontmatter Frontmatter { get; } = new(        "unit-converter",        "Convert between common logistics units.");    protected override string Instructions => """        当用户询问距离或重量换算时:        1. 先读取 conversion-table        2. 再调用 convert        3. 回复中包含原始值、系数和结果        """;    [AgentSkillResource("conversion-table")]    [Description("距离与重量换算系数表")]    public string ConversionTable => """        miles -> kilometers = 1.60934        kilograms -> pounds = 2.20462        """;    [AgentSkillScript("convert")]    [Description("按 value × factor 执行换算")]private static string Convert(double value, double factor)    {        return JsonSerializer.Serialize(new        {            value,            factor,            result = Math.Round(value * factor, 4)        });    }}

这段代码已经把 FrontmatterInstructions、资源和脚本收进了同一个类里。

这件事的意义,不只是“结构更整齐”,而是 Skill 的边界终于变得清晰了。

你不再需要把说明写在一处、资源放在一处、动作散在另一处,而是可以把这些能力收敛成一个完整对象。

从工程角度看,这种写法真正带来的不是优雅感,而是可控感。

三、它是怎么进入 Agent 运行链路的?

Class-based Skill 的底层思路其实并不复杂。

框架会通过 Attribute 去扫描这个 Skill 类的成员:

  • • 哪些是资源
  • • 哪些是脚本
  • • 它们分别叫什么
  • • 该怎么向模型描述

然后,再把这个 Skill 实例交给 AgentSkillsProvider,作为 AIContextProvider 挂到 Agent 上下文中。

例如:

var classSkill = new UnitConverterClassSkill();var provider = new AgentSkillsProvider(classSkill);var agentOptions = new ChatClientAgentOptions{    Name = "ClassSkillUnitConverterAgent",    AIContextProviders = [provider]};

这意味着,AgentClassSkill<T> 并不是一个孤立的类型设计,而是和整个 Agent 上下文装配机制连在一起的。

模型最终看到的,也不是“一个 C# 类”,而是这个类经过扫描、注册和注入之后形成的 Skill 语义。

所以,Class-based Skill 的本质不是“代码式 Skill”,而是“通过强类型类来声明 Skill”。

四、为什么它更适合工程化?

这一点,才是 Class-based Skill 最值得关注的地方。

因为一旦 Skill 变成标准 C# 类,你就自然拥有了很多 File-based 很难直接获得的能力:

  • • 编译期检查
  • • IDE 重构支持
  • • 更自然的单元测试
  • • 更方便接入依赖注入
  • • 更容易挂接日志、安全、审批等治理机制

换句话说,Class-based Skill 的优势,不在“分发”,而在“治理”。

它特别适合那些已经不再频繁变化、但需要长期演进和稳定维护的核心能力。

所以从定位上看:

  • • File-based Skill 更像外部化能力资产
  • • Class-based Skill 更像系统内部能力模块

前者强调可共享,后者强调可治理。

五、它和 File-based Skill 是替代关系吗?

不是。

这两者最容易被误解成“一个新版本替代一个旧版本”,但其实不是这个关系。

更准确地说,它们解决的是不同层面的问题:

  • • File-based Skill 解决“怎么把能力做成资产”
  • • Class-based Skill 解决“怎么把能力做成模块”

一个偏外部化,一个偏工程化。

所以在真实项目里,它们很可能会长期共存:

  • • 可分发、可热更新、可共享的能力,适合 File-based
  • • 稳定、核心、需要测试和治理的能力,适合 Class-based

真正成熟的思路,不是争论哪一种更高级,而是根据能力属性选择更合适的载体。

六、总结

Class-based Skill 真正补齐的,不是另一种写法,而是一条更适合长期治理的 Skill 工程路径。

它让 Skill 不再只是“能被 Agent 调用”,也开始具备“能被系统长期维护”的结构基础。

如果说 Inline Skill 代表“先接入”,File-based Skill 代表“再沉淀”,那么 Class-based Skill 代表的,就是“进一步治理”。

下一篇,我们就把这三种写法放在一起看:Inline、File-based、Class-based,到底该怎么选?