Skip to content

feat(core): one-shot modality probe for pattern-guessed models (/model action, phase 1) - #10335

Draft
jarvislee90s-dot wants to merge 7 commits into
QwenLM:mainfrom
jarvislee90s-dot:feat/modality-probe-phase1
Draft

feat(core): one-shot modality probe for pattern-guessed models (/model action, phase 1)#10335
jarvislee90s-dot wants to merge 7 commits into
QwenLM:mainfrom
jarvislee90s-dot:feat/modality-probe-phase1

Conversation

@jarvislee90s-dot

@jarvislee90s-dot jarvislee90s-dot commented Aug 28, 2026

Copy link
Copy Markdown

What this PR does

Implements phase 1 of #10309: an explicit one-shot image modality probe for models whose input modalities were guessed by the name-pattern table.

  • Probe module (packages/core/src/services/modalityProbe/): sends a single chat-completions request carrying a red 8×8 PNG to the model's own endpoint, deliberately bypassing the content pipeline (the converter's modality gate is exactly the belief under test). Three-state verdict: 200image; error text carrying modality semantics → text_only; auth / rate-limit / timeout / ambiguous errors → unknown (no conclusion, nothing persisted). The successful response's content is never inspected — reasoning models routinely return an empty content with text in reasoning_content/thinking even when the image was accepted. The error-dialect dictionary covers the four vendor dialects observed in the Feature: probe actual modality support at model setup (one-shot capability probe instead of static pattern guessing) #10309 validation matrix (DeepSeek/Ollama does not support image…, Zhipu's content-type enum validation, OpenRouter's router-level 404), with hints deliberately phrased with an object (not support image, 不支持图片) so region-availability errors abstain rather than misjudge.
  • Persistence: verdicts are stored under a new top-level probeResults settings key (authType|modelId|baseUrl{ verdict, probedAt }). unknown is never persisted; the write side guards against hand-corrupted stores; probe results never touch a model entry's generationConfig.modalities.
  • Resolution chain: explicit modalities (settings/modelProviders) > persisted probe result > name-pattern table, stamped as modalitiesSource (explicit / probe / pattern) on the resolved config. A comment marks the insertion point reserved for the Use API-backed model metadata for limits and capabilities #8558 API-backed catalog layer (final chain: explicit > provider-native catalog > probe > regex > conservative defaults).
  • UI: the /model dialog shows a provenance badge next to the modality value (manual / probe-tested / auto-detected) and offers a t — test image support action on pattern-sourced entries only (live settings read; hidden once a verdict concludes, retryable after unknown; never offered for user-configured entries).

Why it's needed

Fixes the recurring pattern-table race documented on #10309 (#4219, then #10270#10278 hours later): every newly shipped vision model needs a manual MODALITY_PATTERNS entry, and until one lands, users' images are silently replaced by a text placeholder. This PR is the staged first step the #10309 triage recommended ("explicit test-image-support action on pattern-guessed entries in /model"), submitted as a Draft to anchor the design discussion — wizard-time auto-probing, invalidation/re-probe policy, and offline behavior are deliberately out of scope pending that discussion.

Reviewer Test Plan

How to verify

cd packages/core && npx vitest run src/services/modalityProbe src/models/modelConfigResolver.test.ts src/models/modelRegistry.test.ts src/models/modelsConfig.test.ts; cd ../..
cd packages/cli && npx vitest run src/ui/components/ModelDialog.test.tsx; cd ../..

Coverage highlights: three-state classification (all four vendor dialects, region-error abstention EN/zh, 5xx abstention, network failure), storage round-trip + corrupted-store guards, and the resolution chain in both directions — an explicit declaration written after a probe verdict still wins at the resolver and in the dialog display.

Optional end-to-end (any OpenAI-compatible provider): configure a pattern-guessed vision model (e.g. deepseek-v4-flash-vision-exp without a modalities override), open /model, press t on the entry — the badge flips to probe-tested and the verdict lands in probeResults. The probe script from #10309 (gist) re-runs the four-endpoint validation with any subset of keys.

Evidence (Before & After)

User-visible TUI change; screenshots to be attached once CI renders the draft. Unit-level before/after is expressed by the tests above (N/A — no visual capture in this environment yet).

Tested on

OS Status
🍏 macOS ⚠️ not tested
🪟 Windows ✅ tested
🐧 Linux ⚠️ not tested

Environment (optional)

