在正式介绍AgentSkillsProvider针对Agent Skills机制的设计和实现原理之前,我们先通过一个简单的实例演示一下Agent Skills在MAF中的编程模式。我们定义了一个名为translator的Skill,它的功能是将中文古典诗词翻译成英文。我们将这个Markdown文件保存在./skills/translator/SKILL.md路径下,内容如下:

---
name: translator
description: 将中文古典诗词精确翻译成地道的英文
---

## 详细指令

你是一位精通汉学与英美文学的翻译大师。请严格按照以下 3 条规则处理输入的古典诗词:

1. **必须提供三种翻译变体**:
   - **变体一:古典韵律版**(必须**押韵**,注重节拍和英诗的传统美感)
   - **变体二:现代诗**(不强求押韵,注重现代诗的流动感)
   - **变体三:孤寂禅意版**(字句极简,精准保留原诗的**禅意**与留白,不做过度解释)

2. **输出格式**:
   请直接以 Markdown 标题列出这三种变体,并在最下方提供必要的背景(典故)补充。

具体的演示程序如下所示:我们根据存储Skill的目录./skills创建了一个AgentSkillsProvider实例。然后我们创建了一个OpenAIClient对象,并将其转换IChatClient对象。在调用AsAIAgent方法将IChatClient转换成IAgent的过程中,我们利用指定的ChatClientAgentOptions完成了针对AgentSkillsProvider的注册。最后我们调用AgentRunAsync方法,并传入一首中文古典诗词,来查看Agent基于这个Skill生成的翻译结果。

using dotenv.net;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
using System.ClientModel;

DotEnv.Load();

var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;

