NsfwAiApi/ガイド
Python用NSFW LLM API:ストリーミング、JSON、ツール:ストリーミング、JSON、ツール
Pythonでnsfw llm apiを使うには、標準のopenaiパッケージと2つの変更(Base URLとキー)だけで十分です。このガイドでは、初回リクエスト、ストリーミング、JSONモード、ツール呼び出し、レート制限のクリーンな処理方法を示します。

セットアップ:インストールと初回呼び出し
現在のPython 3、openaiパッケージ(pip install openai)、およびAPIキーが必要です。新しいアカウントは、サインアップ後に1クリックでAPIキーページで$0.50のトライアルを請求できます。支払い情報は不要です。キーは環境変数に保存し、ソースコードには含めないでください。
このサービスはOpenAIのチャット完了プロトコルを使用します:生成にはPOST /v1/chat/completions、モデル一覧にはGET /v1/modelsを使用します。認証はベアートークンで行い、モデル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)
まずシェルから接続を確認したい場合は、同じリクエストが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統合と同じように見えます。フィクション、キャラクターチャット、その他の法的な成人向けプロジェクト用のプロンプトを使用し、すべてのキャラクターが成人であることを確認してください。
いくつかの習慣は初日から役立ちます。変更しない指示はシステムメッセージに置き、ターンごとに変わる部分はユーザーメッセージに保持してください。タスクごとに1つのtemperatureを選択し、プロンプトを比較している間はそれを変更しないでください。同時に2つ変更すると、何も学べません。各呼び出しの隣に、resp.usageからのリクエストIDとトークン数をログに記録してください。請求額や出力に驚いた際に、確認できる記録が残ります。
SSEによるストリーミング返信
stream=Trueを設定すると、サーバーはサーバー送信イベントとしてトークンを送信します。チャットアプリではこれが重要になります。完全な返信を待つ代わりに、読者は最初の単語をすぐに確認できるためです。使用量の合計はストリーミングの終了時に到着するため、ストリーミングされたリクエストでも正確に課金およびログ記録できます。
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)
デバッグ時間を節約する2つの詳細があります。まず、最後のチャンクには空のchoicesリストが含まれる可能性があるため、インデックスアクセス前にチェックします。第二に、ストリーミングされたリクエストはストリーミング終了まで同時実行的なスロットを保持するため、コードがストリーミングを閉じずに放棄すると、そのスロットは使用中のままになります。with client.chat.completions.create(..., stream=True) as stream:を使用するか、早期に読み取りを停止する場合はstream.close()を呼び出してください。
構造化出力のためのJSON モード
キャラクターシート、シーン展開のリスト、またはストーリーのタグなど、散文ではなくデータが必要な場合は、response_format={"type": "json_object"}でJSONモードを使用してください。また、プロンプトに「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の周りに小さなガードを設けることで、1つの悪い呼び出しがセッション終了に至るのを防げます。
レート制限、リトライ、残高
制限はサービスを守り、予測可能な動作を保証します。フルキーは8つの同時リクエストと1分あたり300リクエストを許可します。トライアルキーは2および60を許可します。最大ボディサイズは8 MBです。2種類の制限エラーがHTTP 429として返されます。コードrate_limitedは1分あたりのリクエストが多すぎることを、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エラーとして返されます。これがある場合、作業はキーが許容する速度で流れ、無駄がありません。1分あたりの予算も実行する場合、フルキーでは1分あたり300リクエスト、つまり1秒あたり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トークン以内に収まる必要があります。いずれかの制限に達する前に、古いコンテキストをトリムまたは要約してください。
サーバー側で何かが正しくない場合、レスポンスからX-Request-Idヘッダーを保存し、短い説明とともにサポートページの連絡先まで送信してください。これにより、オペレーターが正確なリクエストを特定できます。
コストとできないこと
料金は前払いクレジットから、入力トークン100万あたり$0.15、出力トークン100万あたり$0.50です。サブスクリプションはありません。失敗または拒否されたリクエストは課金されず、クレジットは期限切れになりません。計算例として、3,000トークンを送信して800トークンを受信するスクリプトは、入力で$0.00045、出力で$0.0004を支払い、1回あたり$0.00085、つまり1,000回の呼び出しで$0.85がかかります。チャージは$20から$500まで行え、$50以上で+5%、$100以上で+10%、$200以上で+15%のボーナスがあります。当サイトは暗号資産のみを受け付け、TRC20上のUSDTまたはBase上のUSDCです。完全な表は価格ページで、エンドポイントリファレンスはドキュメントをご覧ください。
境界は明確です。これは独立したプロバイダーが運営する単一のオープンウェイトモデルであり、任意のモデルベンダーの公式APIではありません。コンテキストウィンドウはプロンプトと完了の合計で100,000トークンです。画像、オーディオ、ビデオの生成は行わず、埋め込みも提供せず、ファインチューニングもサポートしません。法的な成人向け利用のみに使用し、重要な制限として未成年者を含む性的なコンテンツがないことに注意してください。プロンプトはトレーニングに使用されません。
その他のガイド
質問と回答
PythonからこのAPIを呼び出すのに特別なSDKが必要ですか?
いいえ。公式のopenaiパッケージは動作します。base_urlをhttps://api.nsfwaiapi.com/v1に設定し、APIキーを渡してモデルID uncensoredを使用してください。
ストリーミングはOpenAIと同じように動作しますか?
はい。stream=Trueを渡して、サーバー送信イベントをチャンクごとに読み取ってください。使用量はストリームの終了時に報告されます。
429エラーが発生したときはどうすればよいですか?
待機して再試行してください。レスポンスにRetry-Afterヘッダーがある場合は、それに従ってください。エラーコードrate_limitedは1分あたりのリクエストが多すぎることを、concurrencyは一度に多すぎるリクエストを意味します。
支払いなしでテストできますか?
はい。サインアップ後、APIキーページで$0.50の試用クレジットを請求できます。支払い情報の登録は不要で、7日間有効、1人あたり1回限りです。
キーはフォーム 1 つで手に入ります
アカウントを作成し、キーをコピーし、ベースURLを変更します。これでセットアップは完了です。
API キーを取得NsfwAiApiNSFWテキスト生成用の無検閲LLM API