oh-my-pi 纯文本模型图片附件视觉回退机制:image-attachment-describe 提示词详解与实现原理
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
导读
在 oh-my-pi(⌥ 编码 Agent,与 IDE 深度集成)中,当用户把截图、报错画面或界面截图附加给一个不支持图像输入(vision)的纯文本模型时,图片不会简单地被丢弃,而是会触发一套"视觉回退(vision fallback)"机制:由注册的视觉模型对图片进行详尽的文字描述,再以文本块形式注入下游上下文,让纯文本模型也能"看见"图片并参与推理。本文以 image-attachment-describe.md 提示词为切入点,完整讲解该提示词覆盖的要素规范、配套系统提示词,以及它在 image-vision-fallback.ts 中的实现原理、模型解析优先级与相关配置项。读完本文,你将掌握该机制的全貌,并能在自己的 Agent 或 RAG 应用中复刻一套"图片 → 结构化文字描述 → 纯文本推理"的降级管线。
为什么纯文本模型需要"看图":图片附件的降级问题
对话型大模型的输入模态并不统一:有的模型原生支持多模态图像输入(input中包含image能力),有的则是纯文本模型。当用户在 oh-my-pi 的会话中粘贴一张图片给纯文本模型时,若不处理,Provider 层会直接丢弃图片内容(源码注释中记为NON_VISION_IMAGE_PLACEHOLDER,见 image-vision-fallback.ts),模型对图片内容一无所知——截图里的报错、界面上的按钮状态、图表中的数据全部丢失。
oh-my-pi 的解决方案是"降级为文字":与其让图片被静默丢弃,不如用另一个具备视觉能力的模型把图片"翻译"成详尽的文字描述,再把这段描述作为文本块替换图片注入上下文。这样纯文本模型依然可以基于描述进行推理,整个流程对用户透明,且不改变下游模型的能力边界。
这一思路的核心载体,就是本文的主角——image-attachment-describe.md。它作为user 提示词(user prompt),在 image-vision-fallback.ts 中被以文本资源方式导入:
import describeUserPrompt from "../prompts/tools/image-attachment-describe.md" with { type: "text" }; import describeSystemPrompt from "../prompts/tools/image-attachment-describe-system.md" with { type: "text" };调用时用户消息包含原始图片与这段 user 提示词,系统消息则使用配套的 image-attachment-describe-system.md(见describeImage中的prompt.render(describeSystemPrompt)与消息组装,image-vision-fallback.ts)。
提示词主体:把图片描述成"看不见也能推理"的文字
image-attachment-describe.md全文虽然精炼,却是一份高度可操作、面向多模态场景的"图片要素清单"。它的总目标是:
Describe the image in enough detail for a model unable to see it to reason about its content.(以足够详尽的细节描述图片,让一个看不见图片的模型也能对其内容进行推理。)
这个总目标界定了描述的服务对象:不是给人看的赏析,而是给下游纯文本模型补充推理所需的事实输入。因此它要求描述"证据充分、可独立支撑推理",而不是"生动形象"。
覆盖要素一:整体场景、主体与动作
提示词要求覆盖overall scene, subject, action(整体场景、主体、动作)。视觉模型需要交代"这是一张什么样的图、拍的是什么、正在发生什么",为下游模型建立第一层上下文。例如对一张报错截图,至少要说清"这是终端窗口的截图,顶部是命令提示符,下方输出了一段红色的错误信息"。
覆盖要素二:人物与物体——关系、位置、颜色、数量
对于包含人物或物体的图片,提示词明确要求覆盖relationships, positions, colors, counts(关系、位置、颜色、数量)。这四个维度都是可验证的客观事实:
- 关系:谁在谁旁边、谁指向谁、谁在操作什么;
- 位置:物体在画面中的相对布局(左上、右下、居中……);
- 颜色:关键物体的颜色,往往是定位或状态判断的线索;
- 数量:出现了几个同类物体,例如"页面底部有 3 个按钮"。
覆盖要素三:可见文字逐字转写(OCR)
提示词特别强调all visible text verbatim (OCR)——所有可见文字必须逐字转写。这是整个提示词中对下游推理最关键的一条:报错信息、日志输出、界面标签、代码片段几乎全部依赖文字内容,任何错字、漏字都会直接误导下游模型。配套系统提示词进一步强化了这条纪律(见下文"系统提示词的行为准则")。
覆盖要素四:UI / 截图元素的状态与细节
对 UI 截图,提示词要求覆盖labels, buttons, inputs, states, errors, highlighted or disabled controls——即标签、按钮、输入框、控件状态、错误提示、高亮或禁用状态。这类信息直接对应 Agent 场景中最常见的需求:IDE 报错面板、浏览器控制台、表单校验提示、设置界面开关状态等。描述必须落到"某个具体控件处于什么状态",而不是笼统说"这是一个设置页面"。
覆盖要素五:图表的结构与编码值
对于diagrams, charts, tables,提示词要求覆盖structure, axes, series, encoded values——结构、坐标轴、数据系列、编码值。也就是说,描述不能只说"这是一张折线图",而要交代坐标轴含义、有几条数据系列、趋势方向、关键数据点对应的数值,使下游模型可以据此进行定量或半定量分析。
输出纪律:标注歧义、只输出散文
最后两条同样重要:
- Flag anything ambiguous or unreadable:凡是模糊、无法辨认的内容必须明确标注出来,不能装作看得清;
- Output plain prose only:只输出纯散文文本,不输出 Markdown 列表、JSON、XML 或其他结构化外壳,保证描述作为纯文本块干净地注入下游上下文。
配套系统提示词:八条行为准则保证描述可信
image-attachment-describe-system.md 为视觉模型定义了身份与行为边界,开头即点明:"Description replaces attached image in downstream model context; downstream relies entirely on text, never sees pixels."(描述将替换下游上下文中的附加图片;下游完全依赖文本,永远看不到像素。)这意味着视觉模型是下游模型的"唯一眼睛",其描述质量直接决定纯文本模型的推理质量。系统提示词核心行为如下:
- 证据优先(faithful, evidence-first):明确区分"直接观察"与"推断",观察在前、推断在后,不得混为一谈;
- 逐字转写全部可见文字:保留大小写、标点、布局顺序;对不可读片段明确标注,绝不猜测;
- 禁止编造:绝不虚构被遮挡、模糊或不确定的细节,必须陈述不确定性;
- 详尽而紧凑:信息密度高的散文,无填充废话;
- 只输出描述:禁止元评论、禁止"这张图片展示了……"之类的开场白,禁止收尾寒暄。
这组准则与 user 提示词形成互补:user 提示词解决"覆盖哪些要素",system 提示词解决"以什么态度和格式输出"。二者共同保证了注入下游的文本块是忠实、完整、干净的。
实现原理:图片如何被保存、描述并注入
第一步:判断是否需要回退
回退触发条件在会话 Provider 边界处判定(session-provider-boundary.ts):
const shouldDescribe = !!model && !model.input.includes("image") && // 当前模型是纯文本模型 !this.#host.settings.get("images.blockImages") && this.#host.settings.get("images.describeForTextModels");即:当前活动模型不支持图像输入、且用户没有在设置中全局禁用图片(images.blockImages为 false)、且开启了"为纯文本模型描述图片"(images.describeForTextModels默认 true)时,才走回退管线。命中后调用describeAttachedImagesForTextModel,结果为一段custom类型的隐藏消息(display: false)注入会话,不打断主对话流。
第二步:保存图片到 session 本地根目录
在 image-vision-fallback.ts 中,每张图片会按内容寻址方式保存:
function imageFileName(image: ImageContent): string { const hash = Bun.hash(image.data).toString(16); return `image-${hash}.${extensionForMime(image.mimeType)}`; } async function saveImage(image: ImageContent, localRoot: string): Promise<string> { const fileName = imageFileName(image); const filePath = path.join(localRoot, fileName); await Bun.write(filePath, Buffer.from(image.data, "base64")); return `local://${fileName}`; }要点有二:
- 内容寻址:文件名由图片字节的 hash 决定,同一张图片重复粘贴只产生一个 artifact,且重复写入是幂等的(
Bun.write自动创建父目录); - MIME 扩展名映射:
extensionForMime将 jpeg/png/gif/webp 映射为对应扩展名,未知 subtype 会做净化处理,兜底为png(image-vision-fallback.ts)。
保存后的图片以local://image-<hash>.png形式供后续read工具访问,即使描述失败,图片也不会丢失,可用于后续人工或工具二次分析。
第三步:解析视觉模型并调用
模型解析函数resolveVisionModel采用与显式图像问答一致的优先级(image-vision-fallback.ts):
@vision → @default → 当前活动模型字符串 → 第一个支持 image 输入的可用模型每一步都会通过model.input.includes("image")过滤掉纯文本模型,保证最终选中的一定是视觉模型。解析失败(无可用模型或未配置 API Key)时,describeAttachedImagesForTextModel会降级输出一段说明性占位文本(NO_VISION_MODEL_NOTE/DESCRIPTION_UNAVAILABLE_NOTE,见 image-vision-fallback.ts),提示用户配置modelRoles.vision角色。
调用本身是"一次性问答"(oneshot):system 提示词 + 图片 + user 提示词,stopReason为error或aborted时记为失败并返回null,由上层兜底为占位说明(image-vision-fallback.ts)。整个描述调用通过ONESHOT_KIND = "image_attachment_describe"打点进 telemetry,便于观测。
第四步:以文本块形式注入
formatImageBlock将描述包装为结构化文本块(image-vision-fallback.ts):
function formatImageBlock(localUrl: string, description: string): string { return `<image path="${localUrl}">\n${description}\n</image>`; }最终每张输入图片对应一个TextContent文本块,顺序与输入一致(Promise.all保持次序,image-vision-fallback.ts)。单张图片的描述失败不会抛异常影响其他图片:失败时仍会输出带保存路径的占位块。由于文本块中携带local://路径,下游(或用户在后续会话中)仍可通过read工具定位原始图片。
关键配置项:控制图片回退行为
该机制由images配置组下的多个键控制,均定义在 settings-schema.ts:
| 配置键 | 类型 | 默认值 | 作用 |
|---|---|---|---|
images.describeForTextModels | boolean | true | 当图片附加到无视觉支持的模型时,保存到local://并用视觉模型生成描述注入,而不是直接丢弃(settings-schema.ts) |
images.blockImages | boolean | false | 阻止图片发送给 LLM Provider;开启后回退管线与显式图像问答都会被禁用(settings-schema.ts) |
images.questionTimeoutMs | number | 300_000 | read的?q=图像问答单次请求超时(毫秒),设为0禁用超时(settings-schema.ts) |
images.autoResize(Appearance 组) | boolean | — | 将大图自动缩放至最大 2000×2000 以提升模型兼容性(settings-schema.ts) |
其中images.describeForTextModels是本文机制的总开关;若某次描述调用因配置了@vision角色之外的模型而解析失败,错误提示会引导用户为modelRoles.vision配置一个视觉模型——该角色名与模型角色类型定义一致("default" | "smol" | "slow" | "vision" | ...,见 model-roles.ts)。
与显式图像问答(read ?q=)的分工
与自动回退不同,oh-my-pi 还提供显式图像问答能力:通过read <image>?q=<question>主动向视觉模型提问(read.ts 限定?q=仅支持图像类输入)。其实现位于 image-question.ts:
- 模型解析优先级同样是
@vision → @default → 活动模型,随后回退到"同 Provider 的图像模型 → 任意图像模型"(image-question.ts); - 支持
images.questionTimeoutMs超时、思维链(reasoning)配置,以及模型级resolveThinkingLevelForModel推理强度映射(image-question.ts); - 若解析到的模型不支持图像输入,会直接抛出引导性错误,建议配置
modelRoles.vision(image-question.ts)。
两者的分工很清晰:自动回退解决"用户贴图给纯文本模型"的默认体验问题,显式?q=解决"用户想针对某张图片深挖细节"的主动提问需求。前者是本文描述机制的运行场景,后者是互补的按需分析手段。当自动回退因未配置视觉模型而失败时,提示语也会指引用户"配置视觉模型角色后用?q=<question>分析已保存的图片",两条路径由此闭环。
总结
image-attachment-describe.md虽然只有寥寥数行,却定义了一套"纯文本模型看图"的关键契约:覆盖要素上要求场景/主体/动作、人物物体关系与颜色数量、文字逐字转写、UI 状态、图表结构五类信息全覆盖;输出纪律上要求标注歧义、只输出纯散文。配合系统提示词的行为准则与 image-vision-fallback.ts 的实现,oh-my-pi 把"图片被静默丢弃"的糟糕体验,变成了"图片先被视觉模型翻译成可推理的文字,再注入纯文本模型"的稳健降级方案——图片按内容寻址保存到local://,描述以<image path="...">...</image>文本块注入,失败时也不丢图、不中断,并提供images.describeForTextModels、images.blockImages、images.questionTimeoutMs等开关精细控制行为。
这套"模态降级 + 结构化描述提示词"的设计,对任何需要同时兼容多模态与纯文本模型的 Agent 应用都具有直接的参考价值:描述提示词要围绕"下游无法看图"这一前提来覆盖要素,系统提示词要守住"忠实、不编造、纯文本"的底线,管线实现则要保证"单图失败不拖垮整轮、图片永远可追溯"。
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考