Допустим, сервис отправляет заметки о ремонте модели DeepSeek и ждёт в ответ JSON-объект: что проверили, что рекомендовали, согласился ли клиент, выполнена ли работа. Сегодня он вызывает deepseek-chat, deepseek-flash или маршрут OpenRouter, а вы хотите запустить его на DeepSeek V4.1 Flash через SpicyAPI (spicyapi.ai).
Хватит четырёх правок: использовать ID модели deepseek/v4.1-flash/chat, удалить каждое поле, которое эта модель не принимает, выбрать reasoning_effort и дать max_tokens реальный запас, потому что рассуждение оплачивается из того же бюджета. Затем прогоните один небольшой тест до реального трафика. Попробовать запрос можно на странице модели V4.1 Flash.
Что менять в запросе
Базовый URL, ключ и ID модели — это простая часть (руководство по миграции). Ломается всё на теле запроса:
| В вашем текущем клиенте | В SpicyAPI |
|---|---|
Базовый URL https://api.deepseek.com и ключ DeepSeek | https://api.spicyapi.ai/v1 и ключ, начинающийся с sk-spicy- |
Модель deepseek-flash или старые deepseek-chat, deepseek-reasoner, deepseek-v4-flash, deepseek-v4-pro | deepseek/v4.1-flash/chat |
thinking: {"type": "disabled"} — поведение, которое раньше давал deepseek-chat | reasoning_effort: "none" пропускает этап рассуждения |
| Thinking включён (по умолчанию) | Опустите reasoning_effort, чтобы получить high, или задайте low для массовой работы |
reasoning_effort со значением max, medium, minimal, xhigh | Принимаются только none, low и high; любое другое значение даёт 400 invalid_request |
temperature, top_p, presence_penalty, frequency_penalty | Удалите; 400 unsupported_parameter |
parallel_tool_calls | Удалите; у этой модели не поддерживается |
stop | Оставьте: до 4 последовательностей, совпавший текст в ответ не попадает |
Части image_url или файлов | Части image_url для изображений оставьте; части PDF/файлов уберите и сначала извлеките из них текст |
OpenRouter models, route, provider, usage, transforms, plugins и объект reasoning | Первые шесть удалите; reasoning замените полем верхнего уровня reasoning_effort |
У этой размещённой модели нет управления сэмплированием, даже с reasoning_effort: "none". Проверяйте результаты задач вместо того, чтобы рассчитывать на temperature: 0 ради повторяемости.
Что остаётся прежним: messages (текст или изображения, до 256), tools (до 128 определений) с tool_choice (auto, none, required или одна именованная функция), response_format с {"type": "json_object"}, stop, max_tokens (по умолчанию 32 768, максимум 384 000) и входное окно в 1 048 576 токенов, в которое входит каждое отправленное сообщение, включая системный промпт и прежние результаты инструментов.
SpicyAPI никогда не отбрасывает поле молча: ошибка 400 называет поле, и ничего не списывается. Собственный API DeepSeek в режиме thinking принимает и игнорирует temperature и поля штрафов, поэтому старый клиент может тянуть их незаметно.
Тихая поломка — название модели. В собственном API DeepSeek V4 Flash выведен из обращения, а deepseek-v4-flash временно направляется на V4.1 Flash; актуальное имя — deepseek-flash. С V4 Pro иначе: сначала DeepSeek объявил переход на Flash с 14 сентября, затем его журнал изменений подтвердил, что обслуживание V4 Pro продолжится с неизменной тарификацией. Не переходите, предполагая, что каждый старый вызов Pro уже выполняется на Flash.
В SpicyAPI у трёх чат-маршрутов DeepSeek отдельные опубликованные контракты: оставить в конфигурации deepseek/v4-flash/chat — допустимый запрос, но с потолком вывода 65 536 токенов и более высокими ценами. Алиас нативного сервиса этот размещённый контракт не меняет. Для этой миграции указывайте deepseek/v4.1-flash/chat явно.
Этот маршрут принимает изображения через части image_url, но не части PDF/файлов. Извлеките текст из PDF до отправки; приведённым ниже примерам с текстовыми заметками о ремонте ввод изображений не нужен.
Оставьте место для ответа
Модель рассуждает перед ответом, и рассуждение берётся из max_tokens. Усилие по умолчанию — high, а лимит в 120 токенов не вернул вообще никакого содержимого. max_tokens по умолчанию равен 32 768, и более длинный результат обрезается. Рассчитывайте лимит по видимому ответу плюс несколько тысяч токенов запаса на размышление, а не по максимуму в 384 000.
Начинайте извлечение с усилия Low. Если оно упускает нужное различие, попробуйте High на этой записи, а не удорожайте каждую строку. Для больших входных данных используйте интерфейс потоковой передачи.
Сначала попробуйте в Playground
- Откройте DeepSeek V4.1 Flash, выберите Playground и установите элемент управления усилием на Low.
- Вставьте инструкцию о форме JSON из скрипта ниже в System prompt, а заметку о ремонте R17 — в Message to the model, затем нажмите Run model.
- Прежде чем переносить приложение, убедитесь, что ожидающее согласие не превращается в завершённый ремонт.
Прогоните тест, который ловит дорогую ошибку
Заметка о ремонте — хороший тест, потому что гладкая сводка может ошибаться так, что это стоит денег. «Рекомендуется замена» — не согласие клиента, а согласие — не выполненная работа.
Положите ключ из API Keys в SPICY_API_KEY, сохраните этот скрипт как check_repair.py и запустите python3 check_repair.py. Системный промпт показывает форму JSON и содержит слово «json», которого требует руководство DeepSeek по режиму JSON.
import json
import os
import uuid
import urllib.request
import re
SOURCE = (
'Repair note R17: "Inspected the rear brake pads. Recommend replacement before '
'the next long ride. Customer approval is pending." No later work record is supplied.'
)
KEYS = {"inspected", "recommended_action", "authorized", "completed", "evidence"}
REQUIRED_QUOTES = (
"Inspected the rear brake pads.",
"Recommend replacement before the next long ride.",
"Customer approval is pending.",
)
def check(response: dict) -> list[str]:
choice = response["choices"][0]
problems = []
if choice.get("finish_reason") != "stop":
problems.append(f"finish_reason is {choice.get('finish_reason')!r}: the reply may be cut off")
content = (choice.get("message") or {}).get("content")
if not content or not content.strip():
return problems + ["empty content: reasoning may have used the whole max_tokens budget"]
try:
data = json.loads(content)
except json.JSONDecodeError as error:
return problems + [f"not valid JSON: {error}"]
if not isinstance(data, dict):
return problems + ["JSON must be an object"]
if missing := KEYS - data.keys():
problems.append(f"missing keys: {sorted(missing)}")
for key in ("inspected", "authorized", "completed"):
if data.get(key) is not None and type(data.get(key)) is not bool:
problems.append(f"{key} must be true, false or null")
if data.get("inspected") is not True:
problems.append("inspected should be true")
if data.get("authorized") is not False:
problems.append("authorized must be false: approval is pending")
if data.get("completed") is not None:
problems.append("completed must be null: no completion record is supplied")
action = data.get("recommended_action")
if not isinstance(action, str) or not action.strip():
problems.append("recommended_action must be a non-empty string")
else:
words = set(re.findall(r"[a-z]+", action.lower()))
# A lexical screen, not a proof of semantic equivalence.
required = {"rear", "long", "ride"}
compatible = (required <= words
and bool(words & {"replace", "replacement", "replacing"})
and bool(words & {"pad", "pads"})
and not words & {"not", "no", "never", "front", "after"})
if not compatible:
problems.append("review recommendation wording against R17")
quotes = data.get("evidence")
if not isinstance(quotes, list) or not quotes:
problems.append("evidence must be a non-empty list of quotes")
else:
problems += [f"invalid or unmatched quote: {q!r}" for q in quotes
if not isinstance(q, str) or not q.strip() or q not in SOURCE]
for expected in REQUIRED_QUOTES:
if not any(isinstance(q, str) and expected in q for q in quotes):
problems.append(f"missing supporting quote: {expected}")
return problems
body = {'model': 'deepseek/v4.1-flash/chat',
'messages': [{'role': 'system',
'content': 'Extract only facts stated in the source. Reply with one '
'JSON object shaped like {"inspected": null, '
'"recommended_action": null, "authorized": null, '
'"completed": null, "evidence": []}. inspected, '
'authorized and completed are true, false or null; use '
'null for anything the source does not state. evidence '
'lists exact quotes from the source that support every '
'non-null value.'},
{'role': 'user',
'content': 'Repair note R17: "Inspected the rear brake pads. '
'Recommend replacement before the next long ride. '
'Customer approval is pending." No later work record is '
'supplied.'}],
'max_tokens': 2048,
'reasoning_effort': 'low',
'response_format': {'type': 'json_object'}}
request = urllib.request.Request(
"https://api.spicyapi.ai/v1/chat/completions",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + os.environ["SPICY_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4())},
)
with urllib.request.urlopen(request, timeout=90) as reply:
response = json.load(reply)
problems = check(response)
print(json.dumps(response.get("usage", {})))
if problems:
raise RuntimeError("; ".join(problems))
print(response["choices"][0]["message"]["content"])Ожидаемое решение: проверено, согласие всё ещё ожидается, выполнение неизвестно. Рекомендация должна сохранить задние колодки и срок «до следующей длинной поездки».
Добавьте согласие и выполнение как отдельные тесты
Не останавливайтесь на случае ожидающего согласия. Используйте эти как шаблоны для дополнительных тестов: перед запуском каждого обновите SOURCE, REQUIRED_QUOTES и ожидаемые значения согласия и выполнения. Проверку для R17 нельзя запускать без изменений на R18 или R19:
| Последняя запись | Authorized | Completed |
|---|---|---|
| R17: “Customer approval is pending.” Более поздней записи нет | false | null |
| R18: “Customer approved the quoted replacement. Parts ordered; work scheduled for Thursday.” | true | null |
| R19: “Replaced pads; road check completed Thursday.” Связано с согласованным заказом | true | true |
Храните вместе с источником ID заказа, порядок записей и даты: R18 заменяет ожидающее согласие, но назначение работы на дату не доказывает её выполнения. Если запись «сделано» без даты нельзя связать с тем же заказом, верните неразрешённую связь, а не сливайте её с R17.
То же разделение на три части встречается в производственном процессе автора: «Утвердите этот дизайн персонажа», «дизайн утверждён» и «видео создано по утверждённому дизайну» — это запрос, решение и исполнение. Просите промпт извлечения возвращать эти этапы раздельно, с записью, подтверждающей каждый из них, и сохраняйте любые противоречия между заметками, а не просите модель выбрать самое уверенно звучащее предложение.
Разделяйте согласие и выполнение в процессах с инструментами
Запись о согласии не доказывает, что ремонт состоялся: найдите запись о выполнении, прежде чем менять статус, и держите согласие и выполнение отдельными полями в том, что возвращают ваши инструменты.
Чату без инструментов ничего дополнительного не нужно. Если в запросе есть tools, клиент, который пересобирает каждое сообщение ассистента только из роли и содержимого, проходит первый ход и падает на втором с ошибкой 400, так что одноходовой тест скрывает баг. Добавляйте сообщение ассистента точно в том виде, в каком оно вернулось, включая reasoning_content, затем по одному сообщению tool на каждый вызов с соответствующим tool_call_id и ограничьте число раундов. Тестируйте два хода, а не один. Рецепты чата показывают полный цикл.
Пусть Agent подготовит вызов
Для повторяющегося извлечения записей попросите своего программирующего Agent использовать Skill SpicyAPI: “Read deepseek/v4.1-flash/chat, then adapt the example to my input; show the request before running it.” Запрос через CLI — spicyapi models get deepseek/v4.1-flash/chat --json; через MCP — spicyapi_model_get с model: "deepseek/v4.1-flash/chat". Чат-вызовы идут через совместимый клиент или чат-API; команды медиазадач чат-ответы не генерируют.
Пересчитайте счёт
Тарифы: 0,30 $ за миллион входных токенов без кэша, 0,006 $ за миллион кэшированных входных токенов и 1,20 $ за миллион выходных, единая ставка при любой длине запроса. Промпт в 40 000 токенов без кэша с 2 000 выходных токенов стоит 0,0144 $. Если 30 000 входных токенов оплачиваются как кэшированные, а 10 000 — как некэшированные, тот же вызов стоит 0,00558 $. Токены рассуждения оплачиваются как выходные, поэтому короткий видимый ответ может использовать больше выходных токенов, чем вы ожидаете; только none не даёт им попасть в счёт.
При уходе с собственного API DeepSeek важны два сравнения. Его прайс-лист в часы пик даёт те же 0,30 $, 0,006 $ и 1,20 $, но вне пика там половина цены, а здесь единая ставка: пакет, который вы запускаете вне пика в DeepSeek, в SpicyAPI обойдётся примерно вдвое дороже за те же токены. И ставка для кэша равна одной пятидесятой от входной, но только для префикса, побайтно одинакового от вызова к вызову: системный промпт в 1 500 токенов на 10 000 вызовов — это 15 миллионов входных токенов, 4,50 $ без кэша и не более 0,09 $, если бы каждый был чтением из кэша. Сравните возвращённый usage и итоговую стоимость с этими оценками. Метка времени или переставленные инструкции могут сломать общий префикс; неизменный ввод сам по себе попадания в кэш не гарантирует.
Куда попадает каждый маршрут DeepSeek
| V4.1 Flash | V4 Flash | V4 Pro | |
|---|---|---|---|
| ID модели | deepseek/v4.1-flash/chat | deepseek/v4-flash/chat | deepseek/v4-pro/chat |
| Макс. вывод | 384 000 | 65 536 | 384 000 |
| Вход / кэш / вывод за 1 млн | 0,30 $ / 0,006 $ / 1,20 $ | 0,44 $ / 0,014 $ / 1,32 $ | 1,32 $ / 0,044 $ / 3,96 $ |
parallel_tool_calls | Не поддерживается | Не поддерживается | Поддерживается |
Все три принимают 1 048 576 входных токенов, усилие none, low или high (по умолчанию high) и max_tokens по умолчанию 32 768. При переходе с deepseek/v4-flash/chat тело запроса идентично, меняется только ID: потолок вывода вырастает почти в шесть раз, а все тарифы снижаются. При переходе с V4 Pro вы теряете parallel_tool_calls и платите на 77 % меньше за вход и примерно на 70 % меньше за вывод. Что лучше подходит для конкретной задачи, V4 Flash или V4 Pro, — отдельное решение, разобранное в DeepSeek V4 Flash vs Pro.
Если ваше приложение пишет прозу для взрослых
V4.1 Flash значится в SpicyAPI как модель без цензуры, но это не обещание, что она выполнит любой запрос на прозу для взрослых. В наших тестах в сентябре 2026 года прямые запросы на интенсивные взрослые сцены возвращались смягчёнными каждый раз, а подача их как художественного вымысла или задание рамки в системном промпте давало больше полных ответов, хотя и не все (подробности в обзоре V4.1 Flash). Если ваши промпты писались для модели, принимавшей прямые запросы, при миграции перенесите рамку в системное сообщение и следите, чтобы каждый персонаж был взрослым.
Устраняйте сбои миграции напрямую
| Симптом | Вероятная причина | Исправление |
|---|---|---|
400 unsupported_parameter с именем temperature, top_p, штрафа, parallel_tool_calls или thinking | Поле перенесено из старого клиента | Удалить |
content пуст или finish_reason равен length | max_tokens ушёл на рассуждение, либо ответ его превысил | Увеличить max_tokens, снизить усилие, повторить |
| JSON разбирается, но сообщает о выполненной работе | Рекомендация прочитана как выполнение | Держать отдельные поля, требовать цитаты, запустить проверку |
| Ошибка на втором ходе диалога с инструментами | Сообщение ассистента пересобрано без reasoning_content | Добавлять возвращённое сообщение без изменений |
| 402 при достаточном балансе | Холд 40201 превышает баланс или достигнут лимит расходов 40202 | Изучить ошибку: уменьшить чрезмерный бюджет вывода для холда или проверить лимиты ключа/рабочего пространства |
Повторно использованный Idempotency-Key отклонён (400 или 409) | Ключ относится к завершённому запросу или к другому телу | 400 для завершённого запроса не может воспроизвести ответ; 409 может означать изменённое тело. Используйте ключ повторно только для идентичной сетевой повторной попытки в его окне; перед новым платным вызовом проверьте расчёт по задаче |
| Долгий вызов без потоковой передачи обрывается около 100 секунд | Обрыв на границе сети | Передавайте ответ потоком |
Переключайтесь, не теряя прежнюю базу сравнения
Оставьте старый ID модели в конфигурации и прогоните тест на обоих. Переносите трафик, когда тест проходит на новом ID, ваши собственные источники проходят валидацию, а записанный usage совпадает с расчётом выше.








