全部版块 我的主页
论坛 数据科学与人工智能 人工智能
27 0
2025-12-03

在 .NET AI 聊天应用中集成 Microsoft 代理框架

随着人工智能技术不断演进,传统聊天机器人已难以应对日益增长的复杂业务场景。Microsoft 推出的 Agent Framework 为 .NET 开发者提供了一套强大且灵活的工具,支持构建具备推理能力、任务编排和自主决策功能的智能代理系统。本文将指导你如何将现有的基于 .NET AI 模板的聊天应用迁移至 Microsoft 代理框架,从而提升应用的智能化水平。

1. Microsoft 代理框架简介

Microsoft Agent Framework 是一个面向 .NET 平台的预览版开发框架,专为打造高级 AI 代理而设计。它不仅超越了基础问答型聊天机器人的局限,还提供了以下关键能力:

  • 支持多步骤推理与任务规划
  • 通过函数或工具调用连接外部 API、数据库和服务
  • 在整个对话过程中保持上下文一致性
  • 根据指令和数据实现自主判断与响应
  • 支持多个代理之间的协同工作

该框架基于 .NET 生态中开发者熟悉的编程模式构建,例如依赖注入、中间件机制以及遥测系统,并深度整合了 Microsoft.Extensions.AI 组件,便于快速上手与扩展。

2. 环境准备

在开始升级前,请确保满足以下条件:

  • 已安装 .NET 9 SDK
  • 使用 Visual Studio 或配置好 C# Dev Kit 的 Visual Studio Code
  • 拥有可访问 Azure OpenAI 的 Azure 帐户,或能够连接 GitHub 上的开源模型
  • 已成功安装 .NET AI 应用模板
  • 对 .NET、Blazor 及基本 AI 概念有一定了解
/* by yours.tools - online tools website : yours.tools/zh/calcforce.html */
dotnet new install Microsoft.Extensions.AI.Templates

3. 构建初始 AI 聊天项目

3.1 安装并使用项目模板

首先利用官方提供的 .NET AI 模板创建一个标准的聊天应用作为起点。

3.2 创建新项目

可通过以下两种方式之一完成项目初始化:

Visual Studio 方式:

  1. 打开 Visual Studio 2022
  2. 选择“创建新项目”
  3. 搜索“AI Chat Web App”
  4. 设置项目名称与存储路径
  5. 选择 Azure OpenAI 作为 AI 提供程序
  6. 为向量存储指定“本地磁盘”选项
  7. 选用 .NET Aspire 进行服务编排

CLI 命令行方式:

运行相应命令创建项目,其配置项与 Visual Studio 流程一致。

dotnet new

3.3 查看生成的项目结构

模板会自动生成包含三个项目的解决方案:

ChatApp20/
├── ChatApp20.Web/              # 带有聊天 UI 的 Blazor Server 应用
├── ChatApp20.AppHost/          # .NET Aspire 业务流程
└── ChatApp20.ServiceDefaults/  # 共享服务配置

后续的主要开发工作将集中在 ChatApp20.Web 项目内进行。

4. 集成 Microsoft 代理框架

4.1 引入必要的 NuGet 包

将 Microsoft Agent Framework 相关包添加到 ChatApp20.Web.csproj 文件中以启用核心功能。

/* by yours.tools - online tools website : yours.tools/zh/calcforce.html */
<ItemGroup>
  <!-- 保留现有包 -->
  <PackageReference Include="Aspire.Azure.AI.OpenAI" Version="9.5.1-preview.1.25502.11" />
  <PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.10.0-preview.1.25513.3" />
  
  <!-- 添加 Microsoft 代理框架包 -->
  <PackageReference Include="Microsoft.Agents.AI" Version="1.0.0-preview.251009.1" />
  <PackageReference Include="Microsoft.Agents.AI.Abstractions" Version="1.0.0-preview.251009.1" />
  <PackageReference Include="Microsoft.Agents.AI.Hosting" Version="1.0.0-preview.251009.1" />
  <PackageReference Include="Microsoft.Agents.AI.Hosting.OpenAI" Version="1.0.0-alpha.251009.1" />
  <PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-preview.251009.1" />
</ItemGroup>

4.2 创建独立的搜索服务

为了提升代码可维护性与测试便利性,建议新建 SearchFunctions.cs 文件用于封装搜索相关逻辑。

using System.ComponentModel;

namespace ChatApp20.Web.Services;

