ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MAF 1.0正式发布:.NET Agent开发进入工程化时代

MAF 1.0正式发布:.NET Agent开发进入工程化时代 微软把Microsoft Agent Framework 1.0推到 GA 状态之后微信群里的讨论风向很快就变了。之前大家聊 .NET AI 最多的是“今天又跑通了一个 Semantic Kernel 会话”或者“AutoGen 那套 Python 示例能不能抄来用”现在的问题变成了Agent 应该怎么注册、怎么并发、怎么监控、怎么挂掉后自动恢复。这说明一个事——.NET 的 AI Agent 开发终于从玩具阶段走到了工程化阶段。这件事对每一位 .NET 开发者的意义不是“我们可以多学一个框架”而是Agent 开发终于回到了我们最熟悉的底盘上依赖注入、托管服务、配置、日志、遥测、多实例部署。你以前会写 ASP.NET Core 中间件会调 BackgroundService那现在这套 Agent Framework 的基本心智模型你已经有了一大半。这篇文章就围绕 MAF 1.0 正式发布这件事拆一拆它的核心思路为什么它和以前那些 Demo 性质的 AI 框架不一样工程化到底落在哪里以及如果想在 .NET 项目里落地一个多 Agent 应用应该从哪些地方入手。1. MAF 1.0 这次解决的不是“能不能做 Agent”而是“能不能交付 Agent”1.1 以前 .NET 做 Agent 的尴尬处境过去一年.NET 生态里其实并不缺 Agent 相关的库和示例。Semantic Kernel 很早就支持“人形循环 工具调用”AutoGen 也在 .NET 侧保留了移植版社区的 Agent 示例更是一抓一大把。但如果你真的把某一个示例拿去做生产评估很快就会撞到几堵墙示例基本都在“一个控制台程序里把 Agent 跑通”的层面谈不上生命周期管理。Agent 之间的通信方式很随意通道、消息格式、会话标识没有统一标准。调用外部工具时函数注册、参数 schema 生成、执行异常处理往往和特定模型绑定。监控方面基本靠 Print没有内置的 Activity、Metrics、日志约定。跑在 Docker 里要优雅停机想都不敢想。这些不是某一家的具体实现不行而是整个 .NET AI Agent 领域还处在“先证明概念能跑”的阶段。而Microsoft Agent Framework 1.0 的定位就是把这段混乱期收掉把 Agent 的通信、执行、生命周期、状态这些基础能力收拢到一套框架级的抽象里。1.2 从 Demo 到工程化差的是“运行时”很多人会把 Agent 框架理解成“一个能调大模型的库”。实际上模型调用只是很小一部分。MAF 1.0 更像是一个面向多 Agent 协作的运行时它负责把用户请求变成一个可追踪的任务。它把任务调度到合适的 Agent 上。Agent 之间通过结构化的事件和消息通信。状态和会话被记录下来方便恢复和审计。Agent 执行过程会产出结构化日志、指标和追踪信息。这就把“Agent”从一段反复调用模型的代码变成了一个可以被部署、被观测、被运维的应用实体。我印象最深的是官方文档里强调的你写的 Agent 本身不用关心消息是怎么路由的也不用自己维护死循环的终止条件运行时替你处理这层复杂度。也就是说你从“教模型做事”切换到了“定义一群会做事的人以及他们之间的协作规则”这正是做服务和做中间件的那批工程师最顺手的地方。1.3 对 .NET 开发者最直接的红利这个框架的底层很大程度上建立在Microsoft.Extensions体系上。用过 ASP.NET Core 的人会发现很多概念不用重新学IHost/IServiceProviderAgent 本身就是服务。IOptionsAgent 模型的配置走标准配置体系。ILoggerAgent 运行日志直接进入统一日志管道。ActivitySourceAgent 链路追踪以 .NET 原生方式暴露。Meter调用量、Token 消耗、工具执行次数都有指标出口。这带来的结果非常实际现有团队的 .NET 能力可以直接平移不需要为了一个 Agent 项目再养一支 Python 团队。你熟悉的编码规范、代码检查、单元测试、容器化部署统统可以套在 Agent 应用上。这个信号比某个具体 API 长什么样重要得多。2. Agent 运行时模型与核心机制拆解2.1 几个绕不开的核心概念要理解 MAF 1.0我建议先放下模型和 Prompt把注意力放在四个概念上Agent、Skill、Conversation、Event。Agent是一个具备角色目标、系统提示词、可用工具和状态边界的执行单元。简单说就是“一个会调用大模型并且挂着一些工具和后端能力的独立服务”。它不是一个函数而是一个可长期运行的、有身份的实体。身份意味着它可以被单独寻址、单独启动、单独重启。Skill是 Agent 可执行能力的抽象。早期的框架里喜欢叫 Function、Tool、PluginMAF 把它们做了一个比较统一的收敛。不管是对外 HTTP API、本地数据库查询还是通过 MCP 暴露的外部工具在 Agent 这里都表现为“我可以调用一个能力”。这层抽象最大的好处是模型侧不需要关心能力的实现语言和部署位置。Conversation是 Agent 与用户、Agent 与 Agent 之间交互的上下文容器。它必须有稳定的 ID记录消息历史、状态快照、当前阶段。MAF 选择把 Conversation 做成一等公民意味着你可以在长时间运行后把会话恢复出来而不是只要进程重启AI 就失忆。Event则是 Agent 间通信的载体。一个 Agent 完成了某件事不是直接硬编码调用另一个 Agent 的方法而是发布一个事件关心这个事件的 Agent 去订阅并响应。这个机制可以做出非常灵活的编排也能方便地插入人工审批、审计记录等功能。这四个概念结合在一起Agent 应用就像是一个事件驱动的微服务系统只不过每个服务内部都有一颗会推理的大模型内核。2.2 三种典型编排模式MAF 1.0 的编排模式并不是只有一种“多 Agent 开会”。官方样板代码里能看到三种典型范式理解了它们你基本就能看懂大多数示例在干嘛。工作流式编排Pipelined WorkflowAgent 按固定顺序执行A 做完传给 BB 做完传给 C。适合流程稳定、步骤明确的任务比如工单分类、内容审核、数据清洗。这种模式可预测性强最好调试也是最容易先上生产的模式。自主式编排Autonomous Agent Loop单个 Agent 在循环里自行决策决定下一步是调用工具、查询知识库还是把最终结果返回给用户。适合那些无法提前枚举步骤的问题比如研究型写作、代码修复、跨系统排障。这里的难点是防止 Agent 无限循环或者反复调用同一个失败工具所以框架里的最大步数、超时时间、中止机制非常重要。协作式编排Collaborative / Swarm多个 Agent 参与同一个会话在协调者的管理下发言、提问、执行任务。适合需要多头讨论、互相质疑或分角色完成的场景。这种模式最炫但也最复杂因为对话发散以后成本、延迟、内容质量都可能失控。实际项目的聪明做法不是只选一种而是把三者结合外层是稳定工作流中间某些节点用自主式 Agent 做探索最后再让一个总结 Agent 汇总。盲目追求“全自动多 Agent 会诊”在真实业务里通常不会很快出效果。2.3 为什么要用事件驱动而不是直接调用很多第一次接触 MAF 的人会问Agent 间为什么不能直接函数调用非要发消息这么绕原因是直接调用在单体 Demo 里没问题但一旦你要求 Agent 具备可恢复、可扩展、可审计这几个工程属性硬编码调用就变成了灾难。A 挂了B 直接调用就抛异常B 在另一台机器上A 怎么调用想记录 A 对 B 的每一次请求必须在调用点埋点想禁止某种角色在特定场景下的发言又要在代码里写一堆 if。事件驱动把耦合从“你认识服务 B 的方法”变成了“你关心某一类领域事件”。发布方不关心谁会处理订阅方不关心谁发布的。这样 Agent 可以独立扩容、独立替换也可以随时加入审计节点只订阅事件 stream不干扰现有逻辑。这其实和我们在微服务架构里的教训一模一样。框架把这种模式内置到 Agent 通信层里省掉了一堆基础设施代码。3. 从零搭一个多 Agent 项目核心环节怎么串起来3.1 工程初始化和包引入这里我以一个典型的“客户支持工单助手”为例用户输入问题第一个 Agent 负责分类第二个 Agent 负责查询知识库并给出答案第三个 Agent 负责把整个沟通过程整理成结构化工单。假设你已经安装了 .NET SDK先创建一个控制台项目dotnet new console -n AgentSupportDemo cd AgentSupportDemo dotnet add package Microsoft.Agent.Framework dotnet add package Microsoft.Agent.Framework.OpenAI dotnet add package Microsoft.Extensions.Hosting dotnet add package Microsoft.Extensions.AI.OpenAI包名可能会跟着后续小版本有一点调整但方向不会变主包提供 Agent 运行时抽象OpenAI 包提供具体模型接入。如果你用的是 Azure OpenAI、Ollama 或者国内模型的 OpenAI 兼容接口就换对应的客户端包。提示如果你是在已有 ASP.NET Core 项目里做集成不需要新建控制台直接把相关服务注册到WebApplicationBuilder里就行。Agent 作为一种后台能力可以跑在BackgroundService中也可以作为 API 请求内的处理引擎。3.2 把模型和 Agent 注册进服务容器MAF 1.0 这个版本已经明确遵守依赖注入规范所以不要在代码里到处newAgent。统一在启动时注册using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.AI; using Microsoft.Agent.Framework; using OpenAI; string apiKey builder.Configuration[AI:ApiKey]!; string endpoint builder.Configuration[AI:Endpoint]!; string model builder.Configuration[AI:Model]!; var builder Host.CreateApplicationBuilder(args); // 注册模型客户端 builder.Services.AddSingletonIChatClient(sp { var client new OpenAIClient(new ApiKeyCredential(apiKey), new OpenAIClientOptions { Endpoint new Uri(endpoint) }); return client.GetChatClient(model) .AsBuilder() .UseFunctionInvocation() .Build(); }); // 注册 Agent builder.Services.AddSingletonSupportTriageAgent(); builder.Services.AddSingletonKnowledgeResolverAgent(); builder.Services.AddSingletonWorkOrderWriterAgent(); // 启动 Agent 运行时 builder.Services.AddAgentFramework();这段代码的核心逻辑是把“模型连接”和“Agent 行为”分开。IChatClient是 .NET AI 层推出的统一模型抽象MAF 不需要依赖某一个具体厂商的 SDK。你后续从 GPT 换到其他 OpenAI 兼容模型只需要改endpoint和modelAgent 代码不动。我用的是配置注入而不是把密钥写在代码里。这是工程化最基本的一条底线AI 项目更是如此因为 API Key 一旦泄露损失是直接按 Token 算的。3.3 定义 Agent 的行为和能力每个 Agent 本质上是普通的 .NET 类。最简单的写法是继承框架提供的 Agent 基类然后通过某种声明方式告诉运行时“我的系统提示是什么、我能用什么工具”。示意核心骨架如下public sealed class KnowledgeResolverAgent : AgentBase { private readonly IKnowledgeService _knowledge; public KnowledgeResolverAgent(IKnowledgeService knowledge) { _knowledge knowledge; } protected override ValueTaskstring GetSystemPromptAsync(CancellationToken ct) { return ValueTask.FromResult(你是一个知识库查询助手。请根据用户问题从工具返回的资料中提取准确答案。); } [Skill(搜索产品知识库)] public async Taskstring SearchKnowledgeAsync(string query, CancellationToken ct) { return await _knowledge.SearchAsync(query, ct); } protected override async ValueTask OnMessageReceivedAsync(AgentMessage message, CancellationToken ct) { var answer await CallModelWithSkillsAsync(message.Content, ct); await ReplyAsync(answer, ct); } }在这个示例里SearchKnowledgeAsync加上Skill特性后框架会负责把方法的参数 schema 转成模型能识别的工具定义。Agent 在收到消息后先调用大模型模型觉得需要查知识库就会发起工具请求此时框架自动把函数执行结果回传给模型模型再生成最终回答。这种模式下你完全不需要自己写 while 循环去模拟“调模型-调工具-再调模型”的链路框架在CallModelWithSkillsAsync内部已经做了工具解析和结果回填。如果你第一次写建议先不要急着让 Agent 互相通信。先把单个分类 Agent 的“收到用户消息 - 工具调用 - 返回结果”链路跑通再往上叠加编排。3.4 用 Workflow 把多个 Agent 串起来三个 Agent 都定义好后就可以用一个工作流把它们串成一个完整流程。工作流通常定义在不同阶段使用哪个 Agent、什么条件下进入下一阶段var app builder.Build(); var workflow app.Services.GetRequiredServiceAgentWorkflow(); workflow.AddStageSupportTriageAgent(triage); workflow.AddStageKnowledgeResolverAgent(resolve); workflow.AddStageWorkOrderWriterAgent(summary); var conversationId Guid.NewGuid().ToString(); var input 我上个月购买的商品无法申请售后页面一直报错。; await workflow.RunAsync(conversationId, input, CancellationToken.None);这段代码背后的运行逻辑是triageAgent 先判断问题类型然后把分类结果和原始上下文一起传给resolveresolve查询知识库并生成解答如果它判断问题无法自动解决则进入“人工处理”分支最终summaryAgent 把已解决的真实答案写成可归档的工单。整个过程中Agent 用户不需要关心消息怎么传递也不需要自己维护conversationId对应的历史记录框架会跟踪会话上下文。3.5 处理流式输出和取消实际面向用户的 Agent 应用很少会等模型完全输出完再一次性返回结果。真实体验应该是让用户看到“正在打字”的过程。所以工程上不要用同步的GetResponseAsync等完整结果优先处理流式事件await foreach (var agentEvent in agent.ExecuteAsync(input, CancellationToken.None)) { if (agentEvent is TextChunk chunk) { await response.WriteAsync(chunk.Text); } else if (agentEvent is ToolCallEvent toolEvent) { logger.LogInformation(Agent 调用了工具 {Tool}, toolEvent.ToolName); } }通过事件流你能知道 Agent 当前是在“想”、在“调工具”、还是已经“开始输出”这对接前端交互和运维监控都非常有用。同时所有执行方法都应该接受CancellationToken这是 .NET 异步编程的老规矩在 Agent 场景里更重要。因为大模型调用可能持续几秒甚至几十秒应用停机时如果不取消后台线程会带着昂贵的模型计费一直挂着。4. 工程化能力拆解可观测性、配置与部署4.1 Agent 不是黑盒是普通的后端服务让我特别欣慰的一点是MAF 1.0 在可观测性上没有自己发明一套新的日志格式而是直接对齐了 .NET 的标准。Agent 执行的每一个阶段比如开始处理、工具调用、模型响应、Agent 间消息派发、错误重试都会有对应的结构化日志。你只要配置了 OpenTelemetry就能把 Agent 的 Activity 接到现有 APM 系统里和 Web API、数据库、消息队列的调用链放到同一个 trace 里看。这意味着排查问题的方式终于正常了用户说“我的请求很慢”你不用再猜是哪一步慢直接打开 tracing看 Agent 的activity时间线就知道是哪一次模型调用慢了还是某个工具 API 超时了。成本问题也一样框架会输出 Token 消耗相关的 metric你可以按会话、按用户、按 Agent 维度去分析费用来源。4.2 会话状态与持久化真正的工程化 Agent 应用必须回答一个问题如果进程崩溃了我的会话还能不能继续Demo 阶段无所谓重启全丢。生产环境不行用户和 Agent 聊到一半服务升级等你再启动Agent 对之前聊的内容一无所知这是无法接受的。MAF 1.0 把会话状态从 Agent 实现里剥离了出来。你需要做的是把 Conversation 的状态写入 Redis、数据库或者云存储并且让 Agent 在收到新消息时可以从持久化存储里恢复上下文。这个模式和 ASP.NET Core 的 Session 扩展很像只是存储的数据不光是简单键值对还包括消息历史、中间状态和执行进度。要特别提醒的是不要为了省事把全量历史无限发给模型。模型上下文窗口有上限成本也是随着 Token 增长的。工程上应该在写入持久化状态时保留原始完整记录在调用模型时只抽取最近若干轮关键信息必要时让一个总结 Agent 把长历史压缩成摘要。4.3 配置管理与密钥安全我翻了很多开源 Agent 示例最常见的问题就是密钥直接硬编码再就是把提示词写死在变量里。MAF 1.0 既然和 .NET 配置体系打通了就应该按照标准的层级配置来做{ AI: { Endpoint: https://api.example.com/v1, Model: gpt-4o-mini, TimeoutSeconds: 60 }, Workflow: { DefaultMaxSteps: 10, EnableHumanReview: true } }在实际部署环境里ApiKey从环境变量、Secret Manager 或云平台的密钥管理服务中读取不要放进appsettings.json。每个 Agent 的系统提示词如果比较长建议也放到配置文件或专门的模板文件里而不是散落在 C# 类的字符串常量中。提示词在真实项目里会频繁迭代集中管理能让你用内容管线去做版本管理。4.4 “Demo 型架构”与“工程型架构”的差别我用一个直观对比来总结这部分维度Demo 写法工程写法Agent 创建代码里 new依赖注入 配置文件模型地址写死字符串环境配置自动切换会话状态进程内存变量Redis / 数据库持久化运行日志Console.WriteLineILogger OpenTelemetry超时控制忽略CancellationToken 全链路传递工具调用直接方法调Skill / MCP 统一注册失败处理崩溃重启重试、死信、人工补偿如果团队准备把 Agent 项目推上线我强烈建议在写第一行 Agent 业务代码前先把右边的骨架搭好。后面填业务逻辑时自然会沿着工程路径走。5. 实际落地中常见的问题与处理思路5.1 模型“编造”工具调用结果新手最常遇到的情况是Agent 明明调用了工具但返回的内容和工具实际返回结果不一致。排查到最后通常是两个原因一是模型在工具结果不完整时自行脑补二是工具返回给模型的结果太冗长模型没抓到关键字段。解决方法是让工具返回的内容既结构化又轻量。比如搜索知识库不要直接把几十页文档塞给模型先在工具里做一次切片和抽取返回给模型的是“可能相关的段落列表”。同时要求 Agent 在回答中只引用工具输出并在系统提示里加一句如果工具返回内容不足以回答必须明确说出不知道禁止推测。5.2 Agent 陷入无限循环Agent 自主执行模式下最常见的故障就是反复调用同一个工具或者在不同工具间来回切换看起来忙了很久却毫无产出。框架提供了最大迭代步数配置但步数只是一个兜底真正要优化的是 Agent 的决策条件。我的经验是把“什么时候停止”写得比“下一步做什么”更明确。可以在系统提示里规定只有当获取到关键信息且可以形成答案时才输出最终结果如果连续三次工具调用没有新增信息主动承认无法解决转人工。不要指望模型天生具备这种判断力需要靠提示词和代码双重约束。5.3 工具注册成功但 Agent 不调用有时候工具列表里明明有函数模型却像看不见一样一直用自己的“常识”回答。常见原因有几个工具描述太模糊模型不知道什么场景该用。函数参数太多模型生成参数时经常失败于是干脆放弃调用。模型的函数调用能力被禁用注册工具不会自动启用。排查时先打印实际发给模型的消息和工具定义确认工具 schema 有没有正确送达。然后给每个工具加一句“什么时候用、什么时候不要用”的自然语言说明。如果一个工具参数超过 5 个应该拆分成多个小函数或者用一个“传字符串 JSON”的简化入口降低模型出错概率。5.4 多 Agent 之间上下文丢失在工作流编排中每个 Agent 只能看到自己收到的消息看不到处理链上更早 Agent 的内部想法。这是设计如此避免上下文爆炸但也容易导致最终结果缺少关键信息。解决思路是在工作流传递的上下文对象中显式维护字段。比如SupportRequestContext包含用户原始问题、分类结果、知识库检索结论、人工操作记录。每个 Agent 只往上下文对象上追加自己的输出最终总结 Agent 读取这个对象做整合。用强类型对象管理上下文比把整段对话 JSON 传来传去可靠得多。5.5 成本比预期高很多上线后成本飙升基本都出在两类问题一是重复把历史记录全量发给模型二是 Agent 失败后不重试而是重新从头开始。要控制成本应建立 Token 消耗基线每次测试改动之前先记录这次运行消耗了多少 Token。日志里要能看到每个 Agent、每个会话的 Token 明细。通过对工具返回内容做压缩、对历史消息做裁剪、限制最大迭代步数通常能把成本压下来 40% 以上。6. 迁移到 MAF 1.0 前团队需要想清楚的三个问题6.1 不是所有流程都适合上多 AgentMAF 1.0 提供了很完整的编排能力但多 Agent 不等于更智能只会带来更多集成点、更多延迟、更高成本。如果业务流程只有两个明确步骤直接做一个带工具调用的单 Agent 会更稳定。多 Agent 的收益主要体现在需要分角色专业分工、不同任务能用不同模型、或者某些环节需要独立扩展和独立维护时。团队可以先从“单 Agent 加工具”起步再根据真实瓶颈决定要不要拆。6.2 人机协同必须一开始就设计进去工程化 Agent 应用不可能追求全自动。财务审批、高危操作、客户投诉升级这些环节在可预见的未来都应该保留人工确认。MAF 的事件驱动机制对人工介入很友好你可以在工作流中插入一个节点暂停执行等人触发“同意”事件后再继续。别等 Agent 跑起来了再去补人工节点那会很痛苦。6.3 生命周期和版本管理要提前定Agent 系统提示词和工具定义一改整个行为都可能变。上线后你很可能面临一个新的困惑到底是代码改出了问题还是模型更新后行为漂移了所以从第一天起就要给 Agent 加版本号并把“模型 提示词 工具定义”打成一个可发布的整体配置。MAF 1.0 在服务生命周期上已经给了你宿主管理、配置和日志的红利剩下的版本策略还需要团队按自己的发布节奏来定。最后分享一个我在测试这类框架时踩过的坑总想一开始就把所有 Agent 跑在一个内存进程里把编排搞得特别复杂结果出了 bug 根本分不清是哪一层的问题。后来老老实实从单个 Agent 开始每加一个 Agent 就完整跑一遍流程、看一次日志、测一次异常恢复反而推进得更快。这个习惯放到 MAF 1.0 上也一样适用。框架再完善也只是把你的工程能力放大真正的稳定交付仍然靠你对每个运行环节的理解和掌控。
返回列表