アダルトチャットボット製品の責任ある運営:ビルダー向けチェックリスト
アダルトチャットボットは実際の製品であり、実際の製品には責任が伴います。APIは法的に許容されるアダルトコンテンツを拒否しないモデルを提供するため、年齢確認、自社ルール、レポート、不正防止の制御は、モデルではなくあなたのアプリ側で行う必要があります。このチェックリストは、各レイヤーで動作するコードとともに手順を示すため、自信を持ってリリースできます。
更新日:
主要ポイント
- プラットフォームは大人のみを対象としています。入口で年齢を確認し、チェックはサーバー側で実行してください。
- 独自のコンテンツポリシーと報告先を作成してください。モデルの開放性があなたのルールに取って代わるわけではありません。
- 未成年者を含む性的コンテンツは常に403でブロックされます。明確で落ち着いたUXを設計してください。
- ユーザーごとの利用制限を設け、APIキーをサーバーに保持してください。1つのキーはすべてのトラフィックで共有されます。
責任の分担
スタックを2つのレイヤーで考えてください。APIは18歳以上のユーザー向けに、合法的なアダルトコンテンツ、フィクション、論争的なトピックを拒否せずに提供します。モデル周辺、アクセスできる人物、合意されるルール、問題発生時の対応はすべてあなたの責任です。この分離は自由ですが、あなたの部分を真剣に受け止める場合にのみ有効です。
壁に貼っておける簡潔な責任表を以下に示します。
| 懸念事項 | プラットフォーム | あなたのアプリ |
|---|---|---|
| 未成年者を含む性的コンテンツ | 常にブロック(403) | 説明し、シーンを終了し、回避策を防ぐ |
| ユーザーの年齢 | 規約上は成人のみ | 入口で制限し確認する |
| 法律を超える自社ルール | 適用されない | 作成、公開、執行する |
| ユーザーごとの不正行為 | キーごとに1分あたり300リクエスト | ユーザーごとおよび1日あたりの制限 |
| シークレットキー | 発行する | サーバーに保持 |
これはエンジニアリングのガイダンスであり法的助言ではありません。場所によってルールが異なるため、ユーザーが居住する地域の要件を確認してください。
実際に機能する年齢制限
「私は18歳です」というボタン付きのバナーが最小限の要件ですが、決意のある未成年者はそれをクリックできます。それでも重要なのは、肯定的な表明を記録し、ポリシーの誠実さを維持するためです。キーはここで適用する場所です。チェックがブラウザでチャットを非表示にするだけなら、誰でもエンドポイントに直接アクセスできます。サーバーが署名済みマーカーが存在するまで応答しないようにしてください。
import crypto from "node:crypto";
// Minimal 18+ gate: the user must confirm before the chat routes work.
// Replace the confirmation with a real verification step when your market requires it.
const SECRET = process.env.GATE_SECRET; // random string kept on the server
function sign(value) {
return crypto.createHmac("sha256", SECRET).update(value).digest("hex");
}
export function issueAdultCookie(res, userId) {
const stamp = String(Date.now());
const payload = `${userId}.${stamp}`;
res.cookie("adult_ok", `${payload}.${sign(payload)}`, {
httpOnly: true, sameSite: "strict", secure: true, maxAge: 30 * 24 * 3600 * 1000,
});
}
export function requireAdult(req, res, next) {
const raw = req.cookies?.adult_ok || "";
const [userId, stamp, sig] = raw.split(".");
if (!sig || sign(`${userId}.${stamp}`) !== sig) {
return res.status(403).json({ state: "age_required" });
}
req.userId = userId;
next();
}チャットルートの前にrequireAdultを配置してください。市場やリスクレベルに応じて、第三者の年齢確認など、より強力な検証を追加し、マーカーを短期間に保って再プロンプト可能にしてください。マーケティングでは学生や学校をターゲットにせず、プレビューやサンプル会話を含むすべてのアダルトコンテンツが表示される前にゲートを設置してください。
独自のコンテンツポリシーの作成
モデルは合法的なアダルト素材を拒否しないため、あなたの製品が何を許可するかを決定するのはあなたです。1ページのポリシーで十分です。歓迎されるコンテンツ、合法的であってもコミュニティでは禁止されるコンテンツ(例えば実在する人物への嫌がらせやドクシングなど)、ユーザーが問題を報告する方法、および警告、一時的なミュート、BANなどの対応措置を記載してください。
- ユーザーがサインアップする場所で公開する。平易な言語で。
- システムプロンプトにも反映させてください。「すべてのキャラクターは大人です。非合意または実在の特定の人物が登場するシーンでは終了する」といった一文で、ボットをあなたのルールに合わせます。
- 運用を凡そなものに。 閾値を事前に決定し、モデレーターがその場しのぎの対応をしないようにします。
- ポリシーを四半期ごとにレビューし、製品が変化するにつれて対応してください。
ユーザーがアクセスできる報告先
すべてのメッセージに小さなレポートコントロールを付け、ログインの手続きを省略して機能するようにしてください。誰かが返信を報告した場合、対応するために必要な最小限のデータ(ユーザーID、メッセージID、短い理由、タイムスタンプ)をキャプチャし、モデレーターが監視するキューにルーティングしてください。
app.post("/api/report", requireAdult, express.json(), (req, res) => {
const { messageId, reason } = req.body;
// Store only what you need to act on: who, which message, why, when.
queueForModerator({ userId: req.userId, messageId, reason: String(reason).slice(0, 500), at: Date.now() });
res.json({ ok: true });
});約束を狭く保ってください。報告が何を引き起こすかをユーザーに正確に伝え、あなたのポリシーと適用規則が必要とするデータのみを保存してください。報告の保持期間をレビューし、スケジュールに従って削除してください。報告した人物に、1行の感謝の言葉であっても、報告への対応を示してください。何も起こらないようだとユーザーは報告を止めます。モデレーターが案件を閉じるまでの速度を追跡し、実際に達成可能な目標を設定してください。
403未成年者ブロックの設計
未成年者を含む性的コンテンツは、フィクションやロールプレイでも常にブロックされ、APIは403ステータスとコードcontent_blockedを返します。これを再試行したり、ユーザーに代わって言い換えを試みたりしないでください。代わりに、明確な設計を要する特定の事象として扱ってください。
- サーバー側で変換する。UIが認識できる状態にし、生のアップストリームテキストが画面に表示されないようにします。
- 落ち着いた一文で何が起こったかを伝え、ルールを明記する。
- シーンを終了またはリセットする。「続行する」を提供することは、その意味を損なう。
- イベントをログに記録し、ユーザーIDを記録して、再試行時に独自の執行をトリガーできるようにします。
- 意図を推測しないことブロックの理由は文章の曖昧さによるものもあるため、中立なメッセージは両方のケースで機能します。
// server: translate upstream outcomes into states your UI understands
if (upstream.status === 403) {
return res.status(403).json({
state: "content_blocked",
message: "That request breaks our rules, so we can't continue this scene.",
});
}
if (upstream.status === 402) return res.status(503).json({ state: "paused" });
if (upstream.status === 503) return res.status(503).json({ state: "busy" });// browser: show a calm, specific message for each state
const MESSAGES = {
content_blocked: "This scene can't continue. Sexual content involving minors is never allowed. Start a new chat any time.",
age_required: "Please confirm you are 18 or older to use the chat.",
slow_down: "You're sending messages quickly. Give it a few seconds.",
daily_limit: "You've reached today's message allowance. Come back tomorrow.",
paused: "Chat is briefly unavailable. We're on it.",
busy: "The service is busy. Try again in a moment.",
};
async function send(messages) {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages }),
});
if (!res.ok) {
const info = await res.json().catch(() => ({}));
showBanner(MESSAGES[info.state] || "Something went wrong.");
if (info.state === "content_blocked") endCurrentScene();
return null;
}
return res;
}
ユーザーごとの不正利用とレート制御
単一のAPIキーでは、すべてのユーザーで合計1分あたり300リクエストが許可されます。1つのスクリプトアカウントが他のすべてのユーザーを枯渇させる可能性があるため、その上限をはるかに下回るフェアユース制限を自ら適用してください。例では、各ユーザーを1分あたり12メッセージに制限し、最終ストリーミングチャンク内のusage値から測定される1日あたりのトークン予算を各ユーザーに与えます。
const WINDOW_MS = 60_000;
const PER_USER_PER_MIN = 12; // your product rule, far below the key's 300
const DAILY_TOKEN_BUDGET = 60_000; // per user, tracked from the usage chunk
const hits = new Map(); // userId -> array of timestamps
const spent = new Map(); // userId -> tokens used today
export function userLimit(req, res, next) {
const now = Date.now();
const recent = (hits.get(req.userId) || []).filter((t) => now - t < WINDOW_MS);
if (recent.length >= PER_USER_PER_MIN) {
return res.status(429).json({ state: "slow_down" });
}
if ((spent.get(req.userId) || 0) >= DAILY_TOKEN_BUDGET) {
return res.status(429).json({ state: "daily_limit" });
}
recent.push(now);
hits.set(req.userId, recent);
next();
}
export function recordUsage(userId, totalTokens) {
spent.set(userId, (spent.get(userId) || 0) + totalTokens);
}本番環境では、プロセスメモリではなく共有ストアにカウンターを保存し、再起動後も制限が維持され、複数のサーバー間で機能するようにします。すべてのリクエストに適切なmax_tokensを追加して最悪の場合を制限し、残高不足による402エラーが発生していないか監視してください。7日間の$0.50の無料トライアルは、実際のトラフィックが到着する前にこれらの制御をテストするのに適した方法です。
キーをサーバーに保持する
キーをフロントエンドバンドル、モバイルアプリ、または公開リポジトリに決して含めないでください。環境変数として、またはシークレットマネージャーに保存し、バックエンドでのみ読み込み、ログやエラーレポートから除外します。露出が疑われる場合は、アカウントからキーを再生成してください。古いキーは直ちに無効になるため、デプロイの準備をしておいてください。アカウントごとにキーが1つしかないことを覚えておいてください。これにより、クリーンなプロキシレイヤーの価値がさらに高まります。web chatチュートリアルではそのパターンを最初から最後まで示しており、docsにはラッパーがマッピングすべきエラーコードがリストされています。
リリース前のチェックリスト
オープンする前日にこのリストを確認してください。各項目は数分で完了し、ユーザー到着後に修正するのが難しい同種の問題を防止します。
- ゲートを突破してみてください。クッキーなしでcurlを使用してチャットルートにアクセスしてください。モデルの応答ではなく、403と
age_requiredを返さなければなりません。 - すべての状態をトリガーする。アップストリームをスタブ化して、402、403、429、503を順に返させ、それぞれが正しいバナーを表示し、生のエラーテキストが漏洩しないことを確認します。
- ポリシーを音読する。モデレーターが1分以内にそれに基づいてケースを判断できない場合は、文章を明確にします。
- レポートボタンをスマートフォン画面でテストする。アクセスしにくいコントロールは、レポートパスとしてカウントされません。
- リポジトリ内でキーを検索する。フロントエンドフォルダ、ビルド出力、サンプル構成ファイル、コミット履歴を確認してください。公開されている場所で見つかった場合は再生成してください。
- アラートを設定する。402、単一ユーザーからの403ブロックの急増、日次トークン使用量の増加時にあなたに通知してください。
- 一時停止スイッチの準備をする。インシデント発生時にチャットを適切に停止できる単一フラグは非常に有用です。
安全性の確保は完了することではなく、リストも同様です。インシデントから何かを学んだたびに項目を追加し、チームに新しく加わる全員と共有してください。UIでの画像アップロード、グループルーム、公開キャラクターの共有など、機能を追加する際にはリストを見直してください。各機能は新たな悪用の経路を開くからです。キャラクター自体の設計については、ペルソナ設計ガイドをお読みください。チームが拡大する際、統合メモの基本的な部分はドキュメントで参照しやすくしておいてください。
質問と回答
チェックボックスだけで年齢確認は十分ですか?
これは最小限の要件であり、宣言を記録しますが、回避は容易です。サーバーで適用し、リスクや地域のルールに応じてより強力な検証を追加してください。
403 content_blockedが発生した場合、アプリはどのように振る舞うべきですか?
再試行しない。これをアプリ内の冷静なメッセージに変換し、シーンを終了またはリセットし、独自の執行のためにイベントをログに記録する。
ユーザーにAPIキーの容量を公平に共有させられますか?
はい、サーバーでユーザーごとのレート制限と1日のトークン制限を追加することで可能です。キー自体は合計で1分あたり300リクエストに制限されています。
モデルは私のハウスルールを適用しますか?
いいえ。法的に許容されるアダルトコンテンツは拒否しないため、ポリシー、システムプロンプト、モデレーションフローがその役割を果たす必要があります。