Local source build on Windows, Node ≥22. Scoped suites: probe 19, resolver 62, registry 82, modelsConfig 85, ModelDialog 73 — all green; typecheck / eslint / prettier clean on changed files; settings.schema.json regenerated (CI drift gate). Repo-wide preflight on this Windows machine has known environment-only failures (EPERM symlink, git-path classes — same set on base b90c2f0); none touch the changed files' suites.

Risk & Scope

  • Size note: ~2,010 insertions across 28 files — above the repo's ~1,200-line split guidance, hence this note. Composition: 51% tests; the probe UI logic is isolated in a dedicated hook (packages/cli/src/ui/hooks/use-image-support-probe.ts), so the ModelDialog diff is mostly rendering and keybinding. Before this draft is marked ready-for-review, the plan is to split it into two stacked PRs — core resolution chain (probe module + resolver + persistence + unit tests, ≈1,150 lines) and UI (hook + dialog + dialog tests, ≈970 lines) — each under the guideline.
  • Main risk or tradeoff: a wrong text_only verdict would be persisted with no phase-1 re-probe path — mitigated by the three-state protocol (uncertainty abstains), object-scoped dialect hints, verdict re-validation on read, and the explicit-declaration escape hatch that always overrides in both the runtime chain and the dialog.
  • Known boundaries (deliberate, for the design discussion): the probe verdict feeds the runtime resolution chain on the next model-providers reload (e.g. restart); the dialog already displays it live. Wiring the write to an in-session registry reload is the open option. QWEN_OAUTH routes are not probeable in phase 1; a baseUrl env override can key-diverge from the resolver path (documented in probe-store.ts).
  • Storage choice rationale (open question 1 on Feature: probe actual modality support at model setup (one-shot capability probe instead of static pattern guessing) #10309): a top-level probeResults map because settings.setValue cannot address nested array items and composite keys containing URLs are unsafe as dotted paths; alternatives (per-entry field, standalone cache file) noted for the discussion.
  • Not validated / out of scope: wizard auto-probe, invalidation/TTL, re-probe action, responses-API wire format, video/pdf probes, accepted-not-seen fourth state.
  • Breaking changes / migration notes: none — absent probeResults, behavior is byte-identical to current main (covered by the untouched existing suites).

Linked Issues

References #10309 (draft for the design discussion; not requesting merge yet). Complements #8558 — the catalog covers known provider+model pairs, the probe covers the catalog's blind spots (self-hosted, gateways, brand-new releases); the chain insertion point is marked in modelConfigResolver.ts.

中文说明

本 PR 的改动

实现 #10309一期:为模态来源是名称模式表猜测的模型提供显式一次性图片模态探测。

  • 探测模块packages/core/src/services/modalityProbe/):向模型自身端点发送一条携带红色 8×8 PNG 的 chat-completions 请求,刻意绕过内容管线(转换器的模态闸门正是被检验的对象)。三态判定:200image;报错文本含模态语义 → text_only;鉴权/限流/超时/含糊报错 → unknown(不下结论、不落盘)。绝不检查成功响应的内容——推理模型即使在图片被接受时也经常返回空 content、正文落在 reasoning_content/thinking。报错方言字典覆盖 Feature: probe actual modality support at model setup (one-shot capability probe instead of static pattern guessing) #10309 验证矩阵实测的四种厂商方言,且短语刻意带宾语(not support image不支持图片),使区域可用性类报错弃权而非误判。
  • 持久化:判定存入顶层新键 probeResultsauthType|modelId|baseUrl{ verdict, probedAt })。unknown 永不落盘;写侧对手改损坏的 store 设防;探测结论绝不触碰模型条目的 generationConfig.modalities
  • 解析链:显式 modalities > 已持久化探测结果 > 名称模式表,来源以 modalitiesSourceexplicit/probe/pattern)标注在解析结果上。注释标出为 Use API-backed model metadata for limits and capabilities #8558 目录层预留的插入位(终态链:显式 > 目录 > 探测 > 正则 > 保守默认)。
  • UI/model 对话框模态值旁显示来源徽标(manual / probe-tested / auto-detected),仅对 pattern 来源条目提供 t 测试图片支持动作(活读 settings;得出结论后隐藏,unknown 后可重试;用户手写条目永不显示)。

为什么需要此改动

