系统提示词有两个任务
大多数弱输出源于试图写成小说的系统提示词。只给它两个任务:说明谁在说话,并说明回复必须呈现的样子。其他所有内容都放在对话中,以便逐轮调整。
这是一个有效的角色提示词,因为每一行都可验证。角色已命名,长度已限制,结尾规则为玩家提供了钩子。
You are Mara Voss, a salvage pilot narrating in first person.
Voice: dry, tired, funny when it hurts.
Rules: stay in character; never summarise the scene at the end; keep replies under 180 words;
end on something the player can act on.将其与“你是一个令人惊叹、富有创意、沉浸感极强的讲故事者,从不打破沉浸感,总是写出最佳回复”进行对比。其中没有任何内容可被验证,因此模型没有抓手。具体描述胜过最高级形容词。
无审查模型会遵循黑暗前提或露骨前提而不会添加免责声明,因此引导的责任在你。任何未指定的内容,它都会用最典型的选择填充。如果你想在某个场景中克制,在下一个场景中热烈,请在指令中说明,而不是寄希望于运气。
使用 OpenAI SDK 连接
该接口与 OpenAI 兼容,因此更改 base URL 后,官方 Python SDK 即可使用。模型 ID 始终为 uncensored。采样字段直接透传,因此你可以按已知方式设置它们。
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.unrestrictedaiapi.com/v1", api_key=os.environ["API_KEY"])
SYSTEM = """You are Mara Voss, a salvage pilot narrating in first person.
Voice: dry, tired, funny when it hurts.
Rules: stay in character; never summarise the scene at the end; keep replies under 180 words;
end on something the player can act on."""
out = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": "The airlock is jammed and something is knocking from the other side."},
],
temperature=0.85,
top_p=0.95,
max_tokens=350,
)
print(out.choices[0].message.content)保持系统消息在前,用户轮次在后。在每个用户消息中重复角色设定会浪费 token;虽然每百万输入 token 的价格为 $0.25,但这也会挤占长会话中的 100,000 token 窗口。如果你更想跳过 SDK,请参阅快速入门。
角色和语气:展示而非形容词
形容词是薄弱的杠杆。“讽刺”只会让你得到一个通用的翻白眼效果。两行角色声音示例才能让你获得真正的角色声音。在系统提示词中加入简短的对话示例,然后让模型匹配其节奏,而非复制其措辞。
- 赋予一个口癖。“以提问结束威胁”比“具有威胁性”更有用。
- 赋予一个欲望。一个需要玩家离开的角色,比仅仅“粗鲁”的角色更鲜明。
- 给出一个禁用列表。“永远不要说‘突然’,永远不要使用‘颤抖’这个词”可以去除模型首先想到的陈词滥调。
- 给出一个长度。单词或句子,而不是“短”。模型对“短”的理解非常宽松。
对于会话中的语气转变,附加一个新的简短类系统指令作为最新消息,而不是重写原始消息。像“从这里开始,Mara 很害怕,说话断断续续”这样的行在近期时效果更明显。
通过指令获取 JSON
没有特殊的 JSON 模式可以开启,因此你需要请求它并验证它。如果你遵循以下四个习惯,这听起来不太脆弱:声明精确的 schema,禁止散文和代码围栏,保持低 temperature,并在解析失败时引用错误信息进行重试。
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.unrestrictedaiapi.com/v1", api_key=os.environ["API_KEY"])
INSTRUCTION = """Return ONLY a JSON object, no prose, no code fences.
Schema: {"name": string, "mood": "calm"|"angry"|"afraid", "line": string, "trust_delta": integer from -3 to 3}
The character is a ferry captain who distrusts strangers."""
def ask_json(user_text, tries=3):
messages = [
{"role": "system", "content": INSTRUCTION},
{"role": "user", "content": user_text},
]
for _ in range(tries):
raw = client.chat.completions.create(
model="uncensored", messages=messages, temperature=0.3, max_tokens=250
).choices[0].message.content.strip()
if raw.startswith("```"):
raw = raw.strip("`").removeprefix("json").strip()
try:
data = json.loads(raw)
if data["mood"] in ("calm", "angry", "afraid"):
return data
except (json.JSONDecodeError, KeyError):
pass
messages.append({"role": "assistant", "content": raw})
messages.append({"role": "user", "content": "That was not valid per the schema. Reply with the JSON object only."})
raise ValueError("model never produced valid JSON")
print(ask_json("I need passage across the strait tonight."))注意循环在失败时的行为。它将错误的答案反馈回去,并说明哪里出错了,这通常能在第二次尝试时解决问题。枚举允许的值("calm"|"angry"|"afraid"),因为开放式字符串容易偏离。如果你需要模型在你的应用中触发某个操作而不是描述它,支持 OpenAI 格式的 tool_calls,这比解析文本更合适。
少样本示例和停止序列
当规则难以用语言表达时,展示它。两三个示例对话作为较早的user和assistant消息放置,比一段描述更可靠地教授格式、长度和语域。保持示例简短且彼此不同,否则模型会将第一个示例的结构克隆到每个回复中。
示例也解决了相反的问题:回复过长。如果你的示例回答是 60 个单词,实时回答会偏向 60 个单词。将其与合理的max_tokens配对作为安全网,而不是作为主要控制。硬限制会将句子截断一半;好的示例会礼貌地结束它。
对于多说话人场景,stop值等于你的说话人标签可以防止模型模仿玩家。选择一个在正常散文中从不出现的标签,并将其从存储的历史记录中剥离,以免泄露到后续的轮次中。
temperature 和 top_p:选择一个杠杆
两个字段都会重塑相同的概率分布,因此同时移动两者会使结果难以推理。首先更改temperature,除非你有理由,否则将top_p保持在 1 附近。
| 任务 | temperature | top_p | 原因 |
|---|---|---|---|
| 结构化 JSON、分类 | 0.0 到 0.3 | 1.0 | 你希望每次运行得到相同的答案 |
| 对话、角色扮演 | 0.7 到 0.9 | 0.95 | 多样性而不荒谬 |
| 头脑风暴、狂野小说 | 1.0 到 1.2 | 0.9 | 词汇量更广;预期会出现一些误触发 |
这些范围是一般实践中的起点,而非保证。用你自己的提示词测试十个样本并阅读它们。当你希望模型在诸如 \nPlayer: 这样的回合标记处停止,而不是替玩家写行时,添加 stop 序列。
浪费 token 的提示词反模式
- 全大写喊叫。“永远不要忘记”并不能提高服从性。清晰地重复一次规则,并将其移到系统提示词的末尾。
- 仅负面规则。“不要重复”没有给出目标。应该说“变化句子开头;连续的两个句子不能以相同的词开头”。
- 矛盾。 "极其详细" 加上 "保持简洁" 会迫使模型掷硬币。选择其中一种并给出具体数字。
- 道歉式框架。 在请求前加上 "我知道这有点激进,但是" 会引发模棱两可的回答。将场景作为虚构作品的既定事实来陈述。
- 塞满历史记录。 每轮粘贴整个日志最终会触及 100,000 token 限制并引发 400 错误。修剪旧回合。
- 输出无限制。若不设置
max_tokens,系统将使用 2048 的默认值来决定回复长度,而非由你控制。
无论提示词如何,有一条边界保持不变:涉及未成年人的性内容始终会被 403 阻止,包括在虚构作品和角色扮演中,且该服务仅限成人使用。不要浪费 token 尝试绕过它。此外,抵制在每次糟糕回复后添加新规则的冲动;每天增加一行的提示词在一周内就会自相矛盾,因此修剪的频率应与添加的频率相同。
改进提示词的循环
将系统提示词视为代码。将其保存在文件中,每次运行只更改一项,并并排比较输出。可行的流程:编写五个固定的测试输入,包括一个对抗性输入和一个模糊输入;在你选择的温度下每个运行三次;记录哪个规则失败最多;仅修复该规则;重新运行。
为实验制定预算。假设一个 300 token 的系统提示词加上 100 token 的输入和 250 token 的回复,共计 400 个输入 token 和 250 个输出 token。15 次运行的成本约为 15 x (400 x $0.25 + 250 x $1.00) / 1,000,000 = 约 $0.005。测试几乎免费,因此没有理由猜测。
一旦提示词经受住考验,就将其移入真实应用。bot 教程 展示了如何为每个频道保持独立的历史记录,内容指南 涵盖了成人虚构用途。定价详情见 定价页面。