在 .NET AI 聊天应用中升级到 Microsoft 代理框架

在 .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 概念

3. 创建基础 AI 聊天应用

3.1 安装模板

首先使用官方 .NET AI 模板创建基线聊天应用:

dotnet new install Microsoft.Extensions.AI.Templates 

3.2 创建项目

可以通过 Visual Studio 或 CLI 创建项目:

Visual Studio 方式:

  1. 打开 Visual Studio 2022
  2. 选择"创建新项目"
  3. 搜索"AI Chat Web App"
  4. 配置项目名称和位置
  5. 选择 Azure OpenAI 作为 AI 提供程序
  6. 为矢量存储选择"本地磁盘"
  7. 选择 .NET Aspire 进行业务流程

在 .NET AI 聊天应用中升级到 Microsoft 代理框架

CLI 方式:
使用 dotnet new 命令创建项目,配置选项与 Visual Studio 相同。

3.3 项目结构

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

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

主要工作将在 ChatApp20.Web 项目中进行。

在 .NET AI 聊天应用中升级到 Microsoft 代理框架

4. 添加 Microsoft 代理框架

4.1 安装必要的 NuGet 包

首先将 Microsoft Agent Framework 包添加到 ChatApp20.Web.csproj:

<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> 

在 .NET AI 聊天应用中升级到 Microsoft 代理框架

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 提供了服务发现、统一日志记录和遥测、健康检查等功能:

  1. 运行应用,Aspire 仪表板会自动在浏览器中打开
  2. 首次运行时,系统会提示配置 Azure OpenAI:
    • 选择 Azure 订阅
    • 选择或创建资源组
    • 选择或预配 Azure OpenAI 资源
    • 确保部署了聊天模型(如 gpt-4o-mini)和嵌入模型(如 text-embedding-3-small)

在 .NET AI 聊天应用中升级到 Microsoft 代理框架

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> 

在 .NET AI 聊天应用中升级到 Microsoft 代理框架

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 的仪表板监控:

  • 每次代理交互的令牌使用量
  • 工具调用模式(使用哪些工具,使用频率)
  • 代理操作的响应时间
  • 工具调用的错误率
  • 通过反馈机制获得的用户满意度

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 容器应用
  • 设置 Application Insights 进行监控
  • 配置服务连接和身份验证

结论

通过本文的步骤,我们成功将标准的 AI 聊天应用升级为使用 Microsoft 代理框架的智能代理系统。这一升级带来了更清晰的架构关注点分离、更轻松的测试和内置的可观测性,同时仍然使用 .NET 开发者熟悉的模式。

Microsoft 代理框架的优势在于它不需要开发者学习全新的方式,而是建立在依赖注入、中间件和遥测等熟悉的 .NET 概念之上。无论是简单的工具调用还是复杂的多代理协调,该框架都提供了强大而灵活的功能来满足各种业务需求。

随着 AI 技术的不断发展,采用代理框架将使您的应用能够更好地适应未来需求,提供更智能、更自然的用户体验。

发表评论

评论已关闭。

相关文章