大数跨境

.NET+AI | Agent Skills | MAF 支持Agent Skill了,手把手教你如何集成 Agent Skills,让Agent 拥有领域专长

.NET+AI | Agent Skills | MAF 支持Agent Skill了,手把手教你如何集成 Agent Skills,让Agent 拥有领域专长 dotNET跨平台
2026-02-27
215

随着 Microsoft Agent Framework(MAF)1.0.0-rc1 版本的发布,距离其 1.0 正式版亮相应该已经不远了。

目前 MAF 1.0.0-rc2 已支持 Agent Skills,虽然这一版主要针对静态内容,尚未开放脚本执行能力,但本系列文章将逐步分享如何进一步拓展其功能,敬请持续关注。

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

MAF 如何集成 Agent Skills — 让 Agent 拥有领域专长

MAF 1.0.0-rc2 已支持 Agent Skills,虽然这一版主要针对静态内容,尚未开放脚本执行能力,但已经允许我们为 Agent 注入模块化的领域知识,拓展 Agent的能力边界,接下来我们将带你一步一步的了解 Agent Skills的实现细节和集成路径。


📚 课程目标

本节课将学习 Agent Skills — 一种为 AI Agent 注入领域知识的模块化机制。你将掌握:

  • ✅ 理解 Agent Skills 规范及其渐进式披露(Progressive Disclosure)设计模式
  • ✅ 掌握 SKILL.md 文件的结构(YAML Frontmatter + Markdown Body + 资源引用)
  • ✅ 使用 FileAgentSkillsProvider 从文件系统发现和加载 Skills
  • ✅ 理解 load_skill 和 read_skill_resource 两个工具的工作原理
  • ✅ 通过费用报销场景实战验证 Agent Skills 的完整工作流程

📋 前置知识

必须掌握 ✅

  • 第4章第1-2课: MAF 基础(Agent 创建、多轮对话)
  • 第4章第7课: Agent Function Calling(函数调用/工具调用)
  • 第4章第15-16课: AIContextProvider 概念与实践

建议了解 🔵

  • 第4章第8课: Agent Plugins(插件机制,与 Skills 互补)
  • Markdown 语法: 理解 YAML Frontmatter 格式

预计学习时间: ⏱️ 35-45 分钟
难度级别: ⭐⭐⭐ 中级

💡 说明: Agent Skills 是基于 AIContextProvider 机制的实现,专注于静态领域知识的管理和按需注入。

🧩 第一部分:什么是 Agent Skills?

1.1 问题引入 — Agent 如何获取领域知识?

假设你正在开发一个企业级 AI 助手,需要它精通费用报销政策——包括各类费用限额、审批流程、收据要求等。你有几种选择:

方案
具体做法
缺点
❌ 写死在 Instructions 里
把整个政策文档塞进系统提示
Token 浪费严重,所有请求都负载全部知识
❌ 写成代码逻辑
用 if-else 硬编码规则
不灵活,非开发人员无法维护
⚠️ 做成 RAG
建立向量索引检索
过重,对结构化政策文档不必要
✅ Agent Skills
将知识打包为标准化 Skill
按需加载,模块化,非开发者可维护

1.2 Agent Skills 规范

Agent Skills 是一种开放规范,定义了如何将领域知识打包为模块化、可复用的知识包(Skill),供 AI Agent 按需使用。

核心设计理念 — 渐进式披露(Progressive Disclosure):


三阶段解释:

阶段
触发时机
内容
Token 开销
📢 摘要展示(Advertise)
每次请求
技能名称 + 描述
~100 tokens/skill
📖 加载(Load)
Agent 判断需要时
完整的 SKILL.md 指令
按需加载
📎 读取资源(Read)
Agent 需要参考资料时
FAQ、模板等补充文件
按需加载

💡 为什么不一次性全部加载? 渐进式披露通过延迟加载,大幅降低 Token 消耗。一个 Agent 可能注册 20 个 Skills,但每次请求通常只需要 1-2 个。如果全部塞进 System Prompt,可能消耗数万 Token;而渐进式披露将初始开销控制在每个 Skill ~100 tokens。

1.3 Agent Skills vs Function Calling vs RAG

