From 759024bdff279e9cbd7edc799accc65dcbc2e424 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 11 Sep 2026 07:37:05 -0700 Subject: [PATCH] fix(deepseek): honor Flash 1M window leftovers and native vision Users on native DeepSeek were told to pin model.context_length and model.supports_vision in config.yaml. That is the wrong layer: the 1M window is already in DEFAULT_CONTEXT_LENGTHS, and a global supports_vision pin would also mark text-only deepseek-v4-pro as multimodal. Two catalog gaps still produced the reported symptoms: - A leftover context_length_cache.yaml entry of 128K (the old ``deepseek`` catch-all) outlived the 1M catalog keys because deepseek-flash was missing from _PRE_CATALOG_STALE_KEYS. - When models.dev is empty/cold, Flash has no capability record, so image routing falls through to lossy text. Vendor docs (2026-09-10) mark deepseek-flash as vision-capable and deepseek-v4-pro as not. Discard those 128K leftovers, fill Flash (and retired Flash aliases) via _BUILTIN_MODEL_METADATA, and leave Pro catalog-only. --- agent/model_metadata.py | 3 +++ agent/models_dev.py | 16 ++++++++++++++++ tests/agent/test_model_metadata.py | 7 +++++++ tests/agent/test_models_dev.py | 16 ++++++++++++++++ website/docs/user-guide/features/vision.md | 4 ++-- .../current/user-guide/features/vision.md | 4 ++-- 6 files changed, 46 insertions(+), 4 deletions(-) diff --git a/agent/model_metadata.py b/agent/model_metadata.py index e4b73e6777..99c5b558b2 100644 --- a/agent/model_metadata.py +++ b/agent/model_metadata.py @@ -1303,6 +1303,9 @@ _PRE_CATALOG_STALE_KEYS = frozenset({ "grok-4.3", "grok-4.6", # 1M / 500K; "grok-4" catch-all persisted 256,000 "grok-4-fast", "grok-4.20", # 2M; fell through to the 256K fallback "qwen3.6-plus", # 1M; "qwen" catch-all persisted 131,072 + # V4 / V4.1 Flash: 1M. Pre-entry builds matched the family catch-all and persisted 128K. + "deepseek-flash", "deepseek-v4.1-flash", "deepseek-v4-flash", "deepseek-v4-pro", + "deepseek-chat", "deepseek-reasoner", }) diff --git a/agent/models_dev.py b/agent/models_dev.py index c44e59d224..be32dd0861 100644 --- a/agent/models_dev.py +++ b/agent/models_dev.py @@ -525,6 +525,14 @@ _UNKNOWN_MODEL_BASE: Dict[str, Any] = {"limit": {"context": 200000, "output": 81 # Account-gated models may be usable before models.dev has indexed them. Keep # their capabilities available for an explicitly selected/discovered model # without adding them to any picker catalog. +_DEEPSEEK_FLASH_VISION: Dict[str, Any] = { + "limit": {"context": 1_000_000, "output": 384_000}, + "modalities": {"input": ["text", "image"], "output": ["text"]}, + "tool_call": True, + "reasoning": True, + "family": "deepseek-flash", +} + _BUILTIN_MODEL_METADATA: Dict[Tuple[str, str], Dict[str, Any]] = { ("openai", "gpt-6-astra"): { "limit": {"context": 1_050_000, "output": 128_000}, @@ -533,6 +541,14 @@ _BUILTIN_MODEL_METADATA: Dict[Tuple[str, str], Dict[str, Any]] = { "reasoning": True, "family": "gpt-6", }, + # Native DeepSeek V4.1-Flash is multimodal (https://api-docs.deepseek.com/guides/vision). + # models.dev lagged the 2026-09-10 rename; without this, a cold/empty cache treats + # ``deepseek-flash`` as unknown → image_input_mode falls through to lossy text. + # ``deepseek-v4-pro`` stays catalog-only: vendor docs still mark it text-only. + ("deepseek", "deepseek-flash"): _DEEPSEEK_FLASH_VISION, + ("deepseek", "deepseek-v4-flash"): _DEEPSEEK_FLASH_VISION, + ("deepseek", "deepseek-v4.1-flash"): _DEEPSEEK_FLASH_VISION, + ("deepseek", "deepseek-v4-flash-vision-exp"): _DEEPSEEK_FLASH_VISION, } diff --git a/tests/agent/test_model_metadata.py b/tests/agent/test_model_metadata.py index 63ccc1bfbd..546f1819b2 100644 --- a/tests/agent/test_model_metadata.py +++ b/tests/agent/test_model_metadata.py @@ -1720,6 +1720,13 @@ class TestGenericPreCatalogStaleGuard: assert not _stale_pre_catalog_cache_entry("grok-4.20", 2_000_000) # Sibling qwen slugs with legitimately small windows are untouched. assert not _stale_pre_catalog_cache_entry("qwen3-coder", 131_072) + # DeepSeek V4 / V4.1 Flash: 1M. Pre-entry builds persisted the 128K + # ``deepseek`` catch-all; a leftover must drop, a 1M value must not. + assert _stale_pre_catalog_cache_entry("deepseek-flash", 128_000) + assert _stale_pre_catalog_cache_entry("deepseek/deepseek-flash", 128_000) + assert _stale_pre_catalog_cache_entry("deepseek-v4-pro", 128_000) + assert not _stale_pre_catalog_cache_entry("deepseek-flash", 1_000_000) + assert not _stale_pre_catalog_cache_entry("deepseek", 128_000) def test_unknown_models_never_dropped(self): from agent.model_metadata import _stale_pre_catalog_cache_entry diff --git a/tests/agent/test_models_dev.py b/tests/agent/test_models_dev.py index 40fc1031e9..967e75c99a 100644 --- a/tests/agent/test_models_dev.py +++ b/tests/agent/test_models_dev.py @@ -836,6 +836,22 @@ class TestGetModelCapabilities: api_caps = get_model_capabilities("openai-api", "gpt-6-astra") assert api_caps == caps + def test_deepseek_flash_builtin_vision_fills_catalog_lag(self): + """Native Flash stays multimodal when models.dev is empty; Pro does not. + + Vendor docs: deepseek-flash accepts images, deepseek-v4-pro does not. + A global model.supports_vision pin would lie about Pro. + """ + with patch("agent.models_dev.fetch_models_dev", return_value={}): + flash = get_model_capabilities("deepseek", "deepseek-flash") + alias = get_model_capabilities("deepseek", "deepseek-v4-flash") + pro = get_model_capabilities("deepseek", "deepseek-v4-pro") + + assert flash is not None and flash.supports_vision is True + assert flash.context_window == 1_000_000 + assert alias is not None and alias.supports_vision is True + assert pro is None + # --------------------------------------------------------------------------- # Per-model metadata overrides (model_overrides config) diff --git a/website/docs/user-guide/features/vision.md b/website/docs/user-guide/features/vision.md index 44352af392..628c7ab69e 100644 --- a/website/docs/user-guide/features/vision.md +++ b/website/docs/user-guide/features/vision.md @@ -200,8 +200,8 @@ When a user attaches an image — from the CLI clipboard, the gateway (Telegram/ | Your model | What happens to the image | |---|---| -| **Vision-capable** (GPT-4V, Claude with vision, Gemini, Qwen-VL, MiMo-VL, etc.) | Sent as **real pixels** using the provider's native image content format above. No text summary layer. | -| **Text-only** (DeepSeek V3, smaller open-source models, older chat-only endpoints) | Routed through the `vision_analyze` auxiliary tool — an auxiliary vision model describes the image, and the text description is injected into the conversation. | +| **Vision-capable** (GPT-4V, Claude with vision, Gemini, Qwen-VL, MiMo-VL, DeepSeek Flash / V4.1-Flash, etc.) | Sent as **real pixels** using the provider's native image content format above. No text summary layer. | +| **Text-only** (DeepSeek V4 Pro, DeepSeek V3, smaller open-source models, older chat-only endpoints) | Routed through the `vision_analyze` auxiliary tool — an auxiliary vision model describes the image, and the text description is injected into the conversation. | You don't configure this — Hermes looks up your current model's capability in the provider metadata and picks the right path automatically. The practical effect: you can switch between vision and non-vision models mid-session and image handling "just works" without changing your workflow. Text-only models get coherent context about the image rather than a broken multimodal payload they'd have to reject. diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/vision.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/vision.md index 0262105848..61f96612b0 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/vision.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/vision.md @@ -196,8 +196,8 @@ powershell.exe -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; | 你的模型 | 图像处理方式 | |---|---| -| **支持视觉的模型**(GPT-4V、Claude with vision、Gemini、Qwen-VL、MiMo-VL 等) | 使用上述提供商原生图像内容格式,以**真实像素**发送。无文本摘要层。 | -| **纯文本模型**(DeepSeek V3、较小的开源模型、旧版纯对话端点) | 通过 `vision_analyze` 辅助工具路由——辅助视觉模型描述图像,文本描述注入对话。 | +| **支持视觉的模型**(GPT-4V、Claude with vision、Gemini、Qwen-VL、MiMo-VL、DeepSeek Flash / V4.1-Flash 等) | 使用上述提供商原生图像内容格式,以**真实像素**发送。无文本摘要层。 | +| **纯文本模型**(DeepSeek V4 Pro、DeepSeek V3、较小的开源模型、旧版纯对话端点) | 通过 `vision_analyze` 辅助工具路由——辅助视觉模型描述图像,文本描述注入对话。 | 无需手动配置——Hermes 在提供商元数据中查找当前模型的能力并自动选择正确路径。实际效果:你可以在会话中途切换视觉模型与非视觉模型,图像处理"开箱即用",无需更改工作流。纯文本模型会获得关于图像的连贯上下文,而不是一个会被拒绝的损坏多模态载荷。