Локальный OpenAI-compatible API proxy для DeepSeek Web Chat
Быстрый старт • Возможности • Примеры • Модели • Endpoints • Open WebUI
FreeDeepseekAPI поднимает локальный API-сервер для DeepSeek Web Chat (chat.deepseek.com) и позволяет подключать DeepSeek Web к Open WebUI, LiteLLM, Hermes, Claude Code, OpenAI SDK-style клиентам и другим OpenAI-compatible инструментам.
Проект работает через ваш обычный залогиненный аккаунт DeepSeek в отдельном Chrome-профиле. Локальный сервер принимает API-запросы, а дальше сам ходит в DeepSeek Web через сохранённую browser-сессию.
⚠️ Это экспериментальный web-chat proxy. DeepSeek может менять внутренний Web API без предупреждения. Для production-кейсов надёжнее официальный платный API DeepSeek.
ForgetMeAI: https://t.me/forgetmeai
- Что это даёт
- Возможности
- Быстрый старт
- Windows запуск
- Linux / Chromium запуск
- VPS / headless запуск
- Rootless Podman
- Diagnostics / doctor
- Session reuse и сброс чатов
- Multi-account pool
- Идеи для консольной авторизации
- Проверка работы
- Примеры запросов
- Модели
- Endpoints
- Open WebUI
- Обновить логин
- Статус проекта
- Использовать DeepSeek Web как локальный API endpoint.
- Подключать DeepSeek к Open WebUI и другим OpenAI-compatible клиентам.
- Получать обычные JSON-ответы или streaming SSE.
- Использовать reasoning-модели с отдельным
reasoning_content. - Работать с Anthropic Messages API shim для Claude Code / Anthropic SDK.
- Использовать OpenAI Responses API shim для новых OpenAI/Codex-style клиентов.
- Держать отдельные web-сессии для разных агентов/users.
- OpenAI-compatible API:
POST /v1/chat/completions - Anthropic-compatible shim:
POST /v1/messages - OpenAI Responses shim:
POST /v1/responses - Streaming: SSE chunks и обычные non-stream JSON-ответы
- Reasoning output: отдельный
reasoning_contentдля thinking-моделей - Tool calling: парсинг OpenAI tools, Anthropic tools и Responses function tools
- Model capabilities:
GET /v1/model-capabilitiesс alias → real web mode - Agent sessions: отдельная DeepSeek-сессия на
user/ agent id - Session recovery: авто-сброс устаревших chains/sessions
- Zero dependencies: Node.js 18+ для proxy (для
npm run authнужен Node.js 22+), без npm-зависимостей
git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git
cd FreeDeepseekAPI
npm run auth
npm startnpm run auth открывает меню авторизации:
- выберите пункт
1; - войдите в DeepSeek в отдельном Chrome-профиле;
- отправьте короткое сообщение вроде
ok; - вернитесь в терминал и нажмите Enter.
npm run authиспользует встроенный в Node.js WebSocket, поэтому ему нужен Node.js 22+. Сам proxy работает на Node.js 18+; на старом Node импортируйте готовыйdeepseek-auth.jsonчерезnpm run auth:import.
npm start показывает меню запуска:
1— авторизоваться / обновить DeepSeek login2— импортировать auth-файл / cookies3— показать модели и статусы4— запустить proxy (по умолчанию, Enter)5— выйти
Настройки можно положить в .env (шаблон — .env.example): npm start,
npm run auth и npm run doctor читают его сами. Переменные окружения
важнее значений из .env.
Для headless/CI-запуска без меню:
NON_INTERACTIVE=1 npm start
# или
SKIP_ACCOUNT_MENU=1 npm startПо умолчанию сервер слушает:
http://localhost:9655
По умолчанию proxy доступен только с этого компьютера. Для доступа из сети явно задайте адрес и отдельный ключ proxy:
HOST=0.0.0.0 PROXY_API_KEY='replace-with-a-long-random-value' npm startПосле этого передавайте ключ как Authorization: Bearer <key> или
x-api-key: <key> (так его отправляют Anthropic SDK). Без
PROXY_API_KEY non-health endpoints остаются без авторизации, поэтому не
публикуйте такой экземпляр в сеть.
Browser-запросы разрешены с loopback-origin. Если UI открыт на другом адресе,
добавьте его точный origin через запятую, например
PROXY_CORS_ORIGINS=https://ui.example.com,http://192.168.1.20:3000.
git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git
cd FreeDeepseekAPI
npm run auth
npm startЕсли Chrome установлен нестандартно, явно укажите путь:
$env:CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe"
npm run authЕсли Chrome не найден, npm run auth теперь печатает готовые инструкции для Windows/macOS/Linux вместо загадочного stack trace.
git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git
cd FreeDeepseekAPI
CHROME_PATH=$(which chromium) npm run auth
npm startЕсли Chromium называется иначе:
CHROME_PATH=$(which chromium-browser) npm run auth
# или
CHROME_PATH=$(which google-chrome) npm run authСамый надёжный flow без Chrome на сервере:
- На домашнем ПК, где есть GUI/Chrome:
npm run auth- Скопируйте
deepseek-auth.jsonна VPS:
scp deepseek-auth.json user@your-vps:/opt/FreeDeepseekAPI/deepseek-auth.json- На VPS импортируйте/проверьте файл и выставьте безопасные права:
cd /opt/FreeDeepseekAPI
npm run auth:import -- --input ./deepseek-auth.json
npm run doctor -- --offline- Запускайте proxy без интерактивного меню:
NON_INTERACTIVE=1 npm startМожно импортировать не только готовый deepseek-auth.json, но и browser cookie export:
DEEPSEEK_TOKEN="<token>" npm run auth:import -- --input ./cookies.jsonВажно:
deepseek-auth.json— это доступ к вашему DeepSeek Web login. Не коммитьте, не публикуйте, храните с правами0600.
Контейнер предназначен только для non-interactive запуска proxy. Авторизацию
через браузер выполните на хосте командой npm run auth: auth-скрипты и
deepseek-auth.json в образ не копируются.
Запускайте Podman обычным пользователем, без sudo.
- Соберите локальный образ:
podman build --format docker --tag localhost/free-deepseek-api:local --file Containerfile .- Передайте DeepSeek auth и отдельный ключ proxy через Podman secrets:
podman secret create --replace free-deepseek-auth ./deepseek-auth.json
printf 'Proxy API key: '
IFS= read -r -s PROXY_API_KEY
printf '\n'
printf '%s' "$PROXY_API_KEY" |
podman secret create --replace free-deepseek-proxy-key -Используйте длинный случайный ключ. Значение останется в переменной
PROXY_API_KEY текущего shell, чтобы проверить API; оно не попадает в образ или
командную строку Podman.
- Запустите контейнер с минимальными привилегиями:
podman run --detach \
--name free-deepseek-api \
--publish 127.0.0.1:9655:9655 \
--secret free-deepseek-auth,target=deepseek-auth.json,uid=1000,gid=1000,mode=0400 \
--secret free-deepseek-proxy-key,target=proxy-api-key,uid=1000,gid=1000,mode=0400 \
--read-only \
--cap-drop=ALL \
--security-opt=no-new-privileges \
localhost/free-deepseek-api:localВнутри контейнера заранее выставлены NON_INTERACTIVE=1, HOST=0.0.0.0 и
пути к обоим secrets. REQUIRE_PROXY_API_KEY=1 не даст контейнеру запуститься,
если secret с ключом отсутствует или пуст. На хосте порт публикуется только на
127.0.0.1; не убирайте этот адрес без отдельного сетевого firewall/access
policy.
- Проверьте liveness, readiness аккаунта и защищённый endpoint:
podman healthcheck run free-deepseek-api
curl --fail http://127.0.0.1:9655/readyz
curl --fail \
-H "Authorization: Bearer $PROXY_API_KEY" \
http://127.0.0.1:9655/v1/modelsВстроенный healthcheck проверяет локальный /health (жив ли процесс).
--format docker при сборке обязателен: в формате OCI (по умолчанию у Podman)
инструкция HEALTHCHECK молча отбрасывается.
/readyz дополнительно вернёт 503, если ни один DeepSeek auth-аккаунт сейчас
не готов обслуживать запросы. Диагностика контейнера:
podman logs free-deepseek-api
podman inspect --format '{{.State.Health.Status}}' free-deepseek-apiОстановка и удаление контейнера вместе с сохранёнными Podman secrets:
podman stop free-deepseek-api
podman rm free-deepseek-api
podman secret rm free-deepseek-auth free-deepseek-proxy-key
unset PROXY_API_KEYПри ротации auth или proxy key замените соответствующий secret и пересоздайте контейнер, чтобы поведение не зависело от версии Podman.
npm run doctor
# без сетевых запросов к DeepSeek:
npm run doctor -- --offlinedoctor проверяет:
- найден ли
deepseek-auth.json/DEEPSEEK_AUTH_DIR; - валидный ли JSON;
- есть ли
token,cookie,wasmUrl; - безопасные ли права файла на macOS/Linux (
0600); - при обычном запуске — доступен ли DeepSeek PoW endpoint.
Если видите data.biz_data is null, fetch failed, 401/403/429 или Hermes/OpenCode не видит модели — первым делом запускайте npm run doctor.
FreeDeepseekAPI не создаёт новый DeepSeek чат на каждый HTTP-запрос без причины. Логика такая:
- один
x-agent-session,sessionилиuser→ одна DeepSeek chat session; - если session id уже есть — proxy переиспользует его и продолжает chain через
parent_message_id; - агенты (Hermes, Cline, Claude Code, OpenCode…) присылают всю историю в каждом запросе. Proxy запоминает, какие сообщения уже лежат в DeepSeek-чате, и в живой чат отправляет только новые (результаты tools, новый вопрос) плюс короткое напоминание о формате tool call. Раньше весь диалог (до 80 000 символов) отправлялся заново на каждом шаге, контекст чата рос квадратично, и через десяток шагов DeepSeek начинал отвечать пусто, писал «Содержание слишком длинное» или переставал вызывать tools (#23, #30);
- если клиент изменил историю (сжал/отредактировал), сменил system prompt или набор tools — открывается новый DeepSeek-чат с полным промптом;
- auto-reset происходит при TTL, ошибке DeepSeek session, слишком длинной цепочке сообщений или когда в чате накопилось больше
DEEPSEEK_MAX_SESSION_CHARSсимволов (по умолчанию 3 ×DEEPSEEK_MAX_PROMPT_CHARS); - параллельные запросы одного агента (например, фоновые review в Hermes) не смешиваются в одном чате: второй запрос обслуживается в отдельном временном DeepSeek-чате;
- локальная history сохраняется коротким контекстом, чтобы новая DeepSeek session могла продолжить разговор.
- длинные agent-запросы перед отправкой ограничиваются
DEEPSEEK_MAX_PROMPT_CHARS(по умолчанию 80 000 символов): сохраняются начало задачи, свежие tool results и tool adapter; - если клиент уже прислал multi-turn history, локальная recovery-history второй раз не добавляется;
- пустой ответ повторяется максимум
DEEPSEEK_MAX_RETRIESраз (по умолчанию 2), причём на каждом retry контекст уменьшается. - длинная генерация (thinking) не обрывается по таймауту: поток ограничен паузой без данных (
DEEPSEEK_STREAM_IDLE_TIMEOUT_MS, по умолчанию 60 с) и общим лимитомDEEPSEEK_STREAM_MAX_MS(10 минут).
Явно задать agent/session:
curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-agent-session: my-agent" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Привет"}]}'Посмотреть активные sessions:
curl http://localhost:9655/v1/sessionsСбросить одну session:
curl -X POST "http://localhost:9655/reset-session?agent=my-agent"Сбросить все sessions:
curl -X POST "http://localhost:9655/reset-session?agent=all"Почему чаты всё равно появляются в DeepSeek Web: proxy работает через внутренний Web Chat API, а DeepSeek хранит реальные chat sessions у себя. Это нормально для web-proxy. Задача session reuse — не плодить новые чаты без необходимости и аккуратно сбрасываться только когда chain протух/сломался.
Можно подключить несколько auth-файлов. Правильная модель: sticky account per agent/session — proxy не переключает аккаунт внутри живой DeepSeek-сессии. Если аккаунт получил 401/403/429 и ушёл в cooldown, session безопасно сбрасывается и новый запрос может перейти на другой доступный аккаунт.
Вариант 1 — директория с auth-файлами:
mkdir -p accounts
cp deepseek-auth-main.json accounts/main.json
cp deepseek-auth-backup.json accounts/backup.json
chmod 600 accounts/*.json
DEEPSEEK_AUTH_DIR=./accounts NON_INTERACTIVE=1 npm startВариант 2 — список файлов:
DEEPSEEK_AUTH_PATH="./accounts/main.json,./accounts/backup.json" NON_INTERACTIVE=1 npm startКак работает pool:
- новый agent/session получает доступный аккаунт round-robin;
- выбранный аккаунт закрепляется за session (
sticky); - при
401,403,429аккаунт уходит в cooldown, а запрос повторяется на другом готовом аккаунте; - ответ DeepSeek «Too many messages» тоже считается rate limit: аккаунт уходит в короткий cooldown (
DEEPSEEK_RATE_LIMIT_COOLDOWN_MS, по умолчанию 60 с); если других аккаунтов нет, клиент получает429сRetry-After; - если sticky-аккаунт session ушёл в cooldown, старая DeepSeek-сессия сбрасывается, чтобы не долбить rate-limited/expired аккаунт;
- статус аккаунтов виден в
/healthбез путей к auth-файлам и без имён файлов; - auth-файлы должны храниться с правами
0600.
Настроить cooldown:
DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm startПарольный flow из PR #3 можно делать, но безопаснее не хранить пароль и не делать это дефолтом. Нормальная реализация:
npm run auth:consoleспрашивает email/телефон и пароль через hidden prompt.- Пароль держится только в памяти процесса, не пишется в файлы/logs/history.
- Скрипт повторяет Web login flow через
fetch/CDP: получает captcha/verify challenge, отдаёт человеку ссылку/код, ждёт подтверждение. - После успешного login сохраняется только
deepseek-auth.jsonстандартного формата. - Если DeepSeek просит captcha/2FA — скрипт честно говорит “открой ссылку, пройди проверку, нажми Enter”, а не пытается обходить защиту.
- Для VPS лучше режим
auth:console --no-save-password --output deepseek-auth.json.
Минимальный безопасный MVP: console auth только интерактивный, без env-пароля. Допустимый automation-вариант: DEEPSEEK_EMAIL=... npm run auth:console, но пароль всё равно вводится hidden prompt.
curl http://localhost:9655/
curl http://localhost:9655/v1/models
curl http://localhost:9655/v1/model-capabilitiesЕсли всё ок, /health вернёт статус сервера, список поддерживаемых aliases и config_ready: true.
curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Привет! Ответь одной фразой."}],
"stream": false
}'curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-reasoner",
"messages": [{"role": "user", "content": "Реши коротко: почему небо голубое?"}],
"stream": false
}'При stream: true обычный ответ (запрос без tools) отдаётся клиенту по мере
того, как DeepSeek его пишет: сначала размышление, потом текст. Если в запросе
есть tools, ответ может оказаться вызовом инструмента, поэтому proxy дожидается
конца и отдаёт его целиком, а пока ждёт — раз в
DEEPSEEK_STREAM_KEEPALIVE_MS (10 с) шлёт SSE-комментарий : keep-alive, чтобы
клиент не оборвал соединение по таймауту. Ошибка, случившаяся уже после начала
потока, приходит событием error внутри стрима (SDK OpenAI и Anthropic
превращают его в исключение); до начала потока — обычным HTTP-статусом.
Для reasoning-моделей API отдаёт цепочку размышления отдельно от финального ответа:
- non-stream:
choices[0].message.reasoning_content - stream:
choices[0].delta.reasoning_content - usage:
usage.completion_tokens_details.reasoning_tokens
reasoning_tokens — приблизительная оценка по извлечённому DeepSeek Web THINK-тексту, потому что web stream не отдаёт официальный token usage по reasoning отдельно.
curl -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat-search",
"messages": [{"role": "user", "content": "Найди свежий факт про DeepSeek и ответь кратко."}],
"stream": false
}'curl -N -X POST http://localhost:9655/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Напиши короткую шутку."}],
"stream": true
}'curl -X POST http://localhost:9655/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"max_tokens": 512,
"messages": [{"role": "user", "content": "Ответь ровно OK"}],
"stream": false
}'Для Claude Code можно указывать backend напрямую:
export ANTHROPIC_BASE_URL="http://127.0.0.1:9655"
export ANTHROPIC_AUTH_TOKEN="dummy-key"
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
claude --model deepseek-chatcurl -X POST http://localhost:9655/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"input": "Ответь ровно OK",
"stream": false
}'FreeDeepseekAPI принимает:
- OpenAI
tools; - Anthropic
tools; - Responses API function tools.
Прокси просит DeepSeek вернуть строгий JSON tool call, но также умеет парсить fallback-форматы:
TOOL_CALL:- fenced JSON with an explicit
tool_call,tool_calls, orfunction_callenvelope <tool_call>...</tool_call>- DeepSeek DSML (
<|DSML|tool_calls>...) и Web-вариант с<||DSML|| Tool Calls>
Типичные ошибки модели исправляются при разборе: неэкранированные обратные
слеши в Windows-путях ("C:\git\src\index.ts") и настоящие переводы строк
внутри строковых аргументов. Блоки кода с другим языком (```csharp,
```python) никогда не считаются tool call.
GET /v1/models возвращает только aliases, которые сейчас проверены и работают через этот proxy.
10 сентября 2026 DeepSeek объединил в веб-чате режимы «Быстрый», «Эксперт» и
«Распознавание» в один умный режим на модели DeepSeek-V4.1-Flash, а 14
сентября отключил V4 Pro. Переключателя моделей больше нет, остались только
«Глубокое мышление» (thinking_enabled) и «Поиск» (search_enabled) (#31).
Поэтому все aliases теперь отправляют в Web API один и тот же
model_type: "default" и отличаются только этими двумя флагами. Старые имена
(deepseek-expert, deepseek-v4-pro, …) оставлены, чтобы не ломать конфиги
клиентов, и помечены в /v1/model-capabilities как deprecated. Если DeepSeek
снова поменяет значение model_type, его можно переопределить через
DEEPSEEK_MODEL_TYPE (пустое значение — не отправлять поле).
| Alias | Глубокое мышление | Web search | Комментарий |
|---|---|---|---|
deepseek-chat |
нет | нет | базовый chat |
deepseek-default |
нет | нет | совместимый alias |
deepseek-v4-flash |
нет | нет | имя официального API |
deepseek-v4.1-flash |
нет | нет | актуальная модель веб-чата |
deepseek-v3 |
нет | нет | устаревший alias |
deepseek-reasoner |
да | нет | thinking_enabled=true |
deepseek-r1 |
да | нет | R1-compatible alias |
deepseek-chat-search |
нет | да | web search |
deepseek-default-search |
нет | да | web search alias |
deepseek-reasoner-search |
да | да | reasoning + search |
deepseek-r1-search |
да | да | R1-compatible + search |
deepseek-expert |
да | нет | устаревший: «Эксперт» влит в единый режим |
deepseek-v4-pro |
да | нет | устаревший: V4 Pro отключён, отвечает V4.1-Flash |
deepseek-expert-search |
да | да | устаревший alias, теперь работает |
deepseek-vision по-прежнему недоступен: веб-чат понимает картинки в едином
режиме, но proxy пока не загружает изображения в DeepSeek. Картинки из
запросов заменяются коротким маркером [Image attached; not visible through this proxy] вместо вставки base64 в промпт.
Полный маппинг:
curl http://localhost:9655/v1/model-capabilities| Method | Path | Назначение |
|---|---|---|
GET |
/ или /health |
статус proxy |
GET |
/v1/models |
список рабочих OpenAI-compatible aliases |
GET |
/v1/model-capabilities |
полный маппинг aliases, real model, capabilities |
POST |
/v1/chat/completions |
OpenAI-compatible Chat Completions |
POST |
/v1/messages |
Anthropic Messages API shim |
POST |
/v1/responses |
OpenAI Responses API shim |
GET |
/v1/sessions |
активные локальные agent sessions |
POST |
/reset-session?agent=<id> |
сбросить одну session |
POST |
/reset-session?agent=all |
сбросить все sessions |
Base URL для Open WebUI в Docker:
http://host.docker.internal:9655/v1
Для локального запуска без Docker:
http://localhost:9655/v1
Если PROXY_API_KEY не задан, API key можно указать любой. Если ключ задан,
клиент должен передавать именно его — proxy проверяет bearer token перед
доступом к моделям, сессиям и completions.
npm run auth
npm startЕсли DeepSeek начал отвечать 401, 403 или просит новый PoW/session — повторите npm run auth и обновите сохранённую browser-сессию.
Локальные файлы авторизации не должны попадать в GitHub:
deepseek-auth.json.chrome-for-testing-profile-deepseek/.env
Они уже добавлены в .gitignore.
Синтаксическая проверка, unit-тесты и end-to-end тесты против локального
mock-сервера DeepSeek (сеть и аккаунт не нужны; для node --test нужен
Node.js 18.13+):
npm testLive smoke-тесты против запущенного локального proxy (если задан
PROXY_API_KEY, он берётся из окружения):
BASE_URL=http://127.0.0.1:9655 MODEL=deepseek-chat npm run test:liveFreeDeepseekAPI — экспериментальный web-chat proxy для локального использования и интеграций. Он зависит от текущего контракта DeepSeek Web Chat, поэтому при изменениях на стороне DeepSeek может потребоваться обновление auth/session logic или model mapping.
Если что-то перестало работать:
- обновите логин через
npm run auth; - проверьте
/v1/model-capabilities; - повторите запрос на свежей сессии;
- если проблема сохраняется — вероятно, DeepSeek изменил внутренний Web API.
ForgetMeAI · Telegram