FR ▾
Obtenir une clé API

Scénarios textuels de jeu de rôle avec une API LLM

Mis à jour

Les scénarios de jeu de rôle textuel fonctionnent mieux lorsque le modèle incarne un narrateur avec des règles, et que votre code gère l'état. Cet article présente un petit moteur en Python : prompt, streaming, mémoire et une estimation des coûts pour une longue session.

Diorama d'une petite scène avec des bulles de parole lumineuses représentant un scénario de jeu de rôle textuel

Points clés

  • Partagez le travail : le modèle écrit l'histoire, votre code stocke les faits, l'inventaire et les règles.
  • Utilisez le streaming pour les réponses et gardez chaque flux suffisamment court pour libérer rapidement son slot de concurrence.
  • Une fenêtre de 100 000 tokens contient une session très longue, mais les résumes maintiennent le coût et la dérive à la baisse.
  • Une session de 200 tours à 6 000 tokens d'entrée et 400 tokens de sortie par tour coûte environ 0,22 $.

Commencez par le narrateur, pas par l'intrigue

Les personnes qui créent des scénarios de jeu de rôle textuel commencent souvent par écrire une intrigue. C'est l'inverse. Un scénario est une boucle entre un joueur et un narrateur, et le narrateur a besoin d'une fiche de poste avant d'avoir besoin d'une histoire. Le modèle doit savoir qui parle, ce qu'il peut décider pour le joueur, la longueur d'une réponse et ce qu'il ne doit jamais faire. Tout le reste peut être improvisé.

Un bon prompt système pour un moteur de scénario comporte quatre parties. D'abord le rôle : maître du jeu, intervieweur, ordinateur de bord, personnage d'une pièce. Ensuite, les règles du monde en quelques phrases simples : ton, décor, ce qui existe et ce qui n'existe pas. Puis les règles de formatage, car votre code peut devoir les analyser : par exemple, « terminez chaque réponse par une ligne commençant par OPTIONS: ». Enfin, les limites. L'API textuelle ici est conçue pour répondre sans refus de contenu pour un usage adulte légal, ce qui la rend adaptée à la fiction sombre, aux méchants vraiment menaçants et aux intrigues moralement ambiguës. Elle conserve une limite stricte : pas de contenu sexuel impliquant des mineurs. Ajoutez cette règle à vos propres conditions d'utilisation et appliquez une vérification d'âge dans votre application si vos scénarios s'adressent à des adultes.

Gardez le prompt en dessous d'environ 600 mots. Les longues listes de règles sont souvent partiellement suivies. Si vous avez besoin de nombreuses règles, classez-les et supprimez le tiers inférieur.

Une boucle de fonctionnement avec le streaming

Le streaming est plus important dans le jeu de rôle que dans la plupart des utilisations. Un joueur lisant une histoire veut voir les mots apparaître pendant que le modèle écrit, et une réponse de 300 tokens semble plus lente qu'elle ne l'est si elle arrive d'un coup. L'API diffuse en streaming via des événements envoyés par le serveur (SSE) avec stream: true, et les SDK OpenAI gèrent l'analyse. L'endpoint est compatible OpenAI, l'ID du modèle est uncensored, et l'URL de base pour ce site est 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.")

Les paramètres sont ajustés pour la fiction. Une température autour de 0,8 à 1,0 donne une prose variée, et top_p près de 0,95 atténue les choix de mots les plus étranges. Une petite pénalité de présence décourage le narrateur de revenir aux mêmes images. Définissez max_tokens pour limiter la longueur de la réponse ; c'est optionnel, mais sans cela, un narrateur bavard peut écrire bien plus que vous ne le souhaitiez. Pour une scène que vous voulez rejouer exactement pendant les tests, passez seed et gardez les autres paramètres fixes.

Laissez le code gérer les faits

Le problème le plus courant dans le jeu de rôle long est la dérive. La clé est dans la poche du joueur au tour 5 et sur la table au tour 40. L'aubergiste s'appelait Mara, puis Marta. Un modèle lisant 30 000 tokens d'historique parviendra souvent à le gérer correctement, mais « souvent » n'est pas une conception. Conservez les faits dans une petite structure que votre programme met à jour, et intégrez cette structure dans chaque requête, comme le fait la ligne STATE ci-dessus.

