whosellm 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {whosellm-0.3.0 → whosellm-0.4.0}/.bumpversion.toml +1 -1
- {whosellm-0.3.0 → whosellm-0.4.0}/CHANGELOG.md +10 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/PKG-INFO +30 -1
- {whosellm-0.3.0 → whosellm-0.4.0}/README.md +29 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/docs/add_new_model_family.md +13 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/pyproject.toml +1 -1
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/e2e/test_anthropic.py +35 -1
- whosellm-0.4.0/tests/e2e/test_deepseek.py +151 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/e2e/test_openai.py +41 -1
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_anthropic.py +68 -2
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_deepseek.py +99 -0
- whosellm-0.4.0/tests/models/families/test_gpt6.py +166 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_media_count_limit_config.py +12 -2
- whosellm-0.4.0/tests/test_tool_result_surfaces.py +279 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_variant_priority_config.py +9 -6
- {whosellm-0.3.0 → whosellm-0.4.0}/uv.lock +1 -1
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/__init__.py +14 -2
- whosellm-0.4.0/whosellm/capabilities.py +238 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/alibaba.py +31 -1
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/anthropic.py +123 -1
- whosellm-0.4.0/whosellm/models/families/deepseek/deepseek_official.py +257 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/gemini.py +138 -1
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_3_5.py +4 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_4.py +4 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_4_1.py +10 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_4o.py +14 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_5.py +14 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_5_1.py +11 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_5_2.py +12 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_5_3.py +4 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_5_4.py +16 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_5_5.py +10 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_gpt_5_6.py +10 -0
- whosellm-0.4.0/whosellm/models/families/openai/openai_gpt_6.py +101 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_o1.py +10 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_o3.py +14 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/openai_o4.py +10 -0
- whosellm-0.4.0/whosellm/models/families/openai/surfaces.py +70 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/zhipu.py +49 -1
- whosellm-0.3.0/tests/models/families/test_gpt6.py +0 -70
- whosellm-0.3.0/whosellm/capabilities.py +0 -114
- whosellm-0.3.0/whosellm/models/families/deepseek/deepseek_official.py +0 -145
- whosellm-0.3.0/whosellm/models/families/openai/openai_gpt_6.py +0 -56
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/commands/interview.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/projects/-Users-jqq-PycharmProjects-llmeta/memory/MEMORY.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/projects/-Users-jqq-PycharmProjects-llmeta/memory/feedback_use_uv.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/settings.json +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/code-review/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/create-skill/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/e2e-metadata/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/evolve/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/fix-review/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/release/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/alibaba.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/anthropic.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/deepseek.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/gemini.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/openai.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/others.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/vidu.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/review-provider-model/providers/zhipu.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/update-provider-model/SKILL.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.claude/skills/update-provider-model/testing.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.coveragerc +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.github/workflows/publish.yml +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.github/workflows/tests.yml +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.gitignore +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.windsurf/workflows/addmodel.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.windsurf/workflows/arch.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/.windsurf/workflows/testllmeta.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/CLAUDE.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/LICENSE +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/docs/refactor_proposal.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/docs/spec_model_family_redesign.md +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/examples/advanced_usage.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/examples/basic_usage.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/mypy.ini +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/pytest.ini +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/ruff.toml +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/e2e/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/e2e/conftest.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/e2e/test_google.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/e2e/test_zhipu.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_deepseek_tencent.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_gemini.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm41v_thinking.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm45.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm45v.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm46.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm46v.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm5.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm53.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_glm5v_turbo.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_gpt3_5.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_gpt4_1.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_gpt4o.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_gpt5.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_o1.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_o3.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_o4.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_qwen.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_qwen3_vl_models.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_qwen_ollama.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/models/families/test_qwen_plus.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_auto_register.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_claude_4x_versions.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_llmeta.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_media_count_limit.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_model_version.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_provider.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_registry_merge.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/tests/test_specific_patterns.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/model_version.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/base.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/config.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/dynamic_enum.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/deepseek/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/deepseek/tencent.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/openai/__init__.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/others.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/families/vidu.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/patterns.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/models/registry.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/provider.py +0 -0
- {whosellm-0.3.0 → whosellm-0.4.0}/whosellm/py.typed +0 -0
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **工具结果多模态能力(TFROB-856)**:新增 `LLMApiSurface`(str 枚举:`claude_messages` / `openai_chat_completions` / `openai_responses` / `gemini` / `dashscope` / `glm` / `deepseek` / `deepseek_responses`)与 `ToolResultSurfaceCapability`(`modalities` / `max_inline_bytes` / `mime_types`)。`ModelCapabilities.tool_result_surfaces` 以 surface 为键登记工具结果的原生媒体能力:缺键 = 未声明,空元组 = 明确不支持。
|
|
9
|
+
- 新增文档上限字段:`max_pdf_size_mb`、`max_pdf_pages`、`max_document_size_mb`(预留)、`supported_document_mime_type`。`None` 表示未声明(未知),不代表“不限”;单文件与请求级上限分层登记。
|
|
10
|
+
- 新增单请求内联媒体总预算 `max_inline_media_bytes` 与文件类媒体合计上限 `max_inline_file_bytes`(媒体计数字段继续复用 `media_count_limit`)。
|
|
11
|
+
- 预留 `FileApiCapability` / `ModelCapabilities.file_api`,本版本不消费。
|
|
12
|
+
- 数据登记(官方文档核实 + 2026-10-29 抓取日期;未逐模型 API 实测的已显式标注):Claude 4.x/5 工具结果支持 image + PDF;OpenAI Chat Completions tool 消息 text-only、Responses `function_call_output` 支持 image/file;Gemini 3 系支持 image,Gemini 2.x 显式标为不支持;DeepSeek Chat Completions tool 消息 text-only,Responses `function_call_output` 支持 image,PDF `input_file` 经 2026-10-29 实测为静默不投递(登记为不支持)。DashScope / GLM 的官方 API 参考将 tool 消息 content 定义为字符串,登记为显式不支持(空元组)。
|
|
13
|
+
- 修正 OpenAI gpt-5.1 及更新视觉模型缺失的 `supports_pdf=True`(官方 PDF 解析要求视觉模型,gpt-4o 及更新)。
|
|
14
|
+
|
|
5
15
|
## [0.2.4] - Unreleased
|
|
6
16
|
|
|
7
17
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: whosellm
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A unified LLM model version and capability management library
|
|
5
5
|
Author-email: JQQ <jqq1716@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -146,6 +146,35 @@ print(model.capabilities.max_video_size_mb) # 20.0
|
|
|
146
146
|
validated_params = model.validate_params(your_params)
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
### 工具结果的多模态能力 / Multimodal Tool Results
|
|
150
|
+
|
|
151
|
+
工具结果能否原生承载图片/文档,取决于 **provider × API surface**,而不是模型名字前缀。能力数据以 `LLMApiSurface` 为键,缺键表示「未声明」,空元组表示「明确不支持」——两者都应按不支持处理。
|
|
152
|
+
|
|
153
|
+
Whether a tool result can natively carry images/documents depends on **provider × API surface**, not on a model-name prefix. Capability data is keyed by `LLMApiSurface`; a missing key means "undeclared" and an empty tuple means "explicitly unsupported" — treat both as unsupported.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from whosellm import LLMApiSurface, LLMeta
|
|
157
|
+
|
|
158
|
+
caps = LLMeta("claude-opus-5-5").capabilities
|
|
159
|
+
surface = caps.tool_result_surfaces.get(LLMApiSurface.CLAUDE_MESSAGES)
|
|
160
|
+
print(surface.modalities) # ("image", "document")
|
|
161
|
+
print(surface.supports("document")) # True
|
|
162
|
+
|
|
163
|
+
# Gemini 3 系支持;2.5 系明确不支持(会返回 400)
|
|
164
|
+
print(LLMeta("gemini-3.8-flash").capabilities.tool_result_surfaces[LLMApiSurface.GEMINI].modalities) # ("image",)
|
|
165
|
+
print(LLMeta("gemini-2.5-pro").capabilities.tool_result_surfaces[LLMApiSurface.GEMINI].modalities) # ()
|
|
166
|
+
|
|
167
|
+
# DeepSeek 的 Chat Completions 与 Responses 是两个 surface:
|
|
168
|
+
# Chat Completions tool 消息只能放文本;Responses 的 function_call_output 支持图片,
|
|
169
|
+
# 但 PDF input_file 实测被静默丢弃(2026-10-29 探针)
|
|
170
|
+
print(LLMeta("deepseek-flash").capabilities.tool_result_surfaces[LLMApiSurface.DEEPSEEK].modalities) # ()
|
|
171
|
+
print(LLMeta("deepseek-flash").capabilities.tool_result_surfaces[LLMApiSurface.DEEPSEEK_RESPONSES].modalities) # ("image",)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
用户消息能力仍由 `supports_vision` / `supports_audio` / `supports_video` / `supports_pdf` 表示,工具结果能力只走 `tool_result_surfaces`。文档上限见 `max_pdf_size_mb`、`max_pdf_pages`、`supported_document_mime_type`;单请求内联媒体预算见 `max_inline_media_bytes`,文件类媒体的请求级合计上限见 `max_inline_file_bytes`。
|
|
175
|
+
|
|
176
|
+
User-message capability remains on `supports_vision` / `supports_audio` / `supports_video` / `supports_pdf`; tool-result capability lives only in `tool_result_surfaces`. Document limits are exposed via `max_pdf_size_mb`, `max_pdf_pages`, and `supported_document_mime_type`; the per-request inline-media budget is `max_inline_media_bytes`, and the request-level combined cap for file-type media is `max_inline_file_bytes`.
|
|
177
|
+
|
|
149
178
|
### 非法名称也不会炸 / Lenient on Unknown Names
|
|
150
179
|
|
|
151
180
|
我们不想打断你的流程,即便名称不在我们的数据库里也能返回一个「未知模型占位符」。
|
|
@@ -121,6 +121,35 @@ print(model.capabilities.max_video_size_mb) # 20.0
|
|
|
121
121
|
validated_params = model.validate_params(your_params)
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
### 工具结果的多模态能力 / Multimodal Tool Results
|
|
125
|
+
|
|
126
|
+
工具结果能否原生承载图片/文档,取决于 **provider × API surface**,而不是模型名字前缀。能力数据以 `LLMApiSurface` 为键,缺键表示「未声明」,空元组表示「明确不支持」——两者都应按不支持处理。
|
|
127
|
+
|
|
128
|
+
Whether a tool result can natively carry images/documents depends on **provider × API surface**, not on a model-name prefix. Capability data is keyed by `LLMApiSurface`; a missing key means "undeclared" and an empty tuple means "explicitly unsupported" — treat both as unsupported.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from whosellm import LLMApiSurface, LLMeta
|
|
132
|
+
|
|
133
|
+
caps = LLMeta("claude-opus-5-5").capabilities
|
|
134
|
+
surface = caps.tool_result_surfaces.get(LLMApiSurface.CLAUDE_MESSAGES)
|
|
135
|
+
print(surface.modalities) # ("image", "document")
|
|
136
|
+
print(surface.supports("document")) # True
|
|
137
|
+
|
|
138
|
+
# Gemini 3 系支持;2.5 系明确不支持(会返回 400)
|
|
139
|
+
print(LLMeta("gemini-3.8-flash").capabilities.tool_result_surfaces[LLMApiSurface.GEMINI].modalities) # ("image",)
|
|
140
|
+
print(LLMeta("gemini-2.5-pro").capabilities.tool_result_surfaces[LLMApiSurface.GEMINI].modalities) # ()
|
|
141
|
+
|
|
142
|
+
# DeepSeek 的 Chat Completions 与 Responses 是两个 surface:
|
|
143
|
+
# Chat Completions tool 消息只能放文本;Responses 的 function_call_output 支持图片,
|
|
144
|
+
# 但 PDF input_file 实测被静默丢弃(2026-10-29 探针)
|
|
145
|
+
print(LLMeta("deepseek-flash").capabilities.tool_result_surfaces[LLMApiSurface.DEEPSEEK].modalities) # ()
|
|
146
|
+
print(LLMeta("deepseek-flash").capabilities.tool_result_surfaces[LLMApiSurface.DEEPSEEK_RESPONSES].modalities) # ("image",)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
用户消息能力仍由 `supports_vision` / `supports_audio` / `supports_video` / `supports_pdf` 表示,工具结果能力只走 `tool_result_surfaces`。文档上限见 `max_pdf_size_mb`、`max_pdf_pages`、`supported_document_mime_type`;单请求内联媒体预算见 `max_inline_media_bytes`,文件类媒体的请求级合计上限见 `max_inline_file_bytes`。
|
|
150
|
+
|
|
151
|
+
User-message capability remains on `supports_vision` / `supports_audio` / `supports_video` / `supports_pdf`; tool-result capability lives only in `tool_result_surfaces`. Document limits are exposed via `max_pdf_size_mb`, `max_pdf_pages`, and `supported_document_mime_type`; the per-request inline-media budget is `max_inline_media_bytes`, and the request-level combined cap for file-type media is `max_inline_file_bytes`.
|
|
152
|
+
|
|
124
153
|
### 非法名称也不会炸 / Lenient on Unknown Names
|
|
125
154
|
|
|
126
155
|
我们不想打断你的流程,即便名称不在我们的数据库里也能返回一个「未知模型占位符」。
|
|
@@ -197,6 +197,19 @@ __all__ = [
|
|
|
197
197
|
| `max_image_pixels` | `tuple[int, int] \| None` | 最大图片像素(宽, 高) / Max image pixels (width, height) |
|
|
198
198
|
| `max_video_size_mb` | `float \| None` | 最大视频大小(MB) / Max video size in MB |
|
|
199
199
|
| `max_video_duration_seconds` | `int \| None` | 最大视频时长(秒) / Max video duration in seconds |
|
|
200
|
+
| `media_count_limit` | `MediaCountLimit` | 单请求媒体数量上限 / Per-request media count caps |
|
|
201
|
+
| `max_inline_media_bytes` | `int \| None` | 单请求内联媒体字节总预算 / Per-request inline-media byte budget |
|
|
202
|
+
| `max_inline_file_bytes` | `int \| None` | 单请求文件类媒体字节合计上限 / Combined per-request file-type media byte cap |
|
|
203
|
+
| `max_pdf_size_mb` | `float \| None` | PDF 大小上限(MB) / Max PDF size in MB |
|
|
204
|
+
| `max_pdf_pages` | `int \| None` | PDF 页数上限 / Max PDF pages per request |
|
|
205
|
+
| `max_document_size_mb` | `float \| None` | 非 PDF 文档大小上限(MB,预留) / Max non-PDF document size |
|
|
206
|
+
| `supported_document_mime_type` | `list[str]` | 支持的文档 MIME / Supported document MIME types |
|
|
207
|
+
| `tool_result_surfaces` | `dict[LLMApiSurface, ToolResultSurfaceCapability]` | 工具结果按 API surface 的原生媒体能力;缺键 = 未声明,空元组 = 明确不支持 / Native tool-result media capability per surface; missing = undeclared, empty tuple = unsupported |
|
|
208
|
+
| `file_api` | `FileApiCapability \| None` | 文件 API 能力(预留)/ File API capability (reserved) |
|
|
209
|
+
|
|
210
|
+
工具结果能力是 **provider × API surface** 维度,而不是模型维度。新增模型时应按官方文档登记 `tool_result_surfaces`,并注明来源与抓取日期;未查证的 surface 请保持缺键(内核按不支持处理),不要乐观填写。
|
|
211
|
+
|
|
212
|
+
Tool-result capability is **provider × API surface**, not per-model. When adding models, register `tool_result_surfaces` from official docs with source and fetch date; leave unverified surfaces undeclared (consumers treat them as unsupported) instead of guessing.
|
|
200
213
|
|
|
201
214
|
---
|
|
202
215
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
来源: https://platform.claude.com/docs/en/docs/about-claude/models
|
|
4
4
|
采集日期: 2026-04-12(2026-09-04 增补 Fable 5.1 / Mythos 5.1)
|
|
5
|
+
2026-09-23 增补 Opus 5.5
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
import pytest
|
|
@@ -10,6 +11,33 @@ from whosellm import ModelFamily, Provider
|
|
|
10
11
|
|
|
11
12
|
from .conftest import assert_model_metadata
|
|
12
13
|
|
|
14
|
+
# ============================================================================
|
|
15
|
+
# Claude Family — Version 5.5 (2026-09-22 发布 / Released 2026-09-22)
|
|
16
|
+
# 来源: https://platform.claude.com/docs/en/release-notes/overview
|
|
17
|
+
# ============================================================================
|
|
18
|
+
|
|
19
|
+
CLAUDE_55_MODELS = [
|
|
20
|
+
(
|
|
21
|
+
"claude-opus-5-5",
|
|
22
|
+
{
|
|
23
|
+
# 1M ctx (默认且最大) / 128K out;adaptive thinking 常开且不可关闭
|
|
24
|
+
# 1M ctx (default & max) / 128K out; adaptive thinking always on, cannot be disabled
|
|
25
|
+
"provider": Provider.ANTHROPIC,
|
|
26
|
+
"family": ModelFamily.CLAUDE,
|
|
27
|
+
"version": "5.5",
|
|
28
|
+
"variant": "opus",
|
|
29
|
+
"supports_thinking": True,
|
|
30
|
+
"supports_vision": True,
|
|
31
|
+
"supports_streaming": True,
|
|
32
|
+
"supports_function_calling": True,
|
|
33
|
+
"supports_structured_outputs": True,
|
|
34
|
+
"supports_computer_use": True,
|
|
35
|
+
"context_window": 1_000_000,
|
|
36
|
+
"max_tokens": 128_000,
|
|
37
|
+
},
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
13
41
|
# ============================================================================
|
|
14
42
|
# Claude Family — Version 5.1 (2026-09-01 发布 / Released 2026-09-01)
|
|
15
43
|
# 来源: https://platform.claude.com/docs/en/models/fable-5-1/overview
|
|
@@ -234,7 +262,13 @@ CLAUDE_30_MODELS = [
|
|
|
234
262
|
# ============================================================================
|
|
235
263
|
|
|
236
264
|
ALL_MODELS = (
|
|
237
|
-
|
|
265
|
+
CLAUDE_55_MODELS
|
|
266
|
+
+ CLAUDE_51_MODELS
|
|
267
|
+
+ CLAUDE_LATEST_MODELS
|
|
268
|
+
+ CLAUDE_45_MODELS
|
|
269
|
+
+ CLAUDE_41_MODELS
|
|
270
|
+
+ CLAUDE_40_MODELS
|
|
271
|
+
+ CLAUDE_30_MODELS
|
|
238
272
|
)
|
|
239
273
|
|
|
240
274
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# filename: test_deepseek.py
|
|
2
|
+
"""
|
|
3
|
+
DeepSeek 官方模型元数据 E2E 测试 / DeepSeek official model metadata E2E tests.
|
|
4
|
+
|
|
5
|
+
来源 / Sources:
|
|
6
|
+
https://api-docs.deepseek.com/zh-cn/updates/(2026-09-10 DeepSeek-V4.1-Flash 发布)
|
|
7
|
+
https://api-docs.deepseek.com/zh-cn/quick_start/pricing
|
|
8
|
+
https://api-docs.deepseek.com/zh-cn/guides/vision
|
|
9
|
+
采集日期 / Collected: 2026-09-28(含 api.deepseek.com 实探复核 / verified against the live API)
|
|
10
|
+
|
|
11
|
+
约定 / Conventions:
|
|
12
|
+
* 旧名(deepseek-v4-flash / -vision-exp / deepseek-chat / deepseek-reasoner)版本仍按名字解析为
|
|
13
|
+
4.0,能力按当前实际服务模型(转发到 DeepSeek-V4.1-Flash)标注。
|
|
14
|
+
Legacy names keep their name-derived version (4.0) while capabilities follow the
|
|
15
|
+
currently serving model (routed to DeepSeek-V4.1-Flash).
|
|
16
|
+
* deepseek-v4-pro 未被路由,保持纯文本(无图像输入)。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from whosellm import ModelFamily, Provider
|
|
22
|
+
|
|
23
|
+
from .conftest import assert_model_metadata
|
|
24
|
+
|
|
25
|
+
# ============================================================================
|
|
26
|
+
# DeepSeek V4.1-Flash 正式名(2026-09-10 发布)/ Canonical name of DeepSeek-V4.1-Flash
|
|
27
|
+
# 官方:"将模型名称更改为 deepseek-flash 即可调用最新的 V4.1 Flash 模型"
|
|
28
|
+
# ============================================================================
|
|
29
|
+
|
|
30
|
+
V41_FLASH_MODELS = [
|
|
31
|
+
(
|
|
32
|
+
"deepseek-flash",
|
|
33
|
+
{
|
|
34
|
+
"provider": Provider.DEEPSEEK,
|
|
35
|
+
"family": ModelFamily.DEEPSEEK,
|
|
36
|
+
# 名字无版本号 → 按官方模型版本 DeepSeek-V4.1-Flash 标注
|
|
37
|
+
"version": "4.1",
|
|
38
|
+
"variant": "flash",
|
|
39
|
+
"supports_thinking": True, # 默认开启,effort 默认 high
|
|
40
|
+
"supports_vision": True, # 原生多模态视觉理解
|
|
41
|
+
"supports_function_calling": True,
|
|
42
|
+
"supports_streaming": True,
|
|
43
|
+
"supports_json_outputs": True,
|
|
44
|
+
"supports_structured_outputs": False, # 仅 json_object,无 json_schema
|
|
45
|
+
"context_window": 1_000_000,
|
|
46
|
+
"max_tokens": 384_000,
|
|
47
|
+
},
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# ============================================================================
|
|
52
|
+
# 兼容路由旧名(官方措辞"暂时路由")/ Routed legacy names ("temporarily routed" per vendor)
|
|
53
|
+
# ============================================================================
|
|
54
|
+
|
|
55
|
+
ROUTED_LEGACY_MODELS = [
|
|
56
|
+
(
|
|
57
|
+
"deepseek-v4-flash",
|
|
58
|
+
{
|
|
59
|
+
"provider": Provider.DEEPSEEK,
|
|
60
|
+
"family": ModelFamily.DEEPSEEK,
|
|
61
|
+
"version": "4.0", # 版本轴按名字 / version parses from the name
|
|
62
|
+
"variant": "flash",
|
|
63
|
+
"supports_thinking": True,
|
|
64
|
+
"supports_vision": True, # 能力轴跟实际服务模型 / capabilities follow the serving model
|
|
65
|
+
"supports_function_calling": True,
|
|
66
|
+
"supports_streaming": True,
|
|
67
|
+
"context_window": 1_000_000,
|
|
68
|
+
"max_tokens": 384_000,
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
(
|
|
72
|
+
"deepseek-v4-flash-vision-exp",
|
|
73
|
+
{
|
|
74
|
+
"provider": Provider.DEEPSEEK,
|
|
75
|
+
"family": ModelFamily.DEEPSEEK,
|
|
76
|
+
"version": "4.0",
|
|
77
|
+
"variant": "flash-vision-exp",
|
|
78
|
+
"supports_thinking": True,
|
|
79
|
+
"supports_vision": True,
|
|
80
|
+
"context_window": 1_000_000,
|
|
81
|
+
"max_tokens": 384_000,
|
|
82
|
+
},
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
"deepseek-chat",
|
|
86
|
+
{
|
|
87
|
+
"provider": Provider.DEEPSEEK,
|
|
88
|
+
"family": ModelFamily.DEEPSEEK,
|
|
89
|
+
"version": "4.0",
|
|
90
|
+
"variant": "chat",
|
|
91
|
+
"supports_thinking": False, # 别名默认非思考模式 / alias defaults to non-thinking
|
|
92
|
+
"supports_vision": True,
|
|
93
|
+
"context_window": 1_000_000,
|
|
94
|
+
"max_tokens": 384_000,
|
|
95
|
+
},
|
|
96
|
+
),
|
|
97
|
+
(
|
|
98
|
+
"deepseek-reasoner",
|
|
99
|
+
{
|
|
100
|
+
"provider": Provider.DEEPSEEK,
|
|
101
|
+
"family": ModelFamily.DEEPSEEK,
|
|
102
|
+
"version": "4.0",
|
|
103
|
+
"variant": "reasoner",
|
|
104
|
+
"supports_thinking": True, # 别名默认思考模式 / alias defaults to thinking
|
|
105
|
+
"supports_vision": True,
|
|
106
|
+
"context_window": 1_000_000,
|
|
107
|
+
"max_tokens": 384_000,
|
|
108
|
+
},
|
|
109
|
+
),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# ============================================================================
|
|
113
|
+
# 未被路由:deepseek-v4-pro 仍是 DeepSeek-V4-Pro-0813,纯文本
|
|
114
|
+
# Not routed: deepseek-v4-pro is still DeepSeek-V4-Pro-0813, text-only
|
|
115
|
+
# 实探 2026-09-28:带图不报错但静默忽略并编造答案 → supports_vision 必须为 False
|
|
116
|
+
# ============================================================================
|
|
117
|
+
|
|
118
|
+
TEXT_ONLY_MODELS = [
|
|
119
|
+
(
|
|
120
|
+
"deepseek-v4-pro",
|
|
121
|
+
{
|
|
122
|
+
"provider": Provider.DEEPSEEK,
|
|
123
|
+
"family": ModelFamily.DEEPSEEK,
|
|
124
|
+
"version": "4.0",
|
|
125
|
+
"variant": "pro",
|
|
126
|
+
"supports_thinking": True,
|
|
127
|
+
"supports_vision": False,
|
|
128
|
+
"supports_function_calling": True,
|
|
129
|
+
"supports_streaming": True,
|
|
130
|
+
"context_window": 1_000_000,
|
|
131
|
+
"max_tokens": 384_000,
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
# ============================================================================
|
|
137
|
+
# 聚合 + 参数化 / Aggregation & parametrization
|
|
138
|
+
# ============================================================================
|
|
139
|
+
|
|
140
|
+
ALL_MODELS = V41_FLASH_MODELS + ROUTED_LEGACY_MODELS + TEXT_ONLY_MODELS
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.mark.e2e
|
|
144
|
+
@pytest.mark.parametrize(
|
|
145
|
+
"model_name,expected",
|
|
146
|
+
ALL_MODELS,
|
|
147
|
+
ids=[m[0] for m in ALL_MODELS],
|
|
148
|
+
)
|
|
149
|
+
def test_model_metadata(model_name: str, expected: dict) -> None: # type: ignore[type-arg]
|
|
150
|
+
"""验证 DeepSeek 模型元数据与官方文档一致。"""
|
|
151
|
+
assert_model_metadata(model_name, expected)
|
|
@@ -15,6 +15,7 @@ from .conftest import assert_model_metadata
|
|
|
15
15
|
# 来源: https://developers.openai.com/api/docs/models/gpt-5.4(2026-04-12 采集)
|
|
16
16
|
# https://developers.openai.com/api/docs/models/gpt-5.6-sol(2026-08-20 采集 5.5/5.6)
|
|
17
17
|
# https://developers.openai.com/api/docs/models/gpt-6-astra(2026-09-04 采集 6-astra/5.6-cyber)
|
|
18
|
+
# https://developers.openai.com/api/docs/models/gpt-6-sol(2026-09-23 采集 6-sol/6-luna)
|
|
18
19
|
# ============================================================================
|
|
19
20
|
|
|
20
21
|
GPT_MODELS = [
|
|
@@ -219,7 +220,7 @@ GPT_MODELS = [
|
|
|
219
220
|
},
|
|
220
221
|
),
|
|
221
222
|
# ------------------------------------------------------------------
|
|
222
|
-
# GPT-6
|
|
223
|
+
# GPT-6 天体档位系列(2026-09-04 采集 astra)
|
|
223
224
|
# 来源: https://developers.openai.com/api/docs/models/gpt-6-astra
|
|
224
225
|
# ------------------------------------------------------------------
|
|
225
226
|
(
|
|
@@ -239,6 +240,45 @@ GPT_MODELS = [
|
|
|
239
240
|
"max_tokens": 128_000,
|
|
240
241
|
},
|
|
241
242
|
),
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
# GPT-6 Sol / Luna(2026-09-22 发布,2026-09-23 采集)
|
|
245
|
+
# 与 astra 同窗口同工具面;sol 中档主力、luna 低成本档,GPT-6 无 terra 档
|
|
246
|
+
# 来源: https://developers.openai.com/api/docs/models/gpt-6-sol
|
|
247
|
+
# ------------------------------------------------------------------
|
|
248
|
+
(
|
|
249
|
+
"gpt-6-sol",
|
|
250
|
+
{
|
|
251
|
+
"provider": Provider.OPENAI,
|
|
252
|
+
"family": ModelFamily.GPT,
|
|
253
|
+
"version": "6.0",
|
|
254
|
+
"variant": "sol",
|
|
255
|
+
"supports_thinking": True,
|
|
256
|
+
"supports_vision": True,
|
|
257
|
+
"supports_streaming": True,
|
|
258
|
+
"supports_function_calling": True,
|
|
259
|
+
"supports_structured_outputs": True,
|
|
260
|
+
"supports_computer_use": True,
|
|
261
|
+
"context_window": 1_050_000,
|
|
262
|
+
"max_tokens": 128_000,
|
|
263
|
+
},
|
|
264
|
+
),
|
|
265
|
+
(
|
|
266
|
+
"gpt-6-luna",
|
|
267
|
+
{
|
|
268
|
+
"provider": Provider.OPENAI,
|
|
269
|
+
"family": ModelFamily.GPT,
|
|
270
|
+
"version": "6.0",
|
|
271
|
+
"variant": "luna",
|
|
272
|
+
"supports_thinking": True,
|
|
273
|
+
"supports_vision": True,
|
|
274
|
+
"supports_streaming": True,
|
|
275
|
+
"supports_function_calling": True,
|
|
276
|
+
"supports_structured_outputs": True,
|
|
277
|
+
"supports_computer_use": True,
|
|
278
|
+
"context_window": 1_050_000,
|
|
279
|
+
"max_tokens": 128_000,
|
|
280
|
+
},
|
|
281
|
+
),
|
|
242
282
|
]
|
|
243
283
|
|
|
244
284
|
# ============================================================================
|
|
@@ -253,7 +253,7 @@ class TestClaudeMythosClassOrdering:
|
|
|
253
253
|
assert LLMeta("claude-mythos-5") > LLMeta("claude-fable-5")
|
|
254
254
|
|
|
255
255
|
def test_mythos5_is_top(self):
|
|
256
|
-
"""mythos-5
|
|
256
|
+
"""mythos-5 在所列模型中最高 / mythos-5 is the highest among the models listed"""
|
|
257
257
|
models = [
|
|
258
258
|
LLMeta("claude-mythos-5"),
|
|
259
259
|
LLMeta("claude-fable-5"),
|
|
@@ -264,6 +264,57 @@ class TestClaudeMythosClassOrdering:
|
|
|
264
264
|
assert max(models).variant == "mythos"
|
|
265
265
|
|
|
266
266
|
|
|
267
|
+
class TestClaudeOpus55:
|
|
268
|
+
"""Claude Opus 5.5 测试(2026-09-22 发布,当前最新) / Claude Opus 5.5 tests (released 2026-09-22)"""
|
|
269
|
+
|
|
270
|
+
def test_specific_model_config(self):
|
|
271
|
+
"""验证 claude-opus-5-5 配置 / Validate claude-opus-5-5 config"""
|
|
272
|
+
config = get_specific_model_config("claude-opus-5-5")
|
|
273
|
+
assert config is not None
|
|
274
|
+
version, variant, capabilities = config
|
|
275
|
+
assert version == "5.5"
|
|
276
|
+
assert variant == "opus"
|
|
277
|
+
assert capabilities is not None
|
|
278
|
+
assert capabilities.supports_vision is True
|
|
279
|
+
assert capabilities.supports_thinking is True # 自适应思考常开且不可关闭 / always on, cannot be disabled
|
|
280
|
+
assert capabilities.supports_function_calling is True
|
|
281
|
+
assert capabilities.supports_streaming is True
|
|
282
|
+
assert capabilities.supports_structured_outputs is True
|
|
283
|
+
assert capabilities.supports_computer_use is True
|
|
284
|
+
assert capabilities.max_tokens == 128000
|
|
285
|
+
assert capabilities.context_window == 1000000
|
|
286
|
+
|
|
287
|
+
def test_pattern_match(self):
|
|
288
|
+
"""验证 claude-opus-5-5 模式匹配 / Validate claude-opus-5-5 pattern match"""
|
|
289
|
+
matched = match_model_pattern("claude-opus-5-5")
|
|
290
|
+
assert matched is not None
|
|
291
|
+
assert matched["family"] == ModelFamily.CLAUDE
|
|
292
|
+
assert matched["variant"] == "opus"
|
|
293
|
+
assert matched["provider"] == Provider.ANTHROPIC
|
|
294
|
+
assert matched["_from_specific_model"] == "claude-opus-5-5"
|
|
295
|
+
|
|
296
|
+
@pytest.mark.parametrize("model_name", ["claude-opus-5-5-20260921", "claude-opus-5-5@20260921"])
|
|
297
|
+
def test_pattern_with_snapshot(self, model_name: str):
|
|
298
|
+
"""验证带 snapshot 的解析(- 与 @ 两种格式,版本号不被吞) / Validate snapshot forms keep version 5.5"""
|
|
299
|
+
meta = LLMeta(model_name)
|
|
300
|
+
assert meta.family == ModelFamily.CLAUDE
|
|
301
|
+
assert meta.version == "5.5"
|
|
302
|
+
assert meta.variant == "opus"
|
|
303
|
+
|
|
304
|
+
def test_not_swallowed_by_opus5(self):
|
|
305
|
+
"""claude-opus-5-5 不得被 claude-opus-5 的配置吞掉 / opus-5-5 must not be captured by opus-5
|
|
306
|
+
|
|
307
|
+
回归保护:opus-5 的子模式在 opus-5-5 之前注册,若 snapshot 宽度匹配不当会退化为 5.0
|
|
308
|
+
Regression guard: opus-5's sub-patterns register before opus-5-5's; a loose snapshot
|
|
309
|
+
width would silently degrade opus-5-5 to version 5.0
|
|
310
|
+
"""
|
|
311
|
+
meta = LLMeta("claude-opus-5-5")
|
|
312
|
+
assert meta.version == "5.5"
|
|
313
|
+
assert meta.variant == "opus"
|
|
314
|
+
assert meta.capabilities.context_window == 1000000
|
|
315
|
+
assert meta.capabilities.max_tokens == 128000
|
|
316
|
+
|
|
317
|
+
|
|
267
318
|
class TestClaudeOpus5:
|
|
268
319
|
"""Claude Opus 5 测试(2026-07-24 GA) / Claude Opus 5 tests"""
|
|
269
320
|
|
|
@@ -345,6 +396,19 @@ class TestClaude5Ordering:
|
|
|
345
396
|
"""opus-5 (v5.0) 高于 opus-4-8 (v4.8) / opus-5 outranks opus-4-8 by version"""
|
|
346
397
|
assert LLMeta("claude-opus-5") > LLMeta("claude-opus-4-8")
|
|
347
398
|
|
|
399
|
+
def test_opus55_outranks_opus5(self):
|
|
400
|
+
"""opus-5-5 (v5.5) 高于 opus-5 (v5.0) / opus-5-5 outranks opus-5 by version"""
|
|
401
|
+
assert LLMeta("claude-opus-5-5") > LLMeta("claude-opus-5")
|
|
402
|
+
assert LLMeta("claude-opus-5-5") > LLMeta("claude-fable-5-1")
|
|
403
|
+
|
|
404
|
+
def test_opus55_outranks_mythos5_by_version(self):
|
|
405
|
+
"""版本优先于档位:opus-5-5 (v5.5) 高于 mythos-5 (v5.0),尽管 mythos 变体档更高
|
|
406
|
+
Version precedes tier: opus-5-5 outranks mythos-5 despite mythos's higher variant tier
|
|
407
|
+
"""
|
|
408
|
+
assert LLMeta("claude-opus-5-5") > LLMeta("claude-mythos-5")
|
|
409
|
+
# 同版本 5.0 内档位排序不受影响 / tier ordering within v5.0 is unaffected
|
|
410
|
+
assert LLMeta("claude-mythos-5") > LLMeta("claude-fable-5")
|
|
411
|
+
|
|
348
412
|
def test_sonnet5_outranks_sonnet46(self):
|
|
349
413
|
"""sonnet-5 (v5.0) 高于 sonnet-4-6 (v4.6) / sonnet-5 outranks sonnet-4-6 by version"""
|
|
350
414
|
assert LLMeta("claude-sonnet-5") > LLMeta("claude-sonnet-4-6")
|
|
@@ -358,7 +422,9 @@ class TestClaude5Ordering:
|
|
|
358
422
|
assert LLMeta("claude-fable-5") > LLMeta("claude-opus-5")
|
|
359
423
|
|
|
360
424
|
def test_mythos5_is_top(self):
|
|
361
|
-
"""
|
|
425
|
+
"""同代 v5.0 内 mythos 档最高(v5.1/v5.5 按版本另行比较)
|
|
426
|
+
mythos is the top tier within the v5.0 generation (v5.1/v5.5 compare by version)
|
|
427
|
+
"""
|
|
362
428
|
models = [
|
|
363
429
|
LLMeta("claude-mythos-5"),
|
|
364
430
|
LLMeta("claude-fable-5"),
|
|
@@ -209,3 +209,102 @@ def test_deepseek_v4_flash_vision_exp_llmeta() -> None:
|
|
|
209
209
|
assert model.variant == "flash-vision-exp"
|
|
210
210
|
assert model.capabilities.context_window == 1_000_000
|
|
211
211
|
assert model.capabilities.supports_vision is True
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TestDeepSeekRouting:
|
|
215
|
+
"""DeepSeek 旧模型名的路由实况 / Routing reality of DeepSeek legacy names.
|
|
216
|
+
|
|
217
|
+
官方原文(2026-09-10 changelog):"旧版本模型 V4 Flash 与 V4 Flash Vision Exp 现已下线,
|
|
218
|
+
出于兼容考虑,模型名 deepseek-v4-flash、deepseek-v4-flash-vision-exp 将被暂时路由到 V4.1 Flash。"
|
|
219
|
+
Official: legacy V4 Flash / V4 Flash Vision Exp are offline; their model names are
|
|
220
|
+
*temporarily* routed to V4.1 Flash.
|
|
221
|
+
|
|
222
|
+
实探证据(2026-09-28,api.deepseek.com,见 /tmp/probe_deepseek.py):
|
|
223
|
+
- GET /models 仅返回 deepseek-flash 与 deepseek-v4-pro
|
|
224
|
+
- deepseek-v4-flash / deepseek-v4-flash-vision-exp / deepseek-chat / deepseek-reasoner
|
|
225
|
+
的响应 model 字段均回显 deepseek-flash,且能正确读出白底红方块
|
|
226
|
+
- deepseek-v4-pro 回显自身,带图不报错但静默忽略并编造答案
|
|
227
|
+
|
|
228
|
+
⚠️ 路由是"暂时"的:官方撤销后本类测试会失败,届时需重新探针并回改配置
|
|
229
|
+
(能力轴跟端点现状,版本轴跟名字血统)。/ The routing is temporary: when the vendor
|
|
230
|
+
removes it these tests fail on purpose, prompting a re-probe and a config rollback.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def test_deepseek_flash_canonical_name(self) -> None:
|
|
234
|
+
"""deepseek-flash 为官方正式名(无版本号),对应 DeepSeek-V4.1-Flash"""
|
|
235
|
+
from whosellm import LLMeta
|
|
236
|
+
|
|
237
|
+
model = LLMeta("deepseek-flash")
|
|
238
|
+
assert model.provider == Provider.DEEPSEEK
|
|
239
|
+
assert model.family == ModelFamily.DEEPSEEK
|
|
240
|
+
# 名字无版本号 → 按官方模型版本标注 4.1
|
|
241
|
+
assert model.version == "4.1"
|
|
242
|
+
assert model.variant == "flash"
|
|
243
|
+
|
|
244
|
+
caps = model.capabilities
|
|
245
|
+
assert caps.supports_vision is True
|
|
246
|
+
assert caps.supports_thinking is True # 默认开启 / on by default
|
|
247
|
+
assert caps.media_count_limit.get("image") == 600
|
|
248
|
+
assert caps.context_window == 1_000_000
|
|
249
|
+
assert caps.max_tokens == 384_000
|
|
250
|
+
|
|
251
|
+
def test_v4_flash_routed_to_v41_flash(self) -> None:
|
|
252
|
+
"""deepseek-v4-flash 已被路由到 V4.1 Flash,视觉能力随之可用"""
|
|
253
|
+
from whosellm import LLMeta
|
|
254
|
+
|
|
255
|
+
model = LLMeta("deepseek-v4-flash")
|
|
256
|
+
# 版本轴仍按名字解析为 4.0 / version still parses from the name (4.0)
|
|
257
|
+
assert model.version == "4.0"
|
|
258
|
+
assert model.variant == "flash"
|
|
259
|
+
# 能力轴跟随实际服务模型 / capabilities follow the serving model
|
|
260
|
+
assert model.capabilities.supports_vision is True
|
|
261
|
+
assert model.capabilities.media_count_limit.get("image") == 600
|
|
262
|
+
|
|
263
|
+
def test_vision_exp_routed_to_v41_flash(self) -> None:
|
|
264
|
+
"""deepseek-v4-flash-vision-exp 同样被路由到 V4.1 Flash"""
|
|
265
|
+
from whosellm import LLMeta
|
|
266
|
+
|
|
267
|
+
model = LLMeta("deepseek-v4-flash-vision-exp")
|
|
268
|
+
assert model.version == "4.0"
|
|
269
|
+
assert model.capabilities.supports_vision is True
|
|
270
|
+
assert model.capabilities.media_count_limit.get("image") == 600
|
|
271
|
+
|
|
272
|
+
def test_chat_and_reasoner_routed_have_vision(self) -> None:
|
|
273
|
+
"""deepseek-chat / deepseek-reasoner 路由到 flash,同样可读图
|
|
274
|
+
|
|
275
|
+
两者保留各自的默认思考模式语义(chat 非思考、reasoner 思考),
|
|
276
|
+
但视觉能力跟随实际服务模型。
|
|
277
|
+
"""
|
|
278
|
+
from whosellm import LLMeta
|
|
279
|
+
|
|
280
|
+
chat = LLMeta("deepseek-chat")
|
|
281
|
+
assert chat.capabilities.supports_thinking is False # 默认非思考 / non-thinking by default
|
|
282
|
+
assert chat.capabilities.supports_vision is True
|
|
283
|
+
assert chat.capabilities.media_count_limit.get("image") == 600
|
|
284
|
+
|
|
285
|
+
reasoner = LLMeta("deepseek-reasoner")
|
|
286
|
+
assert reasoner.capabilities.supports_thinking is True
|
|
287
|
+
assert reasoner.capabilities.supports_vision is True
|
|
288
|
+
assert reasoner.capabilities.media_count_limit.get("image") == 600
|
|
289
|
+
|
|
290
|
+
def test_v4_pro_not_routed_stays_text_only(self) -> None:
|
|
291
|
+
"""deepseek-v4-pro 未被路由,保持纯文本(带图会静默幻觉,绝不能标 True)
|
|
292
|
+
|
|
293
|
+
实探:带图请求返回 200,图片被静默忽略,答案系编造;无图对照亦编造;
|
|
294
|
+
直接追问时模型承认"看不到图片 / [Unsupported Image]"。
|
|
295
|
+
来源:https://api-docs.deepseek.com/zh-cn/quick_start/pricing(图像理解 不支持)
|
|
296
|
+
"""
|
|
297
|
+
from whosellm import LLMeta
|
|
298
|
+
|
|
299
|
+
model = LLMeta("deepseek-v4-pro")
|
|
300
|
+
assert model.version == "4.0"
|
|
301
|
+
assert model.variant == "pro"
|
|
302
|
+
assert model.capabilities.supports_vision is False
|
|
303
|
+
assert model.capabilities.media_count_limit.get("image") == 0 # 键缺失视同不支持
|
|
304
|
+
|
|
305
|
+
def test_version_axis_orders_flash_above_pro(self) -> None:
|
|
306
|
+
"""版本优先于档位:V4.1 Flash > V4 Pro(与官方"全面超越 V4 Pro"表述一致)"""
|
|
307
|
+
from whosellm import LLMeta
|
|
308
|
+
|
|
309
|
+
assert LLMeta("deepseek-flash") > LLMeta("deepseek-v4-pro")
|
|
310
|
+
assert LLMeta("deepseek-v4-pro") > LLMeta("deepseek-v4-flash")
|