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.
This commit is contained in:
@@ -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",
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+2
-2
@@ -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 在提供商元数据中查找当前模型的能力并自动选择正确路径。实际效果:你可以在会话中途切换视觉模型与非视觉模型,图像处理"开箱即用",无需更改工作流。纯文本模型会获得关于图像的连贯上下文,而不是一个会被拒绝的损坏多模态载荷。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user