维度
Agent Skills
Function Calling
RAG
加载方式
渐进式披露
全量注册
检索 + 注入
知识类型
静态领域知识
动态操作能力
非结构化文档
维护者
业务专家
开发者
数据工程师
文件格式
Markdown (SKILL.md)
C# 方法 + 属性
向量数据库
典型场景
政策规则、操作指南
调用 API、执行操作
知识库搜索
注册方式 AIContextProviders ChatOptions.Tools AIContextProviders

💡 Skills 与 Function Calling 是互补关系:Skills 提供"知道什么"(领域知识),Function Calling 提供"能做什么"(操作能力)。一个完善的 Agent 通常两者兼备。

📄 第二部分:SKILL.md 文件剖析

一个 Agent Skill 就是一个文件夹,核心是一个 SKILL.md 文件。让我们剖析它的结构:

2.1 目录结构

skills/
└── expense-report/                    # 技能目录(名称与 frontmatter.name 一致)
    ├── SKILL.md                       # 技能定义文件(必需)
    ├── references/                    # 参考资料目录
    │   └── POLICY_FAQ.md             # 费用政策 FAQ
    └── assets/                        # 资产目录
        └── expense-report-template.md # 报销模板

2.2 SKILL.md 结构

SKILL.md 由两部分组成:YAML Frontmatter(元数据)和 Markdown Body(指令内容)。

---
name: expense-report
description: File and validate employee expense reports according to company policy.
---


# Expense Report

## Categories and Limits
| Category | Limit | Receipt | Approval |
|---|---|---|---|
| Meals — solo | $50/day | >$25 | No |
...

## Filing Process
1. Collect receipts...
2. Use template: [assets/expense-report-template.md](assets/expense-report-template.md)
...

## Policy Rules
For policy questions, consult the FAQ: [references/POLICY_FAQ.md](references/POLICY_FAQ.md)

📌 Frontmatter 规则:

字段
必需
规则
示例
name
小写字母、数字和连字符,不超过 64 字符
expense-report
description
不超过 1024 字符,用于系统提示中的技能摘要展示
File and validate...

📌 Body 中的资源引用:

Body 中可以使用 Markdown 链接引用本地文件。FileAgentSkillLoader 会自动解析这些链接,将其注册为可读取的资源:

<!-- ✅ 支持:相对路径引用 -->
[assets/expense-report-template.md](assets/expense-report-template.md)
[references/POLICY_FAQ.md](references/POLICY_FAQ.md)

<!-- ✅ 支持:带 ./ 前缀 -->
[./references/POLICY_FAQ.md](./references/POLICY_FAQ.md)

