ES ▾
Obtener clave de API

Escenarios de rol basados en texto con una API de LLM

Actualizado

Los escenarios de rol basados en texto funcionan mejor cuando el modelo interpreta a un narrador con reglas, y tu código gestiona el estado. Este artículo explica un pequeño motor en Python: prompt, streaming, memoria y una estimación de costos para una sesión larga.

Diorama de un pequeño escenario con burbujas de diálogo brillantes que representan un escenario de rol de texto

Puntos clave

  • Divide el trabajo: el modelo escribe la historia, tu código almacena hechos, inventario y reglas.
  • Transmite las respuestas y mantén cada transmisión lo suficientemente corta para liberar su ranura de concurrencia rápidamente.
  • Una ventana de contexto de 100.000 tokens sostiene una sesión muy larga, pero los resúmenes mantienen el costo y la deriva bajos.
  • Una sesión de 200 turnos con 6,000 tokens de entrada y 400 de salida por turno cuesta unos $0.22.

Empieza con el narrador, no con la trama

Las personas que crean escenarios de rol basados en texto a menudo comienzan escribiendo una trama. Eso es al revés. Un escenario es un bucle entre un jugador y un narrador, y el narrador necesita una descripción del trabajo antes de necesitar una historia. El modelo tiene que saber quién habla, qué puede decidir por el jugador, qué longitud debe tener una respuesta y qué nunca debe hacer. Todo lo demás puede improvisarse.

Un buen prompt del sistema para un motor de escenario tiene cuatro partes. Primero viene el rol: maestro de juegos, entrevistador, computadora de la nave, un personaje en una obra. Segundo, las reglas del mundo en unas pocas frases claras: tono, entorno, qué existe y qué no. Tercero, reglas de formato, porque tu código puede analizarlas: por ejemplo, "termina cada respuesta con una línea que empiece con OPCIONES:". Cuarto, límites. La API de texto aquí está ajustada para responder sin rechazos de contenido para uso adulto lícito, lo que la hace adecuada para ficción oscura, villanos con verdadera amenaza y tramas moralmente grises. Aún tiene una línea dura: sin contenido sexual que involucre menores de edad. Pon esa regla en tus propios términos de uso también, y aplica un filtro de edad en tu aplicación si tus escenarios están dirigidos a adultos.

Mantén el prompt por debajo de unas 600 palabras. Las listas largas de reglas se siguen a medias. Si necesitas muchas reglas, ordénalas y elimina el tercio inferior.

Un bucle de trabajo con streaming

El streaming es más importante en el rol que en la mayoría de los usos. Un jugador que lee una historia quiere ver aparecer las palabras mientras el modelo las escribe, y una respuesta de 300 tokens parece más lenta de lo que es si llega toda de golpe. La API transmite mediante eventos enviados por el servidor con stream: true, y los SDK de OpenAI se encargan del análisis. El endpoint es compatible con OpenAI, el ID del modelo es uncensored y la URL base de este sitio es 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.")

La configuración está ajustada para la ficción. Una temperatura de 0,8 a 1,0 da prosa variada, y top_p cerca de 0,95 recorta las elecciones de palabras más extrañas. Una pequeña penalización por presencia desalienta al narrador a volver a las mismas imágenes. Establece max_tokens para limitar la longitud de la respuesta; es opcional, pero sin él un narrador charlatante puede escribir mucho más de lo que querías. Para una escena que quieras reproducir exactamente durante las pruebas, pasa seed y mantén fijos los demás parámetros.

Deja que el código gestione los hechos

El fallo más común en el rol largo es la deriva. La clave está en el bolsillo del jugador en el turno 5 y en la mesa en el turno 40. El posadero se llamaba Mara, luego Marta. Un modelo que lee 30.000 tokens de historial a menudo lo hará bien, pero "a menudo" no es un diseño. Mantén los hechos en una estructura pequeña que tu programa actualice, e incluye esa estructura en cada petición, como hace la línea STATE anterior.

