1. 从 Demo 到生产.NET 团队做 AI Agent 的真实卡点很多 .NET 团队第一次接触 AI Agent路径都差不多写一个控制台程序把系统提示词塞进ChatClient挂两个[KernelFunction]跑通一次「查天气 生成摘要」然后兴冲冲地拿去给业务方演示。演示很成功但接下来要把它放进真实业务系统时问题就集中爆发了。最典型的三个卡点第一提示词膨胀。为了让 Agent 懂业务团队把产品手册、审批规则、字段说明全塞进 system prompt一个文件写到两千行改一个字段要全文搜索谁都不敢动。第二能力无法复用。A 项目写好的「合同条款抽取」逻辑B 项目想用只能复制粘贴复制过去之后两边各自演化半年后完全对不上。第三行为不可观测。Agent 到底加载了哪些能力、调了哪个工具、为什么这次没调日志里只有一句「模型返回了文本」线上出问题只能靠猜。Microsoft Agent Framework 1.0 的发布加上 Agent Skills 这块拼图补齐恰好对应了上面三个卡点。它把过去散落在 Semantic Kernel 和 AutoGen 里的能力收敛成一套骨架Agents 负责与模型、工具、MCP server 交互处理开放式任务Workflows 负责把 Agent 和确定性函数按显式流程串起来支持顺序、并发、handoff、checkpointing 和 human-in-the-loopAgent Skills 则负责把领域知识、操作说明、脚本和模板资产封装成可移植、可审计、按需加载的模块。换句话说Workflows 解决「过程如何被控制」Skills 解决「能力如何被沉淀和复用」。这两件事合起来.NET 团队才有资格谈「工程化落地」而不是停留在「能跑就行」。这篇文章面向的是已经在用 .NET 做业务系统、准备把 Agent 真正接进生产链路的团队。我会给出可复制的项目结构、Agent Skills 的配置片段、本地运行验证步骤以及如何通过统一 Key/API 通道接入模型服务。最后用一次端到端调用验证技能加载和工具调用是否真的生效。全程以「能跟着做」为标准不堆概念。需要先明确一个判断生产级 Agent 的竞争点不是提示词写得多花而是状态管理、流程控制、工具约束、可观测、可恢复和协作稳定性。如果你现在的 Agent 还停留在「一个 prompt 打天下」那 Agent Skills 的拆分思路会比任何模型升级都更有价值。2. 前置准备项目骨架、依赖与 TaoToken 统一接入通道在动手写 Skill 之前先把工程底座搭好。.NET 团队做 Agent 最容易犯的错是把模型接入代码和业务逻辑混在一个Program.cs里等到要换模型、要加日志、要做多环境配置时改起来非常痛苦。正确的做法是把「模型通道」当成一个独立的基础设施层。2.1 项目结构按职责分层而不是按文件类型堆我建议的目录结构如下核心原则是「技能与代码分离、配置与环境分离」AgentWorkspace/ ├── src/ │ ├── AgentHost/ # 宿主注册 Agent、Workflow、中间件 │ │ ├── Program.cs │ │ ├── appsettings.json │ │ └── appsettings.Development.json │ ├── AgentSkills/ # 技能资产可被多个项目引用 │ │ ├── contract-review/ │ │ │ ├── SKILL.md │ │ │ ├── scripts/ │ │ │ │ └── extract_clauses.py │ │ │ └── references/ │ │ │ └── clause-taxonomy.md │ │ └── ticket-triage/ │ │ ├── SKILL.md │ │ └── assets/ │ │ └── priority-matrix.json │ └── AgentTools/ # 确定性函数Workflow 节点 │ └── CrmLookup.cs └── tests/ └── AgentHost.Tests/这个结构的关键点AgentSkills是一个独立的类库或内容目录它不依赖宿主宿主反过来依赖它。这样 A 项目沉淀的contract-review技能B 项目直接引用即可不需要复制粘贴。AgentTools放的是确定性函数比如查 CRM、写数据库、发消息这些不应该交给 Agent 自由发挥。2.2 依赖与版本在AgentHost.csproj里引入框架包。1.0 之后包名已经收敛注意不要再用旧的 Semantic Kernel 包名混搭ItemGroup PackageReference IncludeMicrosoft.Agents.AI Version1.0.0 / PackageReference IncludeMicrosoft.Agents.AI.Workflows Version1.0.0 / PackageReference IncludeMicrosoft.Extensions.Hosting Version9.0.0 / /ItemGroup2.3 用 TaoToken 做统一模型通道模型接入这块我建议不要在每个 Agent 里各写一份 HTTP 调用。用一个统一的 OpenAI 兼容通道把 Base URL、Key、Model ID 三件套集中管理后面换模型、加限流、做审计都只改一处。TaoToken 提供的就是这样一个统一入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。在appsettings.Development.json里配置{ ModelGateway: { BaseUrl: https://taotoken.net/api, ApiKey: sk-你的Key, ModelId: claude-sonnet-4-5, TimeoutSeconds: 60 } }注意 Base URL 只写到/api不要自己拼/v1/chat/completions兼容层会处理路径。Key 不要提交到仓库用环境变量或用户机密覆盖dotnet user-secrets init --project src/AgentHost dotnet user-secrets set ModelGateway:ApiKey sk-你的Key --project src/AgentHost如果你还没创建 Key可以到 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 生成接入细节参考 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这一步做完模型通道就和业务代码解耦了后面所有 Agent 都复用这一份配置。3. 可复制配置Agent Skills 的 SKILL.md 与宿主注册片段这一节是全文的核心。Agent Skills 的价值不在于「多了一个文件夹」而在于它引入了 Progressive Disclosure渐进式披露机制先只注入技能名称和描述任务匹配后再加载完整SKILL.md确有需要再读取参考资料或执行脚本。这直接改善了上下文窗口管理和 Token 成本。3.1 一个真实可用的 SKILL.md以「合同条款审查」为例AgentSkills/contract-review/SKILL.md内容如下--- name: contract-review description: 审查采购合同中的付款条款、违约责任与交付周期识别偏离公司标准模板的风险点。当用户提交合同文本并要求合规检查时使用。 version: 1.0.0 --- # 合同条款审查 ## 何时使用 当输入包含合同正文且用户意图为「审查 / 合规检查 / 风险识别」时启用。 ## 操作步骤 1. 调用 scripts/extract_clauses.py 抽取条款段落输出 JSON。 2. 对照 references/clause-taxonomy.md 中的分类标准逐条判断风险等级。 3. 对高风险条款引用原文并给出修改建议。 4. 输出结构化结果条款编号、风险等级、原文摘录、建议。 ## 约束 - 不得编造合同中不存在的条款。 - 风险等级只允许高 / 中 / 低。 - 涉及金额的条款必须原样引用不做换算。注意 frontmatter 里的description写得非常具体因为它就是「Advertise 阶段」注入给模型的那段文字。描述越准确模型判断「该不该加载这个技能」就越准。很多团队技能加载不生效问题就出在 description 写得太泛比如只写「处理合同」模型根本不知道什么时候该用。3.2 宿主注册把技能目录挂到 Agent 上在Program.cs里注册 Agent 并挂载技能目录using Microsoft.Agents.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder Host.CreateApplicationBuilder(args); var gateway builder.Configuration.GetSection(ModelGateway); var baseUrl gateway[BaseUrl]!; var apiKey gateway[ApiKey]!; var modelId gateway[ModelId]!; builder.Services.AddSingleton(sp { var client new HttpClient { BaseAddress new Uri(baseUrl), Timeout TimeSpan.FromSeconds( gateway.GetValueint(TimeoutSeconds, 60)) }; client.DefaultRequestHeaders.Authorization new System.Net.Http.Headers.AuthenticationHeaderValue(Bearer, apiKey); return client; }); builder.Services.AddAgent(contract-agent, options { options.ModelId modelId; options.Instructions 你是采购合规助手优先使用已注册的技能完成任务。; options.SkillsDirectory Path.Combine( AppContext.BaseDirectory, AgentSkills); options.EnableProgressiveDisclosure true; }); var app builder.Build(); await app.RunAsync();这里三件套齐了Base URL 指向https://taotoken.net/apiKey 从配置读取Model ID 显式声明。EnableProgressiveDisclosure true是让技能按需加载的关键开关关掉它就会把所有SKILL.md一次性注入Token 成本立刻上去。3.3 技能与 Workflow 的边界怎么划一个常见困惑什么时候用 Skill什么时候用 Workflow判断标准很简单——希望 AI 自主决定路径用 Skill必须保证步骤和顺序用 Workflow。比如「合同审查」里抽取条款是确定性步骤应该做成 Workflow 节点调用extract_clauses.py而「判断某条款风险等级」需要语义理解交给 Skill 里的 Agent 决策。两者不是二选一而是不同层面的复杂度治理手段。4. 本地运行验证一次端到端调用确认技能加载与工具调用配置写完必须验证「技能真的被加载了、工具真的被调用了」否则一切都是纸上谈兵。这一节给出可观测的验证方法。4.1 打开日志先看 Advertise 阶段在appsettings.Development.json里把日志级别调到 Debug{ Logging: { LogLevel: { Default: Information, Microsoft.Agents.AI: Debug } } }运行dotnet run --project src/AgentHost观察启动日志。正常情况下你会看到类似输出info: Microsoft.Agents.AI.Skills[0] Advertised 2 skill(s): contract-review, ticket-triage如果这里只显示 0 个技能说明SkillsDirectory路径不对或者SKILL.md的 frontmatter 格式有误。注意AppContext.BaseDirectory指向的是bin/Debug/net9.0/技能目录需要在 csproj 里配置复制ItemGroup Content Include..\AgentSkills\**\*.* CopyToOutputDirectoryPreserveNewest/CopyToOutputDirectory LinkAgentSkills\%(RecursiveDir)%(Filename)%(Extension)/Link /Content /ItemGroup4.2 发一次真实请求看 Load 与 Run scripts用一个简单的控制台入口触发调用var agent app.Services.GetRequiredServiceIAgent(); var session await agent.CreateSessionAsync(); var prompt 请审查以下合同条款 甲方应在收到货物后 90 日内支付全部款项 逾期未付的按未付金额的 0.01% 计收违约金。 ; await foreach (var update in agent.RunStreamingAsync(prompt, session)) { Console.Write(update.Text); }运行后Debug 日志里应该出现三段关键信息Loading skill: contract-reviewLoad 阶段、Reading resource: clause-taxonomy.mdRead resources 阶段、Executing script: extract_clauses.pyRun scripts 阶段。如果只看到 Loading 没有 Executing说明脚本没被正确识别检查scripts/目录是否随技能一起复制到了输出目录。4.3 成功结果的判断标准一次成功的端到端调用输出应该包含结构化结果而不是一段泛泛而谈。比如条款编号: 2.1 风险等级: 高 原文摘录: 甲方应在收到货物后 90 日内支付全部款项 建议: 公司标准模板为 30 日90 日账期显著偏长建议改为 30 日或增加担保条款。 条款编号: 2.2 风险等级: 中 原文摘录: 按未付金额的 0.01% 计收违约金 建议: 违约金比例偏低建议提高至 0.05% 并设置上限。如果输出里出现了「根据合同内容」这类没有原文引用的空话说明 Skill 的约束段没起作用回去检查SKILL.md的「约束」部分是否写清楚。验证模型行为是否稳定可以到 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 对照不同模型的表现长文本抽取类任务对模型选择比较敏感。5. 本篇常见错排查401、技能不加载与工具调用失败工程化落地过程中报错是常态。这一节列出几个高频问题对照真实报错给出排查路径。5.1 401 UnauthorizedKey 没生效最常见的报错长这样System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).排查顺序第一确认ModelGateway:ApiKey是否被 user-secrets 或环境变量正确覆盖可以在启动时打印 Key 的前 6 位和后 4 位做脱敏确认。第二确认请求头是Authorization: Bearer sk-xxx不是x-api-key。第三确认 Base URL 是https://taotoken.net/api多写或少写路径都会导致鉴权失败。如果 Key 本身有问题重新到 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 生成一个再试。5.2 local proxy failed网络层被拦截error: local proxy failed: connection refused这个报错通常出现在企业内网环境说明请求没有到达目标端点。检查公司出口策略是否允许访问taotoken.net以及本机是否配置了会拦截 HTTPS 的中间件。注意不要通过任何非合规的网络工具绕过正确做法是让运维把域名加入白名单。5.3 reading choices响应结构解析失败System.Text.Json.JsonException: The JSON value could not be converted ... reading choices这说明返回体不是标准的 OpenAI 兼容结构常见原因是 Base URL 拼错请求打到了某个返回 HTML 的地址。用 curl 直接验证一次curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d {model:claude-sonnet-4-5,messages:[{role:user,content:ping}]}如果 curl 正常而代码报错问题在序列化配置检查是否用了大小写敏感的JsonSerializerOptions。5.4 OAuth 相关报错与 Codex auth.json如果你同时在使用 Codex 类工具可能会遇到 OAuth 过期导致的报错。这类工具通常把凭据存在~/.codex/auth.json格式大致如下{ OPENAI_API_KEY: sk-你的Key, base_url: https://taotoken.net/api, model: claude-sonnet-4-5 }三件套必须齐全Base URL、Key、Model ID。缺任何一个都会导致鉴权或路由失败。如果你用 CC Switch 或 Cline MCP 管理多个模型通道同样要保证这三项在每个 profile 里都写全不要只填 Key 就以为完事。5.5 技能加载了但工具没被调用日志显示Loading skill成功但脚本始终没执行。九成原因是SKILL.md的「操作步骤」写得太模糊模型不知道要调脚本。把步骤写成明确的动作指令比如「调用 scripts/extract_clauses.py 抽取条款段落」而不是「分析合同内容」。技能描述是给模型看的接口文档写得越像函数签名调用越稳定。6. 长期编码与 Agent 协作把通道固定下来走到这一步你已经有了一个能加载技能、能调工具、能观测日志的 .NET Agent。接下来真正决定它能不能长期活下去的是通道是否稳定、协作是否顺畅。对于需要长期编码、多 Agent 协作、反复迭代技能的团队建议把模型通道固定成一份团队级配置而不是每个人本地各配一份。TaoToken 的 Coding Plan 就是为这种场景准备的适合把 Agent 开发纳入日常工程流程的团队具体可以看 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。控制台在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以统一管理 Key 和用量。如果你更想先验证模型在具体技能上的表现可以直接在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里把SKILL.md的内容贴进去试跑确认描述和步骤写得够清楚再落到代码里。这个顺序能省掉大量「改代码—重启—看日志」的循环。最后给一个实操建议把每个 Skill 当成一个有版本号的内部包来管理。SKILL.md的 frontmatter 里写version技能变更走 code review脚本加单元测试。这样半年后回头看你能清楚知道哪个版本的技能在生产上跑过、哪个版本被回滚过。Agent 的工程化本质上和普通服务的工程化没有区别只是把「提示词」换成了「技能资产」而已。