修复 #10309 记录的反复出现的模式表竞速(#4219,随后 #10270#10278 数小时竞态):每个新发布的视觉模型都需要人工补 MODALITY_PATTERNS 条目,补上之前用户的图片被静默替换为文本占位符。本 PR 是 #10309 triage 建议的分期第一步("先做 /model 中对 pattern 猜测条目的显式测试动作"),以 Draft 形式提交作为设计讨论的锚点——向导自动探测、失效/重测策略、离线行为均刻意留待讨论。

审查者测试计划

验证方式

cd packages/core && npx vitest run src/services/modalityProbe src/models/modelConfigResolver.test.ts src/models/modelRegistry.test.ts src/models/modelsConfig.test.ts; cd ../..
cd packages/cli && npx vitest run src/ui/components/ModelDialog.test.tsx; cd ../..

覆盖要点:三态分类(四种厂商方言、中英文区域报错弃权、5xx 弃权、网络失败)、存取往返与损坏 store 守卫、以及双向优先级链——探测结论落盘之后再手写显式声明,在解析器与对话框显示两层都仍然获胜。

可选端到端(任一 OpenAI 兼容 provider):配置一个 pattern 猜测的视觉模型(如不带 modalities 覆盖的 deepseek-v4-flash-vision-exp),打开 /model,在条目上按 t —— 徽标变为 probe-tested、判定落入 probeResults#10309 的探测脚本(gist)可用任意子集 key 复跑四端点验证。

证据(修复前后)

用户可见的 TUI 变更;截图待 CI 渲染草稿后补。单测层面的前后对照由上述测试表达(本环境暂无视觉捕获,N/A)。

测试平台

操作系统 状态
macOS ⚠️ 未测试
Windows ✅ 已测试
Linux ⚠️ 未测试

环境(可选)

Windows 本地源码构建,Node ≥22。分范围套件:probe 19、resolver 62、registry 82、modelsConfig 85、ModelDialog 73——全绿;改动文件的 typecheck / eslint / prettier 干净;settings.schema.json 已重新生成(CI 漂移门禁)。本 Windows 机器的全仓 preflight 存在已知环境性失败(EPERM symlink、git 路径类——base b90c2f0 上同一集合);均不涉及改动文件的套件。

风险与范围

  • 体量说明:约 2,010 行新增、28 个文件——超过仓库约 1,200 行的拆分指引,特此说明。构成:51% 为测试;探测 UI 逻辑已隔离到独立 hook(packages/cli/src/ui/hooks/use-image-support-probe.ts),ModelDialog 的 diff 以渲染与键位绑定为主。本草稿标记 ready-for-review 前,计划拆为两个堆叠 PR——核心解析链(探测模块 + resolver + 持久化 + 单测,约 1,150 行)与 UI(hook + 对话框 + 对话框测试,约 970 行)——各自低于指引线。
  • 主要风险或取舍:错误的 text_only 判定会被持久化且一期无重测出口——由三态协议(不确定即弃权)、带宾语的方言短语、读侧 verdict 复验、以及永远获胜的显式声明补救出口共同缓解。
  • 已知边界(刻意保留,供设计讨论):探测判定在下次模型 provider 重载(如重启)时进入运行时解析链;对话框已活读显示。写入时联动会话内 registry 重载是待讨论的开放选项。QWEN_OAUTH 路由一期不可探测;baseUrl 环境覆盖可能与解析路径键分歧(已记录于 probe-store.ts)。
  • 存储选型说明(Feature: probe actual modality support at model setup (one-shot capability probe instead of static pattern guessing) #10309 开放问题 1):顶层 probeResults 映射——因 settings.setValue 无法寻址嵌套数组元素、含 URL 的组合键作 dotted path 不安全;备选(条目内字段、独立缓存文件)已列出供讨论。
  • 未验证 / 不在范围内:向导自动探测、失效/TTL、重测动作、responses API 线格式、video/pdf 探针、accepted-not-seen 第四态。
  • 破坏性变更 / 迁移说明:无——无 probeResults 时行为与当前 main 逐字节一致(由未改动的既有套件覆盖)。

关联 Issue

References #10309(设计讨论用草稿;暂不请求合并)。与 #8558 互补——目录覆盖已知 provider+model 对,探测覆盖目录盲区(自托管、网关、全新发布);链插入位已在 modelConfigResolver.ts 标注。

@jarvislee90s-dot
jarvislee90s-dot force-pushed the feat/modality-probe-phase1 branch from f443463 to 615b9d5 Compare August 30, 2026 09:21
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant