PL ▾
Pobierz klucz API

Wu Xianzhi APIPrzykłady kodu

Kompletny kod przykładów wywoływania API AI bez cenzury: Python, Node.js i cURL

To przewodnik dla programistów dotyczący wywoływania API AI bez cenzury. Interfejs jest kompatybilny z Chat Completions OpenAI, więc znane Ci SDK openai wystarczy skonfigurować, zmieniając dwie linie. W tym dokumencie omawiamy najważniejsze funkcje: podstawowe zapytania, strumieniowanie, wywoływanie funkcji, retry błędów, kontrolę kosztów przez max_tokens oraz utrzymanie kontekstu w rozmowach wieloetapowych. Wszystkie przykłady kodu można skopiować i uruchomić; klucze są odczytywane z zmiennych środowiskowych.

Aktualizacja:

Kluczowe informacje

  1. Zmień base_url na https://api.wuxianzhiapi.com/v1,模型名写 uncensored; oficjalne SDK openai nie wymaga innych zmian
  2. Ostatni blok odpowiedzi strumieniowej zawiera statystyki zużycia; choices jest puste — należy to sprawdzić przed odczytem
  3. Powtarzaj zapytania z eksponencjalnym backoff tylko dla 429 i 503; retry dla 400/401/402/403 nie ma sensu
  4. Interfejs jest bezstanowy; w rozmowach wieloetapowych musisz samodzielnie wysyłać historię i kontrolować koszty za pomocą max_tokens oraz przycinania

Podstawowe informacje o interfejsie i zmienne środowiskowe

Zapamiętaj stałe parametry. Base URL to https://api.wuxianzhiapi.com/v1, model to uncensored, autoryzacja: Authorization: Bearer <klucz>. Dostępne są dwa endpointy: POST /v1/chat/completions (dialog) i GET /v1/models (sprawdzenie modelu). Format jest zgodny z OpenAI, więc w SDK openai wystarczy zmienić base_url i klucz.

Klucz jest widoczny od razu na /get-api-key/. Nowe konto ma $0.50 kredytu próbnego ważnego 7 dni, bez konieczności podawania karty. Przykłady używają zmiennej WUXIANZHI_API_KEY. Okno kontekstu: 100 000 tokenów. Limit: 8 MB na zapytanie, 300 zapytań na minutę.

export WUXIANZHI_API_KEY="把你的密钥放这里"

# 确认连通性,应返回包含 uncensored 的模型列表
curl https://api.wuxianzhiapi.com/v1/models \
  -H "Authorization: Bearer $WUXIANZHI_API_KEY"

Jeśli zwróci 401, sprawdź klucz i zmienną środowiskową. Szczegóły parametrów: dokumentacja interfejsu.

cURL: minimalne poprawne zapytanie

Zalecamy przetestowanie przez cURL. Izoluje to problemy sieciowe od kodu. Przykład z max_tokens: odpowiedź JSON zawiera choices[0].message.content oraz usage do naliczania kosztów.

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
  }'

Nie trzeba ręcznie escapować UTF-8 w JSON. W PowerShell użyj pliku body.json z -d @body.json.

Pełne wywołanie w Pythonie i Node.js

Użyj pakietu openai (v1+). Zainstaluj pip install openai. Podaj base_url i api_key. Zapisz skrypt jako 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)

Użyj pakietu openai (v4+). Zainstaluj: npm install openai. Skrypt używa await, więc zapisz go jako chat.mjs lub dodaj w 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);

Zmień inicjalizację klienta i ustaw model uncensored. Zobacz przewodnik migracji.

Jak odczytać strumieniowanie (SSE)

Ustaw stream: true dla strumieniowania. Serwer wysyła bloki SSE data: {...}, kończąc data: [DONE]. SDK parsuje dane automatycznie.

Ostatni blok zawiera usage i pusty choices. Sprawdź, czy nie jest puste, zanim odwołasz się do 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);

