大模型上下文工程实战:长上下文窗口的Token管理与信息压缩策略

上下文工程为什么比Prompt工程更值得关注

大模型开发中,上下文工程(Context Engineering)正在成为比Prompt工程更高维度的技术方向。当模型支持的上下文窗口从4K扩展到128K乃至百万Token级别,核心挑战不再是”怎么写提示词”,而是”在有限窗口内放什么内容、按什么顺序放、如何压缩冗余信息”。上下文工程直接决定大模型的推理质量、成本和延迟,尤其在AI工具链和智能对话系统场景中,无效Token的累积会显著拉低响应准确率。

上下文窗口的Token管理涉及三个层面:输入侧的上下文裁剪与优先级排序、处理过程中的上下文压缩与摘要、输出侧的上下文窗口溢出处理。实际工程中,很多团队只关注Prompt模板设计,却忽略了系统消息、对话历史、工具调用结果、检索增强内容叠加后的Token总量控制。

Token预算分配与上下文优先级模型

在智能对话系统中,一个完整的上下文由多个部分组成:系统指令、用户画像、对话历史、检索文档、工具调用结果。每个部分对模型推理的贡献度不同,需要建立Token预算分配机制。

以下是一个上下文Token预算分配的Python实现:

import tiktoken

class ContextManager:
    def __init__(self, model_name, max_tokens=128000):
        self.encoder = tiktoken.encoding_for_model(model_name)
        self.max_tokens = max_tokens
        self.budget = {
            'system': 2000,
            'profile': 1000,
            'history': 8000,
            'retrieval': 60000,
            'tools': 3000,
            'response': 15000,
            'buffer': 5000
        }

    def count_tokens(self, text):
        return len(self.encoder.encode(text))

    def allocate(self, context_parts):
        # 按优先级分配Token预算,超出部分截断
        priority = ['system', 'profile', 'tools', 'history', 'retrieval']
        allocated = {}
        remaining = self.max_tokens - self.budget['response'] - self.budget['buffer']

        for key in priority:
            budget = min(self.budget[key], remaining)
            text = context_parts.get(key, '')
            token_count = self.count_tokens(text)

            if token_count <= budget:
                allocated[key] = text
                remaining -= token_count
            else:
                if key == 'history':
                    allocated[key] = self._truncate_history(text, budget)
                elif key == 'retrieval':
                    allocated[key] = self._truncate_retrieval(text, budget)
                else:
                    allocated[key] = self._truncate_tail(text, budget)
                remaining -= budget

        return allocated

    def _truncate_history(self, text, budget):
        # 对话历史截断:保留最近的对话轮次
        messages = text.split('\n---\n')
        result = []
        token_sum = 0
        for msg in reversed(messages):
            tokens = self.count_tokens(msg)
            if token_sum + tokens > budget:
                break
            result.insert(0, msg)
            token_sum += tokens
        return '\n---\n'.join(result)

    def _truncate_retrieval(self, text, budget):
        # 检索文档截断:按相关性分数保留高分片段
        chunks = text.split('\n[CHUNK]\n')
        result = []
        token_sum = 0
        for chunk in chunks:
            tokens = self.count_tokens(chunk)
            if token_sum + tokens > budget:
                break
            result.append(chunk)
            token_sum += tokens
        return '\n[CHUNK]\n'.join(result)

    def _truncate_tail(self, text, budget):
        # 尾部截断:保留前N个Token
        tokens = self.encoder.encode(text)
        truncated = tokens[:budget]
        return self.encoder.decode(truncated)

这个实现的核心思路是给不同上下文来源设定Token预算上限,按优先级分配。系统指令和用户画像优先级最高,因为它们影响全局行为;检索文档预算最大但优先级最低,因为可以被裁剪而不影响基础对话能力。

对话历史压缩:滑动窗口与摘要链

长对话场景中,对话历史会持续增长。简单的滑动窗口截断会丢失早期关键信息。更有效的方案是摘要链(Summary Chain):将超出窗口的历史对话压缩为摘要,再将摘要纳入上下文。

class HistoryCompressor:
    def __init__(self, llm_client, max_turns=10, summary_threshold=5):
        self.llm = llm_client
        self.max_turns = max_turns
        self.summary_threshold = summary_threshold

    def compress(self, messages):
        # 将旧对话轮次压缩为摘要
        if len(messages) <= self.max_turns:
            return messages

        recent = messages[-self.max_turns:]
        old = messages[:-self.max_turns]

        summaries = []
        batch_size = self.summary_threshold
        for i in range(0, len(old), batch_size):
            batch = old[i:i+batch_size]
            summary = self._summarize_batch(batch)
            summaries.append(summary)

        compressed = [
            {'role': 'system', 'content': '以下是之前对话的摘要:\n' + '\n'.join(summaries)},
        ] + recent
        return compressed

    def _summarize_batch(self, batch):
        # 调用模型生成对话摘要
        transcript = '\n'.join([f'{m["role"]}: {m["content"]}' for m in batch])
        prompt = f'请用2-3句话概括以下对话的关键信息,保留重要的事实和决策:\n{transcript}'
        return self.llm.complete(prompt)

摘要链的代价是额外的LLM调用开销。工程上需要权衡:对话轮次较少时用滑动窗口即可,超过20轮的长对话场景才有必要引入摘要压缩。一个实践经验是将摘要阈值设定在对话历史Token占用超过总预算30%时触发。

检索增强生成中的上下文注入策略

RAG场景下,检索返回的文档片段如何注入上下文直接影响模型回答质量。常见做法是将所有片段拼接后放在系统提示中,但更精细的做法是按片段相关性分数动态调整注入位置和数量。

def build_rag_context(query, retriever, max_chunks=5, min_score=0.65):
    # 构建RAG上下文,按分数过滤和排序
    results = retriever.search(query, top_k=max_chunks * 2)

    filtered = [r for r in results if r['score'] >= min_score]
    selected = filtered[:max_chunks]

    context_parts = []
    for idx, chunk in enumerate(selected):
        context_parts.append(
            f'[片段{idx+1} | 相关度: {chunk["score"]:.2f}]\n{chunk["text"]}'
        )

    return '\n\n'.join(context_parts)

片段间的分隔符和编号不是装饰,而是帮助模型区分不同信息来源。实测中,使用明确分隔标记的上下文注入方式,比纯文本拼接的回答准确率提升约15%。

上下文溢出的降级处理

当所有压缩策略都无法将Token降到窗口限制以内时,需要设计降级策略。优先保留系统指令和当前用户输入,逐步丢弃历史对话和检索文档,确保模型至少能基于当前输入给出基本响应。在生产环境中,应该对上下文Token总量做实时监控,超过80%阈值时触发告警,避免因窗口溢出导致服务异常。Prompt工程中常用的思维链(CoT)也会占用上下文空间,在设计时需要将推理链长度纳入Token预算考量。自然语言处理领域的最新研究也表明,上下文中信息的排列顺序对模型理解效果有显著影响,将最相关的信息放在上下文的开头和结尾位置(”中间遗忘”效应),可以进一步提升模型对关键信息的利用率。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/da-mo-xing-shang-xia-wen-gong-cheng-shi-zhan-zhang-shang/

(0)
小编小编
上一篇 11小时前
下一篇 11小时前

相关推荐