.NET+AI | MEAI | 自定义 IChatClient(7)

.NET+AI | MEAI | 自定义 IChatClient(7)
目录一句话简介 核心价值 为什么需要自定义中间件?️ DelegatingChatClient 核心概念 快速开始1. 实现限流中间件2. 实现安全过滤中间件3. 使用 ChatClientBuilder.Use 简化开发 创建可复用扩展方法 企业级最佳实践1. 中间件执行顺序(洋葱模型)2. 处理流式和非流式响应3. 资源管理和生命周期4. 依赖注入集成 总结上一篇一句话简介通过 Microsoft.Extensions.AI 的 DelegatingChatClient 基类,轻松创建自定义中间件,实现限流、重试、安全过滤等企业级功能,让 AI 应用更安全、更稳定。 核心价值✅简单易用:只需继承基类并重写需要的方法✅灵活组合:多个中间件可以管道式串联✅企业就绪:实现限流、安全、监控等生产级功能✅标准化:遵循统一的IChatClient接口规范 为什么需要自定义中间件?在实际应用中,我们经常需要对 AI 服务进行增强和控制:场景挑战中间件方案API 限流超出调用频率限制RateLimitingChatClient网络故障临时性错误导致失败RetryingChatClient内容安全敏感信息泄露风险ContentFilteringChatClient性能监控无法追踪响应时间PerformanceMonitoringClient合规审计需要记录所有交互AuditLoggingChatClient️ DelegatingChatClient 核心概念核心特性:透明转发:默认将所有调用转发到内部客户端可选重写:只需重写需要定制的方法管道友好:支持多个中间件串联组合可重写方法:GetResponseAsync:处理完整响应GetStreamingResponseAsync:处理流式响应Dispose:清理资源 快速开始1. 实现限流中间件保护 API 免受过载,控制调用频率:using Microsoft.Extensions.AI; using System.Threading.RateLimiting; publicsealedclassRateLimitingChatClient : DelegatingChatClient { privatereadonly RateLimiter _rateLimiter; public RateLimitingChatClient(IChatClient innerClient, RateLimiter rateLimiter) : base(innerClient) { _rateLimiter rateLimiter; } public override async TaskChatResponse GetResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default) { // 获取限流许可 usingvar lease await _rateLimiter.AcquireAsync(1, cancellationToken); if (!lease.IsAcquired) thrownew InvalidOperationException(请求被限流拒绝); // 转发到内部客户端 returnawaitbase.GetResponseAsync(messages, options, cancellationToken); } }使用方式:var limiter new ConcurrencyLimiter(new() { PermitLimit 2 }); var client new RateLimitingChatClient(baseClient, limiter);2. 实现安全过滤中间件过滤敏感信息,保护数据安全:public sealedclassContentFilteringChatClient : DelegatingChatClient { privatereadonly HashSetstring _sensitiveWords; public ContentFilteringChatClient( IChatClient innerClient, IEnumerablestring sensitiveWords) : base(innerClient) { _sensitiveWords new HashSetstring( sensitiveWords, StringComparer.OrdinalIgnoreCase); } public override async TaskChatResponse GetResponseAsync( IEnumerableChatMessage messages, ChatOptions? options null, CancellationToken cancellationToken default) { // 过滤输入消息 var filteredMessages FilterMessages(messages); // 调用底层客户端 returnawaitbase.GetResponseAsync( filteredMessages, options, cancellationToken); } private ListChatMessage FilterMessages(IEnumerableChatMessage messages) { return messages.Select(m { if (m.Text ! null ContainsSensitiveWords(m.Text)) { returnnew ChatMessage(m.Role, MaskSensitiveWords(m.Text)); } return m; }).ToList(); } }3. 使用 ChatClientBuilder.Use 简化开发除了继承DelegatingChatClient,还可以使用内联方式:var client baseClient.AsBuilder() // 添加日志中间件 .Use(async (messages, options, innerClient, cancellationToken) { Console.WriteLine($[日志] 收到 {messages.Count()} 条消息); var sw Stopwatch.StartNew(); var response await innerClient.GetResponseAsync( messages, options, cancellationToken); Console.WriteLine($[日志] 耗时: {sw.ElapsedMilliseconds}ms); return response; }) // 添加重试中间件 .Use(async (messages, options, innerClient, cancellationToken) { for (int i 0; i 3; i) { try { returnawait innerClient.GetResponseAsync( messages, options, cancellationToken); } catch (Exception ex) when (i 2) { Console.WriteLine($[重试] 第 {i 1} 次失败,准备重试...); await Task.Delay(1000 * (i 1)); } } thrownew Exception(重试失败); }) .Build();优势对比:方式适用场景优势继承方式复杂逻辑、资源管理完全控制、可复用内联方式简单场景、快速开发代码简洁、灵活 创建可复用扩展方法将中间件封装为扩展方法,提高复用性:public staticclassChatClientExtensions { public static ChatClientBuilder UseRateLimiting( this ChatClientBuilder builder, RateLimiter rateLimiter) { return builder.Use(innerClient new RateLimitingChatClient(innerClient, rateLimiter)); } public static ChatClientBuilder UseContentFiltering( this ChatClientBuilder builder, IEnumerablestring sensitiveWords) { return builder.Use(innerClient new ContentFilteringChatClient(innerClient, sensitiveWords)); } public static ChatClientBuilder UsePerformanceMonitoring( this ChatClientBuilder builder) { return builder.Use(async (messages, options, innerClient, ct) { var sw Stopwatch.StartNew(); var response await innerClient.GetResponseAsync(messages, options, ct); Console.WriteLine($[性能] {sw.ElapsedMilliseconds}ms); return response; }); } }使用扩展方法:var client baseClient.AsBuilder() .UsePerformanceMonitoring() .UseContentFiltering(new[] { 密码, 账号 }) .UseRateLimiting(rateLimiter) .Build(); 企业级最佳实践1. 中间件执行顺序(洋葱模型)请求: 外层 → 内层 → AI 模型 响应: AI 模型 → 内层 → 外层推荐顺序:层级中间件类型原因最外层日志、监控记录所有请求和响应中间层安全过滤在消耗资源前拦截内层限流、缓存减少 API 调用示例配置:var client baseClient.AsBuilder() .UsePerformanceMonitoring() // 外层:监控 .UseContentFiltering(words) // 中层:安全 .UseRateLimiting(limiter) // 内层:限流 .Build();2. 处理流式和非流式响应需要同时支持两种模式:// 非流式响应 public override async TaskChatResponse GetResponseAsync(...) { var response awaitbase.GetResponseAsync(...); ProcessFullContent(response.Text); return response; } // 流式响应 public override async IAsyncEnumerableChatResponseUpdate GetStreamingResponseAsync(...) { StringBuilder accumulated new(); awaitforeach (var update inbase.GetStreamingResponseAsync(...)) { accumulated.Append(update.Text); yieldreturn update; } ProcessFullContent(accumulated.ToString()); }3. 资源管理和生命周期正确实现资源释放:public sealedclassMyCustomChatClient : DelegatingChatClient { privatereadonly IDisposable _resource; protected override void Dispose(bool disposing) { if (disposing) { _resource?.Dispose(); } base.Dispose(disposing); } }最佳实践:✅ 始终调用base.Dispose(disposing)✅ 在disposing true时释放托管资源✅ 使用using语句确保释放4. 依赖注入集成在 ASP.NET Core 中使用:// Program.cs builder.Services.AddSingletonRateLimiter(_ new ConcurrencyLimiter(new() { PermitLimit 10 })); builder.Services.AddChatClient(services { var baseClient /* 创建基础客户端 */; return baseClient .AsBuilder() .UsePerformanceMonitoring() .UseContentFiltering(new[] { 敏感词 }) .UseRateLimiting(services.GetRequiredServiceRateLimiter()) .Build(); }); // 在服务中注入使用 publicclassMyService { privatereadonly IChatClient _chatClient; public MyService(IChatClient chatClient) { _chatClient chatClient; } } 总结✅三种实现方式:继承 DelegatingChatClient、Use 内联、扩展方法✅常见场景:限流、安全、监控、重试、审计✅洋葱模型:外层监控、中层安全、内层限流✅生产就绪:资源管理、依赖注入、错误处理选择建议: 复杂逻辑、需要资源管理 → 继承 DelegatingChatClient 简单场景、快速开发 → Use 内联方式 可复用组件 → 扩展方法下一步:探索 MEAI ChatClient中间件和Function Invoker的区别。https://github.com/mzhongl524/meai_dotnet_for_begenner引入地址