NsfwAiApi/指南
Python 版 NSFW LLM API:流式输出、JSON 和工具
要以 Python 方式调用 nsfw llm api,你只需使用标准的 openai 包并进行两处修改:base URL 和密钥。本指南展示了首次请求、流式输出、JSON 模式、工具调用以及对速率限制的清晰处理。

设置:安装并进行首次调用
你需要当前版本的 Python 3、openai 包(pip install openai)以及一个 API 密钥。新账户在注册后只需点击一下,即可在 API 密钥页面 领取 $0.50 的试用额度;无需支付信息。将密钥存储在环境变量中,不要放在源代码里。
该服务遵循 OpenAI 聊天补全协议:POST /v1/chat/completions 用于生成,GET /v1/models 用于获取模型列表。认证方式为 Bearer 令牌,模型 ID 为 uncensored。这是最小的工作脚本:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.nsfwaiapi.com/v1",
api_key=os.environ["NSFWAIAPI_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You are a fiction co-writer for adult readers. Write vivid, non-explicit scenes."},
{"role": "user", "content": "Open a noir story in a rainy harbor town, three sentences."},
],
temperature=0.8,
)
print(resp.choices[0].message.content)
print(resp.usage)
如果你希望先从 shell 检查连接,相同的请求也可以使用 curl 完成:
curl https://api.nsfwaiapi.com/v1/chat/completions \
-H "Authorization: Bearer $NSFWAIAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "uncensored", "messages": [{"role": "user", "content": "Say hello in five words."}]}'
该模型经过调整,可在合法成人用途下无内容拒绝地回答。这会影响其写作语气,但不影响 API 的结构,因此你的其余代码看起来与其他 OpenAI 集成无异。将用于小说、角色聊天和其他合法成人项目的提示词保留下来,并确保每个角色都是成年人。
一些习惯从第一天起就很有帮助。将永不更改的指令放入系统提示词中,并将用户消息保留给每次交互中变化的部分。为任务选择一个 temperature 并在比较提示词时保持不变,因为同时更改两件事什么也学不到。记录请求 ID 以及 resp.usage 中的 token 计数,这样当账单或输出让你惊讶时,就有记录可供核对。
使用 SSE 进行流式回复
设置 stream=True,服务器将以服务器发送事件的形式发送 token。对于聊天应用来说这非常重要,因为读者可以很快看到开头的文字,而不是等待完整回复。使用情况总量在流结束时到达,因此流式请求仍然可以准确计费并记录。
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.nsfwaiapi.com/v1",
api_key=os.environ["NSFWAIAPI_KEY"],
)
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Write a short scene: two rival smugglers share a lantern during a storm."}],
stream=True,
)
text = ""
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
piece = chunk.choices[0].delta.content
text += piece
print(piece, end="", flush=True)
usage = getattr(chunk, "usage", None)
if usage:
print("\n[usage]", usage)
两个细节可以节省调试时间。首先,最后一个 chunk 可能包含空的 choices 列表,这就是循环在索引前检查它的原因。其次,流式请求会占用其并发槽位直到流结束,因此如果你的代码在没有关闭流的情况下放弃流,该槽位将保持忙碌。使用 with client.chat.completions.create(..., stream=True) as stream: 或在提前停止读取时调用 stream.close()。
用于结构化输出的 JSON 模式
当你的程序需要数据而非散文时,例如角色表、场景节拍列表或故事标签,请使用 JSON 模式并设置 response_format={"type": "json_object"}。同时在提示词中说明“JSON”并描述你需要的键。该设置要求返回 JSON 对象,但不能保证你的键一定存在,因此请检查它们。
import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.nsfwaiapi.com/v1",
api_key=os.environ["NSFWAIAPI_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "Reply with a JSON object with keys: name, age, goal, secret. All characters are adults."},
{"role": "user", "content": "Create a ship's cook for a pirate adventure."},
],
temperature=0.7,
)
sheet = json.loads(resp.choices[0].message.content)
missing = [k for k in ("name", "age", "goal", "secret") if k not in sheet]
if missing:
raise ValueError(f"model skipped keys: {missing}")
print(sheet["name"], "-", sheet["goal"])
如果长回答被 max_tokens 截断,JSON 将不完整,json.loads 将失败。将 max_tokens 留空,因为它是可选的,且补全可以使用窗口的剩余部分,或者将其设置为一个较大的值。
工具调用
工具调用让模型指示你的代码执行某些操作,例如掷骰子、查找角色笔记或获取价格。你传入 tools,如果需要,还可以传入 tool_choice。回复包含 tool_calls;你执行这些操作并将结果作为带有 role: "tool" 的消息发回。流式响应在增量中也包含 tool_calls。
import json
import os
import random
from openai import OpenAI
client = OpenAI(
base_url="https://api.nsfwaiapi.com/v1",
api_key=os.environ["NSFWAIAPI_KEY"],
)
tools = [{
"type": "function",
"function": {
"name": "roll_die",
"description": "Roll one die with the given number of sides",
"parameters": {
"type": "object",
"properties": {"sides": {"type": "integer", "minimum": 2}},
"required": ["sides"],
},
},
}]
messages = [
{"role": "system", "content": "You run a tabletop fiction game. Use roll_die for any chance-based outcome."},
{"role": "user", "content": "I try to pick the lock on the captain's cabin. Roll a d20."},
]
first = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = first.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = random.randint(1, args["sides"])
messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
final = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools
)
print(final.choices[0].message.content)
else:
print(msg.content)
将参数视为不受信任的输入。在执行之前验证它们,就像对待 Web 表单一样。模型有时会发送缺失字段或错误类型,围绕 json.loads 的小守卫可以防止一次糟糕的调用结束你的会话。
速率限制、重试和余额
限制保护服务并为你提供可预测的行为。完整密钥允许 8 个并发请求和每分钟 300 个请求。试用密钥允许 2 个和 60 个。最大请求体为 8 MB。两种类型的限制错误以 HTTP 429 返回:代码 rate_limited 表示每分钟请求过多,concurrency 表示同时请求过多。临时过载也返回 429,带有 Retry-After: 5 标头。
OpenAI SDK 默认会重试一些错误。为了获得控制,请关闭该功能并编写一个小循环来遵守 Retry-After:
import os
import time
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.nsfwaiapi.com/v1",
api_key=os.environ["NSFWAIAPI_KEY"],
max_retries=0,
)
def ask(messages, tries=5):
for attempt in range(tries):
try:
raw = client.chat.completions.with_raw_response.create(
model="uncensored", messages=messages
)
print("balance:", raw.headers.get("X-Balance-USD"), "request id:", raw.headers.get("X-Request-Id"))
return raw.parse()
except RateLimitError as err:
wait = float(err.response.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
raise RuntimeError("still rate limited after retries")
reply = ask([{"role": "user", "content": "Describe a market at dawn in two sentences."}])
print(reply.choices[0].message.content)
每个响应都包含 X-Request-Id,这是你联系支持时引用的内容,以及 X-Balance-USD,显示你剩余的额度。X-RateLimit-Limit-Requests 和 X-RateLimit-Limit-Concurrency 标头告诉你哪些限制适用于你的密钥。要同时运行许多作业,使用信号量在你的代码中限制并发性,大小为密钥的 8(完整密钥)或 2(试用密钥),这样你很少会看到 429。
并行运行多个请求
批处理作业(例如为几百个故事片段生成标签)是并发限制发挥作用的地方。完整密钥同时接受 8 个请求,因此通过异步客户端和该大小的信号量运行工作。如果你使用的是试用密钥,请使用 2。
import asyncio
import os
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://api.nsfwaiapi.com/v1",
api_key=os.environ["NSFWAIAPI_KEY"],
)
gate = asyncio.Semaphore(8)
async def summarize(fragment):
async with gate:
resp = await client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "Summarize the passage in one sentence."},
{"role": "user", "content": fragment},
],
temperature=0.3,
)
return resp.choices[0].message.content
async def main(fragments):
return await asyncio.gather(*(summarize(f) for f in fragments))
results = asyncio.run(main(["The tide came in early.", "Marta hid the ledger under the floor."]))
print(results)
信号量是关键所在。没有它,包含 200 个片段的一个列表会同时发出 200 个请求,其中大多数会返回带有代码 concurrency 的 429 错误。有了它,工作会按照你密钥允许的速度进行,不会浪费任何资源。如果你还设置了每分钟预算,全密钥每分钟 300 个请求相当于每秒 5 个,因此非常短的请求可能会在达到并发限制之前触及每分钟限制。
常见问题及解决方法
- 401 或认证错误。 密钥缺失、被截断或发送到错误的主机。打印
client.base_url并检查环境变量。标头必须读取Authorization: Bearer后跟你的密钥。 - 未找到模型。 唯一的模型 ID 是
uncensored。复制的示例如果提到其他名称将会失败,因此如果你不确定,请调用GET /v1/models。 - 故事循环中出现空回复或短回复。 检查是否在下一轮之前将助手回复追加到消息列表中。如果没有它,模型只能看到你最后一条消息。
- 回复重复自身。 稍微提高
frequency_penalty,例如到 0.3,或者如果文本漂移则降低temperature。惩罚项top_p、stop和seed都会通过。 - 每次运行输出不同。 设置
seed和较低的temperature,以便在调试时获得尽可能可重复的结果。 - 请求过大。 请求体限制为 8 MB,提示词加补全必须适合 100,000 个 token。在达到任一限制之前修剪或总结旧上下文。
当服务器端出现异常时,请保存响应中的 X-Request-Id 头信息,并将其连同简短描述发送至 支持页面 的联系方式。这将帮助运营人员准确定位你的请求。
费用与限制
定价为每 1M 输入 token $0.15,每 1M 输出 token $0.50,从预付额度中扣除,无订阅。失败或被拒绝的请求不收费,且额度永不过期。以实际计算为例:发送 3,000 个 token 并接收 800 个 token 的脚本,输入费用为 $0.00045,输出费用为 $0.0004,每次调用总计 $0.00085,因此 1,000 次调用费用为 $0.85。充值金额范围为 $20 至 $500,充值 $50 及以上赠送 5%,充值 $100 及以上赠送 10%,充值 $200 及以上赠送 15%。本站仅接受加密货币支付,支持 TRC20 链上的 USDT 或 Base 链上的 USDC。完整表格请参见 定价页面,接口参考请参见 文档。
界限很明确。它是一个由独立提供商运营的开放权重模型,并非任何模型供应商的官方 API。上下文窗口为 100,000 个 token(包含提示词和补全)。它不生成图像、音频或视频,不提供嵌入,也不支持微调。仅限合法成人用途,并注意硬性限制:不包含涉及未成年人的性内容。提示词不用于训练。
更多指南
问答
从 Python 调用此 API 需要专用 SDK 吗?
不需要。官方 openai 包即可。将 base_url 设置为 https://api.nsfwaiapi.com/v1,传入密钥并使用模型 ID uncensored。
流式输出是否与 OpenAI 的方式相同?
是的。传递 stream=True 并按块读取服务器发送的事件。使用情况会在流结束时报告。
收到 429 错误时该怎么办?
等待并重试。如果响应包含 Retry-After 标头,请遵守它。错误代码 rate_limited 表示每分钟请求过多,concurrency 表示同时请求过多。
我可以免费测试吗?
可以。注册后,你可以在 API 密钥页面领取 $0.50 的试用额度。无需支付信息,有效期 7 天,每人限领一次。