随着人工智能技术不断演进,传统聊天机器人已难以应对日益增长的复杂业务场景。Microsoft 推出的 Agent Framework 为 .NET 开发者提供了一套强大且灵活的工具,支持构建具备推理能力、任务编排和自主决策功能的智能代理系统。本文将指导你如何将现有的基于 .NET AI 模板的聊天应用迁移至 Microsoft 代理框架,从而提升应用的智能化水平。
Microsoft Agent Framework 是一个面向 .NET 平台的预览版开发框架,专为打造高级 AI 代理而设计。它不仅超越了基础问答型聊天机器人的局限,还提供了以下关键能力:
该框架基于 .NET 生态中开发者熟悉的编程模式构建,例如依赖注入、中间件机制以及遥测系统,并深度整合了 Microsoft.Extensions.AI 组件,便于快速上手与扩展。
在开始升级前,请确保满足以下条件:
/* by yours.tools - online tools website : yours.tools/zh/calcforce.html */
dotnet new install Microsoft.Extensions.AI.Templates
首先利用官方提供的 .NET AI 模板创建一个标准的聊天应用作为起点。
可通过以下两种方式之一完成项目初始化:
Visual Studio 方式:
CLI 命令行方式:
运行相应命令创建项目,其配置项与 Visual Studio 流程一致。
dotnet new
模板会自动生成包含三个项目的解决方案:
ChatApp20/
├── ChatApp20.Web/ # 带有聊天 UI 的 Blazor Server 应用
├── ChatApp20.AppHost/ # .NET Aspire 业务流程
└── ChatApp20.ServiceDefaults/ # 共享服务配置
后续的主要开发工作将集中在 ChatApp20.Web 项目内进行。
将 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>
为了提升代码可维护性与测试便利性,建议新建 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>");
}
}
借助 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>();
修改 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);
}
}
}
.NET Aspire 提供了包括服务发现、统一日志记录、遥测监控和健康检查在内的多项便利功能:
应用启动成功后,可通过 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>
可以轻松地为代理添加额外能力,例如增加天气信息查询功能:
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();
});
框架支持多个专业化代理之间的协调与通信,适用于需要分工处理的任务流。
// 注册研究代理
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();
});
开发者可插入自定义中间件来实现日志追踪、结果缓存或行为拦截等功能。
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();
});
代理能否正确调用工具,高度依赖于工具说明的准确性和完整性。
[Description("在产品文档中搜索特定信息。" +
"当用户询问功能、规格或产品使用方法时使用此工具。" +
"返回带有文件名和页码的相关摘录以供引用。")]
public async Task<IEnumerable<string>> SearchAsync(
[Description("要搜索的特定短语、关键字或问题。" +
"要具体并包含相关上下文。")]
string searchPhrase,
[Description("可选: 要搜索的精确文件名(如'ProductManual.pdf')。" +
"留空则搜索所有文档。")]
string? filenameFilter = null)
{
// 实现
}
应建立完整的测试体系:为单个工具编写单元测试,为整体工作流设计集成测试。
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());
}
}
推荐使用 Application Insights 或 .NET Aspire 内置仪表板跟踪以下关键数据:
根据实际需求权衡是否启用流式输出。流式响应适合长文本生成场景,可提升用户体验;非流式则更适合结构化数据返回或后台处理任务,有助于降低延迟和资源占用。
代理框架具备对流式与非流式响应的双重支持,开发者可根据具体场景灵活选择:
// 好: 具体指令
"仅当用户询问关于文档的具体问题时使用搜索工具。
如果可以从常识中回答,则不搜索。"
// 不好: 模糊指令
"你可以访问搜索工具。"
在实际应用中,应尽可能减少不必要的工具调用,以提升系统效率和响应速度。合理设计调用逻辑,避免冗余请求,是保障智能代理高效运行的关键。
当前应用已具备通过 .NET Aspire 实现 Azure 资源预配与部署的能力。
# 登录 Azure
az login
# 创建 Azure 资源
cd ChatApp20.AppHost
azd init
azd up
该部署流程将自动完成以下关键步骤:
本文通过一系列实践步骤,成功将一个基础的 AI 聊天应用演进为基于 Microsoft 代理框架的智能代理系统。此次升级不仅实现了架构层面的关注点分离,还显著提升了测试便利性,并原生集成了可观测性能力,同时延续了 .NET 开发者所熟悉的编程模型。
Microsoft 代理框架的核心优势在于其与现有 .NET 生态的高度融合——它并非要求开发者重新学习全新范式,而是依托于依赖注入、中间件管道和遥测等已被广泛掌握的技术概念进行扩展。无论是实现基础的工具调用,还是协调多个代理协同工作,该框架均能提供强大且灵活的支持,满足多样化的业务发展需求。
随着人工智能技术的持续进步,采用代理框架构建的应用将更具可扩展性和适应性,能够更从容地应对未来挑战,为用户提供更加智能、流畅和自然的交互体验。
扫码加好友,拉您进群



收藏