public class SearchFunctions
{
    private readonly SemanticSearch _semanticSearch;

    public SearchFunctions(SemanticSearch semanticSearch)
    {
        _semanticSearch = semanticSearch;
    }

    [Description("使用短语或关键字搜索信息")]
    public async Task<IEnumerable<string>> SearchAsync(
        [Description("要搜索的短语")] string searchPhrase,
        [Description("如果可能,指定文件名仅搜索该文件")] string? filenameFilter = null)
    {
        var results = await _semanticSearch.SearchAsync(searchPhrase, filenameFilter, maxResults: 5);
        return results.Select(result =>
            $"<result filename=\"{result.DocumentId}\" page_number=\"{result.PageNumber}\">{result.Text}</result>");
    }
}

4.3 在 Program.cs 中配置 AI 代理

借助 Agent Framework 提供的托管扩展方法,在启动类中注册并初始化 AI 代理实例。

// 注册使用代理框架的 AI 代理
builder.AddAIAgent("ChatAgent", (sp, key) =>
{
    var logger = sp.GetRequiredService<ILogger<Program>>();
    var searchFunctions = sp.GetRequiredService<SearchFunctions>();
    var chatClient = sp.GetRequiredService<IChatClient>();

    // 创建并配置 AI 代理
    var aiAgent = chatClient.CreateAIAgent(
        name: key,
        instructions: "你是一个有用的代理,提供简短而有趣的回答。",
        description: "帮助用户提供简短而有趣回答的 AI 代理。",
        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]
        )
    .AsBuilder()
    .UseOpenTelemetry(configure: c =>
        c.EnableSensitiveData = builder.Environment.IsDevelopment())
    .Build();

    return aiAgent;
});

// 注册 SearchFunctions 以便通过 DI 注入到代理中
builder.Services.AddSingleton<SearchFunctions>();

4.4 更新前端交互组件

修改 Chat.razor 组件,使其调用新的代理服务而非原始 AI 模型接口。

@inject IServiceProvider ServiceProvider
@using Microsoft.Agents.AI

@code {
    private AIAgent aiAgent = default!;

    protected override void OnInitialized()
    {
        // 解析 Program.cs 中注册为"ChatAgent"的键控 AI 代理
        aiAgent = ServiceProvider.GetRequiredKeyedService<AIAgent>("ChatAgent");
    }

    private async Task AddUserMessageAsync(ChatMessage userMessage)
    {
        // 用代理流式处理替换 ChatClient.GetStreamingResponseAsync
        await foreach (var update in aiAgent.RunStreamingAsync(
            messages: messages.Skip(statefulMessageCount),
            cancellationToken: currentResponseCancellation.Token))
        {
            var responseUpdate = update.AsChatResponseUpdate();
            messages.AddMessages(responseUpdate, filter: c => c is not TextContent);
            responseText.Text += update.Text;
            chatOptions.ConversationId = responseUpdate.ConversationId;
            ChatMessageItem.NotifyChanged(currentResponseMessage);
        }
    }
}

5. 启动与验证增强后的应用

5.1 使用 .NET Aspire 启动应用

.NET Aspire 提供了包括服务发现、统一日志记录、遥测监控和健康检查在内的多项便利功能:

  • 运行项目后,Aspire 仪表板会自动在浏览器中打开
  • 首次运行时需完成 Azure OpenAI 配置流程:
    • 选择目标 Azure 订阅
    • 指定或新建资源组
    • 选择或部署 Azure OpenAI 资源
    • 确保已部署合适的聊天模型(如 gpt-4o-mini)和嵌入模型(如 text-embedding-3-small)

5.2 功能测试示例

