Skip to content
Merged
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
6 changes: 4 additions & 2 deletions docs/docs/_generated/model_aliases_deepseek.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
| Model Alias | Maps to |
| --- | --- |
| `deepseek` | `deepseek.deepseek-v4-flash` |
| `deepseek` | `deepseek.deepseek-flash` |
| `DeepSeek V4 Pro` | `deepseek.deepseek-v4-pro` |
| `DeepSeek V4.1 Flash` | `deepseek.deepseek-flash` |
| `deepseek-flash` | `deepseek-flash` |
| `deepseek-v4-flash` | `deepseek-v4-flash` |
| `deepseek-v4-flash-vision-exp` | `deepseek-v4-flash-vision-exp` |
| `deepseek-v4-pro` | `deepseek-v4-pro` |
| `deepseekpro` | `deepseek.deepseek-v4-pro` |
| `deepseekvision` | `deepseek.deepseek-v4-flash-vision-exp` |
| `deepseekvision` | `deepseek.deepseek-flash` |
5 changes: 3 additions & 2 deletions docs/docs/_generated/models_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@
| `luna` | `codexresponses` | Text, Vision, Document | `json` (schema) | effort: `none`, `low`, `medium`, `high`, `xhigh`, `max`, `off`<br>Example: `luna?reasoning=high` | `low`, `medium`, `high`<br>Example: `luna?verbosity=low` | — |
| `sol` | `codexresponses` | Text, Vision, Document | `json` (schema) | effort: `none`, `low`, `medium`, `high`, `xhigh`, `max`, `off`<br>Example: `sol?reasoning=high` | `low`, `medium`, `high`<br>Example: `sol?verbosity=low` | — |
| `terra` | `codexresponses` | Text, Vision, Document | `json` (schema) | effort: `none`, `low`, `medium`, `high`, `xhigh`, `max`, `off`<br>Example: `terra?reasoning=high` | `low`, `medium`, `high`<br>Example: `terra?verbosity=low` | — |
| `deepseek` | `deepseek` | Text | `json` (schema) | effort: `none`, `low`, `high`, `max`, `off`<br>Example: `deepseek?reasoning=max` | — | — |
| `deepseek-v4-flash-vision-exp` | `deepseek` | Text, Vision | `json` (schema) | effort: `none`, `low`, `high`, `max`, `off`<br>Example: `deepseek-v4-flash-vision-exp?reasoning=max` | — | — |
| `deepseek-v4-flash` | `deepseek` | Text, Vision | `json` (schema) | effort: `none`, `low`, `high`, `max`, `off`<br>Example: `deepseek-v4-flash?reasoning=max` | — | — |
| `deepseek` | `deepseek` | Text, Vision | `json` (schema) | effort: `none`, `low`, `high`, `max`, `off`<br>Example: `deepseek?reasoning=max` | — | — |
| `deepseekpro` | `deepseek` | Text | `json` (schema) | effort: `none`, `low`, `high`, `max`, `off`<br>Example: `deepseekpro?reasoning=max` | — | — |
| `deepseekvision` | `deepseek` | Text, Vision | `json` (schema) | effort: `none`, `low`, `high`, `max`, `off`<br>Example: `deepseekvision?reasoning=max` | — | — |
| `passthrough` | `fast-agent` | Text | `json` (schema) | — | — | — |
| `playback` | `fast-agent` | Text | `json` (schema) | — | — | — |
| `silent` | `fast-agent` | Text | `json` (schema) | — | — | — |
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/guides/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,12 +356,12 @@ The model string still selects the provider adapter and protocol:
```bash
# OpenAI-compatible Chat Completions
fast-agent go \
--model generic.deepseek-v4-flash \
--model generic.deepseek-flash \
--base-url https://gateway.example/v1

# Open Responses-compatible Responses API
fast-agent go \
--model openresponses.deepseek-v4-flash \
--model openresponses.deepseek-flash \
--base-url https://responses.example/v1
```