Comment mettez-vous à jour l'état ? Demandez au modèle de signaler les changements après chaque tour, dans un deuxième appel peu coûteux qui utilise le mode JSON. Le premier appel écrit l'histoire pour le joueur. Le second lit le nouveau passage et ne retourne que ce qui a changé.

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

Limitez la liste de faits, comme le fait la slice, ou le bloc d'état grossira jusqu'à devenir aussi lourd que l'historique qu'il était censé remplacer. Si une réponse JSON ne peut pas être analysée, sautez la mise à jour pour ce tour et passez à la suite ; un fait manquant est moins coûteux qu'un plantage.

Les dés, la santé et les compteurs sont mieux gérés en code simple. Lorsque le joueur tente quelque chose de risqué, lancez en Python et dites au narrateur le résultat : « La tentative du joueur échoue. » Le modèle écrit ensuite les conséquences. Cela empêche le modèle de laisser silencieusement le joueur gagner à chaque fois, ce que les modèles de langage ont tendance à faire.

Mémoire pour les sessions qui durent des heures

La fenêtre de contexte est de 100 000 tokens, en comptant le prompt et la réponse ensemble. Une réponse typique de 250 tokens plus un message joueur de 30 tokens représente environ 280 tokens par tour, donc l'historique brut seul pourrait contenir des centaines de tours. Vous pouvez tout envoyer. Beaucoup de développeurs ne le font pas encore, pour deux raisons : le coût augmente avec chaque tour, car tout l'historique est facturé en entrée à chaque fois, et les anciens détails peuvent distraire le modèle.

Une disposition raisonnable garde trois couches :

  1. Épinglé : le prompt système et le bloc d'état. Toujours envoyé.
  2. Résumé : un ou deux paragraphes décrivant ce qui s'est passé, réécrits tous les 10 à 15 tours par un appel court.
  3. Tours récents : les 10 à 20 derniers messages mot pour mot, afin que la prose garde son rythme.

Lorsque la fenêtre des tours récents est pleine, fusionnez les dix plus anciens dans le résumé et supprimez-les. L'appel de résumé peut utiliser une température faible et une limite de 300 tokens. Les joueurs remarquent rarement la compression, tant que les noms, les promesses et les menaces non résolues figurent dans le résumé. Dites-le explicitement dans le prompt de résumé : « gardez les noms, les dettes, les blessures, les questions ouvertes ».

Si vous voulez qu'un personnage garde une voix stable, donnez-lui une fiche de style courte dans la couche épinglée : trois lignes d'exemple de son discours et deux choses qu'il ne dit jamais. Les courts exemples façonnent le ton mieux que les adjectifs.

Coût d'une session

La tarification est de $0,15 par 1M de tokens d'entrée et de $0,50 par 1M de tokens de sortie, avec crédit prépayé et sans abonnement. Parcourez une longue session. Disons 200 tours, avec la couche épinglée, le résumé et les tours récents s'additionnant à une moyenne de 6 000 tokens d'entrée par requête, et 400 tokens de sortie par réponse (la réponse histoire plus une petite part pour les appels d'état et de résumé).

Entrée : 200 x 6 000 = 1 200 000 tokens, ce qui est 1,2 x $0,15 = $0,18. Sortie : 200 x 400 = 80 000 tokens, ce qui est 0,08 x $0,50 = $0,04. La session coûte $0,22.

Comparez la version paresseuse qui renvoie tout l'historique brut. Au tour 200, l'historique est d'environ 56 000 tokens, et sur la session, l'entrée moyenne est d'environ 28 000 tokens par requête. Cela fait 200 x 28 000 = 5 600 000 tokens d'entrée, soit 5,6 x $0,15 = $0,84, plus les mêmes $0,04 pour la sortie, ce qui fait $0,88. Toujours abordable, mais quatre fois la facture pour aucun gain en qualité. Pour une application avec de nombreux joueurs, cet écart décide de votre marge.

Les requêtes échouées ou refusées ne sont pas facturées, et le crédit n'expire jamais, donc le solde inutilisé attend simplement. Les recharges vont de 20 $ à 500 $ en dollars entiers, avec un bonus de 5 % à partir de 50 $, 10 % à partir de 100 $ et 15 % à partir de 200 $. Voir la page de tarification pour la liste complète des options.

Exécuter plusieurs joueurs en même temps

