先定四條原則
- 一個請求一個任務。讓模型既寫劇情又做摘要又出 JSON,三樣都會打折。拆成多次呼叫,成本很低,輸入每百萬 token 只要 $0.25。
- 規則要可檢驗。「寫得生動一點」無法檢驗,「每段不超過 120 字,至少一處環境描寫」可以。
- 正向表述優先。說「只寫動作和對白」,比說「不要寫心理活動」穩定得多。
- 先小樣本,後批量。任何提示詞改動,先用五到十條樣例對比,再上線。
這個模型對合法的成人向內容、虛構題材和有爭議的話題不會拒絕,所以你不需要在提示詞裡繞彎子、反覆聲明「這只是小說」。直接把任務寫清楚,反而更穩。唯一的硬邊界是涉及未成年人的性內容,無論虛構與否都會返回 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,讓呼叫方決定怎麼辦。
如果欄位很多,先在提示詞裡貼一個完整範例物件,再讓它照著輸出,通常比描述一堆規則更準。
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 中轉站分析那篇裡,這裡不重複。