Expand Down
8 changes: 5 additions & 3 deletions docs/docs/models/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,12 @@ fast-agent --model "deepseek?reasoning=high"
fast-agent --model "deepseek?web_search=true"
```

The native route supports `deepseek-v4-flash`,
`deepseek-v4-flash-vision-exp`, and `deepseek-v4-pro`. All provide reasoning,
The native route supports `deepseek-flash` (V4.1 Flash) and `deepseek-v4-pro`.
Both provide reasoning,
function tools, JSON Schema structured output, and provider-managed web search.
The experimental vision variant also accepts JPEG, PNG, GIF, and WebP images.
Flash accepts JPEG, PNG, GIF, and WebP images, with 1M context and up to 384K
output tokens. Both `deepseek` and `deepseekvision` select V4.1 Flash. Retired
Flash API IDs remain accepted and redirect to V4.1 Flash.
Hugging Face aliases such as `deepseek-hf` are separate routes.

### xAI
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/models/providers/additional.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ override only its destination:

```bash
OPENRESPONSES_API_KEY=... fast-agent go \
--model openresponses.deepseek-v4-flash \
--model openresponses.deepseek-flash \
--base-url https://responses.example/v1
```

Expand Down Expand Up @@ -168,7 +168,7 @@ configured generic provider:

```bash
GENERIC_API_KEY=... fast-agent go \
--model generic.deepseek-v4-flash \
--model generic.deepseek-flash \
--base-url https://gateway.example/v1
```

Expand Down
32 changes: 20 additions & 12 deletions docs/docs/models/providers/deepseek.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ social:
# DeepSeek

Use the `deepseek` provider for fast-agent's native DeepSeek Responses route.
It uses a stateless Responses API over SSE and supports `deepseek-v4-flash`,
`deepseek-v4-flash-vision-exp`, and `deepseek-v4-pro`.
It uses a stateless Responses API over SSE and supports `deepseek-flash`
(DeepSeek V4.1 Flash) and `deepseek-v4-pro`. Flash supports vision, a 1M-token
context window, and up to 384K output tokens.

The retired API IDs `deepseek-v4-flash` and `deepseek-v4-flash-vision-exp`
remain accepted: DeepSeek redirects them to V4.1 Flash. They are no longer
listed in the model picker.

## Setup

Expand All @@ -31,7 +36,7 @@ fast-agent go --model deepseek \
The explicit model string is:

```text
deepseek.deepseek-v4-flash
deepseek.deepseek-flash
```

Select V4 Pro with:
Expand All @@ -46,7 +51,7 @@ or its explicit model string:
deepseek.deepseek-v4-pro
```

Select the experimental vision model with:
The `deepseekvision` alias also selects V4.1 Flash:

```bash
fast-agent go --model deepseekvision --message "Describe this image."
Expand All @@ -55,10 +60,10 @@ fast-agent go --model deepseekvision --message "Describe this image."
or its explicit model string:

```text
deepseek.deepseek-v4-flash-vision-exp
deepseek.deepseek-flash
```

The vision model accepts JPEG, PNG, GIF, and WebP images from inline data or
V4.1 Flash accepts JPEG, PNG, GIF, and WebP images from inline data or
public URLs. Attach images through the TUI or other fast-agent input surfaces;
the native adapter sends them as Responses API `input_image` parts.

Expand All @@ -80,7 +85,7 @@ The complete provider shape is:
deepseek:
api_key: "${DEEPSEEK_API_KEY}"
base_url: "https://api.deepseek.com"
default_model: "deepseek-v4-flash"
default_model: "deepseek-flash"
reasoning: "max"
web_search:
enabled: false
Expand All @@ -89,15 +94,17 @@ deepseek:
```

`base_url`, `default_model`, and `default_headers` are optional. A configured
`default_model` must be `deepseek-v4-flash`,
`deepseek-v4-flash-vision-exp`, or `deepseek-v4-pro`. Flash remains the default
when this setting is omitted.
`default_model` accepts `deepseek-flash`, `deepseek-v4-pro`, and the two
retired Flash IDs above. `deepseek-flash` is the default when omitted.

Run `fast-agent check` after configuring credentials.

## Reasoning

Reasoning defaults to `max`. Select an effort in the model string:
fast-agent intentionally defaults reasoning to `max`; DeepSeek itself defaults
to `high` when effort is omitted. The native Responses payload uses
`reasoning: {"effort": "none|low|high|max"}`, with `none` disabling thinking.
Select an effort in the model string:

