Agent Framework 中为 Agent Skill 接入依赖注入 DI
在前面的文章中我们介绍过 FileBased、CodeBased 和 ClassBased 等不同的 Skill 实现方式也演示了如何通过 AgentSkillsProvider 或 AgentSkillsProviderBuilder 将多个 Skill 组合起来让一个 Agent 同时具备多种能力。在实际项目中Skill 通常不只是简单的本地函数它往往需要依赖应用中的各种服务例如数据库访问HTTP API 调用缓存或配置服务如果每个 Skill 都自行创建和管理这些依赖不仅会导致代码重复还会增加测试和维护的成本。因此在 Agent Framework 中可以将 Skill 与 .NET 的依赖注入机制结合使用。下面通过一个单位换算的例子来看一下如何在 Skill 中使用 DI。1. 核心设计思想在 Agent 架构中用户 → Agent → Skill → DI → Service → 业务逻辑分层层级职责Agent理解用户意图Skill能力入口AI 可调用Service业务实现DI生命周期与依赖管理Skill 不承载业务逻辑只负责能力编排。2. 示例场景构建一个“单位换算 Agent”支持距离换算英里 ⇄ 千米重量换算磅 ⇄ 千克实现方式Skill类型功能distance-converterInline距离weight-converterClass重量两者共用同一个业务服务ConversionService2. 创建项目并安装依赖包首先创建一个控制台项目然后安装相关依赖包dotnet add package Azure.AI.OpenAI dotnet add package Azure.Identity dotnet add package Microsoft.Agents.AI.OpenAI dotnet add package Microsoft.Extensions.DependencyInjection3. 环境准备安装依赖dotnet add package Azure.AI.OpenAI --version 2.9.0-beta.1 dotnet add package Azure.Identity dotnet add package Microsoft.Agents.AI.OpenAI dotnet add package Microsoft.Extensions.DependencyInjection版本说明GetResponsesClient()需要Azure.AI.OpenAI 2.9.0-beta.1否则可能出现404或 API 不兼容问题。4. 配置 Azure OpenAI示例中通过环境变量读取 Azure OpenAI 的终结点和模型部署名称string endpoint Environment.GetEnvironmentVariable(AZURE_OPENAI_ENDPOINT) ?? throw new InvalidOperationException(AZURE_OPENAI_ENDPOINT is not set.); string deploymentName Environment.GetEnvironmentVariable(AZURE_OPENAI_DEPLOYMENT_NAME) ?? gpt-5.4-mini;这里需要提前配置环境变量在Windows中可以使用setx AZURE_OPENAI_ENDPOINT https://你的资源名.openai.azure.com/ setx AZURE_OPENAI_DEPLOYMENT_NAME 你的模型部署名在 Linux 或 macOS 中可以使用export AZURE_OPENAI_ENDPOINThttps://你的资源名.openai.azure.com/ export AZURE_OPENAI_DEPLOYMENT_NAME你的模型部署名5. 创建 DI 容器在.NET 中依赖注入通常从 ServiceCollection 开始。示例中注册了一个 ConversionServiceServiceCollection services new(); services.AddSingletonConversionService(); IServiceProvider serviceProvider services.BuildServiceProvider();6. 定义业务服务 ConversionServiceConversionService 是一个普通的 C# 服务类用于提供单位换算能力。它主要包含三个方法internal sealedclassConversionService { public string GetDistanceTable() # 距离转换 公式**结果 值 × 系数** | 从 | 到 | 系数 | |-------------|-------------|----------| | 英里 | 千米 | 1.60934 | | 千米 | 英里 | 0.621371 | ; public string GetWeightTable() # 重量转换 公式**结果 值 × 系数** | 从 | 到 | 系数 | |-------------|-------------|----------| | 磅 | 千克 | 0.453592 | | 千克 | 磅 | 2.20462 | ; public string Convert(double value, double factor) { double result Math.Round(value * factor, 4); return JsonSerializer.Serialize(new { value, factor, result }); } }这里的设计思路很清晰GetDistanceTable()提供距离换算表。GetWeightTable()提供重量换算表。Convert()根据传入的值和换算系数返回计算结果。7. 使用 AgentInlineSkill 实现距离换算第一个 Skill 使用 AgentInlineSkill 实现。它适合比较简单、轻量的场景直接在代码中定义 Skill 的资源和脚本。var distanceSkill new AgentInlineSkill( name: distance-converter, description: 在距离单位之间转换。当要求将英里转换为千米或将千米转换为英里时使用。, instructions: 当用户要求在距离单位英里和千米之间转换时请使用此技能。 1. 查看 distance-table 资源找到所请求转换的系数。 2. 使用 convert 脚本传入值和表中的系数。 ) .AddResource(distance-table, (IServiceProvider serviceProvider) { var service serviceProvider.GetRequiredServiceConversionService(); return service.GetDistanceTable(); }) .AddScript(convert, (double value, double factor, IServiceProvider serviceProvider) { var service serviceProvider.GetRequiredServiceConversionService(); return service.Convert(value, factor); });这里有一个关键点资源和脚本方法中都声明了IServiceProvider参数。例如.AddResource(distance-table, (IServiceProvider serviceProvider) { var service serviceProvider.GetRequiredServiceConversionService(); return service.GetDistanceTable(); })Agent Framework 会自动注入IServiceProvider。然后我们就可以通过var service serviceProvider.GetRequiredServiceConversionService();距离换算的执行过程大致如下用户提出距离换算问题。Agent 判断需要使用distance-converter。Skill 读取distance-table资源。Agent 根据换算表选择正确的系数。调用convert脚本完成计算。返回最终结果。8. 使用 AgentClassSkill 实现重量换算第二个 Skill 使用基于类的方式实现也就是 AgentClassSkill。 这种方式更适合结构稍复杂的 Skill因为可以把资源、脚本、说明信息都封装在一个类中。internal sealedclassWeightConverterSkill : AgentClassSkillWeightConverterSkill { publicoverride AgentSkillFrontmatter Frontmatter { get; } new( weight-converter, 在重量单位之间转换。当要求将磅转换为千克或将千克转换为磅时使用。); protectedoverridestring Instructions 当用户要求在重量单位磅和千克之间转换时请使用此技能。 1. 查看 weight-table 资源找到所请求转换的系数。 2. 使用 convert 脚本传入值和表中的系数。 3. 使用两个单位清晰呈现结果。 ; [AgentSkillResource(weight-table)] [Description(重量转换乘法系数的查找表。)] private static string GetWeightTable(IServiceProvider serviceProvider) { var service serviceProvider.GetRequiredServiceConversionService(); return service.GetWeightTable(); } [AgentSkillScript(convert)] [Description(将值乘以转换系数并以 JSON 形式返回结果。)] private static string Convert(double value, double factor, IServiceProvider serviceProvider) { var service serviceProvider.GetRequiredServiceConversionService(); return service.Convert(value, factor); } }基于ClassBased的 Skill里面属性和方法的作用我们在前面的文章中已经介绍过了这里不再赘述。在类形式的 Skill 中同样可以通过IServiceProvider使用 DI。例如资源方法[AgentSkillResource(weight-table)] [Description(重量转换乘法系数的查找表。)] private static string GetWeightTable(IServiceProvider serviceProvider) { var service serviceProvider.GetRequiredServiceConversionService(); return service.GetWeightTable(); }脚本方法[AgentSkillScript(convert)] [Description(将值乘以转换系数并以 JSON 形式返回结果。)] private static string Convert(double value, double factor, IServiceProvider serviceProvider) { var service serviceProvider.GetRequiredServiceConversionService(); return service.Convert(value, factor); }只要方法参数中声明了IServiceProvider, Agent Framework 就会在执行时自动注入它。这样ClassBased Skill 也可以和普通 ASP.NET Core 或 Worker Service 一样通过 DI 使用应用中的业务服务。9. 注册多个 Skill定义好距离 Skill 和重量 Skill 后需要把它们注册到统一的技能提供者中。var weightSkill new WeightConverterSkill(); var skillsProvider new AgentSkillsProvider(distanceSkill, weightSkill);它们分别负责不同的领域Skill实现方式能力distance-converterAgentInlineSkill英里和千米转换weight-converterAgentClassSkill磅和千克转换这意味着同一个 Agent 可以根据用户问题自动选择合适的 Skill。如果用户问26.2 英里是多少千米Agent 会倾向于使用distance-converter。如果用户问75 千克是多少磅Agent 会倾向于使用weight-converter。如果用户同时问两个问题Agent 也可以分别调用不同的 Skill 来完成任务。10. 创建 Agent 并注入AgentSkillsProvider和IServiceProvider接下来创建 AgentAIAgent agent new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) .GetResponsesClient() .AsAIAgent( options: new ChatClientAgentOptions { Name UnitConverterAgent, ChatOptions new() { Instructions 你是一个可以转换单位的实用助手。, }, AIContextProviders [skillsProvider], }, model: deploymentName, services: serviceProvider);这里有两个地方非常重要。第一个是AIContextProviders [skillsProvider]这表示把前面定义的 Skill 提供给 Agent。第二个是services: serviceProvider这表示把 DI 容器传递给 Agent。 正因为传入了serviceProvider所以 Skill 在执行资源和脚本时才能自动获得IServiceProvider然后解析出ConversionService。 如果没有传入这个服务提供者Skill 中依赖 DI 的代码就无法正常解析服务。11. 运行示例最后我们向 Agent 提出一个同时包含距离和重量换算的问题AgentResponse response await agent.RunAsync( 一场马拉松26.2 英里是多少千米75 千克是多少磅); Console.WriteLine($智能体{response.Text});根据示例中的转换系数计算结果如下为12. 为什么要在 Skill 中使用 DI把 DI 引入 Agent Skill最大的好处是让 Skill 不再直接依赖具体实现。 比如在当前示例中Skill 并不负责保存换算表也不直接维护业务规则而是通过ConversionService来完成具体业务。这样做有几个好处。1. 业务逻辑可以复用ConversionService 不只可以被 Agent Skill 使用也可以被普通 API、后台任务、命令行工具或测试代码使用。 Skill 只是业务能力的一种入口。2. 更容易测试如果未来要测试 Skill可以替换掉真实的服务实现注入 Mock 服务或测试服务。 例如可以把ConversionService抽象成接口IConversionService然后在测试中注入假的实现。3. 更容易扩展如果以后换算规则不再写死在代码中而是来自数据库只需要修改服务层即可。 Skill 的调用方式可以保持不变。4. 更符合 .NET 应用架构在 ASP.NET Core、Worker Service 和现代 .NET 应用中DI 是非常核心的基础设施。 Agent Skill 支持 DI意味着它可以自然融入现有 .NET 应用架构。13. Agent Skill 和传统服务的关系可以把 Agent Skill 理解成一层“AI 可调用的能力入口”。 传统代码中我们可能这样调用服务var result conversionService.Convert(value, factor);而在 Agent Framework 中用户通过自然语言提出问题 26.2 英里是多少千米Agent 会根据问题自动选择 Skill然后 Skill 再调用服务var service serviceProvider.GetRequiredServiceConversionService(); return service.Convert(value, factor);所以它们的关系可以理解为用户自然语言 ↓ Agent ↓ Skill ↓ DI 服务 ↓ 业务逻辑Skill 并不是替代业务服务而是把业务服务包装成 Agent 可以理解和调用的能力。总结本文演示了如何在 Agent Framework 中结合依赖注入使用 Agent Skill。 示例中包含两种 SkillAgentInlineSkill用于距离换算。AgentClassSkill用于重量换算。这两个 Skill 都没有直接维护复杂业务逻辑而是通过 IServiceProvider 从 DI 容器中解析同一个 ConversionService。通过这种方式Agent Skill 可以像普通 .NET 组件一样使用应用中的服务。 这让 Skill 的设计更加清晰也让 Agent 更容易接入真实业务系统。 随着 Skill 接入的服务越来越多Agent 的能力也会不断增强。从简单的单位换算到数据库查询、HTTP API 调用、企业系统集成最终都可以通过同样的方式扩展出来。源代码地址https://github.com/bingbing-gui/dotnet-platform/tree/master/src/09-AI-Agent/Agent-Framework/32-AgentSkill-Integration-DI