IT ▾
Ottieni la chiave API

Wu Xianzhi APIEsempi di codice

Codice completo per le chiamate API AI senza censura: Python, Node.js e cURL

Questa è una guida alle chiamate API per un'AI senza censura pensata per gli sviluppatori. L'endpoint è compatibile con Chat Completions di OpenAI, quindi il tuo SDK openai preferito funziona con due righe di configurazione. In questo articolo spieghiamo tutto ciò che serve sapere: richieste di base, streaming, chiamata di funzioni, retry delle risposte di errore, controllo dei costi con max_tokens e come mantenere la finestra di contesto nelle conversazioni multi-turno. Tutto il codice è pronto all'uso e le chiavi API vengono lette dalle variabili d'ambiente.

Aggiornato il

Punti chiave

  1. Modifica base_url in https://api.wuxianzhiapi.com/v1,模型名写 uncensored; l'SDK openai ufficiale non richiede altre modifiche
  2. L'ultimo blocco della risposta streaming contiene le statistiche d'uso: choices è vuoto, verifica prima di leggere
  3. Esegui il retry con backoff esponenziale solo per 429 e 503; i retry per 400/401/402/403 non hanno senso
  4. L'endpoint è stateless: per le conversazioni multi-turno devi reinviare la cronologia e usare max_tokens e il taglio per controllare i costi

Informazioni di base sull'endpoint e variabili d'ambiente

Imposta prima alcuni parametri fissi, che userai in tutti gli esempi. La Base URL è https://api.wuxianzhiapi.com/v1, il nome del modello è fisso uncensored, l'autenticazione usa l'intestazione Authorization: Bearer <chiave>. Ci sono solo due endpoint: POST /v1/chat/completions per le conversazioni e GET /v1/models per verificare la disponibilità del modello. Il formato delle richieste e delle risposte è identico a Chat Completions di OpenAI, quindi l'SDK ufficiale openai richiede solo la modifica di base_url e della chiave, senza toccare il codice dell'applicazione.

La chiave viene visualizzata immediatamente dopo la registrazione su /get-api-key/ con email e password; i nuovi account ricevono un credito di prova gratuito di $0,50 valido per 7 giorni, senza necessità di inserire una carta di credito. Tutti gli esempi in questo documento leggono la chiave dalla variabile d'ambiente WUXIANZHI_API_KEY; non inserire la chiave nel repository del codice né nelle pagine frontend. Alcune limitazioni: finestra di contesto di 100.000 token (input e output combinati), corpo della richiesta massimo 8 MB, limite di 300 richieste al minuto per chiave.

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

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

Se ricevi un 401, la chiave è errata o la variabile d'ambiente non è attiva: risolvilo prima di procedere con il codice. Per la descrizione completa dei parametri, consulta la documentazione dell'endpoint.

cURL: richiesta minima funzionante

Indipendentemente dal linguaggio finale, ti consigliamo di testare prima con cURL. Questo isola i problemi di rete, chiave e formato della richiesta dal tuo codice business. Di seguito una richiesta standard con max_tokens: nel JSON di risposta, choices[0].message.content è il corpo della risposta, mentre usage contiene i token di input e output consumati; la fatturazione si basa su questi due valori.

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

Nota che il testo cinese nel JSON non richiede escape manuale: basta dichiarare un JSON compatibile con UTF-8 nell'intestazione; nella maggior parte dei terminali puoi incollarlo direttamente. Se esegui il debug in PowerShell su Windows, la gestione delle virgolette è complessa: ti consigliamo di salvare il corpo della richiesta in body.json e inviarlo con -d @body.json.

Chiamate complete in Python e Node.js

Per Python usa il pacchetto ufficiale openai (v1 e superiori), dopo aver eseguito pip install openai. Crea il client passando base_url e api_key; le chiamate successive sono identiche a quelle per OpenAI. Puoi salvare lo script come chat.py ed eseguirlo.

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)

Per Node.js usa il pacchetto npm openai (v4 e superiori), dopo aver eseguito npm install openai. La sintassi usa await a livello di file, quindi salva il file come chat.mjs o imposta "type": "module" in package.json.

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

La struttura del codice è identica, cambia solo la sintassi. Se il tuo progetto contiene già chiamate OpenAI, di solito basta sostituire le righe di inizializzazione del client e cambiare il nome del modello in uncensored. Per la migrazione completa da altri endpoint, consulta la guida alla migrazione.