<!-- ❌ 不支持:URL 链接(会被忽略) -->
[外部链接](https://example.com/doc.md)

<!-- ❌ 不支持:路径穿越(安全防护) -->
[恶意路径](../../etc/passwd)

2.3 安全机制

FileAgentSkillLoader 内置了多重安全防护:

  • 🛡️ 路径穿越防护:资源文件必须位于 Skill 目录内部
  • 🛡️ 符号链接检查:防止通过 symlink 逃逸到外部目录
  • 🛡️ XML 转义:技能元数据在嵌入提示词前会进行 XML 编码
  • 🛡️ 名称验证:仅允许 [a-z0-9-] 格式的技能名称

🔬 第三部分:源码架构深度解析

3.1 核心类关系


3.2 FileAgentSkillsProvider — 技能提供者

FileAgentSkillsProvider 是 AIContextProvider 的子类,实现了完整的渐进式披露机制。

构造函数中的初始化流程:

public FileAgentSkillsProvider(string skillPath, ...)
{
    // 1️⃣ 创建 Loader,扫描目录发现所有 Skill
    this._loader = new FileAgentSkillLoader(this._logger);
    this._skills = this._loader.DiscoverAndLoadSkills(skillPaths);

    // 2️⃣ 构建技能摘要提示词
    this._skillsInstructionPrompt = BuildSkillsInstructionPrompt(options, this._skills);

    // 3️⃣ 注册两个 AI 工具
    this._tools =
    [
        AIFunctionFactory.Create(this.LoadSkill,
            name: "load_skill",
            description: "Loads the full instructions for a specific skill."),
        AIFunctionFactory.Create(this.ReadSkillResourceAsync,
            name: "read_skill_resource",
            description: "Reads a file associated with a skill, such as references or assets."),
    
];
}

内置的系统提示词模板:

You have access to skills containing domain-specific knowledge and capabilities.
Each skill provides specialized instructions, reference documents, and assets.

<available_skills>
  <skill>
    <name>expense-report</name>
    <description>File and validate employee expense reports...</description>
  </skill>
</available_skills>

When a task aligns with a skill's domain:
1. Use `load_skill` to retrieve the skill'
s instructions
2. Follow the provided guidance
3. Use `read_skill_resource` to read any references or other files mentioned

Only load what is needed, when it is needed.

💡 注意FileAgentSkillsProvider 重写的是 ProvideAIContextAsync 方法(而非 InvokingAsync),返回的 AIContext 同时包含 Instructions(系统提示)和 Tools(两个工具函数)。

3.3 FileAgentSkillLoader — 技能发现与加载

FileAgentSkillLoader 负责从文件系统中发现和解析 SKILL.md 文件。

发现流程:


关键代码片段 — YAML 解析:

// 正则匹配 YAML frontmatter(--- 分隔符之间的内容)
privatestaticreadonly Regex s_frontmatterRegex = 
    new(@"\A\uFEFF?^---\s*$(.+?)^---\s*$"
        RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);

// 正则匹配 Markdown 中的本地资源链接
privatestaticreadonly Regex s_resourceLinkRegex = 
    new(@"\[.*?\]\((\.?\.?/?[\w][\w\-./]*\.\w+)\)", RegexOptions.Compiled);

// 技能名称验证:仅允许小写字母、数字和连字符
privatestaticreadonly Regex s_validNameRegex = 
    new(@"^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$", RegexOptions.Compiled);

3.4 工具调用链路分析

当 Agent 调用 load_skill 或 read_skill_resource 时:

// load_skill:根据名称返回 SKILL.md 的 Body 部分
private string LoadSkill(string skillName)
{
    if (!this._skills.TryGetValue(skillName, out FileAgentSkill? skill))
        return$"Error: Skill '{skillName}' not found.";
    return skill.Body;  # 返回 Markdown 指令内容
}

// read_skill_resource:从磁盘读取资源文件内容
private async Task<stringReadSkillResourceAsync(
    string skillName, string resourceName, CancellationToken cancellationToken
)

{
    if (!this._skills.TryGetValue(skillName, out FileAgentSkill? skill))
        return$"Error: Skill '{skillName}' not found.";

    // 委托给 Loader,包含路径安全检查
    returnawaitthis._loader.ReadSkillResourceAsync(skill, resourceName, cancellationToken);
}

🚀 第四部分:实战 — 费用报销 Agent

场景说明

我们将创建一个具备费用报销领域知识的 AI Agent,它能够:

  1. 📋 回答费用报销政策问题(小费能否报销?住宿限额多少?)
  2. 📝 根据模板生成报销报告
  3. 📖 按需加载 FAQ 和模板等参考资料

步骤 1:环境准备

导入必要的依赖包和 Helper 类。

// 导入 AI Client Helper
#!import ../helper/AIClientHelper.cs
// 导入 MAF 核心包
#!import ../helper/MafHelper.cs

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

Console.WriteLine("✅ 依赖包加载完成");
Console.WriteLine("📦 核心命名空间:");
new { 
    Microsoft_Agents_AI = "Agent 核心功能 + Skills 支持",
    Microsoft_Extensions_AI = "AI 抽象层(IChatClient、AIFunctionFactory)"
}.Display();

步骤 2:准备 Skill 文件

我们将在 Notebook 运行目录下动态创建 expense-report 技能文件。这包含三个部分:

  1. SKILL.md — 技能定义(Frontmatter + 指令)
  2. references/POLICY_FAQ.md — 费用政策 FAQ
  3. assets/expense-report-template.md — 报销模板
using System.IO;
// 在 Notebook 运行目录下创建 skills 文件结构
var skillsRootPath = Path.Combine(Directory.GetCurrentDirectory(), "skills");
var expenseSkillPath = Path.Combine(skillsRootPath, "expense-report");
var referencesPath = Path.Combine(expenseSkillPath, "references");
var assetsPath = Path.Combine(expenseSkillPath, "assets");

// 确保目录存在
Directory.CreateDirectory(referencesPath);
Directory.CreateDirectory(assetsPath);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 1️⃣ 创建 SKILL.md — 技能定义文件(已中文化)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var skillMd = """
---
name: expense-report
description: 按照 Contoso 公司政策填写和审核员工费用报销。适用于费用报销、报销规则、收据要求、支出限额或费用类别等相关问题。
---

# 费用报销(Expense Report)

## 费用类别与限额

| 类别 | 限额 | 收据要求 | 审批 |
|---|---|---|---|
| 单人用餐 | $50/天 | >$25 | 无需 |
| 团队/客户用餐 | $75/人 | 必须 | 总额>$200需经理 |
| 住宿 | $250/晚 | 必须 | 超过3晚需经理 |
| 地面交通 | $100/天 | >$15 | 无需 |
| 机票 | 经济舱 | 必须 | >$1,500需VP |
| 会议/培训 | $2,000/次 | 必须 | 经理+L&D |
| 办公用品 | $100 | 是 | 无需 |
| 软件/订阅 | $50/月 | 是 | >$200/年需经理 |

## 报销流程

1. 收集收据——需包含商家、日期、金额、支付方式。
2. 按上表分类。
3. 使用模板:[assets/expense-report-template.md](assets/expense-report-template.md)。
4. 团队/客户用餐需列出参与人及业务目的。
5. 提交——<$500自动审批;$500–$2,000需经理;>$2,000需VP。
6. 报销:10个工作日内通过银行转账。

## 政策规则

- 需在交易后30天内提交。
- 酒精类费用不予报销。
- 外币:按交易日汇率折算为美元,并注明原币种及金额。
- 混合个人/商务出行:仅报销商务部分,需提供对比报价。
- 丢失收据(>$25):需提交财务部的丢失收据声明,每季度最多2次。
- 如有未涵盖的问题,请查阅FAQ:[references/POLICY_FAQ.md](references/POLICY_FAQ.md)。答案应以本文件和FAQ为准。
"
"";

File.WriteAllText(Path.Combine(expenseSkillPath, "SKILL.md"), skillMd);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 2️⃣ 创建 references/POLICY_FAQ.md — 费用政策 FAQ(已中文化)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var faqMd = """
# 费用政策常见问答(FAQ)

## 用餐

**问:工作日咖啡或零食可以报销吗?**
答:每日咖啡/零食低于$10不予报销(视为个人消费)。客户会议或团队协作期间购买的咖啡可作为团队用餐报销。

**问:团队晚餐超出人均限额怎么办?**
答:$75/人限额为指导标准。超出20%以内可书面说明(如“客户指定场地”),超出20%需VP预先批准。

**问:需要列出所有参与人吗?**
答:是。客户用餐需列明客户姓名及公司,团队用餐需列出所有员工姓名。10人以上可单独附名单。

## 差旅

**问:可以预订高端经济舱或商务舱吗?**
答:经济舱为标准。6小时以上可选高端经济舱,商务舱需VP预批,通常仅限10小时以上或医疗原因。

**问:打车(Uber/Lyft)和租车如何选择?**
答:往返30英里以内建议打车,多日或超$100/天建议租车。3人以上可选更大车型。

**问:小费可以报销吗?**
答:用餐、打车、酒店清洁小费20%以内可报销,超出需说明理由。

## 住宿

**问:部分城市$250/晚不够怎么办?**
答:纽约、旧金山、伦敦、东京、悉尼等高消费城市自动提升至$350/晚,无需额外审批。其他特殊情况请提前向经理申请。

**问:住亲友家能否领取补贴?**
答:不可以。公司仅报销实际住宿费用,不提供补贴。

## 订阅与软件

**问:个人效率工具可以报销吗?**
答:需与工作直接相关,如IDE、设计软件、项目管理工具可报销。一般效率类应用需经理书面确认。

## 收据与材料

**问:收据模糊/损坏怎么办?**
答:可向商家补打,如无法获取,需提交丢失收据声明(财务部网站下载),每季度限2次。

**问:停车/过路费需要收据吗?**
答:低于$15无需收据,注明日期、地点、金额即可;$15及以上需收据或银行流水。

## 审批与报销

**问:经理休假谁来审批?**
答:可由上级经理或系统指定的代理审批人审批。

**问:能否报销上季度的费用?**
答:标准为30天内,超期需书面说明并VP批准,超90天仅特殊情况经CFO批准。
"
"";

File.WriteAllText(Path.Combine(referencesPath, "POLICY_FAQ.md"), faqMd);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 3️⃣ 创建 assets/expense-report-template.md — 报销模板(无需翻译,表头已通用)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var templateMd = """
# 费用报销模板(Expense Report Template)

| 日期 | 费用类别 | 商家 | 描述 | 金额(USD) | 原币种 | 原币金额 | 参与人 | 业务目的 | 已附收据 |
|------|----------|------|------|-------------|--------|----------|--------|----------|----------|
|      |          |      |      |             |        |          |        |          | 是 / 否  |
"
"";

File.WriteAllText(Path.Combine(assetsPath, "expense-report-template.md"), templateMd);

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Console.WriteLine("✅ Skill 文件创建完成!");
Console.WriteLine();
Console.WriteLine($"📁 技能根目录: {skillsRootPath}");
Console.WriteLine();

// 显示文件结构
new {
    文件结构 = new[] {
        "skills/",
        "  └── expense-report/",
        "      ├── SKILL.md (技能定义:Frontmatter + 指令)",
        "      ├── references/",
        "      │   └── POLICY_FAQ.md (费用政策 FAQ)",
        "      └── assets/",
        "          └── expense-report-template.md (报销模板)"
    }
}.Display();

步骤 3:加载 Skills 并创建 Agent

使用 FileAgentSkillsProvider 从文件系统发现和加载技能,然后将其注入到 Agent 中。

核心流程:

  1. FileAgentSkillsProvider 扫描 skills/ 目录
  2. 发现 expense-report/SKILL.md,解析 Frontmatter 和 Body
  3. 验证所有资源文件(FAQ、模板)存在且路径安全
  4. 自动注册 load_skill 和 read_skill_resource 两个 AI 工具
  5. 将技能列表注入系统提示词
#pragma warning disable MAAI001
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 1️⃣ 创建 SkillsProvider — 从文件系统发现和加载 Skills
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var skillsProvider = new FileAgentSkillsProvider(
    skillPath: Path.Combine(Directory.GetCurrentDirectory(), "skills")
);

Console.WriteLine("✅ FileAgentSkillsProvider 创建成功");
Console.WriteLine("📂 Skills 已从文件系统加载");
Console.WriteLine();

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 2️⃣ 创建 Agent — 注入 SkillsProvider
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var chatClient = AIClientHelper.GetDefaultChatClient();

AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
    Name = "SkillsAgent",
    ChatOptions = new()
    {
        Instructions = "You are a helpful assistant.",
    },
    // 🔑 关键:通过 AIContextProviders 注入 SkillsProvider
    AIContextProviders = [skillsProvider],
});

Console.WriteLine("✅ Agent 创建成功");
Console.WriteLine();

new {
    Agent名称 = "SkillsAgent",
    基础指令 = "You are a helpful assistant.",
    上下文提供者 = "FileAgentSkillsProvider",
    自动注册的工具 = new[] { "load_skill""read_skill_resource" },
    技能摘要开销 = "~100 tokens/skill(仅名称和描述)"
}.Display();

Console.WriteLine();
Console.WriteLine("💡 注意:此时 Agent 的系统提示中已包含技能摘要,但完整指令尚未加载。");
Console.WriteLine("   只有当 Agent 判断用户问题与技能相关时,才会调用 load_skill 按需加载。");

步骤 4:测试 — 费用政策问答

第一个测试:询问关于小费报销的问题。

预期行为:

  1. Agent 看到系统提示中的技能摘要 → 识别问题属于 expense-report 领域
  2. 调用 load_skill("expense-report") → 获取完整的费用报销规则
  3. 发现规则中提到 FAQ → 调用 read_skill_resource("expense-report", "references/POLICY_FAQ.md")
  4. 根据 FAQ 中的小费政策回答用户问题
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("   📋 测试 1:费用政策 FAQ 问答");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();

// 提出关于小费报销的问题
string question = "小费可以报销吗?我在一次出租车行程中给了 25% 的小费,想知道这是否可以报销。";

Console.WriteLine($"👤 用户: {question}");
Console.WriteLine();

// Agent 会自动执行渐进式披露流程:
// 1. 识别属于 expense-report 领域
// 2. 调用 load_skill 获取完整规则
// 3. 调用 read_skill_resource 获取 FAQ
// 4. 根据 FAQ 内容回答
AgentResponse response = await agent.RunAsync(question);

Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine($"🤖 Agent: {response.Text}");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

Console.WriteLine();
Console.WriteLine("🔍 观察要点:");
new {
    渐进式披露 = "Agent 自动判断需要加载 expense-report 技能",
    工具调用链 = "load_skill → read_skill_resource",
    FAQ匹配 = "FAQ 中明确说明:小费 20% 以内可报销,超过 20% 需要说明理由",
    预期回答 = "25% 的小费超过了 20% 的标准,需要额外提供理由说明"
}.Display();

步骤 5:测试 — 多轮对话生成报销报告

第二个测试:通过多轮对话生成费用报告。Agent 需要加载报销模板资产(assets/expense-report-template.md),并引导用户补充缺失信息。

预期行为:

  1. Agent 加载 skill → 获取费用类别和限额规则
  2. Agent 读取报销模板 → 按照模板格式生成报告
  3. Agent 识别缺失信息 → 主动询问用户补充细节
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("   📝 测试 2:多轮对话生成报销报告");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();

// 使用 AgentSession 支持多轮对话
AgentSession session = await agent.CreateSessionAsync();

// 第 1 轮:提供费用信息,要求生成报告草稿
string request = "我上周有 3 笔客户晚餐费用和一张 $1,200 的机票。请先返回一份报销报告草稿,并询问我缺失的细节信息。";

Console.WriteLine($"👤 用户: {request}");
Console.WriteLine();

// Agent 会:
// 1. 加载 expense-report skill(如果尚未加载)
// 2. 读取报销模板 assets/expense-report-template.md
// 3. 根据用户提供的信息填写模板
// 4. 识别缺失字段并主动询问
AgentResponse response2 = await agent.RunAsync(request, session);

Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine($"🤖 Agent: {response2.Text}");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

Console.WriteLine();
Console.WriteLine("🔍 观察要点:");
new {
    模板加载 = "Agent 通过 read_skill_resource 获取报销模板",
    智能分类 = "Agent 根据 Skill 中的限额表对费用进行分类",
    缺失信息 = "Agent 会询问缺失信息(如晚餐日期、具体金额、参与人等)",
    审批提示 = "$1,200 机票需要 Manager 审批(< $1,500)"
}.Display();

步骤 6:多轮跟进 — 补充缺失信息

根据 Agent 上一轮指出的缺失信息,我们补充细节,观察 Agent 如何更新报销草稿。

Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("   📝 测试 2(续):补充缺失信息");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();

// 提供详细信息,让 Agent 完善报告
string details = """
以下是详细信息:
- 客户晚餐 1:周一在 Starlight Restaurant,$280,4 人(我、来自 Marketing 的 Alice、来自 Contoso Corp 的 Bob Chen、来自 Contoso Corp 的 Lisa Wang)。业务目的:Q4 合作复盘。
- 客户晚餐 2:周三在 Golden Dragon,$195,3 人(我、来自 ABC Inc 的 Tom Li、来自 ABC Inc 的 Sarah Kim)。业务目的:新项目启动会。
- 客户晚餐 3:周五在 Café Milano,$150,2 人(我、来自 XYZ Ltd 的 David Liu)。业务目的:合同续签讨论。
- 机票:Delta Airlines,经济舱,JFK 往返 SFO,通过公司差旅平台预订。
所有收据均已附上。
"
"";

Console.WriteLine($"👤 用户: {details}");
Console.WriteLine();

// Agent 利用 session 中的对话历史,更新报告草稿
AgentResponse response3 = await agent.RunAsync(details, session);

Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine($"🤖 Agent: {response3.Text}");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

Console.WriteLine();
Console.WriteLine("🔍 观察要点:");
new {
    模板填充 = "Agent 使用报销模板格式填充所有字段",
    限额检查 = "Agent 根据 Skill 中的限额规则检查每笔费用",
    Dinner1 = "$280/4人=$70/人 → 未超过 $75/人限额 ✅ 但总额 >$200 需 Manager 审批",
    Flight = "$1,200 经济舱 → 需 Manager 审批(< $1,500,无需 VP)",
    审批总额 = "总额可能超过 $2,000 → 需要 VP 审批"
}.Display();

🔧 第五部分:进阶用法

5.1 自定义技能摘要提示词

FileAgentSkillsProviderOptions 允许你自定义系统提示词模板。使用 {0} 作为技能列表的占位符:

var skillsProvider = new FileAgentSkillsProvider(
    skillPath: "skills",
    options: new FileAgentSkillsProviderOptions
    {
        SkillsInstructionPrompt = """
        你是一名专业的企业咨询顾问。你具备以下专业技能:

        {0}

        当用户问题与某个技能相关时:
        1. 使用 load_skill 加载该技能的详细指令
        2. 严格按照指令中的规则回答
        3. 需要时使用 read_skill_resource 读取参考资料

        务必用中文回答所有问题。
        "
""
    }
);

5.2 多技能注册

一个 Agent 可以同时注册多个 Skills:

// 方式 1:通过父目录自动发现(推荐)
// skills/ 下的每个子目录如果包含 SKILL.md,就会被注册为一个 Skill
var provider = new FileAgentSkillsProvider("skills");
// 自动发现:expense-report, travel-policy, it-support ...

// 方式 2:指定多个路径
var provider = new FileAgentSkillsProvider([
    "skills/finance",     // 财务相关技能
    "skills/hr",          // 人力资源技能
    "skills/it-support"   // IT 支持技能
]);

💡 性能提示:渐进式披露确保了即使注册 20+ 个 Skills,初始 Token 开销也仅为 ~2000 tokens(每个 ~100 tokens 的名称+描述摘要)。Agent 会智能判断并仅加载相关的 Skill。

5.3 Skills 文件夹结构最佳实践

skills/
├── expense-report/          # ✅ 好的:清晰的领域边界
│   ├── SKILL.md
│   ├── references/
│   └── assets/
├── travel-policy/           # ✅ 好的:独立的政策领域
│   ├── SKILL.md
│   └── references/
├── code-review/             # ✅ 好的:操作指南
│   ├── SKILL.md
│   └── assets/
└── general-hr/              # ⚠️ 避免:范围过大
    ├── SKILL.md             # 应拆分为更细粒度的技能
    └── ...

命名规范:

  • ✅ 使用 kebab-caseexpense-reporttravel-policy
  • ✅ 名称简洁且具描述性
  • ❌ 避免特殊字符:~~expense_reportExpenseReport~~
  • ❌ 避免过长名称(最大 64 字符)

5.4 实战:创建第二个 Skill 并测试多技能 Agent

让我们创建一个 travel-policy 技能,然后测试 Agent 在同时拥有两个 Skills 时的智能路由能力。

#pragma warning disable MAAI001
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 创建第二个 Skill:travel-policy(差旅政策,已中文化)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var travelSkillPath = Path.Combine(skillsRootPath, "travel-policy");
Directory.CreateDirectory(travelSkillPath);

var travelSkillMd = """
---
name: travel-policy
description: 公司差旅预订与审批政策。适用于航班预订、酒店预订、差旅审批流程或差旅安全指引等相关问题。
---

# 差旅政策(Travel Policy)

## 预订规则

| 项目 | 政策 | 审批 |
|------|--------|----------|
| 国内航班 | 仅限经济舱 | <$800自动审批 |
| 国际航班 | 经济舱;6小时以上可选高端经济舱 | 均需经理审批 |
| 酒店 | 优先公司协议酒店 | ≤$250/晚自动审批 |
| 租车 | 紧凑/标准车型 | ≤3天自动审批 |
| 火车/高铁 | 标准座 | 自动审批 |

## 预订流程

1. 所有预订须通过公司差旅平台(TravelHub)完成
2. 国内提前14天,国际提前21天预订
3. 总费用超$2,000需经理预先审批
4. 预订后24小时内取消可全额退款

## 安全指引

- 所有国际行程需向差旅安全团队报备
- 超过5天的行程需购买差旅保险
- 随身携带公司应急联系方式卡
"
"";

File.WriteAllText(Path.Combine(travelSkillPath, "SKILL.md"), travelSkillMd);

Console.WriteLine("✅ travel-policy Skill 创建完成");
Console.WriteLine();

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 重新创建包含两个 Skills 的 Provider 和 Agent
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var multiSkillProvider = new FileAgentSkillsProvider(
    skillPath: Path.Combine(Directory.GetCurrentDirectory(), "skills")
);

AIAgent multiSkillAgent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
    Name = "MultiSkillAgent",
    ChatOptions = new()
    {
        Instructions = "You are a helpful corporate assistant. Answer in the same language as the user.",
    },
    AIContextProviders = [multiSkillProvider],
});

Console.WriteLine("✅ 多技能 Agent 创建成功");
Console.WriteLine();

new {
    Agent名称 = "MultiSkillAgent",
    已注册技能 = new[] { "expense-report(费用报销)""travel-policy(差旅政策)" },
    系统提示摘要开销 = "~200 tokens(2 × ~100 tokens/skill)"
}.Display();

测试多技能智能路由

分别提问差旅政策和费用报销问题,观察 Agent 如何智能选择加载正确的 Skill。

Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("   🔀 测试 3:多技能智能路由");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();

// 测试 A:差旅政策问题 → 应加载 travel-policy
string travelQuestion = "我需要预订一张从纽约到伦敦、为期两周项目的航班。我可以乘坐什么舱位?需要审批吗?";

Console.WriteLine("🔵 测试 A:差旅政策问题");
Console.WriteLine($"👤 用户: {travelQuestion}");
Console.WriteLine();

AgentResponse travelResponse = await multiSkillAgent.RunAsync(travelQuestion);
Console.WriteLine($"🤖 Agent: {travelResponse.Text}");
Console.WriteLine();

Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();

// 测试 B:费用报销问题 → 应加载 expense-report
string expenseQuestion = "我上周买了一个 $45/月的项目管理软件订阅,需要什么审批流程?";

Console.WriteLine("🔵 测试 B:费用报销问题");
Console.WriteLine($"👤 用户: {expenseQuestion}");
Console.WriteLine();

AgentResponse expenseResponse = await multiSkillAgent.RunAsync(expenseQuestion);
Console.WriteLine($"🤖 Agent: {expenseResponse.Text}");

Console.WriteLine();
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine();
Console.WriteLine("🔍 观察要点:");
new {
    智能路由 = "Agent 根据问题内容自动选择加载正确的 Skill",
    测试A = "差旅问题 → load_skill('travel-policy')",
    测试B = "报销问题 → load_skill('expense-report')",
    语言适配 = "Agent 会根据用户语言(中/英)自适应回复"
}.Display();

📊 总结

通过本节课,我们系统学习了 Agent Skills — 一种基于 AIContextProvider 的模块化领域知识管理机制。

核心要点

  1. Agent Skills 规范

    • 遵循开放规范,支持跨框架互操作
    • 核心思想是渐进式披露:摘要展示 → 加载 → 读取资源
  2. SKILL.md 文件结构

    • YAML Frontmatter:name + description(技能元数据)
    • Markdown Body:完整的领域指令(规则、流程、资源引用)
    • 资源文件:通过 Markdown 链接引用,按需读取
  3. FileAgentSkillsProvider

    • 继承 AIContextProvider,重写 ProvideAIContextAsync
    • 自动注册 load_skill 和 read_skill_resource 两个 AI 工具
    • 初始化时扫描目录、解析 SKILL.md、验证资源完整性
  4. 安全机制

    • 路径穿越防护 + 符号链接检查 + XML 转义 + 名称格式验证

实际价值

  • ✅ 可维护性:业务专家可以直接编辑 Markdown 文件,无需修改代码
  • ✅ Token 效率:渐进式披露将 Token 开销降低 90%+
  • ✅ 模块化:每个 Skill 独立维护,可即插即用
  • ✅ 安全性:内置路径穿越防护和输入验证

🏋️ 课后练习

  1. 创建自定义 Skill:为你的业务领域创建一个 Skill(如 IT 支持指南、编码规范等),包含至少一个 references/ 资源文件
  2. 中文化提示词:使用 FileAgentSkillsProviderOptions 自定义中文系统提示词模板
  3. 结合 Function Calling:创建一个同时具备 Skills(领域知识)和 Function Calling(操作能力)的 Agent

【声明】内容源于网络
0
0
dotNET跨平台
专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
内容 2302
粉丝 0
dotNET跨平台 专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
总阅读57.0k
粉丝0
内容2.3k