.NET+AI | MEAI | 上下文压缩(7)

Chat Reducer:让 AI 对话突破上下文限制

一句话简介

Microsoft.Extensions.AI 的 Chat Reducer 通过智能压缩策略,在保持对话质量的前提下,有效控制上下文长度、降低成本并提升性能。


🎯 核心价值

  • 突破限制:解决 LLM 上下文窗口限制(如 GPT-4 的 8K/32K tokens)
  • 成本优化:减少输入 token,显著降低 API 调用成本
  • 性能提升:缩短上下文长度,加快模型推理速度
  • 智能压缩:保留关键信息,自动过滤冗余历史消息

📝 为什么需要 Chat Reducer?

在多轮对话场景中,我们面临三大挑战:

挑战 问题 Chat Reducer 方案
上下文限制 超出模型限制导致请求失败 智能压缩到安全范围
成本失控 输入 token 越多费用越高 过滤冗余,只保留必要信息
性能下降 过长上下文增加推理时间 减少处理负担,提升响应速度

典型场景:

  • 💬 长时间客服对话(用户反复咨询)
  • 🏥 医疗咨询(需要完整病史)
  • ⚖️ 法律咨询(案情细节不能丢失)
  • 🎓 教育辅导(需要追踪学习进度)

🏗️ 两种压缩策略

1. MessageCountingChatReducer(计数压缩器)

通过限制消息数量来控制对话长度。

核心特性:

  • 🔧 始终保留第一条系统消息
  • 🔧 保留最近 N 条用户/助手消息
  • 🔧 自动排除函数调用相关消息
  • 🔧 零延迟,无额外 API 成本

适用场景:

  • 客服机器人(只关注最近几轮)
  • 快速问答系统
  • 技术支持(问题独立,不需长期上下文)

2. SummarizingChatReducer(摘要压缩器)

利用 AI 自动生成摘要压缩历史对话。

核心特性:

  • 🔧 超过阈值时自动调用 AI 生成摘要
  • 🔧 摘要存储在 AdditionalProperties
  • 🔧 渐进式压缩(新摘要包含旧摘要)
  • 🔧 保留完整语义上下文

适用场景:

  • 医疗咨询(完整病史重要)
  • 法律咨询(案情细节关键)
  • 教育辅导(长期进度追踪)

💻 快速开始

1. 使用计数压缩器

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

2. 使用摘要压缩器

// 创建摘要压缩器
// 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] 使用人工泪液...

🔧 高级配置

1. 自定义摘要提示词

var reducer = new SummarizingChatReducer(baseChatClient, targetCount: 2);

// 设置领域专用摘要提示词
reducer.SummarizationPrompt = """
请为以下医疗咨询对话生成简洁的临床摘要(不超过3句话):

要求:
- 提取患者主诉症状和时长
- 记录已提供的初步建议
- 保留关键医学信息
- 使用专业医学术语

格式: 【患者主诉】症状 | 【已知信息】背景 | 【初步建议】建议
""";

2. 参数调优建议

MessageCountingChatReducer:

策略 参数配置 适用场景
保守策略 targetCount: 10 上下文敏感场景
均衡策略 targetCount: 5 一般对话
激进策略 targetCount: 2 成本优先

SummarizingChatReducer:

策略 参数配置 效果
频繁摘要 threshold: 0 每次超过立即摘要
延迟摘要 threshold: 3 减少 API 调用

3. 与其他中间件组合

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)专门用于摘要生成,降低成本。


💡 最佳实践

1. 函数调用消息自动保护

两种 Reducer 都会自动排除函数调用相关消息,避免破坏上下文:

// 这些消息会被自动跳过,不计入 targetCount
- FunctionCallContent
- FunctionResultContent

2. 多用户场景

为每个用户会话创建独立消息列表,共享 Reducer 实例:

// 全局共享的 Reducer(无状态)
var sharedReducer = new MessageCountingChatReducer(5);

// 每个用户独立的消息历史
var user1Messages = new List<ChatMessage>();
var user2Messages = new List<ChatMessage>();

3. 自定义 Reducer

实现 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);
    }
}

⚠️ 注意事项

1. 原始消息不会被修改

ReduceAsync() 返回新列表,原始列表保持不变。如需审计,可在本地保留完整历史:

var allMessages = new List<ChatMessage>();      // 完整历史
var reducedMessages = await reducer.ReduceAsync(allMessages);
// allMessages 仍包含所有消息

2. 摘要压缩的信息损失

摘要依赖 LLM 理解能力,可能会:

  • ✅ 保留主要事实和语义
  • ⚠️ 丢失细微情感、口语化表达
  • ⚠️ 潜在的理解偏差

建议: 关键信息(订单号、金额)结合数据库存储,不完全依赖摘要。


3. 流式响应支持

Reducer 完全支持流式场景,在开始传输前自动完成压缩:

await foreach (var update in client.GetStreamingResponseAsync(messages))
{
    Console.Write(update.Text);
}

🎯 总结

  • 两种策略: MessageCounting(快速简单) vs Summarizing(语义完整)
  • 一行集成: 通过 UseChatReducer() 轻松启用
  • 灵活配置: 支持自定义提示词、参数调优、自定义实现
  • 生产就绪: 自动处理函数调用、支持流式、多用户安全

选择建议:

  • 💬 短期对话、成本敏感 → MessageCountingChatReducer
  • 🏥 长期咨询、语义重要 → SummarizingChatReducer
posted @ 2025-11-29 17:13  「圣杰」  阅读(0)  评论(0)    收藏  举报