Come leggere l'output streaming (SSE)

Per testi lunghi o interfacce chat, usa sempre l'output streaming: altrimenti l'utente deve aspettare la fine della generazione per vedere il primo carattere. Impostando stream: true, il server invia blocchi via SSE (Server-Sent Events); ogni blocco è una riga data: {...} e termina con data: [DONE]. L'SDK ufficiale analizza già i dati, ti basta iterare.

Un dettaglio che può creare problemi: alla fine dello streaming viene aggiunto automaticamente un blocco dati con usage, dove choices è un array vuoto. Non devi passare parametri aggiuntivi per attivarlo, ma nel codice non puoi assumere che chunk.choices[0] esista: devi verificare se è vuoto, altrimenti otterrai un errore di indice fuori bounds quando lo streaming sta per terminare.

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

Per vedere i dati grezzi SSE, usa cURL con il flag -N per disabilitare il buffering dell'output: ogni blocco verrà stampato immediatamente, utile per verificare se un proxy o un gateway sta consumando la risposta streaming.

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

Se inoltri la risposta streaming dietro un reverse proxy come Nginx, disabilita il buffering delle risposte per quel percorso; altrimenti il frontend riceverà tutto in un unico blocco invece che carattere per carattere.

Chiamata di funzioni: tools e ritorno dei risultati

La chiamata di funzioni segue il formato OpenAI: dichiara nome, descrizione e parametri JSON Schema delle funzioni tramite tools nella richiesta. Quando il modello decide di chiamare una funzione, restituisce il nome e i parametri come stringa JSON in message.tool_calls. Il tuo codice deve eseguire effettivamente la funzione e inviare il risultato come messaggio con role: "tool"; il modello genererà quindi la risposta finale basandosi sul risultato.

tool_choice è di default "auto", lasciando al modello la decisione; per forzare una funzione specifica, passa {"type": "function", "function": {"name": "get_weather"}}; passa "none" per disabilitare le chiamate. L'esempio seguente mostra un ciclo completo: prima richiesta per ottenere tool_calls, esecuzione della funzione locale, seconda richiesta con il risultato.

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)

Tre errori comuni: 1) dimenticare di reinserire il messaggio assistant con tool_calls nella cronologia prima di aggiungere il messaggio tool, rendendo la richiesta non valida; 2) mismatch di tool_call_id; 3) i parametri restituiti sono stringhe e devono essere parsati con json.loads, gestendo i casi di errore senza concatenare direttamente l'output del modello in comandi o SQL. Il flusso in Node.js è identico; di seguito la versione equivalente.

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

Gestione degli errori e retry: backoff per 429 e 503

Le risposte di errore sono sempre JSON strutturati: {"error":{"code":...,"message":...}}. Nel codice devi fare retry solo per due casi: 429 (superamento del limite di 300 richieste al minuto) e 503 (upstream_busy, modello temporaneamente occupato, riprova tra qualche secondo). Anche i timeout di connessione a livello di rete meritano un retry. Gli altri errori non hanno senso che vengano ritentati: 400 indica un problema nella richiesta (ad esempio prompt con max_tokens superiore a 100k), 401 significa chiave non valida, 402 no_credit indica saldo esaurito o credito di prova scaduto, 403 content_blocked indica contenuto bloccato, e ritentare cento volte non cambierà il risultato.

Usa un backoff esponenziale con jitter: 1° tentativo ~1s, 2° ~2s, 3° ~4s, impostando un limite massimo e un numero massimo di tentativi per evitare che più task concorrenti retryino simultaneamente, aggravando il rate limit. L'SDK ufficiale include max_retries, che retrya automaticamente 429 e 5xx; per scenari semplici, aumenta semplicemente questo valore. Per logging, circuit breaker o attese personalizzate, scrivi un loop manuale.

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)

In Node.js puoi aumentare direttamente maxRetries; l'SDK gestirà il backoff per 429 e 5xx. Per distinguere gli errori, controlla 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;
  }
}

Nota: se una richiesta streaming si interrompe, conserva i dati già ricevuti; il retry rigenera dall'inizio e viene fatturato di nuovo. Per testi lunghi, è consigliabile inviare richieste segmentate invece di una singola richiesta lunga.

