
2 快速开始整合多个来源Skill这里我们来做一个企业HR助手整合以下多个来源的Skill并实现角色管控Skill可见。全局技能库假设通过Remote HTTP API来获取它是所有员工通用的政策和流程本地技能库假设通过InMemory来获取它是各个员工自己定制的任务技能用户角色技能假设通过自定义Source来获取根据当前用户角色做动态过滤在这个案例中我们想要实现只有 全局通用技能 角色专属技能 对Agent可见且本地的同名称技能可以覆盖远程的全局技能。本文案例使用的模型为Qwen3.5-35B-A3B准备工作在开始之前我们创建了一个控制台应用并安装了以下NuGet包PackageReference IncludeMicrosoft.Agents.AI.OpenAI Version1.1.0 /假设我们的企业员工角色定义有三种员工、经理 和 HR管理员public enum EmployeeRole { Employee, Manager, HRAdmin }为了方便实现Skill的角色过滤我们实现一个方法来判断public static class UserRoleHelper { public static bool IsSkillVisibleTo(AgentSkill skill, EmployeeRole role) { var name skill.Frontmatter.Name; // 管理技能仅经理和HR可见 if (name.StartsWith(manager-) role EmployeeRole.Employee) return false; // HR管理技能仅HR可见 if (name.StartsWith(hr-admin-) role ! EmployeeRole.HRAdmin) return false; return true; } }可以看到我们这里针对管理技能仅能经理和HR可见而HR相关技能则仅能HR可见。实现远程SkillSource这里我们定义一个企业的远程SkillSource模拟从远程API拉取统一定义的技能。在实际项目中通常会使用HttpClient来访问一个注册中心来实现。public sealed class SimulatedRemoteApiSkillsSource : AgentSkillsSource { private readonly string _apiEndpoint; public SimulatedRemoteApiSkillsSource(string apiEndpoint) { _apiEndpoint apiEndpoint; } public override async TaskIListAgentSkill GetSkillsAsync(CancellationToken cancellationToken default) { Console.WriteLine($ [RemoteApiSource] 从 {_apiEndpoint} 拉取技能列表...); await Task.Delay(500, cancellationToken); // 模拟网络延迟 var entries GetMockGlobalSkills(); var skills new ListAgentSkill(); foreach (var entry in entries) { try { var skill new AgentInlineSkill(entry.Name, entry.Description, entry.Instructions); skills.Add(skill); } catch (ArgumentException ex) { Console.WriteLine($⚠️ [RemoteApiSource] 跳过非法技能 {entry.Name}: {ex.Message[..Math.Min(60, ex.Message.Length)]}); } } Console.WriteLine($✅ [RemoteApiSource] 已成功加载 {skills.Count} 个远程技能); return skills; } private static IListSkillApiEntry GetMockGlobalSkills() { return new ListSkillApiEntry { new(expense-report, 全局v1企业费用报销政策, 全局版报销规则...), new(hr-onboarding, 全局新员工入职流程, 入职材料清单...), new(leave-policy, 全局请假制度和申请流程, 年假/病假/事假规则...), new(manager-review, 全局绩效评估指南, 季度评估流程...), new(hr-admin-audit, 全局HR 审计和合规, 合规审查清单...), }; } }这里SkillApiEntry模型的定义如下public sealed record SkillApiEntry( string Name, string Description, string Instructions, string[]? Tags null);实现远程Skills的缓存化远程Skills具有时效性需要定期更新来同步因此弄一个带TTLTime-To-Live缓存的装饰器它可以强制Agent客户端定期刷新远程Skills来保持同步。public sealed class CachingSkillsSource : AgentSkillsSource { private readonly AgentSkillsSource _innerSource; private readonly TimeSpan _ttl; private IListAgentSkill? _cache; private DateTime _cacheExpiresAt DateTime.MinValue; private readonly SemaphoreSlim _lock new(1, 1); public CachingSkillsSource(AgentSkillsSource innerSource, TimeSpan ttl) { _innerSource innerSource; _ttl ttl; } public override async TaskIListAgentSkill GetSkillsAsync(CancellationToken cancellationToken default) { if (_cache ! null DateTime.UtcNow _cacheExpiresAt) { Console.WriteLine($⚡ [CachingSource] 命中缓存过期时间: {_cacheExpiresAt.ToLocalTime():HH:mm:ss}); return _cache; } await _lock.WaitAsync(cancellationToken); try { // 双重检查锁避免并发刷新 if (_cache ! null DateTime.UtcNow _cacheExpiresAt) return _cache; Console.WriteLine( [CachingSource] 缓存未命中从内层 Source 刷新...); _cache await _innerSource.GetSkillsAsync(cancellationToken); _cacheExpiresAt DateTime.UtcNow.Add(_ttl); Console.WriteLine($✅ [CachingSource] 缓存已更新{_cache.Count} 个技能有效至 {_cacheExpiresAt.ToLocalTime():HH:mm:ss}); return _cache; } finally { _lock.Release(); } } public void InvalidateCache() { _cache null; _cacheExpiresAt DateTime.MinValue; } public bool IsCacheValid _cache ! null DateTime.UtcNow _cacheExpiresAt; }实现本地Skills这里我们定义一些本地Skills通过Inline Skill的方式增加一个本地任务技能 和 覆盖一个同名的远程仅能。public class SimulatedLocalApiSkillsFactory { public static async TaskIListAgentInlineSkill GetSkillsAsync(CancellationToken cancellationToken default) { Console.WriteLine( [LocalApiFactory] 正在从本地获取技能列表...); var localSkills new ListAgentInlineSkill { // 覆盖全局 expense-report使用 Contoso 定制规则 new AgentInlineSkill(expense-report, Contoso定制v2企业费用报销政策, # Contoso 定制报销规则2025版 - 差旅费上限提升至 8000 元/次 - 新增远程协作设备补贴类目≤3000元免审批 - 年末报销截止日期12月25日 ), // 新增Contoso 特有的技能 new AgentInlineSkill(contoso-benefits, Contoso 员工福利计划详情, 福利弹性办公、年度体检、学习补贴...), }; Console.WriteLine($✅ [LocalApiFactory] 已成功加载 {localSkills.Count} 个本地技能); return localSkills; } }Agent客户端加载Skills这里我们在Agent客户端加载本地 和 远程技能同时对远程技能做1小时的缓存代理。// ── Source 1本地定制技能覆盖全局版注册顺序靠前 → 优先── var localCustomSkills await SimulatedLocalApiSkillsFactory.GetSkillsAsync(); // ── Source 2模拟全局技能库Remote API通用政策── var globalSource new SimulatedRemoteApiSkillsSource(https://global-skills.contoso.com/api); // ── Source 3带缓存的远程 Source生产环境推荐── var cachedGlobalSource new CachingSkillsSource(globalSource, TimeSpan.FromMinutes(60));构建针对角色的SkillsProvider工厂这里我们用MAF提供的Builder模式来实现一个面向指定角色的SkillsProvider工厂方法AgentSkillsProvider BuildProviderForRole(EmployeeRole role) { Console.WriteLine($\n 开始构建 {role} 角色的 Provider...); return new AgentSkillsProviderBuilder() // 本地定制优先先注册 → first-wins .UseSkills(localCustomSkills) // 全局技能库带缓存 .UseSource(cachedGlobalSource) // 角色感知过滤 .UseFilter(s UserRoleHelper.IsSkillVisibleTo(s, role)) // 自定义 Prompt企业内部语气 .UsePromptTemplate( 你是 Contoso 集团的企业服务助手。 ## 你掌握的企业知识库 {skills} ## 工作原则 遇到政策性问题**先加载该技能的详细指引**再作答。请确保所有建议符合 Contoso 最新官方规定。 {resource_instructions} {script_instructions} ) .Build(); }测试验证各个角色的技能可见范围这里通过下面的测试代码来创建三个角色使用的SkllsProvider通过获取其Skills可见范围来进行验证// 验证三个角色看到的技能集合 foreach (var role in Enum.GetValuesEmployeeRole()) { var provider BuildProviderForRole(role); var srcField typeof(AgentSkillsProvider).GetField(_source, BindingFlags.Instance | BindingFlags.NonPublic); var src (AgentSkillsSource?)srcField?.GetValue(provider); var skills src is null ? new ListAgentSkill() : (await src.GetSkillsAsync()).ToList(); Console.WriteLine(\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━); Console.WriteLine($ [{role}] 可见技能{skills.Count} 个); foreach (var s in skills) { string origin s.Frontmatter.Description.StartsWith(Contoso) ? 本地定制 : s.Frontmatter.Description.StartsWith(全局) ? 全局库 : 其他; Console.WriteLine($ • {s.Frontmatter.Name} {origin}); } }