通过


智能体技能

代理技能 是指令、脚本和资源的可移植包,可提供代理的专用功能和域专业知识。 技能遵循开放规范并实现渐进式披露模式,以便代理在需要时仅加载所需的上下文。

在需要时使用代理技能:

  • 封装领域专业知识 - 将专业知识(费用策略、法律工作流、数据分析管道)捕获为可重用且可移植的包。
  • 扩展代理功能 - 为代理提供新功能,而无需更改其核心指令。
  • 确保一致性 - 将多步骤任务转换为可重复的可审核工作流。
  • 启用互作性 - 在不同的代理技能兼容产品中重复使用相同的技能。

技能结构

技能是一个目录,包含一个 SKILL.md 文件,并且可以选择包括用于存放资源的子目录:

expense-report/
├── SKILL.md                          # Required — frontmatter + instructions
├── scripts/
│   └── validate.py                   # Executable code agents can run
├── references/
│   └── POLICY_FAQ.md                 # Reference documents loaded on demand
└── assets/
    └── expense-report-template.md    # Templates and static resources

SKILL.md 格式

该文件 SKILL.md 必须包含 YAML 前置数据,后跟 Markdown 内容:

---
name: expense-report
description: File and validate employee expense reports according to company policy. Use when asked about expense submissions, reimbursement rules, or spending limits.
license: Apache-2.0
compatibility: Requires python3
metadata:
  author: contoso-finance
  version: "2.1"
---
字段 必选 Description
name 是的 最多 64 个字符。 仅小写字母、数字和连字符。 不得以连字符开头或结尾,不得包含连续连字符。 必须与父目录名称匹配。
description 是的 技能的作用以及何时使用它。 最多 1024 个字符。 应包含帮助代理识别相关任务的关键字。
license 许可证名称或对捆绑许可证文件的引用。
compatibility 最多 500 个字符。 指示环境要求(预期产品、系统包、网络访问等)。
metadata 其他元数据的任意键值映射。
allowed-tools 技能可以使用的预先批准的工具的以空格分隔的列表。 实验性 — 代理实现之间的支持可能有所不同。

frontmatter 后面的 Markdown 正文包含技能说明- 分步指导、输入和输出示例、常见边缘情况或任何帮助代理执行任务的内容。 保留 SKILL.md 500 行以下,并将详细的参考资料移动到单独的文件中。

渐进式披露

代理技能使用四阶段渐进式披露模式来最大程度地减少上下文使用情况:

  1. 宣传 (每个技能大约 100 个标识符)——技能名称和描述在每次运行开始时被注入系统提示符,从而让代理了解哪些技能是可用的。
  2. 加载 (< 建议使用 5000 个令牌) - 当任务与技能的域匹配时,代理会调用 load_skill 该工具来检索完整的 SKILL.md 正文,其中包含详细说明。
  3. 读取资源 (根据需要) - 代理仅在需要时调用 read_skill_resource 该工具以提取补充文件(引用、模板、资产)。
  4. 运行脚本 (根据需要) - 代理调用该工具 run_skill_script 以执行与技能捆绑的脚本。

此模式使代理的上下文窗口保持精简状态,同时允许它按需访问深层域知识。

注释

load_skill 始终被广告宣传。 read_skill_resource 仅当至少有一个技能具有资源时才发布。 run_skill_script 仅当至少有一个技能具有脚本时才公布。

向代理提供技能

