NL ▾
API-sleutel aanvragen

Rollenspellen: tekstgebaseerde scenario's met een LLM-API

Bijgewerkt

Tekstgebaseerde rollenspelscenario's werken het beste wanneer het model een narrateur speelt met regels, en jouw code de status beheert. Dit artikel doorloopt een kleine engine in Python: prompt, streaming, geheugen en een kostenraming voor een lange sessie.

Diorama van een klein podium met gloeiende spreekbellen die een tekstrollenspelscenario voorstellen

Belangrijkste punten

  • Verdeel het werk: het model schrijft het verhaal, jouw code slaat feiten, inventaris en regels op.
  • Stream antwoorden en houd elke stream kort genoeg om zijn gelijktijdige slot snel vrij te maken.
  • Een venster van 100.000 tokens omvat een zeer lange sessie, maar samenvattingen houden de kosten en drift laag.
  • Een sessie van 200 beurten met 6.000 input- en 400 outputtokens per beurt kost ongeveer $0,22.

Begin met de narrateur, niet met het plot

Mensen die tekstgebaseerde rollenspelscenario's bouwen, beginnen vaak met het schrijven van een plot. Dat is omgedraaid. Een scenario is een lus tussen een speler en een narrateur, en de narrateur heeft een functieomschrijving nodig voordat hij een verhaal nodig heeft. Het model moet weten wie spreekt, wat het voor de speler mag beslissen, hoe lang een antwoord moet zijn en wat het nooit moet doen. Alles andere kan worden geïmproviseerd.

Een goede systeemprompt voor een scenario-engine heeft vier delen. Eerst komt de rol: spelleider, interviewer, boordcomputer, een personage in een toneelstuk. Ten tweede, de wereldregels in een paar eenvoudige zinnen: toon, setting, wat er wel en niet bestaat. Ten derde, opmaakregels, omdat je code deze mogelijk moet parseren: bijvoorbeeld "eindig elk antwoord met één regel die begint met OPTIONS:". Ten vierde, grenzen. De tekst-API hier is afgestemd om zonder inhoudsweigeringen te antwoorden voor wettelijk volwassen gebruik, wat het geschikt maakt voor donkere fictie, schurken met echte dreiging en moreel grijze plots. Het heeft nog steeds één harde lijn: geen seksuele inhoud met minderjarigen. Zet die regel ook in je eigen gebruiksvoorwaarden en voer een leeftijdsgrens in je app uit als je scenario's voor volwassenen zijn bedoeld.

Houd de prompt onder de 600 woorden. Lange lijst met regels wordt maar half gevolgd. Als je veel regels nodig hebt, rangschik ze dan en laat het onderste derde deel weg.

Een werkende lus met streaming

Streaming is belangrijker in rollenspel dan in de meeste andere toepassingen. Een speler die een verhaal leest, wil woorden zien verschijnen terwijl het model schrijft, en een antwoord van 300 tokens voelt langzamer aan dan het is als het in één keer aankomt. De API streamt via server-sent events met stream: true, en de OpenAI SDKs verwerken de parsing. Het endpoint is OpenAI-compatible, de model ID is uncensored, en de base URL voor deze site is 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.")

De instellingen zijn afgestemd op fictie. Een temperatuur van ongeveer 0,8 tot 1,0 zorgt voor gevarieerde proza, en top_p rond 0,95 filtert de vreemdste woordkeuzes. Een kleine aanwezigheidsstraf ontmoedigt de verteller om terug te keren naar dezelfde beelden. Stel max_tokens in om de lengte van het antwoord te beperken; dit is optioneel, maar zonder deze instelling kan een kletserige verteller veel meer schrijven dan je wilde. Voor een scène die je tijdens het testen exact wilt herhalen, geef je seed op en houd je de andere parameters vast.

Laat code de feiten beheren

De meest voorkomende fout bij lang rollenspel is drift. De sleutel zit in de zak van de speler in beurt 5 en op tafel in beurt 40. De herbergier heette eerst Mara, toen Marta. Een model dat 30.000 tokens aan geschiedenis leest, heeft dit vaak goed, maar "vaak" is geen ontwerp. Houd de feiten in een kleine structuur die je programma bijwerkt, en voeg die structuur toe aan elk verzoek, zoals de STATE-regel hierboven doet.