¿Cómo actualizas el estado? Pide al modelo que informe los cambios después de cada turno, en una segunda llamada barata que use modo JSON. La primera llamada escribe la historia para el jugador. La segunda lee el nuevo pasaje y devuelve solo lo que cambió.

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

Limita la lista de hechos, como hace la porción, o el bloque de estado crecerá hasta ser tan pesado como el historial que debía reemplazar. Si una respuesta JSON falla al analizarse, omite la actualización para ese turno y continúa; un hecho perdido es más barato que un fallo.

Los dados, la salud y los temporizadores se manejan mejor en código simple. Cuando el jugador intenta algo arriesgado, tira en Python y dile al narrador el resultado: "El intento del jugador falla". El modelo luego escribe las consecuencias. Esto mantiene al modelo de dejar que el jugador gane silenciosamente cada vez, lo que tienden a hacer los modelos de lenguaje.

Memoria para sesiones que duran horas

La ventana de contexto es de 100.000 tokens, contando prompt y completado juntos. Una respuesta típica de 250 tokens más un mensaje de jugador de 30 tokens son aproximadamente 280 tokens por turno, por lo que solo el historial crudo podría contener cientos de turnos. Puedes enviarlo todo. Muchos creadores aún no lo hacen por dos razones: el costo crece con cada turno, porque todo el historial se factura como entrada cada vez, y los detalles antiguos pueden distraer al modelo.

Una disposición razonable mantiene tres capas:

  1. Fijado: el prompt del sistema y el bloque de estado. Se envía siempre.
  2. Resumen: un párrafo o dos en curso de lo que ha ocurrido, reescrito cada 10 a 15 turnos mediante una llamada corta.
  3. Turnos recientes: los últimos 10 a 20 mensajes tal cual, para que la prosa mantenga su ritmo.

Cuando se llena la ventana de turnos recientes, pliega los diez más antiguos en el resumen y elimínalos. La llamada de resumen puede usar una temperatura baja y un límite de 300 tokens. Los jugadores rara vez notan la compresión, siempre que los nombres, las promesas y las amenazas sin resolver lleguen al resumen. Dilo explícitamente en el prompt de resumen: "mantén nombres, deudas, lesiones, preguntas abiertas".

Si quieres que un personaje mantenga una voz estable, dale una tarjeta de estilo corta en la capa anclada: tres líneas de ejemplo de su habla y dos cosas que nunca dice. Los ejemplos cortos dan forma al tono mejor que los adjetivos.

Lo que cuesta una sesión

El precio es $0,15 por 1M de tokens de entrada y $0,50 por 1M de tokens de salida, con crédito prepago y sin suscripción. Trabaja a través de una sesión larga. Digamos 200 turnos, con la capa anclada, resumen y turnos recientes que suman un promedio de 6.000 tokens de entrada por petición, y 400 tokens de salida por respuesta (la respuesta de la historia más una pequeña parte para las llamadas de estado y resumen).

Entrada: 200 x 6,000 = 1,200,000 tokens, que es 1.2 x $0.15 = $0.18. Salida: 200 x 400 = 80,000 tokens, que es 0.08 x $0.50 = $0.04. La sesión cuesta $0.22.

Compara la versión perezosa que reenvía todo el historial en bruto. En el turno 200 el historial tiene unos 56.000 tokens, y a lo largo de la sesión la entrada media es de unos 28.000 tokens por petición. Eso son 200 x 28.000 = 5.600.000 tokens de entrada, o 5,6 x $0,15 = $0,84, más los mismos $0,04 de salida, lo que hace $0,88. Todavía asequible, pero cuatro veces la factura sin ganancia en calidad. Para una app con muchos jugadores, esa diferencia decide tu margen.

Las peticiones fallidas o rechazadas no se cobran, y el crédito no caduca, por lo que el saldo no utilizado simplemente espera. Las recargas van de $20 a $500 en dólares enteros, con un bono del 5% desde $50, 10% desde $100 y 15% desde $200. Consulta la página de precios para la lista completa de opciones.

Ejecutar muchos jugadores a la vez

