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 读取密钥,请不要把密钥写进代码仓库,也不要放进前端页面。几个限制提前说清楚:上下文窗口 64,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 超过 64k),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,单次最大 16,000。如果你的场景只需要一两句回复,就明确设成 200 或 300,既能防止模型啰嗦,也能封住单次请求的费用上限。
算一笔账:一次请求把 max_tokens 设为 1,000,最坏情况输出费用是 $0.001;设到上限 16,000,最坏是 $0.016。试用额度 $0.50 折合约 50 万输出 tokens,或者 200 万输入 tokens,足够把本文所有示例跑很多遍。要注意提示词加 max_tokens 的总和不能超过 64,000,否则直接返回 400,所以输入很长时要同步调低输出上限。更详细的价格说明在 价格页。
另一个细节:如果回复被截断,响应里的 finish_reason 会是 "length",说明是 max_tokens 不够,而不是模型自己写完了。写长文时可以检查这个字段,决定要不要接着续写。
多轮对话:自己维护上下文
接口本身是无状态的,服务器不会记住上一次请求。要让模型接着聊,就得每次把完整的历史消息按顺序重新发过去:先是 system,然后 user、assistant 交替。这也意味着每多聊一轮,输入的 token 数就多一截,成本是累加的,聊到后面每一轮的输入费用都会比前面高。
上下文总量被 64,000 tokens 限制(含本次输出),所以长对话必须做裁剪。最简单的办法是保留 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 限制输出。