- 人工智能
- 大模型
- AI 应用
- 交互助手
- 本地部署
【免费下载链接】cherry-studio
🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端
本文解析 Cherry Studio v2 中一项针对「非视觉模型 + 图片附件」的 breaking change:当所选模型未声明图像识别能力时,客户端仍会先执行 OCR,但在 OCR 未识别出文本、未配置或执行失败时,不再将图片降级替换为[could not read this file]占位文本,而是把图片以 base64 原生图像的形式直接转发给提供商。读完本文,你将理解该变更的触发条件、底层附件路由机制(attachmentRouting.ts)、OCR 处理器体系(processorRegistry.ts)以及发布说明撰写的注意事项(含 PR #18297 的历史插曲)。
变更概览:什么场景的行为变了
根据原文档 2026-07-30-chat-image-ocr-native-fallback.md(introduced_in_pr: 17637,category: changed,severity: notice),变更只作用于Home Chat 中、目标模型未声明图像识别能力(non-vision)的图片附件路径。
该路径的标准流程是:先对图片执行 OCR 尝试提取文字,再决定如何呈现给模型。变更前与变更后的差异集中在OCR 空结果 / OCR 不可用这一个分支:
| 场景 | 变更前 | 变更后(v2 净行为) |
|---|---|---|
| 图片有可识别文字(OCR 成功) | OCR 文本内联给模型 | 不变,仍为 OCR 文本内联 |
| 图片无文字、OCR 未配置或失败 | 替换为[could not read this file]占位提示 | 以 base64 原生图片形式转发给提供商 |
| 显式 OCR 功能(翻译工作流) | 独立路径 | 不变 |
read_file工具 | 独立路径 | 不变 |
原文档特别强调:只有 empty-OCR / OCR-unavailable 这一条路径发生了变化;OCR 命中文本、显式 OCR 功能(翻译工作流)以及read_file工具均不受影响。
为什么这个变更对用户重要
文档给出的理由非常明确:照片、图表等没有可识别文字的图片,不再在「非视觉」模型上被静默降级为一段不可读文件的说明文字。两类用户会直接受益:
- 元数据低估了视觉能力的模型:某些模型在能力元数据中未声明 vision(
isVisionModel判定为 false),但它们实际上能理解图像内容。此前这类图片会被替换为占位文本,白白丢失信息;现在模型能收到真实图片。 - 真正无法处理图片的提供商:由提供商按自身行为忽略或拒绝图片,而非由 Cherry Studio 提前降级。
关于模型视觉能力的判定,可以在 model.ts 中看到实现:isVisionModel检查model.capabilities.includes(MODEL_CAPABILITY.IMAGE_RECOGNITION)或model.inputModalities?.includes(MODALITY.IMAGE)。这意味着「模型是否走 OCR 路径」完全取决于元数据声明,与模型真实能力解耦——这正是文档所说「under-declared vision capability」的来源。
用户需要做什么:无需操作
原文档给出的答案是:什么都不用做。
- 有可识别文字的图片,保持既有的 OCR 转文本行为;
- 如果希望文字密集型图片在非视觉模型上获得更好的结果,配置一个
image_to_text处理器仍然有效(这是可选的优化项,不是修复项)。
image_to_text处理器的配置入口在 fileProcessing.ts 的预设中,可用处理器包括:tesseract、system、paddleocr、local-paddleocr、ovocr、mistral(后文详述)。
底层机制:附件路由如何决定图片去向
要理解这条变更,需要看清 Cherry Studio 聊天路径附件路由的完整决策链。核心实现在 attachmentRouting.ts,入口是prepareChatMessages,由 AiService.ts 在每次模型请求前调用。
第一层判定:原生支持(native)还是文本提取(non-native)
prepareChatMessage对每条消息中的每个filepart 逐一处理,先通过isNative判定该附件对当前 (provider, model) 是否属于原生输入:
- 原生:图片(模型具备 vision)、PDF(提供商协议支持)、音视频(模型 + 端点均支持)→ 通过
materializeNativeFilePart物化为真实文件 part 内联保留; - 非原生:替换为提取文本(office/pdf/text 走
extractDocumentText,图片走 OCR,音视频/二进制走说明文字)。
原生支持矩阵由 nativeFileSupport.ts 的resolveNativeFileSupport计算:image字段即isVisionModel(model),pdf/audio/video还叠加提供商白名单与端点类型判断。其中NATIVE_FILE_PROVIDER_IDS是保守默认白名单(openai、anthropic、google、azure、bedrock 等),未知第三方提供商不会默认获得原生 PDF 支持。
第二层判定:非视觉图片的 OCR 分支
对非原生图片(fileType === FILE_TYPE.IMAGE且nativeSupport.image === false),代码走 OCR 分支(attachmentRouting.ts):
if (fileType === FILE_TYPE.IMAGE) { const ocrText = await ocrNonVisionImage(fileEntryId, ctx.signal) if (ocrText === null) { logger.warn('Non-vision image OCR produced no readable text', { ... }) throw new NonVisionImageOcrError() } defer(kept, pending, handle, ocrText) continue }ocrNonVisionImage调用application.get('FileProcessingService').ocrImage({ kind: 'entry', entryId }, signal)并trim():返回空串视为null;OCR 抛错(未配置 / 失败)时记录 warning 并返回null(abort 场景会原样重抛)。
当前仓库中该路径的代码实现
需要如实指出:当前仓库 attachmentRouting.ts 中,OCR 返回 null 时抛出的正是文档提到的本地化错误NonVisionImageOcrError(i18nKey: 'image_unreadable_for_non_vision_model'),对应原文档 Notes 中 PR #18297 引入的「请求前失败」阻断行为,该错误文案在多语言文件中均有本地化(如 en-us.json 中为 "The selected model isn't configured for image input, and Cherry Studio couldn't extract readable text from the attachment...")。这与文档开头描述的 v2 净行为(base64 原样转发)存在差异,属于文档记录的中间态与最终目标之间的演变,具体时间线见后文「发布说明与历史」一节。
同时注意,[could not read this file]占位提示(noteOf,attachmentRouting.ts)在代码中并未消失,仍用于其他降级场景:原生物化失败、解析错误、条目丢失等——这些失败遵循「降级为模型可见说明文字而非静默丢弃」的既定策略(文件头注释有明确说明)。
文本内联与预算上限
OCR 出的文本与文档提取文本一样,通过defer进入PendingInline队列,最后在applyInlineCaps中统一分配预算:一次请求的多个附件共享同一个 token 池(AttachmentBudget,由resolveAttachmentBudget依据系统提示、工具、最大输出 token 等计算),超过上限后截断头部并在尾部给出read_file("handle", offset=...)指针(仅对工具能力模型);非工具模型则只给截断提示。这保证了即使不依赖模型主动调用read_file,弱工具模型也能看到内容。
OCR 引擎体系:image_to_text 处理器全景
OCR 转发/提取的底层执行由文件处理服务完成。FileProcessingService.ts 的ocrImage直接委托给 ocrImageToText.ts 的ocrImageToText,其关键设计:
- 复用处理器解析链:
resolveProcessorConfigByFeature('image_to_text')→getCapabilityHandler→prepare,与后台文件处理任务共用同一套处理器注册表; - 同步直调:不走
JobManager后台任务(聊天工具调用需要同步拿到文本),但远程处理器采用「启动 + 轮询」模式(REMOTE_POLL_INTERVAL_MS = 2_000,REMOTE_POLL_TIMEOUT_MS = 120_000); - 结果缓存:按
文件 entryId + mtime + size计算缓存键,TTL 为 30 分钟,避免每轮对话对同一图片重复 OCR。
可用处理器与平台支持矩阵
处理器注册表见 registry.ts,支持image_to_text的处理器及平台约束如下:
| 处理器 ID | 类型 | 平台支持 | 说明 |
|---|---|---|---|
tesseract | 本地 | 全平台(isSupported: () => true) | 经典本地 OCR,Linux 默认 |
system | 本地 | 仅 macOS / Windows | 系统级 OCR,macOS/Windows 默认 |
paddleocr | 远程 API | 全平台 | 默认apiHost: https://paddleocr.aistudio-app.com/,modelId: PP-OCRv6,同时支持 document_to_markdown |
local-paddleocr | 本地 | 排除 Intel Mac(!isDarwinX64) | 需先下载本地 OCR 模型 |
ovocr | 本地 | isOvOcrAvailable | 视运行时可用性而定 |
mistral | 远程 API | 全平台 | apiHost: https://api.mistral.ai,modelId: mistral-ocr-latest,同时支持 document_to_markdown |
平台默认处理器是自愈式解析而非持久化配置:defaultImageToTextProcessor.ts 返回 macOS/Windows 用system、其余平台用tesseract,这样把配置从一个操作系统备份恢复到另一个操作系统时,不会带入一个不可用的处理器 ID。
未配置时的错误路径
处理器解析在 resolveProcessorConfig.ts 中完成。当偏好未设置默认处理器且平台自愈默认也不可用时,抛出Default file processor for image_to_text is not configured——这正是 attachmentRouting.test.ts 中「OCR 未配置」用例所 mock 的错误,它会触发上述 OCR null 分支。该文件还覆盖了本地模型未下载(needs the local ocr model to be downloaded first)、平台不支持、处理器不支持该 feature 等区分度很高的错误信息。
发布说明与历史:PR #18297 的时间线
原文档「Notes for release manager」一节包含重要的版本历史信息,发布说明撰写时必须知晓:
- v2(PR #17637 引入):OCR 空/失败时 base64 原生图片转发(即文档 What changed 描述的净行为);
- v2.0.5(PR #18297):该回退曾被替换为本地化错误,在请求发出前直接失败整个回合,随 v2.0.5 发布了带 "action required" 措辞的说明;
- 问题后果:在阻断生效期间,使用接受图片的网关/代理的用户无法发送任何图片;更严重的是,由于附件路由会重放整个对话历史,一张此类图片会导致该对话后续每一轮都失败;
- 文档指示:v2 最终应描述的是「净行为」——即 OCR 空/失败时原样转发图片,不要把 v2.0.5 的 "action required" 措辞带入正式发布说明。
当前仓库代码中NonVisionImageOcrError的阻断实现(含测试用例 attachmentRouting.test.ts 对「OCR 空结果」「OCR 未配置/失败」两种场景rejects的断言)即上述时间线中的中间态代码;发布说明以文档开头描述的转发行为为准。
测试用例如何验证路由行为
attachmentRouting.test.ts 系统性地覆盖了该路由的每个分支,可作为理解行为契约的参考:
- 原生图片直通:vision 模型下图片保持 file part 内联,不触发 OCR 与文本提取;
- 非视觉图片 OCR 内联:OCR 成功时,图片被替换为
Attached file "a.png":\n<ocr body>文本(含 legacy 与现代 composer token 两种附件形态); - 非视觉图片 OCR 空/失败:分别断言抛出
NonVisionImageOcrError且i18nKey为image_unreadable_for_non_vision_model,并确认原生物化未被调用(即失败发生在请求发出前); - composer token 关联:现代 managed file part 若其 composer token 不再引用该 source id,会被视为孤儿附件跳过,不做 OCR;
- 预算共享:同回合多个附件共享一个 token 池(不再每文件给满上限),池跨消息分配且写回各自消息;
- read_file 指针:截断文本对工具模型附带
read_file("handle", offset=N)指针,非工具模型只给截断提示; - 降级说明:原生物化失败、二进制/不支持类型、音视频不支持的模型分别得到对应说明文字。
小结
这条 breaking change 的实质,是把「非视觉模型 + 无文字图片」这一边缘场景从静默信息丢失(占位文本)转向把原始图片交给模型:对能力被元数据低估的模型是信息增益,对真正不支持的提供商则交由对方自行处理。用户侧零操作,image_to_text处理器仍是优化文字型图片的可选手段。发布时请牢记原文档的叮嘱:只描述净行为一次,不再沿用 v2.0.5 的 "action required" 措辞。
- 人工智能
- 大模型
- AI 应用
- 交互助手
- 本地部署
【免费下载链接】cherry-studio
🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端
相关推荐
Cherry Studio 向非视觉模型发送图片附件时为什么会报错不发请求?
Cherry Studio 向非视觉模型发送图片附件时为什么会报错不发请求? 在 Cherry Studio 的对话中给消息附加图片后,如果当前选中的模型不支持
人工智能大模型AI 应用交互助手本地部署oh-my-pi 纯文本模型图片附件视觉回退机制:image-attachment-describe 提示词详解与实现原理
oh my pi 纯文本模型图片附件视觉回退机制:image attachment describe 提示词详解与实现原理 导读 在 oh my pi(⌥ 编码
人工智能AI Agent代码智能体工具调用CLIMCP Clientsoh-my-pi 图像附件描述系统提示词拆解:为纯文本模型构建"以文代图"的视觉回退机制
oh my pi 图像附件描述系统提示词拆解:为纯文本模型构建"以文代图"的视觉回退机制 导读 在 oh my pi(⌥ Coding agent with t
人工智能AI Agent代码智能体工具调用CLIMCP Clients
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考