最近我在做一组对比测试,规则很简单:固定模型、只调框架。同一个编码智能体任务,同一个底层模型,不同框架去调度,结果却出现了一个我不能忽略的现象——一旦上下文接近窗口上限,成绩波动变得非常明显。有的框架还能靠中间某段文件完成修改,有的框架像是突然失忆,反复在无关文件里打转。问题不在模型,而在框架如何处理上下文。
1. 固定模型却出现成绩波动:问题出在“上下文”而不是“模型”
1.1 别把上下文当成一个装文字的桶
这里说的上下文,不是编程语言里的“执行上下文”,而是提交给大模型的那段连续输入。很多人把它理解为一只桶:能装多少 token,就算有多少上下文。但真实使用中,它不是一只桶,而是一块会被反复擦拭的白板。模型每次预测下一个 token,只能基于当前白板上仍然存在的文字。代码片段、工具输出、历史对话、系统指令,谁被留下,谁被擦掉,会直接影响模型能“看到”什么。
在上下文充足的时候,擦不擦都无所谓,因为关键信息还都在。可一旦接近上限,框架就必须做删减、压缩、摘要或检索。这个决策做得好不好,会直接体现在任务成绩上。所以你会看到同一个模型、同一个任务,框架 A 能改对,框架 B 彻底跑偏。
1.2 模型还是那个模型,为什么表现会变
模型权重没有变,但它的输出是条件概率,条件变了,输出就会变。假设有一个 bug 藏在某个文件的中段,框架 A 选择把整个文件放进上下文,框架 B 选择了保留最近几轮对话而把文件摘要成三行。对模型来说,这是两个完全不同的任务:一个是“读文件找 bug”,另一个是“根据摘要猜 bug”。成绩波动自然发生。
这个观察也解释了为什么“模型能力”和“任务成绩”不能画等号。模型能力是一个静态属性,任务成绩是模型、上下文、框架策略、任务复杂度共同作用的结果。固定模型、调整框架,就是把其他变量按住,单看上下文管理策略带来的变化。你会发现,在短任务上框架之间的差距很小,越接近上下文上限,差距越明显。
2. 框架管理上下文的三种典型策略
2.1 全量保留:最忠实也最容易先到红线
最朴素的框架会按顺序保留所有消息:用户需求、工具调用、命令输出、错误日志,全部追加进上下文。好处是信息丢失少,模型能看到完整过程。代价是 token 涨得很快,尤其编码任务中,一次 grep、一次编译、一次测试输出就可能吃进几千 token。等到接近上限,框架通常会选择从最早消息开始裁剪,或裁剪最长的工具输出。于是全量策略变成了“前期全量,后期随机失忆”。
2.2 压缩摘要:省位置但可能丢关键细节
更复杂一点的框架会在上下文接近阈值时触发压缩,用一段摘要替代旧的对话。摘要可以由模型生成,也可以用规则提取。这种方式能明显延长有效窗口,但也有代价:摘要无法完整保留某个函数的三处细节修改。如果后续修改依赖这些细节,模型就只能根据摘要“猜”。压缩策略看起来省 token,实际上是把风险转移给了摘要质量。
2.3 检索注入:按需取用,依赖检索质量
还有一类框架会先扫描仓库,建立文件索引或向量索引,然后根据当前任务把相关片段检索出来放进上下文。优点是可以支撑很大的代码库,缺点是检索质量和任务结果是强耦合的。如果检索漏掉了关键函数,模型再强也无从下手。上下文越紧张,检索的取舍越关键,因为漏掉的信息不会有第二次进入上下文的机会。
| 策略 | 保留方式 | 主要优势 | 主要风险 | 适用场景 |
|---|---|---|---|---|
| 全量保留 | 原样追加 | 信息完整 | token 增长快,后期截断随机 | 短任务、小仓库、调试阶段 |
| 压缩摘要 | 旧消息变摘要 | 省 token,延长窗口 | 关键细节丢失 | 长会话、重复探索类任务 |
| 检索注入 | 按需召回片段 | 支撑大代码库 | 召回缺失直接失败 | 大仓库、有明确文件入口的任务 |
框架的具体实现可能混合使用三种策略:先全量,再压缩,最后靠检索。问题在于,多数框架并不会告诉你它到底在哪一步、丢掉了什么。
3. 怎样做一次“固定模型、调整框架”的可信对比
3.1 先把变量锁死
做这个对比,最难的不是写 prompt,而是确保变量真的可控。需要固定下面这些项:
- 模型唯一标识和版本;
- temperature、top_p、max_tokens 等采样参数;
- 任务描述、任务顺序、仓库版本;
- 重试次数、超时时间、工具调用权限;
- 框架版本和底层依赖;
- 同样的代理、网关和限流环境。
你会发现“同一个模型”在 API 上可能对应多个版本,有些框架默认加 system prompt,有些框架会使用不同的工具描述格式。这些都不是模型差异,而是框架差异。对比的目的本来就是要看框架差异,但采样参数不锁死,结论就会被噪声淹没。
3.2 至少跑三组:短上下文、长任务、接近上限
建议每组任务至少跑 3 到 5 次,取分布而不是取单次最好成绩。测试组可以这样设计:
- 短上下文:单个文件的小 bug 修复,上下文远未到达上限;
- 长任务:跨文件的新功能开发,需要多轮工具调用;
- 接近上限:在仓库里加入大量无关文档,或者在任务开始前注入一段较长背景材料,压迫框架必须做筛选或压缩。
最后那一组最容易被忽略,但它恰恰是“固定模型、调整框架”最有信息量的场景。很多框架在短上下文里看不出区别,一旦被压到 80% 以上的窗口占用率,策略差异就全部暴露出来。
注意:上下文紧张测试不是把窗口塞满就行,而是要让框架有理由裁掉那些“看起来不关键、实际很关键”的文件。
3.3 记录的不只是能不能跑通,还有上下文账单
结果不能只记“改对了没有”。建议同时记录:
- 第一次尝试成功率;
- 最终成功率(允许重试);
- 总输入 token、输出 token;
- 是否发生截断、压缩、检索事件;
- 关键文件是否出现在最终 prompt 中;
- 完成时间。
下面是评测骨架的伪代码,只表达结构,不绑定具体框架:
def run_task(model_id, agent_builder, task): agent = agent_builder(model_id=model_id) for step in agent.run(task.prompt): step.log_prompt() # 输出本轮实际发送给模型的 prompt step.log_truncation() # 是否有消息被裁剪 step.log_compaction() # 是否触发摘要压缩 step.log_retrieval() # 检索召回了哪些文件 return { "pass_at_1": task.check(agent.first_output), "pass": task.check(agent.output), "input_tokens": agent.total_input_tokens, "truncation_events": agent.truncation_events, "key_files_in_context": task.required_files & agent.files_in_context, }把这个骨架用在所有框架上,结果才有可比性。实际运行时,很多框架会吞掉内部日志,你需要通过 API 网关或代理把请求包体抓出来。这一步不能省,因为“框架说它做了压缩”和“我们亲眼看到压缩发生”是两回事。
4. 成绩波动背后的三个细节:截断、遗忘、位置
4.1 截断不是均匀裁剪,而是用脚投票
当上下文超过窗口,框架必须决定先扔谁。常见策略是扔最早的消息、扔最长的工具输出、扔中间轮次的思考过程。对编码智能体来说,“最早的消息”里可能包含用户对需求的原始描述,也可能包含仓库结构;如果这些被扔掉,后续模型就会在缺少约束的情况下行动。扔最长的工具输出,则可能让模型失去编译错误的完整信息,只剩一个干巴巴的“失败”。
所以说,截断策略不是技术细节,它本质上是框架在替我们投票:它认为哪些信息不重要。问题是很多框架的默认投票标准是“先来后到”或“长度优先”,而不是“对当前任务重要程度”。
4.2 丢失中间信息导致“遗忘”
用户在智能体跑了几分钟之后,最常见的一句评价是:“它好像忘了最开始的需求。” 这不一定是模型记忆差,而是因为中间过程把早期需求挤出了上下文。比如任务一开始要求“不要动数据库表结构”,中间经历了很多轮调试,框架为了节省空间,把最初约束压缩成“保持兼容”——这个语义在后续代码生成中很容易被忽略。
“遗忘”不是心理现象,而是信息物理上已经不在上下文里。这也是为什么编码智能体容易在长任务后段出现“自信但错误”的修改:它没看到完整约束,但当前上下文看起来是连贯的。
4.3 关键信息的位置决定了成功率
在长上下文评测里,有一个现象反复出现:大模型对上下文开头和结尾的信息更敏感,中间部分更容易被忽略。框架拼装 prompt 的顺序,因此不只是排版问题,而是性能问题。如果关键文件被放在超长工具输出的中间,它可能处于“模型注意力最不友好”的位置;如果框架把当前修改目标固定在开头、把最近错误放在结尾,成功率通常更高。
所以在对比时,我建议你额外记录一个指标:任务中必须出现的文件,分别出现在 prompt 的第几个 token。这个位置信息,往往比总长度更能解释为什么某个框架胜出。
5. 比上下文长度更重要:有效上下文利用率
5.1 怎么估算有效上下文利用率
既然上下文紧张时成绩波动,核心问题就不只是“窗口有多大”,而是“窗口里有多少信息是任务真正需要的”。可以粗略用两个指标衡量:
- 关键信息覆盖率:任务要求的所有文件、函数、约束,有多少最终出现在 prompt 里;
- 有效上下文利用率:任务相关的 token 数除以总 prompt token 数。
后者比较难自动算,因为“相关”没有统一标准。一个实用的做法是,在评测任务里预先把关键信息打上标记,比如限定“必须查看 config.py”,然后检查结果 prompt 中是否出现该文件。你不需要算得很精细,只要能区分出 40% 和 90% 的差距就够了。
5.2 框架应该把哪些信息“钉”在上下文里
当上下文紧张,框架要保证以下内容不被挤掉:
- 用户的原始目标和硬性约束;
- 当前正在操作的文件路径和最新内容;
- 已产生的代码 diff;
- 最近一次失败的报错信息;
- 验证命令和执行结果;
- 不允许触碰的模块或约定。
我把这份清单叫“最小有效上下文”。它不应该由模型临时猜,而应该由框架显式管理。哪个框架能稳定地保住这些内容,它在上下文紧张时的成绩就会更稳定。
5.3 参数与压缩策略的取舍
很多框架允许配置上下文窗口上限、触发压缩的阈值、摘要模型、检索 top-k。经验上,先把触发阈值设得保守一点,比如窗口的 70% 到 80%,不要等到 98% 才压缩。压缩发生得太晚,往往意味着关键信息已经被截断;触发得太早,摘要又可能显著降低信息保真度。
不要等到上下文 98% 才触发压缩,关键信息通常在最后 10% 被牺牲掉。
滑窗式上下文也好,滚动摘要也好,本质上都是用一个更小的信息块去替代完整历史。对于编码任务,我不建议单独依赖某一种策略。更好的组合是:核心文件永远保留,历史信息用摘要承载,仓库级信息靠检索按需注入,三者分开管理。
6. 给正在选框架或调框架的人:五条落地建议
6.1 先复现,再评估
不要因为一个框架在演示任务里表现得惊艳就立刻迁移。先用五到十个覆盖你真实业务的编码任务,做一次小规模复现。演示任务往往上下文很短,所有框架都能过,真正的差异在长上下文和接近上限时才显现。
6.2 在“上下文紧张”场景里选框架
如果目标是选型,建议把“上下文压力测试”写成固定测试用例。具体做法:给每个任务注入 4000 到 8000 字的无关背景材料,或者复制一份较大的仓库文档,让 token 占用率达到 70% 以上。然后观察框架是否能保住真正的关键文件。这个测试比一百个短任务更能说明问题。
6.3 给框架加上可见性
框架不应该是个黑盒。它至少要暴露三个信息:当前上下文 token 数、哪些消息被截断或压缩、检索召回了哪些文件。如果框架不提供,就在外部加日志和代理。没有可见性,你就无法解释为什么这次成功、上次失败,也无法排除上下文策略的随机性。
6.4 当成绩下滑时,按这个顺序排查
一旦智能体在上下文紧张时表现变差,我建议按固定顺序排查,避免一开始就调 prompt 或换模型:
- 看现象:是答非所问、反复无效修改,还是直接断在某个工具调用上;
- 看实际上下文占比:任务开始时的 token 数、是否触发压缩或截断;
- 看截断与压缩位置:被裁掉的到底是历史轮次,还是关键文件;
- 看检索召回:如果用了检索,关键函数或文件是否真的进入了本轮上下文;
- 看模型单独能力:把关键片段摘出来单独问同一个模型,确认它原本能答对;
- 最后再看参数:temperature、重试策略、工具描述长度、max_tokens 是否限制了输出。
这个顺序的好处是,从最外层的现象逐步逼近根因,避免拿模型能力问题去掩盖框架问题,也避免拿框架问题去怪模型。
6.5 把“上下文策略”写进框架选型标准
选框架时,除了看功能列表和支持的模型数量,还要追问几个问题:它如何处理上下文超限?截断时保留哪部分?是否支持把指定文件固定在 prompt 里?是否开放压缩和检索的触发条件?日志里能不能看到每一次 prompt?这些问题的答案,比“谁宣称的上下文更长”更值得放进决策表。
这套对比方法更适合研发和选型阶段,不适合直接拿来做线上性能基准。线上环境还有并发、缓存、限流、成本控制和权限隔离,这些都会改变结果。你要做的是先在线下把上下文管理策略选明白,再谈工程化部署。
最后说一句我自己的判断:固定模型、调整框架,成绩出现波动,这件事本身不奇怪。真正值得长期关注的是,框架有没有把上下文当成一等公民来管理,而不是把它当成一个越用越满的缓冲池。如果你正准备从“能用某个编码智能体”走向“在团队里稳定使用编码智能体”,第一步不是换个更大的窗口,而是把框架如何处理上下文这件事看清楚。