var agentSkillsProvider = new AgentSkillsProvider(["./skills"]);
var agent = new OpenAIClient(
        credential: new ApiKeyCredential(key: apiKey),
        options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
    .GetChatClient(model:model)
    .AsIChatClient()
    .AsAIAgent(options: new ChatClientAgentOptions {  AIContextProviders = [agentSkillsProvider] });

var response = await agent.RunAsync(message: "把这句诗翻译成英文:千江同一月,万户尽皆春。千江有水千江月,万里无云万里天。");
Console.WriteLine(response.Text);

输出:

### 变体一:古典韵律版(押韵)

One moon above a thousand streams is cast,
In every home the breath of spring holds fast.
Where waters flow, there shines the selfsame light;
Cloudless for miles on miles—the boundless height.

---

### 变体二:现代诗版

The same moon
rests on a thousand rivers.
Spring enters every house.

Where there is water,
there is moonlight.
Ten thousand miles—
no clouds,
only sky.

---

### 变体三:孤寂禅意版

One moon.
A thousand rivers.

Water—moon.
Sky—endless.

---

### 背景补充

“千江有水千江月”常见于佛家语境,强调“月”象征真理或佛性,“水”象征众生之心——水清则月现,意指同一真理在万象中显现;“万里无云万里天”则寓意心境澄明,无所遮蔽。整组诗句体现出华夏诗学中“万物一体”的宇宙观与禅宗“明心见性”的思想。

2. AgentSkill

Skill在MAF中被定义为一个抽象类AgentSkill,它定义了组成一个完整Skill的四个元素。

public abstract class AgentSkill
{
	public abstract AgentSkillFrontmatter Frontmatter { get; }
	public abstract string Content { get; }
	public virtual IReadOnlyList<AgentSkillResource>? Resources => null;
	public virtual IReadOnlyList<AgentSkillScript>? Scripts => null;
}

属性成员说明如下:

  • Frontmatter:Skill的元数据,包含Skill的名称、描述等信息。因元数据定义在SKILL.md文件的YAML Frontmatter中而得名;
  • Content:Skill的内容,通常是一些指令性的文本,指导Agent如何使用这个Skill来完成特定的任务;
  • Resources:向LLM提供执行该Skill所需的外部静态资源。比如提示词模板、固定的知识库片段、提示用的示例等;
  • Scripts:向LLM提供可执行的脚本。LLM在触发该Skill后,可以通过执行这些脚本来完成特定的逻辑操作(如数据处理、API请求等);

具有如下定义的AgentSkillFrontmatter提供Skill的名片。包含Skill名称、功能描述、许可证(License)以及兼容性信息。主要用于LLM的Skill发现(Discovery)阶段,让LLM知道何时该调用这个Skill。Frontmatter Spec为每个字段提供了详细的规范。

public sealed class AgentSkillFrontmatter
{
	public string Name { get; }
	public string Description { get; }
	public string? License { get; set; }
	public string? Compatibility{ get; set; }
	public string? AllowedTools { get; set; }

	public AdditionalPropertiesDictionary? Metadata { get; set; }
}

属性成员定义如下:

  • Name:名称,必须唯一;
  • Description:描述信息,简要说明这个Skill的功能和用途;
  • License:许可证信息,说明这个Skill的使用和分发权限;
  • Compatibility:兼容性信息,说明这个Skill适用于哪些类型的Agent或环境;
  • AllowedTools:允许使用的工具列表;
  • Metadata:一个可选的字典,用于存储Skill的其他元数据信息,允许Skill提供更多的自定义属性;

具有如下定义的抽象类AgentSkillResource描述Skill资源,它们为特定Skill提供补充性的静态内容、数据参考或静态资产。AgentSkillResource除了为每个资源提供一个名称和一个可选的描述信息外,还定义了一个抽象方法ReadAsync用于读取这个资源的内容。

public abstract class AgentSkillResource
{
    public string Name { get; }
    public string? Description { get; }
    public abstract Task<object?> ReadAsync(IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default);
}

为Skill提供的脚本通过如下的抽象类AgentSkillScript定义,除了提供一个名称和一个可选的描述信息外,还定义了一个抽象方法RunAsync用于执行这个脚本。AgentSkillScript相当于一个AIFunction对象表示的工具函数,而且传入RunAsync方法的脚本参数直接就是一个AIFunctionArguments对象。

public abstract class AgentSkillScript
{
	public string Name { get; }
	public string? Description { get; }
	public abstract Task<object?> RunAsync(
        AgentSkill skill, 
        AIFunctionArguments arguments, 
        CancellationToken cancellationToken = default);
}
public class AIFunctionArguments : IDictionary<string, object?>

MAF为作为抽象类的AgentSkill提供了三种具体的实现方式,分别是:

  • AgentFileSkill:基于文件的Skill定义;
  • AgentInlineSkill:将Skill元数据、指令内容、资源和脚本直接封装在一个AgentInlineSkill对象中;
  • AgentClassSkill<T>:通过继承AgentClassSkill<T>,并利用特性标记成员的方式来定义Skill的资源和脚本;

2.1 AgentFileSkill

基于文件定义的Skill对应如下这个AgentFileSkill类,我们在构建此对象使需要指定Skill文件的路径。

public sealed class AgentFileSkill : AgentSkill
{	
	public override AgentSkillFrontmatter Frontmatter { get; }
	public override string Content { get; }
	public string Path { get; }
	public override IReadOnlyList<AgentSkillResource> Resources { get; }
	public override IReadOnlyList<AgentSkillScript> Scripts { get; }

	internal AgentFileSkill(
        AgentSkillFrontmatter frontmatter, 
        string content, 
        string path, 
        IReadOnlyList<AgentSkillResource>? resources = null, 
        IReadOnlyList<AgentSkillScript>? scripts = null);
}

一般来说,通过AgentFileSkill类型表示的Skill,其资源和脚本类型也是对应的AgentFileSkillResourceAgentFileSkillScript。如下面的代码所示,AgentFileSkillResource是一个内部类型。不论是构建一个AgentFileSkillResource对象,还是构建一个AgentFileSkillScript对象,我们都需要提供一个文件的完整路径。对于资源来说,ReadAsync方法会读取这个文件的内容并返回;对于脚本来说,RunAsync方法会利用提供的Runner来执行这个脚本文件。这个指定脚本的Runner体现为一个类型为AgentFileSkillScriptRunner的委托对象,该委托的三个输入参数分别代表触发这个脚本的Skill对象、这个脚本对象以及LLM传入的参数,返回值则是脚本执行后的结果。

internal sealed class AgentFileSkillResource : AgentSkillResource
{
	public string FullPath { get; }
	public AgentFileSkillResource(string name, string fullPath);
	public override async Task<object?> ReadAsync(
        IServiceProvider? serviceProvider = null, 
        CancellationToken cancellationToken = default);
}

public sealed class AgentFileSkillScript : AgentSkillScript
{
	public string FullPath { get; }
	internal AgentFileSkillScript(
        string name, 
        string fullPath, 
        AgentFileSkillScriptRunner? runner = null);
	public override async Task<object?> RunAsync(
        AgentSkill skill, 
        AIFunctionArguments arguments, 
        CancellationToken cancellationToken = default);
}

public delegate Task<object?> AgentFileSkillScriptRunner(
    AgentFileSkill skill, 
    AgentFileSkillScript script, 
    AIFunctionArguments arguments, 
    CancellationToken cancellationToken);

2.2 AgentInlineSkill

AgentInlineSkill提供了一种纯代码驱动(Code-First)的方式。我们可以创建一个AgentInlineSkill对象,并利用定义的Fluent API直接绑定元数据、提示词指令、静态资源以及可执行的脚本函数。

public sealed class AgentInlineSkill : AgentSkill
{
	public override AgentSkillFrontmatter Frontmatter { get; }
	public override string Content { get; }
	public override IReadOnlyList<AgentSkillResource>? Resources { get; }
	public override IReadOnlyList<AgentSkillScript>? Scripts { get; }

	public AgentInlineSkill(AgentSkillFrontmatter frontmatter, string instructions);
	public AgentInlineSkill(
        string name, 
        string description, 
        string instructions,
        string? license = null, 
        string? compatibility = null, 
        string? allowedTools = null, 
        AdditionalPropertiesDictionary? metadata = null);

	public AgentInlineSkill AddResource(string name, object value, string? description = null);
	public AgentInlineSkill AddResource(string name, Delegate method, string? description = null);
	public AgentInlineSkill AddScript(string name, Delegate method, string? description = null);
}

通过AddResourceAddScript方法添加的资源和脚本类型也与之匹配,分别是具有如下定义的AgentInlineSkillResourceAgentInlineSkillScript类型。AgentInlineSkillResource使用该委托对象来读取资源的内容,AgentInlineSkillScript使用该对象表示可执行的脚本。由于AIFunctionFactory可以将一个委托对象转换成一个AIFunction对象,所以资源读取和脚本都可以通过执行AIFunction对象的方式来完成。

internal sealed class AgentInlineSkillResource : AgentSkillResource
{
	public AgentInlineSkillResource(string name, object value, string? description = null);
	public AgentInlineSkillResource(string name, Delegate method, string? description = null);
	public override async Task<object?> ReadAsync(
        IServiceProvider? serviceProvider = null, 
        CancellationToken cancellationToken = default);
}
internal sealed class AgentInlineSkillScript : AgentSkillScript
{
	public JsonElement? ParametersSchema { get; }
	public AgentInlineSkillScript(string name, Delegate method, string? description = null);
	public override async Task<object?> RunAsync(
        AgentSkill skill, 
        AIFunctionArguments arguments, 
        CancellationToken cancellationToken = default);
}

2.3 AgentClassSkill

除了上述两种内置的Skill定义方式,我们同样可以通过继承AgentClassSkill<TSelf>来实现自定义的Skill类型,以满足特定的需求。对应组成Skill的三种基本元素,最为核心的指令是必需的,所以抽象类AgentClassSkill<TSelf>将其定义成必需实现的抽象属性Instructions,而资源和脚本则是可选的,所以对应的属性ResourcesScripts被定义成virtual成员,默认返回null。

public abstract class AgentClassSkill<TSelf> : AgentSkill where TSelf : AgentClassSkill<TSelf>
{    
    protected abstract string Instructions { get; }
    protected virtual JsonSerializerOptions? SerializerOptions => null;
    public override string Content {get;}
    public override IReadOnlyList<AgentSkillResource>? Resources{get;}
    public override IReadOnlyList<AgentSkillScript>? Scripts {get;}
}

当我们继承AgentClassSkill<TSelf>来定义一个Skill类时,利用具有如下定义的AgentSkillResourceAttributeAgentSkillScriptAttribute将就具有合法签名的方法来表示资源和脚本。资源除了定义成方法之外也可以定义成属性。定义在AgentClassSkill<TSelf>中的ResourcesScripts属性会通过反射扫描这些被标记的方法和属性,并将它们转换成AgentSkillResourceAgentSkillScript对象,并最终返回资源和脚本列表。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class AgentSkillResourceAttribute : Attribute
{
    public string? Name { get; }
    public AgentSkillResourceAttribute();
    public AgentSkillResourceAttribute(string name);
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class AgentSkillScriptAttribute : Attribute
{;
    public string? Name { get; }
    public AgentSkillScriptAttribute();
    public AgentSkillScriptAttribute(string name);
}

标注了AgentSkillResourceAttribute的方法要求是无参数,或者只包含类型为IServiceProviderCancellationToken的参数。标注在方法或者属性上的DescriptionAttribute特性将用于描述对应的资源和脚本。除此之外,ResourcesScripts属性还会验证资源和脚本名称的唯一性,确保不会有重复的资源和脚本名称。

MAF官方文档提供了如下这个UnitConverterSkill类型,旨在完成英里/公里以及磅/千克之间的转换。我们可以看到它通过标记AgentSkillResourceAttribute特性来定义了一个名为conversion-table的资源,通过标记AgentSkillScriptAttribute特性来定义了一个名为convert的脚本。

using System.ComponentModel;
using System.Text.Json;
using Microsoft.Agents.AI;

internal sealed class UnitConverterSkill : AgentClassSkill<UnitConverterSkill>
{
    public override AgentSkillFrontmatter Frontmatter { get; } = new(
        "unit-converter",
        "Convert between common units using a multiplication factor. Use when asked to convert miles, kilometers, pounds, or kilograms.");

    protected override string Instructions => """
        Use this skill when the user asks to convert between units.

        1. Review the conversion-table resource to find the correct factor.
        2. Use the convert script, passing the value and factor from the table.
        3. Present the result clearly with both units.
        """;

    [AgentSkillResource("conversion-table")]
    [Description("Lookup table of multiplication factors for common unit conversions.")]
    public string ConversionTable => """
        # Conversion Tables
        Formula: **result = value × factor**
        | From       | To         | Factor   |
        |------------|------------|----------|
        | miles      | kilometers | 1.60934  |
        | kilometers | miles      | 0.621371 |
        | pounds     | kilograms  | 0.453592 |
        | kilograms  | pounds     | 2.20462  |
        """;

    [AgentSkillScript("convert")]
    [Description("Multiplies a value by a conversion factor and returns the result as JSON.")]
    private static string ConvertUnits(double value, double factor)
    {
        double result = Math.Round(value * factor, 4);
        return JsonSerializer.Serialize(new { value, factor, result });
    }
}

3. AgentSkillsSource

我们可用为AgentSkillsProvider提供来源于不同渠道的Skill,如下这个AgentSkillsSource抽象了Skill的来源,派生于它的子类都可以利用重写的GetSkillsAsync方法来为AgentSkillsProvider提供Skill集合。

public abstract class AgentSkillsSource
{
	public abstract Task<IList<AgentSkill>> GetSkillsAsync(
        CancellationToken cancellationToken = default);
}

3.1 AgentInMemorySkillsSource & AgentFileSkillsSource

MAF内置AgentInMemorySkillsSourceAgentFileSkillsSource这两种。前者直接从内存中提供Skill集合,后者则从文件系统中加载Skill集合。它们提供AgentSkill的具体类型就是前面介绍的AgentInlineSkillAgentFileSkill

internal sealed class AgentInMemorySkillsSource : AgentSkillsSource
{
	public AgentInMemorySkillsSource(IEnumerable<AgentSkill> skills);
	public override Task<IList<AgentSkill>> GetSkillsAsync(
        CancellationToken cancellationToken = default);
}

internal sealed class AgentFileSkillsSource : AgentSkillsSource
{
	public AgentFileSkillsSource(
        string skillPath, 
        AgentFileSkillScriptRunner? scriptRunner = null, 
        AgentFileSkillsSourceOptions? options = null, 
Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