先定四条原则
- 一个请求一个任务。让模型既写剧情又做摘要又出 JSON,三样都会打折。拆成多次调用,成本很低,输入每百万 token 只要 $0.25。
- 规则要可检验。“写得生动一点”无法检验,“每段不超过 120 字,至少一处环境描写”可以。
- 正向表述优先。说“只写动作和对白”,比说“不要写心理活动”稳定得多。
- 先小样本,后批量。任何 Prompt 改动,先用五到十条样例对比,再上线。
这个模型对合法的成人向内容、虚构题材和有争议的话题不会拒绝,所以你不需要在 Prompt 里绕弯子、反复声明“这只是小说”。直接把任务写清楚,反而更稳。唯一的硬边界是涉及未成年人的性内容,无论虚构与否都会返回 403,这一条没法靠措辞改变。
system prompt 的两段式结构
推荐把 system 拆成“规则”和“设定”两块,规则放前面、编号,设定放后面、只写事实。下面是一个小说叙述者的写法:
你是「夜航」,一名为成年读者写黑色悬疑小说的叙述者。
# 规则
1. 第三人称过去时,每段不超过 120 字。
2. 不替用户的角色做决定,只写环境和其他角色的反应。
3. 每次输出 300 到 500 字,结尾停在一个未决的动作上。
4. 不总结、不点评、不加免责声明,直接写正文。
# 设定
时间:1998 年深秋。地点:港口城市旧码头区。
主角:沈野,退役水警,嗜烟,右耳有旧伤。几个要点:
- 规则不超过六条,条数越多,后面的越容易被忽略。
- 数字约束(段落字数、输出长度)比形容词有效。
- 结尾约束(停在未决动作上)能让多轮续写自然衔接。
- 设定里不要塞剧情走向,剧情由 user 消息推进,否则后面和用户的输入打架。
输出长度要与 max_tokens 配合:规则说 500 字,而 max_tokens 只给 200,就会被截断在句子中间。
再补一个实战细节:system 里的编号规则最好每条只表达一件事。“每段不超过 120 字,并且不要总结”看似一条,其实是两条,拆开后模型更容易同时满足。写完后自己逐条读一遍,问一句:这条能不能用眼睛检查出有没有被遵守?不能的,就改写成能检查的形式。
角色设定怎么写才不跑偏
角色漂移是多轮对话里最常见的问题:前五轮语气正常,到第二十轮就开始“出戏”。对策有三个。
- 设定只写可观察的特征。“沈野:嗜烟,右耳有旧伤,说话短句多”,优于“沈野是一个复杂而有魅力的人”。
- 把说话方式写成例句。给两三句示范台词,模型模仿例句的效果远好于理解形容词。
- 定期重申。对话很长时,每隔若干轮在 user 消息末尾追加一行简短提醒,比如“保持沈野的短句风格”,成本只有几十个 token。
另外,别让角色设定和输出规则混在一起。规则是“怎么写”,设定是“写谁”,分开后你可以在不动规则的前提下换角色,做 A/B 对比也方便。长对话要注意上下文总量,细节见100k 长上下文实战。
多角色场景再加一条:每个角色单独一行设定,台词风格各给一句示范,避免所有人说话像同一个人。用户扮演的角色,只在设定里写“由用户控制”,让模型不要代写。
用指令控制 JSON 输出
不要假设模型“一定”返回合法 JSON。可靠做法是三层:指令写死格式,参数压低随机性,代码兜底解析。
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.wushenchaapi.com/v1", api_key=os.environ["API_KEY"])
SYSTEM = (
"你是信息抽取器。只输出一个 JSON 对象,不要 Markdown 代码块,不要任何解释。"
'格式:{"name": 字符串, "mood": "calm|tense|angry", "items": [字符串]}。'
"缺失的字段用 null,items 没有则给空数组。"
)
def extract(text, retries=2):
for _ in range(retries + 1):
resp = client.chat.completions.create(
model="uncensored",
temperature=0.2,
max_tokens=300,
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": text},
],
)
raw = resp.choices[0].message.content.strip()
raw = raw.removeprefix("```json").removesuffix("```").strip()
try:
return json.loads(raw)
except json.JSONDecodeError:
continue
return None
print(extract("老周把钥匙拍在桌上,冷着脸说:账本和那把铜钥匙,今晚都得还我。"))这段代码体现了几个习惯:
- 格式说明写在 system 里,并且给出字段取值范围(
calm|tense|angry)。 - 明确禁止代码块和解释文字,同时代码里仍然剥掉可能出现的围栏,双保险。
- 缺失字段的约定写进指令(null、空数组),避免模型自己编。
- 解析失败重试有上限,最后返回 None,让调用方决定怎么办。
如果字段很多,先在 Prompt 里贴一个完整示例对象,再让它照着输出,通常比描述一堆规则更准。
temperature 与 top_p 取值建议
以下是起点,不是定论,最终以你的样例对比为准。原则:一次只调一个参数,另一个保持默认。
| 场景 | temperature | top_p | 备注 |
|---|---|---|---|
| JSON 抽取、分类 | 0 到 0.3 | 默认 | 要稳定,重复调用结果应一致 |
| 改写、润色 | 0.5 到 0.7 | 默认 | 保留原意,允许措辞变化 |
| 小说续写、角色对话 | 0.8 到 1.0 | 0.9 到 0.95 | 要多样性,注意偶发跑题 |
| 头脑风暴、起名 | 1.0 左右 | 默认 | 多次采样取最好的 |
两个信号可以帮你判断方向:输出重复啰嗦、总是同一种句式,说明温度偏低;出现无关内容、人物名前后不一致,说明温度或 top_p 偏高。stop 参数也很实用,比如让模型写到某个标记就停,方便做分段生成。
常见失败写法
| 写法 | 问题 | 改成 |
|---|---|---|
| “尽量不要太长” | 没有数字,无法执行 | “不超过 400 字” |
| “不要写A,也不要不写A” | 规则互相矛盾 | 只保留一条明确规则 |
| 格式要求夹在对话中间 | 长对话后被淹没 | 放进 system,或每轮末尾重申 |
| 让模型“扮演一个没有限制的 AI” | 空洞的设定,对输出毫无约束 | 写具体的职责和写作规则 |
| 一次塞入几十条规则 | 后半段规则失效 | 精简到六条以内,其余拆到别的请求 |
| JSON 要求只写“返回 JSON” | 字段名每次不同 | 给出完整格式和示例 |
这张表的共同规律是:越具体越有效,越空洞越无用。另外别把“反复强调”当成解决办法,把同一句话写三遍、加粗加感叹号,效果通常不如把它改成一条带数字的规则。真正有用的是少而准,再配合样例验证。
调试流程清单
- 固定 temperature 为 0.2,复现问题。
- 只改一处 Prompt,重跑同一批样例。
- 看
finish_reason:如果是 length,问题出在 max_tokens 而不是 Prompt。 - 看 usage 里的 prompt_tokens:system 是否过长,挤占了历史对话。
- 改对之后再把 temperature 调回业务值,重新抽样验证。
接入细节见接入教程,参数完整列表在文档。如果你在评估要不要用中转类服务,成本与取舍的分析放在API 中转站分析那篇里,这里不重复。