应用启动成功后,可通过 Aspire 仪表板中的 Web 端点(通常为 https://localhost:7001)进行访问和测试。

基础对话功能:

用户: 你好!你好吗?
代理: 嘿!我很好——充满电,就像应急救生包一样。

结合语义搜索的工具调用:

用户: 应急救生包应该包括什么?
代理: 简短的救生包清单(有趣版) 急救用品——绷带、纱布、消毒剂。
      <citation filename='Example_Emergency_Survival_Kit.pdf' page_number='1'>水和食物供应</citation>

针对特定文件内容的查询:

用户: 告诉我关于 GPS 手表的功能
代理: GPS 手表包括...
      <citation filename='Example_GPS_Watch.pdf' page_number='2'>实时跟踪</citation>

6. 实现更复杂的使用场景

6.1 扩展代理工具集

可以轻松地为代理添加额外能力,例如增加天气信息查询功能:

public class WeatherFunctions
{
    [Description("获取某个位置的当前天气")]
    public async Task<string> GetWeatherAsync(
        [Description("城市和州/国家")] string location)
    {
        // 调用天气 API
        return $"{location}的天气: 晴天, 72°F";
    }
}

// 在 Program.cs 中
builder.Services.AddSingleton<WeatherFunctions>();

builder.AddAIAgent("ChatAgent", (sp, key) =>
{
    var searchFunctions = sp.GetRequiredService<SearchFunctions>();
    var weatherFunctions = sp.GetRequiredService<WeatherFunctions>();
    var chatClient = sp.GetRequiredService<IChatClient>();

    return chatClient.CreateAIAgent(
        name: key,
        instructions: "你可以搜索文档和查询天气...",
        tools: [
            AIFunctionFactory.Create(searchFunctions.SearchAsync),
            AIFunctionFactory.Create(weatherFunctions.GetWeatherAsync)
        ]
    ).Build();
});

6.2 支持多代理协作

框架支持多个专业化代理之间的协调与通信,适用于需要分工处理的任务流。

// 注册研究代理
builder.AddAIAgent("ResearchAgent", (sp, key) =>
{
    var chatClient = sp.GetRequiredService<IChatClient>();
    var searchFunctions = sp.GetRequiredService<SearchFunctions>();

    return chatClient.CreateAIAgent(
        name: "ResearchAgent",
        instructions: "你是研究专家。从文档中查找和总结信息。",
        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]
    ).Build();
});

// 注册写作代理
builder.AddAIAgent("WritingAgent", (sp, key) =>
{
    var chatClient = sp.GetRequiredService<IChatClient>();

    return chatClient.CreateAIAgent(
        name: "WritingAgent",
        instructions: "你是写作专家。获取信息并创建结构良好、引人入胜的内容。",
        tools: []
    ).Build();
});

// 注册协调代理
builder.AddAIAgent("CoordinatorAgent", (sp, key) =>
{
    var chatClient = sp.GetRequiredService<IChatClient>();
    var researchAgent = sp.GetRequiredKeyedService<AIAgent>("ResearchAgent");
    var writingAgent = sp.GetRequiredKeyedService<AIAgent>("WritingAgent");

    // 创建委托给其他代理的函数
    async Task<string> ResearchAsync(string topic)
    {
        var messages = new[] { new ChatMessage(ChatRole.User, topic) };
        var result = await researchAgent.RunAsync(messages);
        return result.Text ?? "";
    }

    async Task<string> WriteAsync(string content)
    {
        var messages = new[] { new ChatMessage(ChatRole.User, $"基于以下内容写文章: {content}") };
        var result = await writingAgent.RunAsync(messages);
        return result.Text ?? "";
    }

    return chatClient.CreateAIAgent(
        name: "CoordinatorAgent",
        instructions: "协调研究和写作以创建全面的文章。",
        tools: [
            AIFunctionFactory.Create(ResearchAsync),
            AIFunctionFactory.Create(WriteAsync)
        ]
    ).Build();
});

6.3 自定义中间件逻辑

开发者可插入自定义中间件来实现日志追踪、结果缓存或行为拦截等功能。

builder.AddAIAgent("ChatAgent", (sp, key) =>
{
    var chatClient = sp.GetRequiredService<IChatClient>();
    var searchFunctions = sp.GetRequiredService<SearchFunctions>();
    var logger = sp.GetRequiredService<ILogger<Program>>();

    return chatClient.CreateAIAgent(
        name: key,
        instructions: "...",
        tools: [AIFunctionFactory.Create(searchFunctions.SearchAsync)]
        )
    .AsBuilder()
    .Use(async (messages, options, next, cancellationToken) =>
    {
        // 自定义预处理
        logger.LogInformation("代理正在处理 {MessageCount} 条消息", messages.Count());

        // 调用管道中的下一个
        var result = await next(messages, options, cancellationToken);

        // 自定义后处理
        logger.LogInformation("代理生成了包含 {ContentCount} 个内容项的响应", result.Contents.Count);

        return result;
    })
    .UseOpenTelemetry(configure: c => c.EnableSensitiveData = true)
    .Build();
});

7. 推荐的最佳实践

7.1 编写清晰的工具描述

