Wu Xianzhi APIMã ví dụ
Mã nguồn đầy đủ ví dụ gọi API AI không kiểm duyệt: Python, Node.js và cURL
Đây là hướng dẫn gọi API AI không kiểm duyệt dành cho nhà phát triển. Interface tương thích với OpenAI Chat Completions, nên SDK openai quen thuộc của bạn chỉ cần đổi hai dòng cấu hình là dùng được. Bài viết này trình bày đầy đủ các tác vụ phổ biến nhất: yêu cầu cơ bản, truyền phát, gọi hàm, xử lý lỗi, dùng max_tokens để kiểm soát chi phí và cách duy trì ngữ cảnh trong hội thoại nhiều vòng. Tất cả mã nguồn đều có thể sao chép và chạy ngay, khóa API được đọc từ biến môi trường.
Cập nhật lúc
Điểm chính
- Đổi base_url thành https://api.wuxianzhiapi.com/v1,模型名写 uncensored, SDK openai chính thức không cần thay đổi khác
- Khối cuối của phản hồi stream là thống kê usage, choices rỗng, khi đọc cần kiểm tra trước
- Chỉ thử lại theo cấp số nhân với 429 và 503, thử lại 400/401/402/403 không có ý nghĩa
- Interface không có trạng thái, hội thoại nhiều vòng cần tự gửi lại lịch sử và dùng max_tokens cùng cắt giảm để kiểm soát chi phí
Thông tin cơ bản về interface và biến môi trường
Trước tiên, bạn hãy ghi nhớ một vài tham số cố định sau, vì tất cả các ví dụ sau này đều sẽ sử dụng chúng. Base URL là https://api.wuxianzhiapi.com/v1, tên mô hình cố định là uncensored, phương thức xác thực là tiêu đề yêu cầu Authorization: Bearer <khóa_bí_mật>. Chỉ có hai endpoint được cung cấp: POST /v1/chat/completions chịu trách nhiệm cho hội thoại, GET /v1/models dùng để xác nhận mô hình có sẵn. Định dạng yêu cầu và phản hồi giống với Chat Completions của OpenAI, vì vậy SDK openai chính thức chỉ cần thay đổi base_url và khóa bí mật, mã nghiệp vụ hầu như không cần thay đổi.
Khóa API sẽ hiển thị ngay sau khi đăng ký tại /get-api-key/, chỉ cần email và mật khẩu. Tài khoản mới có $0.50 tín dụng dùng thử miễn phí, có hiệu lực trong 7 ngày, không cần liên kết thẻ. Tất cả ví dụ trong bài đều đọc khóa từ biến môi trường WUXIANZHI_API_KEY, vui lòng không đưa khóa vào kho mã nguồn hoặc trang frontend. Một số giới hạn cần biết trước: cửa sổ ngữ cảnh 100,000 tokens (bao gồm prompt và đầu ra), kích thước yêu cầu tối đa 8 MB, mỗi khóa API giới hạn 300 yêu cầu mỗi phút.
export WUXIANZHI_API_KEY="把你的密钥放这里"
# 确认连通性,应返回包含 uncensored 的模型列表
curl https://api.wuxianzhiapi.com/v1/models \
-H "Authorization: Bearer $WUXIANZHI_API_KEY"Nếu bước này trả về 401, nghĩa là khóa API sai hoặc biến môi trường chưa có hiệu lực, hãy kiểm tra phần này trước khi xem mã nguồn bên dưới. Xem chi tiết tham số tại tài liệu interface.
cURL: Yêu cầu tối thiểu hoạt động
Bất kể bạn dùng ngôn ngữ nào cuối cùng, hãy thử chạy cURL trước. Cách này giúp tách biệt các vấn đề về 「mạng, khóa API, định dạng yêu cầu」 khỏi mã nguồn nghiệp vụ của bạn. Dưới đây là một yêu cầu bình thường có max_tokens, JSON trả về có choices[0].message.content là nội dung phản hồi, usage chứa số token đầu vào và đầu ra đã tiêu thụ, phí tính dựa trên hai con số này.
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
}'Lưu ý: chữ Trung Quốc trong JSON không cần escape thủ công, chỉ cần tiêu đề yêu cầu khai báo JSON tương thích UTF-8 là được, hầu hết terminal dán trực tiếp là dùng được. Nếu bạn gỡ lỗi trên PowerShell của Windows, việc xử lý dấu ngoặc kép khá phức tạp, hãy lưu yêu cầu vào body.json rồi gửi bằng -d @body.json.
Gọi đầy đủ với Python và Node.js
Python dùng gói openai chính thức (v1 trở lên), trước tiên pip install openai. Khi tạo client, truyền base_url và api_key, cách gọi sau đó hoàn toàn giống với gọi OpenAI. Script dưới đây có thể lưu thành chat.py và chạy ngay.
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 sử dụng gói npm của openai (v4 trở lên), npm install openai. Cách viết dưới đây sử dụng từ khóa await ở cấp cao nhất, vì vậy bạn hãy lưu tệp dưới dạng chat.mjs, hoặc thiết lập "type": "module" trong 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);Cấu trúc hai đoạn mã giống nhau, chỉ khác cú pháp. Nếu dự án của bạn đã có gọi OpenAI, thường chỉ cần đổi vài dòng khởi tạo client và đổi tên mô hình thành uncensored. Các bước di chuyển từ interface khác xem hướng dẫn di chuyển.
Cách đọc truyền phát (SSE)
Khi viết văn bản dài hoặc giao diện chat, bắt buộc phải dùng truyền phát, nếu không người dùng phải chờ đợi đến khi toàn bộ đoạn được tạo xong mới thấy ký tự đầu tiên. Sau khi đặt stream: true, server sẽ đẩy từng khối qua SSE (Server-Sent Events), mỗi khối là một dòng data: {...}, kết thúc bằng data: [DONE]. SDK chính thức đã tự phân tích cho bạn, bạn chỉ cần duyệt qua.
Có một chi tiết dễ gây lỗi: khối dữ liệu cuối của stream sẽ tự động thêm một khối chứa usage, khối này có choices là mảng rỗng. Bạn không cần truyền thêm tham số để bật nó, nhưng mã nguồn không được tự ý lấy chunk.choices[0], phải kiểm tra xem có rỗng không trước, nếu không sẽ bị lỗi vượt chỉ số khi stream sắp kết thúc.
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);Nếu muốn xem dữ liệu SSE thô, hãy dùng cURL kèm tham số -N để tắt bộ đệm đầu ra, như vậy mỗi khối đến sẽ được in ngay lập tức, rất hữu ích khi gỡ lỗi xem proxy hoặc gateway có nuốt mất phản hồi stream không.
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":"数到五。"}]}'Nếu bạn chuyển tiếp phản hồi stream qua reverse proxy như Nginx, hãy nhớ tắt bộ đệm phản hồi cho đường dẫn đó, nếu không frontend sẽ thấy dữ liệu được trả về một lần thay vì xuất hiện từng chữ.
Gọi hàm: tools và trả về kết quả công cụ
Gọi hàm dùng định dạng của OpenAI: trong yêu cầu, dùng tools để khai báo tên, mô tả và tham số JSON Schema của hàm, khi mô hình quyết định gọi, nó sẽ trả về tên hàm và chuỗi tham số dạng JSON trong message.tool_calls của phản hồi. Mã nguồn của bạn chịu trách nhiệm thực thi hàm thật sự, sau đó gửi kết quả dưới dạng tin nhắn role: "tool" trở lại, rồi yêu cầu một lần nữa, mô hình mới dựa vào kết quả để viết câu trả lời cuối cùng.
tool_choice mặc định là "auto", do mô hình tự quyết định; khi cần ép gọi một hàm cụ thể, truyền {"type": "function", "function": {"name": "get_weather"}}; truyền "none" thì cấm gọi hàm. Ví dụ dưới đây chạy trọn vẹn một vòng: yêu cầu đầu tiên lấy tool_calls, thực thi hàm cục bộ, yêu cầu thứ hai kèm kết quả công cụ.
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)Ba lỗi phổ biến: một là quên đưa lại tin nhắn tool_calls của assistant vào lịch sử, chỉ thêm tin nhắn tool, làm cho định dạng yêu cầu không hợp lệ; hai là tool_call_id không khớp; ba là tham số do mô hình trả về là chuỗi, phải json.loads trước, đồng thời phải có cơ chế dự phòng khi phân tích thất bại, không được nối trực tiếp đầu ra của mô hình vào lệnh hoặc SQL. Quy trình Node.js hoàn toàn giống nhau, dưới đây là cách viết tương đương.
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);
}
Xử lý lỗi và thử lại: cách backoff với 429, 503
Phản hồi lỗi đều là JSON thống nhất: {"error":{"code":...,"message":...}}. Trong chương trình, chỉ có hai loại thực sự cần thử lại: 429 (vượt quá giới hạn 300 yêu cầu mỗi phút) và 503 (upstream_busy, mô hình đang bận tạm thời, thử lại sau vài giây). Lỗi kết nối tầng mạng cũng nên thử lại. Các lỗi khác thử lại không có ý nghĩa: 400 là yêu cầu có vấn đề (ví dụ prompt cộng max_tokens vượt quá 100k), 401 là khóa API không hợp lệ, 402 no_credit là hết số dư hoặc hết hạn dùng thử, 403 content_blocked là nội dung bị chặn, gửi lại trăm lần kết quả cũng vậy.
Chiến lược backoff dùng tăng theo cấp số nhân cộng nhiễu ngẫu nhiên: lần 1 đợi khoảng 1 giây, lần 2 khoảng 2 giây, lần 3 khoảng 4 giây, đặt giới hạn trên và số lần tối đa để tránh nhiều tác vụ đồng thời cùng thử lại một lúc, làm tình trạng giới hạn tốc độ càng tệ hơn. SDK chính thức có sẵn max_retries, mặc định sẽ thử lại vài lần với 429 và 5xx, với các tình huống đơn giản chỉ cần tăng giá trị này lên là được; khi cần ghi log, cắt mạch hoặc thời gian chờ tùy chỉnh, hãy tự viết vòng lặp.
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)Trong Node.js cũng có thể tăng trực tiếp maxRetries, SDK sẽ xử lý 429 và 5xx theo chiến lược backoff. Khi cần phân biệt lỗi, chỉ cần dùng error.status để kiểm tra.
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;
}
}Ngoài ra lưu ý: khi stream bị ngắt giữa chừng, nội dung đã nhận được phải tự lưu lại, thử lại sẽ bắt đầu tạo lại từ đầu và tính phí lại, nên với việc tạo văn bản dài, hãy chia nhỏ yêu cầu thay vì yêu cầu một đoạn rất dài.
Kiểm soát chi phí và độ dài bằng max_tokens
Quy tắc tính phí rất rõ ràng: đầu vào $0.25 / triệu tokens, đầu ra $1.00 / triệu tokens, số dư trả trước, không có phí hàng tháng, số dư không bao giờ hết hạn. Đơn giá đầu ra gấp 4 lần đầu vào, nên điểm tiết kiệm thực sự nằm ở đầu ra. max_tokens mặc định là 2048, tối đa mỗi lần là 32,000. Nếu tình huống của bạn chỉ cần một vài câu trả lời, hãy đặt rõ thành 200 hoặc 300, vừa ngăn mô hình lan man, vừa khóa mức phí tối đa cho mỗi yêu cầu.
Tính một ví dụ: một yêu cầu đặt max_tokens là 1,000, chi phí đầu ra xấu nhất là $0.001; đặt lên giới hạn 32,000, chi phí xấu nhất là $0.016. Tín dụng dùng thử $0.50 tương đương khoảng 500.000 token đầu ra, hoặc 2.000.000 token đầu vào, đủ để chạy nhiều lần tất cả ví dụ trong bài. Lưu ý tổng prompt cộng max_tokens không được vượt quá 100,000, nếu không sẽ trả về 400 ngay, nên khi input dài phải đồng thời hạ thấp giới hạn đầu ra. Chi tiết giá cả xem trang giá cả.
Một chi tiết khác: nếu phản hồi bị cắt ngang, finish_reason trong phản hồi sẽ là "length", nghĩa là max_tokens không đủ, chứ không phải mô hình tự viết xong. Khi viết văn bản dài, bạn có thể kiểm tra trường này để quyết định có nên tiếp tục viết nối không.
Hội thoại nhiều vòng: tự duy trì ngữ cảnh
Interface bản thân nó không có trạng thái, server không nhớ yêu cầu trước đó. Để mô hình tiếp tục hội thoại, bạn phải gửi lại toàn bộ tin nhắn lịch sử theo thứ tự mỗi lần: bắt đầu bằng system, sau đó user, assistant xen kẽ. Điều này cũng có nghĩa mỗi vòng hội thoại thêm vào, số token đầu vào sẽ tăng thêm, chi phí sẽ tích lũy, càng về sau chi phí đầu vào cho mỗi vòng sẽ cao hơn các vòng trước.
Tổng lượng ngữ cảnh bị giới hạn ở 100,000 token (bao gồm cả kết quả trả về này), vì vậy các cuộc hội thoại dài cần được cắt bớt. Cách đơn giản nhất là giữ lại tin nhắn hệ thống và một số lượt gần nhất; phức tạp hơn một chút, bạn có thể dùng một yêu cầu để tóm tắt các nội dung cũ hơn thành một đoạn ngắn, rồi đưa vào tin nhắn hệ thống. Lớp mã dưới đây đóng gói logic lưu trữ lịch sử và cắt theo số lượng, bạn có thể đưa trực tiếp vào dịch vụ chat của mình.
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("刚才说的第二个地方,适合带老人吗?")) # 能接上上一轮Cắt theo số lượng tin nhắn là đủ dùng nhưng không chính xác, vì độ dài của mỗi tin khác nhau rất lớn. Nếu bạn cần kiểm soát chặt chẽ, hãy dùng usage.prompt_tokens trong phản hồi làm tham chiếu cho mức sử dụng thực tế: khi tiến gần đến 50,000 thì chủ động nén lịch sử. Nếu bạn muốn xây dựng sản phẩm đồng hành dài hạn, bạn có thể tham khảo thêm các ví dụ về thiết kế ngữ cảnh trong ứng dụng thực tế.
Câu hỏi thường gặp
Tại sao khối dữ liệu stream cuối cùng không có nội dung?
Đó là khối thống kê lượng sử dụng được thêm tự động, với choices là mảng rỗng và usage chứa số token. Khi đọc, bạn chỉ cần kiểm tra xem choices có rỗng không rồi lấy delta là được, không cần thêm tham số để bật.
Có nên thử lại với cả 429 và 503 không? Và nên chờ bao lâu?
Cả hai đều đáng để thử lại. 429 là do vượt quá giới hạn 300 lần mỗi phút, còn 503 với upstream_busy là do mô hình đang bận tạm thời. Bạn nên dùng cơ chế lùi lại theo cấp số nhân kèm nhiễu ngẫu nhiên, bắt đầu từ 1 giây, đặt số lần thử tối đa và đừng thử lại vô hạn.
Khi gọi hàm, mô hình không trả về tool_calls thì phải làm sao?
Điều này cho thấy mô hình cho rằng không cần gọi hàm, lúc này message.content chính là câu trả lời cuối cùng. Nếu bắt buộc phải gọi, bạn có thể chỉ định tool_choice cho một hàm cụ thể và kiểm tra xem mô tả hàm và Schema tham số đã được viết rõ ràng chưa.
Hội thoại nhiều lượt có làm tăng chi phí không?
Có. Vì API không có trạng thái, bạn phải gửi lại toàn bộ lịch sử mỗi lần, khiến số token đầu vào tăng dần theo số lượt. Bạn có thể chỉ giữ lại vài lượt gần nhất, hoặc nén các nội dung cũ thành tóm tắt, đồng thời dùng max_tokens để giới hạn kết quả đầu ra.
Chỉ cần điền biểu mẫu để lấy khóa
Tạo tài khoản, sao chép khóa, sửa Base URL. Cấu hình rất đơn giản.
Lấy khóa API