Hoe werk je de status bij? Vraag het model om veranderingen te rapporteren na elke beurt, in een tweede, goedkope call die JSON-modus gebruikt. De eerste call schrijft het verhaal voor de speler. De tweede leest de nieuwe passage en retourneert alleen wat er is veranderd.

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:]

Beperk de lijst met feiten, zoals de slice dat doet, of het statusblok groeit totdat het net zo zwaar is als de geschiedenis die het moest vervangen. Als een JSON-antwoord niet kan worden geparsed, sla de update voor die beurt over en ga verder; één gemist feit is goedkoper dan een crash.

Dobbelstenen, gezondheid en timers worden beter afgehandeld in gewone code. Wanneer de speler iets risicovols probeert, gooi je in Python en vertel je de narrateur het resultaat: "De poging van de speler faalt." Het model schrijft vervolgens de gevolgen. Dit houdt het model ervan af om stilletjes de speler elke keer te laten winnen, wat taalmodellen neigen te doen.

Geheugen voor sessies die uren duren

Het contextvenster is 100.000 tokens, tellend prompt en completion samen. Een typisch antwoord van 250 tokens plus een bericht van de speler van 30 tokens is ongeveer 280 tokens per beurt, dus ruwe geschiedenis alleen honderden beurten kunnen bevatten. Je kunt het allemaal versturen. Veel bouwers doen dit nog steeds niet, om twee redenen: de kosten groeien met elke beurt, omdat de hele geschiedenis elke keer als input wordt gefactureerd, en oude details kunnen het model afleiden.

Een redelijke indeling bestaat uit drie lagen:

  1. Vastgepind: de system prompt en het statusblok. Altijd verzonden.
  2. Samenvatting: een lopende alinea of twee van wat er is gebeurd, elke 10 tot 15 beurten herschreven door een korte call.
  3. Recente beurten: de laatste 10 tot 20 berichten letterlijk, zodat de proza zijn ritme behoudt.

Wanneer het venster van recente beurten vol is, vouw je de oudste tien in de samenvatting en verwijder je ze. De samenvattingscall kan een lage temperature en een limiet van 300 tokens gebruiken. Spelers merken de compressie zelden, zolang namen, beloftes en onopgeloste dreigingen in de samenvatting terechtkomen. Zeg dat expliciet in de samenvattende prompt: "houd namen, schulden, verwondingen, open vragen".

Als je wilt dat een personage een stabiele stem behoudt, geef het dan een korte style card in de vastgepinde laag: drie voorbeeldregels van zijn spraak en twee dingen die het nooit zegt. Korte voorbeelden vormen de toon beter dan bijvoeglijke naamwoorden.

Wat een sessie kost

Prijzen zijn $0,15 per 1M input tokens en $0,50 per 1M output tokens, met prepaid tegoed en geen abonnement. Werk door een lange sessie. Stel je 200 beurten voor, met de vastgepinde laag, samenvatting en recente beurten die samen gemiddeld 6.000 input tokens per verzoek en 400 output tokens per antwoord (het verhaalantwoord plus een klein aandeel voor status- en samenvattingsaanroepen) opleveren.

Input: 200 x 6.000 = 1.200.000 tokens, wat 1,2 x $0,15 = $0,18 is. Output: 200 x 400 = 80.000 tokens, wat 0,08 x $0,50 = $0,04 is. De sessie kost $0,22.

Vergelijk de luie versie die de hele ruwe geschiedenis opnieuw verzendt. Bij beurt 200 is de geschiedenis ongeveer 56.000 tokens, en over de sessie heen is de gemiddelde input ongeveer 28.000 tokens per verzoek. Dat is 200 x 28.000 = 5.600.000 inputtokens, of 5,6 x $0,15 = $0,84, plus dezelfde $0,04 voor output, wat $0,88 maakt. Nog steeds betaalbaar, maar vier keer de rekening voor geen winst in kwaliteit. Voor een app met veel spelers, bepaalt die kloof je marge.

Mislukte of geweigerde verzoeken worden niet gefactureerd, en tegoed verloopt nooit, dus ongebruikt saldo wacht gewoon. Opwaarderingen lopen van $20 tot $500 in hele dollars, met een bonus van 5% vanaf $50, 10% vanaf $100 en 15% vanaf $200. Zie de prijzenpagina voor de volledige lijst met opties.

