Loading

微软 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.AIIChatClient 只是"能聊天",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 就是系统提示词。NameDescription 后面做多代理编排时会用到。

第五步,建 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,而是直接拿 ResponsesClientAsAIAgent

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),中间靠 IChatClientAsAIAgent 桥接成统一的 AIAgent,上层用 AgentSession 管多轮上下文。换协议只动桥接那几行,业务逻辑不动。

不过现在这个 agent 还只是个"能聊天的",没工具、没记忆、不会协作。系列下一篇讲工具调用(Tool Calling),让 agent 能查天气、能算数、能调你的接口;再往后是 MCP(Model Context Protocol)接入和 Skills,把 agent 的能力从"聊天"升级到"干活"。

先把这三个示例跑通,下篇见。

posted @ 2026-08-13 23:00  拓荒者IT  阅读(17)  评论(0)    收藏  举报