微软 Agent Framework 上手:用三种协议写出你的第一个 AI Agent
微软出了个 Agent Framework,C# 也能快速搞 AI 企业级应用开发了。
这框架是 Semantic Kernel 和 AutoGen 的下一代继任者,核心解决一个问题:不管底层走 OpenAI 的 ChatCompletions、Responses 还是 Anthropic 的 Messages,上层都是同一套 AIAgent 加 AgentSession,换协议不改业务代码。
本篇是系列第一篇,带你从零装好框架,用三种协议各写一个能多轮对话的 agent,先把"装好、跑通"这条路打通。
原文发在微信公众号【拓荒者IT】,探索AI应用,分享好用好玩的产品,喜欢的朋友可以关注一下~
01 这个框架什么来头
先说定位。Microsoft Agent Framework 是 Semantic Kernel 和 AutoGen 的下一代继任者,由原来两个项目的同一拨人做的。Semantic Kernel 偏企业级,类型安全、过滤器、遥测这些东西做得扎实;AutoGen 偏多代理编排,抽象简洁。Agent Framework 把两边合到一起,又加了基于图的工作流。
核心包叫 Microsoft.Agents.AI,MIT 协议,跨平台覆盖面挺广,.NET 8/9/10、.NET Standard 2.0,连 .NET Framework 4.7.2 都能跑。当前稳定版 1.17.0。
框架的能力分四块:
| 能力 | 干什么 | 本篇用不用 |
|---|---|---|
| Agents | 单个 agent,调 LLM、用工具、生成回复 | 用 |
| Harness Agent | 增强版,带规划、待办跟踪、上下文压缩 | 不用 |
| Workflows | 图工作流,编排多个 agent | 不用 |
| Integrations | 对接各家模型、工具、中间件 | 用 |
本篇只碰最基础的 Agents,目标是把一个能聊天的 agent 跑起来。Harness 和 Workflows 留到后面几篇。
02 核心设计:三层桥接
框架为什么能做到换协议不换代码?靠三层抽象。这节把它们讲清楚,后面看代码就顺了。
第一层:IChatClient
来自 Microsoft.Extensions.AI,这是 .NET 官方的 AI 抽象层。它定义了一个 IChatClient 接口,各家模型客户端都能实现它,或者被包装成它。这层的作用是解耦:你的代码面向 IChatClient 写,今天接 OpenAI,明天换别的,上层不用改。
第二层:AIAgent
来自 Microsoft.Agents.AI。IChatClient 只是"能聊天",AIAgent 在上面加了 agent 的概念:名字(Name)、描述(Description)、系统指令(Instructions)。后面要挂工具、加中间件,也是往 AIAgent 上加。
第三层:AgentSession
会话状态容器。你创建一个 session,之后每次 RunAsync 都把它传进去,框架自动维护多轮对话的上下文。你不用自己拼 messages 数组,也不用记 previous_response_id。而且 session 能序列化,想存数据库、想跨进程恢复都行。
三层叠完,桥接链路长这样:
ChatCompletions 路径(最通用):
OpenAIClient -> GetChatClient(model) -> AsIChatClient() -> IChatClient -> AsAIAgent() -> AIAgent
Responses / Anthropic 路径(provider 直连):
ResponsesClient / AnthropicClient -> AsAIAgent(model, instructions, ...) -> AIAgent
两条路最后都拿到一个 AIAgent。不管底层哪个协议,调用都是同一行:
var response = await agent.RunAsync(input, session);
统一就统一在这。下面看三种协议具体差在哪,再逐个跑通。
03 三种协议,差在哪
| ChatCompletions | Responses API | Messages API | |
|---|---|---|---|
| 谁的 | OpenAI | OpenAI | Anthropic |
| 定位 | 最通用的对话接口 | OpenAI 新一代接口 | Anthropic 的对话接口 |
| 上下文维护 | 本地拼 messages 数组 | 服务端托管,previous_response_id | 本地拼 messages,system 单独传 |
| 兼容性 | 几乎所有厂商都兼容 | 主要是 OpenAI 自家 | Anthropic 兼容网关 |
简单说:
ChatCompletions 是老大哥,历史最久,兼容性最好。国内通义、DeepSeek、Moonshot 这些,基本都提供 OpenAI 兼容的 ChatCompletions 端点。如果你只用一个协议,选它最稳。
Responses API 是 OpenAI 的新协议,上下文可以交给服务端托管(拿个 response id 续上就行),还内置了一些工具能力。功能更丰富,但目前主要 OpenAI 自家和少数兼容网关支持。
Messages API 是 Anthropic 的协议。和 ChatCompletions 最大的区别是 system 消息不放在 messages 数组里,而是单独一个参数传。Anthropic 自家和一些兼容网关支持。
三种协议对应的桥接方式也不同,这是下一节代码里会看到的重点。
04 安装与项目搭建
先建个控制台项目:
dotnet new console -n FirstAIChat
cd FirstAIChat
三个示例用到的包不一样。示例 01 和 02 走 OpenAI 系,装一个包就够(它会带上 OpenAI SDK 和 Microsoft.Extensions.AI):
dotnet add package Microsoft.Agents.AI.OpenAI
示例 03 走 Anthropic 系,注意这个包还是预览版,得加 --prerelease:
dotnet add package Microsoft.Agents.AI.Anthropic --prerelease
三个示例都想跑的话,两个包都装上,不冲突。
然后是环境变量。三个示例统一读三个变量,密钥不进代码:
| 变量 | 含义 | 示例值 |
|---|---|---|
LLM_BASE_URL |
模型服务端点 | https://dashscope.aliyuncs.com/compatible-mode/v1 |
LLM_API_KEY |
API 密钥 | sk-xxxx |
LLM_MODEL |
模型名 | deepseek-v4-flash |
下面以一个同时支持三种协议的 OpenAI 兼容网关为例(比如阿里云 DashScope),换其他服务商只需改环境变量。
PowerShell 设环境变量:
$env:LLM_BASE_URL = "https://你的服务端点/v1"
$env:LLM_API_KEY = "你的密钥"
$env:LLM_MODEL = "你的模型名"
bash 的话:
export LLM_BASE_URL="https://你的服务端点/v1"
export LLM_API_KEY="你的密钥"
export LLM_MODEL="你的模型名"
下面三个示例,挑你能跑通的协议试。三个都跑通当然更好,能直观感受"换协议不换上层代码"。
05 示例 01:OpenAI ChatCompletions
完整代码:
using System.ClientModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
/// <summary>
/// 示例 01:使用 OpenAI 兼容接口的 ChatCompletions 进行多轮对话
/// 使用 session 维护上下文
/// </summary>
public class _01_ChatAgent_OpenAI_ChatCompletions
{
public static async Task RunAsync()
{
// 第三方大模型配置全部走环境变量(安全约束:密钥不进配置文件)
var baseUrl = Environment.GetEnvironmentVariable("LLM_BASE_URL")
?? throw new InvalidOperationException(
"请设置环境变量 LLM_BASE_URL,例如 https://dashscope.aliyuncs.com/compatible-mode/v1");
var apiKey = Environment.GetEnvironmentVariable("LLM_API_KEY")
?? throw new InvalidOperationException("请设置环境变量 LLM_API_KEY");
var model = Environment.GetEnvironmentVariable("LLM_MODEL") ?? "deepseek-v4-flash";
// 1. OpenAI 兼容客户端:Endpoint 指向第三方 baseUrl
var openAIClient = new OpenAIClient(
new ApiKeyCredential(apiKey),
new OpenAIClientOptions { Endpoint = new Uri(baseUrl) });
// 2. 包装为 IChatClient(桥接为统一的聊天客户端抽象)
IChatClient chatClient = openAIClient.GetChatClient(model).AsIChatClient();
// 3. 包装为 AIAgent(统一 agent 抽象,后续可加 Tools)
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Name = "FirstAIChat",
Description = "第一个 AI Agent",
ChatOptions = new ChatOptions
{
Instructions = "你是一个乐于助人的 AI 助手,请用简体中文回答。",
},
});
// 4. 多轮控制台对话(AgentSession 自动维护上下文)
var session = await agent.CreateSessionAsync();
Console.WriteLine($"AI 聊天已就绪(模型:{model}),输入 exit 退出。");
while (true)
{
Console.Write("\n你 > ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Trim() == "exit")
{
break;
}
var response = await agent.RunAsync(input, session);
Console.WriteLine($"AI > {response.Text}");
}
}
}
逐段看。
第一步,读环境变量,拿到 baseUrl、apiKey、model。三个示例都这么开头,密钥绝不进配置文件。
第二步,建 OpenAIClient,把 Endpoint 指向你的服务端点。这里用的是 OpenAI 官方 SDK 的客户端,但 endpoint 可以指向任何 OpenAI 兼容服务。
第三步是桥接的关键:
IChatClient chatClient = openAIClient.GetChatClient(model).AsIChatClient();
GetChatClient 拿到 OpenAI 的聊天客户端,AsIChatClient 把它包装成 IChatClient。这就是第 02 节说的第一层抽象接入的地方。
第四步,再往上包一层 AIAgent:
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Name = "FirstAIChat",
Description = "第一个 AI Agent",
ChatOptions = new ChatOptions
{
Instructions = "你是一个乐于助人的 AI 助手,请用简体中文回答。",
},
});
这里 Instructions 就是系统提示词。Name 和 Description 后面做多代理编排时会用到。
第五步,建 session,进多轮循环:
var session = await agent.CreateSessionAsync();
然后 while 循环里读用户输入,调 agent.RunAsync(input, session),session 自动记上下文。你问"我叫张三",下一句问"我叫什么",它答得上来。
06 示例 02:OpenAI Responses API
完整代码:
using System.ClientModel;
using Microsoft.Agents.AI;
using OpenAI;
using OpenAI.Responses;
/// <summary>
/// 示例 02:使用 OpenAI 接口的 Responses API 进行多轮对话
/// 使用 session 维护上下文
/// </summary>
public class _02_ChatAgent_OpenAI_ResponsesAPI
{
public static async Task RunAsync()
{
// 第三方大模型配置全部走环境变量(安全约束:密钥不进配置文件)
var baseUrl = Environment.GetEnvironmentVariable("LLM_BASE_URL")
?? throw new InvalidOperationException(
"请设置环境变量 LLM_BASE_URL,例如 https://dashscope.aliyuncs.com/compatible-mode/v1");
var apiKey = Environment.GetEnvironmentVariable("LLM_API_KEY")
?? throw new InvalidOperationException("请设置环境变量 LLM_API_KEY");
var model = Environment.GetEnvironmentVariable("LLM_MODEL") ?? "deepseek-v4-flash";
// 1. OpenAI 兼容客户端:Endpoint 指向第三方 baseUrl
var openAIClient = new OpenAIClient(
new ApiKeyCredential(apiKey),
new OpenAIClientOptions { Endpoint = new Uri(baseUrl) });
// 2. Responses API 路径:GetResponsesClient -> AsAIAgent
ResponsesClient responsesClient = openAIClient.GetResponsesClient();
// 3. 包装为 AIAgent(统一 agent 抽象,后续可加 Tools)
AIAgent agent = responsesClient.AsAIAgent(
model: model,
instructions: "你是一个乐于助人的 AI 助手,请用简体中文回答。",
name: "FirstAIChat",
description: "第一个 AI Agent");
// 4. 多轮控制台对话(AgentSession 自动维护上下文)
var session = await agent.CreateSessionAsync();
Console.WriteLine($"AI 聊天已就绪(模型:{model}),输入 exit 退出。");
while (true)
{
Console.Write("\n你 > ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Trim() == "exit")
{
break;
}
var response = await agent.RunAsync(input, session);
Console.WriteLine($"AI > {response.Text}");
}
}
}
和示例 01 比,桥接路径变了。这次不走 IChatClient,而是直接拿 ResponsesClient 再 AsAIAgent:
ResponsesClient responsesClient = openAIClient.GetResponsesClient();
AIAgent agent = responsesClient.AsAIAgent(
model: model,
instructions: "...",
name: "FirstAIChat",
description: "第一个 AI Agent");
GetResponsesClient 拿到的是 OpenAI Responses API 的客户端,AsAIAgent 直接把它包成 AIAgent,不需要中间那层 IChatClient。参数也换成了更直接的 model、instructions、name、description。
后面的 session 和多轮循环一模一样。好处是底层从 ChatCompletions 换成 Responses,上层代码几乎不用改。
07 示例 03:Anthropic Messages API
完整代码:
using Microsoft.Agents.AI;
using Anthropic;
using Anthropic.Core;
/// <summary>
/// 示例 03:使用 Anthropic Messages API 进行多轮对话
/// 使用 session 维护上下文
/// </summary>
public class _03_ChatAgent_Anthropic_MessagesAPI
{
public static async Task RunAsync()
{
// 第三方大模型配置全部走环境变量(安全约束:密钥不进配置文件)
var baseUrl = Environment.GetEnvironmentVariable("LLM_BASE_URL")
?? throw new InvalidOperationException(
"请设置环境变量 LLM_BASE_URL,例如 https://dashscope.aliyuncs.com/compatible-mode/v1");
var apiKey = Environment.GetEnvironmentVariable("LLM_API_KEY")
?? throw new InvalidOperationException("请设置环境变量 LLM_API_KEY");
var model = Environment.GetEnvironmentVariable("LLM_MODEL") ?? "deepseek-v4-flash";
// baseUrl 特殊处理:部分兼容网关的 Anthropic 端点路径与 OpenAI 端点不同
baseUrl = baseUrl.Replace("/v1", "/anthropic");
// 1. Anthropic 官方 SDK 客户端,指向第三方兼容网关
AnthropicClient client = new(new ClientOptions
{
ApiKey = apiKey,
BaseUrl = baseUrl,
ExtraHeaders = new Dictionary<string, string>
{
["User-Agent"] = "TryAIAgent-FirstAIChat/1.0", //会被加在默认UA后面
},
});
// 2. 包装为 AIAgent(Microsoft Agent Framework Anthropic provider)
AIAgent agent = client.AsAIAgent(
model: model,
instructions: "你是一个乐于助人的 AI 助手,请用简体中文回答。",
name: "FirstAIChat",
description: "第一个 AI Agent");
// 3. 多轮控制台对话(AgentSession 自动维护上下文)
var session = await agent.CreateSessionAsync();
Console.WriteLine($"AI 聊天已就绪(模型:{model}),输入 exit 退出。");
while (true)
{
Console.Write("\n你 > ");
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Trim() == "exit")
{
break;
}
var response = await agent.RunAsync(input, session);
Console.WriteLine($"AI > {response.Text}");
}
}
}
这次换了 SDK,用 Anthropic 官方客户端。有个细节要注意:
baseUrl = baseUrl.Replace("/v1", "/anthropic");
这一行在做路径替换。很多服务商的 OpenAI 兼容端点是 .../v1 结尾,但 Anthropic 兼容端点往往是另一个路径。比如阿里云 DashScope 的 OpenAI 兼容端点是 .../compatible-mode/v1,Anthropic 兼容端点是 .../compatible-mode/anthropic,所以这里把 /v1 替换成 /anthropic。
这不是通用做法,取决于你的服务商。有些服务商 Anthropic 端点和 OpenAI 端点压根是两个域名,那就直接在环境变量里配对,不用这行替换。用的时候按你自己的服务商调整。
建好客户端后,桥接方式和示例 02 一样,直接 AsAIAgent:
AIAgent agent = client.AsAIAgent(
model: model,
instructions: "...",
name: "FirstAIChat",
description: "第一个 AI Agent");
后面的多轮循环还是那套。三种协议,三段代码,最后都落到 agent.RunAsync(input, session)。
08 小结与下篇预告
三种协议都跑通了,回头看整条链路:底层是各家 SDK(OpenAI、Anthropic),中间靠 IChatClient 和 AsAIAgent 桥接成统一的 AIAgent,上层用 AgentSession 管多轮上下文。换协议只动桥接那几行,业务逻辑不动。
不过现在这个 agent 还只是个"能聊天的",没工具、没记忆、不会协作。系列下一篇讲工具调用(Tool Calling),让 agent 能查天气、能算数、能调你的接口;再往后是 MCP(Model Context Protocol)接入和 Skills,把 agent 的能力从"聊天"升级到"干活"。
先把这三个示例跑通,下篇见。

浙公网安备 33010602011771号