サポートされる機能とされない機能
| 項目 | ステータス | 注記 |
|---|---|---|
| チャット補完 | はい | POST /v1/chat/completions |
| レスポンス | はい | POST /v1/responses |
| ストリーミング(SSE) | はい | stream: true、末尾にusage |
| 関数呼び出し | はい | tools、tool_choice、ストリーミングされた tool_calls、結果は role: "tool" として |
| JSON モード | はい | response_format: {"type":"json_object"} |
| サンプリング制御 | はい | temperature、top_p、stop、seed、ペナルティ |
| 画像、音声、動画 | なし | テキストのみ |
| 埋め込み、ファインチューニング | なし | 提供不可 |
このモデルは、独立したプロバイダが運用する ID uncensored のオープンウェイトテキストモデルです。合法的な成人向け利用に対してコンテンツ拒否なしで応答し、OpenAI やその他のベンダーの公式 API ではありません。以下のすべての例では、Base URL https://api.chatgptunfiltered.com/v1 を使用しています。
このリストにないものは、ドキュメントで異なる旨が明記されるまで利用不可として扱ってください。これには、以下に示す関数呼び出しとは異なるベンダー固有のホスト型ツールも含まれます。
ストリーミング
stream: trueを設定し、チャンクをイテレートします。到着したテキスト片を出力し、最終チャンクからusageを読み取ります。
from openai import OpenAI
client = OpenAI(
base_url="https://api.chatgptunfiltered.com/v1",
api_key="YOUR_KEY",
)
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Write a four-line poem about a night train."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage:
print("\nusage:", chunk.usage)
2つの注意点があります。まず、ストリーミングリクエストはストリームが終了するまで同時接続スロットを占有するため、放棄されたストリームはスロットを占有し続けます。ユーザーが離脱した場合は閉じてください。第二に、最終チャンクにはテキストなしでusageのみが含まれる可能性があるため、上記のようにchoicesへのアクセスをガードしてください。
Webアプリでは、到着したチャンクをブラウザに転送します(例えば独自のSSEルート経由)。ページ側でテキストの各部分を受信メッセージに追加します。ストリームが終了した時点でサーバー側で最終チャンクからリクエストのコストを把握し、使用量の管理を行います。
ツール呼び出し、エンドツーエンド
関数を宣言し、モデルに呼び出しを依頼し、自分で実行し、role: "tool"で結果を送り返します。
import json
tools = [{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up the status of an order by its ID.",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
},
}]
def get_order_status(order_id):
return {"order_id": order_id, "status": "shipped"}
messages = [{"role": "user", "content": "Where is order A-1042?"}]
r = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = r.choices[0].message
if msg.tool_calls:
messages.append(msg.model_dump(exclude_none=True))
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = get_order_status(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
})
final = client.chat.completions.create(model="uncensored", messages=messages, tools=tools)
print(final.choices[0].message.content)
else:
print(msg.content)
モデルが関数を呼び出すかどうかを判断します。tool_choiceでこれを誘導できます。関数自体はサーバー上ではなくあなたのコードで実行されるため、アクセス可能なリソースを制御できます。モデルは間違った型や欠落したフィールドを送信する可能性があるため、実行前に引数を検証してください。
ツール呼び出しのストリーミング
stream: true と tools を組み合わせると、呼び出しは複数のチャンクとして到着します。関数名が最初に来ます。引数文字列は断片化された形式で到着するため、index を使用して結合する必要があります。
calls = {}
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Where is order A-1042?"}],
tools=tools,
stream=True,
)
for chunk in stream:
if not chunk.choices:
continue
for tc in chunk.choices[0].delta.tool_calls or []:
slot = calls.setdefault(tc.index, {"id": "", "name": "", "args": ""})
if tc.id:
slot["id"] = tc.id
if tc.function and tc.function.name:
slot["name"] = tc.function.name
if tc.function and tc.function.arguments:
slot["args"] += tc.function.arguments
for slot in calls.values():
print(slot["name"], json.loads(slot["args"]))
ストリームが終了するまでargsを解析しないでください。半分だけ受信された文字列は有効なJSONではありません。その後、関数を実行し、ストリーミングなしの例と同じ方法で結果を送り返します。
JSON モード
構造化されたレスポンスにはJSONモードを有効にし、プロンプトで希望する形状を記述します。
r = client.chat.completions.create(
model="uncensored",
temperature=0.2,
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "Reply with JSON only: {\"mood\": string, \"intensity\": integer from 1 to 5}."},
{"role": "user", "content": "Rate this line: The door slammed and nobody spoke for an hour."},
],
)
try:
data = json.loads(r.choices[0].message.content)
except json.JSONDecodeError:
data = None # retry once, or fall back
print(data)
JSON モードは有効な JSON の生成を大幅に容易にしますが、スキーマを強制するわけではありません。キーと型を自身で確認し、リトライ経路を確保してください。抽出には低い temperature が役立ちます。temperature、top_p、stop、seed、ペナルティなどのサンプリング設定はそのまま渡されます。
有用なパターンは、JSONモードを明確なエラーメッセージを返す小さな検証関数と組み合わせ、そのメッセージを1回のリトライでモデルにフィードバックすることです。「最後のレスポンスではキー intensity が欠けていました。有効なJSONで再回答してください。」モデルは通常2回目の試行で自己修正し、追加のリクエストは1セントの小数点以下のコストで済みます。
ストリーム中のエラー処理
ストリームは通常のリクエストでは発生しない方法で失敗します。テキストの半分が送られた後に接続が切断される可能性があります。その可能性に備えてください。
import time
from openai import RateLimitError, APIConnectionError
def stream_reply(messages):
for attempt in range(3):
parts = []
try:
stream = client.chat.completions.create(
model="uncensored", messages=messages, stream=True
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
parts.append(chunk.choices[0].delta.content)
return "".join(parts)
except RateLimitError:
time.sleep(5)
except APIConnectionError:
time.sleep(1)
raise RuntimeError("stream failed after retries")
ここで429は制限または一時的な過負荷を意味し、APIが送信するRetry-After: 5に一致する5秒待機するのが正しい対応です。ストリームが途中で切れた場合、部分的な回答が得られます。それを破棄してリトライするか、維持してモデルに継続を依頼するかを判断してください。チャットUIでは、破棄してリトライする方がユーザーにとって混乱が少ない場合がほとんどです。
計画すべき制限とコスト
- レート: フルキーでは1分あたり300リクエスト、トライアルキーでは60リクエスト。
- 同時実行数: フルキーでは8、トライアルキーでは2。オープンなストリームはすべてカウントされることを忘れないでください。
- サイズ: リクエストボディ最大8 MB、コンテキストウィンドウ100,000トークン(プロンプトと補完を含む)。
- 429処理: コード
rate_limitedとconcurrencyは制限超過を意味します。一時的な過負荷はRetry-After: 5付きの429を返します。
コスト例:2つのリクエストを含む関数呼び出しのやり取り。1回目は入力1,200トークン、出力80トークン、2回目は入力1,400トークン、出力150トークン。入力は 2,600 x $0.15 / 1M = $0.00039。出力は 230 x $0.50 / 1M = $0.000115。合計 $0.000505。失敗または拒否されたリクエストには課金されません。料率は 料金に記載されており、完全なメソッドリストは ドキュメントにあります。APIキーページから $0.50 のトライアルクレジットで開始します。モデルの背景については 無検閲ChatGPTモデルの理解をご覧ください。すべてのモードで硬い制限が適用されます:未成年者が関わる性的コンテンツは禁止されています。
プレリリースチェックリスト
- ツール引数はtryブロック内で
json.loadsで解析され、実行前に期待されるキーと型に対してチェックされます。 - 公開する関数は、匿名ユーザーに許可するもののみを実行できます。呼び出すタイミングはあなたではなくモデルが判断します。
- JSONレスポンスは検証され、失敗時に1回リトライします。
- ユーザーがページを離れるとストリームは終了するため、同時実行数のスロットを占有しません。
- コードでの同時実行数の制限は、キーの制限以下です。フルキーの場合の制限は 8 です。
X-Request-IdからのリクエストIDはログに記録されます。- キーはサーバー上にあり、再生成可能です。古いキーはすぐに無効になります。
サービスはテキストのみなので、画像や埋め込みが必要な機能は別のプロバイダから取得する必要があります。
