FR ▾
Obtenir votre clé API

Wu Xianzhi APIExemples de code

Code complet pour les appels API IA sans censure : Python, Node.js et cURL

Ce guide de l'API IA sans censure est pensé pour les développeurs. L'interface est compatible avec OpenAI Chat Completions ; vous pouvez utiliser votre SDK OpenAI habituel en modifiant simplement deux lignes de configuration. Nous couvrons les cas d'usage essentiels : requêtes de base, streaming, appel de fonctions, gestion des erreurs, contrôle des coûts via max_tokens et gestion du contexte multi-tours. Le code est prêt à l'emploi et les clés sont lues depuis les variables d'environnement.

Mis à jour le

Points clés

  1. Modifiez base_url vers https://api.wuxianzhiapi.com/v1,模型名写 uncensored ; le SDK openai ne nécessite aucune autre modification
  2. Le dernier bloc de réponse streamée contient les statistiques d'utilisation ; choices est vide et doit être vérifié avant lecture
  3. Appliquez un backoff exponentiel uniquement pour les erreurs 429 et 503 ; les erreurs 400/401/402/403 ne justifient pas de réessais
  4. L'API est sans état : vous devez gérer l'historique vous-même et utiliser max_tokens ainsi que le découpage pour contrôler les coûts

Informations de base de l'interface et variables d'environnement

Retenez ces paramètres fixes, utilisés dans tous les exemples. L'URL de base est https://api.wuxianzhiapi.com/v1, le modèle est toujours uncensored et l'authentification se fait via l'en-tête Authorization: Bearer <clé>. Deux endpoints sont disponibles : POST /v1/chat/completions pour les conversations et GET /v1/models pour vérifier la disponibilité. Le format des requêtes et réponses est identique à celui d'OpenAI, donc le SDK openai officiel ne nécessite que la modification de base_url et de la clé ; le code métier reste inchangé.

La clé s'affiche après l'inscription sur /get-api-key/ (email et mot de passe suffisent). Un crédit d'essai gratuit de 0,50 $ est attribué aux nouveaux comptes et est valable 7 jours, sans carte bancaire requise. Tous les exemples lisent la clé depuis la variable d'environnement WUXIANZHI_API_KEY. Ne stockez pas la clé dans votre dépôt de code ou dans le frontend. Limites : fenêtre de contexte de 100 000 tokens (entrée + sortie), corps de requête maximal de 8 Mo, et limite de débit de 300 requêtes par minute par clé.

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

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

Si vous obtenez une erreur 401, vérifiez d'abord la clé ou la variable d'environnement avant de lire le code. Consultez la documentation de l'interface pour la liste complète des paramètres.

cURL : requête minimale fonctionnelle

Nous recommandons de tester d'abord avec cURL pour isoler les problèmes de réseau, d'authentification ou de format de requête du code métier. Voici une requête standard avec max_tokens. La réponse JSON contient le texte dans choices[0].message.content et les statistiques de tokens dans usage, qui servent au calcul des coûts.

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

Le chinois dans le JSON n'a pas besoin d'être échappé manuellement si l'en-tête indique un JSON compatible UTF-8. Sur Windows PowerShell, l'utilisation des guillemets peut être complexe ; il est préférable de sauvegarder le corps de la requête dans body.json et de l'envoyer avec -d @body.json.

Appels complets en Python et Node.js

En Python, utilisez le package officiel openai (v1+). Installez-le avec pip install openai. Créez le client en passant base_url et api_key. L'utilisation est identique à celle d'OpenAI. Vous pouvez sauvegarder le script sous chat.py et l'exécuter.

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)

En Node.js, utilisez le package npm openai (v4+). Installez-le avec npm install openai. L'exemple utilise await au niveau supérieur ; sauvegardez le fichier sous chat.mjs ou définissez "type": "module" dans 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 structure des deux codes est identique ; seule la syntaxe diffère. Si vous utilisez déjà le SDK OpenAI, remplacez simplement l'initialisation du client et changez le modèle vers uncensored. Consultez le guide de migration pour les étapes de migration complètes.

