DelegatingChatClient:构建企业级 AI 中间件的利器
一句话简介
通过 Microsoft.Extensions.AI 的 DelegatingChatClient 基类,轻松创建自定义中间件,实现限流、重试、安全过滤等企业级功能,让 AI 应用更安全、更稳定。
🎯 核心价值
- ✅ 简单易用:只需继承基类并重写需要的方法
- ✅ 灵活组合:多个中间件可以管道式串联
- ✅ 企业就绪:实现限流、安全、监控等生产级功能
- ✅ 标准化 :遵循统一的
IChatClient接口规范
📝 为什么需要自定义中间件?
在实际应用中,我们经常需要对 AI 服务进行增强和控制:
| 场景 | 挑战 | 中间件方案 |
|---|---|---|
| API 限流 | 超出调用频率限制 | RateLimitingChatClient |
| 网络故障 | 临时性错误导致失败 | RetryingChatClient |
| 内容安全 | 敏感信息泄露风险 | ContentFilteringChatClient |
| 性能监控 | 无法追踪响应时间 | PerformanceMonitoringClient |
| 合规审计 | 需要记录所有交互 | AuditLoggingChatClient |
🏗️ DelegatingChatClient 核心概念

核心特性:
- 🔧 透明转发:默认将所有调用转发到内部客户端
- 🔧 可选重写:只需重写需要定制的方法
- 🔧 管道友好:支持多个中间件串联组合
可重写方法:
GetResponseAsync:处理完整响应GetStreamingResponseAsync:处理流式响应Dispose:清理资源
💻 快速开始
1. 实现限流中间件
保护 API 免受过载,控制调用频率:
csharp
using Microsoft.Extensions.AI;
using System.Threading.RateLimiting;
public sealed class RateLimitingChatClient : DelegatingChatClient
{
private readonly RateLimiter _rateLimiter;
public RateLimitingChatClient(IChatClient innerClient, RateLimiter rateLimiter)
: base(innerClient)
{
_rateLimiter = rateLimiter;
}
public override async Task<ChatResponse> GetResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
// 获取限流许可
using var lease = await _rateLimiter.AcquireAsync(1, cancellationToken);
if (!lease.IsAcquired)
throw new InvalidOperationException("请求被限流拒绝");
// 转发到内部客户端
return await base.GetResponseAsync(messages, options, cancellationToken);
}
}
使用方式:
csharp
var limiter = new ConcurrencyLimiter(new() { PermitLimit = 2 });
var client = new RateLimitingChatClient(baseClient, limiter);
2. 实现安全过滤中间件
过滤敏感信息,保护数据安全:
csharp
public sealed class ContentFilteringChatClient : DelegatingChatClient
{
private readonly HashSet<string> _sensitiveWords;
public ContentFilteringChatClient(
IChatClient innerClient,
IEnumerable<string> sensitiveWords)
: base(innerClient)
{
_sensitiveWords = new HashSet<string>(
sensitiveWords,
StringComparer.OrdinalIgnoreCase);
}
public override async Task<ChatResponse> GetResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
// 过滤输入消息
var filteredMessages = FilterMessages(messages);
// 调用底层客户端
return await base.GetResponseAsync(
filteredMessages,
options,
cancellationToken);
}
private List<ChatMessage> FilterMessages(IEnumerable<ChatMessage> messages)
{
return messages.Select(m =>
{
if (m.Text != null && ContainsSensitiveWords(m.Text))
{
return new ChatMessage(m.Role, MaskSensitiveWords(m.Text));
}
return m;
}).ToList();
}
}
3. 使用 ChatClientBuilder.Use 简化开发
除了继承 DelegatingChatClient,还可以使用内联方式:
csharp
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
{
return await innerClient.GetResponseAsync(
messages, options, cancellationToken);
}
catch (Exception ex) when (i < 2)
{
Console.WriteLine($"[重试] 第 {i + 1} 次失败,准备重试...");
await Task.Delay(1000 * (i + 1));
}
}
throw new Exception("重试失败");
})
.Build();
优势对比:
| 方式 | 适用场景 | 优势 |
|---|---|---|
| 继承方式 | 复杂逻辑、资源管理 | 完全控制、可复用 |
| 内联方式 | 简单场景、快速开发 | 代码简洁、灵活 |
🔧 创建可复用扩展方法
将中间件封装为扩展方法,提高复用性:
csharp
public static class ChatClientExtensions
{
public static ChatClientBuilder UseRateLimiting(
this ChatClientBuilder builder,
RateLimiter rateLimiter)
{
return builder.Use(innerClient =>
new RateLimitingChatClient(innerClient, rateLimiter));
}
public static ChatClientBuilder UseContentFiltering(
this ChatClientBuilder builder,
IEnumerable<string> 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;
});
}
}
使用扩展方法:
csharp
var client = baseClient.AsBuilder()
.UsePerformanceMonitoring()
.UseContentFiltering(new[] { "密码", "账号" })
.UseRateLimiting(rateLimiter)
.Build();
🏢 企业级最佳实践
1. 中间件执行顺序(洋葱模型)
请求: 外层 → 内层 → AI 模型
响应: AI 模型 → 内层 → 外层
推荐顺序:
| 层级 | 中间件类型 | 原因 |
|---|---|---|
| 最外层 | 日志、监控 | 记录所有请求和响应 |
| 中间层 | 安全过滤 | 在消耗资源前拦截 |
| 内层 | 限流、缓存 | 减少 API 调用 |
示例配置:
csharp
var client = baseClient.AsBuilder()
.UsePerformanceMonitoring() // 外层:监控
.UseContentFiltering(words) // 中层:安全
.UseRateLimiting(limiter) // 内层:限流
.Build();
2. 处理流式和非流式响应
需要同时支持两种模式:
csharp
// 非流式响应
public override async Task<ChatResponse> GetResponseAsync(...)
{
var response = await base.GetResponseAsync(...);
ProcessFullContent(response.Text);
return response;
}
// 流式响应
public override async IAsyncEnumerable<ChatResponseUpdate>
GetStreamingResponseAsync(...)
{
StringBuilder accumulated = new();
await foreach (var update in base.GetStreamingResponseAsync(...))
{
accumulated.Append(update.Text);
yield return update;
}
ProcessFullContent(accumulated.ToString());
}
3. 资源管理和生命周期
正确实现资源释放:
csharp
public sealed class MyCustomChatClient : DelegatingChatClient
{
private readonly 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 中使用:
csharp
// Program.cs
builder.Services.AddSingleton<RateLimiter>(_ =>
new ConcurrencyLimiter(new() { PermitLimit = 10 }));
builder.Services.AddChatClient(services =>
{
var baseClient = /* 创建基础客户端 */;
return baseClient
.AsBuilder()
.UsePerformanceMonitoring()
.UseContentFiltering(new[] { "敏感词" })
.UseRateLimiting(services.GetRequiredService<RateLimiter>())
.Build();
});
// 在服务中注入使用
public class MyService
{
private readonly IChatClient _chatClient;
public MyService(IChatClient chatClient)
{
_chatClient = chatClient;
}
}
🎯 总结
- ✅ 三种实现方式:继承 DelegatingChatClient、Use 内联、扩展方法
- ✅ 常见场景:限流、安全、监控、重试、审计
- ✅ 洋葱模型:外层监控、中层安全、内层限流
- ✅ 生产就绪:资源管理、依赖注入、错误处理
选择建议:
- 💻 复杂逻辑、需要资源管理 → 继承 DelegatingChatClient
- 💻 简单场景、快速开发 → Use 内联方式
- 💻 可复用组件 → 扩展方法
下一步: 探索 MEAI ChatClient中间件和Function Invoker的区别