ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

长任务稳定运行的秘诀:OpenCollab 上下文整形(Context Shaping)与 Token 预算分配原理解析

长任务稳定运行的秘诀:OpenCollab 上下文整形(Context Shaping)与 Token 预算分配原理解析

【免费下载链接】OpenCollab

A research-friendly, high-performance multi-agent collaboration coding framework.

项目地址:https://gitcode.com/gh_mirrors/op/OpenCollab
点击查看免费下载

在长任务中,AI 编码 Agent 经常因为上下文窗口被塞满而"失忆"甚至崩溃。OpenCollab 是一个研究友好的高性能多智能体协作编码框架,它通过**上下文整形(Context Shaping)**和精细的Token 预算分配机制,在读取时动态构建有界的上下文视图,让 Agent 能在数千步的长任务中稳定运行,而不丢失任何原始对话记录。本文带你完整解析这套机制的设计原理与实现细节。

为什么长任务容易"断气"? 🤯

想象一个持续运行数小时的编码 Agent:它不断调用file_read、bash、grep等工具,每一步的工具输出都追加进对话历史。到第几百步时,上下文窗口(context window)被工具输出彻底占满,模型要么被提供商拒绝请求(context overflow),要么在"记忆"里塞满了早已没用的旧文件内容。

大多数框架的应对方式是粗暴截断——但这会丢失关键决策。OpenCollab 的做法完全不同:整形只发生在"发送给模型的那一刻",持久化的完整转录(transcript)永远不动。这意味着:

  • 压缩、删除、摘要都只是"读取时的投影",随时可以无损恢复(resume);
  • 整形是分级渐进的,压力不大就什么都不做;
  • 每一层都只处理上一层的输出,一旦压力解除,后续层自动跳过。

整个运行时的分层架构如图所示:

上图展示了 OpenCollab 的完整运行时:从任务/模型/拓扑/Token 与上下文策略的规范层,到 Single、Team、Workflow 三种协作模式,再到共享的会话循环(Session loop)。右侧的 Evaluation 列则体现了它对每一次运行在角色、行为、结果、成本上的全程审计能力——而上下文整形正是其中 "tokens / context" 策略的核心执行者。

上下文整形四段流水线:ShaperPipeline 🔧

OpenCollab 的所有整形逻辑集中在 opencollab/application/shaping/ 包中,由一个左到右的整形器流水线(ShaperPipeline,定义于 pipeline.py)串联。默认流水线在 container.py 的_build_default_shaper中组装,共 4 层:

层整形器触发条件动作损失程度
1PerToolResultBudgetShaper无条件单条工具结果超长则截断 + 重读指引极低
2ToolOutputClearShaper压力触发清空旧的可重建工具输出内容低
3OldHistorySnipShaper压力触发整组删除旧的工具交换轮次中
4AutoCompactShaper压力触发用模型摘要替换整段历史高(最后手段)

第 1 层:单条工具结果预算(Per-tool Result Budget)

实现于 tool_budget.py。任何一条工具结果只要超过预算(默认16,000 字符),就会被截断为"头部内容 + 重读指针",例如附上一条[truncated N chars — re-read a narrower range]的提示,告诉模型如何用更窄的offset/limit范围重新读取。这个设计很妙:模型永远知道自己"看过但没看完",可以按需精确回读,而不是盲目地整块丢失。

第 2 层:压力触发清空旧工具输出(Layer A0)

实现在 reactive.py 的ToolOutputClearShaper。只有当上下文估算值越过触发线时才工作:它保留最近 5 条可压缩工具结果原样不动,把更旧的bash、file_read、grep、git_diff等可重建的只读输出内容替换为一个短占位符[Old tool result content cleared]。注意它只清空"内容",调用与回答的骨架(以及助手轮次的推理)完整保留——零孤儿风险,零模型调用。编辑/写入类工具的返回则从不被触碰。

第 3 层:整组删除旧轮次(Layer A)

