Microsoft.Extensions.AI 的 Chat Reducer 通过智能压缩策略,在保持对话质量的前提下,有效控制上下文长度、降低成本并提升性能。
在多轮对话场景中,我们面临三大挑战:
挑战 | 问题 | Chat Reducer 方案 |
|---|---|---|
上下文限制 | 超出模型限制导致请求失败 | 智能压缩到安全范围 |
成本失控 | 输入 token 越多费用越高 | 过滤冗余,只保留必要信息 |
性能下降 | 过长上下文增加推理时间 | 减少处理负担,提升响应速度 |
典型场景:
通过限制消息数量来控制对话长度。
核心特性:
适用场景:
利用 AI 自动生成摘要压缩历史对话。
核心特性:
AdditionalProperties 中适用场景:
using Microsoft.Extensions.AI;
// 创建压缩器,保留最近 3 条消息
var countingReducer = new MessageCountingChatReducer(targetCount: 3);
// 集成到 Chat Client
var client = baseChatClient.AsBuilder()
.UseChatReducer(reducer: countingReducer)
.Build();
// 正常使用,自动压缩
var response = await client.GetResponseAsync(messages);工作原理:
原始消息(13条) 压缩后(4条)
[System] 你是助手 [System] 你是助手
[User] 问题1
[Assistant] 回答1
[User] 问题2
[Assistant] 回答2
... [User] 问题5
[User] 问题5 [Assistant] 回答5
[Assistant] 回答5 [User] 问题6
[User] 问题6// 创建摘要压缩器
// targetCount: 保留最近 2 条消息
// threshold: 超过 targetCount + threshold 时触发摘要
var summarizingReducer = new SummarizingChatReducer(
chatClient: baseChatClient,
targetCount: 2,
threshold: 1 // 超过 3 条时触发
);
// 集成到 Chat Client
var client = baseChatClient.AsBuilder()
.UseChatReducer(reducer: summarizingReducer)
.Build();工作原理:
原始消息(7条) 压缩后(4条)
[System] 你是医疗助手 [System] 你是医疗助手
[User] 我头痛 [Summary] 患者主诉头痛,
[Assistant] 可能是压力... 睡眠不足,已建议休息
[User] 我睡眠不足 [User] 我眼睛干涩
[Assistant] 建议保证睡眠 [Assistant] 使用人工泪液...
[User] 我眼睛干涩
[Assistant] 使用人工泪液...var reducer = new SummarizingChatReducer(baseChatClient, targetCount: 2);
// 设置领域专用摘要提示词
reducer.SummarizationPrompt = """
请为以下医疗咨询对话生成简洁的临床摘要(不超过3句话):
要求:
- 提取患者主诉症状和时长
- 记录已提供的初步建议
- 保留关键医学信息
- 使用专业医学术语
格式: 【患者主诉】症状 | 【已知信息】背景 | 【初步建议】建议
""";MessageCountingChatReducer:
策略 | 参数配置 | 适用场景 |
|---|---|---|
保守策略 | targetCount: 10 | 上下文敏感场景 |
均衡策略 | targetCount: 5 | 一般对话 |
激进策略 | targetCount: 2 | 成本优先 |
SummarizingChatReducer:
策略 | 参数配置 | 效果 |
|---|---|---|
频繁摘要 | threshold: 0 | 每次超过立即摘要 |
延迟摘要 | threshold: 3 | 减少 API 调用 |
var client = baseChatClient.AsBuilder()
.UseChatReducer(reducer: summarizingReducer) // 先压缩
.UseFunctionInvocation() // 再处理函数
.Build();⚠️ 注意: Reducer 应放在管道前端,确保在调用 API 前完成压缩。
场景 | 推荐 Reducer | 原因 |
|---|---|---|
客服机器人 | MessageCounting | 只需最近几轮,历史价值低 |
技术支持 | MessageCounting | 问题独立,不需长期上下文 |
医疗咨询 | Summarizing | 需完整病史,摘要保证连续性 |
法律咨询 | Summarizing | 案情细节重要,不能丢失 |
教育辅导 | Summarizing | 学习进度需长期追踪 |
快速问答 | MessageCounting | 对话简短,不需复杂摘要 |
对比项 | MessageCounting | Summarizing |
|---|---|---|
额外 API 调用 | ✅ 无 | ❌ 每次摘要 1 次 |
延迟 | ✅ 0ms | ⚠️ 1-3 秒 |
语义完整性 | ⚠️ 可能丢失 | ✅ 保留 |
成本 | ✅ 低 | ⚠️ 中等 |
适用场景 | 短期对话 | 长期对话 |
💡 优化技巧: 使用较小模型(如 GPT-3.5)专门用于摘要生成,降低成本。
两种 Reducer 都会自动排除函数调用相关消息,避免破坏上下文:
// 这些消息会被自动跳过,不计入 targetCount
- FunctionCallContent
- FunctionResultContent为每个用户会话创建独立消息列表,共享 Reducer 实例:
// 全局共享的 Reducer(无状态)
var sharedReducer = new MessageCountingChatReducer(5);
// 每个用户独立的消息历史
var user1Messages = new List<ChatMessage>();
var user2Messages = new List<ChatMessage>();实现 IChatReducer 接口创建自定义压缩逻辑:
public class CustomReducer : IChatReducer
{
public Task<IEnumerable<ChatMessage>> ReduceAsync(
IEnumerable<ChatMessage> messages,
CancellationToken cancellationToken)
{
// 自定义压缩逻辑
var reduced = messages
.Where(m => /* 自定义条件 */)
.TakeLast(5);
return Task.FromResult(reduced);
}
}ReduceAsync() 返回新列表,原始列表保持不变。如需审计,可在本地保留完整历史:
var allMessages = new List<ChatMessage>(); // 完整历史
var reducedMessages = await reducer.ReduceAsync(allMessages);
// allMessages 仍包含所有消息摘要依赖 LLM 理解能力,可能会:
建议: 关键信息(订单号、金额)结合数据库存储,不完全依赖摘要。
Reducer 完全支持流式场景,在开始传输前自动完成压缩:
await foreach (var update in client.GetStreamingResponseAsync(messages))
{
Console.Write(update.Text);
}UseChatReducer() 轻松启用选择建议: