
前阵子接了一个很折磨人的活儿要把一批平均几百页的行业报告喂给模型做结构化提取。我一开始的方案很朴素——把文档大段大段塞进上下文靠模型自身去归纳。结果不用我说你也猜得到输出质量一路下滑到后半程基本就是复读机加胡言乱语。换到 Qwen3.5-9B 之后情况好了不少但随之而来的是一堆以前没怎么认真对待的问题上下文到底怎么算的1M 窗口该怎么激活为什么有时候系统提示我“请启用 1M 上下文后重试”以及那种“上下文已使用满”的报错到底是谁造成的。这一套折腾下来我对“上下文”这个词的理解彻底改写了。这篇文章就只聊一件事Qwen3.5-9B 这类大模型在真实项目里上下文到底该怎么理解、怎么配置、怎么用才不会翻车。适合正在做长文档分析、Agent 应用或者正在研究提示词工程和上下文工程区别的开发者。我尽量用自己踩过的坑来串起整篇内容。1. “上下文”这个词在大模型领域究竟指什么聊 Qwen3.5-9B 的上下文得先面对一个问题我们日常挂在嘴边的“上下文”在计算机世界里起码有四五个完全不同的含义。搜索热词里密密麻麻排着“js 执行上下文”“当前页面非 https 安全上下文”“仓库版本工作树上下文祖先链”这些词和模型上下文不能说毫无关系但也绝对不是一回事。1.1 从 js 执行上下文到模型上下文别把词义搞混了Js 的执行上下文是 JavaScript 引擎在运行函数时建立的一条作用域链它决定的是变量和 this 的指向浏览器里“非安全上下文”指的是接口能否在 http 协议下被调用仓库里的上下文祖先链则是指 git 合并时 commit 之间的先后关系。这三者都是各自领域里的精准术语但它们和“模型能看到的输入”没有任何关系。大模型领域说的上下文就是模型在做预测时能够参考的全部输入信息。这个输入既包括用户当前问的一句话也包括历史对话记录、检索回来的资料片段、系统提示词以及文档里被截取进来的正文。你可以粗暴地把它理解成模型工作台面上摊开的全部资料——桌子多大决定了你能摊多少份文件进去。我刚开始用 Qwen3.5-9B 的时候犯过一个很基础的错误把这两个概念放进了同一个思维模型里结果排查上下文问题的时候思路全部走偏。后来我给自己立了一条规矩只要谈模型上下文只指模型输入窗口内能找到的所有 token。其他域里出现的“上下文”一律无视。1.2 我理解的上下文等于窗口加记忆加注意力预算窗口大小也就是常说的 32K、128K、1M指的是模型最多能容纳的 token 数量这是硬性上限。但“窗口”代表能放得下≠“能处理好”。因为模型还有一个隐形的约束注意力机制的预算。Qwen3.5-9B 这类模型窗口越长模型要做的人物关系推断和关键信息抓取就越分散。你可以把注意力想象成人的精力——桌子上的资料越多就越容易忽略角落里的关键数字。模型会努力在整段上下文里分配注意力权重但权重总量是有限的前面塞进来的内容总是比后面的更容易拿到更高的注意力比例。这个现象也叫“上下文中间丢失”一长串无关紧要的历史对话会把真正重要的指令挤到注意力边缘最后模型输出的就是被边缘化的结果。所以我给上下文下的定义是窗口容量、历史记忆、注意力预算三者的叠加。管理上下文本质上就是把这三样都管好。后面聊“上下文工程”聊“执行上下文”聊“1M 上下文全量可用”你都带着这三层去看会清晰很多。2. 上下文工程提示词工程之后的新瓶颈这两年提示词工程已经聊烂了。以前我们觉得只要把指令写清楚模型就能按你的思路输出。但真实情况是当上下文从几千 token 涨到几十万 token提示词的写作技巧对最终效果的影响会快速衰减取而代之的是信息摆放位置、信息密度和历史对话的压缩策略。这就是上下文工程要处理的问题。2.1 为什么通用提示词模板在长上下文场景里失效先看一个很典型的失败案例。我做报告提取时系统提示词大概是“你是专业的行业分析师请根据给定的文档内容提取市场趋势、竞争格局、风险点三个维度的信息”然后直接把文档正文全部塞进去。短文档没问题但长文档一进来前面放的系统提示词很快被淹没。问题出在哪通用提示词模板假设的是模型只需要关注提示词本身其余输入都是辅助信息。可上下文的输入一长模型对“哪些内容只是资料”“哪些内容是硬性指令”的区分能力就会变弱。系统提示词虽然权重高但也架不住后面跟进来几十页或上百页的检索结果——模型会在长距离依赖上逐渐失焦指令就变成了背景噪音。解决办法不是删减提示词而是把提示词当作上下文数据流中的一环去重新设计先拆分文档再让模型做分段精读最后汇总。也就是说你要关注的不再是“提示词写得好不好”而是“每个阶段有多少信息进入模型、信息以什么顺序进入、哪些信息需要被丢弃”。2.2 提示词工程的“第四层 harness”和上下文工程的分工圈里有人把上下文工程定义为提示词工程的下一个阶段。也有人把 Agent 应用的提示词拆成“第四层 harness”意思是系统层面要有一套控制逻辑包裹住模型的行为。这两种说法互补。Harness 的概念可以简单理解成模型外面包了一个壳壳里有工具调用、历史对话裁剪、检索排序、信息脱敏这些策略。提示词工程在壳里负责文案层面的指挥上下文工程则负责内容层面的调度。打个比方提示词是交通规则上下文工程是路口信号灯。规则再清楚车流一多没有信号灯调度照样瘫痪。我自己在 Qwen3.5-9B 上的经验是凡是需要处理超长输入的场景都应该先画上下文数据流图再做提示词优化。数据流图画清楚之后你会很自然地看到哪个环节在重复注入哪个环节原始文本占了大头哪个环节的历史对话冗余度最高。这几个往往是提示词层面根本看不出来的。2.3 上下文数据流图分解先画图后写代码上下文数据流图这个叫法可能有些陌生其实就是把“哪些内容、从哪来、到哪里去、在模型里占多大空间”全部画出来。我一般会拆成六步输入阶段用户原始问题进入系统占多少 token。记忆阶段从短期记忆或长期记忆里加载的历史摘要占多少 token。检索阶段从向量库 / 文档库里捞出来的候选段落占多少 token。指令阶段提示词模板和系统约束占多少 token。组装阶段上面的内容按什么顺序拼接成最终请求。输出阶段给模型预留多少输出 token。这个图画完之后大多数上下文问题会直接暴露出来。比如我碰到过一个“上下文已使用满”的报错图一画就发现系统提示词本身就占了 4K历史对话存了 20 轮没有压缩每次检索还固定捞 10 段内容每段 1500 token。加起来早就爆炸了跟模型本身一点关系都没有。搜索热词里有一个“上下文数据流图的分解”我认为未来上下文工程的核心就是这件事。先把图刻在脑子里再去调具体参数效率会高出很多。3. 1M 上下文全量可用后我们应该怎么配置和使用Qwen3.5-9B 这个级别里最吸引人的数字就是 1M 上下文全量可用。要知道大部分开源模型还在 128K 附近打转1M 意味着理论上可以一次塞进一整本书或几十万行代码。但这里头藏了不少坑我一个个说。3.1 为什么系统会提示“请启用 1M 上下文后重试”很多人遇到这个提示第一反应是模型不支持其实不是。原因通常是当前接入模型的请求配置里上下文窗口并没有切到 1M系统检测到你传的内容长度超出了当前窗口的允许范围于是给出提示。这和我们熟悉的 chatgpt 更改上下文长度是同一个逻辑。ChatGPT 的界面里可以选 32K 上下文还是更长选小了单次对话里传大文件就会提示截断或者报错Qwen 这边也类似不同的服务端配置决定了你实际能用的上下文上限。我查过很多次资料最后总结出一个比较通用的规律当你看到“请启用 1M 上下文后重试”优先检查三件事客户端或 SDK 有没有配置 enable_long_context 之类的开关服务端部署时有没有设置对应的环境变量把上下文长度调到最大当前请求文本本身是否已经超过了你所声明的窗口长度。第三个看似废话但非常常见。有时候你以为自己传了几千字实际上文档转 JSON 之后 token 数翻了好几倍实际请求早超过 128K 了系统自然建议你启用更高的 1M 上下文。3.2 激活 1M 上下文的关键检查点我以 API 调用和本地部署两条路径分别说明。API 调用场景你需要确认的首先是模型版本是否支持长上下文其次是显式声明请求参数比如 openai 兼容协议里常见的 max_model_tokens 或者 qwen_1m_scene 这类开关不同服务的叫法不一样。我的建议是不要在代码里写死一个 1M 常量而是先做一个小的探测请求用真实文本测出当前配置下的最大上下文然后再决定后续策略。本地部署场景这里有个容易被忽略的点。9B 参数量级的模型可以完整装进显存但 1M 上下文在推理阶段会消耗相当大的显存来保存 KV Cache。KV Cache 的占用与序列长度成正比长上下文下它甚至能超过模型权重本身。所以本地要真正跑 1M你得提前检查显存余量否则配置写对了一推理照样 OOM。下面这个表格是我在本地环境里做的一个粗略估算具体数值视实现而定只用于理解量级配置项128K 上下文1M 上下文模型权重4bit 量化约 6-8 GB约 6-8 GBKV Cache 预估约 4-8 GB约 32-64 GB推理峰值显存约 16 GB约 64 GB 以上所以不要只看“模型权重能装下”就觉得本地 1M 也能随便跑。简单的经验16GB 消费级显卡跑 1M 上下文基本不现实预算不够就用截断和压缩让 128K 跑出接近长上下文的效果。3.3 9B 参数模型跑 1M 上下文的成本与收益核算参数只有 9B却要处理 1M 的上下文这里头存在一个很微妙的平衡。大参数量模型有更强的记忆和推理能力但显存开销大9B 模型显存开销小可是注意力在超长窗口上的“广度”够不够需要实测。我的结论是9B 模型跑 1M 上下文适合做“宽而浅”的任务——比如整本书的人物关系搜索、跨章节的时间线提取、大量文献的关键词过滤。不适合做“窄而深”的任务——比如要你基于全书最后三章做极其严密的逻辑推理这种任务窗口越短越集中越好。成本方面你在实际上手时可以做一次预算试算假设每次请求平均注入 500K token光是输入 token 的费用就比 32K 场景高出 15 倍以上。如果任务本身只需要局部检索那么 500K 的注入就是纯粹浪费。我自己做长文档时经常用的组合是先开 1M 把文档整体读一遍生成章节级摘要然后把摘要塞回上下文里做精细分析这样既用上了长窗口又不需要每个请求都烧 1M 的输入费用。4. 上下文“已使用满”现象、原因与完整排查链路“workbuddy 上下文已使用满了如何解决”这个问题搜索热度很高。我也被类似的报错困扰过很多次。它听起来像是个内存报警但实际上源头非常多。我整整花了几天时间才建立了一套完整的排查链路。4.1 从 workbuddy 的报错说起WorkBuddy 这类基于大模型的工具在会话中会持续累积对话历史。当历史累积到窗口上限系统就会提示“上下文已使用满”。这类提示比“请启用 1M 上下文后重试”更真实因为它反映了窗口真的满了而不是配置没开对。但这里有个反直觉的地方很多用户会话明明没聊几句却早早触发了上下文已满。真正的原因是系统里塞了太多看不见的东西——工具返回的 JSON、上一次任务附带的长文档切片、几轮之前的系统警告、调试日志。这些都会占用窗口但用户感知不到它们的存在。排查的第一步就是去日志里看请求体的真实 token 数而不是猜。我遇到过最夸张的一次用户只问了“今天天气怎么样”但请求体里塞了一个完整的产品文档因为构建会话时把“可能用到的资料”一股脑全加了进去。4.2 第一层会话长度超限如果请求体真实 token 已经超过窗口上限那问题很单纯要么开启更长窗口要么压缩会话。对 Qwen3.5-9B 来说比较稳妥的做法是给会话设置一个软阈值比如窗口的 70%。超过阈值就触发压缩而不是等到 100% 才处理。我目前使用的软阈值逻辑是这样的40% 以下不做任何处理完整保留历史40%-70%开始对中间轮次做摘要压缩保留最近几轮原文70% 以上强制触发一次“历史摘要重写”释放空间。这个策略类似人的记忆机制细节保不住但骨架留下。这也正好回应了搜索里“dst 记住对话上下文 人的短期记忆怎么实现”这个问题——大模型的短期记忆就得靠这种主动遗忘来实现不可能永远原文保留代价就是窗口很快被吃光。4.3 第二层隐式重复注入上下文已满的第二个常见原因是同一段内容被反复注入。比如每次检索都返回文档前几章的内容因为它们向量相似度最高比如 Agent 在每轮循环里都附带了完整的系统提示词和工具描述再比如你用了 RAG 但没做历史去重同一段产品说明出现在三条不同的返回结果里。这些重复内容就像是往已经装满的箱子继续塞相同的旧报纸一点价值没有却把新信息挤出去了。我的检测方法很简单在请求日志里对所有文本块做一次局部相似度对比。相似度超过 0.85 的块保留最新的一份其余标记为冗余。这个操作在实际项目里经常能砍掉 30%-50% 的 token 占用。4.4 第三层检索段的长度失控这是第四类常见问题你以为自己在做 RAG检索系统返回了 10 个“最相关”段落但每个段落 2000 token十个就是 20000。而这十个段落里可能有一半只是在主题上沾边核心答案只藏在一个段落里。检索长度失控会瞬间吃掉窗口却不一定带来信息增益。拆这条路我有两个指标命中率检索结果中有多少内容真正被模型用在回答里边际收益再增加一个检索段落输出质量的提升是否明显。如果边际收益趋近于零就把 top-k 从 10 降到 5甚至 3。很多时候少即是多。4.5 修复实战一份带 Token 账本的对话策略最后分享一份我现在固定使用的对话策略基本能解决大部分“上下文已使用满”的问题。核心思路是给每一次请求建一个 token 账本系统提示词固定 1200 token精简后几乎不再变动用户指令控制在 500 token 以内历史对话按轮次保存摘要每轮摘要不超过 200 token检索段落每段限制 800 token最多取 5 段长期背景只保留最近 2 轮的完整原文输出预留预留 1000 token 给生成结果。这个账本在 32K 窗口下非常稳定即使升级到 128K 或 1M我也不会轻易放开。因为窗口越大你越需要纪律——不加约束的长上下文只是让模型“内存更大”并不会让它更聪明。5. “短期记忆”实现像人一样记住该记的东西热词里有一句很具体的话“dst 记住对话上下文 人的短期记忆怎么实现”。这牵扯到对话状态跟踪DSTDialogue State Tracking也牵扯到模型的记忆机制。我做过的所有上下文管理方案本质上都是在用工程手段模仿人的短期记忆。5.1 模型的短期记忆与人的短期记忆的映射人的短期记忆有几个特点容量有限、快速遗忘、以核心语义为主。你回忆五分钟前的对话记住的往往是“他要去深圳出差”这个语义而不是逐字逐句的原话。大模型如果持续保留全部历史原文就像一个人把所有对话都录了音却从不整理等到需要回忆的时候反而找不到重点。模型侧实现短期记忆最朴素的做法是“每 N 轮做一次摘要”。可以把摘要拆成三层轮次摘要单轮对话的最小化记录包含用户核心意图模型输出结论会话摘要最近多次轮次摘要再压缩保留整体主线长期档案跨会话的核心事实比如用户偏好、项目背景。这三层对应到召回机制里就是“当前轮优先全文、前若干轮用摘要、跨会话用档案”。我在 Qwen3.5-9B 上跑过的多轮 Agent 任务里这套分层的效果比一次性把所有历史塞进去要好很多尤其是连续操作型任务比如分多步完成一次数据清洗模型能准确记得过程又不会被无关细节干扰。5.2 一种 MapReduce 风格的双层记忆方案我叫它 MapReduce 双层记忆。原理并不复杂先逐段映射压缩再归并汇总。实现上分两个阶段Map 阶段把新产生的对话记录按 1000 token 切成小段对每一段用模型生成 50 token 的摘要。这个阶段可以并行做成本低。Reduce 阶段把整批摘要再次输入模型合并成一份不含冗余的会话记忆存成固定结构。最终这份记忆会被注入系统提示词的“历史记忆”区域。这套方案的好处是显著控制了窗口占用。假设 20 轮对话原始共 20000 token经过两层压缩后可能只剩 1500 token。保留的信息量足够支撑后续任务成本却降低了 90% 以上。有一个值得注意的细节Reduce 阶段的摘要质量比 Map 阶段是否完整更重要。因为一旦压缩成摘要细节就永久丢失了。所以在 Map 阶段我会刻意要求模型保留数字、日期、姓名、决策理由这几个要素用格式约束的方式防止关键信息被“压没”。5.3 一段可复用的伪代码示例这里给出一段可以改造后直接用在自己业务里的伪代码。它描述的是如何在对话循环里实现“压缩型短期记忆”。不是某个特定框架的 API所以只需要理解逻辑即可。def manage_context(history, new_turn, max_budget8192): # 每次对话新增轮次 history.append(dict(new_turn)) # 1. 如果当前累计 token 未超预算原样保留 if estimate_tokens(history) max_budget: return history # 2. 超预算时把前面的历史批量压缩成摘要 recent history[-4:] # 保留最近 4 轮原文 older history[:-4] # 更早的进入压缩 # 逐段压缩Map 阶段 summaries [] for chunk in split_by_tokens(older, 1000): summary summarize(chunk) # 调用模型生成摘要 summaries.append(summary) # 合并摘要Reduce 阶段 merged_memory merge_summaries(summaries) # 重新拼接上下文 return [{type: memory, content: merged_memory}] recent这段代码不长但它解决了上下文管理中最核心的问题谁该丢、谁该留。一旦跑通了这套逻辑你再回头看“workbuddy 上下文已使用满”这类问题会发现那些工具其实本质上也是在内部帮你做类似的套娃压缩只是控制权不在你手里。6. 我在 Qwen3.5-9B 长上下文场景里踩过的坑这一篇如果只讲理论和编排而没有实际教训价值会少一大半。接下来分享四个我真实踩过、并且花了比较多时间才绕出来的坑希望对正在做同类项目的朋友有点启发。6.1 坑一盲目调大 max_tokens结果模型提前“失忆”有一段时间我把输出 token 上限调得很大希望模型一次性生成完整长报告比如 8000 token 输出。但模型输出的质量随长度增加而迅速下降到后半程甚至开始重复前面段落。原因在于输出 token 和输入 token 共用一个窗口。你把输出预留调大等于压缩了输入能使用的空间。输入空间一旦压缩模型能看到的历史和资料变少自然就容易“失忆”。正确做法是限制单次输出长度把长报告拆成多段生成。每段控制在 1500 token 左右生成完一段后把已生成的部分作为上下文再继续下一段。这样既保证连贯性又不挤压输入空间。6.2 坑二把 1M 上下文当成外挂内存用拿到 1M 上下文以后我一度陷入一种错觉什么内容都可以在同一次请求里塞进去。结果就是请求体越来越大单次调用延迟从 3 秒涨到 20 秒费用也水涨船高而输出质量并没有因此变得更好。后来我把 1M 的定位调整为“一次性的深度阅读容器”而不是“常驻内存”。适合 1M 的场景是一次性读完整本书、大批量代码仓库、超长会议记录不适合的场景是高频多轮对话、实时交互应用这类场景更适合 32K 到 128K 之间。如今我做架构选型时会先问一句这个任务真的需要同时看到 1M 内容吗还是只是因为懒得分批处理。6.3 坑三默认文本任务的经验直接套用到视觉内容Qwen3.5-9B 在某些版本里具备视觉理解能力支持图文混合输入。搜索热词里有一条“视觉内容上下文模型”这恰好是我踩过的另一个坑的出口。我以为图片只要转成 base64 给模型它就会像理解文字一样理解图片。但图片进入上下文后占用的 token 形态和文本完全不同且模型对图片的注意力分配也有不同的策略。我最初做多页 PDF 转图再让模型分析时直接把 20 张图全塞进上下文结果模型只记得前两三页的内容。后来参考视觉内容上下文模型的做法先调用专门的视觉描述接口把每页图转成结构化文字摘要再让文本模型做全局推理。这样图片上下文被转换成了高质量文本上下文任务成功率直线上涨。如果你也要做图文混合的长文档分析建议优先考虑“视觉模型转文字描述文本模型做综合分析”的串联方案。6.4 坑四忽略了上下文在“组装阶段”的顺序敏感度最后一个坑比较隐蔽同样的内容不同排列顺序模型表现截然不同。我之前一直把检索结果按“相似度从高到低”排列再注入上下文。但有段时间效果很不稳定。后来我尝试把“最重要的长文摘要”放最前面“系统提示词”次之“检索原文”放最后面效果立刻稳定了很多。这和“上下文中间丢失”现象高度相关——模型对开头和结尾的内容更敏感中间部分容易失焦。我现在的组装顺序是固定的第一段任务目标与全局摘要相当于文章导语第二段当前会话的核心记忆抽取相当于文章背景第三段重要的检索结果按重要程度降序排列第四段原始候选资料第五段明确的输出格式约束。顺序调整前后我的长文档抽取任务准确率大概能差出 15% 到 20%这个差距非常可观。最后再分享一个小技巧我给所有做长上下文应用的朋友一个小建议不要用“现在 token 多少”来判断上下文压力要养成记录变化趋势的习惯。每跑一轮请求就把当时的窗口占用、压缩触发次数、摘要规模记录下来形成一张简单的趋势表。很多时候上下文问题不是突然爆发的而是慢慢累积到临界点才给你一次猝不及防的报错。有了这张表你就能在那个临界点到来之前提前调整策略。我自己现在维护的 Qwen3.5-9B 长文档项目已经稳定跑了大几个月没有再遇到“上下文已使用满”或者“请启用 1M 上下文后重试”这类卡死问题。核心就一句话把上下文当成一种需要持续经营的系统资源而不是一个可以无限扩容的黑盒。理解了这一点大部分和上下文有关的疑难杂症其实都不难治。