上下文窗口:模型的"工作台"

上下文窗口是模型的工作台——它能在一次推理中处理的所有信息。窗口越大,模型能"看到"的信息越多,但也意味着更高的计算成本和更慢的推理速度。如何在有限的窗口中放入最有价值的信息,是一个核心工程问题。

窗口大小的演进

GPT-3 (2020):      2K tokens
GPT-3.5 (2022):    4K-16K tokens
GPT-4 (2023):      8K-128K tokens
Gemini 1.5 (2024): 1M-2M tokens
Llama 4 (2026):    256K-10M tokens

窗口在持续增长,但"能用"和"用好"是两回事。研究表明,即使支持百万token的窗口,模型在长上下文中的表现也远不如短上下文——这就是"Lost in the Middle"问题。

Lost in the Middle问题

现象

模型对上下文开头和结尾的信息处理得好,中间的信息容易被忽略:

准确率分布:
位置1-10%:    ████████████████████ 85%
位置10-90%:   ████████████ 55%     ← 中间区域
位置90-100%:  ████████████████████ 82%

缓解策略

def reorder_context(query, documents):
    """重排上下文,将最相关的放在开头和结尾"""
    # 计算每个文档与query的相关性
    scored = [(doc, relevance(query, doc)) for doc in documents]
    scored.sort(key=lambda x: x[1], reverse=True)
    
    # 最相关的放在开头和结尾
    n = len(scored)
    top = scored[:n//2]
    bottom = scored[n//2:]
    
    # 开头放最相关,结尾放次相关
    reordered = [d for d, _ in top] + [d for d, _ in reversed(bottom)]
    return reordered

上下文管理策略

1. 滑动窗口

最简单的策略:保留最近的N条消息,丢弃更早的:

class SlidingWindow:
    def __init__(self, max_tokens=8000):
        self.max_tokens = max_tokens
        self.messages = []
    
    def add(self, message):
        self.messages.append(message)
        self._truncate()
    
    def _truncate(self):
        while self._total_tokens() > self.max_tokens:
            self.messages.pop(0)  # 移除最早的消息

问题:完全丢失早期上下文,可能遗忘关键信息。

2. 摘要压缩

将早期对话压缩为摘要:

class SummaryCompression:
    def __init__(self, max_tokens=8000, summary_threshold=6000):
        self.max_tokens = max_tokens
        self.summary_threshold = summary_threshold
        self.messages = []
        self.summary = ""
    
    def add(self, message):
        self.messages.append(message)
        
        if self._total_tokens() > self.summary_threshold:
            self._compress()
    
    def _compress(self):
        # 保留最近N条消息
        keep_count = min(5, len(self.messages) // 3)
        recent = self.messages[-keep_count:]
        old = self.messages[:-keep_count]
        
        # 摘要旧消息
        old_text = "\n".join([m["content"] for m in old])
        self.summary = llm.generate(f"""
        概括以下对话的关键信息,保留:
        1. 用户的意图和需求
        2. 已经做出的决定
        3. 关键的技术细节
        4. 尚未解决的问题
        
        对话内容:
        {old_text}
        """)
        
        self.messages = recent
    
    def get_context(self):
        context = []
        if self.summary:
            context.append({
                "role": "system",
                "content": f"之前的对话摘要:{self.summary}"
            })
        context.extend(self.messages)
        return context

3. 选择性记忆

不是所有消息都同等重要。选择性记忆保留关键信息,丢弃闲聊:

class SelectiveMemory:
    def __init__(self):
        self.importance_classifier = ImportanceClassifier()
        self.important_messages = []
        self.recent_messages = []
        self.max_recent = 10
        self.max_important = 20
    
    def add(self, message):
        # 评估消息重要性
        importance = self.importance_classifier.score(message)
        
        if importance > 0.7:
            # 重要消息:长期保留
            self.important_messages.append({
                **message,
                "importance": importance,
                "timestamp": datetime.now()
            })
            if len(self.important_messages) > self.max_important:
                self._evict_least_important()
        
        # 所有消息都加入最近列表
        self.recent_messages.append(message)
        if len(self.recent_messages) > self.max_recent:
            self.recent_messages.pop(0)
    
    def get_context(self):
        return self.important_messages + self.recent_messages

class ImportanceClassifier:
    def score(self, message):
        """评估消息的重要性"""
        score = 0.0
        
        # 包含决策性内容
        if any(kw in message["content"] for kw in ["决定", "选择", "确认", "同意"]):
            score += 0.3
        
        # 包含技术细节
        if any(kw in message["content"] for kw in ["代码", "配置", "路径", "版本"]):
            score += 0.2
        
        # 包含用户偏好
        if any(kw in message["content"] for kw in ["我喜欢", "不要", "请记住"]):
            score += 0.3
        
        # 消息长度(长消息通常更重要)
        if len(message["content"]) > 200:
            score += 0.1
        
        return min(score, 1.0)

4. 结构化上下文

将非结构化的对话历史组织为结构化状态:

class StructuredContext:
    def __init__(self):
        self.user_profile = {}        # 用户信息
        self.task_state = {}          # 任务状态
        self.decisions = []           # 已做决策
        self.open_questions = []      # 待解决问题
        self.technical_context = {}   # 技术上下文
    
    def update(self, message):
        """从消息中提取结构化信息"""
        # 使用LLM提取结构化信息
        extracted = llm.generate(f"""
        从以下对话消息中提取结构化信息:
        {message["content"]}
        
        提取:
        - 用户信息(姓名、偏好、背景)
        - 任务状态(当前在做什么、进展如何)
        - 决策记录(做了什么决定)
        - 待解决问题(还有什么未解决)
        - 技术细节(代码、配置、错误信息)
        
        输出JSON格式。
        """)
        
        self._merge(extracted)
    
    def get_context(self):
        """生成结构化的上下文"""
        return f"""
## 用户信息
{json.dumps(self.user_profile, ensure_ascii=False)}

## 当前任务
{json.dumps(self.task_state, ensure_ascii=False)}

## 已做决策
{json.dumps(self.decisions, ensure_ascii=False)}

## 待解决问题
{json.dumps(self.open_questions, ensure_ascii=False)}

## 技术上下文
{json.dumps(self.technical_context, ensure_ascii=False)}
"""

5. 检索增强上下文

将历史对话存入向量数据库,按需检索:

class RetrievalAugmentedContext:
    def __init__(self, embedder, vector_store):
        self.embedder = embedder
        self.store = vector_store
        self.recent = []  # 最近几条消息
    
    def add(self, message):
        self.recent.append(message)
        if len(self.recent) > 5:
            old = self.recent.pop(0)
            # 存入向量数据库
            embedding = self.embedder.encode(old["content"])
            self.store.add({
                "embedding": embedding,
                "message": old,
                "timestamp": datetime.now()
            })
    
    def get_context(self, current_query):
        # 检索与当前查询相关的历史消息
        query_embedding = self.embedder.encode(current_query)
        relevant = self.store.search(query_embedding, top_k=5)
        
        context = []
        
        # 添加检索到的相关历史
        for item in relevant:
            context.append(item["message"])
        
        # 添加最近的消息
        context.extend(self.recent)
        
        return context

策略组合

实际系统中通常组合多种策略:

class HybridContextManager:
    def __init__(self):
        self.structured = StructuredContext()     # 结构化状态
        self.selective = SelectiveMemory()        # 选择性记忆
        self.retrieval = RetrievalAugmentedContext()  # 检索增强
        self.recent = []  # 最近5条消息
    
    def add(self, message):
        self.structured.update(message)
        self.selective.add(message)
        self.retrieval.add(message)
        self.recent.append(message)
    
    def get_context(self, query):
        context_parts = []
        
        # 1. 结构化状态(始终包含)
        context_parts.append({
            "role": "system",
            "content": self.structured.get_context()
        })
        
        # 2. 检索到的相关历史
        relevant = self.retrieval.get_context(query)
        context_parts.extend(relevant)
        
        # 3. 重要记忆
        context_parts.extend(self.selective.get_context())
        
        # 4. 最近消息
        context_parts.extend(self.recent)
        
        return context_parts

Token预算分配

class TokenBudgetAllocator:
    def __init__(self, total_budget=32000):
        self.budget = total_budget
    
    def allocate(self, components):
        """智能分配token预算"""
        # 权重配置
        weights = {
            "system_prompt": 0.10,    # 系统提示
            "structured_state": 0.15,  # 结构化状态
            "retrieved_context": 0.25, # 检索上下文
            "important_memory": 0.15,  # 重要记忆
            "recent_messages": 0.25,   # 最近消息
            "current_query": 0.05,     # 当前查询
            "reserved": 0.05,          # 预留(用于输出)
        }
        
        allocation = {}
        for component, weight in weights.items():
            allocation[component] = int(self.budget * weight)
        
        return allocation

结语

上下文窗口管理是在有限空间内最大化信息价值的艺术。随着模型支持越来越长的上下文,管理策略的复杂性也在增长。但核心原则不变:放入最相关的信息,丢弃冗余,保持结构化。好的上下文管理可以让8K窗口的效果超过糟糕的128K窗口管理——关键不在于窗口多大,而在于你放进了什么。