繁中 ▾
取得 API 金鑰

Wu Xianzhi API程式碼範例

無審查 AI API 呼叫範例:Python、Node.js 與 cURL 完整程式碼

這是一份面向開發者的無審查 AI API 呼叫手冊。介面相容 OpenAI Chat Completions,所以你熟悉的 openai SDK 改兩行設定就能用。本文把最常用的幾件事一次講全:基礎請求、串流輸出、函式呼叫、錯誤重試、用 max_tokens 控制成本,以及多輪對話怎麼保持上下文。所有程式碼都能直接複製執行,金鑰統一從環境變數讀取。

更新於

重點

  1. 改 base_url 為 https://api.wuxianzhiapi.com/v1,模型名写 uncensored,官方 openai SDK 無需其他改動
  2. 串流回應的最後一塊是用量統計,choices 為空,讀取時要先判斷
  3. 僅對 429 與 503 進行指數退避重試,400/401/402/403 重試無意義
  4. 介面無狀態,多輪對話要自己重發歷史,並用 max_tokens 和裁剪控制成本

介面基礎資訊與環境變數

先把幾個固定參數記下來,後面所有範例都會用到。Base URL 是 https://api.wuxianzhiapi.com/v1,模型名固定為 uncensored,鑑權方式是請求頭 Authorization: Bearer <金鑰>。對外只有兩個介面:POST /v1/chat/completions 負責對話,GET /v1/models 用來確認模型是否可用。請求和回應格式與 OpenAI 的 Chat Completions 一致,所以官方 openai SDK 只需要改 base_url 和金鑰,業務程式碼基本不用動。

金鑰在 /get-api-key/ 註冊後立即顯示,輸入電子郵件與密碼即可,新帳號有 $0.50 試用額度,7 天內有效,不需要綁卡。本文所有範例都從環境變數 WUXIANZHI_API_KEY 讀取金鑰,請不要將金鑰寫進程式碼倉儲,也不要放進前端頁面。幾個限制提前說清楚:上下文視窗 100,000 tokens(提示詞與輸出合計),單次請求體不超過 8 MB,每個金鑰每分鐘 300 次請求。

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

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

如果這一步返回 401,表示金鑰寫錯或者環境變數沒生效,先排查這裡,再往下看程式碼。完整參數說明見 介面文件。

cURL:最小可用請求

不管你最終要用什麼語言,建議先用 cURL 跑通一次。這樣能把「網路、金鑰、請求格式」三類問題和你自己的業務程式碼隔離開。下面是一個帶 max_tokens 的一般請求,返回的 JSON 裡 choices[0].message.content 是回覆正文,usage 裡是本次消耗的輸入和輸出 token 數,計費就按這兩個數字算。

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 即可,多數終端機直接貼上就能用。如果你在 Windows 的 PowerShell 裡除錯,引號處理比較麻煩,建議把請求體存成 body.json,再用 -d @body.json 發送。

Python 與 Node.js 的完整呼叫

Python 使用官方 openai 套件(v1 及以上),先 pip install openai。建立用戶端時傳入 base_url 和 api_key,之後的呼叫方式與呼叫 OpenAI 完全相同。下面的腳本可以直接儲存為 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)

Node.js 使用 openai 的 npm 套件(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);

兩段程式碼的結構是一樣的,區別只在語法。如果你的專案裡已經有 OpenAI 的呼叫,通常只需要把用戶端初始化那幾行換掉,再把模型名改成 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 與回傳工具結果

函式呼叫採用 OpenAI 的格式:在請求裡用 tools 宣告函式的名稱、說明和 JSON Schema 參數,模型決定要呼叫時,會在回覆的 message.tool_calls 裡給出函式名和 JSON 字串形式的參數。你的程式碼負責真正執行函式,然後把結果以 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)

三個常見錯誤:一是忘記把 assistant 的 tool_calls 訊息放回歷史,直接追加 tool 訊息,這會讓請求格式不合法;二是 tool_call_id 對不上;三是模型給出的參數是字串,必須先 json.loads,而且要做好解析失敗的備援,不要把模型輸出直接拼進指令或 SQL。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":...}}。程式中真正需要重試的只有兩類: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 / 百萬 tokens,輸出 $1.00 / 百萬 tokens,預付額度,沒有月費,餘額不會過期。輸出單價是輸入的 4 倍,所以真正能省錢的地方在輸出。max_tokens 預設是 2048,單次最大 32,000。如果你的情境只需要一兩句回覆,就明確設成 200 或 300,既能防止模型冗長,也能封住單次請求的費用上限。

算一筆帳:一次請求把 max_tokens 設為 1,000,最壞情況輸出費用是 $0.001;設到上限 32,000,最壞是 $0.016。試用額度 $0.50 折合約 50 萬輸出 tokens,或者 200 萬輸入 tokens,足夠把本文所有範例跑很多遍。要注意提示詞加 max_tokens 的總和不能超過 100,000,否則直接返回 400,所以輸入很長時要同步調低輸出上限。更詳細的價格說明在 價格頁。

另一個細節:如果回覆被截斷,回應裡的 finish_reason 會是 "length",表示是 max_tokens 不夠,而不是模型自己寫完了。寫長文時可以檢查這個欄位,決定要不要接著續寫。

多輪對話:自己維護上下文

介面本身是無狀態的,伺服器不會記住上一次請求。要讓模型接著聊,就得每次把完整的歷史訊息按順序重新發過去:先是 system,然後 user、assistant 交替。這也意味著每多聊一輪,輸入的 token 數就多一截,成本是累加的,聊到後面每一輪的輸入費用都會比前面高。

上下文總量受 100,000 token 限制(含本次輸出),因此長對話必須進行裁剪。最簡單的方法是保留 system 訊息與最近的若干輪對話;稍複雜的做法是將較早的內容透過一次請求總結成一小段摘要,並放入 system 訊息中。下方的類別封裝了保存歷史與按數量裁剪的邏輯,可直接放入你的聊天服務。

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 裡有 token 數。讀取時先判斷 choices 是否為空,再取 delta 即可,不需要額外傳參數開啟。

429 和 503 都要重試嗎?該等多久?

都值得重試,429 是超過每分鐘 300 次的限制,503 的 upstream_busy 是模型暫時繁忙。建議指數退避加隨機抖動,從 1 秒起步,設定最大次數,別無限重試。

進行函式呼叫時,模型沒有返回 tool_calls 該怎麼辦?

這表示模型認為不需要呼叫,此時 message.content 即為最終回答。若必須呼叫,可將 tool_choice 指定為某個函式,並檢查函式描述與參數 Schema 是否撰寫清晰。

多輪對話會不會越聊越貴?

會。介面為無狀態,每次都要重新發送歷史紀錄,輸入 token 隨輪數累加。可只保留最近幾輪,或將早期內容壓縮成摘要,同時使用 max_tokens 限制輸出。

只需填寫表單即可取得 API 金鑰

建立帳戶,複製金鑰,修改 Base URL。設定就是這麼簡單。

取得 API 金鑰