繁中 ▾
取得 API 金鑰

使用 LLM API 進行文字角色扮演情境

更新

當模型扮演帶有規則的敘事者,且你的程式碼掌控狀態時,文字角色扮演情境效果最佳。本文將透過 Python 中的小型引擎進行說明:提示詞、串流輸出、記憶與長會話的成本估算。

小型舞台的立體模型,發光的對話氣泡代表文字角色扮演情境

重點摘要

  • 分工:模型撰寫故事,你的程式碼儲存事實、物品和規則。
  • 串流輸出回覆,並保持每個串流足夠短,以便快速釋放並行請求槽位。
  • 100,000 token 的上下文視窗可容納非常長的會話,但摘要可降低費用與漂移。
  • 200 輪會話,每輪 6,000 輸入 token 和 400 輸出 token,費用約為 $0.22。

從敘事者開始,而非劇情

建立文字角色扮演情境的人通常從撰寫劇情開始。這是不對的。情境是玩家與敘事者之間的迴圈,而敘事者需要先有職責描述,然後才需要故事。模型必須知道誰在說話、它可以為玩家做出哪些決定、回覆應該多長,以及它絕對不能做什麼。其他一切都可以即興發揮。

情境引擎的良好系統提示詞有四個部分。首先是角色:遊戲主持人、訪談者、船載電腦、戲劇中的角色。其次是世界規則,用幾句簡單的話描述:基調、設定、存在與不存在的事物。第三是格式規則,因為你的程式碼可能會解析它們:例如「每則回覆以一行以 OPTIONS: 開頭的內容結束」。第四是界限。此文本 API 針對合法成人用途調整為無審查,這使其適合黑暗小說、具有真正威脅感的反派和道德灰色劇情。它仍有一條紅線:不涉及未成年人的性內容。將該規則放入你自己的服務條款中,如果你的情境針對成人,請在應用程式中實施年齡驗證。

將提示詞保持在約 600 字以內。長規則列表往往只能被部分遵循。如果你需要許多規則,請對它們進行排序並刪除最後三分之一。

帶有串流輸出的工作迴圈

串流輸出在角色扮演中比在大多數用途中更重要。閱讀故事的玩家希望看到模型撰寫時文字出現,如果 300 token 的回覆一次性顯示,感覺會比實際更慢。API 透過伺服器發送事件 (SSE) 進行串流,並使用 stream: true,OpenAI SDK 會處理解析。端點與 OpenAI 兼容,模型 ID 為 uncensored,此站點的基礎 URL 為 https://api.sdxlapi.com/v1。

from openai import OpenAI

client = OpenAI(base_url="https://api.sdxlapi.com/v1", api_key="YOUR_KEY")

SYSTEM = """You are the narrator of a noir mystery set in a rainy harbour city.
Write in second person, present tense, 120 to 200 words per reply.
Never decide what the player says or does. Invent details freely, but stay
consistent with the STATE block you are given.
End every reply with one line: OPTIONS: a | b | c"""

state = {"place": "Pier 9 office", "time": "23:40", "inventory": ["brass key"], "facts": []}
history = []

def turn(player_text: str) -> str:
    history.append({"role": "user", "content": player_text})
    msgs = [{"role": "system", "content": SYSTEM + "\nSTATE: " + str(state)}] + history[-20:]
    stream = client.chat.completions.create(
        model="uncensored", messages=msgs, stream=True,
        temperature=0.9, top_p=0.95, max_tokens=500,
        presence_penalty=0.3,
    )
    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)
    history.append({"role": "assistant", "content": reply})
    return reply

turn("I check the desk drawer.")

設定已針對虛構內容進行調整。溫度設為 0.8 至 1.0 可產生多樣的散文風格,而 top_p 設為 0.95 左右可排除最奇怪的用詞選擇。設定較小的存在懲罰 (presence penalty) 可抑制敘述者重複使用相同的意象。將 max_tokens 設為上限以限制回覆長度;這雖為選配,但若未設定,健談的敘述者可能會寫出超出你預期長度的內容。若有一段場景希望在測試時完全重現,請傳入 seed 並保持其他參數固定。

讓程式碼掌控事實

