引言
随着 AI Agent 技术的快速发展,如何让 Agent 具备可复用、可扩展的专业能力成为一个重要课题。Agent Skills 规范提供了一种标准化的方式来定义和分发 Agent 技能,而 Microsoft Agent Framework (MAF) 则提供了构建 AI Agent 的强大基础设施。
本文将深入介绍如何基于 MAF 的上下文扩展(AIContextProvider)实现 Agent Skills 的集成,包括核心架构设计、关键组件实现以及实际应用示例。
一、架构概述
整体架构
Maf.AgentSkills 项目采用了 MAF 官方推荐的AIContextProviderFactory模式,实现了与 MAF 的无缝集成。整体架构如下:
技术栈
- 目标框架: .NET 10.0
- 核心依赖:
Microsoft.Agents.AI- MAF 核心框架Microsoft.Extensions.AI- AI 抽象层YamlDotNet- YAML Frontmatter 解析Microsoft.Extensions.DependencyInjection- 依赖注入支持
二、核心设计理念
1. 渐进式披露 (Progressive Disclosure)
Agent Skills 的核心理念之一是渐进式披露:Agent 首先只获取技能的元数据(名称和描述),只有在真正需要使用某个技能时,才加载完整的指令内容。
这种设计有几个重要优势:
- 减少 Token 消耗:系统提示只包含简短的技能列表,而不是所有技能的完整内容
- 提高效率:Agent 可以快速判断哪些技能与当前任务相关
- 按需加载:详细指令仅在需要时获取,避免信息过载
信息获取流程:
2. 符合 MAF 设计模式
项目严格遵循 MAF 的AIContextProviderFactory模式,这是 MAF 推荐的上下文注入方式:
// MAF 标准模式AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions{ AIContextProviderFactory = ctx => new MyContextProvider( chatClient, ctx.SerializedState, ctx.JsonSerializerOptions)});通过实现AIContextProvider抽象类,我们可以:
- 在每次 Agent 调用前注入技能信息
- 动态提供 Instructions、Messages 和 Tools
- 支持线程状态的序列化和反序列化
3. 安全第一
技能系统涉及文件读取和可能的脚本执行,因此安全性是首要考虑:
- 路径遍历防护:所有文件操作都经过路径安全验证
- 符号链接检测:防止通过符号链接逃逸
- 脚本执行默认禁用:需要显式启用并配置白名单
- 命令执行白名单:只允许预定义的命令
三、关键组件详解
1. SkillsContextProvider - 技能上下文提供器
SkillsContextProvider是整个系统的核心,它继承自 MAF 的AIContextProvider抽象类:
public sealedclassSkillsContextProvider : AIContextProvider{ privatereadonly IChatClient _chatClient; privatereadonly SkillLoader _skillLoader; privatereadonly SkillsOptions _options; private SkillsState _state; // 构造函数1:创建新实例 public SkillsContextProvider(IChatClient chatClient, SkillsOptions? options = null) { _chatClient = chatClient; _options = options ?? new SkillsOptions(); var settings = new SkillsSettings(_options.AgentName, _options.ProjectRoot); _skillLoader = new SkillLoader(); _state = new SkillsState(); // 自动加载技能 LoadSkills(settings); } // 构造函数2:从序列化状态恢复(支持线程持久化) public SkillsContextProvider( IChatClient chatClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null) { // 反序列化恢复状态... } // 在 Agent 调用前注入技能上下文 public override ValueTask<AIContext> InvokingAsync( InvokingContext context, CancellationToken cancellationToken = default) { // 生成技能系统提示 var instructions = GenerateSkillsPrompt(_state.AllSkills); // 创建技能工具 var tools = CreateSkillsTools(_state); return ValueTask.FromResult(new AIContext { Instructions = instructions, Tools = tools }); } // 序列化状态以支持线程持久化 public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { var state = new { Options = _options, State = _state }; return JsonSerializer.SerializeToElement(state, jsonSerializerOptions); }}关键设计点:
- 双构造函数模式:一个用于创建新实例,一个用于从序列化状态恢复
- InvokingAsync:在每次 Agent 调用前被调用,返回
AIContext注入技能信息 - Serialize:支持将技能状态序列化,用于线程持久化场景
2. SkillLoader - 技能加载器
SkillLoader负责从文件系统发现和加载技能:
public sealedclassSkillLoader{ privatereadonly SkillParser _parser; /// <summary> /// 从指定目录加载所有技能 /// </summary> public IEnumerable<SkillMetadata> LoadSkillsFromDirectory( string skillsDirectory, SkillSource source) { if (!Directory.Exists(skillsDirectory)) yieldbreak; foreach (var skillDir in Directory.GetDirectories(skillsDirectory)) { var skill = TryLoadSkill(skillDir, source); if (skill is not null) yieldreturn skill; } } private SkillMetadata? TryLoadSkill(string skillDirectory, SkillSource source) { var skillFilePath = Path.Combine(skillDirectory, "SKILL.md"); if (!File.Exists(skillFilePath)) returnnull; // 安全检查:验证符号链接 if (PathSecurity.IsSymbolicLink(skillFilePath)) { var realPath = PathSecurity.GetRealPath(skillFilePath); if (!PathSecurity.IsPathSafe(realPath, skillDirectory)) returnnull; } return _parser.Parse(skillFilePath, source); }}技能目录结构:
~/.maf/{agent-name}/skills/ # 用户级技能{project-root}/.maf/skills/ # 项目级技能(优先级更高)每个技能是一个独立的目录,包含SKILL.md文件:
skills/├── web-research/│ ├── SKILL.md│ ├── search.py│ └── templates/│ └── report.md├── code-review/│ ├── SKILL.md│ └── checklist.md└── pdf-tools/ ├── SKILL.md ├── split_pdf.py └── merge_pdf.py技能加载流程:
3. SkillParser - 技能解析器
SkillParser负责解析 SKILL.md 文件的 YAML Frontmatter:
public sealedclassSkillParser{ privateconststring FrontmatterDelimiter = "---"; public SkillMetadata Parse(string skillFilePath, SkillSource source) { var content = File.ReadAllText(skillFilePath); var skillDirectory = Path.GetDirectoryName(skillFilePath)!; var directoryName = Path.GetFileName(skillDirectory); // 提取 YAML Frontmatter var frontmatter = ExtractFrontmatter(content); if (frontmatter isnull) thrownew SkillParseException(skillFilePath, "SKILL.md must have YAML frontmatter delimited by '---'."); // 解析 YAML var yamlData = _yamlDeserializer.Deserialize<SkillFrontmatter>(frontmatter); // 验证必需字段 if (string.IsNullOrWhiteSpace(yamlData.Name)) thrownew SkillParseException(skillFilePath, "Skill 'name' is required."); if (string.IsNullOrWhiteSpace(yamlData.Description)) thrownew SkillParseException(skillFilePath, "Skill 'description' is required."); // 验证名称格式和目录匹配 SkillValidator.ValidateName(yamlData.Name); SkillValidator.ValidateNameMatchesDirectory(yamlData.Name, directoryName); returnnew SkillMetadata( Name: yamlData.Name, Description: yamlData.Description, Path: skillDirectory, Source: source, License: yamlData.License, AllowedTools: AllowedTool.Parse(yamlData.AllowedTools) ); }}SKILL.md 格式示例:
---name: web-researchdescription: A skill for conducting comprehensive web researchlicense: MITallowed-tools: web_search fetch_url---# Web Research Skill## When to UseUse this skill when researching topics online...## Instructions1. Clarify the research scope2. Search strategically3. Synthesize information...4. SkillsToolFactory - 工具工厂
SkillsToolFactory根据配置创建技能相关的工具:
public sealedclassSkillsToolFactory{ public IReadOnlyList<AITool> CreateTools() { var tools = new List<AITool>(); // 默认启用的安全工具 if (_options.EnableReadSkillTool) tools.Add(new ReadSkillTool(_loader, _stateProvider).ToAIFunction()); if (_options.EnableReadFileTool) tools.Add(new ReadFileTool(_stateProvider).ToAIFunction()); if (_options.EnableListDirectoryTool) tools.Add(new ListDirectoryTool(_loader, _stateProvider).ToAIFunction()); // 需要显式启用的高危工具 if (_options.EnableExecuteScriptTool) tools.Add(new ExecuteScriptTool(_stateProvider, _options).ToAIFunction()); if (_options.EnableRunCommandTool && _options.AllowedCommands.Count > 0) tools.Add(new RunCommandTool(_stateProvider, _options).ToAIFunction()); return tools; }}内置工具:
| 工具名 | 功能 | 默认状态 |
|---|---|---|
read_skill | 读取 SKILL.md 完整内容 | ✅ 启用 |
read_skill_file | 读取技能目录中的文件 | ✅ 启用 |
list_skill_directory | 列出技能目录内容 | ✅ 启用 |
execute_skill_script | 执行技能中的脚本 | ❌ 禁用 |
run_skill_command | 运行白名单命令 | ❌ 禁用 |
工具创建决策流程:
5. ChatClientExtensions - 便捷扩展方法
为了简化使用,项目提供了ChatClient的扩展方法:
public staticclassChatClientExtensions{ public static AIAgent CreateSkillsAgent( this IChatClient chatClient, Action<SkillsOptions>? configureSkills = null, Action<ChatClientAgentOptions>? configureAgent = null) { var skillsOptions = new SkillsOptions(); configureSkills?.Invoke(skillsOptions); var agentOptions = new ChatClientAgentOptions { AIContextProviderFactory = ctx => { // 检查是否从序列化状态恢复 if (ctx.SerializedState.ValueKind != JsonValueKind.Undefined) { returnnew SkillsContextProvider( chatClient, ctx.SerializedState, ctx.JsonSerializerOptions); } // 创建新实例 returnnew SkillsContextProvider(chatClient, skillsOptions); } }; configureAgent?.Invoke(agentOptions); return chatClient.CreateAIAgent(agentOptions); }}四、实现细节
Agent 调用完整流程
以下是 Agent 执行任务时的完整调用流程:
1. 技能系统提示生成
技能信息通过系统提示注入到 Agent 中。系统提示采用渐进式披露的设计:
public static class SkillsPromptTemplates{ public const string SystemPromptTemplate = """ ## Skills System You have access to a skills library that provides specialized capabilities. {skills_locations} **Available Skills:** {skills_list} --- ### How to Use Skills (Progressive Disclosure) - CRITICAL Skills follow a **progressive disclosure** pattern - you know they exist (name + description above), but you **MUST read the full instructions before using them**. **MANDATORY Workflow:** 1. **Recognize when a skill applies**: Check if the user's task matches any skill's description above 2. **Read the skill's full instructions FIRST**: Use `read_skill` tool to get the complete SKILL.md content 3. **Follow the skill's instructions precisely**: SKILL.md contains step-by-step workflows and examples 4. **Execute scripts only after reading**: Use the exact script paths and argument formats from SKILL.md **IMPORTANT RULES:** ⚠️ **NEVER call `execute_skill_script` without first reading the skill with `read_skill`** ✅ **Correct Workflow Example:** ```User: "Split this PDF into pages" 1. Recognize: "split-pdf" skill matches this task 2. Call: read_skill("split-pdf") → Get full instructions 3. Learn: SKILL.md shows the actual script path and argument format 4. Execute: Use the exact command format from SKILL.md ```Remember: **Read first, then execute.** This ensures you use skills correctly! """;}2. 技能状态管理
技能状态通过SkillsState类管理,支持序列化:
public sealedclassSkillsState{ public IReadOnlyList<SkillMetadata> UserSkills { get; init; } = []; public IReadOnlyList<SkillMetadata> ProjectSkills { get; init; } = []; public DateTimeOffset LastRefreshed { get; init; } /// <summary> /// 获取所有技能,项目级技能优先级更高 /// </summary> public IReadOnlyList<SkillMetadata> AllSkills { get { var projectSkillNames = ProjectSkills .Select(s => s.Name) .ToHashSet(StringComparer.OrdinalIgnoreCase); var userSkillsWithoutOverrides = UserSkills .Where(s => !projectSkillNames.Contains(s.Name)); return [.. ProjectSkills, .. userSkillsWithoutOverrides]; } } public SkillMetadata? GetSkill(string name) { return ProjectSkills.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) ?? UserSkills.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); }}3. 路径安全验证
所有文件操作都经过严格的路径安全验证:
public staticclassPathSecurity{ /// <summary> /// 解析安全路径,防止路径遍历攻击 /// </summary> publicstaticstring? ResolveSafePath(string basePath, string relativePath) { var fullPath = Path.GetFullPath(Path.Combine(basePath, relativePath)); var normalizedBase = Path.GetFullPath(basePath); // 确保解析后的路径仍在基础路径内 if (!fullPath.StartsWith(normalizedBase, StringComparison.OrdinalIgnoreCase)) returnnull; return fullPath; } /// <summary> /// 检查是否是符号链接 /// </summary> public static bool IsSymbolicLink(string path) { var fileInfo = new FileInfo(path); return fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint); } /// <summary> /// 验证路径是否安全 /// </summary> public static bool IsPathSafe(string targetPath, string allowedBasePath) { var normalizedTarget = Path.GetFullPath(targetPath); var normalizedBase = Path.GetFullPath(allowedBasePath); return normalizedTarget.StartsWith(normalizedBase, StringComparison.OrdinalIgnoreCase); }}五、使用方法
基本用法
using Maf.AgentSkills.Agent;using OpenAI;// 创建 ChatClientvar chatClient = new OpenAIClient(apiKey) .GetChatClient("gpt-4") .AsIChatClient();// 创建支持技能的 Agentvar agent = chatClient.CreateSkillsAgent( configureSkills: options => { options.AgentName = "my-assistant"; options.ProjectRoot = Directory.GetCurrentDirectory(); }, configureAgent: options => { options.ChatOptions = new() { Instructions = "You are a helpful assistant." }; });// 使用 Agentvar thread = agent.GetNewThread();var response = await agent.RunAsync("What skills do you have?", thread);Console.WriteLine(response.Text);线程序列化
技能状态可以随线程一起序列化,支持持久化会话:
// 序列化线程var serializedThread = thread.Serialize();// 保存到数据库或文件await SaveThreadAsync(userId, serializedThread);// 稍后恢复并继续对话var restoredThread = agent.DeserializeThread(serializedThread);var response = await agent.RunAsync("Continue our chat", restoredThread);序列化/反序列化流程:
依赖注入集成
var builder = Host.CreateApplicationBuilder(args);// 注册 ChatClientbuilder.Services.AddChatClient(sp =>{ returnnew OpenAIClient(apiKey) .GetChatClient("gpt-4") .AsIChatClient();});// 注册技能 Agentbuilder.Services.AddSingleton<AIAgent>(sp =>{ var chatClient = sp.GetRequiredService<IChatClient>(); return chatClient.CreateSkillsAgent( configureSkills: options => { options.AgentName = "di-agent"; options.ProjectRoot = Directory.GetCurrentDirectory(); options.ToolsOptions.EnableReadSkillTool = true; options.ToolsOptions.EnableReadFileTool = true; });});var host = builder.Build();var agent = host.Services.GetRequiredService<AIAgent>();var thread = agent.GetNewThread();var path = "E:\\GitHub\\My\\dotnet-agent-skills\\NET+AI:技术栈全景解密.pdf";var response = await agent.RunAsync($"请将指定目录:{path}的文件拆分前3页", thread);启用脚本执行
var agent = chatClient.CreateSkillsAgent( configureSkills: options => { options.AgentName = "power-assistant"; options.ProjectRoot = Directory.GetCurrentDirectory(); // 启用脚本执行(需要显式开启) options.ToolsOptions.EnableExecuteScriptTool = true; options.ToolsOptions.AllowedScriptExtensions = [".py", ".ps1", ".cs"]; options.ToolsOptions.ScriptTimeoutSeconds = 60; // 启用命令执行(白名单模式) options.ToolsOptions.EnableRunCommandTool = true; options.ToolsOptions.AllowedCommands = ["git", "npm", "dotnet"]; });六、安全考量
1. 默认安全
项目遵循"默认安全"原则:
- 脚本执行默认禁用:
EnableExecuteScriptTool = false - 命令执行默认禁用:
EnableRunCommandTool = false - 只读工具默认启用:
ReadSkill,ReadFile,ListDirectory
2. 路径遍历防护
所有文件操作都限制在技能目录内:
// 读取文件时验证路径var safePath = PathSecurity.ResolveSafePath(skill.Path, relativePath);if (safePath is null){ return JsonSerializer.Serialize(new { success = false, error = "Path traversal attempt detected" });}3. 脚本执行白名单
即使启用了脚本执行,也只允许特定扩展名:
public class SkillsToolsOptions{ public List<string> AllowedScriptExtensions { get; set; } = [".py", ".ps1", ".sh", ".cs"]; public int ScriptTimeoutSeconds { get; set; } = 30; public int MaxOutputSizeBytes { get; set; } = 50 * 1024; // 50KB}4. 命令执行白名单
命令执行采用严格的白名单机制:
options.AllowedCommands = ["git", "npm", "dotnet"]; // 只允许这些命令七、最佳实践
1. 技能设计原则
- 单一职责:每个技能专注于一个领域
- 清晰描述:description 字段要足够描述技能用途
- 详细指令:SKILL.md 正文要包含完整的使用说明
- 示例驱动:提供具体的使用示例
2. 目录组织
# 推荐的技能目录结构my-skill/├── SKILL.md # 必需:技能定义文件├── README.md # 可选:详细文档├── scripts/ # 脚本文件│ ├── main.py│ └── utils.py├── templates/ # 模板文件│ └── output.md└── config/ # 配置文件 └── settings.json3. SKILL.md 编写规范
---name: my-skilldescription: Brief description under 1024 characterslicense: MITallowed-tools: web_search file_write---# Skill Name## OverviewClear explanation of what this skill does.## When to Use- Situation 1- Situation 2## Prerequisites- Required tools or dependencies## InstructionsStep-by-step workflow:1. First step2. Second step3. Third step## Available Scripts### script.py- **Purpose**: What it does- **Arguments**: `--input <file> --output <file>`- **Example**: `python script.py --input data.csv --output result.json`## Examples### Example 1: Basic Usage...4. 项目级 vs 用户级技能
- 用户级技能(
~/.maf/{agent}/skills/):通用技能,适用于多个项目 - 项目级技能(
{project}/.maf/skills/):项目特定技能,可覆盖同名用户级技能
八、总结
Maf.AgentSkills 项目展示了如何基于 Microsoft Agent Framework 实现 Agent Skills 集成。
核心设计要点:
- 遵循 MAF 模式:使用
AIContextProviderFactory实现无侵入式集成 - 渐进式披露:通过三层结构(元数据 → 指令 → 资源)优化 Token 使用
- 安全第一:默认禁用危险操作,采用白名单机制
- 线程序列化:完整支持会话持久化
- 依赖注入友好:易于集成到现有应用
通过这套实现,开发者可以轻松为 AI Agent 添加可复用的专业技能,使 Agent 能够完成更复杂的任务。
那么,如何系统的去学习大模型LLM?
作为一名深耕行业的资深大模型算法工程师,我经常会收到一些评论和私信,我是小白,学习大模型该从哪里入手呢?我自学没有方向怎么办?这个地方我不会啊。如果你也有类似的经历,一定要继续看下去!这些问题啊,也不是三言两语啊就能讲明白的。
所以我综合了大模型的所有知识点,给大家带来一套全网最全最细的大模型零基础教程。在做这套教程之前呢,我就曾放空大脑,以一个大模型小白的角度去重新解析它,采用基础知识和实战项目相结合的教学方式,历时3个月,终于完成了这样的课程,让你真正体会到什么是每一秒都在疯狂输出知识点。
由于篇幅有限,⚡️ 朋友们如果有需要全套 《2025全新制作的大模型全套资料》,扫码获取~
👉大模型学习指南+路线汇总👈
我们这套大模型资料呢,会从基础篇、进阶篇和项目实战篇等三大方面来讲解。
👉①.基础篇👈
基础篇里面包括了Python快速入门、AI开发环境搭建及提示词工程,带你学习大模型核心原理、prompt使用技巧、Transformer架构和预训练、SFT、RLHF等一些基础概念,用最易懂的方式带你入门大模型。
👉②.进阶篇👈
接下来是进阶篇,你将掌握RAG、Agent、Langchain、大模型微调和私有化部署,学习如何构建外挂知识库并和自己的企业相结合,学习如何使用langchain框架提高开发效率和代码质量、学习如何选择合适的基座模型并进行数据集的收集预处理以及具体的模型微调等等。
👉③.实战篇👈
实战篇会手把手带着大家练习企业级的落地项目(已脱敏),比如RAG医疗问答系统、Agent智能电商客服系统、数字人项目实战、教育行业智能助教等等,从而帮助大家更好的应对大模型时代的挑战。
👉④.福利篇👈
最后呢,会给大家一个小福利,课程视频中的所有素材,有搭建AI开发环境资料包,还有学习计划表,几十上百G素材、电子书和课件等等,只要你能想到的素材,我这里几乎都有。我已经全部上传到CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】
相信我,这套大模型系统教程将会是全网最齐全 最易懂的小白专用课!!