Comment lire le streaming (SSE)

Pour les interfaces de chat ou la génération de texte long, le streaming est indispensable pour afficher le contenu au fur et à mesure. Définissez stream: true pour recevoir des événements SSE (Server-Sent Events). Chaque bloc est une ligne data: {...}, terminée par data: [DONE]. Le SDK officiel analyse les données ; vous n'avez plus qu'à itérer sur les résultats.

Un détail piégeant : le dernier chunk du streaming contient automatiquement un chunk avec usage, où choices est un tableau vide. Vous n'avez pas besoin de passer de paramètre pour l'activer, mais votre code ne doit pas supposer qu'il peut accéder directement à chunk.choices[0] ; vérifiez d'abord qu'il n'est pas vide, sinon vous obtiendrez une erreur d'indice hors limites à la fin du streaming.

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

Pour inspecter les données brutes SSE, utilisez l'option -N avec cURL pour désactiver la mise en mémoire tampon. Cela permet de voir chaque bloc s'afficher instantanément, ce qui est utile pour déboguer les proxies ou les gateways qui pourraient tronquer le flux.

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

Si vous utilisez un proxy inverse comme Nginx, assurez-vous de désactiver la mise en mémoire tampon des réponses pour ce chemin, sinon le client verra le contenu s'afficher en une seule fois plutôt que progressivement.

Appel de fonctions : outils et retour des résultats

L'appel de fonctions suit le format OpenAI : déclarez les fonctions avec tools (nom, description, schéma JSON). Le modèle retourne le nom de la fonction et les paramètres en JSON dans message.tool_calls. Votre code doit exécuter la fonction et renvoyer le résultat avec role: "tool" pour que le modèle puisse générer la réponse finale.

tool_choice vaut "auto" par défaut. Pour forcer : {"type": "function", "function": {"name": "get_weather"}}. "none" désactive. Exemple : 1ère req → tool_calls, exécution, 2ème req avec résultat.

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)

Erreurs : 1. Ne pas renvoyer assistant tool_calls. 2. tool_call_id incorrect. 3. Paramètres en chaîne : utiliser json.loads. Node.js identique.

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

Gestion des erreurs et réessais : backoff pour 429 et 503

Les réponses d'erreur sont toujours un JSON unifié : {"error":{"code":...,"message":...}}. Dans votre programme, vous ne devez réessayer que deux types d'erreurs : 429 (limite dépassée à plus de 300 requêtes par minute) et 503 (upstream_busy, modèle temporairement occupé, réessayez dans quelques secondes). Il est aussi judicieux de réessayer les timeouts de connexion au niveau du réseau. Les autres erreurs ne valent pas la peine d'être réessayées : 400 signifie que la requête elle-même est incorrecte (par exemple, le prompt plus max_tokens dépasse 100k), 401 indique une clé invalide, 402 no_credit signifie que le solde est épuisé ou que l'essai est expiré, 403 content_blocked signifie que le contenu a été bloqué, et renvoyer la requête cent fois donnera le même résultat.

Backoff exponentiel + jitter. 1er: 1s, 2e: 2s, 3e: 4s. Limitez retries pour éviter pics. SDK a max_retries pour 429/5xx. Pour logs/circuit breaker, écrivez votre boucle. Évitez que les tâches concurrentes ne retentent toutes en même temps.

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)

En Node.js, augmentez simplement maxRetries ; le SDK gère le backoff pour les 429 et 5xx. Utilisez error.status pour distinguer les erreurs.

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

Notez que si un flux est interrompu, le contenu déjà reçu doit être conservé. Un retry recommence la génération depuis le début et entraîne une nouvelle facturation. Pour les longs textes, privilégiez les requêtes segmentées.

Contrôle des coûts et de la longueur avec max_tokens

Prix : entrée $0.25/M tokens, sortie $1.00/M tokens. Solde prépayé, pas d'abonnement, n'expire jamais. Sortie 4x plus chère. max_tokens : 2048 par défaut, max 32 000. Réglez sur 200-300 pour limiter coûts et éviter modèles verbeux.

