mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
feat(cli): add /model --vision for a fallback vision model (#5778)
* feat(cli): add /model --vision for a fallback vision model
Add `/model --vision <id>` and an interactive vision-model picker to
configure an image-capable model the vision bridge borrows when a
text-only main model receives an image, mirroring the `/model --fast`
pattern (flat `visionModel` setting + runtime `setVisionModel`). An
explicit vision model takes precedence over same-provider auto-select and
may cross providers; an unreachable pin falls back to auto-select rather
than firing the bridge at a model it can't reach.
Also fixes two vision-bridge issues surfaced while verifying #5126:
- The gate read stale modalities after a runtime model switch, so an
@image sent right after switching to a multimodal model could still
trigger the bridge. Refresh model-derived modalities on the qwen-oauth
hot-update paths (setModel default-model + handleModelChange) so the
gate reflects the current model.
- The transcription was appended after a now-empty "Content from <file>:"
header, leading the primary model to re-read the image file and ignore
the transcript. Stand the transcription in the image's slot and tell the
model the image cannot be read by a tool.
Closes #5597
* fix(cli): add vision model description translations
* feat(cli): warn on non-image-capable /model --vision pin; cover modality refresh
Self-review follow-ups on the /model --vision work:
- Warn (but still honor) when /model --vision or the picker pins a model that
isn't image-capable, reusing the vision bridge's isImageCapable so the
explicit and auto-select paths agree on what counts as vision-capable.
isImageCapable is now exported for the CLI to share.
- Exclude fastOnly models from --vision validation so it matches the
completion filter.
- Add a Config-level test asserting handleModelChange refreshes modalities on
the qwen-oauth hot path (the vision-bridge gate fix that lacked direct
coverage).
* fix(cli): translate the non-image-capable vision model warning
Add zh/zh-TW/en entries for the /model --vision picker warning shown when a pinned model isn't image-capable, matching the existing vision-string translations.
* fix(vision-bridge): stop showing the image transcription twice
The vision bridge printed the full transcription in its notice AND fed it to
the primary model, which then re-stated it — so the user read the same image
description twice (once in the notice box, once in the answer).
- The notice now shows only the egress-disclosure header; the transcription is
fed to the primary model and surfaced in its answer, so it is no longer
duplicated. Removes the now-unused transcript truncation / control-char
stripping helpers.
- The bridge model is instructed to transcribe/describe, not answer the user
request (the user's intent is carried as a focus hint, not a question), so its
output is context for the primary model rather than a second competing answer.
* fix(cli): register vision model i18n strings and cover /model --vision
- Register 'Vision Model' / 'Select Vision Model' in en/zh/zh-TW so the
vision picker and confirmation aren't English-only for non-English users
(mirrors the existing Voice Model entries).
- formatNonVisionModelWarning reuses the dialog's already-registered t()
key instead of a plain English literal, keeping both paths i18n-consistent.
- Add the missing bare '/model --vision' tests (interactive dialog +
non-interactive current-model report), mirroring the --voice coverage.
* refactor(vision-bridge): remove dead VisionBridgeResult.transcript field
The inline transcript display was dropped in 14de33333 (it duplicated the
description already fed to the primary model), leaving `transcript` set on
the result but never read in any production path. Remove the field and its
now-redundant test — the untrusted-wrapper assertion it carried is already
covered by 'converts images to an untrusted text block on success'.
* test(cli): cover vision-model branches; dedupe fast/vision helpers
Refactor (behavior-neutral):
- modelCommand.ts: fold identical formatUnavailableFast/VisionModelMessage
bodies into formatUnavailableAuxModelMessage(label, hint).
- ModelDialog.tsx: extract the fast/vision selection-key parsing into
encodeAuxModelSelector(); voice keeps using the model id directly.
Tests:
- config: malformed pin (catch branch) falls back instead of throwing;
authType-qualified visionModel binds to the matching provider only;
setVisionModel('')/undefined clears back to auto-select.
- modelCommand: authType-scoped vision rejection message.
- ModelDialog: non-image-capable pin emits the warning in history.
* test(cli): vision-mode coverage + register --vision completion i18n key
- i18n: register the `--vision` completion description key in en/zh/zh-TW
(was wrapped in t() with no entry, so non-English showed English).
- useModelCommand: test vision mode opens + suppresses fast, resets on close.
- modelCommand: test --vision rejects fastOnly/voiceOnly models.
Also corrects the stale "30-minute" wait-timer comment in
qwen-code-pr-review.yml (actual env timer is 10m; point to repo settings).
* fix(core): exclude primary model from explicit vision bridge selection
resolveVisionModelSelection lacked the m.id !== primary guard that the
auto-select path (selectVisionBridgeModel) already has, so pinning the
text-only primary via /model --vision would fire the bridge at a model
that cannot read images. Mirror the auto-select guard.
* chore(core): log vision bridge transcription for traceability
The transcription is no longer surfaced to the user or carried on the
result; log it at debug so a wrong primary-model answer can be traced to
a misread image vs a downstream hallucination.
* test: cover vision model helpers and vision-model dialog dispatch
Add direct unit tests for encodeAuxModelSelector (3 key shapes) and
isImageCapable (isVision/modalities/name-fallback), and a dispatch test
for the /model --vision dialog case mirroring voice-model.
* fix(cli): render vision bridge notice as a dim tip, drop doubled marker
The success/cancelled vision-bridge notice was added as MessageType.INFO
(primary-color `●` gutter) while its text also led with `🔎`, producing a
doubled `● 🔎` marker. Route success/cancelled notices to a new dim
`vision_notice` variant (secondary color, single `🔎` gutter glyph) and
strip the baked-in glyphs from the notice text so the renderer owns the
prefix. Failures keep the prominent red `✕` ERROR variant (also
de-duplicated).
* test(cli): assert VISION_NOTICE type for mid-turn vision bridge notice
* test(cli): cover vision_notice classification and rendering
* fix(vision): harden vision-bridge model selection + UX
Address review feedback on #5778:
- config: provider-aware isCurrentPrimaryModel() so a cross-provider namesake
(e.g. anthropic:shared-model vs an openai shared-model primary) stays an
eligible vision pin; resolveVisionModelSelection now also filters fast/voice-
only models (a settings.json pin bypasses the slash command) and warns on
every silent-drop path so a stale pin is debuggable.
- modelCommand/ModelDialog: reject pinning the current primary as the vision
model at set time (ignored at runtime) instead of persisting a dead pin.
- useModelCommand: vision mode suppresses voice so the dialog can't end up in
two specialized modes at once.
- HistoryItemDisplay: vision_notice gets 0 top margin like its notice peers.
- vision-bridge-service: log transcription metadata only (model + length),
never the raw text — screenshots carry tokens/PII and debug logs can be
shared in support bundles.
PR #5778.
* fix(vision): fail-closed bridge routing, ambiguity guards, i18n, settings, tests
Address the remaining #5778 review feedback:
- baseLlmClient/sideQuery/vision-bridge: add an opt-in `failClosed` to the
per-model resolve path; the vision bridge sets it so a missing cross-provider
credential fails the conversion instead of silently routing image payloads at
the text-only main generator (which BaseLlmClient otherwise falls back to).
- ModelDialog: reject a vision pin that maps to multiple same-provider endpoints
(the persisted authType:modelId can't disambiguate base URLs), mirroring the
voice-mode duplicate guard.
- modelCommand: wrap the non-interactive vision status message in t() with
en/zh/zh-TW locale entries.
- SettingsDialog: wire the Vision Model row into the sub-dialog dispatch so Enter
/ Right-Arrow open the picker (like fastModel).
- image-part-utils: document the multi-image drop / "don't read_file" contract.
- tests: fail-closed resolve paths, primary-guard fallback, vision initialIndex,
authType-qualified + malformed vision selectors, failure-path image removal.
PR #5778.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
This commit is contained in:
parent
22389247d5
commit
2583b5c7dd
40 changed files with 1758 additions and 194 deletions
4
.github/workflows/qwen-code-pr-review.yml
vendored
4
.github/workflows/qwen-code-pr-review.yml
vendored
|
|
@ -144,9 +144,9 @@ jobs:
|
|||
github.event.pull_request.state == 'open' &&
|
||||
!github.event.pull_request.draft &&
|
||||
needs.authorize.outputs.should_review == 'true'
|
||||
# Stays on hosted: the 30-minute environment wait timer would otherwise idle a self-hosted ECS slot for the whole wait (GitHub allocates the runner before evaluating the environment timer).
|
||||
# Stays on hosted: the environment wait timer would otherwise idle a self-hosted ECS slot for the whole wait (GitHub allocates the runner before evaluating the environment timer).
|
||||
runs-on: 'ubuntu-latest'
|
||||
# Configured in repo settings with a 30-minute wait timer.
|
||||
# Wait timer is configured in repo settings (Settings → Environments → qwen-pr-review-delay), currently 10 minutes.
|
||||
environment:
|
||||
name: 'qwen-pr-review-delay'
|
||||
deployment: false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue