fix(llm): stop assuming DeepSeek is vision-capable

This commit is contained in:
matevip 2026-06-29 14:48:44 +08:00
parent 07d6f01b56
commit 421fd3cd61
2 changed files with 23 additions and 13 deletions

View File

@ -97,9 +97,12 @@ public class ModelCapabilityService {
m.put("claude-haiku", EnumSet.of(Modality.VISION));
// ===== DeepSeek =====
// V4 (Apr 2026) is the first DeepSeek line with native multimodal image + video.
// V3 and earlier are text-only (no entry defaults to text only).
m.put("deepseek-v4", EnumSet.of(Modality.VISION, Modality.VIDEO));
// All released DeepSeek chat models (deepseek-chat / deepseek-reasoner /
// deepseek-v3.x) are text-only, so there is no entry and they default to
// text. Do NOT assume a future line is multimodal here: a wrong vision
// assumption makes the router send image_url to a text model, which the
// provider rejects with a 400. A genuinely multimodal model should declare
// its modalities on the model config instead.
// ===== ByteDance Doubao / Seed =====
// Seed 2.0 Pro (Feb 2026) handles hour-long videos. Seed1.5-VL also supports video.

View File

@ -98,17 +98,24 @@ class ModelCapabilityServiceTest {
}
@Test
@DisplayName("DeepSeek V4 / V4-Pro → VIDEO; V3 (text-only) gets nothing")
void deepseekV4_supportsVideo() {
// DeepSeek V4 (Apr 2026) introduced native multimodal incl. video to the line.
// V3 and earlier remain text-only and must NOT match the V4 entry.
assertTrue(service.supports("deepseek-v4", null, Modality.VIDEO));
assertTrue(service.supports("deepseek-v4-pro", null, Modality.VIDEO));
assertTrue(service.supports("deepseek-v4-flash", null, Modality.VIDEO));
assertFalse(service.supports("deepseek-v3", null, Modality.VIDEO),
"V3 must NOT inherit V4 capabilities — text-only base differs from V4 entirely");
@DisplayName("DeepSeek is text-only by default — no speculative vision entry (issue #288)")
void deepseek_textOnlyByDefault() {
// DeepSeek's released chat models (deepseek-chat / deepseek-reasoner /
// deepseek-v3.x) are text-only. A hardcoded deepseek-v4 vision/video entry
// made the router send image_url to a text model, which DeepSeek rejects with
// a 400 (and it never fell back to the vision sidecar). Default must be
// text-only; a genuinely multimodal model is opted in via the DB modalities
// declaration, not assumed here.
assertFalse(service.supports("deepseek-v4", null, Modality.VISION),
"deepseek must not be assumed vision-capable (issue #288)");
assertFalse(service.supports("deepseek-v4", null, Modality.VIDEO));
assertFalse(service.supports("deepseek-chat", null, Modality.VISION));
assertFalse(service.supports("deepseek-reasoner", null, Modality.VISION));
assertFalse(service.supports("deepseek-v3.2", null, Modality.VIDEO));
assertFalse(service.supports("deepseek-r1", null, Modality.VIDEO));
assertEquals(EnumSet.of(Modality.TEXT), service.resolve("deepseek-v4", null));
// A real multimodal model can still be declared explicitly via DB modalities.
assertTrue(service.supports("deepseek-v4", "[\"vision\"]", Modality.VISION),
"an explicit DB declaration must still grant vision when the model truly has it");
}
@Test