代理技能 是指令、脚本和资源的可移植包,可提供代理的专用功能和域专业知识。 技能遵循开放规范并实现渐进式披露模式,以便代理在需要时仅加载所需的上下文。
在需要时使用代理技能:
- 封装领域专业知识 - 将专业知识(费用政策、法律工作流、数据分析管道)封装为可复用、可移植的软件包。
- 扩展代理功能 - 为代理提供新功能,而无需更改其核心指令。
- 确保一致性 - 将多步骤任务转换为可重复的可审核工作流。
- 启用互操作性 - 在不同的代理技能兼容产品中重复使用相同的技能。
技能结构
技能是一个目录,包含一个 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 |
否 | 技能可能使用的预先批准工具的空格分隔列表。 实验性 - 支持可能因代理实现而异。 |
前置元数据后的 Markdown 正文包含技能说明 - 分步指导、输入和输出示例、常见边缘情况或任何帮助智能体执行任务的内容。 保留 SKILL.md 500 行以下,并将详细的参考资料移动到单独的文件中。
渐进式披露
代理技能使用四阶段渐进式披露模式来最大程度地减少上下文使用情况:
- 宣告(每个技能约 100 个令牌)- 在每次运行开始时,技能名称和描述会被注入到系统提示中,让代理知道有哪些可用技能。
-
加载 (< 建议使用 5000 个令牌) - 当任务与技能的域匹配时,代理会调用
load_skill该工具以检索完整的 SKILL.md 正文,其中包含详细说明。 -
读取资源 (根据需要) - 代理仅在需要时调用
read_skill_resource该工具以提取补充文件(引用、模板、资产)。 -
运行脚本 (根据需要) - 代理调用该工具
run_skill_script以执行与技能捆绑的脚本。
此模式使代理的上下文窗口保持精简状态,同时允许它按需访问深层域知识。
注释
load_skill 始终公开。
read_skill_resource 仅当至少一个技能有资源时才公开。
run_skill_script 仅当至少一个技能有脚本时才公开。
向代理提供技能
处理技能时,涉及三个基本组成部分:
-
供应商 -
AgentSkillsProvider(C#) 或SkillsProvider(Python) 是向代理公开技能的上下文提供程序。 它播发系统提示中的可用技能,并注册代理用于加载技能、读取资源和运行脚本的工具。 -
源 - 源向提供程序提供技能。 技能可能来自多种源类型:
-
基于文件的 技能 - 从
SKILL.md文件系统目录中的文件发现的技能。 -
代码中定义的 - 使用
AgentInlineSkill(C#)或InlineSkill(Python)在代码中以内联方式定义的技能。 -
基于类 — 封装于派生自
AgentClassSkill<T>(C#)或ClassSkill(Python)的类中的技能。 -
基于 MCP 的技能 - 通过 (C#) 或
UseMcpSkills(Python) 从MCPSkillsSourceMCP (模型上下文协议) 服务器发现的技能。
-
基于文件的 技能 - 从
-
建设者 -
AgentSkillsProviderBuilder(C#) 将多个源组合到单个提供程序中,应用聚合、重复数据删除、缓存和可选筛选。 在 Python 中,直接组合源类,例如AggregatingSkillsSource、FilteringSkillsSource和DeduplicatingSkillsSource。
以下部分演示如何创建每种源类型的技能,以及如何组合源并从中构造提供程序。
基于文件的技能
创建一个指向包含你的技能的目录的 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"),
]);
提供程序最多搜索两级深度。
自定义资源和脚本发现机制
默认情况下,提供程序可识别扩展名为 .md、.json、.yaml、.yml、.csv、.xml 和 .txt 的资源,以及扩展名为 .py、.js、.sh、.ps1、.cs 和 .csx 的脚本。 它会在每个技能目录中向下搜索最多两级。 用于 AgentFileSkillsSourceOptions 更改以下默认值:
var fileOptions = new AgentFileSkillsSourceOptions
{
AllowedResourceExtensions = [".md", ".txt"],
AllowedScriptExtensions = [".py"],
SearchDepth = 3, // Search up to 3 levels deep (default is 2)
ResourceFilter = context => context.RelativeFilePath.StartsWith("references/"),
ScriptFilter = context => context.RelativeFilePath.StartsWith("scripts/")
|| context.RelativeFilePath.StartsWith("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();
ResourceFilter 和 ScriptFilter 会接收一个 AgentFileSkillFilterContext,其中包含技能名称和文件的相对路径,从而让您能够根据位置、命名约定或任何自定义逻辑来限制可处理的文件。
脚本执行
将 SubprocessScriptRunner.RunAsync 作为脚本运行器传递,以启用基于文件的脚本执行:
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
SubprocessScriptRunner.RunAsync);
SubprocessScriptRunner.RunAsync 大致等效于以下内容:
// Simplified equivalent of what SubprocessScriptRunner.RunAsync does internally
using System.Diagnostics;
using System.Text.Json;
static async Task<object?> RunAsync(
AgentFileSkill skill,
AgentFileSkillScript script,
JsonElement? args,
IServiceProvider? serviceProvider,
CancellationToken cancellationToken)
{
var psi = new ProcessStartInfo("python3")
{
RedirectStandardOutput = true,
UseShellExecute = false,
};
psi.ArgumentList.Add(script.FullPath);
if (args is { ValueKind: JsonValueKind.Array } json)
{
foreach (var element in json.EnumerateArray())
{
psi.ArgumentList.Add(element.GetString()!);
}
}
using var process = Process.Start(psi)!;
string output = await process.StandardOutput.ReadToEndAsync(cancellationToken);
await process.WaitForExitAsync(cancellationToken);
return output.Trim();
}
运行程序将每个发现的脚本作为本地子进程运行。 基于文件的脚本要求参数采用由字符串组成的 JSON 数组形式——数组中的每个元素都会成为一个命令行位置参数。
警告
SubprocessScriptRunner
仅用于演示目的。 对于生产用途,请考虑添加:
- 沙盒(例如容器或隔离的执行环境)
- 资源限制(CPU、内存、壁钟超时)
- 可执行脚本的输入验证和允许列表
- 结构化日志记录和审计追踪
基于文件的技能
使用 SkillsProvider.from_paths() 工厂从包含 SKILL.md 文件的目录中发现技能,并将提供程序添加到智能体的上下文提供程序中:
import os
from pathlib import Path
# Discover skills from the 'skills' directory
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "skills",
)
# Create an agent with the skills provider
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini")
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment,
credential=AzureCliCredential(),
)
agent = Agent(
client=client,
instructions="You are a helpful assistant.",
context_providers=[skills_provider],
)
多个技能目录
你可以将提供程序指向单个父目录 - 每个包含 SKILL.md 的子目录会自动被发现为技能:
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "all-skills"
)
或者传递路径列表以搜索多个根目录:
skills_provider = SkillsProvider.from_paths(
skill_paths=[
Path(__file__).parent / "company-skills",
Path(__file__).parent / "team-skills",
]
)
提供程序最多搜索两级深度。
自定义资源和脚本发现机制
默认情况下,根据 references/,从assets/scripts/子目录和脚本中发现资源。 已识别的资源扩展包括.md、、.json、.yaml.yml、.csv、和.xml.txt。 它会在每个技能目录中向下搜索最多两级。 使用resource_extensions、script_extensions、search_depth和resource_filterscript_filter自定义发现:
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "skills",
resource_extensions=(".md", ".txt"),
script_extensions=(".py", ".sh"),
search_depth=3, # Search up to 3 levels deep (default is 2)
resource_filter=lambda skill_name, path: path.startswith("references/"),
script_filter=lambda skill_name, path: path.startswith("scripts/"),
)
resource_filter 和 script_filter 谓词接收技能名称和文件的相对路径,让你能够按位置、命名约定或任何自定义逻辑筛选文件。 使用 "." 以包含技能根级别的文件,除了子目录。
脚本执行
若要启用基于文件的脚本执行,请将 script_runner 传递给 SkillsProvider.from_paths()。 可以使用满足 SkillScriptRunner 协议的任何同步或异步可调用项:
from pathlib import Path
from agent_framework import FileSkill, FileSkillScript, SkillsProvider
def my_runner(
skill: FileSkill,
script: FileSkillScript,
args: dict | list[str] | None = None,
) -> str:
"""Run a file-based script as a subprocess."""
import subprocess, sys
script_path = Path(script.full_path)
cmd = [sys.executable, str(script_path)]
if isinstance(args, list):
cmd.extend(args)
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=30, cwd=str(script_path.parent)
)
return result.stdout.strip()
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "skills",
script_runner=my_runner,
)
运行器接收解析后的 FileSkill、FileSkillScript 以及一个可选的 args 参数。 基于文件的脚本要求参数采用由字符串组成的 JSON 数组形式——数组中的每个元素都会成为一个命令行位置参数。 系统会从每个技能目录的 .py 子目录中的 scripts/ 文件里自动发现脚本。
警告
上述示例运行程序 仅用于演示目的。 对于生产用途,请考虑添加:
- 沙盒化(例如,容器
seccomp或firejail) - 资源限制(CPU、内存、壁钟超时)
- 可执行脚本的输入验证和允许列表
- 结构化日志记录和审计追踪
注释
如果提供了包含脚本的基于文件的技能,但未设置 script_runner,则当尝试执行脚本时,SkillsProvider 会引发错误。
基于文件的技能
Go 智能体通过 agent/skills 包支持技能。 技能遵循相同的渐进式披露模式:播发 -> 加载 -> 读取资源 -> 运行脚本。
从磁盘上的 SKILL.md 文件中发现技能,并将技能提供程序注册为代理上下文提供程序:
import (
"os"
"github.com/microsoft/agent-framework-go/agent"
"github.com/microsoft/agent-framework-go/provider/foundryprovider"
"github.com/microsoft/agent-framework-go/agent/skills"
"github.com/microsoft/agent-framework-go/agent/skills/fsskills"
)
skillsRoot, _ := os.OpenRoot("skills")
defer skillsRoot.Close()
skillsProvider := skills.NewContextProvider(skills.ContextProviderOptions{
Sources: []skills.Source{
fsskills.NewSourceOptions(fsskills.SourceOptions{}, skillsRoot.FS()),
},
})
a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
Instructions: "You are a helpful assistant.",
Config: agent.Config{
ContextProviders: []agent.ContextProvider{skillsProvider},
},
})
代码定义的技能
除了从 SKILL.md 文件中发现的基于文件的技能之外,还可以使用 AgentInlineSkill完全在代码中定义技能。 代码定义的技能在以下情况下非常有用:
- 技能内容是动态生成的(例如,从数据库或环境读取)。
- 你希望将技能定义与使用它们的应用程序代码一起保留。
- 你需要在读取时执行逻辑的资源,而不是提供静态文件。
- 技能定义需要在运行时根据数据构建——例如,根据其角色或权限为每个用户会话创建个性化技能。
- 技能需要闭合调用站点状态(局部变量、闭包),而不是从 DI 容器解析服务。
基本代码技能
使用名称、描述和指令创建一个 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 - 请参阅 提供程序构造。
除了从 SKILL.md 文件中发现的基于文件的技能之外,还可以使用 InlineSkill 在Python代码中完全定义技能。 代码定义的技能在以下情况下非常有用:
- 技能内容是动态生成的(例如,从数据库或环境读取)。
- 你希望将技能定义与使用它们的应用程序代码一起保留。
- 你需要在读取时执行逻辑的资源,而不是提供静态文件。
- 技能定义需要在运行时根据数据构建——例如,根据其角色或权限为每个用户会话创建个性化技能。
- 技能需要闭合调用站点状态(局部变量、闭包),而不是通过
**kwargs解析服务。
基本代码技能
使用 InlineSkill(包含名称和描述)以及指令内容创建一个 SkillFrontmatter 实例。 (可选)附加 InlineSkillResource 包含静态内容的实例:
from textwrap import dedent
from agent_framework import InlineSkill, InlineSkillResource, SkillFrontmatter, SkillsProvider
code_style_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="code-style",
description="Coding style guidelines and conventions for the team",
),
instructions=dedent("""\
Use this skill when answering questions about coding style,
conventions, or best practices for the team.
"""),
resources=[
InlineSkillResource(
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(code_style_skill)
动态资源
使用 @skill.resource 修饰器将函数注册为资源。 每次代理读取资源时都会调用此函数,因此可以返回最新的数据。 支持同步和异步函数:
import os
from agent_framework import InlineSkill, SkillFrontmatter
project_info_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="project-info",
description="Project status and configuration information",
),
instructions="Use this skill for questions about the current project.",
)
@project_info_skill.resource
def environment() -> str:
"""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() -> str:
"""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 InlineSkill, SkillFrontmatter
unit_converter_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="unit-converter",
description="Convert between common units using a conversion factor",
),
instructions="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.md 文件中发现的基于文件的技能外,还可以在 Go 代码中完全定义技能:
skill := &skills.Skill{
Frontmatter: skills.Frontmatter{
Name: "unit-converter",
Description: "Convert between common units using a multiplication factor.",
},
GetContent: func(context.Context) (string, error) {
return "Use this skill when the user asks to convert between units.", nil
},
Resources: []skills.Resource{
{
Name: "conversion-table",
Description: "Lookup table of multiplication factors.",
Read: func(context.Context) (any, error) {
return conversionTable, nil
},
},
},
Scripts: []skills.Script{
{
Name: "convert",
Description: "Multiplies a value by a conversion factor. Pass value and factor as positional string arguments: [\"<value>\", \"<factor>\"].",
Run: func(_ context.Context, _ *skills.Skill, args []string) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("expected value and factor")
}
value, err := strconv.ParseFloat(args[0], 64)
if err != nil {
return nil, err
}
factor, err := strconv.ParseFloat(args[1], 64)
if err != nil {
return nil, err
}
return map[string]any{
"value": value,
"factor": factor,
"result": value * factor,
}, nil
},
},
},
}
provider := skills.NewContextProvider(skills.ContextProviderOptions{
Skills: []*skills.Skill{skill},
})
GetContent 仅在代理调用 load_skill时加载技能指令。 脚本接收按位置传递的 CLI 风格字符串参数,例如 ["26.2", "1.60934"],并且可以根据脚本需要,以任何方式解析这些参数。
小窍门
有关完整的可运行示例,请参阅 技能示例 。
基于课堂的技能
基于类的技能使你可以将所有技能组件(名称、说明、说明、资源和脚本)捆绑到单个 C# 类中。 这使得这些技能能够轻松打包为 NuGet 包并进行分发——团队可以独立编写并发布这些技能,而使用方只需通过 dotnet add package 和一次 .UseSkill() 调用即可将其添加进来。 从 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> 还支持重写 Resources 和 Scripts 作为集合,适用于基于属性的发现不适合的场景。
基于课堂的技能
基于类的技能使你可以将所有技能组件(名称、说明、说明、资源和脚本)捆绑到单个Python类中。 这使得它们能够轻松打包并作为 PyPI 包分发——团队可以独立编写和发布技能,而用户只需通过 pip install 和一次 SkillsProvider() 调用即可添加它们。 子类化 ClassSkill,然后使用 @ClassSkill.resource 和 @ClassSkill.script 装饰器进行自动发现:
import json
from textwrap import dedent
from agent_framework import ClassSkill, SkillFrontmatter
class UnitConverterSkill(ClassSkill):
"""A unit-converter skill defined as a Python class."""
def __init__(self) -> None:
super().__init__(
frontmatter=SkillFrontmatter(
name="unit-converter",
description=(
"Convert between common units using a multiplication factor. "
"Use when asked to convert miles, kilometers, pounds, or kilograms."
),
),
)
@property
def instructions(self) -> str:
return dedent("""\
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.
""")
@property
@ClassSkill.resource
def conversion_table(self) -> str:
"""Lookup table of multiplication factors for common unit conversions."""
return dedent("""\
# 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 |
""")
@ClassSkill.script(name="convert", description="Multiplies a value by a conversion factor.")
def convert_units(self, value: float, factor: float) -> str:
"""Convert a value using a multiplication factor."""
result = round(value * factor, 4)
return json.dumps({"value": value, "factor": factor, "result": result})
使用 SkillsProvider 注册基于类的技能:
from agent_framework import SkillsProvider
skill = UnitConverterSkill()
skills_provider = SkillsProvider(skill)
当作为裸修饰符(无参数)应用时 @ClassSkill.resource ,方法名称将变为资源名称(下划线转换为连字符),而 docstring 将成为说明。 使用 @ClassSkill.resource(name="...", description="...") 显式设置它们。 相同的模式适用于 @ClassSkill.script。
资源可以定义为常规方法或 @property 描述符。 使用@property时,先放置@property,再放置@ClassSkill.resource。 资源返回值在首次访问后缓存。
注释
ClassSkill 还支持显式重写 resources 和 scripts 属性,以分别直接返回 InlineSkillResource 和 InlineSkillScript 实例,适用于基于装饰器的发现机制不适用的场景。
基于 MCP 的技能
注释
基于 MCP 的技能需要 Microsoft.Agents.AI.Mcp NuGet 包。 MCP 技能 API 是实验性的,将来的版本可能会更改。
可以从在 skill:// URI 方案下公开技能资源的 MCP(模型上下文协议)服务器中发现技能。 MCP 服务器通过 skill://index.json 发现文档公布技能,框架按需获取技能内容。
基于 MCP 的技能支持两种索引条目类型:
-
skill-md- 技能的SKILL.md及其同级资源按需从 MCP 服务器获取。 -
archive- 技能作为一个打包存档(ZIP、TAR 或 gzip 压缩的 TAR)分发,该存档在本地下载和解压缩。
基本用法
在 UseMcpSkills 上使用 AgentSkillsProviderBuilder 扩展方法来添加 MCP 技能源:
using Microsoft.Agents.AI;
using ModelContextProtocol.Client;
// Connect to the MCP server
await using McpClient client = await McpClient.CreateAsync(
new StdioClientTransport(new()
{
Name = "skills-server",
Command = "dotnet",
Arguments = [skillsServerPath, "--server"],
}));
// Build a skills provider that discovers skills over MCP
var skillsProvider = new AgentSkillsProviderBuilder()
.UseMcpSkills(client)
.Build();
// Create an agent with the MCP skills
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "SkillsAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant. Use available skills to answer the user.",
},
AIContextProviders = [skillsProvider],
},
model: deploymentName);
归档类技能
对于归档类技能,请使用 AgentMcpSkillsSourceOptions(来自 Microsoft.Agents.AI.Mcp 软件包)来配置提取行为:
var skillsProvider = new AgentSkillsProviderBuilder()
.UseMcpSkills(client, new AgentMcpSkillsSourceOptions
{
ArchiveSkillsDirectory = Path.Combine(AppContext.BaseDirectory, "extracted-skills"),
ArchiveMaxFileCount = 50,
ArchiveMaxSizeBytes = 2 * 1024 * 1024, // 2 MB
})
.Build();
AgentMcpSkillsSourceOptions 公开以下属性来控制存档提取:
-
ArchiveSkillsDirectory- 提取的存档的基目录。 默认使用当前工作目录下的一个唯一子目录,该子目录会为每个源实例单独生成,以避免多个源之间发生冲突。 -
ArchiveResourceExtensions- 已提取的存档中资源允许的扩展名。 默认值为.md、、.json、.yaml、.yml.csv、.xml。.txt -
ArchiveResourceSearchDepth- 如何深入搜索每个提取的技能目录中的资源。 默认值为2. -
ArchiveMaxFileCount- 每个存档的最大文件数。 跳过超出此限制的存档。 默认值为20. -
ArchiveMaxSizeBytes- 每个存档的最大下载大小。 默认值为1 MB. -
ArchiveMaxUncompressedSizeBytes- 每个存档的最大未压缩大小。 默认值为1 MB.
Important
归档类型技能中打包的脚本 绝不会被执行。 这是一种故意的安全措施 - 来自远程 MCP 服务器的可执行内容需要显式信任。
基于 MCP 的技能
注释
基于 MCP 的技能是实验性的,将来的版本中可能会发生变化。 使用 MCPSkillsSource 在 MCP_SKILLS 功能标志下发出 FutureWarning。
可以从在 skill:// URI 方案下公开技能资源的 MCP(模型上下文协议)服务器中发现技能。 MCP 服务器通过 skill://index.json 发现文档公布技能,框架通过 resources/read 按需获取每项技能的 SKILL.md 主体。
将 MCP ClientSession 封装在 MCPSkillsSource 中,并将其传给 SkillsProvider:
import os
from agent_framework import Agent, MCPSkillsSource, SkillsProvider, ToolApprovalMiddleware
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamable_http_client
mcp_url = os.environ["MCP_SKILLS_SERVER_URL"]
# Connect to the MCP server over streamable HTTP
async with streamable_http_client(url=mcp_url) as (read, write, _), ClientSession(read, write) as session:
await session.initialize()
# MCPSkillsSource reads skill://index.json and creates one skill per
# skill-md entry; SKILL.md bodies are fetched on demand.
skills_provider = SkillsProvider(MCPSkillsSource(client=session))
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ.get("FOUNDRY_MODEL", "gpt-4o-mini"),
credential=AzureCliCredential(),
)
async with Agent(
client=client,
instructions="You are a helpful assistant. Use available skills to answer the user.",
context_providers=[skills_provider],
middleware=[ToolApprovalMiddleware(auto_approval_rules=[SkillsProvider.all_tools_auto_approval_rule])],
) as agent:
response = await agent.run("...")
注释
Python MCPSkillsSource 仅支持 skill-md 索引条目(任何其他类型的索引条目都会被静默跳过)。 与.NET实现不同,它不支持存档类型技能。 如果 skill://index.json 不存在、不可读、空或无法分析,则源将返回空列表。
Important
外部 MCP 服务器控制哪些技能内容(包括代理可能运行的指令和脚本)到达代理。 仅连接到你已审核并信任的 MCPSkillsSource 服务器,并将其响应视为不受信任的输入。
技能源
AgentSkillsProvider 从一个或多个源(实现了 AgentSkillsSource 的对象)中检索技能。 源分为两类:发现或持有技能的叶源(如 AgentFileSkillsSource 用于基于文件的技能),以及转换另一个源输出的装饰器(聚合、去重、缓存和筛选)。 还可以创建自定义 源。
每个源实现单个方法 - GetSkillsAsync(AgentSkillsSourceContext context, CancellationToken cancellationToken = default)。
AgentSkillsSourceContext 包含有关当前请求的信息:
-
Agent- 请求技能的AIAgent实例。 -
Session- 与调用关联的AgentSession,如果没有会话,则为null。
此上下文在整个源管道中都可用,因此 FilteringAgentSkillsSource 谓词或自定义源可以根据该上下文来确定其逻辑——例如,根据发出请求的代理返回不同的一组技能。
叶源
AgentFileSkillsSource
从磁盘上的 SKILL.md 文件中发现技能。 接受一个或多个目录路径、一个可选的脚本运行器,以及可选的 AgentFileSkillsSourceOptions(在 基于文件的技能 中有相关说明)。
var source = new AgentFileSkillsSource(
[Path.Combine(AppContext.BaseDirectory, "skills")],
scriptRunner: SubprocessScriptRunner.RunAsync,
options: new AgentFileSkillsSourceOptions { SearchDepth = 3 });
AgentInMemorySkillsSource
将 AgentSkill 实例(代码定义或基于类)包装在内存中。
var source = new AgentInMemorySkillsSource([volumeConverterSkill, temperatureConverter]);
组合器
AggregatingAgentSkillsSource
将多个源合并为一个源。 技能按注册顺序返回,且未应用重复数据删除或筛选。
var aggregated = new AggregatingAgentSkillsSource([fileSource, inMemorySource]);
修饰器
修饰器包装内部源并转换其输出。 可以将它们串联起来以构建一个管道。
DeduplicatingAgentSkillsSource
移除重复的技能名称(不区分大小写,保留首次出现的名称)。 重复项会以警告级别记录到日志中。
var deduplicated = new DeduplicatingAgentSkillsSource(innerSource);
CachingAgentSkillsSource
缓存内部源返回的技能列表。 并发调用方按缓存密钥进行序列化,因此每次只运行一个提取。 接受可选的 CachingAgentSkillsSourceOptions:
-
RefreshInterval(TimeSpan?) - 设置后,缓存结果在此间隔后过期,并重新调用内部源。 当null(默认值)时,缓存的结果永远不会过期。 -
CacheIsolationKeySelector(Func<AgentSkillsSourceContext, string?>?) - 返回一个缓存密钥,以按上下文(例如,每个租户)隔离缓存结果。 当null所有调用方共享单个缓存存储桶时。
var cached = new CachingAgentSkillsSource(innerSource, new CachingAgentSkillsSourceOptions
{
RefreshInterval = TimeSpan.FromMinutes(5)
});
FilteringAgentSkillsSource
使用谓词以决定包含还是排除技能。 谓词接收技能和 AgentSkillsSourceContext。
var filtered = new FilteringAgentSkillsSource(
innerSource,
(skill, context) => skill.Frontmatter.Name != "experimental-skill");
自定义来源
当内置源无法满足你的场景时,请实现你自己的源。 对于叶源(从新来源 [如数据库或远程服务] 生成技能的源),请子类化 AgentSkillsSource;对于转换另一个源输出的装饰器,请子类化 DelegatingAgentSkillsSource。
叶源
派生自 AgentSkillsSource 并实现 GetSkillsAsync。 该 AgentSkillsSourceContext 参数使源可以根据当前请求定制其结果,例如,根据请求代理返回一组不同的技能。 如果源拥有资源(如客户端或连接),请重写 Dispose(bool)。
public sealed class TenantSkillsSource : AgentSkillsSource
{
private readonly ISkillStore _store;
public TenantSkillsSource(ISkillStore store)
{
_store = store;
}
public override async Task<IList<AgentSkill>> GetSkillsAsync(
AgentSkillsSourceContext context,
CancellationToken cancellationToken = default)
{
// Use the requesting agent to decide which skills to load.
var tenantId = context.Agent.Name ?? "default";
return await _store.GetSkillsForTenantAsync(tenantId, cancellationToken);
}
}
自定义修饰器
从 DelegatingAgentSkillsSource 派生,调用 InnerSource.GetSkillsAsync,并转换或观察其结果。 这与内置缓存、去重和过滤修饰器使用的模式相同。 例如,一个在不改变结果的情况下记录每个请求返回了多少技能的装饰器:
public sealed class MetricsAgentSkillsSource : DelegatingAgentSkillsSource
{
private readonly ILogger<MetricsAgentSkillsSource> _logger;
public MetricsAgentSkillsSource(
AgentSkillsSource innerSource,
ILogger<MetricsAgentSkillsSource> logger)
: base(innerSource)
{
_logger = logger;
}
public override async Task<IList<AgentSkill>> GetSkillsAsync(
AgentSkillsSourceContext context,
CancellationToken cancellationToken = default)
{
var skills = await base.GetSkillsAsync(context, cancellationToken);
_logger.LogInformation(
"Returned {SkillCount} skills to agent {AgentName}.",
skills.Count,
context.Agent.Name);
return skills;
}
}
这两个自定义源都可以直接传递给 AgentSkillsProvider 或嵌套在更大的管道中,就像内置源一样。
提供程序构造
AgentSkillsProvider 是向代理公开技能的组件。 它包装一个或多个源并注册load_skill和read_skill_resourcerun_skill_script工具。 有三种方法可以创建一个:
-
AgentSkillsProviderBuilder- 使用自动聚合、重复数据删除、缓存和可选筛选将多个技能类型组合到一个提供程序中。 最适合结合基于文件、代码定义、类和 MCP 的技能的场景。 -
直接源组合 - 使用公共
AgentSkillsSource类自行构造源管道。 不会自动应用缓存或去重——整个流程由你控制。 当你需要控制顺序、条件逻辑或自定义装饰器行为时,这是最佳选择。 - 方便构造函数 - 直接从文件路径或技能实例创建提供程序。 自动应用重复数据删除和缓存。 最适用于单一源场景。
使用 AgentSkillsProviderBuilder
如果需要以下任一项,请使用 AgentSkillsProviderBuilder :
-
混合技能类型 - 在单个提供程序中合并基于文件的、代码定义的(
AgentInlineSkill)、基于类的(AgentClassSkill)和基于 MCP 的技能。 - 技能筛选 - 使用谓词包括或排除技能。
混合技能类型
通过串联 UseFileSkill、UseSkill、UseMcpSkills 和 UseFileScriptRunner,在一个提供程序中组合多种技能类型:
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills")) // file-based skills
.UseSkill(volumeConverterSkill) // AgentInlineSkill
.UseSkill(temperatureConverter) // AgentClassSkill
.UseMcpSkills(mcpClient) // MCP-based skills
.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, context) => approvedSkillNames.Contains(skill.Frontmatter.Name))
.Build();
直接编写源内容
当生成器不提供所需的控件时,请自行编写源类并将生成的管道 AgentSkillsProvider传递给 。 有关可用源及其选项的完整列表,请参阅 技能源 。
以下示例构建了一个类似的多源管道,但让你能够显式控制每个修饰器:
// 1. Create the leaf sources
var fileSource = new AgentFileSkillsSource(
[Path.Combine(AppContext.BaseDirectory, "skills")],
SubprocessScriptRunner.RunAsync);
var inMemorySource = new AgentInMemorySkillsSource(
[volumeConverterSkill, temperatureConverter]);
// 2. Aggregate them into one source
var aggregated = new AggregatingAgentSkillsSource([fileSource, inMemorySource]);
// 3. Add deduplication and caching decorators
var deduplicated = new DeduplicatingAgentSkillsSource(aggregated);
var cached = new CachingAgentSkillsSource(deduplicated);
// 4. Create the provider, transferring source ownership
var skillsProvider = new AgentSkillsProvider(
cached,
options: new AgentSkillsProviderOptions(),
ownsSource: true);
注释
当 ownsSource 为 true 时,释放提供程序也会释放整个源管道。 如果您自行管理源的生命周期,请将其设置为 false。
便捷构造函数
对于单源方案,请直接使用 AgentSkillsProvider 构造函数。 这些功能可自动应用去重和缓存,无需构建器或手动组合源。
从文件路径导入:
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
scriptRunner: SubprocessScriptRunner.RunAsync);
来自技能实例:
var skillsProvider = new AgentSkillsProvider(volumeConverterSkill, temperatureConverter);
技能源
SkillsProvider 从一个或多个 源 中检索技能,这些源是派生自 SkillsSource 的对象。 源分为两类:发现或持有技能的叶源(如 FileSkillsSource 用于基于文件的技能),以及转换另一个源输出的装饰器(聚合、去重、缓存和筛选)。 还可以创建自定义 源。
每个源实现单个方法 - async def get_skills(self, context: SkillsSourceContext) -> list[Skill]。
SkillsSourceContext 包含有关当前请求的信息:
-
agent- 代理 (SupportsAgentRun) 请求技能。 -
session- 与调用关联的AgentSession,如果没有会话,则为None。
此上下文会贯穿整个源管道,因此,FilteringSkillsSource 谓词或自定义源都可以基于该上下文进行逻辑判断——例如,根据发出请求的代理返回不同的一组技能。
叶源
-
FileSkillsSource- 从磁盘上的SKILL.md文件中发现技能。 接受一个或多个目录路径、可选的script_runner以及发现选项(resource_extensions、script_extensions、search_depth、resource_filter、script_filter),详见 基于文件的技能。 -
InMemorySkillsSource- 在内存中封装Skill实例(代码定义的或基于类的)。 -
MCPSkillsSource- 从 MCP 服务器发现技能(请参阅 基于 MCP 的技能)。
from pathlib import Path
from agent_framework import FileSkillsSource, InMemorySkillsSource
file_source = FileSkillsSource(Path(__file__).parent / "skills", script_runner=my_runner)
in_memory_source = InMemorySkillsSource([volume_converter_skill, temperature_converter_skill])
组合器
AggregatingSkillsSource 将多个源合并为一个源。 技能按注册顺序返回,且未应用重复数据删除或筛选。
from agent_framework import AggregatingSkillsSource
aggregated = AggregatingSkillsSource([file_source, in_memory_source])
修饰器
修饰器包装内部源并转换其输出。 可以将它们串联起来以构建一个管道。
-
DeduplicatingSkillsSource- 删除重复的技能名称(不区分大小写,第一次匹配获胜)。 重复项会以警告级别记录到日志中。 -
CachingSkillsSource- 缓存内部源返回的技能列表。 针对同一缓存键的并发调用者共享一个正在进行的获取操作,因此每个键最多只会查询一次内部源。 接受两个可选关键字参数:-
refresh_interval(timedelta | None) - 设置时,缓存列表在早于间隔后被视为过时,因此下一次调用会重新查询内部源。 当None(默认值)时,缓存的结果永远不会过期。 对于技能在进程生命周期内发生变化的内部源非常有用,例如MCPSkillsSource。 -
cache_isolation_key_selector(Callable[[SkillsSourceContext], str | None]) - 从上下文派生缓存密钥以隔离缓存的结果(例如,每个代理或租户)。 键应具有较低的基数,并保持稳定。 返回None(或将其保留为None)将使用一个共享缓存桶。
-
-
FilteringSkillsSource- 应用谓词以包含或排除技能。 谓词接收技能 和SkillsSourceContext:Callable[[Skill, SkillsSourceContext], bool]。
from datetime import timedelta
from agent_framework import (
CachingSkillsSource,
DeduplicatingSkillsSource,
FilteringSkillsSource,
)
deduplicated = DeduplicatingSkillsSource(aggregated)
cached = CachingSkillsSource(
deduplicated,
refresh_interval=timedelta(minutes=5),
cache_isolation_key_selector=lambda context: context.agent.name,
)
filtered = FilteringSkillsSource(
cached,
predicate=lambda skill, context: skill.frontmatter.name != "experimental-skill",
)
自定义来源
当内置源无法满足你的场景时,请实现你自己的源。 对于叶源(从新来源 [如数据库或远程服务] 生成技能的源),请子类化 SkillsSource;对于转换另一个源输出的装饰器,请子类化 DelegatingSkillsSource。
叶源
派生自 SkillsSource 并实现 get_skills。 该 SkillsSourceContext 参数使源可以根据当前请求定制其结果- 例如,根据请求代理返回一组不同的技能:
from agent_framework import Skill, SkillsSource, SkillsSourceContext
class TenantSkillsSource(SkillsSource):
def __init__(self, store: "SkillStore") -> None:
self._store = store
async def get_skills(self, context: SkillsSourceContext) -> list[Skill]:
# Use the requesting agent to decide which skills to load.
tenant_id = context.agent.name or "default"
return await self._store.get_skills_for_tenant(tenant_id)
自定义修饰器
从 DelegatingSkillsSource 派生,调用 self.inner_source.get_skills(context),并转换或观察其结果。 这与内置缓存、去重和过滤修饰器使用的模式相同。 例如,一个在不改变结果的情况下记录每个请求返回了多少技能的装饰器:
import logging
from agent_framework import DelegatingSkillsSource, Skill, SkillsSourceContext
logger = logging.getLogger(__name__)
class MetricsSkillsSource(DelegatingSkillsSource):
async def get_skills(self, context: SkillsSourceContext) -> list[Skill]:
skills = await self.inner_source.get_skills(context)
logger.info("Returned %d skills to agent %s.", len(skills), context.agent.name)
return skills
这两个自定义源都可以直接传递给 SkillsProvider 或嵌套在更大的管道中,就像内置源一样。
提供程序构造
SkillsProvider 是向代理公开技能的组件。 它包装一个或多个源并注册load_skill和read_skill_resourcerun_skill_script工具。 有三种方法可以创建一个:
-
从技能实例 - 将单个
Skill或一系列技能传递给构造函数。 最适合用于以代码定义和基于类的技能。 自动应用重复数据删除和缓存。 -
从文件路径 - 使用
SkillsProvider.from_paths()工厂。 最适合基于单一源文件的技能。 自动应用重复数据删除和缓存。 -
直接源组合 - 使用公共
SkillsSource类自行构造源管道,并将其传递给构造函数。 可以控制整个管道。 当你需要控制顺序、条件逻辑、缓存键或自定义装饰器行为时,这种方式最合适。
从技能实例
from agent_framework import SkillsProvider
# Single skill or a list of skills - deduplicated and cached automatically.
skills_provider = SkillsProvider(volume_converter_skill)
skills_provider = SkillsProvider([volume_converter_skill, temperature_converter_skill])
从文件路径
from pathlib import Path
from agent_framework import SkillsProvider
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "skills",
script_runner=my_runner,
)
直接编写源内容
当您需要完全控制时,请自行组合源类,并将生成的管道传递给 SkillsProvider。 有关可用源及其选项的完整列表,请参阅 技能源 。
以下示例构建了一个多源管道,并对每个装饰器进行显式控制。 此示例使用占位符对象:
-
volume_converter_skill- 任何按照InlineSkill中所示方式构建的 实例。 -
temperature_converter_skill- 按照ClassSkill中所示构建的任何 实例。 -
my_runner- 一个SkillScriptRunner可调用对象,其定义如 脚本执行 中所示。
from pathlib import Path
from agent_framework import (
AggregatingSkillsSource,
CachingSkillsSource,
DeduplicatingSkillsSource,
FileSkillsSource,
InMemorySkillsSource,
SkillsProvider,
)
# 1. Create the leaf sources
file_source = FileSkillsSource(Path(__file__).parent / "skills", script_runner=my_runner)
in_memory_source = InMemorySkillsSource([volume_converter_skill, temperature_converter_skill])
# 2. Aggregate them, then add deduplication and caching decorators
aggregated = AggregatingSkillsSource([file_source, in_memory_source])
deduplicated = DeduplicatingSkillsSource(aggregated)
cached = CachingSkillsSource(deduplicated)
# 3. Create the provider from the composed pipeline
skills_provider = SkillsProvider(cached)
Important
调用方提供的 SkillsSource 将按原样使用:不会自动去重,也不会封装在 CachingSkillsSource 中。 将具备上下文感知的源自动缓存到单个共享存储桶中,可以使一个智能体或租户的技能重用于另一个智能体或租户。 在需要时,自行编写 DeduplicatingSkillsSource 和 CachingSkillsSource(可选添加 cache_isolation_key_selector)。 自动删除重复和缓存仅适用于直接传递技能或文件路径(上述选项 1 和 2)。
混合技能类型
使用 AggregatingSkillsSource 在一个提供程序中结合基于文件的、通过代码定义的和基于类的技能:
from pathlib import Path
from agent_framework import (
AggregatingSkillsSource,
DeduplicatingSkillsSource,
FileSkillsSource,
InMemorySkillsSource,
SkillsProvider,
)
temperature_converter_skill = TemperatureConverterSkill()
skills_provider = SkillsProvider(
DeduplicatingSkillsSource(
AggregatingSkillsSource([
FileSkillsSource(
Path(__file__).parent / "skills",
script_runner=my_runner,
),
InMemorySkillsSource([volume_converter_skill, temperature_converter_skill]),
])
)
)
技能筛选
使用 FilteringSkillsSource 来控制代理可以看到哪些技能。 谓词接收每个 Skill 和 SkillsSourceContext,并返回 True 以包含技能。 例如,若要从共享目录加载技能,但隐藏实验性技能:
from pathlib import Path
from agent_framework import (
DeduplicatingSkillsSource,
FileSkillsSource,
FilteringSkillsSource,
SkillsProvider,
)
skills_provider = SkillsProvider(
DeduplicatingSkillsSource(
FilteringSkillsSource(
FileSkillsSource(Path(__file__).parent / "skills"),
predicate=lambda skill, context: skill.frontmatter.name != "experimental-tools",
)
)
)
缓存行为
默认情况下,构建器使用 CachingAgentSkillsSource 包装源管道,以缓存底层源返回的技能列表。 在第一个请求上解析技能后,后续请求将重复使用缓存列表,而无需重新查询源。 若要禁用缓存(例如,在开发期间技能定义经常变化时),请在构建器上使用 DisableCaching():
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills"))
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync)
.DisableCaching()
.Build();
注释
在开发过程中,当技能内容频繁更改时,禁用缓存非常有用。 在生产环境中,保留启用缓存(默认值),以提高性能。
缓存行为
默认情况下,技能工具和说明在首次生成后缓存。 将 disable_caching=True 设置为在每次调用时强制重新构建:
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "skills",
disable_caching=True,
)
对于以代码定义的技能和基于类的技能,disable_caching 也可在 SkillsProvider 构造函数上使用。
若要保持缓存启用状态,但定期重新发现技能(例如,当基于文件的源或 MCP 源在进程生命周期内发生更改时),请传递 cache_refresh_interval。 内置缓存的缓存时间一旦超过该间隔,就会被视为已过期,因此下一次运行会重新查询源端:
from datetime import timedelta
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "skills",
cache_refresh_interval=timedelta(minutes=5),
)
cache_refresh_interval 仅影响提供程序在内部构建的缓存(基于技能或文件路径);如果 disable_caching=True,并且对调用方提供的 SkillsSource 没有影响,其会被忽略(对于此类情况,请自行构建 CachingSkillsSource 并设置 refresh_interval)。
注释
在开发过程中,当技能内容频繁更改时,禁用缓存非常有用。 在生产环境中,保留启用缓存(默认值),以提高性能。
工具审批
默认情况下,(AgentSkillsProviderload_skill、)read_skill_resourcerun_skill_script公开的所有工具都需要审批。 当工具调用需要批准时,代理会暂停并返回一个 ToolApprovalRequestContent,而不是立即执行。 使用 UseToolApproval 带有自动审批规则的中间件选择性地绕过受信任操作的提示:
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
SubprocessScriptRunner.RunAsync);
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)
.AsBuilder()
.UseToolApproval(new ToolApprovalAgentOptions
{
// Auto-approve read-only skill tools (load_skill, read_skill_resource).
// run_skill_script still requires explicit user approval.
AutoApprovalRules = [AgentSkillsProvider.ReadOnlyToolsAutoApprovalRule],
})
.Build();
若要自动批准所有技能工具,包括脚本执行:
.UseToolApproval(new ToolApprovalAgentOptions
{
AutoApprovalRules = [AgentSkillsProvider.AllToolsAutoApprovalRule],
})
禁用特定工具的审批
使用 AgentSkillsProviderOptions 可禁用单个工具的审批,将它们完全移出审批流程:
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
SubprocessScriptRunner.RunAsync,
options: new AgentSkillsProviderOptions
{
DisableLoadSkillApproval = true,
DisableReadSkillResourceApproval = true,
// DisableRunSkillScriptApproval remains false - scripts still require approval
});
当某些工具需要审批,而另一些工具不在同一响应中,模型可能会同时调用这两种类型。 设置 EnableNonApprovalRequiredFunctionBypassing,以便免审批工具立即执行,而仅提示用户处理其余工具:
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],
EnableNonApprovalRequiredFunctionBypassing = true,
},
model: deploymentName)
.AsBuilder()
.UseToolApproval()
.Build();
处理审批请求
当工具需要审批(且没有自动审批规则匹配)时,代理将返回 ToolApprovalRequestContent 必须批准或拒绝的项目,然后再继续:
AgentSession session = await agent.CreateSessionAsync();
AgentResponse response = await agent.RunAsync("Convert 26.2 miles to kilometers", session);
List<ToolApprovalRequestContent> approvalRequests = response.Messages
.SelectMany(m => m.Contents)
.OfType<ToolApprovalRequestContent>()
.ToList();
while (approvalRequests.Count > 0)
{
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(request =>
{
var toolCall = (FunctionCallContent)request.ToolCall;
Console.WriteLine($"Approve {toolCall.Name}? (Y/N)");
bool approved = Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false;
return new ChatMessage(ChatRole.User, [request.CreateResponse(approved)]);
});
response = await agent.RunAsync(userInputResponses, session);
approvalRequests = response.Messages
.SelectMany(m => m.Contents)
.OfType<ToolApprovalRequestContent>()
.ToList();
}
脚本错误详细信息
默认情况下,当技能脚本执行失败时,异常将传播到基础 FunctionInvokingChatClient。 如果将其 IncludeDetailedErrors 属性设置为 true,则异常消息将转发到模型,通过使用不同的参数重试,使其能够自行更正:
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(
options: new ChatClientAgentOptions
{
Name = "SkillsAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant.",
},
AIContextProviders = [skillsProvider],
},
model: deploymentName,
clientFactory: client => client
.AsBuilder()
.UseFunctionInvocation(configure: (c) => c.IncludeDetailedErrors = true)
.Build());
如果无法直接配置 FunctionInvokingChatClient ,请改为进行设置 AgentSkillsProviderOptions.IncludeDetailedErrors 。 这会捕获技能提供程序级别的异常,并将错误消息直接返回给模型:
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
SubprocessScriptRunner.RunAsync,
options: new AgentSkillsProviderOptions
{
IncludeDetailedErrors = true,
});
警告
任一方法都可能会向模型披露原始异常详细信息。 异常消息可以包含敏感信息,例如连接字符串、文件路径或内部服务名称。 此外,如果技能或脚本来自不受信任的来源,恶意构造的脚本可能会引发异常,其异常消息中嵌入了提示注入载荷。
默认情况下,由 SkillsProvider(load_skill、read_skill_resource 和 run_skill_script)提供的所有工具都需要批准。 当工具调用需要批准时,代理会暂停,并通过 result.user_input_requests 返回批准请求,而不是立即执行。 你使用 request.to_function_approval_response(approved=...) 批准或拒绝每个请求,并将响应发回:
from textwrap import dedent
from agent_framework import Agent, Content, InlineSkill, Message, SkillFrontmatter, SkillsProvider
deployment_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="deployment",
description="Tools for deploying application versions to production",
),
instructions=dedent("""\
Use this skill when the user asks to deploy an application.
Run the deploy script with the version and environment parameters.
"""),
)
@deployment_skill.script
def deploy(version: str, environment: str = "staging") -> str:
"""Deploy the application to the specified environment."""
return f"Deployed version {version} to {environment}"
# All skill tools require approval by default.
skills_provider = SkillsProvider(deployment_skill)
async with Agent(
client=client,
instructions="You are a deployment assistant.",
context_providers=[skills_provider],
) as agent:
# Use a session so the agent retains context across approval round-trips
session = agent.create_session()
result = await agent.run("Deploy version 2.5.0 to production", session=session)
# Collect a response for every request and send them in one run so the
# loop always makes progress.
while result.user_input_requests:
approval_responses: list[Content] = []
for request in result.user_input_requests:
if request.function_call is None:
approval_responses.append(request.to_function_approval_response(approved=False))
continue
print(f"Approve {request.function_call.name}? Args: {request.function_call.arguments}")
# In a real application, prompt the user here.
approval_responses.append(request.to_function_approval_response(approved=True))
result = await agent.run(Message(role="user", contents=approval_responses), session=session)
print(result)
当工具调用被拒绝(approved=False)时,代理会通知用户拒绝并可以相应地做出响应。
自动批准可信工具
不要为每次调用都进行提示,而应使用 ToolApprovalMiddleware 提供的静态自动批准规则之一来安装 SkillsProvider。 这样可以让只读工具自动运行,同时仍会提示是否执行脚本:
from agent_framework import Agent, SkillsProvider, ToolApprovalMiddleware
skills_provider = SkillsProvider(deployment_skill)
# Auto-approve read-only skill tools (load_skill, read_skill_resource).
# run_skill_script still requires explicit approval via result.user_input_requests.
approval_middleware = ToolApprovalMiddleware(
auto_approval_rules=[SkillsProvider.read_only_tools_auto_approval_rule],
)
agent = Agent(
client=client,
instructions="You are a deployment assistant.",
context_providers=[skills_provider],
middleware=[approval_middleware],
)
有两个规则可用:
-
SkillsProvider.read_only_tools_auto_approval_rule- 仅批准只读工具(load_skill、read_skill_resource),同时仍会提示run_skill_script。 -
SkillsProvider.all_tools_auto_approval_rule- 批准每个技能工具,包括run_skill_script(不需要手动审批循环)。
这两条规则都会拒绝任何带有 server_label 的调用,因此其作用范围始终限定在该提供商的本地工具上,绝不会自动批准同名的托管工具。 这些规则仅适用于仍需审批的工具——通过下方的 disable_*_approval 参数选择退出的工具无论如何都无需审批即可运行。
禁用特定工具的审批
对于可信技能,传递 disable_load_skill_approval、disable_read_skill_resource_approval 和/或 disable_run_skill_script_approval,使单个工具完全跳过审批流程(这些工具已通过 approval_mode="never_require" 注册):
skills_provider = SkillsProvider(
deployment_skill,
disable_load_skill_approval=True,
disable_read_skill_resource_approval=True,
# disable_run_skill_script_approval remains False - scripts still require approval
)
这些参数也可用于 SkillsProvider.from_paths()。
警告
仅对来自可信来源的技能和脚本禁用审批功能,或对脚本执行启用自动审批。 技能指令将注入代理的上下文中,并 run_skill_script 执行源提供的代码。
自定义系统提示
默认情况下,技能提供程序会注入系统提示,其中列出了可用的技能,并指示代理使用 load_skill 和 read_skill_resource。 可以自定义此提示:
var skillsProvider = new AgentSkillsProvider(
skillPath: Path.Combine(AppContext.BaseDirectory, "skills"),
options: new AgentSkillsProviderOptions
{
SkillsInstructionPrompt = """
You have skills available. Here they are:
{skills}
When a task matches a skill, use load_skill to retrieve instructions,
then read_skill_resource for referenced resources, and run_skill_script for scripts.
"""
});
注释
自定义模板必须包含用作生成的技能列表占位符的 {skills}。 文本大括号必须转义为 {{ 和 }}。
skills_provider = SkillsProvider.from_paths(
skill_paths=Path(__file__).parent / "skills",
instruction_template=(
"You have skills available. Here they are:\n{skills}\n"
"{resource_instructions}\n"
"{runner_instructions}"
),
)
注释
自定义模板必须包含用于生成的技能列表的占位符 {skills}。 它可以选择包含 {resource_instructions}(资源工具提示)和 {runner_instructions}(脚本工具提示)占位符;如果提供了这些占位符,则会填入内置指引;如果省略,则不会渲染它们(相应的工具仍会被注册)。 文本大括号必须转义为 {{ 和 }}。
注入服务和运行时参数
技能资源和脚本函数可以接收在运行时提供的外部应用程序上下文。
技能资源和脚本委托函数可以声明一个 IServiceProvider 参数,代理框架会自动注入。 这样,技能就可以按需解决已注册的应用程序服务。
Setup
注册你的应用程序服务,并通过 IServiceProvider 参数将构建的 services 传递给智能体:
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 声明为 AddResource 或 AddScript 委托中的参数——当代理读取资源或运行脚本时,框架会自动解析并注入它:
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时,框架每次代理读取资源时都会转发运行时关键字参数:
import os
from typing import Any
from agent_framework import InlineSkill, SkillFrontmatter
project_info_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="project-info",
description="Project status and configuration information",
),
instructions="Use this skill for questions about the current project.",
)
@project_info_skill.resource(name="environment", description="Current environment configuration")
def environment(**kwargs: Any) -> str:
"""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 时,框架会将运行时关键字参数与代理提供的 args 一起转发:
import json
from typing import Any
from agent_framework import InlineSkill, SkillFrontmatter
converter_skill = InlineSkill(
frontmatter=SkillFrontmatter(
name="unit-converter",
description="Convert between common units using a conversion factor",
),
instructions="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})
代理通过工具调用提供value 和factor;应用程序通过args 提供precision。 未包含 **kwargs 的脚本函数仅接收代理提供的参数。
带 kwargs 的基于类的技能
基于类的技能方法还可以接受 **kwargs 接收运行时参数。 模式的工作方式相同 - 在资源方法或脚本方法上声明 **kwargs :
from typing import Any
from agent_framework import ClassSkill, SkillFrontmatter
class WeightConverterSkill(ClassSkill):
def __init__(self) -> None:
super().__init__(
frontmatter=SkillFrontmatter(
name="weight-converter",
description="Convert between weight units (pounds and kilograms).",
),
)
@property
def instructions(self) -> str:
return "Use this skill to convert between pounds and kilograms."
@ClassSkill.resource(name="weight-table")
def get_weight_table(self, **kwargs: Any) -> str:
"""Weight conversion factors, scoped to caller context."""
user_id = kwargs.get("user_id", "anonymous")
return f"Weight table for {user_id}: | lbs | kg | 0.453592 |"
@ClassSkill.script(name="convert")
def convert(self, value: float, factor: float, **kwargs: Any) -> str:
"""Convert a weight value."""
import json
precision = kwargs.get("precision", 4)
result = round(value * factor, precision)
return json.dumps({"value": value, "factor": factor, "result": result})
安全最佳做法
代理技能应被视为引入项目的任何第三方代码。由于技能指令注入到代理的上下文中,并且技能可以包括脚本,因此,对开放源代码依赖项应用相同的评审和管理级别至关重要。
-
使用前查看 - 在部署之前读取所有技能内容(
SKILL.md、脚本和资源)。 验证脚本的实际行为是否与其已声明的意向匹配。 检查尝试绕过安全准则、泄露数据或修改代理配置文件的对抗说明。 - 源信任 - 仅安装受信任的作者或经过审查的内部参与者的技能。 首选具有明确来源、版本控制和主动维护的技能。 警惕拼写相似的技能名称,模仿流行包。
- 沙盒化 - 在隔离环境中运行包含可执行脚本的技能。 仅将文件系统、网络和系统级访问限制为技能所需的内容。 在执行潜在敏感操作前,需要明确用户确认。
- 审核和日志记录 - 记录加载哪些技能、读取哪些资源以及执行哪些脚本。 这为你提供了审计跟踪,以在出现问题时将智能体行为追溯到特定技能内容。
何时使用技能与何时使用工作流
代理技能和 代理框架工作流 都扩展了代理可以执行的作,但它们的工作方式基本不同。 选择最符合要求的方法:
- 控制 - 借助技能,AI 决定如何执行指令。 当希望代理具有创造性或自适应性时,这是理想的选择。 使用工作流,可以显式定义执行路径。 如果需要确定性、可预测的行为,请使用工作流。
- 弹性 - 技能在单个智能体轮次中运行。 如果出现故障,则必须重试整个操作。 工作流支持检查点,因此它们可以在失败后从上一个成功步骤恢复。 当重新执行整个进程的成本很高时,请选择工作流。
- 副作用 - 当操作是幂等或低风险时,技能是合适的。 当步骤产生副作用(发送电子邮件、收费付款)时,首选工作流,这些副作用不应在重试时重复。
- 复杂性 - 技能最适合一个代理可以处理的集中的单域任务。 工作流更适用于协调多个代理、人工审批或外部系统集成的多步骤业务流程。
小窍门
经验法则:如果希望 AI 弄清楚 如何 完成任务,请使用技能。 如果需要保证 执行哪些 步骤以及按何种顺序执行,请使用工作流。