OldHistorySnipShaper是最便宜的"真删除":它按"最旧优先"整组移除旧的工具交换组(一个助手的tool_calls轮次连同其所有工具回答),直到估算值降回目标线。删除有严格的安全约束:

  • 系统提示组(第 0 组)和最近 4 组永远受保护,只清理"中间地带";
  • 删除以"组"为单位,绝不会把tool_call_id弄成孤儿(提供商要求每次调用都必须有对应回答);
  • 被删内容仍完整保存在state.messages和持久化转录中,恢复会话时可完整重建。

第 4 层:模型摘要压缩(Layer B,最后手段)

AutoCompactShaper是重量级手段,默认关闭(不注入 summarizer 时恒等不变)。当更便宜的层做完压力仍然超限时,它把整段可压缩区间交给摘要器,替换成一条带可见标记的消息([Context auto-compacted — summary of N earlier messages]),而不是伪装成原始历史。摘要提示词(compaction_prompt.py)刻意保留意图、决策、错误、用户指令、待办工作和下一步动作,并带有 LRU 摘要缓存,历史不变前缀可以复用旧摘要,避免重复付费。

一个被刻意移除的"直觉方案" 🤔

值得玩味的是 eager.py 中的EagerToolOutputClearShaper——一个每次调用都无条件清空旧工具输出的整形器。它没有被放进默认流水线:因为在低上下文压力时,反复改写旧前缀会让原本可复用的 prompt 缓存失效,还可能诱发模型重复读取文件,代价超过收益。这正是 OpenCollab "压力触发而非预防性清理"哲学的实证:宁可少做,不做无用功。

Token 预算分配:触发线是怎么算出来的? 📐

从模型窗口推导触发线

触发/目标阈值不是拍脑袋的常数,而是由history_trigger_target(见 pipeline.py)从当前模型的真实上下文窗口推导:

有效输入预算 = 上下文窗口 − 输出预留(20,000) − 安全缓冲(13,000) 触发线 trigger = 有效输入预算 目标线 target = trigger × 75%
  • 输出预留:为下一轮模型的总结/回答腾出空间;
  • 安全缓冲:防止估算误差导致真实请求溢出;
  • 75% 目标比:压缩到 75% 而非压到触发线以下一点点,留出余量,避免下一轮立刻再次触发压缩(压缩风暴);
  • 模型窗口未知时,优雅降级为固定默认值:120,000 触发 / 90,000 目标。

窗口很小时,系统还会按比例缩小预留和保留组数(affordable_groups),保证小窗口模型同样行为合理。

用真实 usage 校准估算(ContextPressure)

Token 估算最难的地方在于:提供商只报告整个请求的 input token 数,不告诉你每条消息各占多少。context_pressure.py 中的ContextPressure用了一个精妙的加性分摊方案:

  • 每次成功请求后,把提供商报告的 input tokens按各消息此前的估算比例分摊回每条消息;
  • 未改动的消息复用这些权重(保持加性,供各整形层做增量计算);新消息则先用本地估算(ASCII 约 3 字符/token,CJK 约 1 字符/token),等下一次真实 usage 再校准;
  • 加密的推理块不按字节数硬算,而是用提供商报告的 output usage 封顶,避免高估。

这样各层看到的 Token 视图是"被真实账单校准过的估算",而不是一句空话。

优先级保护:谁不能被压缩? 🛡️

每个上下文来源都带优先级标签(定义于 domain/context.py):

上下文来源默认优先级
身份 / 角色提示(Identity)100
团队拓扑(Team)90
技能目录(Skill)85
委托任务(Task)80
项目地图(Project)30

关键常量PIN_FLOOR = 70:优先级 ≥ 70 的来源(身份、团队、技能、任务)被"钉住",永远不会被折叠进摘要;项目地图这类低优先级上下文则在压力下可被优先清理。即使进入"强制压缩"兜底模式(提供商真的拒绝了请求后的最后一搏),钉住的种子消息也照样受保护。