Veel spelers tegelijk uitvoeren

Een volledige sleutel staat 8 gelijktijdige verzoeken en 300 verzoeken per minuut toe. Een gestreamd verzoek houdt zijn slot vast tot de stream eindigt, dus met 8 slots en antwoorden die ongeveer tien seconden duren, kun je ongeveer 48 antwoordstreams per minuut bedienen, ervan uitgaande dat er niets anders draait. Dit is een ruwe richtlijn, geen gemeten figuur, omdat de snelheid varieert met belasting en antwoordlengte. Het punt is erop te plannen: als je spel meer gelijktijdige spelers heeft dan slots, queue dan de verzoeken op je server en toon een korte "verteller denkt na"-status in plaats van alles tegelijk te versturen.

Wanneer je een limiet overschrijdt, retourneert de API HTTP 429. De code is rate_limited voor verzoeken per minuut en concurrency voor te veel tegelijk. Tijdelijke overbelasting geeft ook 429, met een Retry-After: 5 header. Een eenvoudige retry die die vijf seconden wacht, is genoeg voor een casual game. Lees ook de response headers: X-RateLimit-Limit-Requests en X-RateLimit-Limit-Concurrency tonen je caps, en X-Balance-USD toont wat er nog over is, wat je kunt tonen in een admin scherm.

Voer de status-extractie call niet parallel uit met de volgende spelersbeurt. Wacht erop, of de narrateur schrijft vanuit verouderde feiten. Een goed compromis is om de extractie te starten zodra de stream eindigt en de speler te laten lezen terwijl het draait.

Scènes verbinden met je image pipeline

Deze site is gebouwd voor mensen die tekstmodellen gebruiken rond Stable Diffusion workflows, en scenario's passen daar goed bij. De API zelf is alleen tekst: het genereert geen afbeeldingen, audio of video. Maar de narrateur kan een scènedescriptie schrijven die je pipeline omzet in een image prompt. Voeg nog een JSON-verzoek toe na een grote scènewijziging, met het verzoek om een korte visuele beschrijving van de setting met verlichting, stemming en camerahoek, en voeg die string door naar welke image tool je ook draait. De docs pagina toont de request shape voor prompt-writing calls, en het artikel over de SD prompt generator gaat dieper in op die kant.

Om de engine te proberen, maak een account aan en claim de proefversie. Nieuwe gebruikers kunnen met één klik op de API-sleutelpagina $0,50 aan proeftegoed krijgen, geldig voor 7 dagen zonder dat een creditcard nodig is. Volgens de cijfers hierboven is dat genoeg voor een paar volledige sessies. Proefsleutels zijn beperkt tot 2 gelijktijdige verzoeken en 60 per minuut, wat past bij solo testen.

Houd fictie in de fictie. Scenario's met volwassen thema's zijn wettelijk gebruik wanneer iedereen die betrokken is een volwassene is; de enige regel die nooit buigt is geen seksuele inhoud met minderjarigen. Prompts verzonden naar de API worden niet gebruikt voor training.

Vragen en antwoorden

Kan het model elk personage of elk genre spelen?

Het is een algemeen tekstmodel dat is afgestemd om wettelijke volwassen verzoeken niet te weigeren, dus horror, misdaad, drama en komedie zijn prima. De enige harde limiet is seksuele inhoud met minderjarigen.

Hoe lang kan een sessie duren voordat het contextvenster vol is?

Het venster is 100.000 tokens voor prompt plus voltooiing. Met een samenvattingslaag kun je in de praktijk veel verder gaan dan dat, omdat eerdere wendingen worden gecomprimeerd.

Moet ik de antwoorden streamen?

Ja, voor alles wat een persoon live leest. Streaming geeft je het verbruik aan het einde, en een stream bezet een van je gelijktijdige verzoeken tot het klaar is.

Is er een manier om een scène exact te laten herhalen?

Geef dezelfde seed met identieke berichten en instellingen. Het is een hulpmiddel voor testen en garandeert geen byte-identieke output.

Je sleutel is nog maar één formulier verwijderd

Maak een account aan, kopieer de sleutel, pas de base URL aan. Dat is de hele opzet.

API-sleutel aanvragen