Локальный API-прокси сервер для работы с Qwen AI через браузерную эмуляцию. Позволяет использовать модели Qwen без официального API-ключа.
- Бесплатный доступ: Используйте модели Qwen без оплаты API-ключа
- Полная совместимость: Поддержка OpenAI-совместимого интерфейса для простой интеграции
- Возможность загрузки файлов и получение ссылки прямо из прокси
- 🆕 API v2: Обновлено на новый Qwen API с улучшенной системой контекста
Прокси переведён на Qwen API v2. Основные изменения:
- ✅ История чатов хранится на серверах Qwen (не локально)
- ✅ Новая система контекста через
parentId - ✅ Автоматическое создание чатов
- ✅ Старые эндпоинты работают с расширенным интерфейсом
{
"chatId": "a606fcac-8351-4f1f-80e7-f2f81a88e06a", // ID чата
"parentId": "7f637df8-e696-43d9-94b3-40b767da117b" // Для продолжения диалога
}// Первое сообщение
const res1 = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: "Привет!" })
});
const data1 = await res1.json();
// Второе сообщение с контекстом
const res2 = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({
message: "Как дела?",
chatId: data1.chatId,
parentId: data1.parentId // Из предыдущего ответа!
})
});- 🚀 Быстрый старт
- 💡 Возможности
- 📘 API Reference
- 📝 Примеры использования
- 🔄 Работа с контекстом
- 🔌 Совместимость с OpenAI API
- 🔧 Особенности реализации
- Клонировать репозиторий
- Установить зависимости:
npm installnpm startТакже доступен файл быстрого запуска:
start.bat
- Выполните авторизацию и соберите токены:
npm run auth- После сохранения токенов в папке
session/запустите контейнер:
docker compose up --build -d- Приложение будет доступно на
http://localhost:3264/api.
⚙️ Контейнер запускается с переменной
SKIP_ACCOUNT_MENU=true, поэтому интерактивное меню не блокирует старт. Папкиsession/,logs/иuploads/примонтированы в контейнер как тома, что позволяет повторно использовать сохранённые токены и журналы.
⚠️ Важно: если файлsrc/Authorization.txtпустой, авторизация отключена.
-
Файл
src/Authorization.txt- Создаётся автоматически при первом запуске если его нет.
- Внутри уже есть подробный шаблон-инструкция.
- Один токен на строку. Пустые строки и строки, начинающиеся с
#, игнорируются.
-
Отключить авторизацию – оставьте файл пустым. Middleware пропустит все запросы.
-
Проверка на стороне клиента
Отправляйте HTTP-заголовок:
Authorization: Bearer <your_token>Пример cURL:
curl -X POST http://localhost:3264/api/chat \ -H "Content-Type: application/json" \ -H "Authorization: Bearer my-secret-token-123" \ -d '{"message":"Привет"}'
При старте npm start появляется интерактивное меню:
Список аккаунтов:
N | ID | Статус
1 | acc_1752745840684 | ✅ OK
2 | acc_1752745890062 | ❌ INVALID
=== Меню ===
1 - Добавить новый аккаунт
2 - Перелогинить аккаунт с истекшим токеном
3 - Запустить прокси (Enter по умолчанию)
4 - Удалить аккаунт
Статусы:
| Значок | Значение | Поведение |
|---|---|---|
| ✅ OK | токен активен | используется в ротации |
| ⏳ WAIT | токен временно заблокирован (RateLimited) | пропускается до истечения тайм-аута |
| ❌ INVALID | токен просрочен (401 Unauthorized) | недоступен, выберите пункт 2 для повторного входа |
Пункты меню:
- Добавить новый аккаунт – откроется браузер, авторизуйтесь, токен будет сохранён.
- Перелогинить аккаунт с истекшим токеном – выберите нужный ID, откроется браузер для повторного входа, статус сменится на ✅.
- Запустить прокси – доступно, если есть хотя бы один статус ✅ или ⏳.
- Удалить аккаунт – полностью удаляет токен и папку сессии.
Файлы:
session/accounts/<id>/token.txt– токен аккаунтаsession/tokens.json– реестр аккаунтов и состоянийnpm run auth– отдельный скрипт для управления аккаунтами без запуска сервера (то же меню, плюс CLI-аргументы--list,--add,--relogin,--remove)
| Переменная | Значение по умолчанию | Назначение |
|---|---|---|
PORT |
3264 |
Порт HTTP-сервера |
HOST |
0.0.0.0 |
Адрес привязки сервера |
SKIP_ACCOUNT_MENU |
false |
При значении true отключает интерактивное меню запуска (нужно для Docker/CI) |
SKIP_ACCOUNT_MENUавтоматически активирован в Docker Compose. Если при старте нет валидных токенов, сервер завершит работу с подсказкой запуститьnpm run auth.
Автоматическая ротация:
- запросы распределяются по токенам циклически.
- При ответе 429 RateLimited токен получает ⏳ WAIT на указанное время.
- При ответе 401 Unauthorized токен помечается ❌ INVALID.
- Если все токены недействительны – прокси завершает работу, запустите его и перелогиньтесь.
Этот проект позволяет:
- Использовать модели Qwen AI через локальный API
- Сохранять контекст диалогов между запросами
- Управлять диалогами через API
- Выбирать различные модели Qwen для генерации ответов
- Отправлять изображения для анализа моделью
- Использовать OpenAI-совместимый API с поддержкой streaming режима
| Эндпоинт | Метод | Описание |
|---|---|---|
/api/chat |
POST | Отправка сообщения с поддержкой chatId и parentId |
/api/chat/completions |
POST | OpenAI-совместимый эндпоинт, возвращает chatId/parentId |
/api/models |
GET | Получение списка доступных моделей |
/api/status |
GET | Проверка статуса авторизации и аккаунтов |
/api/files/upload |
POST | Загрузка изображения для использования в запросах |
/api/chats |
POST | Создание нового чата на серверах Qwen |
GET /api/chats- список чатовGET /api/chats/:chatId- история чатаDELETE /api/chats/:chatId- удаление чатаPUT /api/chats/:chatId/rename- переименованиеPOST /api/chats/cleanup- автоудаление
Причина: чаты теперь управляются на серверах Qwen
| Эндпоинт | Использование контекста | Формат запроса | Совместимость |
|---|---|---|---|
/api/chat |
Контекст управляется через chatId + parentId. История хранится на серверах Qwen. |
Упрощённый message + chatId + parentId |
Нативный для прокси |
/api/chat/completions |
Поддерживает chatId + parentId в запросе. Возвращает их в ответе для продолжения. |
Массив messages (OpenAI format) + опционально chatId/parentId |
OpenAI SDK |
{
"message": "Текст сообщения",
"model": "qwen-max-latest",
"chatId": "идентификатор_чата",
"parentId": "response_id_из_предыдущего_ответа"
}{
"messages": [
{"role": "user", "content": "Привет, как дела?"}
],
"model": "qwen-max-latest",
"chatId": "идентификатор_чата",
"parentId": "response_id_из_предыдущего_ответа"
}Новая система:
- История хранится на серверах Qwen, не локально
- Контекст управляется через
chatId+parentId parentId- этоresponse_idиз предыдущего ответа
Пример диалога:
// 1. Первое сообщение
const res1 = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: "Сколько будет 2+2?" })
});
const data1 = await res1.json();
// Ответ: { chatId: "abc-123", parentId: "xyz-789", ... }
// 2. Второе сообщение (с контекстом)
const res2 = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({
message: "А результат плюс 3?",
chatId: data1.chatId, // Тот же чат
parentId: data1.parentId // Из предыдущего ответа!
})
});
// Модель помнит контекст и ответит "7"Новое в v2: Поддержка системных сообщений для настройки поведения модели!
Системные инструкции передаются через поле role: "system" в массиве messages. Это позволяет задать модели контекст, стиль общения, правила поведения и т.д.
Пример:
// Запрос с системной инструкцией
const response = await fetch('/api/chat/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [
{
role: "system",
content: "Ты - опытный программист на Python. Отвечай кратко и предоставляй примеры кода."
},
{
role: "user",
content: "Как отсортировать список в Python?"
}
],
model: "qwen-max-latest"
})
});Как работает:
systemmessage извлекается из массива и передаётся отдельным параметром в Qwen API v2- Может использоваться в обоих эндпоинтах:
/api/chatи/api/chat/completions - System message применяется ко всему чату и влияет на все последующие ответы
Примеры использования:
// 1. Ролевая инструкция
{
"messages": [
{"role": "system", "content": "Ты - эксперт по машинному обучению"},
{"role": "user", "content": "Объясни, что такое градиентный спуск"}
]
}
// 2. Стиль ответов
{
"messages": [
{"role": "system", "content": "Отвечай как пират"},
{"role": "user", "content": "Как дела?"}
]
}
// 3. Формат вывода
{
"messages": [
{"role": "system", "content": "Всегда отвечай в формате JSON"},
{"role": "user", "content": "Дай информацию о Python"}
]
}Прокси поддерживает отправку сообщений с изображениями:
{
"message": [
{
"type": "text",
"text": "Опишите объекты на этом изображении"
},
{
"type": "image",
"image": "URL_ИЗОБРАЖЕНИЯ"
}
],
"model": "qwen-vl-max",
"chatId": "идентификатор_чата",
"parentId": "response_id"
}POST http://localhost:3264/api/files/upload
Формат запроса: multipart/form-data
Параметры:
file- файл изображения (поддерживаются форматы: jpg, jpeg, png, gif, webp)
Пример использования с curl:
curl -X POST http://localhost:3264/api/files/upload \
-F "file=@/путь/к/изображению.jpg"Пример ответа:
{
"imageUrl": "https://cdn.qwenlm.ai/user-id/file-id_filename.jpg?key=..."
}Для отправки изображений через API прокси необходимо сначала получить URL изображения. Это можно сделать двумя способами:
Отправьте POST запрос на эндпоинт /api/files/upload для загрузки изображения, как описано выше.
- Загрузите изображение в официальном веб-интерфейсе Qwen (https://chat.qwen.ai/)
- Откройте инструменты разработчика в браузере (F12 или Ctrl+Shift+I)
- Перейдите на вкладку "Network" (Сеть)
- Найдите запрос к API Qwen, содержащий ваше изображение (обычно это запрос GetsToken)
- В теле запроса найдите URL изображения, который выглядит примерно так:
https://cdn.qwenlm.ai/user-id/file-id_filename.jpg?key=... - Скопируйте этот URL для использования в вашем API-запросе
POST http://localhost:3264/api/chats
Тело запроса:
{
"name": "Название диалога"
}Ответ:
{
"chatId": "уникальный_идентификатор"
}GET http://localhost:3264/api/chats
GET http://localhost:3264/api/chats/:chatId
DELETE http://localhost:3264/api/chats/:chatId
PUT http://localhost:3264/api/chats/:chatId/rename
Тело запроса:
{
"name": "Новое название чата"
}POST http://localhost:3264/api/chats/cleanup
Тело запроса (все параметры опциональны):
{
"olderThan": 604800000, // Удалить чаты старше указанного времени (в мс), например 7 дней
"userMessageCountLessThan": 3, // Удалить чаты с менее чем 3 сообщениями от пользователя
"messageCountLessThan": 5, // Удалить чаты с менее чем 5 сообщениями всего
"maxChats": 50 // Оставить только 50 самых новых чатов
}▶️ Пример простого текстового запроса
curl -X POST http://localhost:3264/api/chat \
-H "Content-Type: application/json" \
-d '{
"message": "Что такое искусственный интеллект?",
"model": "qwen-max-latest"
}'▶️ Пример запроса в формате официального API
curl -X POST http://localhost:3264/api/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "Что такое искусственный интеллект?"}
],
"model": "qwen-max-latest"
}'▶️ Пример загрузки изображения и отправки запроса с ним
# Шаг 1: Загрузка изображения
UPLOAD_RESPONSE=$(curl -s -X POST http://localhost:3264/api/files/upload \
-F "file=@/путь/к/изображению.jpg")
# Шаг 2: Извлечение URL изображения
IMAGE_URL=$(echo $UPLOAD_RESPONSE | grep -o '"imageUrl":"[^"]*"' | sed 's/"imageUrl":"//;s/"//')
# Шаг 3: Отправка запроса с изображением
curl -X POST http://localhost:3264/api/chat \
-H "Content-Type: application/json" \
-d '{
"message": [
{ "type": "text", "text": "Опишите объекты на этом изображении" },
{ "type": "image", "image": "'$IMAGE_URL'" }
],
"model": "qwen3-235b-a22b"
}'▶️ Пошаговое руководство через Postman
-
Загрузка изображения:
- Создайте новый запрос POST к
http://localhost:3264/api/files/upload - Выберите вкладку "Body"
- Выберите тип "form-data"
- Добавьте ключ "file" и выберите тип "File"
- Загрузите изображение, нажав на кнопку "Select Files"
- Нажмите "Send"
Ответ будет содержать URL изображения:
{ "imageUrl": "https://cdn.qwenlm.ai/user-id/file-id_filename.jpg?key=..." } - Создайте новый запрос POST к
-
Использование изображения в запросе:
- Создайте новый запрос POST к
http://localhost:3264/api/chat - Выберите вкладку "Body"
- Выберите тип "raw" и формат "JSON"
- Вставьте следующий JSON, заменив
URL_ИЗОБРАЖЕНИЯна полученный URL:
{ "message": [ { "type": "text", "text": "Опишите объекты на этом изображении" }, { "type": "image", "image": "URL_ИЗОБРАЖЕНИЯ" } ], "model": "qwen3-235b-a22b" }- Нажмите "Send"
- Создайте новый запрос POST к
-
Запрос через OpenAI-совместимый эндпоинт:
- Создайте новый запрос POST к
http://localhost:3264/api/chat/completions - Выберите вкладку "Body"
- Выберите тип "raw" и формат "JSON"
- Вставьте следующий JSON, заменив
URL_ИЗОБРАЖЕНИЯна полученный URL:
{ "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Опиши, что изображено на этой картинке?" }, { "type": "image", "image": "URL_ИЗОБРАЖЕНИЯ" } ] } ], "model": "qwen3-235b-a22b" }- Нажмите "Send"
- Создайте новый запрос POST к
-
Запрос с потоковым режимом (streaming):
- Используйте тот же URL и тело запроса, но добавьте параметр
"stream": true - Примечание: для корректного отображения потока в Postman, проверьте опцию "Preserve log" в консоли
- Используйте тот же URL и тело запроса, но добавьте параметр
Система автоматически сохраняет историю диалога и отправляет ее в каждом запросе к API Qwen. Это позволяет моделям учитывать предыдущие сообщения при генерации ответов.
- Первый запрос (без указания
chatId):
{
"message": "Привет, как тебя зовут?"
}- Ответ (содержит
chatId):
{
"chatId": "abcd-1234-5678",
"choices": [...]
}- Последующие запросы (с указанием полученного
chatId):
{
"message": "Сколько будет 2+2?",
"chatId": "abcd-1234-5678"
}Прокси поддерживает эндпоинт, совместимый с OpenAI API для подключения клиентов, которые работают с OpenAI API:
POST /api/chat/completions
-
Создание нового чата для каждого запроса: Каждый запрос к
/chat/completionsсоздаёт новый чат в системе с именем "OpenAI API Chat". -
Сохранение полной истории сообщений: Все сообщения из запроса (включая системные, пользовательские и сообщения ассистента) сохраняются в истории чата.
-
Поддержка системных сообщений: Прокси корректно обрабатывает и сохраняет системные сообщения (
role: "system"), которые часто используются для настройки поведения модели.
Пример запроса с системным сообщением:
{
"messages": [
{"role": "system", "content": "Ты эксперт по JavaScript. Отвечай только на вопросы о JavaScript."},
{"role": "user", "content": "Как создать класс в JavaScript?"}
],
"model": "qwen-max-latest"
}Прокси поддерживает режим потоковой передачи ответов (streaming), что позволяет получать ответы по частям в режиме реального времени:
{
"messages": [
{"role": "user", "content": "Напиши длинный рассказ о космосе"}
],
"model": "qwen-max-latest",
"stream": true
}При использовании streaming режима, ответ будет возвращаться постепенно в формате Server-Sent Events (SSE), совместимом с OpenAI API.
▶️ Пример использования с OpenAI Node.js SDK
// Пример использования с OpenAI Node.js SDK
import OpenAI from 'openai';
import fs from 'fs';
import axios from 'axios';
const openai = new OpenAI({
baseURL: 'http://localhost:3264/api', // Базовый URL прокси
apiKey: 'dummy-key', // Не требуется реальный ключ, но поле обязательное для библиотеки
});
// Запрос без streaming
const completion = await openai.chat.completions.create({
messages: [{ role: 'user', content: 'Привет, как дела?' }],
model: 'qwen-max-latest', // Используемая модель Qwen
});
console.log(completion.choices[0].message);
// Запрос со streaming
const stream = await openai.chat.completions.create({
messages: [{ role: 'user', content: 'Расскажи длинную историю о космосе' }],
model: 'qwen-max-latest',
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
// Загрузка и использование изображения
async function uploadAndAnalyzeImage(imagePath) {
// Загрузка изображения через API прокси
const formData = new FormData();
formData.append('file', fs.createReadStream(imagePath));
const uploadResponse = await axios.post('http://localhost:3264/api/files/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
const imageUrl = uploadResponse.data.imageUrl;
// Создание запроса с изображением
const completion = await openai.chat.completions.create({
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'Опиши, что изображено на этой картинке?' },
{ type: 'image', image: imageUrl }
]
}
],
model: 'qwen3-235b-a22b',
});
console.log(completion.choices[0].message.content);
}
// Использование: uploadAndAnalyzeImage('./image.jpg');Ограничения совместимости:
- Некоторые специфичные для OpenAI параметры (например,
logprobs,functionsи т.д.) не поддерживаются.- Скорость потоковой передачи может отличаться от оригинального OpenAI API.