Cherry Studio 非视觉模型图片附件回退行为变更解析:OCR 无文本时以 base64 原生图片转发
2026/9/20 5:45:33 网站建设 项目流程
  • 人工智能
  • 大模型
  • AI 应用
  • 交互助手
  • 本地部署

【免费下载链接】cherry-studio

🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端

项目地址:https://gitcode.com/CherryHQ/cherry-studio
点击查看免费下载

本文解析 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工具均不受影响。

为什么这个变更对用户重要

文档给出的理由非常明确:照片、图表等没有可识别文字的图片,不再在「非视觉」模型上被静默降级为一段不可读文件的说明文字。两类用户会直接受益:

  1. 元数据低估了视觉能力的模型:某些模型在能力元数据中未声明 vision(isVisionModel判定为 false),但它们实际上能理解图像内容。此前这类图片会被替换为占位文本,白白丢失信息;现在模型能收到真实图片。
  2. 真正无法处理图片的提供商:由提供商按自身行为忽略或拒绝图片,而非由 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.IMAGEnativeSupport.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 时抛出的正是文档提到的本地化错误NonVisionImageOcrErrori18nKey: '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')getCapabilityHandlerprepare,与后台文件处理任务共用同一套处理器注册表;
  • 同步直调:不走JobManager后台任务(聊天工具调用需要同步拿到文本),但远程处理器采用「启动 + 轮询」模式(REMOTE_POLL_INTERVAL_MS = 2_000REMOTE_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.aimodelId: 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 空/失败:分别断言抛出NonVisionImageOcrErrori18nKeyimage_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 提供商的桌面客户端

项目地址:https://gitcode.com/CherryHQ/cherry-studio
点击查看免费下载

相关推荐

上一篇:5分钟上手MLX-Audio:Apple Silicon本地语音合成跑起来
下一篇:Swift Algorithms分块算法完全解析:chunked方法的5种使用场景

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

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

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

立即咨询