资讯动态

.NET AI Agent实战:用Agent Skills为智能体装上执行能力

发布时间:2026/10/20 11:41:44 来源:尧图企业网站定制
做.NET的人现在聊AI绕不开一个痛点拿大模型接口写个聊天Demo很容易但要让Agent真正“干活”调个数据库、算笔账、对接个内部系统就得在Prompt里堆工具描述、自己维护Function Calling的schema工具一多就乱成一团。最近我在用Microsoft Agent Framework做 Agent 项目时把Agent Skills这一整套机制从头到尾踩了一遍发现它把“让Agent具备执行能力”这件事做得很顺手。这篇就用一步步实操的方式讲清楚 Skills 是什么、为什么需要它、怎么在十分钟内把自定义技能挂到 Agent 上以及怎么从 MCP 服务把外部能力导入成自己的技能。如果你正打算用 .NET 构建AI应用或者已经用 Semantic Kernel / Aspire 跑通了基础对话想让 Agent 摆脱“只会聊天”的尴尬这篇文章能给你一份直接照着抄的配置和代码。读完你不仅能快速集成 Skills还能避开几个我刚上手时踩过的坑。1. 内容整体设计与思路拆解给Agent“装上双手”1.1 Agent Skills的本质从“会说话”到“会做事”先讲清楚一个概念Agent Skills是 Microsoft Agent Framework 里用来封装和注册工具能力的标准单位。Agent 本身是一个能理解用户意图、进行多轮对话的实体但只有对话能力它本质上还是一个聊天机器人。要让 Agent 真正完成复杂任务它需要调用外部能力——查数据、跑代码、调接口。Skills 就是这套框架里承担这个职责的模块。我用一个生活化的类比你把 Agent 理解成一个新入职的助理。助理脑子很聪明模型能力强但刚到岗时没技能不会看报表、不会发邮件、不会写代码。Skills 就像你逐个给助理发“上岗证书”每拿到一个技能证书助理就能独立处理一类任务。框架里的 Skills 集合就是这个助理的技能清单模型在对话过程中会根据任务自动挑出合适的技能来调用。这套设计最核心的价值是把“模型的理解能力”和“工具的执行能力”彻底解耦。你不需要为了加一个工具去改 Prompt不需要每次对话都手动拼接 Function Calling 的参数。你唯一要做的是把技能注册到 Agent 上剩下的触发时机、参数解析、结果返回都交给框架和模型协作完成。1.2 为什么用“技能化”而不是把工具全塞进Prompt早年间做AI应用大家习惯把所有工具函数直接写进 System Prompt 里让模型调用。小项目里工具三五个这种写法确实简单粗暴还能跑。但一旦工具数量超过十个问题就接踵而至Prompt 长度膨胀模型选择工具的准确率肉眼可见地下降每加一个工具就要动一遍 Prompt迭代成本高得离谱。Skills 方案的关键优势在于把工具的“元信息”名称、描述、入参出参和“执行逻辑”打包成一个独立单元。注册到 Agent 之后模型根据用户请求动态决定要不要调用技能、调用哪个。它天然和模型上下文分离——技能再多也不是每轮对话都把所有技能细节塞进上下文而是需要时才加载相关信息。这种设计在企业级 Agent 项目里特别有意义因为企业里的技能数量和复杂度远超个人 Demo。1.3 两种技能来源本地定制技能与MCP外部技能Microsoft Agent Framework 默认给了两条引入技能的路径可以混合使用。第一条是本地定制技能。继承 AgentSkill 基类不同预览版本命名可能略有差异重写执行逻辑适合封装项目内部的业务能力比如查订单、算运费、做数据校验。这种方式灵活度最高技能内部可以调用任何 .NET 代码。第二条是从 MCP 服务器加载技能。MCPModel Context Protocol是一个把远程工具服务标准化的协议你的 Agent 可以连上某个 MCP Server把服务器上暴露的工具自动导入为本地技能。这就像给浏览器装插件——装上就能用卸载也干净不污染核心逻辑。从职责划分的角度说这套框架把“技能发现”和“技能执行”分得很清楚。你不需要知道技能内部怎么实现的只需要知道它有名字、有描述、有入参出参Agent 会自动决定什么时候调用。我在企业项目里推荐用这个模型原因就是职责边界清晰不同团队各自维护自己的技能模块互不干扰集成成本低。顺带提一句Agent Skills 的相关 API 目前还在迭代不同预览版可能命名略有差异。我这里写的是当前预览版能跑通的写法建议你以自己项目实际引用的 NuGet 包版本为准重点掌握设计思路和步骤不用死记 API 名称。2. 环境准备把Agent基础工程跑起来2.1 依赖包与运行环境我本机用的是 .NET 9实际上 .NET 8 也能跑关键是把依赖包装齐。需要引入的项目依赖大致如下Microsoft.Agent.FrameworkAgent 框架核心包Microsoft.Agent.Framework.Skills技能相关的扩展包Microsoft.SemanticKernelAgent 框架底层依赖的语义内核Microsoft.Extensions.AIAI 客户端统一抽象Microsoft.Extensions.AI.AzureOpenAIAzure OpenAI 客户端实现如果还要接 MCP额外装这两个Microsoft.Agent.Framework.Skills.MCPMCP 技能接入扩展ModelContextProtocolMCP 协议客户端每个预览版的包名和依赖关系会调整最稳妥的装法是在 NuGet 里搜关键词“Agent Framework”看当前版本包含哪些程序集再按需引入。2.2 接入模型服务ChatClient的配置Agent 要能对话必须先绑定一个模型客户端。Microsoft Agent Framework 沿用了Microsoft.Extensions.AI的抽象也就是IChatClient接口。不管底层是 Azure OpenAI、OpenAI 官方接口还是本地模型只要实现了IChatClient都能直接接入。我以 Azure OpenAI 为例写一下依赖注入配置using Azure.AI.OpenAI; using Azure.Identity; using Microsoft.Extensions.AI; var builder WebApplication.CreateBuilder(args); // 从配置读取连接信息 var endpoint builder.Configuration[AZURE_OPENAI_ENDPOINT]; var deploymentName builder.Configuration[AZURE_OPENAI_DEPLOYMENT_NAME]; builder.Services.AddSingletonIChatClient(_ new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) .AsChatClient(deploymentName)); var app builder.Build();这段代码的核心两步AzureOpenAIClient负责和 Azure OpenAI 服务建立连接AsChatClient扩展方法把它适配成统一的IChatClient。之后创建 Agent 时只需要依赖IChatClient这个抽象完全不用关心底层是哪个模型服务商。如果你的密钥走环境变量也可以换new ApiKeyCredential(密钥)看团队的凭据管理规范来定。用DefaultAzureCredential的好处是支持托管身份和本地开发凭据链部署到云上时不用改代码。2.3 创建基础ChatAgent并绑定技能集合有了IChatClient接下来创建 Agent。ChatAgent是框架内置的标准聊天代理类型创建时需要传一个 agent 名称和 chatClientusing Microsoft.Agent.Framework; using Microsoft.Agent.Framework.Skills; var agent new ChatAgent(appAgent, chatService); agent.Description 你是一个企业智能助手可以回答用户问题并根据需要调用工具技能。;Description这段文本会被模型读取影响 Agent 后续的行为风格和技能选择倾向建议一开始就写清楚。创建之后最关键的一步是把技能挂到 Agent 上。Agent 暴露了一个Skills集合所有技能都往里面 Addagent.Skills.Add(new CodeInterpreterSkill());这里CodeInterpreterSkill是框架自带的一个通用技能——代码解释器。它能让 Agent 真正执行 C# 或 Python 代码处理数值计算、数据分析等任务。对很多场景来说这算是开箱即用的第一个技能相当于给 Agent 装了个“计算器”。注册完之后Agent 就已经具备基础对话能力和一项执行能力了。接下来真正的重头戏是写出一个完全属于自己的自定义技能这正是下一节的内容。3. 核心实操一步步集成Agent Skills3.1 第一步编写自定义技能类自定义技能的写法非常规范。你需要继承AgentSkill基类在构造函数里定义技能的名称和描述然后重写ExecuteAsync方法实现具体逻辑。我写一个具体的例子汇率换算技能。Agent 在对话中遇到“100美元等于多少人民币”这类问题时模型会判断这个任务适合用汇率换算技能然后触发执行。using Microsoft.Agent.Framework.Skills; public sealed class CurrencyExchangeSkill : AgentSkill { public CurrencyExchangeSkill() : base(CurrencyExchange) { Description 当用户需要把一种货币金额换算成另一种货币时使用例如100美元等于多少人民币。支持USD、CNY、EUR等币种。; } protected override async Taskstring ExecuteAsync( SkillContext skillContext, string userInput, CancellationToken cancellationToken) { // 这里简化处理实际项目中建议调用实时汇率API或者从配置中心读取 var rates new Dictionarystring, decimal { [USD_CNY] 7.24m, [EUR_CNY] 7.85m, [CNY_USD] 0.138m }; // 从 userInput 中解析金额和币种 var amount ParseAmount(userInput); var from ParseCurrency(userInput, from); var to ParseCurrency(userInput, to); var rateKey ${from}_{to}; if (!rates.TryGetValue(rateKey, out var rate)) { return $暂不支持 {from} 到 {to} 的汇率换算; } var result amount * rate; return ${amount} {from} {result:F2} {to}; } private static decimal ParseAmount(string input) { var match System.Text.RegularExpressions.Regex.Match(input, (\d(\.\d)?)); return match.Success ? decimal.Parse(match.Groups[1].Value) : 0m; } private static string ParseCurrency(string input, string direction) { // 简化版通过关键字匹配币种 foreach (var currency in new[] { USD, CNY, EUR, 美元, 人民币, 欧元 }) { if (input.Contains(currency, StringComparison.OrdinalIgnoreCase)) { return currency switch { 美元 USD, 人民币 CNY, 欧元 EUR, _ currency }; } } return USD; } }注意几个细节。第一构造函数里传给基类的字符串就是技能的唯一标识。模型在决策时就是靠这个名字加Description来决定是否调用所以名字要简短清晰描述要写得具体最好包含“什么时候用”的触发条件。第二ExecuteAsync方法接收一个SkillContext里面封装了当前对话的上下文信息包括历史消息、用户输入等可以直接读取。第三返回类型是Taskstring。返回的结果会作为工具调用的输出送回给模型模型再结合这个结果生成最终回复。所以返回值最好是人类可读的文本而不是裸的 JSON。实际项目中我更推荐定义一个强类型的输入参数结构让模型按结构化参数传入而不是像示例这样从自然语言里硬解析。不过示例的方式对快速验证技能链路已经足够。3.2 第二步把自定义技能注册到Agent注册技能就一行代码agent.Skills.Add(new CurrencyExchangeSkill());但这一行背后有一个值得注意的机制。Add操作执行时框架会把技能的元信息——名字、描述、参数定义——登记到 Agent 的技能注册表。当用户发起对话时模型会先看到所有技能的名字和描述然后根据任务相关性决定要不要调用某个技能。这里有个实战经验注册多个技能后模型的每一次对话都面临“要不要用技能、用哪个技能”的决策。技能名称如果太相似模型很可能选错。比如你注册一个“汇率查询”和一个“汇率换算”模型大概率会混淆。所以命名要让技能边界清晰一个技能只完成一件职责明确的事。3.3 第三步从MCP服务器加载外部技能除了本地自定义技能更高效的能力扩展方式是从 MCP 服务器直接导入技能。现在很多平台提供了 MCP Server数据库查询服务、在线搜索服务、企业内部系统等都可以通过 MCP 协议暴露能力。Agent 框架提供了一个非常便捷的创建入口using Microsoft.Agent.Framework.Skills.MCP; using ModelContextProtocol; // 创建一个MCP客户端连接 var mcpServer await McpServer.CreateAsync( new() { Url http://localhost:8080/mcp }); // 从MCP服务器创建技能并注册到Agent var skillFromMCP await agent.Skills.CreateFromMCPAsync(mcpServer); agent.Skills.Add(skillFromMCP);CreateFromMCPAsync方法会去 MCP 服务器上拉取所有注册的工具列表把每一个工具封装成 Agent 能识别的技能底层用的是 MCP 协议标准的initialize和tools/list接口所以跨平台、跨语言都通用。这种方式最大的价值在于你的 Agent 不需要为每个新工具手写适配代码。只要服务端按 MCP 标准暴露工具Agent 就能无缝接入。类比一下就像你的电脑装上了一个“万能驱动”插上任何符合标准的设备都能直接识别。实际使用中有个注意点MCP 导入过来的技能其描述信息由服务端提供质量参差不齐。如果某些技能描述写得太含糊Agent 可能不会在正确时机调用。这种情况建议回服务端修正描述或者在导入后对技能做二次加工把描述改写清晰。3.4 第四步启动服务并验证技能调用效果技能注册完成后整个 Agent 已经可以对外服务了。我用一个 Web API 验证一下app.MapPost(/chat, async (ChatTurn[] chatTurns) { var response await agent.GetNewResponseAsync(chatTurns); return Results.Ok(response); }); app.Run();这里ChatTurn是 Agent 框架定义的消息结构和通用 ChatMessage 类型基本一致。发起一次对话请求后Agent 内部会经历这样的流程模型读取用户消息结合当前注册的技能清单做决策如果模型判定需要调用某个技能框架自动执行技能逻辑技能返回结果模型综合结果生成最终回复返回给调用方。我用上面的汇率技能做了一次实测。对话内容是“帮我算一下500美元能换多少人民币”。模型识别到这是汇率换算场景自动调用了CurrencyExchange技能最终返回“500 USD 3620.00 CNY”。整个调用过程用户完全无感知他只是在和一个智能助手对话不需要自己指定工具。这个“无感调用”的特性非常关键。Agent 框架把工具选择的决策权交给模型和系统这是体验上和传统机器人对话框最大的区别。4. 常见问题与排查技巧实录4.1 技能“没被调用”怎么办先查描述再查命名最后查注册时机我踩过不少次这个坑。技能注册好了代码没报错但 Agent 就是不调用技能回答全靠模型硬答。排查思路一般分三步。第一步检查技能的Description。模型决定是否调用技能主要依据就是描述文本。如果描述写得太宽泛比如就“汇率计算”四个字模型很难判断这个技能和当前问题有什么关系。建议写成触发条件清晰的完整句子例如“当用户需要把一种货币换算成另一种货币时使用支持USD/CNY/EUR等币种”。这样模型能准确匹配用户意图。第二步检查技能名称是否和其他技能冲突。如果两个技能名字前缀相同或者功能相近模型容易混淆。比如“GetWeather”和“GetWeatherForecast”同时存在时模型可能选错。第三步检查注册时机。确认技能是在 Agent 创建之后、首次对话之前 Add 进 Skills 集合的。如果 Agent 已经在处理对话了中途注册技能也未必能让当前会话立即感知。4.2 技能执行报错但Agent没报错返回的是文本而不是异常有时候技能内部抛了异常但框架层并没有向上传播。这是因为框架在技能执行环节做了容错处理——执行失败时会把错误信息包装成文本返回给模型由模型决定怎么跟用户解释而不是直接中断对话。所以排查技能内部问题时建议在技能代码里加好日志输出把关键变量和执行路径打出来。比如Console.WriteLine($[Skill] CurrencyExchange called, amount{amount}, from{from}, to{to});开发阶段这个日志特别有用。你可以在服务端日志里看到技能的实际输入参数判断到底是模型传参错误还是执行逻辑有 bug。4.3 多技能之间的选择冲突描述里写清边界一个 Agent 往往注册了多个技能模型面对技能列表时怎么选框架没有严格的优先级配置主要靠描述引导。我的经验是描述里把“适用范围”和“典型问法”写清楚模型就能根据语义特征做区分。举个例子我注册过“订单查询”和“物流查询”两个技能。一开始描述都写“查询订单信息”模型经常选错。后来把物流查询的描述改成“当用户询问包裹当前所在位置、配送进度、运单号时使用”模型的选择准确率明显提升。这是很值得记住的一点技能描述本质上是你写给模型看的“使用说明书”不是给自己写的注释。描述质量直接决定技能在复杂对话场景下的命中率。4.4 MCP技能导入失败先查tools/list接口再查网络从 MCP 服务器导入技能时如果导入后技能列表为空先检查服务器端是否把工具正确暴露了。MCP 协议有tools/list接口可以直接用调试工具比如命令行 curl请求一下这个接口确认返回的工具列表非空。其次检查网络连通性确认 Agent 服务能访问到 MCP 服务器地址。如果是跨网络的 MCP 服务还要检查防火墙和身份认证配置。4.5 技能执行的“黑盒”问题善用日志和跟踪最后说一个通用技巧任何 Agent 框架在初期调试时都像一个黑盒——你发一句话进去返回一句话出来中间发生了什么完全靠日志还原。建议在项目里给IChatClient加上日志中间件记录模型每次收到和返回的消息。Microsoft.Extensions.AI里可以通过ChatClientBuilder加自定义的日志处理器或者接入 OpenTelemetry。这样你就能看到模型在调用技能前后的完整决策路径排查问题效率翻倍。我把常见问题整理成一张速查表问题现象常见原因优先排查动作技能注册了但从未被调用描述模糊、名称与其它技能冲突、注册时机太晚重写Description检查命名确认在首次对话前注册技能执行报错但用户看到的是正常回复框架把异常包装成文本返回给模型在技能内增加日志看实际输入参数和执行路径多技能场景选错技能技能描述边界不清、功能重叠为每个技能写明典型问法明确适用范围MCP导入后技能列表为空服务器未暴露工具、网络不通、认证失败用curl请求tools/list接口确认连通性和认证Agent回答完全忽略技能模型上下文里技能信息被截断、基线模型太弱缩短技能描述确认使用足够强的模型版本5. 实操心得把Skills用好的几点建议5.1 技能粒度宁可多而小不要少而大技能设计最容易犯的错误是把一个技能写得非常大入参又多又杂描述也写不清楚。我见过有人把“订单管理”做成一个技能包含查询订单、创建订单、修改订单、取消订单四个功能结果模型经常搞混参数。更好的做法是把每个操作拆成独立技能QueryOrder、CreateOrder、UpdateOrder、CancelOrder。每个技能描述都很聚焦模型选择时准确率更高维护时各改各的互不影响。你每多拆一个技能实际上是在给模型减少一次“选错”的机会。5.2 技能描述这是给模型看的文档很多开发者把技能描述当作给自己备忘的注释随便写一句“汇率换算”就完事。但要注意这些描述是直接进入模型决策上下文的质量直接影响技能被调用的概率和正确率。我建议描述至少包含三个要素触发场景什么时候该用这个技能输入要求需要哪些参数典型示例给一两个用户可能的问法。好的描述大概是这样的Description 当用户需要把一种货币金额换算成另一种货币时使用例如100美元等于多少人民币。输入包括金额、原始币种、目标币种支持USD、CNY、EUR等币种。;模型看到这种描述后基本不会在类似场景下选错技能。你在开发阶段多花两分钟把描述写好能省掉后面大量排查时间。5.3 从技能到完整Agent能力的扩展路径最后聊一点方向性的思考。Agent Skills 不是终点它是 Agent 能力扩展的基础设施。我在企业级 Agent 项目里的扩展路径大致是这样第一步先用框架自带技能和 MCP 导入技能把 Agent 能做的事快速铺开覆盖典型场景。第二步根据业务反馈把高频使用的 MCP 技能沉淀为本地自定义技能补充更细致的提示词和校验逻辑。第三步再叠加规划能力和多 Agent 协作能力让多个不同技能的 Agent 协同完成更大粒度的任务。每一步都不需要重写底层逻辑只是持续往 Skills 集合里加技能、优化技能描述。Agent 的智能化程度会随着技能数量和质量同步提升。这也是这套框架给我最直观的感受——Agent 的能力是“长”出来的不是“写”出来的。我不太喜欢堆砌概念的框架但在这点上 Microsoft Agent Framework 确实做得很实在把复杂的能力扩展做成了一套简单的注册机制让开发者把注意力集中在业务技能本身而不是底层工具调度的细节上。如果你正在评估如何给现有 .NET 项目引入 AI 能力可以从 Agent Skills 入手很快你就能感受到一个从“聊天机器人”进化到“数字员工”的完整过程。最后再分享一个小技巧技能里如果能返回结构化和人类可读文本兼顾的结果后续排查和模型理解都会舒服很多。比如汇率换算技能既返回“500 USD 3620.00 CNY”这样的展示文本又可以在后面附带 JSON 明细。这样模型既有可读的回复素材你也有结构化的数据可追踪。这个小习惯在我后来做复杂技能集成时帮了大忙。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