# DeepSeek V4.1-Flash-1017 или DeepSeek V3: что выбрать для API и приложений

> AI Release · @ai_release1 · https://ai-release.org/guides/deepseek-v4-1-flash-1017-ili-deepseek-v3-chto-vybrat-dl.html

## TL;DR

- Сравнивать **DeepSeek V4.1-Flash-1017** и **DeepSeek V3** имеет смысл только на своём наборе промптов: в исходных материалах канала нет ни цифр замеров, ни цен, поэтому любые «модель X быстрее в N раз» здесь были бы выдумкой.
- Обе модели подключаются одинаково, если API OpenAI-совместим: меняется только значение поля `model` в теле запроса — остальной код остаётся тем же.
- Цена за 1M токенов, лимиты запросов и точные идентификаторы моделей — **сверьтесь с документацией** провайдера перед расчётами.
- Минимальный честный тест: 10–30 своих промптов, `temperature=0`, одинаковый `max_tokens`, замер задержки и доли обрезанных ответов (`finish_reason: "length"`).
- Решение принимается по трём числам: медианная задержка, стоимость одного прогона, доля пригодных ответов на вашем наборе.

## Требования и подготовка
![Иллюстрация к разделу «Требования и подготовка»](https://ai-release.org/guides/img/deepseek-v4-1-flash-1017-ili-deepseek-v3-chto-vybrat-dl-ru-1.jpg)


- ОС: Linux/macOS или Windows 10+ (в примерах — bash; для Windows используйте WSL или PowerShell с теми же флагами `curl`).
- Python 3.10+ (`python3 --version`) — примеры ниже используют только стандартную библиотеку, `pip install` не нужен.
- `curl` (`curl --version`) — для быстрой проверки одним запросом.
- API-ключ провайдера и базовый URL API — из личного кабинета и раздела API Reference.
- Пустой каталог под тест: `mkdir -p ~/ds-ab-test && cd ~/ds-ab-test` — в нём будут лежать скрипт и лог прогона.
- Знание, что такое токен: токен — это кусочек текста (примерно 3–4 символа), в токенах считаются лимиты и стоимость запроса.

## Что на самом деле различается: таблица для заполнения

Пустые ячейки — не ошибка: это поля, которые вы заполняете из документации провайдера, а не из чужих обзоров.

| Модель | Отличие (по карточке модели) | Цена за 1M входных / выходных токенов | Где взять цифру |
|---|---|---|---|
| DeepSeek V4.1-Flash-1017 | в исходных материалах не указано — сверьтесь с документацией | не указано — сверьтесь с документацией | раздел Pricing + карточка модели |
| DeepSeek V3 | в исходных материалах не указано — сверьтесь с документацией | не указано — сверьтесь с документацией | раздел Pricing + карточка модели |

**Пример:** если в прайсе указано «$X за 1M входных токенов», то стоимость одного прогона из 30 промптов по 500 входных и 300 выходных токенов считается как `30 × (500/1_000_000 × X_вх + 300/1_000_000 × X_вых)`. Подставьте X из прайса — арифметика занимает одну строку в таблице.

Если вам нужен более широкий контекст по тому, как вообще сравнивать модели между поколениями, начните с материала [GPT-6.1 Sol или GPT-4o: сравнение моделей и что выбрать](https://ai-release.org/guides/gpt-6-1-sol-ili-gpt-4o-sravnenie-modeley-i-chto-vybrat.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) — логика замера там та же.

## Шаг 1. Зафиксируйте критерии и соберите тестовый набор промптов

**Что сделать:** создайте файл `prompts.txt` — по одному промпту на строку, 10–30 штук из вашей реальной задачи (не «напиши стих», а то, что приложение реально спрашивает у модели).

**Зачем:** без фиксированного набора вы будете сравнивать случайные ответы и получите случайный вывод. Набор — это ваш эталон, его можно переиспользовать при следующем обновлении модели.

**Пример:** `prompts.txt` для сервиса поддержки:

```
Верни JSON с полями id и title для обращения «оплата не проходит третий день».
Классифицируй обращение одним словом: billing, delivery, account: «не приходит SMS с кодом».
Сократи ответ до 200 символов: <вставьте свой реальный абзац>.
```

**Как проверить:** `wc -l prompts.txt` должен показать число от 10 до 30, а `head -n 3 prompts.txt` — ваши первые три строки.

## Шаг 2. Прогоните оба идентификатора одним скриптом
![Иллюстрация к разделу «Шаг 2. Прогоните оба идентификатора одним скриптом»](https://ai-release.org/guides/img/deepseek-v4-1-flash-1017-ili-deepseek-v3-chto-vybrat-dl-ru-2.jpg)


**Что сделать:** сохраните ключ и идентификаторы моделей в переменные окружения, затем запустите скрипт `ab.py`, который делает одинаковые запросы к обеим моделям и пишет задержку и `usage`.

**Зачем:** один и тот же код с разным значением `model` — единственный способ получить сравнимые числа. Идентификаторы моделей в API обычно записываются строчными буквами и отличаются от человекочитаемых названий, поэтому **точные `id` сверьте с документацией** и списком моделей.

```bash
export DEEPSEEK_API_KEY="<ваш-ключ>"
export DEEPSEEK_BASE_URL="<базовый-URL-из-документации>"
export MODEL_A_ID="<id модели DeepSeek V4.1-Flash-1017 из документации>"
export MODEL_B_ID="<id модели DeepSeek V3 из документации>"
```

Быстрая проверка, что ключ и URL рабочие (до всякого сравнения):

```bash
curl -s "$DEEPSEEK_BASE_URL/models" -H "Authorization: Bearer $DEEPSEEK_API_KEY" | head -c 400
```

Ожидаемый вывод: JSON со списком объектов, у каждого есть поле `id`. Если вместо этого пришёл `{"error":...}` — ключ или URL неверны, дальше идти нет смысла.

Сам прогон:

```python
import os, json, time, statistics, urllib.request

BASE = os.environ["DEEPSEEK_BASE_URL"].rstrip("/")
KEY = os.environ["DEEPSEEK_API_KEY"]
MODELS = {"A": os.environ["MODEL_A_ID"], "B": os.environ["MODEL_B_ID"]}
PROMPTS = [l.strip() for l in open("prompts.txt", encoding="utf-8") if l.strip()]

def call(model, prompt):
    body = json.dumps({
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 0,
        "max_tokens": 1024,
    }).encode()
    req = urllib.request.Request(
        f"{BASE}/chat/completions", data=body,
        headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
    t0 = time.perf_counter()
    with urllib.request.urlopen(req, timeout=90) as r:
        data = json.load(r)
    dt = time.perf_counter() - t0
    ch = data["choices"][0]
    return dt, ch.get("finish_reason"), data.get("usage", {}), ch["message"]["content"]

for key, model in MODELS.items():
    lat, truncated = [], 0
    for p in PROMPTS:
        dt, finish, usage, text = call(model, p)
        lat.append(dt)
        truncated += (finish == "length")
        print(key, model, f"{dt:.2f}s", finish, usage.get("completion_tokens"), repr(text[:60]))
        time.sleep(1)  # пауза против 429
    print(f"{key}: медиана {statistics.median(lat):.2f}s, обрезано {truncated} из {len(PROMPTS)}")
```

Почему это работает: `temperature=0` убирает случайность формулировок, одинаковый `max_tokens` не даёт одной модели «обрезаться» раньше другой, а `time.perf_counter()` измеряет полное время запроса, включая сеть.

**Как проверить:** в консоли после каждого ответа печатается строка вида `A <id> 1.84s stop 128 'Отлично, вот JSON...'`, а в конце — итог `A: медиана ...s, обрезано 0 из 20`. Если итоговой строки нет — скрипт упал на исключении, смотрите раздел проблем ниже.

## Шаг 3. Снимите метрики и примите решение

**Что сделать:** сведите результаты прогона в чек-лист и посчитайте стоимость по прайсу.

**Зачем:** выбор «на глаз» по паре ответов почти всегда ошибочен: модели отличаются не качеством в целом, а поведением на вашем типе задач.

**Пример:** чек-лист, который заполняется один раз на каждую модель:

- [ ] Прогон завершён без исключений, число ответов = число строк в `prompts.txt`
- [ ] Доля `finish_reason: "length"` равна нулю для обеих моделей
- [ ] Записана медианная задержка (p50) и максимальная (p95) по каждому прогону
- [ ] Сохранён суммарный `completion_tokens` из `usage` по каждому прогону
- [ ] Стоимость прогона посчитана как `токены × цена за 1M / 1_000_000` по прайсу
- [ ] Ответы, которые не парсятся как JSON в вашей задаче, посчитаны отдельно
- [ ] Решение записано в файл `decision.md` с датой прогона

**Как проверить:** откройте `decision.md` — там должно быть две строки с числами (задержка, стоимость, доля брака) и одна строка с решением: «для прод-нагрузки берём модель A, для офлайн-разбора — модель B». Если хотя бы одно поле пустое, возвращайтесь к Шагу 2.

## Возможные проблемы и решения

**Проблема. На запрос к одной из моделей API отвечает 404 вместо ответа модели.**
- Ошибка: `{"error":{"message":"Model Not Exist","type":"invalid_request_error"}}`
- Причина: в поле `model` передано человекочитаемое название («DeepSeek V4.1-Flash-1017») вместо идентификатора из API, либо у ключа нет доступа к этой модели.
- Решение: выполните `curl -s "$DEEPSEEK_BASE_URL/models" -H "Authorization: Bearer $DEEPSEEK_API_KEY"` и скопируйте `id` из ответа в `MODEL_A_ID`. Если нужного `id` в списке нет — сверьтесь с документацией: возможно, доступ выдаётся отдельно.

**Проблема. Прогон падает на середине, в логе 429, часть ответов пустая.**
- Ошибка: `{"error":{"message":"Rate limit reached for requests","type":"rate_limit_error"}}`
- Причина: скрипт шлёт промпты подряд без паузы, лимит запросов в минуту исчерпан.
- Решение: оставьте `time.sleep(1)` между вызовами и оберните `urlopen` в повтор с нарастающей паузой по коду 429:

```python
except urllib.error.HTTPError as e:
    if e.code == 429:
        time.sleep(10); continue  # повтор после паузы
    raise
```

**Проблема. Сравнение нечестное: у одной модели ответы заметно короче.**
- Ошибка: в ответе одной модели `"finish_reason": "length"`, у другой — `"stop"`.
- Причина: `max_tokens` мало для полного ответа, модель обрывается на середине, и вы сравниваете обрезанный текст с целым.
- Решение: поднимите `max_tokens` до одинакового значения (например, 2048) для обеих моделей, перезапустите прогон и исключите из подсчёта все ответы с `"length"`.

## Как выбрать
![Иллюстрация к разделу «Как выбрать»](https://ai-release.org/guides/img/deepseek-v4-1-flash-1017-ili-deepseek-v3-chto-vybrat-dl-ru-3.jpg)


- Если приложение работает в реальном времени (чат, автодополнение) — берите ту модель, у которой на вашем наборе ниже медианная задержка при нулевой доле обрезанных ответов.
- Если нагрузка пакетная (ночная обработка заявок, разбор документов) — решающим становится третье число: стоимость полного прогона по прайсу.
- Если модели дают сопоставимые числа, начните с более дешёвой по прайсу и оставьте вторую как резерв: переключение — это смена одной переменной `MODEL_A_ID`.
- Если качество на ваших промптах отличается сильно, не «докручивайте» промптами слабую модель — сначала проверьте, не связано ли различие с обрезкой ответов или лимитом токенов.

Общий обзор того, как разные ассистенты и модели ведут себя на типовых задачах, есть в гайде [ChatGPT, Claude, Gemini, DeepSeek: сравнение ИИ-ассистентов](https://ai-release.org/guides/sravnenie-ii-assistentov.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).

## Резюме и следующие шаги

Вы получили воспроизводимый стенд: одинаковые промпты, одинаковые параметры запроса и три измеримых числа по каждой модели. Следующий шаг — перенести выбранный идентификатор модели в конфиг приложения и повторить прогон через месяц, чтобы поймать изменения на стороне провайдера.

## FAQ

**Можно ли переключаться между DeepSeek V4.1-Flash-1017 и DeepSeek V3 без переписывания кода?**
Да, если API OpenAI-совместим: меняется только значение поля `model` в теле запроса, остальные поля и структура ответа остаются прежними. Точные идентификаторы сверьте с документацией.

**Где взять цены за токены, если в обзорах их нет?**
В разделе Pricing документации провайдера; далее считайте по формуле `токены × цена за 1M / 1_000_000`, используя `usage` из реального прогона, а не оценку «на глаз».

**Почему скрипт вернул 429, хотя я сделал всего 20 запросов?**
Сработал лимит запросов в минуту (rate limit). Добавьте паузу между вызовами и повтор с задержкой при коде 429 — пример есть в разделе проблем.

**Нужно ли менять промпты при переходе на другую модель?**
Да, прогоните свой набор из `prompts.txt` заново: формулировки, которые работали на одной модели, могут давать другой формат ответа на другой. Именно для этого набор и сохраняется в файл.