長篇角色扮演中最常見的失誤是劇情偏離。鑰匙在第 5 回合時在玩家口袋裡,在第 40 回合時卻在桌上。店主先被稱為瑪拉,後來變成瑪塔。讀取 30,000 token 歷史的模型通常能正確處理,但「通常」並非設計上的保證。將事實保存在小型結構中,讓你的程式進行更新,並將該結構放入每個請求中,如上方的 STATE 行所示。

如何更新狀態?讓模型在每個回合後報告變更,透過第二次使用 JSON 模式的低成本呼叫來完成。第一次呼叫為玩家撰寫故事。第二次讀取新的段落,僅回傳變更的內容。

import json

def extract_changes(passage: str) -> dict:
    r = client.chat.completions.create(
        model="uncensored",
        response_format={"type": "json_object"},
        temperature=0,
        max_tokens=250,
        messages=[
            {"role": "system", "content":
             'Read the passage. Return JSON: {"place": str|null, "inventory_add": [str], '
             '"inventory_remove": [str], "new_facts": [str]}. Only report what the passage states.'},
            {"role": "user", "content": passage},
        ],
    )
    return json.loads(r.choices[0].message.content)

def apply(ch: dict):
    if ch.get("place"):
        state["place"] = ch["place"]
    for item in ch.get("inventory_add", []):
        if item not in state["inventory"]:
            state["inventory"].append(item)
    state["inventory"] = [i for i in state["inventory"] if i not in ch.get("inventory_remove", [])]
    state["facts"] = (state["facts"] + ch.get("new_facts", []))[-30:]

限制事實清單的長度,如同切片操作一樣,否則狀態區塊會持續增長,直到變得與它旨在取代的歷史記錄一樣沉重。如果 JSON 回覆無法解析,則跳過該回合的更新並繼續;遺漏一個事實的成本比系統崩潰還要低。

骰子、生命值與計時器最好由純程式碼處理。當玩家嘗試冒險行為時,在 Python 中執行擲骰邏輯並告知敘述者結果:「玩家的嘗試失敗了。」接著由模型撰寫後果。這可防止模型悄悄讓玩家每次都贏,因為語言模型往往會這樣做。

數小時會話的記憶

上下文視窗為 100,000 token,計算提示詞和補全的總和。典型的回覆 250 token 加上 30 token 的玩家訊息每輪約為 280 token,因此純歷史本身可以容納數百輪。你可以全部發送。許多開發者仍然不這樣做,原因有二:費用隨著每一輪增長,因為整個歷史每次都作為輸入計費,舊細節可能會分散模型的注意力。

合理的佈局保持三個層:

  1. 固定: 系統提示詞和狀態區塊。始終發送。
  2. 摘要: 一段或兩段關於已發生事件的持續更新段落,每 10 到 15 輪透過一次短呼叫進行重新撰寫。
  3. 最近輪次: 最後 10 到 20 條訊息逐字,以保持文筆的節奏。

當最近幾輪的上下文視窗填滿時,將最早的十輪內容合併至摘要中並將其移除。摘要呼叫可使用較低的溫度,並將 token 數限制在 300 以內。玩家通常不會注意到壓縮過程,只要名字、承諾與未解決的威脅都包含在摘要中即可。請在摘要提示詞中明確寫道:「保留名字、債務、傷勢、未解之謎」。

若希望角色保持穩定的語氣,請在固定層中提供一張短小的風格卡片:包含三句其對話範例以及兩項它絕不會說的話。短範例比形容詞更能塑造語氣。

一次對話的成本

定價為每 1M 輸入 token $0.15,每 1M 輸出 token $0.50,採用預付額度且無須訂閱。進行長會話的計算。假設 200 個回合,加上固定層、摘要和最近回合,平均每個請求有 6,000 個輸入 token,每個回覆有 400 個輸出 token(故事回覆加上狀態和摘要呼叫的小部分)。

輸入:200 x 6,000 = 1,200,000 token,即 1.2 x $0.15 = $0.18。輸出:200 x 400 = 80,000 token,即 0.08 x $0.50 = $0.04。會話費用為 $0.22。

