성인 채팅봇 제품 책임 운영: 빌더 체크리스트
성인 채팅봇은 실제 제품이며, 실제 제품에는 책임이 따릅니다. API는 합법적인 성인 콘텐츠를 거부하지 않는 모델을 제공하므로, 연령 확인, 자체 규칙, 신고 및 오용 제어는 모델이 아닌 앱에 위치합니다. 이 체크리스트는 작동하는 코드를 통해 각 레이어를 단계별로 설명하므로 자신 있게 출시할 수 있습니다.
업데이트됨
주요 포인트
- 플랫폼은 성인 전용입니다. 진입로에서 연령을 확인하고 검증을 서버 측에서 유지하십시오.
- 자체 콘텐츠 정책과 신고 경로를 작성하십시오. 모델의 개방성이 귀하의 규칙을 대체하지 않기 때문입니다.
- 미성년자가 포함된 성적 콘텐츠는 항상 403으로 차단됩니다. 명확하고 차분한 UX를 설계하십시오.
- 사용자별 사용량을 제한하고 API 키를 서버에 보관하세요. 모든 트래픽은 하나의 키로 공유됩니다.
누가 어떤 책임이 있습니까
스택을 두 레이어로 나누어 생각하세요. API는 18세 이상 사용자를 위해 거부 없이 합법적인 성인 콘텐츠, 픽션 및 논쟁적인 주제를 제공합니다. 모델 주변 환경, 접근 가능한 사람들, 그들이 동의하는 규칙, 문제가 발생했을 때의 처리 방식은 모두 귀하의 것입니다. 이러한 분리는 자유롭게 해줍니다. 하지만 귀하가 자신의 부분을 진지하게 받아들이지 않는다면 그 자유는 무의미합니다.
벽에 걸어둘 수 있는 짧은 책임 테이블이 있습니다.
| 우려 사항 | 플랫폼 | 귀하의 앱 |
|---|---|---|
| 미성년자가 포함된 성적 콘텐츠 | 항상 차단됨 (403) | 설명하고, 장면을 종료하며, 우회 방법을 방지하세요. |
| 사용자의 연령 | 약관상 성인 전용 | 입구에서 제한하고 확인하십시오 |
| 법률 이상의 자체 규칙 | 적용되지 않음 | 작성, 게시 및 시행하십시오 |
| 사용자별 오용 | 키당 분당 300회 요청 | 사용자별 및 일일 제한 |
| 비밀 키 | 발급합니다 | 서버에 보관하십시오 |
이는 엔지니어링 가이드라인이며 법률 자문이 아닙니다. 규칙은 장소에 따라 다르므로 사용자가 거주하는 지역의 요구 사항을 확인하십시오.
실제로 차단하는 연령 제한
"나는 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를 연결하십시오. 시장이나 위험 수준에서 요구하는 경우 제3자 연령 확인과 같은 더 강력한 확인을 추가하고, 재프롬프트할 수 있도록 마커를 충분히 짧게 유지하십시오. 마케팅에서 학생이나 학교를 대상으로 하지 마십시오. 미리보기 및 샘플 대화 포함하여 모든 성인 콘텐츠가 표시되기 전에 게이트를 배치하십시오.
자체 콘텐츠 정책 작성
모델은 합법적인 성인 자료를 거부하지 않으므로 귀하가 제품에 허용할 내용을 결정합니다. 한 페이지 분량의 정책으로 충분합니다. 환영받는 콘텐츠, 합법적이더라도 커뮤니티에서 금지된 콘텐츠(예: 실제 사람에 대한 괴롭힘 또는 도싱), 사용자가 문제를 보고하는 방법, 그리고 귀하가 취하는 조치(경고, 일시 정지, 밴)를 다루십시오.
- 사용자가 가입하는 곳에 게시하십시오, 평이한 언어로.
- 시스템 프롬프트에 이를 반영하세요. "모든 캐릭터는 성인입니다. 비동의적이거나 이름이 명시된 사람들이 포함된 장면은 종료합니다."와 같은 문구는 봇이 귀하의 규칙에 부합하도록 유지합니다.
- 시행을 지루하게 만드세요. 모더레이터가 즉흥적으로 결정하지 않도록 임계값을 미리 결정하세요.
- 정책을 분기별로 검토하십시오 제품 진화에 따라.
사용자가 찾을 수 있는 신고 경로
모든 메시지에 간단한 신고 컨트롤을 제공하며, 로그인 과정을 거치지 않고 작동하도록 하세요. 누군가가 응답을 신고하면 대응에 필요한 최소한의 정보를 캡처하세요: 사용자 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 });
});약속을 좁게 유지하세요. 사용자에게 보고가 무엇을 트리거하는지 정확히 알려주고, 자체 정책과 적용 가능한 규칙이 요구하는 것만 저장하세요. 보고서를 얼마나 오래 보관하는지 검토하고, 일정에 따라 삭제하세요. 보고서를 제출한 사람에게 각 보고를 인정하세요. 심지어 한 줄의 감사 인사라도, 아무 일도 일어나지 않는 것 같으면 사용자가 보고하는 것을 멈출 것이기 때문입니다. 모더레이터가 항목을 닫는 속도를 추적하고, 실제로 달성할 수 있는 목표를 설정하세요.
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 키는 모든 사용자가 공유하는 총 분당 300개의 요청을 허용합니다. 하나의 스크립트 계정이 다른 모든 사용자를 기아 상태로 만들 수 있으므로, 해당 한계선보다 훨씬 낮은 수준에서 자체적으로 공정 사용 제한을 적용해야 합니다. 예시에서는 각 사용자를 분당 12개 메시지로 제한하고, 마지막 스트리밍 청크의 usage 값을 기준으로 일일 토큰 예산을 제공합니다.
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의 무료 체험 크레딧을 사용하는 것입니다.
키는 서버에 보관하세요
API 키를 프론트엔드 번들, 모바일 앱 또는 공개 저장소에 포함하지 마세요. 환경 변수나 시크릿 매니저에 저장하고 백엔드에서만 로드하며 로그와 오류 보고서는 제외하세요. 노출이 의심되면 계정에서 키를 재생성하세요. 이전 키는 즉시 작동하지 않으므로 배포 준비를 해두세요. 계정당 하나의 키가 존재하므로 깔끔한 프록시 레이어가 더욱 가치 있다는 점을 기억하세요. 웹 채팅 튜토리얼에서 패턴을 전체적으로 확인하고, 문서에는 래퍼가 매핑해야 하는 오류 코드가 나열되어 있습니다.
출시 전 체크리스트
문을 열기 전 날 이 목록을 확인하세요. 각 항목은 몇 분 걸리며, 사용자가 도착한 후에 수정하기 어려운 유형의 문제를 방지합니다.
- 게이트를 깨보십시오. 쿠키 없이 curl로 채팅 라우트를 호출하세요. 모델 응답이 아닌 403과 함께
age_required를 반환해야 합니다. - 모든 상태를 트리거하세요. 상위 서비스에서 402, 403, 429, 503을 순서로 반환하도록 스텁 처리하고, 각 상태가 올바른 배너를 표시하며 원본 오류 텍스트가 누출되지 않음을 확인하세요.
- 귀하의 정책을 소리 내어 읽어보세요. 모더레이터가 이를 바탕으로 한 건을 1분 내에 결정할 수 없다면, 문구를 명확하게 하세요.
- 신고 버튼을 휴대폰 화면에서 테스트하십시오. 접근하기 어려운 컨트롤은 보고 경로로 간주되지 않습니다.
- 저장소에서 키를 검색하십시오. 프론트엔드 폴더, 빌드 출력, 샘플 구성 파일 및 커밋 기록을 확인하세요. 공개된 곳에서 발견되면 키를 재생성하세요.
- 알림을 설정하십시오. 402 오류, 단일 사용자로부터의 403 차단 급증, 일일 토큰 사용량 급증 시 페이지를 설정하세요.
- 일시 정지 스위치를 준비하십시오. 채팅을 우아하게 종료하는 단일 플래그는 사고 발생 시 매우 귀중합니다.
안전은 완결되지 않으며 목록도 마찬가지입니다. 사고가 새로운 교훈을 줄 때마다 항목을 추가하고, 팀에 새로 합류하는 모든 구성원과 공유하세요. 자체 UI에 이미지 업로드, 그룹 룸 또는 공개 캐릭터 공유와 같은 기능을 추가할 때마다 목록을 다시 검토하세요. 각 기능은 새로운 남용 경로를 열 수 있습니다. 캐릭터 자체를 구성하는 데 도움이 필요하면 페르소나 디자인 가이드를 읽고, 팀이 성장함에 따라 문서의 통합 기본 사항을 가까이 두세요.
질문과 답변
체크박스로 연령 확인이 충분한가요?
최소한의 조치이며 진술을 기록하지만 우회하기 쉽습니다. 서버에서 이를 강제하고, 위험도나 현지 규정에 따라 더 강력한 확인 절차를 추가하세요.
403 content_blocked 오류 발생 시 앱은 어떻게 해야 하나요?
재시도하지 마세요. 이를 차분한 인앱 메시지로 번역하고, 세션을 종료하거나 초기화하며, 자체 시행을 위해 이벤트를 로그하세요.
사용자에게 내 API 키의 용량을 공정하게 공유할 수 있나요?
네, 서버에서 사용자별 속도 제한과 일일 토큰 제한을 추가하면 됩니다. 키 자체는 총 분당 300개의 요청으로 제한됩니다.
모델이 제 규칙을 강제하나요?
아니요. 합법적인 성인 콘텐츠는 거부하지 않으므로, 정책, 시스템 프롬프트 및 검열 흐름에서 해당 작업을 수행해야 합니다.