Una clave completa permite 8 peticiones simultáneas y 300 peticiones por minuto. Una petición en streaming mantiene su espacio hasta que termina el streaming, por lo que con 8 espacios y respuestas que tardan unos diez segundos, puedes servir aproximadamente 48 flujos de respuesta por minuto, asumiendo que no se ejecute nada más. Esa es una regla aproximada, no una cifra medida, ya que la velocidad varía según la carga y la longitud de la respuesta. El punto es planificarlo: si tu juego tiene más jugadores simultáneos que espacios, encola las peticiones en tu servidor y muestra un estado corto de "el narrador está pensando" en lugar de disparar todo a la vez.

Cuando excedes un límite, la API devuelve HTTP 429. El código es rate_limited para peticiones por minuto y concurrency para demasiados a la vez. La sobrecarga temporal también da 429, con una cabecera Retry-After: 5. Un reintento simple que espere esos cinco segundos es suficiente para un juego casual. Lee también las cabeceras de respuesta: X-RateLimit-Limit-Requests y X-RateLimit-Limit-Concurrency muestran tus límites, y X-Balance-USD muestra lo que queda, que puedes mostrar en una pantalla de administración.

No ejecutes la llamada de extracción de estado en paralelo con el siguiente turno del jugador. Espera a que termine, o el narrador escribirá con hechos obsoletos. Un buen compromiso es iniciar la extracción tan pronto como termine el flujo y permitir que el jugador lea mientras se ejecuta.

Conectar escenas con tu pipeline de imágenes

Este sitio está pensado para personas que usan modelos de texto en flujos de trabajo de Stable Diffusion, y los escenarios encajan bien ahí. La API en sí es solo de texto: no genera imágenes, audio ni video. Pero el narrador puede escribir una descripción de escena que tu flujo de trabajo convierta en una indicación de imagen. Añade una llamada JSON más después de un cambio de escena importante, pidiendo una breve descripción visual del entorno con iluminación, estado de ánimo y ángulo de cámara, luego pasa esa cadena a la herramienta de imagen que uses. La página de documentación docs muestra la forma de la petición para las llamadas de escritura de indicaciones, y el artículo en el generador de indicaciones SD profundiza en ese lado.

Para probar el motor, crea una cuenta y reclama la prueba. Los nuevos usuarios pueden obtener $0,50 de crédito de prueba con un clic en la página de clave de API, válido por 7 días sin necesidad de tarjeta. Por las cifras anteriores, eso es suficiente para un par de sesiones completas. Las claves de prueba están limitadas a 2 peticiones concurrentes y 60 por minuto, lo que encaja para pruebas en solitario.

Mantén la ficción en la ficción. Los escenarios para adultos son uso lícito cuando todos los involucrados son adultos; la única regla que nunca cede es sin contenido sexual que involucre menores de edad. Las indicaciones enviadas a la API no se usan para entrenamiento.

Preguntas y respuestas

¿Puede el modelo interpretar cualquier personaje o género?

Es un modelo de texto general ajustado para no rechazar solicitudes adultas lícitas, por lo que el terror, el crimen, el drama y la comedia están bien. La única limitación estricta es el contenido sexual que involucre a menores de edad.

¿Cuánto puede durar una sesión antes de que se llene la ventana de contexto?

La ventana es de 100.000 tokens para la indicación más la completación. Con una capa de resumen puedes ir mucho más allá en la práctica, ya que los turnos antiguos se comprimen.

¿Debería transmitir las respuestas?

Sí, para cualquier cosa que una persona lea en vivo. El streaming te muestra el uso al final, y una transmisión ocupa una de tus ranuras de concurrencia hasta que termine.

¿Hay alguna forma de hacer que una escena se repita exactamente?

Pasa la misma semilla con mensajes y configuraciones idénticos. Es una ayuda para pruebas y no garantiza una salida idéntica byte a byte.

Tu clave está a un formulario de distancia

Crea una cuenta, copia la clave, cambia la URL base. Esa es toda la configuración.

Obtener clave de API