比較懶惰的版本,即重新發送整個原始歷史。到第 200 輪,歷史約為 56,000 token,整個會話的平均輸入每請求約為 28,000 token。那是 200 x 28,000 = 5,600,000 輸入 token,或 5.6 x $0.15 = $0.84,加上相同的 $0.04 輸出,總計 $0.88。仍然負擔得起,但質量沒有提升的情況下費用是四倍。對於擁有許多玩家的應用程式,這個差距決定你的利潤。

失敗或拒絕的請求不收費,額度永不過期,所以未使用的餘額只是等待。儲值從 $20 到 $500 整美元,從 $50 起有 5% 獎勵,從 $100 起有 10% 獎勵,從 $200 起有 15% 獎勵。請參見 定價頁面 以獲取選項的完整列表。

同時運行多個玩家

完整金鑰允許 8 個並行請求和每分鐘 300 個請求。串流請求會佔用其槽位直到串流結束,因此使用 8 個槽位和約十秒鐘的回覆,你可以服務大約每分鐘 48 個串流回覆,假設沒有其他運行。這是一個粗略的規則,不是測量數據,因為速度隨負載和回覆長度而變化。重點是為此進行規劃:如果你的遊戲同時玩家多於槽位,請在伺服器中排隊請求,並顯示短「敘事者正在思考」狀態,而不是同時觸發所有請求。

當超過限制時,API 會回傳 HTTP 429。每分鐘請求的錯誤代碼為 rate_limited,同時請求過多時的錯誤代碼為 concurrency。暫時過載也會回傳 429,並帶有 Retry-After: 5 標頭。簡單的等待五秒後重試就足以應付休閒遊戲。也要讀取回應標頭:X-RateLimit-Limit-Requests 和 X-RateLimit-Limit-Concurrency 顯示你的上限,X-Balance-USD 顯示剩餘金額,你可以將其顯示在管理畫面中。

不要與下一個玩家輪次並行運行狀態提取呼叫。等待它,否則敘事者將基於過時的事實撰寫。一個好的妥協是串流結束後立即開始提取,讓玩家在運行時閱讀。

將場景連接到你的影像管線

此站點是為在 Stable Diffusion 工作流程中使用文本模型的人構建的,情境非常適合該 API 的純文本特性。API 本身僅限文本:它不生成圖像、音頻或視頻。但敘事者可以撰寫場景描述,你的管線將其轉換為圖像提示詞。在重大場景變化後添加另一個 JSON 呼叫,要求環境的短視覺描述,包括照明、情緒和攝像機角度,然後將該字符串傳遞給你運行的任何圖像工具。文件頁面 顯示提示詞撰寫呼叫的請求結構,SD 提示詞生成器 的文章更深入地介紹了這一點。

若要試用引擎,請建立帳戶並領取試用額度。新用戶可在 API 金鑰頁面 上一鍵獲得 $0.50的免費試用額度,有效期為 7 天且無需綁定信用卡。根據上述數據,該額度足以進行幾次完整的會話。試用金鑰限制為 2 個並行請求與每分鐘 60 次請求,適合單人測試。

將小說內容保留在小說中。成人主題的情境在參與者均為成年人時屬於合法使用;唯一不可妥協的原則是禁止涉及未成年人的性內容。傳送至 API 的提示詞不會用於訓練。

問答

模型能扮演任何角色或類型嗎?

這是一個通用文本模型,調整為不拒絕合法的成人請求,因此恐怖、犯罪、戲劇和喜劇均可。唯一的嚴格限制是涉及未成年人的性內容。

上下文填滿之前會話可以持續多久?

上下文視窗為 100,000 token,包含提示詞與補全。透過摘要層,由於舊輪次被壓縮,實際運行可遠超此限制。

我應該串流輸出回覆嗎?

是的,適用於任何人即時閱讀的內容。串流會在結束時提供用量資訊,且串流會佔用一個並行請求槽位,直到完成為止。

有辦法讓場景完全重複嗎?

傳入相同的 seed 與相同的訊息和設定。這是測試輔助工具,不保證輸出內容的位元組完全相同。

你的金鑰只需填寫一張表單即可取得

建立帳號、複製金鑰、更改基礎 URL。這就是整個設定過程。

取得 API 金鑰