Controllo dei costi e della lunghezza con max_tokens

Le regole di fatturazione sono semplici: input a $0.25 per milione di token, output a $1.00 per milione di token, credito prepagato, nessun canone mensile, il saldo non scade mai. Il prezzo dell'output è 4 volte quello dell'input, quindi il vero risparmio sta nell'output. max_tokens ha un default di 2048, con un massimo per singola richiesta di 32.000. Se il tuo scenario richiede solo una o due frasi di risposta, impostalo esplicitamente a 200 o 300: eviti che il modello diventi prolisso e blocchi il costo massimo della richiesta.

Facciamo due calcoli: una richiesta con max_tokens impostato a 1.000 costa al massimo $0.001 di output; impostato al massimo di 32.000, il costo massimo è $0.016. Il credito di prova gratuito di $0.50 equivale a circa 500.000 token di output, o 2 milioni di token di input, più che sufficiente per eseguire tutti gli esempi di questa guida. Tieni presente che la somma del prompt e di max_tokens non deve superare 100.000, altrimenti ricevi un 400: se il prompt è lungo, riduci il limite di output. Per i dettagli sui prezzi, visita la pagina dei prezzi.

Un altro dettaglio: se la risposta viene troncata, finish_reason sarà "length", indicando che max_tokens era insufficiente e non che il modello ha terminato. Per testi lunghi, controlla questo campo per decidere se continuare la generazione.

Conversazioni multi-turno: gestione autonoma del contesto

L'endpoint è stateless: il server non memorizza le richieste precedenti. Per continuare la conversazione, devi reinviare l'intera cronologia dei messaggi in ordine: system, poi user e assistant alternati. Questo significa che ogni turno aggiuntivo aumenta i token di input e il costo si accumula; le richieste successive avranno un costo di input più elevato rispetto alle precedenti.

Il contesto totale è limitato a 100.000 token (inclusa questa risposta), quindi le conversazioni lunghe devono essere troncate. Il metodo più semplice è conservare il messaggio di system e gli ultimi scambi; in alternativa, puoi riassumere i contenuti precedenti in una breve sintesi da inserire nel messaggio di system. La classe seguente incapsula la logica di salvataggio della cronologia e il troncamento basato sul numero di messaggi, pronto per essere integrato nel tuo servizio di chat.

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

Il troncamento basato sul numero di messaggi è sufficiente ma impreciso, poiché la lunghezza di ogni messaggio varia notevolmente. Se hai bisogno di un controllo rigoroso, utilizza usage.prompt_tokens come riferimento per l'utilizzo effettivo: comprimi la cronologia quando ti avvicini a 50.000. Se desideri creare un prodotto di conversazione a lungo termine, consulta gli esempi di progettazione del contesto nella sezione Casi d'uso.

Domande frequenti

Perché l'ultimo blocco di dati streaming è vuoto?

Si tratta di un blocco di statistiche di utilizzo aggiunto automaticamente, con choices come array vuoto e usage contenente il numero di token. Durante la lettura, verifica se choices è vuoto prima di estrarre delta; non è necessario impostare parametri aggiuntivi.

È necessario ritentare in caso di 429 e 503? Quanto aspettare?

Tutti questi errori meritano un retry: il 429 indica il superamento del limite di 300 richieste al minuto, il 503 con upstream_busy indica che il modello è temporaneamente occupato. Usa un backoff esponenziale con jitter, partendo da 1 secondo, impostando un numero massimo di tentativi ed evitando retry infiniti.

Cosa fare se il modello non restituisce tool_calls durante la chiamata di funzioni?

Significa che il modello ritiene non necessaria la chiamata; in questo caso, message.content è la risposta finale. Se è obbligatoria la chiamata, imposta tool_choice su una funzione specifica e verifica che la descrizione della funzione e lo Schema dei parametri siano chiari.

Le conversazioni multi-turno diventano più costose?

Sì. L'endpoint è stateless e ogni volta è necessario reinviare la cronologia, quindi i token di input si accumulano con il numero di turni. Puoi conservare solo gli ultimi scambi o comprimere i contenuti precedenti in un riassunto, limitando l'output con max_tokens.

Compila il modulo per ottenere la chiave

Crea un account, copia la chiave e modifica il Base URL. La configurazione è così semplice.

Ottieni la chiave API