Wu Xianzhi API程式碼範例
無審查 AI API 呼叫範例:Python、Node.js 與 cURL 完整程式碼
這是一份面向開發者的無審查 AI API 呼叫手冊。介面相容 OpenAI Chat Completions,所以你熟悉的 openai SDK 改兩行設定就能用。本文把最常用的幾件事一次講全:基礎請求、串流輸出、函式呼叫、錯誤重試、用 max_tokens 控制成本,以及多輪對話怎麼保持上下文。所有程式碼都能直接複製執行,金鑰統一從環境變數讀取。
更新於
重點
- 改 base_url 為 https://api.wuxianzhiapi.com/v1,模型名写 uncensored,官方 openai SDK 無需其他改動
- 串流回應的最後一塊是用量統計,choices 為空,讀取時要先判斷
- 僅對 429 與 503 進行指數退避重試,400/401/402/403 重試無意義
- 介面無狀態,多輪對話要自己重發歷史,並用 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 限制輸出。