Wu Xianzhi APIПримеры кода
Полный код примеров вызова API без цензуры: Python, Node.js и cURL
Это руководство по вызову API без цензуры для разработчиков. API совместим с Chat Completions от OpenAI, поэтому вы можете использовать знакомый SDK OpenAI, просто изменив две строки конфигурации. В этой статье мы подробно разберём самые частые сценарии: базовые запросы, потоковую передачу, вызов функций, повторные запросы при ошибках, контроль расходов через max_tokens и поддержание контекста в многопольных диалогах. Весь код можно скопировать и запустить, ключ API берётся из переменных окружения.
Обновлено
Ключевые моменты
- Измените base_url на https://api.wuxianzhiapi.com/v1,模型名写 uncensored, официальный SDK openai не требует других изменений
- Последний блок потокового ответа содержит статистику использования: массив choices пуст, при чтении необходимо сначала выполнить проверку
- Применяйте экспоненциальную задержку только для 429 и 503; повторные запросы при 400/401/402/403 не имеют смысла
- API не сохраняет состояние, историю диалога нужно отправлять заново, а расходы контролировать с помощью max_tokens и обрезки контекста
Базовая информация об API и переменные окружения
Запомните несколько постоянных параметров, которые будут использоваться во всех примерах. Base URL — это https://api.wuxianzhiapi.com/v1, имя модели всегда uncensored, а аутентификация выполняется через заголовок Authorization: Bearer <ключ>. Доступны только два эндпоинта: POST /v1/chat/completions для диалога и GET /v1/models для проверки доступности модели. Формат запросов и ответов соответствует Chat Completions от OpenAI, поэтому в официальном SDK openai достаточно изменить base_url и ключ, а бизнес-код практически не нужно менять.
Ключ отображается сразу после регистрации на /get-api-key/. Для входа достаточно логина и пароля. Новый аккаунт получает пробный баланс в размере $0.50, который действителен в течение 7 дней и не требует привязки карты. Во всех примерах ключ берётся из переменной окружения WUXIANZHI_API_KEY. Не храните ключ в репозитории кода и не выкладывайте его на фронтенд. Ограничения: контекстное окно на 100 000 токенов (сумма промпта и ответа), размер тела запроса не более 8 МБ, лимит запросов — 300 в минуту на ключ.
export WUXIANZHI_API_KEY="把你的密钥放这里"
# 确认连通性,应返回包含 uncensored 的模型列表
curl https://api.wuxianzhiapi.com/v1/models \
-H "Authorization: Bearer $WUXIANZHI_API_KEY"Если на этом шаге вы получаете 401, значит, ключ введён неверно или переменная окружения не подхватилась. Разберитесь с этим, прежде чем переходить к коду. Полное описание параметров см. в документации API.
cURL: минимальный рабочий запрос
Рекомендуем сначала протестировать cURL. Это изолирует проблемы сети, ключа и формата запроса от вашего кода. В ответе max_tokens, текст в choices[0].message.content, токены в usage.
curl https://api.wuxianzhiapi.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WUXIANZHI_API_KEY" \
-d '{
"model": "uncensored",
"messages": [
{"role": "system", "content": "你是一个直来直去的写作助手。"},
{"role": "user", "content": "用三句话描述一场暴雨前的小镇。"}
],
"max_tokens": 300
}'Китайские иероглифы в JSON не требуют ручного экранирования, если заголовок указывает совместимый с UTF-8 формат JSON. В PowerShell Windows сложно обрабатывать кавычки, поэтому сохраните тело запроса в body.json и отправьте через -d @body.json.
Полные вызовы на Python и Node.js
Используйте пакет openai (v1+). Установите: pip install openai. Передайте base_url и api_key. Сохраните как chat.py.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.wuxianzhiapi.com/v1",
api_key=os.environ["WUXIANZHI_API_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "你是一个直来直去的写作助手。"},
{"role": "user", "content": "用三句话描述一场暴雨前的小镇。"},
],
max_tokens=300,
)
print(resp.choices[0].message.content)
print("输入 tokens:", resp.usage.prompt_tokens, "输出 tokens:", resp.usage.completion_tokens)Используйте npm-пакет openai (v4+). Установите: npm install openai. Используйте await в файле chat.mjs или настройте package.json: "type": "module".
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.wuxianzhiapi.com/v1",
apiKey: process.env.WUXIANZHI_API_KEY,
});
const resp = await client.chat.completions.create({
model: "uncensored",
messages: [
{ role: "system", content: "你是一个直来直去的写作助手。" },
{ role: "user", content: "用三句话描述一场暴雨前的小镇。" },
],
max_tokens: 300,
});
console.log(resp.choices[0].message.content);
console.log("用量:", resp.usage);Структура кода одинакова. Для миграции замените инициализацию и укажите модель uncensored. См. руководство по миграции.
Как читать потоковую передачу (SSE)
При генерации длинных текстов или создании интерфейсов чата обязательно используйте потоковую передачу, иначе пользователь будет ждать окончания генерации. Установите stream: true: сервер будет отправлять данные блоками через SSE (Server-Sent Events). Каждая строка имеет формат data: {...}, а последняя — data: [DONE]. Официальный SDK уже парсит эти данные, вам нужно лишь их перебрать.
В конце потока есть блок с usage и пустым choices. Не берите chunk.choices[0] без проверки на пустоту.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.wuxianzhiapi.com/v1",
api_key=os.environ["WUXIANZHI_API_KEY"],
)
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "写一段 200 字左右的悬疑小说开头。"}],
max_tokens=600,
stream=True,
)
usage = None
for chunk in stream:
if chunk.usage: # 最后一块:用量统计
usage = chunk.usage
if not chunk.choices: # 用量块没有 choices
continue
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()
if usage:
print("输入", usage.prompt_tokens, "输出", usage.completion_tokens)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.wuxianzhiapi.com/v1",
apiKey: process.env.WUXIANZHI_API_KEY,
});
const stream = await client.chat.completions.create({
model: "uncensored",
messages: [{ role: "user", content: "写一段 200 字左右的悬疑小说开头。" }],
max_tokens: 600,
stream: true,
});
let usage = null;
for await (const chunk of stream) {
if (chunk.usage) usage = chunk.usage;
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
console.log("\n用量:", usage);Для отладки SSE используйте cURL с флагом -N для отключения буферизации.
curl -N https://api.wuxianzhiapi.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WUXIANZHI_API_KEY" \
-d '{"model":"uncensored","stream":true,"max_tokens":100,
"messages":[{"role":"user","content":"数到五。"}]}'Если вы проксируете потоковый ответ через обратный прокси-сервер, такой как Nginx, не забудьте отключить буферизацию ответов для этого пути; иначе фронтенд получит весь ответ сразу, а не посимвольно.
Вызов функций: инструменты и передача результатов
Используйте параметры tools. Модель возвращает параметры в message.tool_calls. Отправьте результат с role: "tool" для финального ответа.
tool_choice: "auto" (по умолчанию), {"type": "function", "function": {"name": "get_weather"}} (принудительно), "none" (запрет). Пример: запрос → tool_calls → результат.
import json, os
from openai import OpenAI
client = OpenAI(
base_url="https://api.wuxianzhiapi.com/v1",
api_key=os.environ["WUXIANZHI_API_KEY"],
)
def get_weather(city: str) -> dict:
# 这里用假数据代替真实的天气接口
return {"city": city, "temp_c": 18, "condition": "多云"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "城市名"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "杭州现在天气怎么样?"}]
first = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, tool_choice="auto", max_tokens=500
)
msg = first.choices[0].message
if msg.tool_calls:
messages.append(msg) # 必须把带 tool_calls 的 assistant 消息原样放回历史
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = get_weather(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
final = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, max_tokens=500
)
print(final.choices[0].message.content)
else:
print(msg.content)Ошибки: 1) не добавляйте tool_calls в историю; 2) несовпадение tool_call_id; 3) парсите параметры через json.loads. Node.js аналогичен.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.wuxianzhiapi.com/v1",
apiKey: process.env.WUXIANZHI_API_KEY,
});
const getWeather = (city) => ({ city, temp_c: 18, condition: "多云" });
const tools = [{
type: "function",
function: {
name: "get_weather",
description: "查询指定城市的当前天气",
parameters: {
type: "object",
properties: { city: { type: "string", description: "城市名" } },
required: ["city"],
},
},
}];
const messages = [{ role: "user", content: "杭州现在天气怎么样?" }];
const first = await client.chat.completions.create({
model: "uncensored", messages, tools, tool_choice: "auto", max_tokens: 500,
});
const msg = first.choices[0].message;
if (msg.tool_calls?.length) {
messages.push(msg);
for (const call of msg.tool_calls) {
const args = JSON.parse(call.function.arguments);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(getWeather(args.city)),
});
}
const final = await client.chat.completions.create({
model: "uncensored", messages, tools, max_tokens: 500,
});
console.log(final.choices[0].message.content);
} else {
console.log(msg.content);
}
Обработка ошибок и повторные запросы: экспоненциальная задержка при 429 и 503
Все ошибки возвращают единый JSON: {"error":{"code":...,"message":...}}. Повторные запросы (retry) нужны только для 429 (превышен лимит 300 запросов в минуту) и 503 (upstream_busy — модель временно занята). Также стоит повторить запрос при тайм-ауте соединения. Остальные ошибки не стоит повторять: 400 — ошибка запроса (например, max_tokens превышает 100k), 401 — неверный ключ, 402 no_credit — исчерпан баланс или истёк пробный период, 403 content_blocked — контент заблокирован.
Стратегия с экспоненциальной задержкой и случайным разбросом: 1-я попытка — ~1 с, 2-я — ~2 с, 3-я — ~4 с. Установите лимит и максимальное число попыток, чтобы избежать одновременных повторных запросов при параллельных задачах. Официальный SDK имеет параметр max_retries для 429 и 5xx.
import os, random, time
import openai
from openai import OpenAI
# max_retries=0:关闭 SDK 自带重试,完全由下面的函数控制
client = OpenAI(
base_url="https://api.wuxianzhiapi.com/v1",
api_key=os.environ["WUXIANZHI_API_KEY"],
max_retries=0,
timeout=60,
)
def chat_with_retry(messages, max_attempts=5, **kwargs):
for attempt in range(max_attempts):
try:
return client.chat.completions.create(
model="uncensored", messages=messages, **kwargs
)
except (openai.RateLimitError, openai.InternalServerError,
openai.APIConnectionError, openai.APITimeoutError) as e:
if attempt == max_attempts - 1:
raise
wait = min(30, 2 ** attempt) + random.uniform(0, 1)
print(f"{type(e).__name__},{wait:.1f} 秒后重试(第 {attempt + 1} 次)")
time.sleep(wait)
except openai.APIStatusError as e:
# 400 / 401 / 402 / 403 / 404:重试无效,直接交给上层处理
print("不可重试:", e.status_code, e.response.text)
raise
resp = chat_with_retry([{"role": "user", "content": "你好"}], max_tokens=100)
print(resp.choices[0].message.content)В Node.js также можно напрямую увеличить значение maxRetries, SDK самостоятельно обработает 429 и 5xx. Для различения ошибок используйте проверку error.status.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.wuxianzhiapi.com/v1",
apiKey: process.env.WUXIANZHI_API_KEY,
maxRetries: 5,
timeout: 60_000,
});
try {
const resp = await client.chat.completions.create({
model: "uncensored",
messages: [{ role: "user", content: "你好" }],
max_tokens: 100,
});
console.log(resp.choices[0].message.content);
} catch (err) {
if (err instanceof OpenAI.APIError) {
if (err.status === 402) console.error("余额不足,请充值后再试");
else if (err.status === 403) console.error("内容被拦截:", err.message);
else console.error("请求失败:", err.status, err.message);
} else {
throw err;
}
}Также обратите внимание: если потоковый запрос прерывается, полученные данные нужно сохранить самостоятельно. Повторный запрос начнет генерацию с начала и будет оплачен заново, поэтому для длинных текстов рекомендуется разбивать запросы на части, а не запрашивать большой объем текста за один раз.
Контроль стоимости и длины с помощью max_tokens
Правила тарификации просты: входные токены — $0.25 за миллион, выходные — $1.00 за миллион. Предоплаченный баланс, нет ежемесячной платы, баланс не сгорает. Стоимость вывода в 4 раза выше стоимости ввода, поэтому экономия достигается за счёт оптимизации вывода. По умолчанию max_tokens равен 2048, максимум — 32 000. Если вам нужен короткий ответ, установите значение 200 или 300, чтобы ограничить расходы и предотвратить многословие модели.
Расчёт: при установке max_tokens в 1 000 максимальная стоимость вывода составит $0,001; при максимальном значении 32 000 — $0,016. Пробный баланс в $0,50 эквивалентен примерно 500 000 выходных токенов или 2 000 000 входных токенов, чего хватит на множество запусков примеров из этой статьи. Сумма промпта и max_tokens не должна превышать 100 000, иначе вернётся ошибка 400. При длинном промпте уменьшайте лимит вывода. Подробнее о ценах на странице цен.
Важный нюанс: если ответ был обрезан, поле finish_reason будет равно "length". Это означает, что исчерпан лимит max_tokens, а не то, что модель сама завершила генерацию. При написании длинных текстов проверяйте это поле, чтобы решить, нужно ли продолжать генерацию.
Многопоточные диалоги: самостоятельное управление контекстом
Сервис без состояния: сервер не запоминает предыдущие запросы. Для продолжения диалога отправляйте историю сообщений: system, затем user и assistant. Каждая итерация увеличивает объём входных данных, что повышает стоимость запроса.
Общий объём контекста ограничен 100 000 токенами (включая текущий вывод), поэтому длинные диалоги необходимо обрезать. Самый простой способ — сохранить системные сообщения и несколько последних раундов. Более сложный подход: сжать более ранние данные в краткое резюме с помощью одного запроса и поместить его в системное сообщение. Приведённый ниже класс инкапсулирует логику сохранения истории и обрезки по количеству сообщений; его можно сразу использовать в вашем сервисе чата.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.wuxianzhiapi.com/v1",
api_key=os.environ["WUXIANZHI_API_KEY"],
)
class Chat:
def __init__(self, system: str, keep_last: int = 20):
self.system = {"role": "system", "content": system}
self.history = [] # 只存 user / assistant 消息
self.keep_last = keep_last
def say(self, text: str, max_tokens: int = 500) -> str:
self.history.append({"role": "user", "content": text})
recent = self.history[-self.keep_last:]
resp = client.chat.completions.create(
model="uncensored",
messages=[self.system] + recent,
max_tokens=max_tokens,
)
reply = resp.choices[0].message.content
self.history.append({"role": "assistant", "content": reply})
return reply
bot = Chat("你是一位说话简短的旅行顾问。")
print(bot.say("我想去云南玩五天,有什么建议?"))
print(bot.say("刚才说的第二个地方,适合带老人吗?")) # 能接上上一轮По количеству сообщений достаточно, но неточно, так как длина каждого сообщения сильно различается. Если вам нужен строгий контроль, используйте usage.prompt_tokens из ответа как ориентир фактического потребления: как только значение приближается к 50 000, активно сжимайте историю. Если вы хотите создать продукт для долгосрочного взаимодействия, обратитесь к примерам проектирования контекста в разделе Сценарии применения.
Часто задаваемые вопросы
Почему последний блок потоковых данных пустой?
Это автоматически добавляемый блок статистики использования, где массив choices пуст, а в usage указаны токены. При чтении сначала проверьте, пуст ли массив choices, затем извлеките delta. Дополнительные параметры для включения не требуются.
Нужно ли повторять запросы при 429 и 503? Как долго ждать?
Стоит повторить запрос, 429 означает превышение лимита в 300 запросов в минуту, а 503 (upstream_busy) — временную занятость модели. Рекомендуется использовать экспоненциальную задержку с добавлением случайного шума, начиная с 1 секунды, установив максимальное количество попыток, чтобы избежать бесконечных повторных запросов.
Что делать, если при вызове функций модель не возвращает tool_calls?
Это означает, что модель считает вызов функций не нужным, и message.content содержит окончательный ответ. Если вызов обязателен, укажите tool_choice для конкретной функции и убедитесь, что описание функции и параметры Schema написаны чётко.
Удорожает ли многопольный диалог запросы?
Да. Интерфейс не сохраняет состояние, поэтому историю необходимо отправлять заново каждый раз, и количество входных токенов растёт с каждым раундом. Вы можете сохранять только последние несколько раундов или сжимать ранние данные в резюме, одновременно ограничивая вывод с помощью max_tokens.
Заполните форму, чтобы получить ключ
Создайте аккаунт, скопируйте ключ и измените Base URL. Настройка проста.
Получить API-ключ