Faisons le calcul : une requête avec max_tokens fixé à 1 000 coûte au maximum $0,001 ; fixé à son maximum de 32 000, le coût est de $0,016. Le crédit d'essai de $0,50 équivaut à environ 500 000 tokens de sortie, ou 2 000 000 tokens d'entrée, ce qui suffit pour exécuter de nombreuses fois tous les exemples de cet article. Notez que la somme des tokens du prompt et de max_tokens ne doit pas dépasser 100 000, sinon vous recevrez immédiatement une erreur 400 ; lorsque le prompt est long, réduisez donc le nombre maximum de tokens de sortie. Pour des détails sur les prix, consultez la page des tarifs.

Si réponse tronquée, finish_reason = "length". Vérifiez ce champ pour continuer. max_tokens insuffisant.

Gestion du contexte multi-tours

L'API est sans état : le serveur ne conserve pas l'historique. Pour continuer une conversation, vous devez renvoyer l'intégralité des messages (system, user, assistant) à chaque appel. Cela signifie que le nombre de tokens d'entrée augmente avec chaque tour, ce qui alourdit le coût. Les tours suivants coûtent donc plus cher que les premiers.

Le volume total du contexte est limité à 100 000 tokens (incluant cette sortie), il faut donc tronquer les longues conversations. La méthode la plus simple consiste à conserver les messages système et les quelques dernières tours ; pour une approche plus élaborée, vous pouvez résumer les contenus plus anciens en une seule requête et les intégrer dans le message système. La classe ci-dessous encapsule la logique de conservation de l'historique et de troncation par nombre de tours, prête à être intégrée dans votre service de 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("刚才说的第二个地方,适合带老人吗?"))  # 能接上上一轮

La troncation par nombre de tours est suffisante mais peu précise, car la longueur de chaque message varie considérablement. Si vous avez besoin d'un contrôle strict, utilisez usage.prompt_tokens de la réponse comme référence de consommation réelle : compressez l'historique dès que vous vous approchez de 50 000. Pour concevoir un produit de type compagnon conversationnel, consultez les exemples de conception de contexte dans Scénarios d'application.

Questions fréquentes

Pourquoi le dernier bloc de données en streaming est-il vide ?

Il s'agit d'un bloc de statistiques d'utilisation ajouté automatiquement, avec un tableau choices vide et des nombres de tokens dans usage. Il suffit de vérifier si choices est vide avant de lire le delta ; aucun paramètre supplémentaire n'est nécessaire pour activer ce comportement.

Faut-il réessayer en cas d'erreur 429 ou 503 ? Combien de temps attendre ?

Toutes méritent un retry : le 429 signale un dépassement de la limite de 300 requêtes par minute, et le 503 indique un upstream_busy (modèle temporairement occupé). Nous vous suggérons un backoff exponentiel avec un jitter aléatoire, en partant de 1 seconde, en définissant un nombre maximal de tentatives pour éviter des retries infinis.

Que faire si le modèle ne retourne pas tool_calls lors d'un appel de fonctions ?

Cela signifie que le modèle estime qu'aucun appel n'est nécessaire ; le contenu de message.content constitue alors la réponse finale. Si un appel est obligatoire, définissez tool_choice sur une fonction spécifique et vérifiez que la description de la fonction et le schéma des paramètres sont clairement rédigés.

Le coût des conversations multi-tours augmente-t-il avec le temps ?

Oui. L'interface est sans état, vous devez renvoyer l'historique à chaque fois ; les tokens d'entrée s'accumulent avec les tours. Vous pouvez ne conserver que les derniers tours ou compresser les contenus anciens en un résumé, tout en limitant la sortie avec max_tokens.

Remplissez simplement le formulaire pour obtenir votre clé

Créez un compte, copiez votre clé et modifiez la Base URL. La configuration est aussi simple que cela.

Obtenir la clé API