更新于
中文网络小说(网文)与角色扮演应用:上下文、角色与成本
中文网络小说连载与角色扮演聊天是两种极具挑战性的文本工作负载:每章数千字符、角色需在数月内保持一致、读者对任何瑕疵都极为敏感。本指南展示如何构建提示词、管理 100,000 个 token 的上下文窗口,并根据既定假设估算成本。
这些工作负载的特征
网络小说连载通常以 2,000 到 4,000 字的章节撰写,经常每日更新,剧情跨越数百章。角色扮演应用是对话式应用:一个角色、一个场景和一段漫长的历史。两者都需要模型 API 提供相同的三样东西:足够的上下文窗口以记住内容、足够的输出预算以进行长文本生成,以及风格不漂移。
该服务在 https://api.chinesellmapi.com/v1 提供 OpenAI 兼容的聊天接口,仅有一个文本模型,ID 为 uncensored。上下文窗口为 100,000 个 token,提示词与补全共享;max_tokens 默认为 2,048,最高可达 32,000;标准采样字段如 temperature、top_p 和 stop 均透传。支持流式输出,适合聊天场景。基础设置和编码说明位于 应用快速入门。
内容规则,前置说明
这是仅限成人的服务。合法的成人小说、成熟主题和争议性主题本身不会被拒绝,服务面向 18 岁及以上用户。涉及未成年人的色情内容始终会被拦截并返回 403 content_blocked 错误,即使以小说或角色扮演形式呈现也是如此。没有设置可以改变这一点。
从一开始就为此进行构建。在角色卡中声明每个角色均为成年人并给出明确年龄。在你的产品中增加年龄验证。将 403 视为该请求的最终答案:显示中性消息,不要使用改写后的提示词重试。无论平台规则如何,这些步骤都是良好的产品规范。
角色卡与风格块
一致性来自于将稳定事实集中在一处并在每次调用时重复。角色卡是姓名、年龄、角色、说话习惯、关系和硬性约束(包括角色尚未揭示的秘密)的紧凑记录。单独的风格块固定了视角、节奏、对话比例、章节长度和结尾习惯。两者都放入系统消息中,使其位于每个请求的开头。
CHARACTER_SHEET = """\
【人物卡】
姓名:沈清禾(女,28岁)
身份:江城古籍修复师,性格沉静,嘴硬心软
口头禅:“先别急,东西不会跑。”
说话方式:短句,少用感叹号,偶尔引用旧书里的句子
关系:与顾远舟(男,31岁,旧书商)是多年好友,彼此有未说出口的好感
禁忌:不提及她离开出版社的真正原因(第40章才揭晓)
"""
STYLE = """\
【文风】第三人称限知视角,贴近沈清禾;节奏舒缓,多写物件与天气的细节;
对话占比约四成;每章 2500 到 3000 字;结尾留一个小悬念。"""
用中文编写这些内容。中文示例中表达的说话习惯(如口头禅或句子长度)比用英文描述它们更可靠地转移。每个表格保持在几百个 token 以内,并为每个反复出现的角色提供一个;十个角色的阵容也仅占用几千个 token。当角色的情况发生变化时,更新表格本身,而不是依赖历史记录来携带变化。
连载章节的长上下文
诱惑在于将之前所有章节粘贴到提示词中。先进行计算。在 100,000 个 token 的窗口下,每章约 4,500 个 token,整个窗口仅能容纳约十四章,且没有剩余空间用于输出,而连载通常持续数百章。可行的模式是分层的记忆:
- 始终存在的角色卡和风格块。
- 滚动故事摘要,每章几百字,合并为一个运行摘要,你每隔几章刷新一次。
- 上一章的完整文本,以便正文在衔接处保持连贯。
- 正在撰写的章节大纲。
每章之后,要求模型生成一个保留关系变化和开放剧情线的简短摘要,并将其附加到摘要中。下面的代码编写一章并生成下一个摘要。它还返回 usage,以便你记录实际的 token 数量:
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.chinesellmapi.com/v1", api_key=os.environ["API_KEY"])
def write_chapter(sheet, style, story_so_far, last_chapter, outline):
messages = [
{"role": "system", "content": "你是一位连载网络小说的作者,所有人物均为成年人。严格遵守人物卡和文风设定。\n" + sheet + "\n" + style},
{"role": "user", "content": (
"【前情提要】\n" + story_so_far +
"\n\n【上一章全文】\n" + last_chapter +
"\n\n【本章大纲】\n" + outline +
"\n\n请直接写出本章正文,不要写标题以外的说明。")},
]
r = client.chat.completions.create(
model="uncensored",
messages=messages,
max_tokens=5000, # ~3,000 characters needs roughly 4,500 tokens under our assumption
temperature=0.9,
top_p=0.95,
)
return r.choices[0].message.content, r.usage
def summarize(chapter_text):
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "用 200 字以内概括下面这一章的剧情,保留人物关系的变化和未解的伏笔:\n" + chapter_text}],
max_tokens=400,
temperature=0.3,
)
return r.choices[0].message.content
角色扮演:人设、回合形状与停止序列
角色扮演需要同样的角色卡与风格规范,加上一些聊天特定的控制。将人设放入系统消息,声明谁扮演谁,固定回复长度范围,并告知模型不要生成用户角色的动作。使用 stop 在模型开始生成用户的下一行时截断输出,并流式输出回复,以便文本在生成时显示。
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.chinesellmapi.com/v1", api_key=os.environ["API_KEY"])
PERSONA = (
"你扮演顾远舟,31岁的旧书商,说话随和,爱开玩笑,但在重要的事上很认真。"
"用户扮演沈清禾。所有角色均为成年人。保持第一人称,每次回复 80 到 200 字,"
"用(括号)写简短的动作描写,不要替用户的角色做决定。"
)
history = [{"role": "system", "content": PERSONA}]
def turn(user_text):
history.append({"role": "user", "content": user_text})
stream = client.chat.completions.create(
model="uncensored",
messages=history,
max_tokens=400,
temperature=1.0,
stop=["\n用户:"], # keep the model from writing the user's next line
stream=True,
)
reply = ""
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
piece = chunk.choices[0].delta.content
reply += piece
print(piece, end="", flush=True)
print()
history.append({"role": "assistant", "content": reply})
turn("(推开书店的门)这场雨下得真不是时候。")
历史记录每回合增加一条用户消息和一条助手消息。一旦接近预算,将最老的部分总结为一条消息,并保留最近的回合原文。温度在 0.9 到 1.0 之间可为角色提供生动的对话;当需要角色严格遵循剧本时,将其降低至 0.5 左右。
贯穿长连载的风格控制
风格漂移是读者抱怨最多的问题:叙述者逐渐变得话多,对话变得正式,节奏加快。用具体细节来对抗。不要说“以优雅的风格写作”,而是描述可衡量的习惯:句子长度、成语的使用频率、包含多少天气和物体细节、章节是以悬念结尾还是平静收尾。目标语气的两三个短样本段落放在风格块中,比一页形容词更有价值。
不同体裁需要不同的调节。修仙或奇幻连载受益于虚构术语的词表(如境界、宗门和功法),以确保同一名称不会有两种拼写。现代浪漫题材依赖于对话节奏和细微的身体细节。悬疑小说需要线索账本:读者已看到的事实列表,以便模型不与之矛盾。将这些列表保留在系统提示词中,并随着故事进展进行更新。
最后,在连载过程中保持 temperature 和 top_p 固定。如果你在章节之间更改采样,文风会随之改变。仅在头脑风暴大纲时提高 temperature,而不是在正文本身。
基于既定假设的成本计算
所有数据使用公布的费率:每百万输入 token $0.25,每百万输出 token $1.00,以及假设的中文字符 1.5 个 token。将假设替换为你自己的 usage 日志中的数字。
| 场景 | 假设的输入 token | 假设的输出 token | 每次调用成本 |
|---|---|---|---|
| 连载章节(输出 3,000 字符) | 8,000 | 4,500 | $0.0020 + $0.0045 = $0.0065 |
| 角色扮演回合,20,000 token 历史 | 20,000 | 300 | $0.0050 + $0.0003 = $0.0053 |
| 角色扮演回合,完整 60,000 token 历史 | 60,000 | 500 | $0.0150 + $0.0005 = $0.0155 |
在章节行中,8,000 个输入 token 分解为 1,200 token 的角色表和风格块、2,000 token 的摘要、4,500 token 的上一章和 300 token 的大纲。一百个这样的章节成本约为 $0.65。一百次角色扮演回合在 20,000 token 的历史大小下成本约为 $0.53。要点是历史长度驱动聊天成本,因此修剪和摘要很快就能收回成本。
$0.50 的试用额度有效期为七天,在第一行的假设下可覆盖大约 76 章。查看 pricing 获取当前费率,并查看 trial FAQ 获取账户详情。
生产环境的运营说明
章节生成是长请求,因此使用慷慨的超时设置或流式输出并自行组装文本。使用短退避处理 429,因为每个密钥允许每分钟 300 次请求,并将 503 视为 upstream_busy 事件,在几秒后重试。402 表示预付余额已用完或试用已过期,应触发警报而非重试。
在生成下一章之前,将生成的章节及其摘要保存到自有数据库中。如果在夜间批处理过程中调用中途失败,你可以从最后一个存储的章节恢复,且不会为同一文本支付两次费用。将提示词版本和使用数据与每章一起存储,以便每章成本是一个查询而非猜测。
对于角色扮演类产品,请设置每次会话的预算。限制单次会话可消耗的轮数或 token 总数,并在达到上限时向用户显示清晰的提示。由于提示词包含历史记录,超长会话每轮的成本远高于全新会话,因此除了适应上下文窗口外,对旧轮次进行摘要也很重要。提示词不会用于训练,你可以在产品的隐私说明中连同你自己的存储策略一起声明这一点。
问答
单次请求中章节最长可以是多少?
每次请求的输出上限为 32,000 个 token,并与提示词共享 100,000 个 token 的上下文窗口。3,000 个字符的章节完全在限制范围内,因此请将 max_tokens 设置为略高于预估值的数值。
如何在数百个章节中保持角色一致性?
在每次调用的系统提示词中保留角色设定表和风格说明,并以滚动摘要加上上一章节的内容来延续剧情。
允许成人小说吗?
成年角色之间的合法成人小说不会被拒绝,服务面向 18 岁及以上用户。涉及未成年人的色情内容始终会被拦截并返回 403 错误,包括在小说和角色扮演中。
我可以流式输出角色扮演的回复吗?
可以。将 stream 设为 true 以接收服务器发送事件(SSE),并使用停止序列防止模型续写用户的台词。
只差一张表单,即可获得密钥
创建账户,复制密钥,更改基础 URL。这就是整个设置。