6 min read教程

M08. 上下文:有限窗口如何承载长任务

分析工具输出截断、动态提示词、技能和历史处理如何共同管理有限上下文。

M08. 上下文:有限窗口如何承载长任务#

上下文工程不是“把更多资料塞进 prompt”。真正的问题是:在窗口有限、工具输出可能爆炸、项目规则需要始终生效、历史又不断增长的情况下,哪些信息应该被保留,哪些应该延迟加载,哪些必须被压缩。

1. 两个方向的防护#

Pi 的上下文处理可以按两个方向理解:

  • 输入侧:控制一次工具结果和动态提示词的大小。
  • 历史侧:控制长期对话、分支和摘要如何进入当前路径。

它们不能互相替代。压缩解决历史增长,不能阻止一条异常大的 bash 输出瞬间占满窗口;截断工具结果也不能替代跨几十轮对话的摘要。

2. 工具输出截断#

限制工具输出时,通常同时看行数和字节数,先达到的限制生效。头部截断适合保留命令开头的错误和环境信息,尾部截断适合保留最新结果或末尾摘要。

ts
function truncateTail(text: string, maxBytes: number) {
  const bytes = Buffer.byteLength(text, "utf8");
  if (bytes <= maxBytes) return text;
  return text.slice(-Math.floor(maxBytes / 2)) + "\n[truncated]";
}

真实实现还要处理 UTF-8 多字节边界、单行超限和准确的提示语。示意代码的重点是:截断后必须明确告诉模型发生过截断,否则模型会把不完整结果当完整事实。

3. 动态系统提示词#

项目规则、当前工作目录、可用工具和 skills 往往需要动态拼接。稳定的系统骨架可以保持简洁,把项目指令用清楚的边界包装起来,把技能列表放进提示词,把技能正文留到按需读取时再加载。

text
静态 Agent 规则
  + 工作目录与环境摘要
  + 当前工具说明
  + 项目上下文文件
  + 可用 skills 的名称和用途

这是一种渐进式披露:路由信息常驻,详细知识按需进入。它同时降低 token 成本和错误路由概率。

4. transformContext 是扩展点,不是银弹#

宿主可以通过 transformContext 对待发送的 AgentMessage 做裁剪、补充或重排。这个扩展点适合加入项目特定的上下文策略,但不应该在里面悄悄删除用户约束,也不应该把安全策略交给模型自己推断。

上下文变换应可解释:记录哪些消息被过滤、哪些工具结果被截断、压缩从哪里开始。否则用户看到 Agent 忘记某件事时,你无法判断是模型问题还是 projection 问题。

5. 分支摘要与普通压缩#

普通 compaction 处理的是“当前历史太长”;branch summarization 处理的是“从另一条分支跳回来后,旧路径的信息怎样带回来”。两者都使用摘要,但触发原因和语义不同:前者是窗口管理,后者是分支切换时的语义桥。

6. 一次上下文处理链#

text
Session Tree 当前路径
  → 取出可见 entry
  → 处理 compaction / branch summary
  → 生成 AgentMessage
  → transformContext
  → convertToLlm
  → provider adapter

任何一步都可能改变模型最终看到的内容。因此调试上下文时,要同时保存“原始历史”和“最终请求”,不能只看会话文件或只看模型请求中的最后几条消息。

7. 工具调用也是按需加载#

当模型决定调用 readgrep 或某个 skill 工具时,它是在选择一块上下文载体。工具输出应足够支持下一步决策,但不应把整个仓库一次性搬进窗口。好的工具接口本身就是上下文预算策略。

8. 上下文工程的判断标准#

任何上下文变换都应该能回答三个问题:删掉了什么,为什么可以删;补进了什么,来源是否可信;模型是否知道当前内容经过了截断或摘要。上下文不是越多越好,而是要让下一步决策所需的信息密度更高。把原始大结果保存到文件、把短摘要放进消息,正是把存储和推理两个问题分开。

资料

本文依据 Pi SDK 文档Compaction 文档重新组织。

相关文章