恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
.NET中构建AI智能体:Microsoft Agent Framework实战指南
首页
资讯中心
/
.NET中构建AI智能体:Microsoft Agent Framework实战指南
.NET中构建AI智能体:Microsoft Agent Framework实战指南
发布时间:2026/10/20 2:55:47
不知道你有没有遇到过这种场景公司技术栈明明以 .NET 为主结果一聊到 AI 智能体团队里默认的潜台词就是“这东西得用 Python 写”。我一开始也是这么想的直到我把一个内部业务流程的智能体原型用 .NET 写完、跑通才发现这个刻板印象耽误了不少事。现在.NET 生态里已经有了微软官方的 Agent Framework现在一般叫 Microsoft Agent Framework简称 MAF它设计思路很新而且跟 .NET 的异步模型、依赖注入、日志体系能比较自然地融合。如果你也想在 .NET 里搭一个 AI 智能体或者只是想多了解一种可落地的智能体编排方案这篇文章值得你花几分钟顺着我的实际操作思路看一遍。这篇不是官方文档复述而是我实打实从安装包、写代码、调 bug、重新设计拓扑一路走下来的记录。我会把框架里最容易卡人的几个概念用大白话讲清楚再给一套可以直接抄作业的多智能体示例代码最后把调试和排查经验也整理出来。不管你是做后端、系统集成还是架构设计只要吃过“想上 AI 但被语言栈绑住”的亏应该都能从里面捞到点有用的东西。1. 为什么是 .NET为什么是现在1.1 .NET 做智能体到底缺什么智能体这个词这几年被用得特别泛滥但核心诉求其实就一句话让程序不再只是“处理请求”而是能自己拆解任务、调用工具、调用模型、根据结果决定下一步动作。要支撑这种动态流程框架层面至少得解决四件事怎么跟大模型对话、怎么管理多步执行状态、怎么让多个智能体协作、怎么把上面这些东西接进现有业务系统。.NET 在“跟大模型对话”这件事上毫不含糊官方 SDK 提供的流式接口、可观测性、重试机制都很成熟。真正缺的是后面三件事的统一抽象。以前我们想在 .NET 里做多智能体协作往往要自己定义消息格式、自己实现调度循环、自己维护一个状态机。这些东西不是做不到而是每个项目都从零造一遍轮子最后长得都不一样维护成本奇高。微软解决这个问题的思路很直接。它把智能体拆成几个基础抽象加上一个叫“拓扑”Topology的编排层再用本地 Runner 把整个流程跑起来。不需要天然地依赖某个云服务你想接什么模型就接什么模型传递信息靠的是统一的“Activity”活动。我刚开始看到 Activity 这个概念的时候就觉得挺亲切的因为它就像一个强类型版本的消息包比较像我们做通讯协议时定义的报文结构。1.2 Agent Framework 能带来的最大价值这个框架给我的核心印象是它并不指望你写多少“魔法代码”而是逼着你把所有逻辑沉淀成清晰的模块。一个智能体在 MAF 里就是一个普通类继承AgentBase重写OnActivityAsync方法。任何一个外部事件、命令、消息都可以通过 Activity 进入智能体。你是不是联想起什么了对有点像我们写 MVC 里的控制器路由只不过这次处理的不光是 HTTP 请求还有模型输出。它的第二个价值点在于“干净结构”。用 MAF 写出来的智能体业务逻辑和模型调用是解耦的模型只是智能体内部的一个执行部件。这意味着你可以很自信地把智能体逻辑交给单元测试跑不需要真的调外部 API。我后来把核心流程的逻辑抽取成纯计算模块之后单测覆盖率一下子就拉起来了。第三个价值点则是多智能体编排的“拓扑”设计。MAF 允许你定义一个拓扑类在里面声明各个 Agent 的依赖关系然后 Runner 会根据 Activity 的流向自动路由。这个设计避免了常见的“智能体互相调用到晕头”的问题因为协作关系是在拓扑里静态声明的而不是运行时靠 Agent 自己临时决定。2. 先动手五分钟跑通一个本地智能体2.1 环境准备和包介绍我建议直接用最新的 .NET (比如 .NET 8 或更新版本) 作为宿主环境。建好一个空的控制台项目后需要加入几个 NuGet 包。注意这个框架的包名有一个“名不副实”的坑——核心包目前叫Microsoft.Agent.Framework不是 “Semantic Kernel” 也不是 “Agent SDK”。我最早就在这一步找错了方向把熊搞成大白菜了。你需要装的基础组合是核心抽象包Microsoft.Agent.Framework跑测试用的本地执行器Microsoft.Agent.Framework.Runner如果要用 Azure OpenAI 或者 OpenAI再装对应的模型封装包比如Microsoft.Agent.Framework.Connector或官方模型接入包具体名字看当前版本配置层面比我想象的简单连接字符串和模型信息塞进appsettings.json即可。MAF 内部自带配置绑定你不用自己写一堆IOptions模板代码直接用框架的AgentApplicationBuilder就能完成服务注册和配置读取。注意: 不同预览版本的包名和命名空间可能会微调建议看你当前 NuGet 页面的最新示例。如果遇到编译错误优先检查是不是把“Framework”和“Runtime”两套包混用了。2.2 第一个 AgentHello World先用最直白的方式感受一下智能体的骨架。新建一个类继承AgentBaseusing Microsoft.Agent.Framework; using Microsoft.Agent.Framework.Activities; public class GreetingAgent : AgentBase { protected override async Task OnActivityAsync(IContext context, CancellationToken ct) { if (context.Activity.TryGetMessagePayload(out var message)) { var reply $收到你的消息了: {message.Text}。我还能继续做事不只是打招呼。; context.Transmit(reply.ToAgentActivity()); } } }这段代码看起来是不是像普通的事件处理器框架里最关键的一个设计就是传入的 Activity 是一个多态容器。你不需要用if...else分辨一堆子类而是用TryGetMessagePayload、TryGetCommandPayload、TryGetComponentPayload这类方法去尝试提取不同业务载荷。这比判断字符串类型可靠得多也满足开闭原则。然后是注册和运行。MAF 里的本地 Runner 很适合调试它甚至会配对拓扑文件让你直接看到 Activity 在哪些 Agent 之间的传播路径。using Microsoft.Extensions.Hosting; using Microsoft.Agent.Framework; var builder Host.CreateApplicationBuilder(args); builder.Services.AddAgentFramework(agentBuilder { agentBuilder.AddAgentGreetingAgent(greeter); }); var app builder.Build(); var runner app.Services.GetRequiredServiceRunnerBase(); await runner.RunAsync(app.Services, ct);运行起来之后往控制台输入任意一句文本智能体就能基于注册好的模型返回结果。第一次跑通的时候我最大的感觉是“轻”。没有繁琐的中间件配置没有在启动配置里堆一长串 Prompt也没有各种神秘的环境变量。2.3 Activity 的三种载荷Message、Command、Component想用好 MAF必须理解 Activity 里三种最常见的载荷形态否则写出来的 Agent 很容易变成“只能聊天的机器人”而不是干活的工作流。第一种是Message也就是自然语言消息。这种载荷最常见适合把所有不确定信息一股脑扔给大模型处理。第二种是Command它常用于表达明确的意图。比方说“查询订单号 #12345” 这种指令你用 Command 结构解析就能绕开模型变成确定性逻辑。第三种是Component它更多用于承载组件化调用结果比如负责“商品详情”组件返回的数据、工具调用的结果。大体上你可以这样理解Message 是“模糊输入”Command 是“明确指令”Component 是“结构化数据”。在设计智能体时我建议你先问自己这个环节到底适合哪种载荷。如果用户随便说句话就能触发用 Message如果必须在满足条件时才做某个动作用 Command 解析如果只是把一个子任务的输出交给另一个子任务用 Component 最干净。3. 核心设计思路为什么 Activity 和 Topology 是灵魂3.1 从消息总线到智能体拓扑接触 MAF 之后我总忍不住拿它和我们做微服务时的消息总线做类比。一个 Agent 收到的 Activity就像是服务总线里的消息Agent 处理完后的输出又是新的 Activity。既然我们做微服务时特别强调服务边界Agent 设计也同理——每个 Agent 应该只做一件清晰的事而且对外沟通只靠 Activity。所谓“拓扑”Topology就是把 Agent 之间的关系明确画出来。MAF 的拓扑类一般是一个继承自AgentTopology的类你在里面通过属性声明各 Agent 的依赖关系。举个简单的例子public class MyTopology : AgentTopology { public GreetingAgent Greeter { get; set; } new(); public WeatherAgent WeatherWorker { get; set; } new(); public override void Configure() { AddActivityHandlerGreetingAgent, MessageActivity(Greeter); AddActivityHandlerGreetingAgent, CommandActivity(Greeter); AddActivityHandlerWeatherAgent, WeatherRequest(WeatherWorker); } }初次看起来这个拓扑类好像就是注册表但它隐含了一个重要的设计原则路由关系是静态的、可由架构审查的。相比让两个 Agent 互相注入然后在运行时随意调用这种声明式路由能让人一眼看清整个系统的数据流也更方便做测试替身。我实际做项目时发现一个挺舒服的点拓扑类和依赖注入容器很契合。你可以把各个 Agent 需要的服务通过构造函数注入进去然后在拓扑类的构造函数里也注入这些服务最后用拓扑来构建整个工作流。如果团队里有人对智能体内部实现不熟悉看拓扑配置就能理解七八成。3.2 为什么这套设计适合人机协作和自动化另一个我特别欣赏的点是MAF 的 Activity 机制天然适合“人机协作”和“无人值守”两种模式的切换。人工介入时你可以通过输入文本把用户指令包成 Message 发送过去自动化流程中你可以在代码里生成 Component 或 Command 来驱动下一步。比如说一个工单处理系统。如果 AI 无法判断工单归属它可以返回一个特殊的Activity要求人工给出答复。然后人工拿着结果再次把 Activity 注入流程接着往下走。这种模式不像传统的“回调函数”那样把流程拆得七零八落而是在同一个活动的流转里保留上下文。在处理自动化流程时这种强类型载荷还有一个隐藏价值可追踪。你可以在中间件或日志中记录每一个 Activity 的类型、来源、去向和耗时。调 bug 时拉出一张活动时间线几乎能还原整个流程。我后来排查问题就全靠这个时间线比看混乱的文本日志强太多。4. 实战拆解多智能体协作处理一个业务请求4.1 模拟场景定义为了说清楚核心用法咱们造一个足够贴日常的应用场景。假设我们有一个“商品咨询智能体系统”包含两个子智能体和一个汇聚控制器OrderAgent从用户消息中抽取订单号和商品查询业务系统拿到物流状态和库存信息。PolicyAgent判断用户的诉求属于退货、换货还是开发票并生成政策解释。MediatorAgent汇总前两个智能体输出生成一段友好的最终回复。这个场景选得好不好无所谓关键是它涵盖了“任务分割、并行处理、结果合并”三段经典模式。4.2 代码实现和关键参数解析先定义两个子智能体。它们各自接收一个强类型 Component内部调用模型或查询逻辑最后把结果发出去。public class OrderAgent : AgentBase { private readonly IOrderService _orders; public OrderAgent(IOrderService orders) _orders orders; protected override async Task OnActivityAsync(IContext context, CancellationToken ct) { if (context.Activity.TryGetComponentPayload(out OrderQuery query)) { var result await _orders.GetOrderStatusAsync(query.OrderId, ct); context.Transmit(result.ToAgentActivity()); } } } public class PolicyAgent : AgentBase { protected override async Task OnActivityAsync(IContext context, CancellationToken ct) { if (context.Activity.TryGetComponentPayload(out UserIntention intention)) { var policyText intention.Type switch { refund 退货政策未拆封且不影响二次销售..., exchange 换货政策非质量问题需7日内..., _ 可为您转接人工处理... }; context.Transmit(policyText.ToAgentActivity()); } } }也许你会觉得PolicyAgent里用 switch 分支太简单。这只是为了说清楚模式。实战中你可以把这段逻辑改成调用真实的大模型甚至让模型决定输出结构化的政策编号。框架不限制你用什么模型推理它只帮忙解决信息的传递和路由。然后是MediatorAgent。它需要同时监听两个子 Agent 的输出并把结果拼接成最终文案。实现方式是在 Activity 处理中识别来源或者用拓扑建立映射。public class MediatorAgent : AgentBase { private readonly IResultFormatter _formatter; public MediatorAgent(IResultFormatter formatter) _formatter formatter; protected override async Task OnActivityAsync(IContext context, CancellationToken ct) { if (context.Activity.TryGetComponentPayload(out OrderInfo orderInfo)) { _partials.Order orderInfo; } else if (context.Activity.TryGetComponentPayload(out PolicyInfo policyInfo)) { _partials.Policy policyInfo; } if (_partials.IsComplete) { var finalReply _formatter.Format(_partials); context.Transmit(finalReply.ToAgentActivity()); } } }这里用了个累加器_partials但在多实例部署下这种内存状态会出问题。如果系统需要水平扩展建议把状态放到分布式缓存或数据库里。上面这几段代码能跑通但只是单机演示级别的写法。4.3 拓扑配置和运行效果拓扑定义如下public class CustomerServiceTopology : AgentTopology { public OrderAgent Order { get; set; } new(); public PolicyAgent Policy { get; set; } new(); public MediatorAgent Mediator { get; set; } new(); public override void Configure() { AddActivityHandlerOrderAgent, OrderQuery(Order); AddActivityHandlerPolicyAgent, UserIntention(Policy); AddActivityHandlerMediatorAgent, OrderInfo(Mediator); AddActivityHandlerMediatorAgent, PolicyInfo(Mediator); } }当我们注入一个主 Activity比如用户说“我订单超时了想退款”时Runner 会根据负载类型把消息派给能处理它的 Agent。如果这条消息不是强类型 Query而是自由文本你可以在入口加一个 IntentExtractionAgent让大模型负责从自然语言里抽出OrderQuery和UserIntention。这个阶段我不推荐后台任务直接接收原始文本当作命令处理容易把对话模型和流程逻辑搅在一起。整个流程跑起来以后你会看到OrderAgent写出的订单信息和PolicyAgent写出的政策信息都被送到了MediatorAgent最终被拼成一段自然语言回复。这种“小步骤、可测试、易替换”的风格正好是普通开发者参与 AI 应用开发最舒适的姿势。5. 从玩具到生产调试、测试与常见问题5.1 本地调试技巧日志和中间件MAF 的本地调试体验比我想象中成熟。第一条建议开启框架内置的 Activity 跟踪日志。你会看到类似这样的日志输出[AAA] OrderAgent received Component(OrderQuery) [BBB] MediatorAgent received Component(OrderInfo)这种结构化日志能让你瞬间知道消息是否路由错了、是否卡在某个 Agent 里。如果发现某个 Agent 一直收不到任何输入多半是拓扑里的路由条件没匹配上或者 Activity 的载荷类型和TryGetComponentPayload的泛型不一致。第二条建议写一个“观察中间件”。MAF 支持在 Agent 执行管线中插入中间件你可以在中间件里记录每个 Activity 的耗时和成败。我把这个观察中间件比喻成“手术室的监控屏”没它就只能瞎猜内部发生了什么。具体实现也不复杂继承框架的中间件基类重写执行前后的钩子方法即可。5.2 高发坑位和小抄表格下表列一下我踩过和预见别人很容易踩的典型问题症状常见原因解决思路Agent 收不到消息拓扑里没注册路由载荷泛型不一致检查 Topology.Configure打印 Activity.PayloadType回复消息没被模型总结直接把 Component 发出去没有经过语言模型在入口 Agent 内调用模型把结果作为 Message 发出多 Agent 并发时状态错乱在 Agent 实例里放了可变状态改用无状态 Agent状态存外部存储依赖注入失效拓扑里用了new而不是从容器获取让拓扑从构造函数注入服务或通过服务提供者解析日志太吵打开了全量 Activity 追踪按活动类型和 Agent 名过滤只保留关键路由日志模型超时导致卡死没有配置取消令牌和超时控制所有外部调用都传 CancellationToken并设置超时时间5.3 关于单元测试和测试替身因为 MAF 把智能体封装成了一个可独立调用的类所以你完全可以直接实例化一个 Agent然后手动构造一个 Activity 来测试它的行为。关键是把模型调用抽象成可替换的接口比如public interface IChatModel { Taskstring CompleteAsync(string prompt, CancellationToken ct); }测试时注入一个 fake 实现直接返回“模拟模型输出”从而快速验证业务逻辑。我第一次给智能体写测试的时候发现竟然没有比普通 Service 测试复杂多少这种“去神秘化”的体验确实很能增强团队信心。另外要注意拓扑类本身也应该有测试。如果你的业务逻辑依赖“A Agent 的输出会进入 B Agent”那么测试拓扑时就应该断言给 A 发送一个 ActivityB 能被接收到预期载荷。手动模拟整个 Runner 毕竟繁琐但 MAF 官方推荐还是直接把 Runner 组件建出来跑因为 Runner 本身就是一个小型消息循环跑起来很快。6. 更进一步把智能体接进真实业务系统6.1 从本地 Runner 到宿主程序本地 Runner 只是验证逻辑的起点。真正要部署时你需要把 Agent 放在一个常驻进程中可能是 ASP.NET Core 后台服务也可能是 Worker Service。我把调试好的逻辑迁到后台服务时几乎只改了一小段配置核心代码没动。框架天然支持依赖注入和宿主生命周期所以在常见“寄宿模型”下迁移很顺。有一点需要特别留意后台服务的并发模型。不要默认智能体里的代码是单线程跑的如果托管环境会为多个对话创建多个执行方式请做好并发控制尤其在操作数据库或外部 API 时。我的做法是每次都把一个ConversationId放进 Activity 的元数据里整个链路传递这个 ID所有缓存和数据库操作都以它为维度隔离这样并发对话之间就不会互相干扰了。6.2 可观测与风险控制把 AI 智能体真正投入生产你的风险控制能力比模型能力更重要。这里说三个我坚持执行的原则第一给所有外部模型调用加超时和重试。模型不是数据库它可能因为峰值流量而响应缓慢你需要一个针对“推理服务”定制的重试策略而不是简单沿用普通 HTTP 的指数退避。第二对 Agent 的输出做“守卫校验”。如果MediatorAgent输出了一段明显偏离格式的结果比如空字符串、缺失关键字段务必在生成最终回复前拦截。可以用正则或 JSON Schema 验证防的是模型偶尔“放飞自我”。第三保留每一次 Activity 的完整记录。AI 流程的问题排查非常依赖“当时模型到底看到了什么”。把进入每个 Agent 的 Prompt或关键载荷记录下来一旦用户投诉或审计要求调取记录时才能有据可查。6.3 这个框架还能往哪扩展做完上面的基础系统后你完全可以继续往这几个方向扩展一个是在拓扑中加入“并发分支”让多个子 Agent 并行处理任务MAF 支持在一个父 Activity 下派生出多个子 Activity并在返回点聚合另一个是给 Agent 加入工具调用能力让模型在思考过程中决定调用哪些外部函数还有一个是把可观测信息接入现有的监控系统用结构化日志和 APM 追踪智能体全链路。我个人目前正在做的扩展是把OrderAgent和PolicyAgent变成能动态清点自身能力的“插件式智能体”。具体做法是用拓扑上的元数据描述这个 Agent 能处理哪些载荷类型然后在运行前生成能力清单再由另一个“分配 Agent”根据用户请求自动挑选合适的 Agent 子集参与本轮任务。这比硬编码拓扑更灵活但要注意不能让模型拥有修改系统拓扑的权限安全边界还是要靠代码守住。讲真AI 智能体这个领域每天都有新概念但落到工程上依然是老一套的高质量原则清晰边界、可测试、可观测、可控制。MAF 把这些原则用一种挺自然的语言融进了 .NET. 至少对我来说现在可以说是“谁说.NET没有智能体我们不但有而且能按自己的方式跑得很好”了。最后顺手分享一个小经验如果你想在团队里推广这套方案别急着炫代码先把 Activity 的概念和拓扑图摆在白板上你会发现大家理解协作流程的速度比你预期快得多。