API 提供什么,你保留什么
该服务是一个托管的、OpenAI 兼容的文本 API。你只需将 OpenAI SDK 指向 https://api.chatbotnsfw.com/v1,使用模型 ID uncensored 并发送正常的聊天消息即可。该模型是一个开放权重模型,针对合法成人用途进行了调整,不会拒绝回答。它仅支持文本:不支持图像、音频或视频。
这就是整个协议。API 不知道你的用户是谁、年龄多大,也不知道你的平台允许什么。它也不会记住调用之间的任何内容。每次请求都必须包含你希望模型看到的完整对话。
| 关注点 | 谁负责什么 |
|---|---|
| 生成无内容拒绝的回复 | API |
| 角色、语气和故事规则 | 你,在系统提示词中 |
| 对话记忆 | 你,在你的数据库中 |
| 年龄验证与同意 | 你 |
| 屏蔽涉及未成年人的内容 | 你,API 也有相同的硬性限制 |
| 报告、封禁、使用条款 | 你 |
如果你之前没有对接过聊天 API,API 文档涵盖了请求格式。现有的 NSFW LLM 集成指南更详细地介绍了集成方面;本文主要介绍围绕它的产品。
架构:将密钥保留在服务器上
你的前端不应持有 API 密钥。在浏览器或应用与 API 之间放置一个小型后端。后端验证你的用户身份,检查年龄门控,加载其历史记录,调用 API 并存储回复。
- 浏览器将用户的消息连同其会话一起发送到你的后端。
- 后端检查用户已验证为成年人且未超过速率限制。
- 后端构建消息列表:系统提示词、记忆、最近的对话轮次、新消息。
- 后端调用
POST /v1/chat/completions并流式返回 token。 - 流式传输结束后,后端保存新的对话轮次。
完整密钥允许 8 个并发请求和每分钟 300 个请求。这对于业余机器人来说绰绰有余,对于小型产品也是一个不错的开始,前提是你的后端不让一个用户同时打开多个流。流式请求会占用并发槽位,直到流结束,因此一个离开打开状态流的用户仍然会占用一个槽位。
将角色设定为契约
系统提示词承担了大部分产品工作。一个好的提示词读起来像是一份与模型的简短契约,而不是一段形容词的堆砌。涵盖四个方面:角色是谁、它如何说话、故事是什么,以及什么是不允许的。
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.chatbotnsfw.com/v1",
api_key=os.environ["NSFW_API_KEY"],
)
PERSONA = """You play Mara, a 34-year-old ship's navigator in a slow-burn
space romance. All characters are adults.
Voice: dry, warm, short paragraphs, first person.
Story rules: keep continuity with the notes below; never speak for the user;
stay in character unless the user writes OOC: in brackets.
Hard limits: no sexual content involving anyone under 18, ever. If the user
steers there, refuse in character and change the scene."""
def reply(history, user_text):
messages = [{"role": "system", "content": PERSONA}]
messages += history[-24:]
messages.append({"role": "user", "content": user_text})
r = client.chat.completions.create(
model="uncensored",
messages=messages,
temperature=0.9,
top_p=0.95,
)
return r.choices[0].message.content有两个细节比看起来更重要。在提示词中用直白的语言说明所有角色均为成年人。并告诉模型在边界情况下该怎么做,例如切换场景,这样故事就能继续推进,而不是以错误消息结束。提高 temperature 可以让行文更生动,如果角色偏离则降低它。
在发布前测试角色
每次编辑后,将相同的二十条用户消息通过提示词运行,包括尴尬的情况:一个要求机器人脱离角色的用户,一个在场景中途改变设定的用户,一个试探未成年人规则的用户。保存输出并阅读它们。修复一个案例的提示词修改通常会破坏另一个案例。
适应上下文窗口的记忆
上下文窗口为 100,000 个 token(包含提示词和补全),因此有足够的空间容纳长篇故事。更长并不总是更好。你为每一轮的每个输入 token 付费,而拥有长上下文历史的模型无论如何都会丢失早期细节。
实用的布局有三层。系统提示词保存角色。简短的笔记块保存必须保留的事实:名字、关系状态、故事中做出的承诺。最近 20 到 30 轮对话以原文形式放入。较早的对话通过每 20 轮左右的一次廉价调用摘要到笔记块中。
上面的代码已经切片了 history[-24:],保留最后 24 条消息。如果有笔记块,将其作为第二条系统消息添加。这样成本保持很低:典型的一轮发送几千个输入 token,按每 100 万个输入 token $0.15计算,不到一分钱。定价页面有具体数字,集成文章展示了请求格式。
年龄验证与同意
成人内容产品需要在第一次聊天调用之前进行年龄验证,而不是之后。API 不验证年龄。这项职责由你承担,正确的方法取决于你运营的地区以及你的用户居住的地区。
- 一个写着 我年满 18 岁 的复选框只是一个通知,并非验证。某些司法管辖区需要更多验证。
- 在服务器端针对账户存储结果,并在每次聊天请求时进行检查,而不仅仅是在注册时。
- 在注册前,在着陆页和使用条款中明确说明仅限成人。
- 确保每个角色设定、提示词以及向用户展示的卡片中的虚构角色都是成年人。
请咨询了解你所在市场的律师,最低要求是多少。本文无法告诉你,API 提供商也无法替你决定。
由你负责的审核职责
无审查模型改变了模型拒绝的内容,但并没有改变你负责的内容。从第一天起就计划好这些:
- 对未成年人硬性屏蔽。 禁止涉及未成年人的性内容。添加你自己的输入检查以检测明显的尝试,并在触发时结束会话。
- 报告机制。 让用户可以标记对话,并决定如何处理这些报告。
- 账户管理。 能够迅速封禁用户。
- 数据。 决定保留对话记录的时间长度,并在隐私页面中说明。API 提供商不会将提示词用于训练,但你的数据库由你自行负责。
- 范围。 将产品限制在合法的成人用途和虚构内容范围内,并在服务条款中声明。
失败或被拒绝的请求不会收费,这意味着被你自己的检查拦截的请求也不会产生费用,因为它们从未到达 API。
用户级限额与成本控制
一个没有限制的开放聊天框会导致一个用户耗尽你的预付额度。为每个账户设置每日 token 上限,并在 API 调用前进行检查。响应会告诉你每轮使用了多少,因此上限只需要一个计数器。
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.chatbotnsfw.com/v1",
api_key=os.environ["NSFW_API_KEY"],
)
DAILY_CAP = 400_000 # tokens per user per day
used_today = {} # user_id -> tokens, keep this in your database
def guarded_reply(user_id, messages):
if used_today.get(user_id, 0) >= DAILY_CAP:
return "You have reached today's limit. Come back tomorrow."
r = client.chat.completions.create(model="uncensored", messages=messages)
used = r.usage.prompt_tokens + r.usage.completion_tokens
used_today[user_id] = used_today.get(user_id, 0) + used
return r.choices[0].message.content400,000 个 token 的上限是一个需要调整的数字,而非推荐值。每轮 6,000 个 token 的提示词大约允许每天 65 轮。计算用户对你的价值,据此设定上限,并在你的监控中查看 X-Balance-USD 头部,以防止 bug 在一夜之间耗尽额度。
为测试环境保留一个独立的小额度余额。测试流量和真实流量不应共用密钥,因为一个在每次错误时重试的测试循环,在一小时内消耗的金额可能超过真实用户一周的用量。
流式输出与处理 429 错误
没有流式输出时聊天感觉缓慢。设置 stream: true 并将数据块转发到浏览器。流式输出也会以使用块结束,你可以记录它来跟踪成本。
超过限制时,API 返回 HTTP 429。错误代码为 rate_limited(每分钟请求数)和 concurrency(并发请求过多)。临时过载也返回 429 并附带 Retry-After: 5。请遵守该头部。
import os, time
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.chatbotnsfw.com/v1",
api_key=os.environ["NSFW_API_KEY"],
max_retries=0,
)
def stream_reply(messages):
for attempt in range(4):
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:
yield chunk.choices[0].delta.content
return
except RateLimitError:
time.sleep(5)
yield "The service is busy. Please try again in a moment."添加用户级锁,使一个账户同时只能保持一个流。这样,8 个并发请求大约支持 8 个同时聊天的用户,其余请求则进入队列等待。
首先,获取 API 密钥。新用户可以在密钥页面一键领取 $0.50 的试用额度。试用密钥限制为 2 个并发请求和每分钟 60 个请求,足以测试流程。
