oh-my-pi Snapcompact 上下文占位符注入:将 AGENTS.md 指令压缩为 PNG 帧的提示词工程机制
2026/9/11 16:09:32 网站建设 项目流程

oh-my-pi Snapcompact 上下文占位符注入:将 AGENTS.md 指令压缩为 PNG 帧的提示词工程机制

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

oh-my-pi 的 Snapcompact inline imaging 机制会在请求发出前,把系统提示中的 context 文件指令(如 AGENTS.md、<repo-rules>## Context段落)以及冗长的历史工具结果,渲染为高密度 PNG 帧以压缩在途 token。snapcompact-context-stub.md正是这一机制的关键一环:当原文本被替换成图片后,它作为"占位符提示"留在原位,指导模型把图片当作被移走的原文来读取。读完本文,你将理解该占位符的注入时机、与其余四个 prompt 文件的配合关系、背后SnapcompactInlineTransformer的完整调用链,以及如何通过snapcompact.*配置项启用和调优这套机制。

占位符文件本身:一行指令承载的职责

snapcompact-context-stub.md 全文只有一行:

Loaded context-file instructions: PNG image(s) attached below at the first user message start. At this marker, read every frame in order; apply as if original context-file text remained here.

它被编译进 snapcompact-inline.ts,与contextFramesNotesystemFramesNotesystemStubtoolResultNote一起通过with { type: "text" }导入。它回答模型三个问题:

  1. 发生了什么——"你本来加载的 context-file 指令被移走了";
  2. 去哪找——图片附加在第一条用户消息的开头,按顺序读取;
  3. 怎么用——把每一帧当作被移走的原文等价物("apply as if original context-file text remained here"),而不是普通配图。

这正是一份"指令占位符(instruction stub)":图片只承载文本密度,语义权威性仍来自这行文字对模型阅读行为的约束。

占位符的注入点:replaceContextSections的文本替换逻辑

占位符并非随便贴在系统提示里,而是精确替换掉被图片化的原文段落。核心逻辑在 selectSystemPromptImageTarget 和 replaceContextSections:

const CONTEXT_SECTION_PATTERNS = [ /<repo-rules>\n[\s\S]*?\n<\/repo-rules>/g, /## Context\n<instructions>\n[\s\S]*?\n<\/instructions>/g, ] as const; function replaceContextSections(block: string, extracted: string[]): string { let replaced = block; for (const pattern of CONTEXT_SECTION_PATTERNS) { replaced = replaced.replace(pattern, match => { extracted.push(match.trim()); return contextStub.trim(); }); } return replaced; }

也就是说,当snapcompact.systemPrompt设为agents-md时,系统提示中两类结构——<repo-rules>…</repo-rules>标签块,以及## Context标题下<instructions>…</instructions>包裹的段落——会整体被提取出来(存入extracted),原位替换为contextStub.trim()。这正是 AGENTS.md 等 context 文件指令被加载进系统提示后的典型形态,因此该模式又被称为"仅搬走 context 文件指令"。

被提取的原文随后拼接为一段文本,经snapcompact.frames(text, { shape })估算帧数,最终在第一条用户消息前插入渲染出的 PNG 帧,同时把contextFramesNote作为该帧前的文字说明。

三种模式与兄弟占位符:一套完整的"图片化替换"家族

SnapcompactSystemPromptMode定义了系统提示图片化的三种模式(snapcompact-inline.ts):

模式行为
none系统提示保持纯文本,不做任何替换
agents-md仅替换<repo-rules>## Context <instructions>段落,占位符为 context-stub
all整个系统提示被替换为systemStub,说明文本改用systemFramesNote

五种 prompt 文件按场景各司其职,全部位于 packages/coding-agent/src/prompts/system/:

  • snapcompact-context-stub.md——context 文件指令段的原位占位符(本文主角);
  • snapcompact-context-frames-note.md——附加在第一条用户消息前的说明:"读下面的图片,把它们当作被替换的 context 文件";
  • snapcompact-system-stub.md——all模式下整份系统提示的原位占位符:"图片是权威的操作指令,优先读取并按权威执行";
  • snapcompact-system-frames-note.md——all模式下用户消息前的说明文字;
  • snapcompact-toolresult-note.md——历史工具结果被图片化后,随帧附带的说明,其中特别注明"这是刻意的上下文节省行为,不是故障,不要重跑工具或上报问题",并解释源图片位置标记的对应关系。

注意到 snapcompact-inline.ts 中,系统提示被替换后,用户消息的content被重组为[{ type: "text", text: userNote }, ...frames, ...originalContent]——即说明文字 + 帧 + 原用户消息内容依次排列,与 stub 中"image(s) attached below at the first user message start"的描述完全对应。

触发链路:transformProviderContext中的执行顺序

占位符替换不是独立的文本处理,而是 agent 循环里每次请求前的一段转换管线。在 sdk.ts 中,SnapcompactInlineTransformertransformProviderContext钩子里按固定顺序执行:

  1. obfuscateProviderContext——可选的混淆器先行;
  2. snapcompactInline.transform——本机制核心:计划交换(planInlineSwaps)、渲染帧、把 stub 写回系统提示、把帧挂到第一条用户消息;
  3. clampProviderContextImages——按提供商的图片预算裁剪超出的图片块;
  4. normalizeProviderContextImagesForModel——按模型归一化图片格式;
  5. dropUnreadableContextImages——剔除无法解码的图片块,降级为文字占位;
  6. blobBroker.decorateContext——将内联字节换成可服务的远程 URL(若配置了 blob broker);
  7. dateCwdReminder.transform——把日期/工作目录提醒注入首条用户消息。

关键约束在文件头注释中写得很明确:transform 只构造新的 message 对象,绝不修改输入的content数组引用,因为输入共享了持久化SessionMessageEntry的引用,直接改动会把渲染出的图片泄漏进session.jsonl。因此帧是"每次请求瞬态生成"的,历史文件里永远只有文本。

交换策略与门控:什么时候才值得图片化

planInlineSwaps(snapcompact-inline.ts)是唯一决策入口,实时请求与/context节省估算共用同一份规则,保证二者永不打架:

  • 工具结果门控MIN_TOOL_RESULT_TOKENS = 3000——低于 3000 token 的工具结果绝不栅格化,文本足够便宜;isError的工具结果必须保持纯文本(提供商 API 校验需要);
  • 节省裕度SAVINGS_MARGIN = 0.9——只有帧数 × frameTokenEstimate <= textTokens × 0.9才渲染,保证图片 token 至少比原文本便宜 10%;
  • 系统提示帧上限MAX_SYSTEM_PROMPT_FRAMES = 6
  • 预算约束:先扣掉上下文中已存在的图片数,得到剩余预算;系统提示交换放在工具结果之后,只用"剩下的"预算;
  • 跳过而非停止:单个候选超出剩余预算时跳过它继续看后面的更小候选;且最新一条工具结果永远保持文本,保证最新输出清晰可读、利于缓存稳定性。

预算来自 snapcompact.ts 的PROVIDER_IMAGE_BUDGETSproviderImageBudget:各提供商有独立预算表,未知名提供商回退到DEFAULT_PROVIDER_IMAGE_BUDGET = 5estimateInlineSavings(snapcompact-inline.ts)据此输出visionCapablesystemPrompttoolResults三组估算,/context面板可预览"下一请求将省多少 token"。

缓存与懒渲染:避免重复栅格化

SnapcompactInlineTransformer内部维护两个渲染缓存(snapcompact-inline.ts):

  • #toolCache:以toolCallId为键、Bun.hash(text)为内容指纹;每次 transform 后用当前上下文中存活的toolCallId清掉失效条目,缓存大小受实时历史约束;
  • #systemCache:以整个待图片化系统提示文本的 hash 为指纹,命中即复用。

#framesFor优先调用构造时注入的frameSink(blob broker 的懒渲染服务):帧在缓存里只是"小占位符"({ data: "", url }),真正的 PNG 只在提供商实际抓取 URL 时才栅格化,内存里从不囤积像素。这也是 provider-image-budget.ts 注释里提到的形态:transform 先于图片裁剪执行,因此它见到的占位符已经是"引用形状",不会误判为待解码的内联字节。

配置项与实操参数

在 settings-schema.ts 中,本机制暴露为三个实验性配置(UI 位于 context 标签页的 Experimental 分组):

配置项类型默认值说明
snapcompact.systemPromptenum:none/agents-md/allnone是否将系统提示(或其 context 文件段)图片化;仅视觉模型生效;节省 token 但会失去被图片化文本的提示缓存
snapcompact.toolResultsbooleanfalse是否将大体积历史工具结果渲染为 PNG,省下累积的 read/search 输出
snapcompact.shapeenum:auto+ 各形状变体auto帧的排版形状;auto按当前模型自动挑选,回退到其提供商家族

兼容性细节:settings.ts 会把旧版布尔值snapcompact.systemPrompt自动迁移为枚举——trueallfalsenone

snapcompact.shape的主要变体(详见 schema 描述):

变体特征
8x8r-bw/8x8r-sentunscii 方块字格,每行重复两遍,后者按句子换色
6x6u-bw/6x6u-sent6x6 最密可读格,帧数最少
5x8-bw/5x8-sent最初的 snapcompact 形状(X.org 5x8 字形)
8on22-bw8x13 字形配 8x22 行距,OpenAI/Google 默认
11on16-bw8x13 字形配 11x16 字距,Anthropic 默认
silver16-bw内嵌 Silver TrueType 字体 16px 网格,面向 CJK 等非拉丁文本
doc-8on16-bw/doc-8on16-sent/doc-8on16-sent-dim双栏报纸式排版,可选句子换色与虚词压暗

建议启用顺序:先snapcompact.toolResults = true观察长会话中累积工具输出的节省,再试snapcompact.systemPrompt = agents-md(仅图片化 AGENTS.md 等 context 指令,风险面最小),最后才考虑all。由于图片化会破坏对应文本的提示缓存(schema 描述已明确提示 "loses prompt caching for imaged text"),对依赖前缀缓存的开源权重模型,需权衡每次请求的 in-flight 节省与缓存命中的长期收益。

局限性与兜底设计

  • 仅视觉模型生效model.input不含image时 transform 直接原样返回上下文(snapcompact-inline.ts),因为纯文本提供商会静默丢弃图片,栅格化等于丢内容;
  • 预算耗尽回退:一旦预算被已附带的归档图片或系统提示帧花光,工具结果原样以文本发送;
  • 损坏图片兜底:若帧或历史图片无法解码,provider-image-budget.ts 的dropUnreadableContextImages会把不可读块降级为[image omitted: undecodable …]文字,而不是让整个请求被提供商拒绝;
  • 不出现在历史:帧只存在于出站Context的瞬时副本,session.jsonl中永远只有被 stub 替换前的文本形态,onToolResultSavings追加式账本(sdk.ts)是帧 token 节省的唯一痕迹。

从源码结构看,这套"文本 → 密集 PNG 帧 + 原位占位符指令"的模式是 oh-my-pi 应对长上下文预算的核心实验路径:占位符文件虽短,却负责在语义上把"图片即原文"的阅读契约固定下来,缺了它,模型将无法区分"附带截图"与"被替换的指令",整个压缩机制就会失去保真度。

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询