代理能否正确调用工具,高度依赖于工具说明的准确性和完整性。

[Description("在产品文档中搜索特定信息。" +
             "当用户询问功能、规格或产品使用方法时使用此工具。" +
             "返回带有文件名和页码的相关摘录以供引用。")]
public async Task<IEnumerable<string>> SearchAsync(
    [Description("要搜索的特定短语、关键字或问题。" +
                 "要具体并包含相关上下文。")] 
    string searchPhrase,
    [Description("可选: 要搜索的精确文件名(如'ProductManual.pdf')。" +
                 "留空则搜索所有文档。")] 
    string? filenameFilter = null)
{
    // 实现
}

7.2 全面测试代理行为

应建立完整的测试体系:为单个工具编写单元测试,为整体工作流设计集成测试。

public class SearchFunctionsTests
{
    [Fact]
    public async Task SearchAsync_WithValidQuery_ReturnsResults()
    {
        // 准备
        var mockSemanticSearch = new Mock<SemanticSearch>();
        mockSemanticSearch
            .Setup(s => s.SearchAsync("test", null, 5))
            .ReturnsAsync(new List<IngestedChunk>
            {
                new IngestedChunk { DocumentId = "test.pdf", PageNumber = 1, Text = "测试内容" }
            });

        var searchFunctions = new SearchFunctions(mockSemanticSearch.Object);

        // 执行
        var results = await searchFunctions.SearchAsync("test");

        // 断言
        Assert.NotEmpty(results);
        Assert.Contains("测试内容", results.First());
    }
}

7.3 持续监控代理性能指标

推荐使用 Application Insights 或 .NET Aspire 内置仪表板跟踪以下关键数据:

  • 每次交互消耗的 token 数量
  • 工具调用频率及分布情况
  • 代理操作的平均响应时间
  • 工具调用失败率
  • 用户反馈体现的满意度趋势

8. 性能优化建议

8.1 流式与非流式响应的选择

根据实际需求权衡是否启用流式输出。流式响应适合长文本生成场景,可提升用户体验;非流式则更适合结构化数据返回或后台处理任务,有助于降低延迟和资源占用。

代理框架具备对流式与非流式响应的双重支持,开发者可根据具体场景灵活选择:

适用流式响应的场景包括:

  • 构建具备实时交互能力的聊天界面
  • 用户需要即时反馈的交互过程
  • 处理耗时较长的查询任务

适用非流式响应的场景则有:

  • 执行后台异步处理任务
  • 进行数据的批量操作
  • 调用轻量级、简单的 API 接口
// 好: 具体指令
"仅当用户询问关于文档的具体问题时使用搜索工具。
如果可以从常识中回答,则不搜索。"

// 不好: 模糊指令
"你可以访问搜索工具。"

8.2 工具调用的优化策略

在实际应用中,应尽可能减少不必要的工具调用,以提升系统效率和响应速度。合理设计调用逻辑,避免冗余请求,是保障智能代理高效运行的关键。

9. 部署至 Azure 平台

当前应用已具备通过 .NET Aspire 实现 Azure 资源预配与部署的能力。

# 登录 Azure
az login

# 创建 Azure 资源
cd ChatApp20.AppHost
azd init
azd up

该部署流程将自动完成以下关键步骤:

  • 创建并配置 Azure OpenAI 服务资源
  • 将 Web 应用发布到 Azure 容器应用(Azure Container Apps)
  • 集成 Application Insights,实现全面的应用性能监控
  • 建立服务间的连接配置,并完成身份验证机制的设定

总结

本文通过一系列实践步骤,成功将一个基础的 AI 聊天应用演进为基于 Microsoft 代理框架的智能代理系统。此次升级不仅实现了架构层面的关注点分离,还显著提升了测试便利性,并原生集成了可观测性能力,同时延续了 .NET 开发者所熟悉的编程模型。

Microsoft 代理框架的核心优势在于其与现有 .NET 生态的高度融合——它并非要求开发者重新学习全新范式,而是依托于依赖注入、中间件管道和遥测等已被广泛掌握的技术概念进行扩展。无论是实现基础的工具调用,还是协调多个代理协同工作,该框架均能提供强大且灵活的支持,满足多样化的业务发展需求。

随着人工智能技术的持续进步,采用代理框架构建的应用将更具可扩展性和适应性,能够更从容地应对未来挑战,为用户提供更加智能、流畅和自然的交互体验。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群