Użyj cURL z -N, aby zobaczyć surowe dane SSE bez buforowania.

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":"数到五。"}]}'

Jeśli przesyłasz odpowiedzi strumieniowe przez reverse proxy (np. Nginx), pamiętaj o wyłączeniu buforowania odpowiedzi dla tej ścieżki — w przeciwnym razie frontend zobaczy dane w jednym kawałku, a nie stopniowo.

Wywoływanie funkcji: tools i zwracanie wyników

Użyj tools do deklaracji funkcji. Model zwraca message.tool_calls. Wyślij wynik z role: "tool", aby uzyskać odpowiedź.

tool_choice: "auto" (domyślnie), {"type": "function", "function": {"name": "get_weather"}} (wymuszenie), "none" (blokada). Przykład: pobierz tool_calls, wykonaj funkcję, wyślij wynik.

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)

Błędy: 1. Niezapisanie tool_calls w historii. 2. Błędny tool_call_id. 3. Parametry jako string – użyj json.loads. Node.js działa analogicznie.

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);
}

Obsługa błędów i retry: backoff dla 429 i 503

Wszystkie błędne odpowiedzi mają jednolity format JSON: {"error":{"code":...,"message":...}}. W kodzie należy zretrykować tylko dwa typy: 429 (przekroczenie limitu 300 zapytań na minutę) i 503 (upstream_busy, model jest chwilowo zajęty, spróbuj ponownie za kilka sekund). Warto zretrykować również timeouty połączenia warstwy sieciowej. Pozostałe błędy nie wymagają retry: 400 oznacza problem z samym zapytaniem (np. prompt plus max_tokens przekraczają 100k), 401 to nieprawidłowy klucz, 402 no_credit oznacza brak środków lub wygaśnięcie kredytu, a 403 content_blocked to zablokowanie treści — setki ponownych prób dadzą ten sam wynik.

Strategia wycofania się wykorzystuje wykładniczy wzrost z losowym jitterem: 1. próba trwa około 1 sekundy, 2. próba około 2 sekundy, 3. próba około 4 sekundy. Ustawia się górny limit i maksymalną liczbę powtórzeń, aby uniknąć sytuacji, w której wiele równoległych zadań ponawia zapytania w tym samym momencie, co nasila limit zapytań. Oficjalny SDK zawiera max_retries, który domyślnie wykonuje kilka ponowień dla kodów 429 i 5xx; w prostych przypadkach wystarczy zwiększyć tę wartość, a przy potrzebie logowania, wyłączania obciążenia lub niestandardowych czasów oczekiwania należy napisać własną pętlę.

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)

W Node.js zwiększ maxRetries. SDK obsługuje backoff dla 429 i 5xx. Sprawdzaj status przez 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;
  }
}

Dodatkowa uwaga: jeśli strumieniowe zapytanie zostanie przerwane, odebrane dane należy zapisać; retry zacznie generować od nowa i ponownie naliczy opłaty. Dlatego przy generowaniu długich tekstów zalecamy dzielenie na mniejsze zapytania, a nie wysyłanie jednego dużego żądania.

Kontrola kosztów i długości odpowiedzi za pomocą max_tokens

Ceny: $0.25 / 1M tokenów input, $1.00 / 1M output. Przedpłacony kredyt, bez opłat miesięcznych, nie wygasa. Output jest 4x droższy. Domyślny max_tokens to 2048, max 32 000. Ustaw 200-300 dla krótkich odpowiedzi.

Zróbmy rachunek sumowania: przy zapytaniu z max_tokens ustawionym na 1 000, koszt w najgorszym scenariuszu to $0,001; przy limicie 32 000 wynosi $0,016. Darmowy kredyt próbny w wysokości $0,50 to odpowiednio ok. 500 000 tokenów wyjściowych lub 2 000 000 tokenów wejściowych — wystarczy to na wielokrotne uruchomienie wszystkich przykładów z tego artykułu. Pamiętaj, że suma promptu i max_tokens nie może przekroczyć 100 000, w przeciwnym razie otrzymasz błąd 400, więc przy długich promptach obniż limit wyjściowy. Szczegółowy opis cen znajdziesz na stronie z cenami.

