Published on

多轮对话管理中的滑动窗口机制 (Sliding Window Mechanism)

Authors
  • avatar
    Name
    Shelton Ma
    Twitter

在 AI Companion、聊天机器人等应用中,为了维持连贯的对话体验,必须妥善管理对话上下文.由于大模型存在 Token 限制(如 GPT-4 上限约 128k tokens),长对话会因 Token 超限而丢失早期对话内容.

滑动窗口 + 摘要压缩 + 长期记忆持久化 (如 Redis、MongoDB), 不仅能够在 GPT 的 Token 限制内高效保留对话信息,还能最大化上下文的完整度,是在多轮对话管理中值得推荐的解决方案.

1. 滑动窗口机制的核心概念

  1. 保留最新对话内容,并在 Token 达到上限时丢弃较早的对话.
  2. 类似 窗口 在文本上滑动,仅保留窗口内的数据.
  3. 窗口的大小应根据模型的 Token 限制动态调整.

2. 滑动窗口的 TypeScript 实现

import { encoding_for_model } from 'tiktoken';

const encoder = encoding_for_model('gpt-4'); // GPT-4 编码器

interface Message {
  role: 'system' | 'user' | 'assistant';
  content: string;
}

// 最大 Token 限制
const MAX_TOKENS = 8192;

// 滑动窗口函数
function slidingWindow(messages: Message[], maxTokens: number): Message[] {
  let totalTokens = 0;
  const result: Message[] = [];

  // 从最新消息往前遍历,尽可能保留最多的消息
  for (let i = messages.length - 1; i >= 0; i--) {
    const tokens = encoder.encode(messages[i].content).length;
    totalTokens += tokens;

    if (totalTokens > maxTokens) break; // 超出上限时停止
    result.unshift(messages[i]); // 保留该条消息
  }

  return result;
}

// 示例对话数据
const messages: Message[] = [
  { role: 'user', content: '你好,请推荐一家意大利餐厅.' },
  { role: 'assistant', content: '推荐你去“小意大利之家”,非常正宗.' },
  { role: 'user', content: '这家人均消费多少?' },
  { role: 'assistant', content: '大约150元每人.' },
];

// 模拟不断添加对话
messages.push({ role: 'user', content: '环境如何?' });
messages.push({ role: 'assistant', content: '氛围温馨且安静,适合约会.' });

// 调用滑动窗口
const filteredMessages = slidingWindow(messages, MAX_TOKENS);
console.log(filteredMessages);

3. 最佳实践

1. 优化方案

  1. 重要消息加权
  2. 摘要/压缩 (Summarization)
  3. 角色区分 (Role Splitting)

2. 方案确定

在 AI Companion 或聊天机器人中,滑动窗口摘要压缩 结合使用,可以有效平衡以下两方面:

  • 保持最近对话的完整性(关键细节不丢失)
  • 提炼早期对话的核心信息(降低 Token 消耗)

3. 滑动窗口 + 摘要压缩 的整体思路

  1. 滑动窗口优先
    • 先保留最近几轮对话,确保最新内容完整.
    • 只在 Token 超出阈值时,触发摘要压缩.
  2. 摘要压缩早期内容:
    • 当窗口中消息过多、超出 Token 限制时,将窗口内的早期对话压缩成摘要.
    • 压缩内容保留核心信息,降低 Token 占用.

4. 实现

import { encoding_for_model } from 'tiktoken';
import axios from 'axios';

const encoder = encoding_for_model('gpt-4');

interface Message {
  role: 'system' | 'user' | 'assistant';
  content: string;
}

// Token 限制 (如 GPT-4 限制 8192 tokens)
const MAX_TOKENS = 8192;
const SUMMARY_MAX_TOKENS = 500;

// 模拟 OpenAI API 摘要功能
async function summarizeText(content: string): Promise<string> {
  const { data } = await axios.post(
    'https://api.openai.com/v1/chat/completions',
    {
      model: 'gpt-4-turbo',
      messages: [
        { role: 'system', content: '请将以下内容压缩成不超过 500 字的摘要.' },
        { role: 'user', content }
      ],
      max_tokens: SUMMARY_MAX_TOKENS
    },
    {
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        'Content-Type': 'application/json'
      }
    }
  );
  return data.choices[0].message.content;
}

// 滑动窗口 + 摘要压缩的主逻辑
async function manageConversation(messages: Message[]): Promise<Message[]> {
  let totalTokens = 0;
  const result: Message[] = [];

  // 从最新消息开始保留
  for (let i = messages.length - 1; i >= 0; i--) {
    const tokens = encoder.encode(messages[i].content).length;
    totalTokens += tokens;

    if (totalTokens > MAX_TOKENS) {
      // Token 超过限制,触发摘要
      const oldMessages = messages.slice(0, i + 1); // 提取超出 Token 前的对话
      const oldContent = oldMessages.map(msg => msg.content).join('\n');
      const summary = await summarizeText(oldContent);

      // 将摘要作为新消息插入
      result.unshift({ role: 'system', content: `摘要:${summary}` });
      break;
    }

    result.unshift(messages[i]);
  }

  return result;
}

// 示例对话数据
const messages: Message[] = [
  { role: 'user', content: '你好,推荐一家上海的意大利餐厅.' },
  { role: 'assistant', content: '推荐你去“小意大利之家”,非常正宗.' },
  { role: 'user', content: '我喜欢安静的环境.' },
  { role: 'assistant', content: '这家氛围温馨,适合安静约会.' },
  // ... (更多对话)
];

// 调用示例
manageConversation(messages).then(filteredMessages => {
  console.log(filteredMessages);
});