Une clé complète permet 8 requêtes simultanées et 300 requêtes par minute. Une requête en streaming occupe son emplacement jusqu'à la fin du flux, donc avec 8 emplacements et des réponses prenant environ dix secondes, vous pouvez servir environ 48 flux de réponse par minute, en supposant qu'aucune autre tâche ne s'exécute. Il s'agit d'une règle approximative, pas d'une mesure précise, car la vitesse varie selon la charge et la longueur des réponses. L'objectif est de planifier en conséquence : si votre jeu compte plus de joueurs simultanés que d'emplacements, mettez les requêtes en file d'attente sur votre serveur et affichez un état court « le narrateur réfléchit » au lieu de tout déclencher en même temps.

Lorsque vous dépassez une limite, l'API renvoie un HTTP 429. Le code est rate_limited pour les requêtes par minute et concurrency pour trop de requêtes à la fois. Une surcharge temporaire donne également un 429, avec un en-tête Retry-After: 5. Une simple tentative de réessai qui attend ces cinq secondes suffit pour un jeu occasionnel. Lisez également les en-têtes de réponse : X-RateLimit-Limit-Requests et X-RateLimit-Limit-Concurrency indiquent vos limites, et X-Balance-USD indique le solde restant, que vous pouvez afficher dans un écran d'administration.

Ne lancez pas l'appel d'extraction d'état en parallèle avec le prochain tour du joueur. Attendez-le, ou le narrateur écrira à partir de faits obsolètes. Un bon compromis est de lancer l'extraction dès la fin du flux et de laisser le joueur lire pendant qu'elle s'exécute.

Connecter les scènes à votre pipeline d'images

Ce site est conçu pour les personnes qui utilisent des modèles textuels dans le cadre de flux de travail autour de Stable Diffusion, et les scénarios s'y intègrent bien. L'API elle-même est textuelle uniquement : elle ne génère ni images, ni audio, ni vidéo. Mais le narrateur peut écrire une description de scène que votre pipeline transformera en prompt pour une image. Ajoutez un appel JSON supplémentaire après un changement majeur de scène, en demandant une courte description visuelle du décor avec l'éclairage, l'ambiance et l'angle de caméra, puis transmettez cette chaîne à l'outil image que vous utilisez. La page docs montre la structure de la requête pour les appels de génération de prompt, et l'article sur le SD prompt generator approfondit ce sujet.

Pour essayer le moteur, créez un compte et réclamez l'essai. Les nouveaux utilisateurs peuvent obtenir 0,50 $ de crédit d'essai en un clic sur la page Clé API, valable 7 jours sans besoin de carte. Avec les chiffres ci-dessus, cela suffit pour quelques sessions complètes. Les clés d'essai sont limitées à 2 requêtes simultanées et 60 par minute, ce qui convient aux tests en solo.

Gardez la fiction dans la fiction. Les scénarios à thème adulte sont une utilisation légale lorsque tous les participants sont des adultes ; la seule règle qui ne plie jamais est pas de contenu sexuel impliquant des mineurs. Les prompts envoyés à l'API ne sont pas utilisés pour l'entraînement.

Questions et réponses

Le modèle peut-il incarner n'importe quel personnage ou genre ?

C'est un modèle textuel général ajusté pour ne pas refuser les requêtes adultes légales, donc l'horreur, le crime, le drame et la comédie sont tous valides. La seule limite stricte concerne le contenu sexuel impliquant des mineurs.

Combien de temps une session peut-elle durer avant que la fenêtre de contexte ne soit pleine ?

La fenêtre de contexte est de 100 000 tokens pour le prompt plus la complétion. Avec une couche de résumé, vous pouvez aller bien au-delà en pratique, car les anciens tours sont compressés.

Faut-il utiliser le streaming pour les réponses ?

Oui, pour tout ce qu'une personne lit en direct. Le streaming vous donne l'utilisation à la fin, et un flux occupe l'un de vos slots de concurrence jusqu'à sa fin.

Existe-t-il un moyen de faire répéter exactement une scène ?

Transmettez le même seed avec des messages et des paramètres identiques. C'est une aide au test et ne garantit pas une sortie byte-à-byte identique.

Il ne vous reste qu'un formulaire à remplir pour obtenir votre clé

Créez un compte, copiez la clé, modifiez l'URL de base. C'est toute la configuration.

Obtenir une clé API