```bash
fast-agent go --model "deepseek?reasoning=none" --message "Answer directly."
Expand Down Expand Up @@ -150,7 +157,7 @@ DeepSeek's route differs from OpenAI's stateful Responses API:
- requests use SSE; WebSocket transport is not supported;
- server-side response storage and continuation are not used;
- service tiers are not supported;
- image input is supported only by `deepseek-v4-flash-vision-exp`;
- image input is supported by V4.1 Flash, including the retired Flash IDs;
- PDF, audio, video, and general file inputs are not supported;
- OpenAI-only request fields such as `include`, `parallel_tool_calls`,
`service_tier`, and `store` are omitted.
Expand All @@ -177,6 +184,7 @@ route.

- [DeepSeek platform](https://platform.deepseek.com/)
- [DeepSeek API documentation](https://api-docs.deepseek.com/)
- [DeepSeek thinking mode](https://api-docs.deepseek.com/guides/thinking_mode)
- [DeepSeek vision guide](https://api-docs.deepseek.com/guides/vision)
- [DeepSeek models and pricing](https://api-docs.deepseek.com/quick_start/pricing)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fast-agent-mcp"
version = "0.10.22"
version = "0.10.23"
description = "Code, Build and Evaluate agents - excellent Model and Skills/MCP/ACP/A2A Support"
readme = "README.md"
license = { file = "LICENSE" }
Expand Down
5 changes: 3 additions & 2 deletions src/fast_agent/llm/model_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
"opus48": "claude-opus-4-8",
"fable": "claude-fable-5",
"fable5": "claude-fable-5",
"deepseek": "deepseek.deepseek-v4-flash",
"deepseek": "deepseek.deepseek-flash",
"deepseekpro": "deepseek.deepseek-v4-pro",
"deepseekvision": "deepseek.deepseek-v4-flash-vision-exp",
"deepseekvision": "deepseek.deepseek-flash",
"gemini": "gemini-3.8-flash",
"gemini2": "gemini-2.0-flash",
"gemini25": "gemini-2.5-flash",
Expand Down Expand Up @@ -146,6 +146,7 @@
"Grok 4.5": "xai.grok-4.5",
"Grok 4.5 (X Search)": "xai.grok-4.5?x_search=true",
"Grok 4.3": "xai.grok-4.3",
"DeepSeek V4.1 Flash": "deepseek.deepseek-flash",
"DeepSeek V4 Pro": "deepseek.deepseek-v4-pro",
"Muse Spark 1.2": "metaai.muse-spark-1.2",
"Muse Spark 1.2 (Contributor)": "metaai.muse-spark-1.2-contributor",
Expand Down
8 changes: 5 additions & 3 deletions src/fast_agent/llm/model_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ class ModelDatabase:
}
)
DEEPSEEK_V4_PRO = DEEPSEEK_V4_FLASH
DEEPSEEK_V4_FLASH_VISION = DEEPSEEK_V4_FLASH.model_copy(
DEEPSEEK_V41_FLASH = DEEPSEEK_V4_FLASH.model_copy(
update={"tokenizes": [*OPENAI_VISION, "image/gif"]}
)

Expand Down Expand Up @@ -1361,8 +1361,10 @@ class ModelDatabase:
"claude-haiku-4-5-20251001": ANTHROPIC_SONNET_4_VERSIONED,
"claude-haiku-4-5": _with_fast(ANTHROPIC_SONNET_4_VERSIONED),
# DeepSeek Models
"deepseek-v4-flash": _with_fast(DEEPSEEK_V4_FLASH),
"deepseek-v4-flash-vision-exp": _with_fast(DEEPSEEK_V4_FLASH_VISION),
"deepseek-flash": _with_fast(DEEPSEEK_V41_FLASH),
# Retired native IDs redirect to V4.1 Flash; HF snapshots above stay historical.
"deepseek-v4-flash": _with_fast(DEEPSEEK_V41_FLASH),
"deepseek-v4-flash-vision-exp": _with_fast(DEEPSEEK_V41_FLASH),
"deepseek-v4-pro": DEEPSEEK_V4_PRO,
"deepseek-ai/deepseek-v4-flash-0731": _with_fast(DEEPSEEK_V4_FLASH_HF),
# Z.ai models
Expand Down
9 changes: 2 additions & 7 deletions src/fast_agent/llm/model_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,8 @@ class ModelSelectionCatalog:
),
Provider.DEEPSEEK: (
_builtin_entry(
"deepseek",
display_label="DeepSeek V4 Flash",
fast=True,
),
_builtin_entry(
"deepseekvision",
display_label="DeepSeek V4 Flash Vision (experimental)",
"DeepSeek V4.1 Flash",
display_label="DeepSeek V4.1 Flash",
fast=True,
),
_builtin_entry(
Expand Down
4 changes: 3 additions & 1 deletion src/fast_agent/llm/provider/openai/llm_deepseek.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
from fast_agent.types import RequestParams

DEEPSEEK_BASE_URL = "https://api.deepseek.com"
DEFAULT_DEEPSEEK_MODEL = "deepseek-v4-flash"
DEFAULT_DEEPSEEK_MODEL = "deepseek-flash"
# Intentionally stronger than the provider default of high.
DEFAULT_DEEPSEEK_REASONING_EFFORT = "max"
SUPPORTED_DEEPSEEK_MODELS: Final = (
DEFAULT_DEEPSEEK_MODEL,
"deepseek-v4-flash",
"deepseek-v4-flash-vision-exp",
"deepseek-v4-pro",
)
Expand Down
17 changes: 10 additions & 7 deletions tests/unit/fast_agent/llm/providers/test_deepseek_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from fast_agent.llm.provider.openai.llm_deepseek import (
DEEPSEEK_BASE_URL,
DEFAULT_DEEPSEEK_MODEL,
DEFAULT_DEEPSEEK_REASONING_EFFORT,
SUPPORTED_DEEPSEEK_MODELS,
DeepSeekResponsesLLM,
)
Expand Down Expand Up @@ -76,16 +75,20 @@ def test_deepseek_factory_builds_sse_responses_adapter() -> None:
assert llm.configured_transport == "sse"


def test_deepseek_vision_model_serializes_inline_image_for_responses() -> None:
@pytest.mark.parametrize(
"model", ["deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"]
)
@pytest.mark.parametrize("mime", ["image/jpeg", "image/png", "image/gif", "image/webp"])
def test_deepseek_vision_model_serializes_inline_image_for_responses(model: str, mime: str) -> None:
llm = DeepSeekResponsesLLM(
context=Context(config=Settings()),
model="deepseek-v4-flash-vision-exp",
model=model,
)

parts = llm._convert_content_parts(
[
TextContent(type="text", text="What is in this image?"),
ImageContent(type="image", data="aW1hZ2U=", mime_type="image/png"),
ImageContent(type="image", data="aW1hZ2U=", mime_type=mime),
],
role="user",
)
Expand All @@ -94,7 +97,7 @@ def test_deepseek_vision_model_serializes_inline_image_for_responses() -> None:
{"type": "input_text", "text": "What is in this image?"},
{
"type": "input_image",
"image_url": "data:image/png;base64,aW1hZ2U=",
"image_url": f"data:{mime};base64,aW1hZ2U=",
},
]

Expand Down Expand Up @@ -122,8 +125,8 @@ def test_deepseek_rejects_websocket_transport() -> None:
@pytest.mark.parametrize(
("reasoning", "wire_effort"),
[
(None, DEFAULT_DEEPSEEK_REASONING_EFFORT),
(True, DEFAULT_DEEPSEEK_REASONING_EFFORT),
(None, "max"),
(True, "max"),
("low", "low"),
("high", "high"),
("max", "max"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,15 @@ def test_huggingface_provider_default_model_used_with_provider_suffix() -> None:
assert request["model"] == "moonshotai/kimi-k2-instruct:fireworks-ai"


def test_deepseek_provider_defaults_to_v4_flash() -> None:
def test_deepseek_provider_defaults_to_v41_flash() -> None:
llm = DeepSeekResponsesLLM(context=Context(config=Settings()), model="")

assert llm.default_request_params.model == "deepseek-v4-flash"
assert llm.default_request_params.model == "deepseek-flash"


@pytest.mark.parametrize(
"model",
["deepseek-v4-flash", "deepseek-v4-flash-vision-exp", "deepseek-v4-pro"],
["deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp", "deepseek-v4-pro"],
)
def test_deepseek_provider_config_default_model_used_when_model_missing(model: str) -> None:
settings = Settings(deepseek=DeepSeekSettings(default_model=model))
Expand Down
20 changes: 19 additions & 1 deletion tests/unit/fast_agent/llm/test_model_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ def test_model_database_supports_mime_basic():
)

# Text-only models should not support images
assert not ModelDatabase.supports_mime("deepseek-v4-flash", "image/png")
assert not ModelDatabase.supports_mime("deepseek-v4-pro", "image/png")
assert not ModelDatabase.supports_mime("deepseek-v4-flash", "pdf")
assert not ModelDatabase.supports_mime(
"deepseek-v4-flash",
Expand All @@ -568,8 +568,26 @@ def test_model_database_supports_mime_basic():
assert ModelDatabase.supports_mime("gpt-4o", "png")


@pytest.mark.parametrize(
"model", ["deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"]
)
def test_deepseek_flash_capabilities(model: str) -> None:
params = ModelDatabase.get_model_params(model)
assert params is not None
assert params.context_window == 1_048_576
assert params.max_output_tokens == 393_216
for mime in ("image/jpeg", "image/png", "image/gif", "image/webp"):
assert ModelDatabase.supports_mime(model, mime)
assert not ModelDatabase.supports_mime(model, "application/pdf")


def test_deepseek_hf_flash_remains_text_only() -> None:
assert not ModelDatabase.supports_mime("deepseek-ai/DeepSeek-V4-Flash-0731", "image/png")


def test_deepseek_v4_flash_uses_learned_shell_contract() -> None:
for model_name in (
"deepseek-flash",
"deepseek-v4-flash",
"deepseek-v4-pro",
"deepseek-ai/DeepSeek-V4-Flash-0731",
Expand Down
11 changes: 6 additions & 5 deletions tests/unit/fast_agent/llm/test_model_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1122,10 +1122,11 @@ def test_gemini38_flash_aliases_resolve_to_current_google_flash(alias: str) -> N
assert config.model_name == "gemini-3.8-flash"


def test_deepseek_alias_resolves_to_deepseek_responses_model():
config = ModelFactory.parse_model_string("deepseek")
@pytest.mark.parametrize("alias", ["deepseek", "deepseekvision", "DeepSeek V4.1 Flash"])
def test_deepseek_alias_resolves_to_deepseek_responses_model(alias: str) -> None:
config = ModelFactory.parse_model_string(alias)
assert config.provider == Provider.DEEPSEEK
assert config.model_name == "deepseek-v4-flash"
assert config.model_name == "deepseek-flash"


def test_deepseek_pro_alias_resolves_to_deepseek_responses_model() -> None:
Expand All @@ -1137,7 +1138,7 @@ def test_deepseek_pro_alias_resolves_to_deepseek_responses_model() -> None:

@pytest.mark.parametrize(
"model",
("deepseekvision", "deepseek-v4-flash-vision-exp"),
("deepseek-v4-flash-vision-exp",),
)
def test_deepseek_vision_model_resolves_to_deepseek_responses(model: str) -> None:
config = ModelFactory.parse_model_string(model)
Expand All @@ -1155,7 +1156,7 @@ def test_deepseek_hf_aliases_resolve_to_hf_deepseek_v4_pro():

@pytest.mark.parametrize(
"model",
["deepseek-v4-flash", "deepseek-v4-flash-vision-exp", "deepseek-v4-pro"],
["deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp", "deepseek-v4-pro"],
)
def test_deepseek_responses_model_resolves_to_official_provider(model: str) -> None:
config = ModelFactory.parse_model_string(model)
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/fast_agent/llm/test_model_selection_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def test_gpt_6_astra_is_available_through_codex_and_api_routes() -> None:

def test_deepseek_catalog_exposes_native_responses_models() -> None:
aliases = ModelSelectionCatalog.list_current_aliases(Provider.DEEPSEEK)
assert aliases == ["deepseek", "deepseekvision", "DeepSeek V4 Pro"]
assert aliases == ["DeepSeek V4.1 Flash", "DeepSeek V4 Pro"]


def test_non_current_aliases_are_listed_but_not_current() -> None:
Expand Down
Loading
Loading