AgentSkillsProvider(C#)和SkillsProvider(Python)是提供上下文的程序,使代理可以使用技能。 它们支持三个技能源:

  • 基于文件的 技能 - 从 SKILL.md 文件系统目录中的文件发现的技能
  • 代码定义 — 使用 AgentInlineSkill (C#)或 Skill (Python)内联定义于代码中的技能
  • 基于类 的 — 封装在 C# 类中的技能派生自 AgentClassSkill<T> (仅限 C#)

若要在一个提供程序中混合多个源,请使用 AgentSkillsProviderBuilder (仅限 C# — 请参阅 Builder:高级多源方案)。

基于文件的技能

创建一个指向包含你技能的目录的AgentSkillsProvider,并将其添加到代理的上下文提供者中。 传递一个脚本执行器以允许执行技能目录中存在的基于文件的脚本:

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;

string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";

// Discover skills from the 'skills' directory
var skillsProvider = new AgentSkillsProvider(
    Path.Combine(AppContext.BaseDirectory, "skills"));

// Create an agent with the skills provider
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
    .GetResponsesClient()
    .AsAIAgent(new ChatClientAgentOptions
    {
        Name = "SkillsAgent",
        ChatOptions = new()
        {
            Instructions = "You are a helpful assistant.",
        },
        AIContextProviders = [skillsProvider],
    },
    model: deploymentName);

警告

DefaultAzureCredential 对于开发来说很方便,但在生产中需要仔细考虑。 在生产环境中,请考虑使用特定凭据(例如), ManagedIdentityCredential以避免延迟问题、意外凭据探测以及回退机制的潜在安全风险。

多个技能目录

可以将提供程序指向一个父目录:每个包含 SKILL.md 的子目录都会自动被识别为技能。

var skillsProvider = new AgentSkillsProvider(
    Path.Combine(AppContext.BaseDirectory, "all-skills"));

或者传递路径列表以搜索多个根目录:

var skillsProvider = new AgentSkillsProvider(
    [
        Path.Combine(AppContext.BaseDirectory, "company-skills"),
        Path.Combine(AppContext.BaseDirectory, "team-skills"),
    ]);

提供程序最多搜索两个级别。

自定义资源发现

默认情况下,提供程序在referencesassets子目录中识别具有.md.json.yaml.yml.csv.xml.txt扩展名的资源。 用于 AgentFileSkillsSourceOptions 更改以下默认值:

var fileOptions = new AgentFileSkillsSourceOptions
{
    AllowedResourceExtensions = [".md", ".txt"],
    ResourceDirectories = ["docs", "templates"],
};

var skillsProvider = new AgentSkillsProvider(
    Path.Combine(AppContext.BaseDirectory, "skills"),
    fileOptions: fileOptions);

脚本执行

SubprocessScriptRunner.RunAsync 作为第二个参数传递给 AgentSkillsProvider 来启用通过文件的脚本执行:

var skillsProvider = new AgentSkillsProvider(
    Path.Combine(AppContext.BaseDirectory, "skills"),
    SubprocessScriptRunner.RunAsync);

SubprocessScriptRunner.RunAsync 大致等效于以下内容:

// Simplified equivalent of what SubprocessScriptRunner.RunAsync does internally
using System.Diagnostics;

static async Task<string> RunAsync(AgentSkill skill, AgentSkillScript script, IDictionary<string, object?>? args)
{
    var psi = new ProcessStartInfo("python3")
    {
        RedirectStandardOutput = true,
        UseShellExecute = false,
    };
    psi.ArgumentList.Add(Path.Combine(skill.Path, script.Path));
    if (args != null)
    {
        foreach (var (key, value) in args)
        {
            if (value is not null)
            {
                psi.ArgumentList.Add($"--{key}");
                psi.ArgumentList.Add(value.ToString()!);
            }
        }
    }
    using var process = Process.Start(psi)!;
    string output = await process.StandardOutput.ReadToEndAsync();
    await process.WaitForExitAsync();
    return output.Trim();
}

运行程序将每个发现的脚本作为本地子进程运行,将代理提供的 JSON 参数转发为命令行标志。

警告

SubprocessScriptRunner 仅用于演示目的。 对于生产用途,请考虑添加:

  • 沙盒(例如容器或隔离的执行环境)
  • 资源限制(CPU、内存、壁钟超时)
  • 输入验证和可执行脚本的允许列表
  • 结构化日志记录和审计追踪

自定义脚本发现

默认情况下,提供程序会识别在scripts子目录中的具有.py.js.sh.ps1.cs.csx扩展名的脚本。 用于 AgentFileSkillsSourceOptions 更改以下默认值:

传递给AgentFileSkillsSourceOptionsAgentSkillsProvider构造函数或传递给UseFileSkill / UseFileSkills生成器。

var fileOptions = new AgentFileSkillsSourceOptions
{
    AllowedScriptExtensions = [".py"],
    ScriptDirectories = ["scripts", "tools"],
};

// Via constructor
var skillsProvider = new AgentSkillsProvider(
    Path.Combine(AppContext.BaseDirectory, "skills"),
    fileOptions: fileOptions);

// Via builder
var skillsProvider = new AgentSkillsProviderBuilder()
    .UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills"), options: fileOptions)
    .Build();

基于文件的技能

创建一个指向包含您技能的目录的SkillsProvider,并将其添加到代理的上下文提供者中。

import os
from pathlib import Path
from agent_framework import SkillsProvider
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity.aio import AzureCliCredential

# Discover skills from the 'skills' directory
skills_provider = SkillsProvider(
    skill_paths=Path(__file__).parent / "skills"
)

# Create an agent with the skills provider
agent = OpenAIChatCompletionClient(
    model=os.environ["AZURE_OPENAI_CHAT_COMPLETION_MODEL"],
    azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
    api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
    credential=AzureCliCredential(),
).as_agent(
    name="SkillsAgent",
    instructions="You are a helpful assistant.",
    context_providers=[skills_provider],
)

多个技能目录

可以将提供程序指向单个父文件夹,包含 SKILL.md 的每个子文件夹会被自动识别为技能。

skills_provider = SkillsProvider(
    skill_paths=Path(__file__).parent / "all-skills"
)

或者传递路径列表以搜索多个根目录:

skills_provider = SkillsProvider(
    skill_paths=[
        Path(__file__).parent / "company-skills",
        Path(__file__).parent / "team-skills",
    ]
)

提供程序最多搜索两个级别。

自定义资源发现

默认情况下, SkillsProvider 识别具有扩展 .md.json.yaml.yml.csv.xml和的资源 .txt。 它会扫描每个技能文件夹中的所有子目录。 传递 resource_extensions 以更改已识别的文件类型:

skills_provider = SkillsProvider(
    skill_paths=Path(__file__).parent / "skills",
    resource_extensions=(".md", ".txt"),
)

脚本执行

若要启用基于文件的脚本执行,请将 script_runner 传递给 SkillsProvider。 可以使用满足 SkillScriptRunner 协议的任何同步或异步可调用项:

from pathlib import Path
from agent_framework import Skill, SkillScript, SkillsProvider

def my_runner(skill: Skill, script: SkillScript, args: dict | None = None) -> str:
    """Run a file-based script as a subprocess."""
    import subprocess, sys
    cmd = [sys.executable, str(Path(skill.path) / script.path)]
    if args:
        for key, value in args.items():
            if value is not None:
                cmd.extend([f"--{key}", str(value)])
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    return result.stdout.strip()

skills_provider = SkillsProvider(
    skill_paths=Path(__file__).parent / "skills",
    script_runner=my_runner,
)

运行程序接收已解析的SkillSkillScript以及可选args字典。 基于文件的脚本会自动从 .py 技能目录中的文件发现。

警告

上述示例运行程序 仅用于演示目的。 对于生产用途,请考虑添加:

  • 沙盒化(例如,容器seccompfirejail
  • 资源限制(CPU、内存、壁钟超时)
  • 输入验证和可执行脚本的允许列表
  • 结构化日志记录和审计追踪

注释

如果提供了基于文件的技能并带有脚本,但未设置 script_runner,则 SkillsProvider 会引发一个 ValueError

代码定义的技能

除了从 SKILL.md 文件中发现的基于文件的技能之外,还可以使用 AgentInlineSkill完全在代码中定义技能。 代码定义的技能在以下情况下非常有用:

  • 技能内容是动态生成的(例如,从数据库或环境读取)。
  • 你希望将技能定义与使用它们的应用程序代码一起保留。
  • 你需要在读取时执行逻辑的资源,而不是提供静态文件。

基本代码技能

使用名称、描述和指令创建一个 AgentInlineSkill 。 使用 .AddResource() 附加资源:

using Microsoft.Agents.AI;

var codeStyleSkill = new AgentInlineSkill(
    name: "code-style",
    description: "Coding style guidelines and conventions for the team",
    instructions: """
        Use this skill when answering questions about coding style, conventions, or best practices for the team.
        1. Read the style-guide resource for the full set of rules.
        2. Answer based on those rules, quoting the relevant guideline where helpful.
        """)
    .AddResource(
        "style-guide",
        """
        # Team Coding Style Guide
        - Use 4-space indentation (no tabs)
        - Maximum line length: 120 characters
        - Use type annotations on all public methods
        """);

var skillsProvider = new AgentSkillsProvider(codeStyleSkill);

动态资源

将工厂委托传递给 .AddResource(),以便在运行时计算内容。 每次代理读取资源时都会调用委托:

var projectInfoSkill = new AgentInlineSkill(
    name: "project-info",
    description: "Project status and configuration information",
    instructions: """
        Use this skill for questions about the current project.
        1. Read the environment resource for deployment configuration details.
        2. Read the team-roster resource for information about team members.
        """)
    .AddResource("environment", () =>
    {
        string env = Environment.GetEnvironmentVariable("APP_ENV") ?? "development";
        string region = Environment.GetEnvironmentVariable("APP_REGION") ?? "us-east-1";
        return $"Environment: {env}, Region: {region}";
    })
    .AddResource(
        "team-roster",
        "Alice Chen (Tech Lead), Bob Smith (Backend Engineer)");

脚本由代码定义

使用 .AddScript() 将委托注册为可执行脚本。 代码定义的脚本作为直接委托调用 运行进程内 。 不需要脚本执行器。 委托的已定义类型参数会自动转换为代理用来传递参数的 JSON 模式:

using System.Text.Json;

var unitConverterSkill = new AgentInlineSkill(
    name: "unit-converter",
    description: "Convert between common units using a conversion factor",
    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.
        """)
    .AddResource(
        "conversion-table",
        """
        # 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  |
        """)
    .AddScript("convert", (double value, double factor) =>
    {
        double result = Math.Round(value * factor, 4);
        return JsonSerializer.Serialize(new { value, factor, result });
    });

var skillsProvider = new AgentSkillsProvider(unitConverterSkill);

注释

若要在单个提供程序中将代码定义的技能与基于文件的技能或基于类的技能相结合,请使用 AgentSkillsProviderBuilder - 请参阅 Builder:高级多源方案

除了从 SKILL.md 文件中发现的基于文件的技能之外,还可以在Python代码中完全定义技能。 代码定义的技能在以下情况下非常有用:

  • 技能内容是动态生成的(例如,从数据库或环境读取)。
  • 你希望将技能定义与使用它们的应用程序代码一起保留。
  • 你需要在读取时执行逻辑的资源,而不是提供静态文件。

基本代码技能

使用具有名称、描述和指令内容的Skill实例。 (可选)附加 SkillResource 包含静态内容的实例:

from textwrap import dedent
from agent_framework import Skill, SkillResource, SkillsProvider

code_style_skill = Skill(
    name="code-style",
    description="Coding style guidelines and conventions for the team",
    content=dedent("""\
        Use this skill when answering questions about coding style,
        conventions, or best practices for the team.
    """),
    resources=[
        SkillResource(
            name="style-guide",
            content=dedent("""\
                # Team Coding Style Guide
                - Use 4-space indentation (no tabs)
                - Maximum line length: 120 characters
                - Use type annotations on all public functions
            """),
        ),
    ],
)

skills_provider = SkillsProvider(skills=[code_style_skill])

动态资源

使用 @skill.resource 修饰器将函数注册为资源。 每次代理读取资源时都会调用此函数,因此可以返回最新的数据。 支持同步和异步函数:

import os
from agent_framework import Skill

project_info_skill = Skill(
    name="project-info",
    description="Project status and configuration information",
    content="Use this skill for questions about the current project.",
)

@project_info_skill.resource
def environment() -> Any:
    """Get current environment configuration."""
    env = os.environ.get("APP_ENV", "development")
    region = os.environ.get("APP_REGION", "us-east-1")
    return f"Environment: {env}, Region: {region}"

@project_info_skill.resource(name="team-roster", description="Current team members")
def get_team_roster() -> Any:
    """Return the team roster."""
    return "Alice Chen (Tech Lead), Bob Smith (Backend Engineer)"

在没有参数的情况下使用修饰器时(@skill.resource),函数名称将成为资源名称,docstring 将成为说明。 使用 @skill.resource(name="...", description="...") 显式设置它们。

脚本由代码定义

使用 @skill.script 修饰器将函数注册为技能上的可执行脚本。 代码定义的脚本 在进程内 运行,不需要脚本执行程序。 支持同步和异步函数:

from agent_framework import Skill

unit_converter_skill = Skill(
    name="unit-converter",
    description="Convert between common units using a conversion factor",
    content="Use the convert script to perform unit conversions.",
)

@unit_converter_skill.script(name="convert", description="Convert a value: result = value × factor")
def convert_units(value: float, factor: float) -> str:
    """Convert a value using a multiplication factor."""
    import json
    result = round(value * factor, 4)
    return json.dumps({"value": value, "factor": factor, "result": result})

在没有参数的情况下使用修饰器时(@skill.script),函数名称将成为脚本名称,docstring 将成为说明。 函数的类型化参数会自动转换为代理用于传递参数的 JSON 架构。

组合基于文件的技能和代码定义的技能

skill_pathsskills 同时传递给单个 SkillsProvider。 首先发现基于文件的技能;如果代码定义的技能与现有基于文件的技能同名,则跳过代码定义的技能:

from pathlib import Path
from agent_framework import Skill, SkillsProvider

my_skill = Skill(
    name="my-code-skill",
    description="A code-defined skill",
    content="Instructions for the skill.",
)

skills_provider = SkillsProvider(
    skill_paths=Path(__file__).parent / "skills",
    skills=[my_skill],
)

基于课堂的技能

基于类的技能允许将所有技能组件(名称、说明、说明、资源和脚本)捆绑到单个 C# 类中。 从 AgentClassSkill<T> 派生(其中 T 是类),然后使用 [AgentSkillResource] 注解属性,并使用 [AgentSkillScript] 注解方法,以实现自动发现:

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 });
    }
}

使用AgentSkillsProvider注册基于类的技能

var skill = new UnitConverterSkill();
var skillsProvider = new AgentSkillsProvider(skill);

[AgentSkillResource] 属性应用于属性或方法时,其返回值将在代理读取资源时用作资源内容——如果需要在读取时计算内容,则使用方法。 当[AgentSkillScript]应用于方法时,代理调用脚本时该方法将被调用。 使用[Description]System.ComponentModel描述代理的每一项资源和脚本。

注释

AgentClassSkill<T> 还支持在基于属性的发现不适用的场景中,将 ResourcesScripts 作为集合来覆盖。

生成器:高级多源场景

对于简单的单源方案,请直接使用 AgentSkillsProvider 构造函数。 如果需要以下任一项,请使用 AgentSkillsProviderBuilder

  • 混合技能类型 - 在单个提供程序中合并基于文件的、代码定义的(AgentInlineSkill)和基于类的技能AgentClassSkill
  • 技能筛选 - 使用谓词包括或排除技能。

混合技能类型

将所有三种技能类型在一个提供程序中通过链式链接连接:UseFileSkillUseSkillUseFileScriptRunner

var skillsProvider = new AgentSkillsProviderBuilder()
    .UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills"))  // file-based skills
    .UseSkill(volumeConverterSkill)                                  // AgentInlineSkill
    .UseSkill(temperatureConverter)                                  // AgentClassSkill
    .UseFileScriptRunner(SubprocessScriptRunner.RunAsync)            // runner for file scripts
    .Build();

技能筛选

使用 UseFilter 仅包括符合您条件的技能,例如,从共享目录载入技能,但排除实验技能。

var approvedSkillNames = new HashSet<string> { "expense-report", "code-style" };

var skillsProvider = new AgentSkillsProviderBuilder()
    .UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills"))
    .UseFilter(skill => approvedSkillNames.Contains(skill.Frontmatter.Name))
    .Build();

脚本审批

使用 AgentSkillsProviderOptions.ScriptApproval 将所有脚本执行置于人工审批之后。 启用后,代理将暂停并返回审批请求,而不是立即执行:

var skillsProvider = new AgentSkillsProvider(
    skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
    options: new AgentSkillsProviderOptions
    {
        ScriptApproval = true,
    });

若要在生成器配置的提供程序上启用脚本审批,请使用 UseScriptApproval

var skillsProvider = new AgentSkillsProviderBuilder()
    .UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills"))
    .UseScriptApproval(true)
    .Build();

使用require_script_approval=TrueSkillsProvider上为所有脚本执行设置需要人工审批的权限。 代理会暂停并返回审批请求,而不是立即执行。

from agent_framework import Agent, Skill, SkillsProvider

# Create provider with approval enabled
skills_provider = SkillsProvider(
    skills=[my_skill],
    require_script_approval=True,
)

# Run the agent — script calls pause for approval
result = await agent.run("Deploy version 2.5.0 to production", session=session)

# Handle approval requests
while result.user_input_requests:
    for request in result.user_input_requests:
        print(f"Script: {request.function_call.name}")
        print(f"Args: {request.function_call.arguments}")

        approval = request.to_function_approval_response(approved=True)
        result = await agent.run(approval, session=session)

当脚本被拒绝(approved=False)时,代理会通知用户拒绝并可以相应地做出响应。

自定义系统提示

默认情况下,技能提供程序会注入系统提示,其中列出了可用的技能,并指示代理使用 load_skillread_skill_resource。 可以自定义此提示:

var skillsProvider = new AgentSkillsProvider(
    skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
    options: new AgentSkillsProviderOptions
    {
        SkillsInstructionPrompt = """
            You have skills available. Here they are:
            {skills}
            {resource_instructions}
            {script_instructions}
            """
    });

注释

自定义模板必须包含 {skills} (技能列表)、 {resource_instructions} (资源工具提示)和 {script_instructions} (脚本工具提示)占位符。 文本大括号必须转义为 {{}}

skills_provider = SkillsProvider(
    skill_paths=Path(__file__).parent / "skills",
    instruction_template=(
        "You have skills available. Here they are:\n{skills}\n"
        "Use the `load_skill` function to get skill instructions.\n"
        "Use the `read_skill_resource` function to read skill files."
    ),
)

注释

自定义模板必须包含一个 {skills} 占位符,用于插入技能列表,以及一个 {runner_instructions} 占位符,用于插入脚本相关指令。

缓存行为

默认情况下,技能工具和说明在首次生成后缓存。 将DisableCaching = true设置为AgentSkillsProviderOptions,以在每次调用时强制重建。

var skillsProvider = new AgentSkillsProvider(
    Path.Combine(AppContext.BaseDirectory, "skills"),
    options: new AgentSkillsProviderOptions
    {
        DisableCaching = true,
    });

注释

在开发过程中,当技能内容频繁更改时,禁用缓存非常有用。 在生产环境中,保留启用缓存(默认值),以提高性能。

依赖注入

技能资源和脚本委托函数可以声明一个 IServiceProvider 参数,代理框架会自动注入。 这样,技能就可以解析应用程序服务(例如数据库客户端、配置或业务逻辑),而无需将它们硬编码到技能定义中。

Setup

注册应用程序服务,并通过services参数将构建的IServiceProvider传递给代理:

using Microsoft.Extensions.DependencyInjection;

// Register application services
ServiceCollection services = new();
services.AddSingleton<ConversionService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Create the agent and pass the service provider
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
    .GetResponsesClient()
    .AsAIAgent(
        options: new ChatClientAgentOptions
        {
            Name = "ConverterAgent",
            ChatOptions = new() { Instructions = "You are a helpful assistant." },
            AIContextProviders = [skillsProvider],
        },
        model: deploymentName,
        services: serviceProvider);

使用 DI 的代码定义技能

声明 IServiceProvider 作为 AddResourceAddScript 委托中的参数——当代理读取资源或运行脚本时,框架会自动解析并注入它。

var distanceSkill = new AgentInlineSkill(
    name: "distance-converter",
    description: "Convert between distance units (miles and kilometers).",
    instructions: """
        Use this skill when the user asks to convert between miles and kilometers.
        1. Read the distance-table resource for conversion factors.
        2. Use the convert script to compute the result.
        """)
    .AddResource("distance-table", (IServiceProvider sp) =>
    {
        return sp.GetRequiredService<ConversionService>().GetDistanceTable();
    })
    .AddScript("convert", (double value, double factor, IServiceProvider sp) =>
    {
        return sp.GetRequiredService<ConversionService>().Convert(value, factor);
    });

使用 DI 的基于类的技能

使用 [AgentSkillResource][AgentSkillScript] 注解方法,并声明一个 IServiceProvider 参数 - 框架通过反射发现这些成员,从而自动注入服务提供器:

internal sealed class WeightConverterSkill : AgentClassSkill<WeightConverterSkill>
{
    public override AgentSkillFrontmatter Frontmatter { get; } = new(
        "weight-converter",
        "Convert between weight units (pounds and kilograms).");

    protected override string Instructions => """
        Use this skill when the user asks to convert between pounds and kilograms.
        1. Read the weight-table resource for conversion factors.
        2. Use the convert script to compute the result.
        """;

    [AgentSkillResource("weight-table")]
    [Description("Lookup table of multiplication factors for weight conversions.")]
    private static string GetWeightTable(IServiceProvider serviceProvider)
    {
        return serviceProvider.GetRequiredService<ConversionService>().GetWeightTable();
    }

    [AgentSkillScript("convert")]
    [Description("Multiplies a value by a conversion factor and returns the result as JSON.")]
    private static string Convert(double value, double factor, IServiceProvider serviceProvider)
    {
        return serviceProvider.GetRequiredService<ConversionService>().Convert(value, factor);
    }
}

小窍门

基于类的技能还可以通过其 构造函数解析依赖项。 在 ServiceCollection 中注册技能类并从容器中解析,而不是直接调用 new

services.AddSingleton<WeightConverterSkill>();
var weightSkill = serviceProvider.GetRequiredService<WeightConverterSkill>();

当技能类本身需要注入的服务超出资源和脚本委托使用的情况时,这非常有用。

接受 **kwargs 自动接收传递给 agent.run()的运行时关键字参数的资源和脚本函数。 这样,技能函数就可以访问应用程序上下文(例如配置、用户标识或服务客户端),而无需将它们硬编码到技能定义中。

传递运行时参数

function_invocation_kwargs传递agent.run(),以提供由框架转发到资源和脚本函数的关键字参数。

response = await agent.run(
    "How many kilometers is 26.2 miles?",
    function_invocation_kwargs={"precision": 2, "user_id": "alice"},
)

具有 kwargs 的资源函数

当资源函数声明 **kwargs时,框架每次代理读取资源时都会转发运行时关键字参数:

from typing import Any
from agent_framework import Skill

project_info_skill = Skill(
    name="project-info",
    description="Project status and configuration information",
    content="Use this skill for questions about the current project.",
)

@project_info_skill.resource(name="environment", description="Current environment configuration")
def environment(**kwargs: Any) -> Any:
    """Return environment config, optionally scoped to a user."""
    user_id = kwargs.get("user_id", "anonymous")
    env = os.environ.get("APP_ENV", "development")
    return f"Environment: {env}, Caller: {user_id}"

不带**kwargs的资源函数无需参数,且不会接收运行时上下文。

使用 kwargs 编写函数脚本

当脚本函数声明 **kwargs 时,框架会将运行时关键字参数与代理提供的 args 一起转发:

import json
from typing import Any
from agent_framework import Skill

converter_skill = Skill(
    name="unit-converter",
    description="Convert between common units using a conversion factor",
    content="Use the convert script to perform unit conversions.",
)

@converter_skill.script(name="convert", description="Convert a value: result = value × factor")
def convert_units(value: float, factor: float, **kwargs: Any) -> str:
    """Convert a value using a multiplication factor.

    Args:
        value: The numeric value to convert (provided by the agent).
        factor: Conversion factor (provided by the agent).
        **kwargs: Runtime keyword arguments from agent.run().
    """
    precision = kwargs.get("precision", 4)
    result = round(value * factor, precision)
    return json.dumps({"value": value, "factor": factor, "result": result})

代理通过工具调用提供valuefactor;应用程序通过function_invocation_kwargs 提供precision。 未包含 **kwargs 的脚本函数仅接收代理提供的参数。

安全最佳做法

代理技能应被视为引入项目的任何第三方代码。由于技能指令注入到代理的上下文中,并且技能可以包括脚本,因此对开放源代码依赖项应用相同的评审和管理级别至关重要。

  • 使用前查看 — 在部署之前读取所有技能内容(SKILL.md、脚本和资源)。 验证脚本的实际行为是否与其已声明的意向匹配。 检查尝试绕过安全准则、泄露数据或修改代理配置文件的对抗说明。
  • 源信任 - 仅安装受信任的作者或经过审查的内部参与者的技能。 首选具有明确来源、版本控制和主动维护的技能。 留意通过错拼的方式模仿常用包的技能名称。
  • 沙盒技术——在隔离环境中运行包含可执行脚本的技能。 仅将文件系统、网络和系统级访问限制为技能所需的内容。 在执行潜在敏感操作前,需要明确用户确认。
  • 审核和日志记录 - 记录加载哪些技能、读取哪些资源以及执行哪些脚本。 这为你提供了一个审核线索,用于跟踪代理行为,如果出现问题,可追溯回特定技能内容。

何时使用技能与工作流

代理技能和 代理框架工作流 都扩展了代理可以执行的作,但它们的工作方式基本不同。 选择最符合要求的方法:

  • 控制 - 借助技能,AI 决定如何执行指令。 当希望代理具有创造性或自适应性时,这是理想的选择。 使用工作流,可以显式定义执行路径。 如果需要确定性、可预测的行为,请使用工作流。
  • 复原能力 - 技能在单个代理轮次内运行。 如果出现故障,则必须重试整个操作。 工作流支持 检查点,因此发生故障后可以从上一次成功的步骤继续。 当重新执行整个进程的成本很高时,请选择工作流。
  • 副作用 - 技能适用于操作幂等或低风险时。 当步骤产生副作用(发送电子邮件、收费付款)时,首选工作流,这些副作用不应在重试时重复。
  • 复杂性 - 技能最适合一个代理可以处理的集中的单域任务。 工作流更适用于协调多个代理、人工审批或外部系统集成的多步骤业务流程。

小窍门

经验法则:如果希望 AI 弄清楚 如何 完成任务,请使用技能。 如果需要保证 执行哪些 步骤以及按何种顺序执行,请使用工作流。

后续步骤