Kolejna uwaga: jeśli odpowiedź zostanie ucięta, pole finish_reason w odpowiedzi przyjmie wartość "length", co oznacza, że zabrakło max_tokens, a nie że model sam skończył pisać. Przy generowaniu długich tekstów sprawdź to pole, aby zdecydować, czy kontynuować pisanie.

Rozmowy wieloetapowe: samodzielne utrzymanie kontekstu

Interfejs jest bezstanowy — serwer nie zapamiętuje poprzednich zapytań. Aby kontynuować rozmowę, musisz za każdym razem wysyłać pełną historię wiadomości w kolejności: system, user, assistant, user itd. Oznacza to, że każda kolejna tura zwiększa liczbę tokenów wejściowych, a koszty się sumują — im dalej w rozmowie, tym wyższy koszt wejścia w każdej kolejnej turze.

Całkowita ilość kontekstu jest ograniczona do 100 000 tokenów (włącznie z tą odpowiedzią), więc długie rozmowy wymagają przycinania. Najprostszym rozwiązaniem jest zachowanie wiadomości systemowych oraz kilku ostatnich tur; w bardziej zaawansowanych przypadkach można zsumować wcześniejsze treści w jednym zapytaniu i umieścić krótkie streszczenie w wiadomości systemowej. Poniższa klasa encapsuluje logikę zapisu historii i przycinania według liczby wiadomości, gotowa do użycia w Twojej usłudze czatu.

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("刚才说的第二个地方,适合带老人吗?"))  # 能接上上一轮

Przycinanie według liczby wiadomości wystarcza, ale nie jest precyzyjne, ponieważ długość każdej wiadomości może się znacznie różnić. Jeśli potrzebujesz ścisłej kontroli, użyj usage.prompt_tokens z odpowiedzi jako wskaźnika rzeczywistego zużycia: aktywuj kompresję historii, gdy zbliżysz się do 50 000. Jeśli tworzysz produkt typu long-term companion, zapoznaj się z przykładami projektowania kontekstu w scenariuszach użycia.

Najczęściej zadawane pytania

Dlaczego ostatni blok danych strumieniowych jest pusty?

To automatyczny blok statystyk: choices to pusta tablica, usage zawiera tokeny. Sprawdź czy choices nie są puste, zanim odczytasz delta.

Czy należy ponawiać zapytania przy kodach 429 i 503? Jak długo czekać?

Warto ponawiać w obu przypadkach. 429 oznacza przekroczenie limitu 300 zapytań na minutę, a 503 z upstream_busy oznacza tymczasowe zajęcie modelu. Zalecamy eksponencjalne wycofywanie z losowym jitterem, zaczynając od 1 sekundy, z ustawionym limitem liczby prób, aby unikać nieskończonego ponawiania.

Co zrobić, jeśli model nie zwraca tool_calls podczas wywoływania funkcji?

Oznacza to, że model uznał wywołanie za niepotrzebne; w takim przypadku message.content stanowi ostateczną odpowiedź. Jeśli wywołanie jest konieczne, ustaw tool_choice na konkretną funkcję i sprawdź, czy opisy funkcji oraz schemat parametrów są jasno zdefiniowane.

Czy wieloetapowe rozmowy stają się coraz droższe?

Tak. Interfejs jest bezstanowy, więc każda tura wymaga ponownego wysłania historii, a liczba tokenów wejściowych rośnie z każdą turą. Możesz zachować tylko kilka ostatnich tur lub skompresować wcześniejsze treści w streszczenie, jednocześnie ograniczając output za pomocą max_tokens.

Wypełnij formularz, aby uzyskać klucz

Utwórz konto, skopiuj klucz i zmień Base URL. Konfiguracja jest taka prosta.

Uzyskaj klucz API