Skip to content

Microsoft Agent Framework 入门实战(C#)

适用读者:有 C# 和 .NET 开发基础,想系统学习微软 AI Agent 框架的开发者 本文目标:基于官方 Microsoft Agent Framework SDK,6 个步骤从零到生产 更新日期:本文基于 2026 年 4 月发布的 Microsoft Agent Framework SDK


前置准备

环境要求

  • .NET 8 SDK 或更高版本
  • Azure 订阅(可使用免费试用账户)
  • 已部署的 Azure OpenAI 资源(或 OpenAI API Key)
  • Visual Studio 2022 / VS Code + C# Dev Kit

官方 NuGet 包

Microsoft Agent Framework 的核心包:

包名用途
Azure.AI.ProjectsAzure AI 项目客户端,Agent 的入口点
Azure.IdentityAzure 身份认证(DefaultAzureCredential)
Microsoft.Agents.AIAgent 核心库(AIAgent 基类)
Microsoft.Agents.AI.Workflows工作流引擎
Microsoft.Agents.AI.HostingASP.NET Core 托管

MCP 开发助手

Microsoft Learn 提供了 MCP(Model Context Protocol)端点,可以让 AI 开发工具直接搜索官方文档:

yaml
# ~/.hermes/config.yaml
mcp_servers:
  microsoft-learn:
    url: "https://learn.microsoft.com/api/mcp"
    timeout: 30

配置后,Agent 开发工具可以调用 microsoft_docs_searchmicrosoft_code_sample_searchmicrosoft_docs_fetch 三个工具,直接在编码过程中查询最新官方文档。

关于 Streamable HTTP 传输方式:Microsoft Learn 的 MCP 端点使用的是 Streamable HTTP 传输方式,而不是传统的 stdio 传输。这意味着客户端需要同时接受 application/jsontext/event-stream 两种 Content-Type。第一次 POST 请求返回的是 SSE(Server-Sent Events)格式的响应,包含 JSON-RPC 2.0 格式的消息。Hermes Agent 的内置 MCP 客户端已经支持这种传输方式,你只需要把上面的配置添加到 config.yaml 中,重启 Hermes 后就能自动发现可用的 MCP 工具。这些工具可以直接用来在写代码时搜索 Microsoft 文档中的代码示例和 API 参考,大大提高开发效率。

关于 MCP 配置的说明

如果你正在使用 Hermes Agent 阅读本文档,MCP 端点配置已经自动添加到了你的 Hermes 配置中。这意味着 Hermes 启动时会自动连接到 Microsoft Learn 的 MCP 服务器,发现并注册以下工具:

  • microsoft_docs_search:搜索 Microsoft Learn 文档库,支持关键词和过滤条件
  • microsoft_code_sample_search:搜索代码示例,按编程语言和产品筛选
  • microsoft_docs_fetch:获取指定文档的完整内容

当你在编码过程中遇到 API 用法不确定的情况,可以直接让 Hermes 使用这些工具查询官方文档,而不需要手动浏览网页。MCP 工具返回的是经过结构化处理的文档内容,比直接浏览网页更高效——搜索工具返回匹配的文档列表,获取工具返回完整页面内容,代码搜索工具专门针对代码片段进行索引。这三个工具组合使用,覆盖了日常开发中绝大部分的文档查询需求。需要注意的是,MCP 端点是远程服务,需要网络连接才能使用。如果你在离线环境中开发,建议预先下载官方文档的离线版本。


了解 Microsoft Agent Framework 的架构

Microsoft Agent Framework 由多层架构组成,理解这些层有助于你快速定位所需的功能:

┌─────────────────────────────────────────────────────────┐
│                   应用层(你的 App)                       │
├─────────────────────────────────────────────────────────┤
│  ┌──────────────────┐  ┌──────────────────────────────┐  │
│  │  托管层 Hosting   │  │  协议层 A2A / OpenAI / AG-UI  │  │
│  │  (DI注册/工作流)   │  │  (对外暴露 Agent)            │  │
│  └────────┬─────────┘  └──────────────┬───────────────┘  │
├───────────┴───────────────────────────┴─────────────────┤
│  ┌────────────────────────────────────────────────────┐  │
│  │            Agent 核心层                             │  │
│  │  ChatClientAgent · AIAgent · AgentSession          │  │
│  │  工具调用 · 多轮对话 · 记忆 · 流式响应              │  │
│  └────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────┤
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐  │
│  │ OpenAI   │ │ Azure    │ │ Ollama   │ │ Anthropic  │  │
│  │ IChatClient│ │ IChatClient│ │ IChatClient│ │ IChatClient│  │
│  └──────────┘ └──────────┘ └──────────┘ └────────────┘  │
├─────────────────────────────────────────────────────────┤
│  向量数据库 · 聊天历史 · Context Provider · 安全检查     │
└─────────────────────────────────────────────────────────┘

这张图展示的是 Microsoft Agent Framework 的完整技术栈,从上到下分为四个层次。最上层是应用层,即你的 ASP.NET Core 项目或 Console 应用程序,它负责启动 Agent 并处理用户输入。第二层是托管与协议层,由 Microsoft.Agents.AI.Hosting 提供——它负责把 Agent 注册到 ASP.NET Core 的依赖注入容器中,并通过 A2A(Agent-to-Agent)协议、OpenAI 兼容端点或 AG-UI 协议对外暴露。第三层是 Agent 核心层,这是本文重点讲解的部分,包含 AIAgent 基类、ChatClientAgent 实现、AgentSession 会话管理以及工具调用和流式响应等核心能力。最底层是基础设施层,包括不同 AI 提供商的 IChatClient 实现、向量数据库集成、聊天历史存储和安全检查机制。

理解这个分层架构很重要,因为你开发 Agent 时往往不会只用到某一层。举个例子:你定义了一个 ChatClientAgent(核心层),然后在 Program.cs 中用 builder.AddAIAgent() 注册到 DI(托管层),最后通过 app.MapA2A() 以 A2A 协议对外公开(协议层)。这三层是一起工作的,缺少任何一层 Agent 都无法真正服务于用户。


Step 1:第一个 Agent — 创建与调用

创建项目

bash
mkdir MyFirstAgent && cd MyFirstAgent
dotnet new console -n Step1_HelloAgent
cd Step1_HelloAgent
dotnet add package Azure.AI.Projects --prerelease
dotnet add package Azure.Identity
dotnet add package Microsoft.Agents.AI

这里安装的三个包各自的作用值得花一分钟理解,因为这是 Microsoft Agent Framework 区别于其他 AI 框架的核心设计:

Azure.AI.Projects 是 Azure AI 平台的项目级客户端。它管理着你的 AI 资源连接——你在 Azure AI Foundry 中创建的 project、部署的模型、关联的数据源,都通过这个客户端访问。AIProjectClient 是 Agent 的入口点,它不做 AI 推理,而是负责资源的连接管理和认证。

Azure.Identity 是 Azure SDK 的统一认证库。DefaultAzureCredential 会自动尝试多种认证方式:先检查环境变量 AZURE_CLIENT_ID/AZURE_TENANT_ID/AZURE_CLIENT_SECRET,然后检查 AZURE_USERNAME/AZURE_PASSWORD,接着尝试 Azure CLI 的登录缓存(az login),最后尝试托管标识(Managed Identity)。这种链式尝试在开发环境中很方便——你只需要在本地执行一次 az login,代码里不需要写任何凭据。但在生产环境中,建议明确指定 ManagedIdentityCredentialClientSecretCredential,避免因回退机制的不可预测性导致延迟或认证失败。

Microsoft.Agents.AI 是 Agent 的核心库。它定义了 AIAgent 基类、ChatClientAgent 实现、AgentSession 会话管理、以及 RunAsync/RunStreamingAsync 等核心方法。这个包不依赖特定的 AI 提供商,你可以通过不同的 IChatClient 实现来接入 OpenAI、Azure OpenAI、Anthropic 或任何兼容的 LLM 服务。

配置 Azure OpenAI 端点

在终端设置环境变量:

bash
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"

编写第一个 Agent

csharp
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;

// 从环境变量读取 Azure OpenAI 端点
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
    ?? throw new InvalidOperationException("请设置 AZURE_OPENAI_ENDPOINT");

var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")
    ?? "gpt-4o-mini";

// 第一步:创建 AIProjectClient(Azure AI 项目客户端)
// AIProjectClient 是 Agent 的入口点,它管理模型连接和资源
var projectClient = new AIProjectClient(
    new Uri(endpoint),
    new DefaultAzureCredential());

// 第二步:通过 AsAIAgent 扩展方法创建 Agent
// AsAIAgent 把 AIProjectClient 包装成一个 AIAgent 实例
AIAgent agent = projectClient.AsAIAgent(
    model: deploymentName,
    instructions: "你是一个友好的助手。回答简洁明了。",
    name: "HelloAgent");

// 第三步:运行 Agent(非流式)
// RunAsync 发送消息并等待完整回复
var response = await agent.RunAsync("法国最大的城市是哪个?");
Console.WriteLine(response);

运行这段代码,控制台会输出 Agent 的回答。RunAsync 方法是 Agent 的核心调用方式,它接受用户输入,由 Agent 框架自动处理与 LLM 的通信、工具调用(如果已注册)、上下文管理等一系列复杂操作,最后返回一个 AgentResponse 对象。这个对象包含 AI 生成的文本内容,以及可能的工具调用结果、文件引用等结构化数据。

关于 DefaultAzureCredential 的说明DefaultAzureCredential 是 Azure SDK 推荐的开发阶段认证方式。它的工作方式是"链式尝试"——按顺序尝试多种认证来源,直到其中一个成功。默认尝试顺序是:环境变量 > Azure CLI > Visual Studio > Managed Identity。这意味着如果你在本地用 az login 登录了 Azure CLI,代码中不需要任何额外的配置就能使用。但在生产环境中,建议替换为 ManagedIdentityCredential(Azure 托管环境中)或 ChainedTokenCredential(精确控制尝试顺序),避免因意外回退导致的延迟和不必要的鉴权请求。

流式响应

csharp
// 流式输出——每个 Token 生成后立即返回
await foreach (var update in agent.RunStreamingAsync("给我讲一个有趣的事实。"))
{
    Console.Write(update);
}
Console.WriteLine();

流式输出的原理:Agent 不是等 LLM 生成完完整回复才返回,而是每生成一个 Token 就立即通过 RunStreamingAsync 返回给调用者。用户界面可以实现"打字机效果",大幅改善等待体验。

完整交互式终端

csharp
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;

var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException();
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";

AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
    .AsAIAgent(model: deploymentName, instructions: "你是一个友好的助手。回答简洁明了。", name: "ChatBot");

Console.WriteLine("🤖 Agent 已就绪!输入 'exit' 退出。\n");

while (true)
{
    Console.Write("🧑 ");
    var input = Console.ReadLine();
    if (string.IsNullOrEmpty(input) || input == "exit") break;

    Console.Write("🤖 ");
    await foreach (var update in agent.RunStreamingAsync(input))
    {
        Console.Write(update);
    }
    Console.WriteLine("\n");
}

Step 2:添加工具 — 让 Agent 能做实事

Agent 和普通 Chat API 最大的区别:Agent 可以调用你定义的工具函数。

Function Calling 原理

大语言模型本身不执行代码。Function Calling 的工作流程:

用户:"北京今天多少度?"

① Agent 把用户问题 + 可用工具描述发给 LLM
② LLM 分析后决定调用 get_weather(city="北京")
③ LLM 返回 JSON:{name:"get_weather", arguments: {city:"北京"}}
④ Agent 执行 GetWeather("北京") → "25°C,晴"
⑤ Agent 把结果传回给 LLM
⑥ LLM 基于结果生成回答:"北京今天 25°C,天气晴朗。"

关键理解:LLM 只决定"该调用哪个函数",真正执行的是你的 C# 代码

定义工具函数

csharp
using System.ComponentModel;

public class WeatherTools
{
    [Description("根据城市名获取当前天气信息")]
    public string GetWeather(
        [Description("城市名,中文,如:北京、上海")] string city)
    {
        var data = new Dictionary<string, (double temp, string desc)>
        {
            ["北京"] = (25.0, "晴 ☀️"),
            ["上海"] = (28.0, "多云 ⛅"),
            ["广州"] = (32.0, "阵雨 🌦️"),
            ["深圳"] = (30.0, "多云转阴 ☁️"),
        };

        return data.TryGetValue(city, out var w)
            ? $"📍 {city}:{w.temp}°C,{w.desc}"
            : $"❌ 没有 {city} 的数据";
    }
}

[Description] 特性至关重要——LLM 通过这些描述来判断"这个工具是干什么的"以及"参数应该怎么填"。描述越清晰,LLM 调用的准确率越高。

工具设计的原则

通过上面的例子,可以总结出工具设计的几个核心原则。首先是描述精准原则[Description] 是你和 LLM 之间的"合同",描述不仅要说明工具"是什么",还要说明"什么时候该用它"。比如"获取天气"太模糊,改成"根据城市名获取当前天气数据。当用户询问某城市的天气、温度或气候情况时调用此工具"要好得多。描述中甚至可以包含"不要在用户问历史天气时调用"这样的负面提示。

其次是参数友好原则:参数的类型和格式要让 LLM 容易推断。例如 city 参数注明"中文城市名",LLM 就知道应该传"北京"而不是"Beijing"。如果是日期参数,注明格式"yyyy-MM-dd"能避免 LLM 猜错格式。如果是枚举值,在描述中列出所有可选值。

第三是错误优雅原则:工具函数应该做好异常处理。如果数据库连接失败、API 超时、参数不合法,工具应该返回友好的错误信息而不是抛出未处理的异常。Agent 框架会把工具返回的文本传给 LLM,所以错误信息也应该是自然语言——"数据库暂时不可用,请稍后重试"就比 "Exception: Connection timeout" 好得多。LLM 看到前者会说"抱歉,查询暂时不可用",看到后者可能不知所措。

第四是幂等性原则:尽可能让工具函数是幂等的——多次调用相同参数应该得到相同结果,且不会产生副作用。特别是查询类的工具,幂等是天然要求。对于写入类的工具(如创建订单、发送邮件),需要考虑重复调用保护。因为 LLM 在 function calling 失败时可能会自动重试,如果工具不是幂等的,同一笔订单可能被创建两次。

注册工具并创建 Agent

csharp
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;  // AIFunctionFactory

var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException();
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";

// 创建带工具的 Agent
// tools 参数接收一个 AIFunction 数组,AIFunctionFactory.Create 自动提取方法的元数据
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
    .AsAIAgent(
        model: deploymentName,
        instructions: "你是天气助手。用户问天气时请使用 GetWeather 工具。",
        tools: [AIFunctionFactory.Create(WeatherTools.GetWeather)]);

// Agent 会自动判断何时调用工具
var reply = await agent.RunAsync("北京和上海今天哪个更热?差几度?");
Console.WriteLine(reply);

多个工具协作

真实场景中一个 Agent 通常需要多个工具配合。创建一个运维工具箱:

csharp
public class OpsTools
{
    [Description("检查指定服务器是否在线")]
    public string PingServer(
        [Description("服务器 IP 地址,如 10.0.0.1")] string host)
    {
        var status = new Dictionary<string, string>
        {
            ["10.0.0.1"] = "🟢 在线 (12ms)",
            ["10.0.0.2"] = "🟢 在线 (8ms)",
            ["10.0.0.3"] = "🔴 离线 (超时)",
        };
        return status.TryGetValue(host, out var s) ? s : "❌ 未知主机";
    }

    [Description("获取服务器的 CPU 和内存使用率")]
    public string GetSystemMetrics(
        [Description("服务器 IP 地址")] string serverIp)
    {
        var data = new Dictionary<string, (double cpu, double mem)>
        {
            ["10.0.0.3"] = (99.1, 95.7),
            ["10.0.0.1"] = (45.2, 62.0),
        };
        if (data.TryGetValue(serverIp, out var m))
            return $"CPU:{m.cpu}% {(m.cpu > 90 ? "⚠️" : "✅")},内存:{m.mem}%";
        return $"❌ 没有 {serverIp} 的监控数据";
    }

    [Description("重启指定服务器。注意:此操作会导致服务中断")]
    public string RebootServer(
        [Description("要重启的服务器 IP")] string serverIp)
    {
        return $"✅ {serverIp} 重启命令已下发,预计 60 秒恢复";
    }
}

// 注册多个工具
var ops = new OpsTools();
AIAgent agent = projectClient.AsAIAgent(
    model: deploymentName,
    instructions: "你是运维工程师助手。收到服务器告警时先检查状态,分析指标,必要时重启。",
    tools: [
        AIFunctionFactory.Create(ops.PingServer),
        AIFunctionFactory.Create(ops.GetSystemMetrics),
        AIFunctionFactory.Create(ops.RebootServer),
    ]);

Agent 会自动编排工具调用顺序:先 Ping 检测离线,再查系统指标,分析错误原因,最后执行重启。


Step 3:多轮次对话 — 使用 Session 保持状态

单轮对话中,Agent 每次调用都是独立的——第二次问"那物流到哪了?"时,Agent 已经忘了刚才说的是哪个订单。多轮对话通过 AgentSession 解决这个问题。

什么是 AgentSession?

AgentSession 是跨 Agent 运行的会话状态容器。它在多次 RunAsync 调用之间保存上下文:

csharp
// 创建会话
AgentSession session = await agent.CreateSessionAsync();

// 第一轮
var r1 = await agent.RunAsync("我叫张三,是一名 .NET 开发者。", session);

// 第二轮——Agent 记得用户叫张三
var r2 = await agent.RunAsync("我的职业是什么?", session);
// ✅ 回复:你是 .NET 开发者

// 第三轮——继续深入
var r3 = await agent.RunAsync("帮我推荐几本 C# 进阶的书。", session);

每一轮调用后,Agent 框架自动把本轮的用户消息和 AI 回复追加到 session 中。后续调用带着完整历史上下文,所以 Agent 知道"职业"指的是"刚说的 .NET 开发者"。

Session 的序列化与恢复

AgentSession 可以序列化保存,也可以从序列化数据恢复——这在 Web 应用中至关重要:

csharp
// 序列化会话(存到文件/数据库/Redis)
var serialized = agent.SerializeSession(session);
await File.WriteAllTextAsync($"session_{userId}.json", serialized);

// 从序列化恢复会话
var savedSessionData = await File.ReadAllTextAsync($"session_{userId}.json");
AgentSession restoredSession = await agent.DeserializeSessionAsync(savedSessionData);

// 用恢复的会话继续对话
var reply = await agent.RunAsync("我们刚才说到哪了?", restoredSession);

完整的对话管理

Agent 的多轮对话能力是 Agent 区别于普通 LLM API 调用的核心特征之一。没有 AgentSession 的情况下,每次 RunAsync 调用都是独立的——Agent 不记得上次说过什么。有了 AgentSession,Agent 在调用时会自动把历史消息拼接到用户消息前面,让 LLM 看到完整的对话上下文实现"记得"的效果。

在实现上,AgentSession 内部维护了一个消息列表。每次 RunAsync 调用时,Agent 框架会:

  1. 从 Session 中读取历史消息
  2. 拼接新的用户消息
  3. 发送给 LLM
  4. 把 LLM 的回复追加到 Session 中

这看起来简单,但生产环境中需要考虑几个实际问题。首先是并发安全——多个请求同时操作同一个 Session 可能导致消息乱序。解决方案是为每个用户会话使用独占的 Session 实例,或者实现 Session 的锁机制。

其次是存储持久化——AgentSession 默认存在于内存中,服务器重启后丢失。生产环境需要实现 Session 的序列化存储。上面的 ConversationManager 类展示了基于文件的持久化方案,适用于单机部署。分布式部署建议使用 Redis 或 Cosmos DB 来存储序列化的 Session 数据,这样即使某个服务器实例宕机,其他实例也能从共享存储中恢复会话。

第三是上下文窗口管理——LLM 的上下文窗口是有限的(GPT-4o-mini 支持 128K tokens)。随着对话轮次增加,历史消息越来越多。当接近 Token 上限时,LLM 要么报错(超过硬限制),要么静默丢弃最早的消息(导致"失忆")。解决办法是定期检查 Session 中的 Token 总数,在接近上限时把早期对话压缩为一段摘要,用摘要替换掉早期的详细对话。SerializeSessionDeserializeSessionAsync 方法让这种操作成为可能——你可以把 Session 序列化成 JSON,修改消息列表后反序列化回来。

csharp
public class ConversationManager
{
    private readonly AIAgent _agent;
    private readonly string _storagePath;

    public ConversationManager(AIAgent agent, string storagePath)
    {
        _agent = agent;
        _storagePath = storagePath;
    }

    public async Task<AgentSession> GetOrCreateSessionAsync(string userId)
    {
        var sessionFile = Path.Combine(_storagePath, $"session_{userId}.json");
        if (File.Exists(sessionFile))
        {
            var data = await File.ReadAllTextAsync(sessionFile);
            return await _agent.DeserializeSessionAsync(data);
        }
        return await _agent.CreateSessionAsync();
    }

    public async Task SaveSessionAsync(string userId, AgentSession session)
    {
        var serialized = _agent.SerializeSession(session);
        var sessionFile = Path.Combine(_storagePath, $"session_{userId}.json");
        Directory.CreateDirectory(_storagePath);
        await File.WriteAllTextAsync(sessionFile, serialized);
    }

    public async Task<string> ChatAsync(string userId, string message)
    {
        var session = await GetOrCreateSessionAsync(userId);
        var response = await _agent.RunAsync(message, session);
        await SaveSessionAsync(userId, session);
        return response.ToString();
    }
}

Step 4:内存和持久性 — 通过 Context Provider 注入持久上下文

多轮对话只在当前 Session 存活期内有效。重启程序后 Session 丢失了。持久化上下文让 Agent 能在不同会话间记住用户信息。

ChatHistoryProvider

Agent 的对话历史通过 ChatHistoryProvider 管理。默认使用 InMemoryChatHistoryProvider(内存中),你可以传入自定义实现:

csharp
// 默认:InMemoryChatHistoryProvider
AIAgent agent = projectClient.AsAIAgent(
    model: deploymentName,
    options: new ChatClientAgentOptions
    {
        Instructions = "你是友好的助手。",
    });

// 可以传入自定义 ChatHistoryProvider
AIAgent agent = projectClient.AsAIAgent(
    model: deploymentName,
    options: new ChatClientAgentOptions
    {
        Instructions = "你是友好的助手。",
        ChatHistoryProvider = new CustomChatHistoryProvider()
    });

基于文件的持久化实现

csharp
public class FileChatHistoryProvider : ChatHistoryProvider
{
    private readonly string _filePath;
    private List<ChatMessage> _messages = new();

    public FileChatHistoryProvider(string filePath)
    {
        _filePath = filePath;
        LoadAsync().GetAwaiter().GetResult();
    }

    public override IReadOnlyList<ChatMessage> Messages => _messages.AsReadOnly();

    public override void AddMessage(ChatMessage message)
    {
        _messages.Add(message);
    }

    public override async Task SaveAsync()
    {
        var json = System.Text.Json.JsonSerializer.Serialize(_messages);
        await File.WriteAllTextAsync(_filePath, json);
    }

    private async Task LoadAsync()
    {
        if (File.Exists(_filePath))
        {
            var json = await File.ReadAllTextAsync(_filePath);
            _messages = System.Text.Json.JsonSerializer
                .Deserialize<List<ChatMessage>>(json) ?? new();
        }
    }
}

基于 Session 的上下文共享

更推荐的方式是使用 AgentSession 来跨 Agent 运行共享上下文:

csharp
// 创建会话
AgentSession session = await agent.CreateSessionAsync();

// 第一轮对话告诉 Agent 个人信息
var r1 = await agent.RunAsync("记住:我叫张三,喜欢简洁的回答风格。", session);

// 模拟新的 Agent 调用(中间可能经过了其他处理)
// 同一个 Session 中 Agent 记得用户偏好
var r2 = await agent.RunAsync("你还记得我吗?", session);
// ✅ 回复:当然记得!你是张三,喜欢简洁的回答风格。

// 第三轮验证
var r3 = await agent.RunAsync("我的名字是什么?用两个字回答。", session);
// ✅ 回复:张三

补充概念:Agent 的类型与执行模式

在进入工作流之前,有必要了解 Microsoft Agent Framework 中 Agent 的不同类型和执行模式,这有助于你理解 Workflow 在架构中的位置。

Agent 的核心类型

AIAgent 是 Agent 的抽象基类,它定义了所有 Agent 的统一接口。目前框架提供了三种主要的 Agent 类型:

ChatClientAgent 是最常用的 Agent 类型。它包装了一个 IChatClient(即底层的 AI 模型连接),提供标准的对话体验。ChatClientAgent 支持函数调用、多轮对话、流式输出和结构化输出。当你用 AIProjectClient.AsAIAgent() 创建 Agent 时,底层实际创建的就是一个 ChatClientAgent。它的运行模型是"接收用户消息 → 与 LLM 交互 → 可能需要调用工具 → 返回回复"的循环。

自定义 Agent 通过继承 AIAgent 基类来实现。如果 ChatClientAgent 的默认行为不满足你的需求——比如你想在每次 Agent 响应前都做一次安全检查,或者你想用一个自定义的调度逻辑来控制 Agent 的决策流程——你可以继承 AIAgent 并重写它的核心方法。这种方式给了你对 Agent 行为的完全控制权,但代价是你需要自己管理与 LLM 的通信和工具调用的协调。

远程 Agent 代理 是一个特殊的 Agent 类型,它不代表本地运行的 Agent,而是通过 A2A 协议连接到远端的一个 Agent 实例。当你构建多 Agent 系统时,远程 Agent 代理让你可以像调用本地 Agent 一样调用其他服务上的 Agent,A2A 协议负责底层的网络通信、任务调度和状态同步。

执行模式:RunAsync 和 RunStreamingAsync

无论哪种 Agent 类型,都通过两个核心方法执行:

RunAsync 是同步(阻塞式)执行。Agent 发送消息给 LLM,等待完整回复,然后返回一个 AgentResponse 对象。AgentResponse 包含完整的回复内容,你可以通过 .Text 属性获取文本,或者遍历内容集合获取结构化的数据(如工具调用结果、文件引用等)。非流式模式适合后台处理、批处理操作、以及不需要实时交互的场景。

RunStreamingAsync 是异步流式执行。Agent 发送消息给 LLM,然后立即返回一个 IAsyncEnumerable<AgentResponseUpdate>。调用者通过 await foreach 遍历更新,每次迭代收到一个增量数据块。这些数据块可能是文本片段、工具调用进度、或者元数据更新。流式模式适合聊天应用、实时仪表盘、以及任何需要即时反馈的场景。

响应内容的结构化处理

AgentResponse 的响应内容是一个 IEnumerable<IAgentContent> 集合,每个元素可以是以下类型之一:

  • TextContent:文本片段,最常见的响应类型
  • DataContent:二进制数据(如图片、文件),以 Base64 编码
  • UriContent:指向外部资源的 URI 引用
  • FunctionCallContent:Agent 决定调用的工具函数信息
  • FunctionResultContent:工具函数的执行结果

这种结构化设计意味着 Agent 的响应不只是"一段文字"——它可以同时包含文字解释、一张图片、一个文件链接、以及一个工具调用结果。你的客户端代码可以根据内容类型进行不同的渲染。


Step 5:工作流 — 撰写多步骤任务

单个 Agent 的单个调用能做的事情有限。真实业务中的需求往往是多步骤的:

"帮我处理退款,订单号 ORD-2024-001"

背后可能是:查订单 → 验证退款资格 → 计算金额 → 执行退款 → 发通知 → 记入 CRM。

工作流核心概念

Microsoft Agent Framework 的工作流引擎包含:

  • Executor:处理单元(可以是 AI Agent,也可以是自定义逻辑)
  • Edge:连接 Executor 的路径
  • WorkflowBuilder:构建有向图
  • InProcessExecution:执行工作流

定义 Executor 步骤

Executor 是工作流的基本处理单元。有两种定义方式:

方式一:继承 Executor 基类

csharp
using Microsoft.Agents.AI.Workflows;

// 自定义 Executor:反转文本
class ReverseTextExecutor : Executor<string, string>
{
    public ReverseTextExecutor() : base("ReverseText") { }

    public override ValueTask<string> HandleAsync(
        string message,
        IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        return ValueTask.FromResult(
            string.Concat(message.Reverse()));
    }
}

方式二:使用 BindAsExecutor 扩展方法

csharp
// 把普通方法转为 Executor
Func<string, string> uppercaseFunc = s => s.ToUpperInvariant();
var uppercase = uppercaseFunc.BindAsExecutor("UppercaseExecutor");

构建和执行工作流

csharp
// 创建 Executor 实例
var uppercase = uppercaseFunc.BindAsExecutor("ToUpper");
ReverseTextExecutor reverse = new();

// 用 WorkflowBuilder 构建有向图
// 第一个参数是起始 Executor
WorkflowBuilder builder = new(uppercase);

// 添加边:uppercase → reverse
// WithOutputFrom 指定工作流的最终输出来自 reverse
builder.AddEdge(uppercase, reverse)
       .WithOutputFrom(reverse);

// 构建工作流
var workflow = builder.Build();

// 执行工作流
// InProcessExecution.RunAsync 启动同步执行
await using Run run = await InProcessExecution.RunAsync(
    workflow, "Hello, World!");

// 读取事件结果
foreach (WorkflowEvent evt in run.NewEvents)
{
    if (evt is ExecutorCompletedEvent completed)
    {
        Console.WriteLine($"{completed.ExecutorId}: {completed.Data}");
    }
}
// 输出:
// ToUpper: HELLO, WORLD!
// ReverseText: !DLROW ,OLLEH

多 Agent 工作流

在实际场景中,你的 Executor 通常是 AI Agent 而不是简单的文本处理函数:

csharp
// 定义两个 Agent
AIAgent orderAgent = projectClient.AsAIAgent(
    model: deploymentName,
    instructions: "你是订单查询专家。根据订单号查询订单信息。",
    tools: [AIFunctionFactory.Create(OrderTools.GetOrderInfo)]);

AIAgent refundAgent = projectClient.AsAIAgent(
    model: deploymentName,
    instructions: "你是退款处理专家。根据订单信息判断退款资格并执行退款。",
    tools: [AIFunctionFactory.Create(RefundTools.ProcessRefund)]);

// 把 Agent 包装为 Executor(结合 Session 保持上下文)
var orderStep = new AgentExecutor("CheckOrder", orderAgent);
var refundStep = new AgentExecutor("ProcessRefund", refundAgent);

// 构建退款工作流
WorkflowBuilder builder = new(orderStep);
builder.AddEdge(orderStep, refundStep)
       .WithOutputFrom(refundStep);

var workflow = builder.Build();
await using var run = await InProcessExecution.RunAsync(
    workflow, "处理退款 ORD-2024-001");

Step 6:托管 Agent — 通过 ASP.NET Core 公开 Agent

Hosting 库的核心 API

Microsoft.Agents.AI.Hosting 提供了在 ASP.NET Core 中注册和配置 Agent 及工作流的基础设施。

注册 AI Agent 到 DI

csharp
using Microsoft.Agents.AI.Hosting;

var builder = WebApplication.CreateBuilder(args);

// 1. 注册 IChatClient(底层 AI 连接)
IChatClient chatClient = new AIProjectClient(
        new Uri(endpoint),
        new DefaultAzureCredential())
    .GetProjectOpenAIClient()
    .GetProjectResponsesClient()
    .AsIChatClient(deploymentName);

builder.Services.AddSingleton(chatClient);

// 2. 注册 Agent(AddAIAgent 是 Hosting 库的核心方法)
var pirateAgent = builder.AddAIAgent(
    "pirate",                              // Agent 名称(用于 DI keyed service)
    instructions: "You are a pirate. Speak like a pirate.",
    description: "An agent that speaks like a pirate.",
    chatClientServiceKey: "chat-model");   // 指定使用哪个 IChatClient

// 3. 链式配置工具和会话存储
builder.AddAIAgent("support", instructions: "你是技术支持工程师。")
    .WithAITool(new TicketTool())                       // 添加工具
    .WithInMemorySessionStore();                        // 内存会话存储

注册工作流

csharp
// 注册多个 Agent
builder.AddAIAgent("agent-1", instructions: "you are agent 1!");
builder.AddAIAgent("agent-2", instructions: "you are agent 2!");

// 注册工作流(顺序执行两个 Agent)
var workflow = builder.AddWorkflow("my-workflow", (sp, key) =>
{
    var agent1 = sp.GetRequiredKeyedService<AIAgent>("agent-1");
    var agent2 = sp.GetRequiredKeyedService<AIAgent>("agent-2");
    return AgentWorkflowBuilder.BuildSequential(key, [agent1, agent2]);
});

// 将工作流转为 AIAgent(以便通过协议暴露)
workflow.AddAsAIAgent();

通过 A2A 协议暴露 Agent

A2A(Agent-to-Agent)是标准的 Agent 间通信协议:

csharp
// 注册 A2A 服务
builder.Services.AddA2AServer();

var app = builder.Build();

// 映射 A2A 端点
app.MapA2A(pirateAgent,
    path: "/a2a/pirate",
    agentCard: new()
    {
        Name = "Pirate Agent",
        Description = "An agent that speaks like a pirate.",
        Version = "1.0"
    });

app.Run();

全部组合:完整的托管示例

csharp
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Hosting.A2A;

var builder = WebApplication.CreateBuilder(args);

// 1. AI 服务
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";

IChatClient chatClient = new AIProjectClient(
        new Uri(endpoint), new DefaultAzureCredential())
    .GetProjectOpenAIClient().GetProjectResponsesClient()
    .AsIChatClient(deploymentName);
builder.Services.AddSingleton(chatClient);

// 2. 注册 Agent
builder.AddAIAgent("support", instructions: "你是技术支持工程师。回答专业简洁。")
    .WithInMemorySessionStore();

// 3. A2A 协议
builder.Services.AddA2AServer();
builder.Services.AddCors(o => o.AddDefaultPolicy(p =>
    p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));

var app = builder.Build();
app.UseCors();

// 4. 映射 A2A 端点
app.MapA2A(agent, path: "/a2a/support",
    agentCard: new() { Name = "Support Agent", Description = "技术支持", Version = "1.0" });

// 5. 健康检查和 HTTP API
app.MapGet("/health", () => Results.Ok(new {
    status = "healthy",
    version = "1.0.0",
    agent = "support"
}));

// 6. 启动服务
// app.Run() 启动 ASP.NET Core 的 Kestrel 服务器,开始监听 HTTP 请求
// 从这一步开始,你的 Agent 不再是本地控制台程序,而是一个真正的网络服务
// 客户端可以通过 A2A 协议或 OpenAI 兼容 API 来调用它
// 多个客户端可以同时连接,每个客户端使用独立的 Session 来隔离对话
app.Run();

执行 dotnet rundocker compose up 启动服务后,你会看到控制台输出 Kestrel 监听的地址(默认 http://localhost:5000)。访问 http://localhost:5000/health 应该看到健康检查的 JSON 响应。如果你想验证 Agent 能否正常工作,可以用 curl 或 Postman 向 A2A 端点发送请求。在实际生产部署中,你还应该添加应用日志、配置 HTTPS 证书、设置请求速率限制、以及对接 Azure Monitor 或 Prometheus 进行监控——这些运维工作与传统 Web 服务完全一致,只是在前面加了一层 Agent 的编排逻辑。

Docker 部署

为了方便部署到任意环境,把 Agent 服务容器化是标准做法。Docker 部署的优势在于环境一致性——你的 Dockerfile 定义了从操作系统到 .NET SDK 到应用程序的完整运行环境,无论在本地开发机、测试服务器还是生产集群上运行,行为都是一致的。

dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["AgentService.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "AgentService.dll"]

深入理解 Agent 架构

Agent 的类型

Microsoft Agent Framework 支持多种 Agent 类型:

Agent 类型基类适用场景
ChatClientAgentAIAgent标准聊天 Agent,支持函数调用、多轮对话
自定义 AgentAIAgent(子类化)完全控制 Agent 行为的场景
远程 Agent 代理AIAgent(A2A 代理)通过 A2A 协议连接远程 Agent
csharp
// ChatClientAgent 是最常用的类型
var chatAgent = new ChatClientAgent(
    chatClient,
    instructions: "You are a helpful assistant.");

// 基类 AIAgent 提供了统一的接口
// 无论底层是什么 Agent 类型,都可以用 RunAsync 调用
AIAgent agent = chatAgent; // 向上转型
var response = await agent.RunAsync("Hello!");

响应内容类型

Agent 的响应可以包含多种内容类型:

csharp
AgentResponse response = await agent.RunAsync("给我画一张饼图");

// 文本内容
var text = response.Text;

// 内容集合中可能包含:
foreach (var content in response)
{
    switch (content)
    {
        case TextContent textContent:
            Console.WriteLine($"文本: {textContent.Text}");
            break;
        case DataContent dataContent:
            Console.WriteLine($"二进制数据: {dataContent.Data.Length} bytes");
            break;
        case UriContent uriContent:
            Console.WriteLine($"URI: {uriContent.Uri}");
            break;
        case FunctionCallContent fnCall:
            Console.WriteLine($"函数调用: {fnCall.Name}");
            break;
        case FunctionResultContent fnResult:
            Console.WriteLine($"函数结果: {fnResult.Result}");
            break;
    }
}

Workflows 的两种 API

工作流引擎提供了两种互补的 API:

特性函数式 API图形 API (WorkflowBuilder)
控制流原生 C#(if/for/await)Edges 和 Conditions
适用场景顺序流水线、自定义循环固定图、扇出/扇入
并行Task.WhenAll并行边组
人机协同直接代码实现RequestInfoExecutor

集成生态

Microsoft Agent Framework 的集成体系非常丰富,可以分为几个层次来理解:

向量存储集成是整个框架最成熟的集成点之一。通过 Microsoft.Extensions.VectorData.Abstractions 这个统一抽象层,你可以在不改变业务代码的前提下切换不同的向量数据库后端。目前支持的实现包括 Azure AI Search、Azure Cosmos DB for MongoDB vCore、Elasticsearch、Pinecone、PostgreSQL(pgvector)、Qdrant、Redis Stack、Weaviate 等 19 种。选择向量数据库时,主要考虑以下因素:如果你已经使用 Azure,Azure AI Search 是最自然的集成选择;如果你需要自托管,Qdrant 或 Weaviate 是成熟的开源方案;如果你只需要轻量级的本地原型验证,Redis Stack 就足够了。

聊天历史 Provider 提供了两种选择:InMemoryChatHistoryProvider 用于开发和测试场景,数据不持久化;CosmosDBChatHistoryProvider 用于生产环境,把对话历史持久化到 Cosmos DB 中,支持跨实例共享和故障恢复。

Memory Provider 为 Agent 添加有状态的长期记忆能力。ChatHistoryMemoryProvider 基于对话历史提供短期记忆,适合记住用户在本次会话中的偏好。而 Neo4jMemoryProvider 基于图数据库的知识图谱记忆,适合存储实体之间的复杂关系——比如用户 A 是项目经理,用户 B 是开发者,他们都在项目 X 中工作。这种图结构的关系记忆在传统的关系数据库或向量数据库中很难高效表达。

RAG Provider 提供了知识增强生成的能力。Neo4jGraphRAGProvider 基于图数据库的 RAG 实现,把文档中的实体关系提取为图结构,在问答时不仅能检索相关文本段落,还能理解实体之间的关联关系。TextSearchProvider 则基于传统的关键词和向量混合搜索,适合结构化的文档检索场景。

A2A 协议集成是 Agent 之间互相通信的标准协议。A2A 定义了四个核心概念:Agent Card(Agent 的发现信息,包括名称、描述、能力)、消息通信(Agent 之间交换的结构化消息)、长期运行任务(支持异步任务的创建、查询和取消)、以及跨平台互操作(不同技术栈的 Agent 可以互相通信)。在 C# 中,A2A 集成通过 Microsoft.Agents.AI.Hosting.A2AMicrosoft.Agents.AI.Hosting.A2A.AspNetCore 两个包实现。

从开发到生产的完整路径

开发者从零到生产通常经历五个阶段:

第一阶段是概念验证,只需要一个 Console 应用、一个 AIAgent 实例和 RunAsync 方法。这个阶段验证"Agent 能不能回答我的问题"。

第二阶段是工具集成,把 Agent 接入真实的业务系统——数据库、API、文件系统。这个阶段验证"Agent 能不能替我做事"。

第三阶段是对话管理,引入 AgentSession 处理多轮对话,并实现会话的序列化和恢复。这个阶段验证"Agent 能不能在真实对话中保持上下文"。

第四阶段是工作流编排,用 WorkflowBuilder 把多个 Agent 和自定义逻辑组合为确定性的业务工作流。这个阶段验证"Agent 能不能处理复杂的业务流程"。

第五阶段是生产部署,用 Microsoft.Agents.AI.Hosting 把 Agent 注册到 ASP.NET Core,通过 A2A 或 OpenAI 兼容端点对外暴露,容器化部署到 Azure Container Apps 或 Kubernetes。


常见问题与最佳实践

认证与配置

Q:DefaultAzureCredential 在本地开发时认证失败怎么办? A:先确认已经执行过 az login 登录 Azure CLI。如果仍然失败,可以设置环境变量 AZURE_TENANT_IDAZURE_CLIENT_IDAZURE_CLIENT_SECRET 来使用服务主体认证。也可以在代码中临时换用 AzureCliCredential 来验证是否是 Azure CLI 的问题。

Q:生产环境应该用什么认证方式? A:如果你运行在 Azure 托管环境中(Azure App Service、Azure Container Apps、Azure VM),使用 ManagedIdentityCredential。如果运行在自托管的服务器上,使用 ClientSecretCredential 并妥善保管凭据。永远不要在生产环境使用 DefaultAzureCredential——它的链式尝试机制会导致不必要的延迟,而且多个认证来源会增加安全风险。

Q:必须用 Azure OpenAI 吗?能不能用其他模型? A:Microsoft.Agents.AI 支持任何实现了 IChatClient 接口的 AI 提供商。官方支持 OpenAI、Azure OpenAI、Anthropic。你也可以通过自定义 IChatClient 实现来接入 Ollama、DeepSeek、智谱等任何兼容 OpenAI API 的模型。唯一的限制是 AsAIAgent 扩展方法目前主要面向 Azure AI 项目模式,如果你使用 OpenAI 直接 API,可以考虑直接实例化 ChatClientAgent

Agent 行为

Q:Agent 不调用注册的工具,而是自己编答案怎么办? A:这是初学者最常遇到的问题。根本原因通常是工具的 [Description] 写得太模糊。LLM 靠这些描述来判断工具是否与当前问题相关。描述应该包含"触发条件"和"返回值格式"两个要素。例如,不要把工具写成 [Description("获取天气")],而要写成 [Description("根据城市名获取实时天气数据。当用户问天气时调用此工具。返回格式:城市名、温度、天气状况")]。描述越具体,Agent 调用的准确率越高。

Q:Agent 在多轮对话中自己纠正之前的回答,这是好是坏? A:Agent 有能力在后续对话中纠正自己之前的错误,这通常是因为用户提供了新的信息或者 Agent 在后续工具调用中获取了更准确的数据。这是 Agent 的一大优势——它不像传统程序那样一次执行就固定结果,而是可以"思考"和"修正"。但这同时也意味着你需要在关键业务场景中对 Agent 的输出做验证。

工具设计

Q:一个 Agent 可以注册多少个工具? A:理论上没有硬性限制,但实践中建议控制在 20 个以内。LLM 在大量工具面前会出现"选择困难"——工具越多,LLM 选错工具的概率就越高,响应速度也越慢。如果你确实有大量工具,可以考虑分组策略:创建一个"路由 Agent"先判断用户意图,然后路由到专门的子 Agent(每个子 Agent 管理一组相关的工具)。

Q:工具函数的参数类型有什么限制? A:AIFunctionFactory.Create 支持大部分常见类型:stringintdoubleboolDateTime、枚举类型、以及它们的数组和可空版本。不支持复杂的嵌套对象、泛型类型或接口类型。如果你的工具需要复杂参数,考虑把 JSON 字符串作为参数传入,在工具内部反序列化。

性能与成本

Q:流式响应和非流式响应哪个更贵? A:在 Token 计费上是一样的——LLM 按生成的 Token 数量计费,和是否流式无关。流式只是改变了返回方式(分块 vs 整批),Token 总数不变。但在网络传输上,流式会稍微多消耗一点带宽(因为多了分块标记)。从用户体验角度,流式响应几乎是必须的——没有用户愿意对着空白屏幕等待 10 秒钟。

Q:多轮对话的 Token 消耗怎么控制? A:每轮对话的 Token 消耗 = 用户输入 + Agent 回复 + 历史上下文。随着轮次增加,Token 消耗线性增长。控制策略有三种:第一种是滑动窗口——只保留最近 N 轮对话;第二种是摘要压缩——定期把早期对话压缩为一段摘要;第三种是AgentSession 序列化——把 Session 序列化后分段加载。实际项目中通常组合使用:保留最近 20 轮完整对话,更早的内容压缩为摘要。

架构设计

Q:AgentSession 和 ChatHistoryProvider 有什么区别?什么时候用哪个? A:这是初学者最容易混淆的两个概念。ChatHistoryProvider 管理底层消息列表的"存储方式"——消息存到内存里还是数据库里。AgentSession 是一个更高层次的抽象,它除了包含消息历史,还包含会话状态、工具调用上下文等信息。简单来说:如果你只需要控制"对话历史怎么存",用 ChatHistoryProvider;如果你需要管理"完整的会话生命周期"(创建、运行、序列化、恢复),用 AgentSession

Q:什么场景应该用 Workflow,什么场景应该让 Agent 自主调用? A:这是一个架构决策。用 Workflow 的场景:业务逻辑有确定的步骤顺序,比如退款处理必须先查订单再验资格最后执行退款,步骤不能跳过也不能调换顺序。让 Agent 自主调用的场景:步骤顺序不确定,需要根据中间结果动态决策,比如"帮我处理服务器告警"——Agent 需要先检查、再诊断、再决定重启还是扩容。Workflow 提供确定性,Agent 自主调用提供灵活性。

调试技巧

Q:怎么查看 Agent 内部调用了哪些工具? A:当前版本的 Agent 框架没有内置的调用日志,但你可以通过以下方式调试:

  1. 在工具函数内部加 Console.WriteLine 或日志输出
  2. 用 ASP.NET Core 的 ILogger 记录每次工具调用
  3. 检查 AgentResponse 中的内容类型——如果有 FunctionCallContentFunctionResultContent 就说明工具被调用了

Q:想要中途停止 RunStreamingAsync 怎么办? A:RunStreamingAsync 支持 CancellationToken。创建一个 CancellationTokenSource,当用户点击"停止"按钮时调用 cts.Cancel(),流式循环会优雅停止。需要注意的是:取消流式只停止接收新 Token,不会回滚已经完成的操作——如果你的工具函数有副作用(如写入数据库),取消通知只影响 LLM 的后续输出。

Q:Agent 在开发环境运行正常,部署到生产环境后工具调用不工作了,可能是什么原因? A:最可能的原因有四个:一是生产环境的模型不同导致 Function Calling 行为差异(不同模型的工具调用准确率差异很大);二是生产环境的网络策略阻止了工具调用的 API 请求;三是认证方式不同(生产环境应该用 ManagedIdentityCredential 而不是 DefaultAzureCredential);四是生产环境的 Prompt 模板被修改了。逐一排查这四个方面通常能定位问题。

学习路径建议

如果你已经掌握了本文的 6 个步骤,还想继续深入学习 Microsoft Agent Framework,建议按以下路径进阶:

第一阶段:巩固基础(1-2 周) 把本文的每个步骤的代码亲手敲一遍,确保对 AIAgentAgentSessionWorkflowBuilder 的核心用法有肌肉记忆。不要复制粘贴——亲手敲代码的过程中遇到的那些编译错误,是你真正理解 API 的契机。

第二阶段:探索官方文档(1 周) 通读 Microsoft Learn 上的 Agent Framework 官方文档,重点看 Agents 章节中的 Tools、Conversations、Running Agents、Middleware 等子页面。官方文档中的"用户指南"部分包含了大量高级用法和配置选项。

第三阶段:企业实战(2-4 周) 选择一个真实业务场景(如内部 IT 工单系统、智能客服、文档处理流水线),从头开始设计并实现一个完整的 Agent 应用。在这个阶段你会真正理解本文中提到的所有概念——工具设计的原则、会话管理、工作流编排——以及那些本文没有展开的边缘情况处理。

第四阶段:架构深入(长期) 学习 A2A 协议、多 Agent 协作模式、向量数据库集成、CI/CD 部署流水线。这个阶段的目标不是"会调 API",而是"能设计 Agent 系统的架构"。关注 Microsoft Learn 上的 Agents 章节中的 Agent Pipeline、Observability、Evaluation、Agent Safety 等高级主题。

版本兼容性说明

本文基于 2026 年 4 月发布的 Microsoft Agent Framework SDK。需要注意以下版本兼容性信息:

  • NuGet 包的 prerelease 标记Azure.AI.ProjectsMicrosoft.Agents.AI.Foundry 当前为 prerelease 版本,API 可能会在正式版中发生变化。建议关注官方发布说明中的 breaking changes。
  • .NET 版本要求:目前要求 .NET 8 或更高版本。.NET 9 的完全兼容性正在测试中。
  • Workflow 包的独立性Microsoft.Agents.AI.Workflows 是独立的包,如果你不使用工作流编排,可以不引用它。
  • A2A 协议包的额外依赖Microsoft.Agents.AI.Hosting.A2A.AspNetCore 依赖 ASP.NET Core,不能在 Console 应用中使用。

参考资源

以下资源对你的学习会很有帮助,建议收藏以便随时查阅:

资源名称访问地址
MS Learn 官方文档(中文)learn.microsoft.com/zh-cn/agent-framework
官方 GitHub 源代码仓库github.com/microsoft/agent-framework
NuGet 包页面nuget.org/packages/Microsoft.Agents.AI
Azure AI Foundry 门户ai.azure.com

如果你使用 Hermes Agent 进行开发,配置 Microsoft Learn 的 MCP 服务器后,可以直接在开发过程中搜索官方文档和代码示例,无需离开终端。这是最高效的学习方式——遇到 API 不明确的地方,立即查询官方原文。

最后,记住 Agent 开发的核心心法:**Agent 不是传统的程序,你写的不是控制逻辑,而是行为边界和决策指南。工具是 Agent 的手脚,Prompt 是 Agent 的大脑,Session 是 Agent 的记忆,Workflow 是 Agent 的行动计划。把这四样设计好,Agent 自然会做出正确的决策。

每当你遇到 API 用法不明确的情况,优先查看 MS Learn 官方文档——本文也是基于官方文档编写的。技术发展很快,框架 API 可能会更新,但本文中讲述的这些核心概念——Agent 的本质、工具调用的原理、会话管理、工作流编排——是不会过时的。掌握原理,以不变应万变。


总结

本文带你从零开始,走完了 Microsoft Agent Framework 的完整入门路径。从第一个 Console 程序里的 AIAgent 创建与 RunAsync 调用开始,到给 Agent 添加工具让它具备实际的业务能力,再到用 AgentSession 实现多轮对话的状态持久化,接着引入 ChatHistoryProvider 实现跨会话的记忆管理,然后用 WorkflowBuilder 把多个 Agent 和自定义逻辑编排为确定性的工作流,最后用 Microsoft.Agents.AI.Hosting 把 Agent 部署到 ASP.NET Core 并通过 A2A 协议对外暴露。这六个步骤构成了一个从"能说话"到"能干活"到"能部署"的完整链路。

记住几个关键要点:

第一,Agent 的核心价值在于它能自主决策。传统程序遵循固定的 if-else 分支,Agent 根据 LLM 的判断动态决定调用哪个工具、执行哪条路径。这种灵活性是 Agent 最大的优势,但同时也意味着你的工具设计和 Prompt 编写质量直接决定了 Agent 的智能水平。

第二,错误处理是生产级 Agent 的基石。Agent 不是万能的,LLM 可能选错工具、工具调用可能超时、网络可能中断。在设计 Agent 系统时,要假设每一步都可能出错,并设计相应的降级策略——工具调用失败时返回清晰的错误信息,Agent 重复调用时保证工具幂等,长时间运行的任务设置合理的超时控制。

第三,从简单开始,逐步迭代。不要试图在第一天就构建一个包含工具、多轮对话、工作流、A2A 协议的完美 Agent。先让一个最简单的 Agent 跑通,验证 LLM 连接正常。然后逐步添加工具、引入会话、加入工作流。每一步都验证前一步的功能不被破坏。这种迭代式开发在 Agent 项目中比传统软件开发更加重要,因为 Agent 的行为是非确定性的——你改了工具的 Description,可能影响 Agent 在其他所有场景中的调用行为。此外,建议在项目初期就建立一套简单的评估机制——准备一组固定的测试问题,每次修改后都跑一遍,对比 Agent 的回答质量。这种"回归测试"在 Agent 开发中比传统单元测试更难做(因为答案不是精确的),但维护一个测试问题集仍然能帮你及早发现退化。

步骤核心概念关键 NuGet 包
Step 1AIProjectClient.AsAIAgent、RunAsync、RunStreamingAsyncAzure.AI.Projects、Azure.Identity、Microsoft.Agents.AI
Step 2AIFunctionFactory.Create、工具注册、Function CallingMicrosoft.Extensions.AI
Step 3AgentSession、CreateSessionAsync、序列化/反序列化Microsoft.Agents.AI
Step 4ChatHistoryProvider、InMemoryChatHistoryProviderMicrosoft.Agents.AI
Step 5Executor、WorkflowBuilder、InProcessExecutionMicrosoft.Agents.AI.Workflows
Step 6AddAIAgent、AddWorkflow、A2A 协议、Docker 部署Microsoft.Agents.AI.Hosting

你的学习路线图:

  1. 掌握本文的 6 个步骤 → 能独立开发一个完整的 Agent
  2. 深入学习 Microsoft.Agents.AI.Workflows 的高级用法 → 能编排复杂的多步骤业务流程
  3. 探索 Microsoft.Agents.AI.Hosting 的 A2A 协议 → 能构建分布式多 Agent 系统
  4. 集成向量数据库和 RAG Provider → 能为 Agent 构建大规模知识库
  5. 学习 Agent Pipeline、Observability 和 Evaluation → 能为生产级 Agent 做持续优化

现在,打开终端,执行 dotnet new console,开始你的第一个 Agent 吧。代码跑通的那一刻,你就能感受到 AI Agent 开发与传统后端开发之间的区别——你写的不是"怎么做的"指令,而是"能做到什么"的边界和工具。这是一种全新的编程范式,也是 .NET 开发者在这个 AI 时代最需要掌握的技能之一。

记住,Agent 开发最忌讳的是"一次性追求完美"。不要试图在第一天就把工具、对话管理、工作流、A2A 协议都集成进来。先让一个最简单的 Agent 跑通——dotnet new console,引用 Azure.AI.Projects,写三行代码调用 RunAsync。看到控制台输出 AI 回答的那一刻,你就迈出了最难的第一步。后面的每一个步骤都是在前一步的基础上增量的、可验证的改进。这种渐进式的方法在 Agent 开发中比传统后端开发更加重要,因为每一步都涉及 LLM 的行为验证,而验证非确定性的 AI 行为比验证确定性代码要困难得多。

本文作者:比特矩阵 许可协议CC BY-NC-SA 4.0更新日期:2026 年 4 月(基于 Microsoft Agent Framework 官方文档) 官方文档learn.microsoft.com/zh-cn/agent-framework

学而不思则罔,思而不学则殆