溢出恢复:一次重试,然后体面停

当提供商返回可分类的上下文溢出错误时,系统会强制所有反应层全量压缩并重试一次;若二次溢出则携带原因优雅停止(STOPPED),保留所有部分结果——绝不让一个受保护的巨型种子导致死循环,也绝不把"被限制停止"伪装成"任务成功"。

团队场景下的 Token 预算分配 🧑‍🤝‍🧑

多 Agent 团队中,Token 预算不只是窗口问题,还是分配问题。OpenCollab 按运行构造方式分四种分配规则(详见 docs/runtime-design.md):

模式分配规则
预声明团队(共享池)每个座位累积上限 =min(total, c × total / N),座位不预占,单轮授权同时受剩余池和座位额度约束
动态名单子 Agent 创建时同步预留最多min(total, max(10,000, total // 4))
声明角色预算每个角色独立额度,必须全员声明
WorkflowAgent 调用前从工作流池预留,清理后释放未用部分

预声明团队的规则让"团队池 = N 倍单人额度"时,每个座位与独立 Agent 完全可比,还能区分"角色没干活"和"角色额度耗尽"两种情况。下图展示了真实运行中的时间线:预算耗尽(out of budget)如何影响团队的行为轨迹——左边采纳者成功收尾,右边则因预算枯竭导致 Agent 无法结束自己的轮次:

会话预检(precheck)在每次模型调用之间执行 Token 与步数的硬性天花板检查;已在飞行中的调用可能超出剩余额度——这些是优雅的执行限制,而非实时计费上限,这一点在文档中被明确区分,体现了框架在研究可复现性上的严谨。

如何在团队配置中控制上下文策略 ⚙️

普通用户无需读源码,就能通过团队配置文件(如 configs/team.example.yaml)调整上下文策略:

context: default # 或者: context: { policy: no_history_compaction, tool_result_budget: 20000 }
  • default:完整的压力触发历史压缩流水线;
  • no_history_compaction:只保留单层工具结果预算,历史层全部移除——适合希望完整保留历史用于研究审计的场景(策略解析见 context_policy.py);
  • tool_result_budget:直接调大或调小单层工具结果的字符上限。

所有实际生效的触发/目标值都会由session.history_compaction追踪记录落盘,方便跨实验对比(runtime-design.md 中专门强调了用这些记录做比较,而不是用代码里的默认值)。

总结:为什么这套机制让长任务稳定? ✅

OpenCollab 用不到 5 万行核心代码支撑起 SWE-bench Pro 64.25%、Terminal-Bench 2.1 83.15%、DeepSWE 69.91% 的成绩,其上下文工程的要点可以浓缩为四条:

  1. 读取时整形,存储时不动——压缩永远可逆,恢复永远无损;
  2. 分级渐进,损失最小优先——清空内容 → 删整组 → 模型摘要,便宜的先上,压力解除即停;
  3. 预算来自真实窗口与真实 usage——推导式触发线 + 提供商账单校准的估算,杜绝"拍脑袋常量";
  4. 优先级保护 + 优雅失败——身份/团队/任务永不被折叠,二次溢出就体面停止并保留原因。

如果你想深入阅读,建议从 docs/runtime-design.md 入手,它系统性地解释了上下文装配、压力整形、Token 记账与转向(steering)的全部不变量;而 tests/runtime/ 目录下的 shaping、compaction 与 context-overflow 主题测试,则是每条设计决策的可执行证据。

【免费下载链接】OpenCollab

A research-friendly, high-performance multi-agent collaboration coding framework.

项目地址:https://gitcode.com/gh_mirrors/op/OpenCollab
点击查看免费下载

相关推荐

上一篇:如何为Chat API添加一个新的大模型上游?渠道适配器开发实战教程
下一篇:GitHub_Trending/ssr-benchmark对比分析:Remix框架Defer API性能优化效果

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进