NsfwAiApi/Handleiding
NSFW LLM API in Python: streaming, JSON en tools
Om een NSFW LLM API vanuit Python te gebruiken, heb je alleen het standaard openai-pakket en twee aanpassingen nodig: de base URL en de sleutel. Deze gids toont een eerste verzoek, streaming, JSON-modus, tool calling en nette afhandeling van rate limits.

Instellen: installeren en de eerste aanroep doen
Je hebt een actuele Python 3, het openai-pakket (pip install openai) en een API-sleutel nodig. Nieuwe accounts kunnen een $0,50 proefbedrag claimen op de API-sleutelpagina met één klik na het aanmelden; er zijn geen betaalgegevens nodig. Sla de sleutel op in een omgevingsvariabele, niet in de broncode.
De service gebruikt het OpenAI chat-completiesprotocol: POST /v1/chat/completions voor generatie en GET /v1/models voor de modellenlijst. Authenticatie is een bearer token, en de model-ID is uncensored. Dit is het kleinste werkende script:
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)
Als je de verbinding liever eerst vanuit een shell controleert, werkt hetzelfde verzoek met 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."}]}'
Het model is afgestemd op het beantwoorden zonder inhoudelijke weigeringen voor wettelijk volwassen gebruik. Dit beïnvloedt de toon van wat het zal schrijven, niet de vorm van de API, dus de rest van je code lijkt op elke andere OpenAI-integratie. Houd prompts voor fictie, karakterchat en ander wettelijk volwassen project, en zorg dat elk personage volwassen is.
Enkele gewoontes helpen vanaf de eerste dag. Zet de instructies die nooit veranderen in het systeembericht, en houd het gebruikersbericht voor het deel dat elke beurt verandert. Kies één temperatuur voor een taak en laat deze met rust terwijl je prompts vergelijkt, omdat het veranderen van twee dingen tegelijk je niets leert. Log de verzoek-ID en de tokenaantallen van resp.usage naast elke aanroep, zodat er een record is om tegen te controleren als je rekening of output je verbaasd stelt.
Streaming-antwoorden met SSE
Stel stream=True in en de server stuurt tokens als server-sent events. Voor chat-apps is dit van groot belang, omdat de lezer de eerste woorden snel ziet in plaats van te wachten op het volledige antwoord. Totale gebruikscijfers komen aan het einde van de stream, dus een gestreamd verzoek kan nog steeds nauwkeurig worden gefactureerd en gelogd.
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)
Twee details besparen foutopsporijdtijd. Ten eerste kan de laatste chunk een lege choices-lijst hebben, vandaar dat de lus dit controleert voordat er geïndexeerd wordt. Ten tweede houdt een gestreamd verzoek zijn concurrency-slot vast tot de stream eindigt; als je code een stream verlaat zonder deze te sluiten, blijft dat slot bezet. Gebruik with client.chat.completions.create(..., stream=True) as stream: of roep stream.close() aan als je eerder stopt met lezen.
JSON-modus voor gestructureerde output
Wanneer je programma data nodig heeft en geen proza, zoals een karakterblad, een lijst met scènebeats of tags voor een verhaal, gebruik dan JSON-modus met response_format={"type": "json_object"}. Zeg ook "JSON" in de prompt en beschrijf de sleutels die je wilt. De instelling vraagt om een JSON-object, maar kan niet beloven dat je sleutels aanwezig zijn, dus controleer ze.
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"])
Als een lang antwoord wordt afgekapt door max_tokens, is de JSON onvolledig en faalt json.loads. Laat max_tokens ongesteld, omdat het optioneel is en de voltooiing de rest van het venster kan gebruiken, of stel het ruim genoeg in.
Function calling
Tool calling stelt het model in staat je code iets te laten doen, zoals dobbelen, een karakternotitie opzoeken of een prijs ophalen. Je geeft tools door en, als je dat wilt, tool_choice. De respons bevat tool_calls; je voert ze uit en stuurt de resultaten terug als berichten met role: "tool". Gestreamde responsen bevatten ook tool_calls in de deltas.
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)
Behandel de argumenten als onbetrouwbare invoer. Valideer ze voordat je ze gebruikt, precies zoals je dat bij een webformulier zou doen. Modellen sturen soms een missend veld of een verkeerd type, en een kleine guard rondom json.loads voorkomt dat één slechte aanroep je sessie beëindigt.
Rate limits, retries en je saldo
Limieten beschermen de service en geven je voorspelbaar gedrag. Een volledige sleutel staat 8 gelijktijdige verzoeken en 300 verzoeken per minuut toe. Een proefsleutel staat 2 en 60 toe. De maximale bodytekst is 8 MB. Twee soorten limietfouten komen terug als HTTP 429: de code rate_limited betekent te veel verzoeken per minuut, en concurrency betekent te veel in één keer. Een tijdelijke overbelasting retourneert ook 429, met een Retry-After: 5-header.
De OpenAI SDK probeert een paar fouten standaard opnieuw. Voor controle zet je dat uit en schrijf je een kleine lus die Retry-After respecteert:
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)
Elke respons draagt X-Request-Id, wat je citeert als je contact opneemt met support, en X-Balance-USD, wat je resterende tegoed toont. De X-RateLimit-Limit-Requests en X-RateLimit-Limit-Concurrency-headers vertellen je welke limieten van toepassing zijn op je sleutel. Om veel taken tegelijk uit te voeren, beperk je parallelisme in je eigen code met een semaphore die is afgestemd op je sleutel, 8 voor een volledige sleutel en 2 voor een proefsleutel, zodat je zelden een 429 ziet.
Veel verzoeken parallel uitvoeren
Batchtaken, zoals het genereren van tags voor een paar honderd verhaalfragmenten, zijn waar concurrency limieten ertoe doen. Een volledige sleutel accepteert 8 gelijktijdige verzoeken, dus voer het werk uit via een async client en een semaphore van die grootte. Als je een proefsleutel hebt, gebruik dan 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)
De semaphore is de hele truc. Zonder deze vuurt een lijst van 200 fragmenten 200 verzoeken tegelijk af, en de meeste komen terug als 429-fouten met de code concurrency. Met deze stroomt het werk op snelheid die je sleutel toelaat en wordt niets verspild. Als je ook een per-minuut budget hebt, werken 300 verzoeken per minuut op een volledige sleutel uit tot vijf per seconde, dus zeer korte verzoeken kunnen de minuutlimiet raken voordat de concurrency limiet.
Veelvoorkomende problemen en fixes
- 401 of authenticatiefouten. De sleutel ontbreekt, is afgekapt of wordt naar de verkeerde host gestuurd. Print
client.base_urlen controleer de omgevingsvariabele. De header moet lezenAuthorization: Bearergevolgd door je sleutel. - Model niet gevonden. De enige model-ID is
uncensored. Een gekopieerd voorbeeld dat een andere naam noemt, faalt, dus roepGET /v1/modelsaan als je niet zeker weet. - Lege of korte antwoorden in een verhaallus. Controleer of je het antwoord van de assistant aan de berichtlijst toevoegt voor de volgende beurt. Zonder dit ziet het model alleen je laatste bericht.
- Antwoorden die zichzelf herhalen. Verhoog
frequency_penaltyiets, bijvoorbeeld naar 0,3, of verlaagtemperatureals de tekst afdrijft. De straffen,top_p,stopenseedworden allemaal doorgegeven. - Andere uitvoer bij elke run. Stel
seeden een lagetemperaturein als je resultaten nodig hebt die zo herhaalbaar mogelijk zijn terwijl je fouten opspoort. - Verzoek te groot. De body limiet is 8 MB, en prompt plus voltooiing moeten passen in 100.000 tokens. Knip of vat oude context samen voordat deze een van beide limieten bereikt.
Als er aan de serverkant iets fout lijkt te zijn, bewaar dan de X-Request-Id-header van de respons en stuur deze naar het contact op de ondersteuningspagina samen met een korte beschrijving. Het stelt de medewerkers in staat je exacte verzoek te vinden.
Wat het kost en wat het niet kan
Prijzen zijn $0,15 per 1M input tokens en $0,50 per 1M output tokens, van prepaid tegoed zonder abonnement. Mislukte of geweigerde verzoeken worden niet in rekening gebracht, en tegoed vervalt nooit. Als werkvoorbeeld betaalt een script dat 3.000 tokens stuurt en 800 ontvangt $0,00045 voor input plus $0,0004 voor output, wat $0,00085 per aanroep is, dus 1.000 aanroepen kosten $0,85. Opwaarderingen lopen van $20 tot $500, met bonussen van +5% vanaf $50, +10% vanaf $100 en +15% vanaf $200. Deze site accepteert alleen crypto, in USDT op TRC20 of USDC op Base. Zie de prijzen pagina voor de volledige tabel, en de documentatie voor de endpoint referentie.
De grenzen zijn duidelijk. Het is een enkel open-weight model, gerund door een onafhankelijke provider, en niet de officiële API van een modelleverancier. Het contextvenster is 100.000 tokens voor prompt plus voltooiing. Het genereert geen afbeeldingen, audio of video, biedt geen embeddings aan en ondersteunt geen fine-tuning. Gebruik het alleen voor wettelijk volwassen gebruik, en let op de harde limiet: geen seksuele inhoud met minderjarigen. Prompts worden niet gebruikt voor training.
Meer handleidingen
Vragen en antwoorden
Heb je een speciale SDK nodig om deze API vanuit Python aan te roepen?
Nee. Het officiële openai-pakket werkt. Stel base_url in op https://api.nsfwaiapi.com/v1, geef je sleutel door en gebruik de model-ID uncensored.
Werkt streaming hetzelfde als bij OpenAI?
Ja. Geef stream=True door en lees de server-sent events stuk voor stuk. Gebruik wordt aan het einde van de stream gerapporteerd.
Wat moet je doen als je een 429-fout krijgt?
Wacht en probeer het opnieuw. Als de respons een Retry-After-header heeft, houd er dan rekening mee. De foutcode rate_limited betekent te veel verzoeken per minuut, en concurrency betekent te veel verzoeken tegelijk.
Kan ik het testen zonder te betalen?
Ja. Na het aanmelden kun je $0,50 aan proeftegoed claimen op de API-sleutelpagina. Er zijn geen betaalgegevens nodig, het is 7 dagen geldig en het is één per persoon.
Je sleutel is nog maar één formulier verwijderd
Maak een account aan, kopieer de sleutel, pas de base URL aan. Dat is de hele setup.
API-sleutel ophalenNsfwAiApiUncensored LLM API voor NSFW tekstgeneratie