大模型上下文窗口受限?四种工程方案解决长文本记忆难题

📅 发布时间:2026/8/30 9:40:15
大模型上下文窗口受限?四种工程方案解决长文本记忆难题 大家好我是老K。最近刷到一条挺有意思的新闻Cohere 的 CEO Aidan Gomez 在一次公开场合自嘲说自己的头衔可以改成“Chief Brain Damage Officer”首席大脑损伤官。乍一听像段子细想其实点破了大模型行业一个很真实的困境——模型推理时“脑容量”上下文窗口有限信息一多就“短路”这种“脑损”感用过长文本大模型的朋友估计都懂。今天不聊八卦就借着这个梗把大模型“记忆差、上下文有限、处理长文本容易懵”的问题掰开揉碎讲清楚。我们会聊到 Transformer 的注意力机制、上下文窗口的物理限制、Token 与显存的关系再给出工程项目里最常用的几种缓解方案比如 Prompt 压缩、检索增强生成RAG、摘要记忆、滑动窗口等并配上可落地的代码示例。如果你正在做大模型应用开发或者准备把 LLM 接入业务系统这篇文章应该能帮你省不少踩坑时间。1. 背景与核心概念1.1 “大脑损伤”从何而来先解释一下标题里的梗。Aidan Gomez 是 Cohere 的联合创始人兼 CEO也是 Transformer 论文《Attention Is All You Need》的作者之一。他自嘲“首席大脑损伤官”其实是在调侃大语言模型LLM的“记忆”和“理解”并没有我们想象中那么稳定。模型接收一段很长的文本时如果关键信息出现在中间位置或者上下文总长度逼近上限推理质量就会明显下降。表现出来就是读完了 20 页 PDF问它第一页写了啥它支支吾吾。长对话聊到后面忘了最开始用户提的需求。代码文件一长补全时开始胡编 API。多文档对比时抓不住重点输出又长又空。这些现象本质上都可以归因于模型处理长上下文时的“注意力分散”和“上下文截断”。1.2 为什么模型会“忘事”大模型的语言能力建立在 Transformer 架构之上。它的核心机制是自注意力Self-Attention也就是模型在处理每个 Token可以粗略理解为一个词或一个子词时会去看输入序列里的其他 Token并计算它们之间的关联权重。但注意力机制存在两个问题注意力是“有限容量”的。当输入序列非常长时模型需要把有限的注意力分配到大量 Token 上关键信息容易被稀释。计算复杂度高。原始 Transformer 的注意力复杂度是 O(n²)也就是 Token 数量翻倍计算量和显存占用会变成原来的四倍。所以模型不可能无限扩展上下文。这也是为什么很多模型虽然宣称支持“128K 上下文”但真正处理超长文本时效果依然会打折扣。1.3 上下文窗口是什么上下文窗口Context Window是指模型在生成下一个 Token 时能“看到”的最大 Token 数量。它既包括用户输入的 Prompt也包括模型已经生成的内容。举个例子假设一个模型的上下文窗口是 4096 Token那么用户输入的 Prompt 占了多少 Token模型可用生成空间就少了多少。如果 Prompt 超过 4096 Token超出部分会被截断不同 API 处理方式不同有报错的有静默丢弃开头的有自动摘要的。如果对话轮次很多历史消息也会占上下文窗口“历史越长模型越笨”的情况就会出现。理解上下文窗口是理解大模型应用工程化的第一步。2. 环境准备与版本说明本文后续的代码示例主要围绕 OpenAI 兼容接口和 Cohere 的 Python SDK 展开目的是演示通用的长文本处理思路而不是绑定某一家平台。2.1 运行环境项目说明操作系统Windows 10/11、macOS、Linux 均可Python3.9 或以上版本依赖库openai、cohere、tiktoken、langchain可选网络要求能正常访问相应模型 API 服务IDEPyCharm、VS Code 或 Jupyter Notebook 均可2.2 安装依赖pip install openai cohere tiktoken如果需要用 LangChain 做检索增强可以额外安装pip install langchain langchain-community chromadb说明不同时期的 SDK 版本方法名和参数可能会有差异。如果运行报错优先查看官方最新文档。本文的重点是演示思路不是绑定某个具体版本。2.3 准备 API Key无论使用哪家模型服务都需要配置 API Key。建议通过环境变量加载避免把密钥硬编码在代码里。import os # 环境变量方式避免明文写在代码中 os.environ[OPENAI_API_KEY] 你的 Key # 如果使用 Cohere则设置 os.environ[COHERE_API_KEY] 你的 Key3. 核心原理解析3.1 Token、上下文与“脑损”的关系在进入代码示例之前先把几个核心概念讲透。Token模型处理文本的最小单位。英文通常一个单词分成一个或多个 Token中文一个汉字可能对应一个或多个 Token。同样一段文本不同分词器的 Token 数量不同。上下文窗口的数值比如 4096、8192、32768、131072这些数字描述的是模型单次推理能容纳的 Token 总数而不是字符数。中文字符大约 1 到 1.5 个 Token英文单词大约 1.3 个 Token。关键理解窗口一旦填满再多的输入也无法处理。工程上的各种优化技巧本质上就是在“窗口有限”的约束下尽量保住关键信息。3.2 为什么长文本会“中间遗忘”有研究指出很多模型存在“Lost in the Middle”现象当关键信息位于输入文本的中间位置时模型的表现最差而开头和结尾的信息往往更容易被模型利用。为什么会这样模型在训练时很多样本都是“前面是任务描述后面是答案”因此模型更习惯从开头提取指令、从结尾提取结论。自注意力机制中相邻 Token 之间的关联更容易被捕捉距离太远的 Token 即使有关联权重也可能被稀释。长文本包含大量冗余信息模型难以判断哪些 Token 才是“关键先生”。这个现象告诉我们不要单纯依赖模型自己“领悟”长文本而是应该主动帮它把关键信息放到更容易被注意到的位置。3.3 长文本处理的工程三板斧在真实的项目里处理长文本通常有三种路线截断Truncation简单粗暴按窗口上限切分适合早期的玩具应用。压缩Compression把历史内容总结成摘要把无关内容剔除保留核心信息。检索增强RAG把文档切块、向量化先检索出与问题最相关的片段再把这些片段拼进 Prompt。这三种方案不互斥实际项目中往往是组合使用。下面我们逐一来看可运行的代码示例。4. 完整实战案例4.1 先写一个最简单的“上下文体感测试”我们的目标是写一个函数模拟大模型 API 在上下文长度受限时的表现。# 文件路径demo_context_limit.py from openai import OpenAI client OpenAI() def chat_with_context(messages, modelgpt-4o-mini): 发送对话到模型并返回回复内容。 messages: 消息列表格式为 [{role: user, content: ...}] response client.chat.completions.create( modelmodel, messagesmessages, temperature0.7, ) return response.choices[0].message.content # 构造一个简单的两轮对话 messages [ {role: system, content: 你是一个乐于助人的助手。}, {role: user, content: 我的名字叫小明我住在上海。}, ] # 第一轮模型应该记住名字 reply1 chat_with_context(messages) print(第一轮回复, reply1) # 构造第二轮对话模拟长时间对话后上下文被截断或遗忘 messages.append({role: assistant, content: reply1}) messages.append({role: user, content: 我刚才说我叫什么名字}) reply2 chat_with_context(messages) print(第二轮回复, reply2)这个例子本身很简单但它暴露了一个问题当 messages 列表越来越长直到超出上下文窗口时怎么办我们可以在 messages 里添加大量填充文本来压测。# 文件路径demo_context_pressure.py import tiktoken def count_tokens(text, modelgpt-4o-mini): 估算文本的 Token 数量 enc tiktoken.encoding_for_model(model) return len(enc.encode(text)) # 构造大量填充文本模拟长文档 long_text 这是一段无关紧要的内容。 * 5000 print(填充文本 Token 数, count_tokens(long_text))运行后会看到填充文本轻松超过几千 Token。如果把这些内容塞进 Prompt模型要么报错要么开头信息被截断。你可以在自己的环境里跑一下感受上下文窗口被塞满时的现象。4.2 方案一Prompt 压缩文本摘要当历史对话或文档过长时我们可以先用一个“摘要模型”把历史信息提炼成精简版本再交给下一轮对话使用。# 文件路径compress_context.py from openai import OpenAI client OpenAI() def summarize_text(text, max_tokens200): 使用大模型对文本进行摘要压缩。 text: 原始长文本 max_tokens: 摘要的最大 Token 数 prompt f请对以下内容进行压缩摘要保留关键信息不要遗漏重要人名、数字和结论。 内容如下 {text} 请输出摘要 response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: user, content: prompt} ], max_tokensmax_tokens, temperature0.3, ) return response.choices[0].message.content # 一段模拟的超长业务日志 raw_log 【用户反馈】2025-01-10 10:23:11 用户小明反馈订单号 10086 无法支付。 【排查记录】2025-01-10 10:30:05 工程师初步检查发现支付网关回调超时。 【处理记录】2025-01-10 10:45:33 订单状态被重置为待支付但用户仍然无法完成支付。 【二次排查】2025-01-10 11:02:47 检查发现用户账户风控状态异常疑似触发限流规则。 【最终结论】2025-01-10 11:20:19 解除风控标记后订单支付成功。 # 压缩摘要 summary summarize_text(raw_log, max_tokens100) print(压缩后的摘要, summary)为什么这样做有效摘要相当于把“散落的关键信息”重组成一个紧凑的、被放在显眼位置的段落。后续模型在推理时只需要依赖摘要里的信息不需要再去大海捞针。注意事项摘要有损压缩必须根据业务场景设置“不可遗漏”的信息类型比如金额、订单号、时间、责任人。摘要本身也会占 Token所以摘要长度要控制好。对长对话可以采用“滑动窗口 历史摘要”的混合方案最近几轮对话保留原始内容更早的对话压缩成摘要。4.3 方案二检索增强生成RAGRAG 是目前大模型应用中最主流的方案之一。它的核心思路是模型不需要读完整篇文档而是先通过向量检索找到与问题最相关的少数几个片段再把片段拼接进 Prompt。# 文件路径rag_demo.py import os from openai import OpenAI from chromadb import PersistentClient from chromadb.utils import embedding_functions client OpenAI() # 1. 准备一个简易文档集 documents [ 订单号 10086 的支付失败原因是网关回调超时。, 用户小明住在上海注册时间是 2024 年 6 月。, 风控记录账户 A 在短时间内触发多次限流规则。, 退款流程用户发起退款后系统会在 24 小时内审核。, 商品库存SKU 888 当前库存为 32 件。, ] # 2. 初始化向量数据库Chroma 本地模式 chroma_client PersistentClient(path./chroma_db) embed_fn embedding_functions.OpenAIEmbeddingFunction( api_keyos.environ[OPENAI_API_KEY], model_nametext-embedding-3-small ) collection chroma_client.get_or_create_collection( namedocs, embedding_functionembed_fn ) # 3. 插入文档带 ID ids [str(i) for i in range(len(documents))] collection.upsert(idsids, documentsdocuments) # 4. 检索与问题最相关的片段 def retrieve(query, k2): results collection.query(query_texts[query], n_resultsk) return results[documents][0] # 5. 构造 Prompt 并调用模型 def answer_with_rag(question): related_docs retrieve(question) context \n\n.join(related_docs) prompt f请根据以下资料回答问题。 如果资料中没有相关信息请直接回答“资料中未找到相关信息”。 资料 {context} 问题{question} 请回答 response client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0.3, ) return response.choices[0].message.content # 测试 question 订单 10086 为什么支付失败 print(检索到的相关文档, retrieve(question)) print(模型回答, answer_with_rag(question))RAG 的优势避免把整份文档塞进上下文节省 Token。检索过程是透明的可以审计模型用了哪些资料。文档更新时不需要重新训练模型只需要更新知识库。RAG 的坑切片chunking策略对效果影响很大。切得太碎语义断裂切得太大检索噪音增加。嵌入模型的选择会影响检索质量。需要维护向量数据库增加基础设施复杂度。4.4 方案三滑动窗口对话记忆在做聊天机器人时历史消息会无限增长。一个常见做法是只保留最近 N 轮对话更早的内容压缩成摘要。# 文件路径sliding_window.py from collections import deque class SlidingWindowMemory: def __init__(self, max_rounds5): max_rounds: 保留最近多少轮对话 self.max_rounds max_rounds self.history deque(maxlenmax_rounds * 2) def add_message(self, role, content): self.history.append({role: role, content: content}) def get_messages(self): return list(self.history) # 使用示例 memory SlidingWindowMemory(max_rounds2) # 模拟用户与模型多轮对话 memory.add_message(user, 你好我叫小明。) memory.add_message(assistant, 你好小明很高兴认识你) memory.add_message(user, 帮我推荐一款适合编程的笔记本。) memory.add_message(assistant, 可以考虑 MacBook Pro 或者 ThinkPad X1 Carbon。) memory.add_message(user, 我还是学生预算有限。) # 查看保留的上下文 print(当前记忆内容) for msg in memory.get_messages(): print(f{msg[role]}: {msg[content]})运行结果由于只保留最近 2 轮早期“我叫小明”这段信息已经从内存中移除。实际工程中可以把被移除的早期内容定期做摘要再额外填充进 System Prompt。为什么要用 dequePython 的 deque 在maxlen固定时会自动丢弃最旧元素非常适合这种滑动窗口场景而且性能比列表切片高。4.5 方案四结构化输出与思维链引导有时候模型“脑损”不是因为上下文不够而是因为 Prompt 写得太开放。给模型明确的输出框架可以有效提升复杂任务的表现。# 文件路径structured_output.py from openai import OpenAI client OpenAI() # 一个复杂的业务分析场景 raw_text 项目 A用户活跃度下降 20%付费转化率下降 5%主要原因是新用户引导流程变长。 项目 B服务器响应时间从 200ms 上升到 500ms数据库慢查询增多。 项目 C客服工单量增加 30%其中 60% 与支付问题相关。 prompt f你是一名资深业务分析师。请阅读下面的原始材料并严格按照以下格式输出分析结果。 原始材料 {raw_text} 输出格式 1. 问题清单列出每个项目的主要问题每行一个使用编号。 2. 根因分析针对每个问题给出可能的原因。 3. 行动建议给出每条建议并注明优先级高/中/低。 4. 风险提示列出需要重点关注的风险。 要求输出内容简洁不要添加无关信息。 response client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0.2, ) print(response.choices[0].message.content)为什么要加结构约束模型在生成时是逐 Token 预测的。如果你不限定格式模型很容易跑偏或者在长文本中遗漏关键点。结构化 Prompt 相当于告诉模型“请把注意力放在这几个方面”无形中降低了注意力分散的概率。5. 常见问题与排查思路在实际开发中处理长文本相关的问题非常多。下面整理了一份高频问题排查表。问题现象常见原因解决思路API 报错This models maximum context length is xxx tokens输入 Prompt 超过上下文窗口压缩 Prompt、截断历史消息、改用更长上下文的模型模型遗忘早期对话内容上下文窗口被后续内容挤占使用滑动窗口 摘要策略关键信息写入 System Prompt长文档中关键信息检索不到切片方式不合理或嵌入模型不匹配调整 chunk size 和 overlap尝试换 embedding 模型生成内容冗长但没重点Prompt 缺乏约束使用结构化 Prompt限定输出格式和长度多文档对比时结论错误多个文档关键信息彼此矛盾模型无法判断优先级限定资料范围增加引用来源必要时人工校验对话越长响应越慢输入 Token 数量增大推理时间变长控制历史轮次、对历史做摘要、精简 System Prompt摘要丢失关键数字摘要模型没有收到“必须保留关键信息”的指令在摘要 Prompt 里显式声明需要保留的字段类型下面展开讲几个高频问题的排查步骤。5.1 如何确认 Token 超限在调用 API 之前先本机估算 Token 数量import tiktoken def count_tokens_from_messages(messages, modelgpt-4o-mini): 估算消息列表的 Token 数量。 注意不同模型的计费规则和上下文窗口不同这里只是估算。 try: enc tiktoken.encoding_for_model(model) except KeyError: enc tiktoken.get_encoding(cl100k_base) total 0 for msg in messages: total len(enc.encode(msg.get(content, ))) total 4 # 每条消息额外开销 total 2 # 回复预留 return total # 示例消息 test_messages [ {role: system, content: 你是一个助手。}, {role: user, content: 请帮我总结这篇文章。 这是一段很长的文章。 * 200} ] print(估算 Token, count_tokens_from_messages(test_messages))如果估算值已经接近模型上限就别硬塞了。5.2 如何排查“模型忘了”的问题如果模型在长对话中表现不佳建议按以下顺序排查检查实际发送给模型的 messages 列表确认历史消息是否还在。检查 System Prompt 是否被后续用户消息覆盖。检查是否有代码逻辑错误地把早期消息裁掉。尝试把关键信息同时写入 System Prompt 和最近的 User 消息。如果仍然遗忘说明上下文太长了需要启用摘要机制。5.3 如何避免“中间遗忘”针对“Lost in the Middle”现象最实用的办法是把任务指令放在 Prompt 最前面。把关键背景信息放在 Prompt 靠前和靠后的位置。中间位置放辅助性内容。如果某条信息非常重要可以在多条消息中重复出现。6. 最佳实践与工程建议6.1 关于上下文窗口的容量规划在设计大模型应用时建议提前做一个 Prompt 容量预算表。组成部分预估 TokenSystem Prompt固定指令200 - 500历史对话滑动窗口保留1000 - 2000检索到的文档片段1000 - 3000用户当前输入100 - 1000模型输出预留500 - 1000合计2800 - 7500先算好账再选模型。不要一股脑把全部知识库都塞进上下文。6.2 摘要与原始文本的取舍摘要适合处理“历史背景”类信息比如旧对话、历史文档、日志聚合。但摘要不适合处理“需要精确回答”的信息比如法律条款、财务数据、代码细节。建议对重要原文做“保留原文 摘要索引”的双层结构。需要精确信息时回到原文片段中去检索而不是从摘要里猜。6.3 RAG 与微调的选择很多人困惑长文本效果差是应该做 RAG还是应该微调模型简单来说RAG 适合“知识动态更新、需要可解释性”的场景。你可以控制模型参考哪份文档也便于在文档过期时更新。微调适合“固定风格的输出、特定格式要求、私有术语理解”的场景。但微调并不能有效扩展模型的上下文窗口“记忆”外部知识的效果也不如 RAG 稳定。大多数业务场景RAG 的投入产出比更高。6.4 日志、监控与可观测性大模型应用上线后一定要做完善的日志和监控。建议至少记录每次请求的 Token 消耗。输入消息长度。上下文是否被截断。模型响应时间。Prompt 版本。当用户反馈“模型变笨了”时第一件事就是回放当时的 Prompt看看是不是上下文被塞爆了。6.5 安全与合规提醒在把用户数据送入大模型 API 之前务必确认数据脱敏手机号、身份证号、银行卡号等敏感信息要先脱敏。权限控制不是所有用户都能查询所有文档。合规审查企业内部数据出境或调用第三方 API 前要经过安全和法务审批。最小化原则只把处理任务真正需要的文本片段发送给模型而不是整库导出。这一步不能省尤其是涉及生产环境和真实用户数据的场景。7. 总结与学习路线本文从 Cohere CEO 的“首席大脑损伤官”梗切入聊的是大模型在处理长文本时的真实短板。我们动手验证了上下文窗口的限制也实现了四种缓解方案Prompt 压缩、RAG 检索增强、滑动窗口记忆、结构化 Prompt。需要记住的核心结论有三条上下文窗口是有限资源设计应用时必须做容量规划。关键信息要放在 Prompt 的开头和结尾中间位置容易被忽略。面对长文档RAG 通常比硬塞进上下文更可靠。如果你正在学习大模型应用开发接下来可以按这个顺序深入熟悉 Tokenizer 原理理解不同模型对文本的切分差异。学习向量数据库的选型与调优掌握文本切块的各种策略。研究更高级的 Agent 记忆机制比如分层记忆、反思式记忆。理解模型评测方法学会用客观指标评估“上下文增强”方案是否真的有效。大模型的“脑损”短期内很难根治但通过合理的工程手段我们完全可以把它对业务的影响降到最低。建议你动手跑一下文中的代码把长文本文档换成自己的业务数据感受一下不同方案的实际效果再决定哪种方案最适合你的项目。