Ваш ассистент AS внезапно начал падать при обращении к DeepSeek. В логах — вот это:
422: Failed to deserialize the JSON body into the target type:
messages[0].role: unknown variant `developer`, expected one of
`system`, `user`, `assistant`, `tool`, `latest_reminder`
Мы разобрались, почему это происходит, и сделали своё решение. Ниже — что именно случилось и как это чинится за пять минут.
Почему это сломалось
OpenAI в конце 2024 года ввела новую роль — developer. Она задумывалась как замена system: туда разработчик кладёт инструкции, которые модель должна соблюдать всегда.
AS, как и многие современные клиенты, следует свежей спецификации OpenAI и отправляет системный промпт с role: "developer". Всё было бы хорошо, если бы целевой API был официальным эндпоинтом OpenAI.
Но DeepSeek принимает только эти роли:
system | user | assistant | tool | latest_reminder
Его Rust-бэкенд видит незнакомый вариант developer — и отказывается десериализовать запрос. Отсюда 422 и список допустимых значений в тексте ошибки.
Что мы сделали
Вариант «покопаться в настройках AS и попросить его отправлять system» мы отмели сразу — во многих клиентах роль developer зашита во внутреннюю логику, и пользовательскими настройками её не переопределить.
Вместо этого мы поставили между AS и DeepSeek лёгкий Flask-прокси. Он перехватывает запрос, меняет developer на system и передаёт дальше. Клиент ничего не подозревает, DeepSeek получает валидный запрос, все довольны.
Схема простая:
AS → http://127.0.0.1:1234 → https://api.deepseek.com
(наш прокси) (реальный API)
Ключевой кусок кода
Вот та самая функция, которая всё чинит:
def normalize_developer_role(data):
"""Заменяет роль 'developer' на 'system' для совместимости с DeepSeek."""
if "messages" in data and isinstance(data["messages"], list):
for msg in data["messages"]:
if isinstance(msg, dict) and msg.get("role") == "developer":
msg["role"] = "system"
if "input" in data and isinstance(data["input"], list):
for item in data["input"]:
if isinstance(item, dict) and item.get("role") == "developer":
item["role"] = "system"
Она обрабатывает два места:
- Массив
messagesв/v1/chat/completions— классический Chat Completions API. - Массив
inputв/v1/responses— новый Responses API, где системные инструкции тоже лежат вdeveloper.
Прокси обслуживает все три эндпоинта (/v1/chat/completions, /v1/responses, /responses) плюс /v1/models для списка моделей. Так что AS может стучаться куда угодно — всё уйдёт по назначению.
Как запустить
Шаг 1. Скачать и распаковать
unzip proxy-as-deepseek.zip
cd proxy-as-deepseek
pip install flask requests
python proxy.py
По умолчанию прокси слушает 127.0.0.1:1234. Если порт занят — поправьте в последней строке файла.
Шаг 2. Переключить AS на прокси
В настройках AS замените Base URL:
- Было:
https://api.deepseek.com - Стало:
http://127.0.0.1:1234
API-ключ оставьте свой, от DeepSeek. Прокси прозрачно передаст заголовок Authorization дальше.
Шаг 3. Проверить
Запустите прокси, отправьте запрос из AS — и смотрите в консоль прокси. Туда выводится всё: модель, количество сообщений, распределение ролей, а после ответа — расход токенов и попадание в кэш. Если видите role: system вместо developer — всё работает.
Что ещё умеет прокси
Считает токены. Если DeepSeek вернул поле usage (включая последний чанк потокового ответа), прокси напишет в лог: сколько ушло на prompt, сколько на completion, сколько попало в кэш. Если prompt больше 1000 токенов, а кэш пустой — предупредит. Полезно, когда хочется понять, почему запросы дорогие.
Не ломает стриминг. Для запросов с stream: true используется stream_with_context — чанки идут в реальном времени, как и раньше. Разбор usage происходит только на том чанке, где оно есть, и не мешает основному потоку.
Не мешает заголовкам. Hop-by-hop заголовки (Host, Content-Length, Connection и прочие) вычищаются, чтобы не путать DeepSeek. Всё остальное — включая ваш ключ — уходит как есть.
Почему не «просто поправить настройки»
Мы пробовали. В некоторых клиентах есть переключатель supportsDeveloperRole: false, который якобы должен заставить их отправлять system. На практике он либо отсутствует, либо игнорируется — базовая ветка кода всё равно отправляет developer. Это не наша выдумка: с тем же самым сталкивались разработчики Cherry Studio и OmniRoute, и оба проекта в итоге поставили ровно такой же прокси-слой с конвертацией ролей.
Прокси-слой обходит всю внутреннюю логику клиента. Это самый надёжный вариант — и, что важно, не требует ждать, пока AS или DeepSeek что-то там починят.
Файл: proxy-as-deepseek.zip · Python 3.8+ · Flask + Requests

Комментарии 0
Чтобы оставить комментарий, войдите на сайт — комментируют только зарегистрированные пользователи.
Комментариев пока нет — будьте первым.