Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions src/qwenpaw/providers/data/model_capabilities.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"catalog_version": "2026.07.29",
"published_at": "2026-07-29T00:00:00Z",
"catalog_version": "2026.08.24",
"published_at": "2026-08-24T00:00:00Z",
"capabilities": [
{
"provider_id": "modelscope",
Expand Down Expand Up @@ -571,6 +571,38 @@
"doc_url": "https://api-docs.deepseek.com/",
"note": "DeepSeek-V4 Pro reasoning model: no multimodal"
},
{
"provider_id": "siliconflow-cn",
"model_id": "deepseek-ai/DeepSeek-V4-Flash",
"expected_image": false,
"expected_video": false,
"doc_url": "https://www.siliconflow.cn/models",
"note": "DeepSeek-V4 Flash supports tools but not visual input"
},
{
"provider_id": "siliconflow-cn",
"model_id": "deepseek-ai/DeepSeek-V4-Pro",
"expected_image": false,
"expected_video": false,
"doc_url": "https://www.siliconflow.cn/models",
"note": "DeepSeek-V4 Pro supports tools but not visual input"
},
{
"provider_id": "siliconflow-intl",
"model_id": "deepseek-ai/DeepSeek-V4-Flash",
"expected_image": false,
"expected_video": false,
"doc_url": "https://www.siliconflow.cn/models",
"note": "DeepSeek-V4 Flash supports tools but not visual input"
},
{
"provider_id": "siliconflow-intl",
"model_id": "deepseek-ai/DeepSeek-V4-Pro",
"expected_image": false,
"expected_video": false,
"doc_url": "https://www.siliconflow.cn/models",
"note": "DeepSeek-V4 Pro supports tools but not visual input"
},
{
"provider_id": "gemini",
"model_id": "gemini-3.1-pro-preview",
Expand Down
6 changes: 5 additions & 1 deletion src/qwenpaw/providers/provider_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ def apply(
) -> None:
"""Apply expected capabilities without overwriting probe results."""
for provider in providers:
for model in provider.all_models():
candidates = [
*provider.all_models(),
*provider.discovered_models,
]
for model in candidates:
if model.probe_source == "probed":
continue
if not refresh and model.supports_multimodal is not None:
Expand Down
4 changes: 4 additions & 0 deletions src/qwenpaw/providers/provider_manager_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ProviderInfo,
)
from .provider_catalog import BUILTIN_PROVIDER_CATALOG_KEYS
from .provider_annotations import ProviderAnnotationService
from .provider_manager_host import ProviderManagerHost
from .provider_discovery import (
ProviderModelDiscoveryResult,
Expand Down Expand Up @@ -197,6 +198,9 @@ async def _save_discovery_snapshot(
candidate.discovered_models = [
model.model_copy(deep=True) for model in models or []
]
ProviderAnnotationService(self._capability_registry).apply(
[candidate],
)
candidate.models_last_synced_at = synced_at
candidate.models_last_sync_error = None
else:
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/providers/test_siliconflow_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import qwenpaw.providers.provider_manager as provider_manager_module
from qwenpaw.providers.openai_provider import OpenAIProvider
from qwenpaw.providers.provider import ModelInfo
from qwenpaw.providers.provider_manager import (
PROVIDER_SILICONFLOW_CN,
PROVIDER_SILICONFLOW_INTL,
Expand Down Expand Up @@ -72,6 +73,50 @@ def test_siliconflow_registered_in_provider_manager(
assert provider_intl.base_url == "https://api.siliconflow.com/v1"


@pytest.mark.asyncio
@pytest.mark.parametrize(
"provider_id",
["siliconflow-cn", "siliconflow-intl"],
)
async def test_discovered_deepseek_v4_models_are_text_only(
isolated_secret_dir,
monkeypatch,
provider_id,
) -> None:
"""Apply packaged capabilities during the discovery transaction."""
manager = ProviderManager()
provider = manager.get_provider(provider_id)
assert provider is not None

async def fetch_models(_self, timeout=5):
return [
ModelInfo(
id="deepseek-ai/DeepSeek-V4-Flash",
name="DeepSeek V4 Flash",
),
ModelInfo(
id="deepseek-ai/DeepSeek-V4-Pro",
name="DeepSeek V4 Pro",
),
]

monkeypatch.setattr(OpenAIProvider, "fetch_models", fetch_models)

result = await manager.discover_provider_models(provider_id)

assert result.success is True
for model_id in (
"deepseek-ai/DeepSeek-V4-Flash",
"deepseek-ai/DeepSeek-V4-Pro",
):
model = provider.get_discovered_model_info(model_id)
assert model is not None
assert model.supports_image is False
assert model.supports_video is False
assert model.supports_multimodal is False
assert model.probe_source == "documentation"


@pytest.mark.asyncio
async def test_siliconflow_check_connection_success(monkeypatch) -> None:
"""Siliconflow check_connection should delegate to OpenAI client."""
Expand Down
Loading