Cenários de RPG baseados em texto com uma API LLM
Cenários de RPG baseados em texto funcionam melhor quando o modelo interpreta um narrador com regras, e seu código controla o estado. Este artigo mostra um pequeno motor em Python: prompt, streaming, memória e uma estimativa de custo para uma sessão longa.

Pontos principais
- Divida o trabalho: o modelo escreve a história, seu código armazena fatos, inventário e regras.
- Transmita as respostas e mantenha cada transmissão curta o suficiente para liberar sua vaga de concorrência rapidamente.
- Uma janela de 100.000 tokens suporta uma sessão muito longa, mas resumos mantêm o custo e o desvio baixos.
- Uma sessão de 200 turnos com 6.000 tokens de entrada e 400 de saída por turno custa cerca de $0,22.
Comece com o narrador, não com o enredo
Pessoas que criam cenários de RPG baseados em texto muitas vezes começam escrevendo um enredo. Isso é o contrário do ideal. Um cenário é um loop entre um jogador e um narrador, e o narrador precisa de uma descrição de trabalho antes de precisar de uma história. O modelo precisa saber quem fala, o que pode decidir pelo jogador, qual deve ser o tamanho de uma resposta e o que ele nunca deve fazer. Tudo o mais pode ser improvisado.
Um bom prompt de sistema para um motor de cenário tem quatro partes. Primeiro vem o papel: mestre de jogo, entrevistador, computador de navegação, um personagem de uma peça. Segundo, as regras do mundo em poucas frases simples: tom, cenário, o que existe e o que não existe. Terceiro, regras de formatação, porque seu código pode analisá-las: por exemplo, "termine cada resposta com uma linha começando com OPÇÕES:". Quarto, limites. A API de texto aqui é ajustada para responder sem recusas de conteúdo para uso adulto legal, o que a torna adequada para ficção sombria, vilões com ameaça real e enredos moralmente cinzentos. Ela ainda tem uma linha dura: sem conteúdo sexual envolvendo menores de idade. Coloque essa regra em seus próprios termos de uso também e aplique uma verificação de idade no seu app se seus cenários forem voltados para adultos.
Mantenha o prompt com menos de 600 palavras. Listas longas de regras são seguidas apenas parcialmente. Se você precisar de muitas regras, classifique-as e remova o terço inferior.
Um loop de trabalho com streaming
O streaming é mais importante no roleplay do que na maioria dos usos. Um jogador que lê uma história quer que as palavras apareçam enquanto o modelo as escreve, e uma resposta de 300 tokens parece mais lenta do que é se ela chegar de uma vez só. A API faz streaming sobre eventos enviados pelo servidor com stream: true, e os SDKs da OpenAI cuidam do parsing. O endpoint é compatível com OpenAI, o ID do modelo é uncensored, e a URL base deste site é 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.")As configurações são ajustadas para ficção. Uma temperatura em torno de 0,8 a 1,0 dá prosa variada, e top_p perto de 0,95 corta as escolhas de palavras mais estranhas. Uma pequena penalidade de presença desencoraja o narrador a voltar às mesmas imagens. Defina max_tokens para limitar o tamanho da resposta; é opcional, mas sem isso um narrador falante pode escrever muito mais do que você queria. Para uma cena que você quer reproduzir exatamente durante os testes, passe seed e mantenha os outros parâmetros fixos.
Deixe o código controlar os fatos
A falha mais comum em RPGs longos é o desvio. A chave está no bolso do jogador no turno 5 e na mesa no turno 40. O estalajadeiro foi chamado de Mara, depois Marta. Um modelo lendo 30.000 tokens de histórico geralmente acertará isso, mas "geralmente" não é um planejamento. Mantenha os fatos em uma pequena estrutura que seu programa atualiza e coloque essa estrutura em cada requisição, como a linha STATE acima faz.
Como você atualiza o estado? Peça ao modelo para relatar mudanças após cada rodada, em uma segunda chamada barata que usa modo JSON. A primeira chamada escreve a história para o jogador. A segunda lê o novo trecho e retorna apenas o que mudou.
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:]Limite a lista de fatos, como a fatia faz, ou o bloco de estado cresce até ficar tão pesado quanto o histórico que deveria substituir. Se uma resposta JSON falhar na análise, pule a atualização para essa rodada e siga em frente; um fato perdido é mais barato que uma falha.
Dados, vida e temporizadores são melhor tratados em código simples. Quando o jogador tenta algo arriscado, faça a rolagem em Python e informe ao narrador o resultado: "A tentativa do jogador falha." O modelo então escreve as consequências. Isso impede que o modelo deixe o jogador vencer silenciosamente todas as vezes, o que é comum em modelos de linguagem.
Memória para sessões que duram horas
A janela de contexto tem 100.000 tokens, contando prompt e conclusão juntos. Uma resposta típica de 250 tokens mais uma mensagem de jogador de 30 tokens são cerca de 280 tokens por turno, então apenas o histórico bruto poderia suportar centenas de turnos. Você pode enviar tudo. Muitos criadores ainda não o fazem, por dois motivos: o custo cresce a cada turno, porque todo o histórico é cobrado como entrada cada vez, e detalhes antigos podem distrair o modelo.
Um layout razoável mantém três camadas:
- Fixado: o prompt de sistema e o bloco de estado. Sempre enviado.
- Resumo: um parágrafo ou dois em andamento sobre o que aconteceu, reescrito a cada 10 a 15 turnos por uma chamada curta.
- Turnos recentes: as últimas 10 a 20 mensagens literalmente, para que a prosa mantenha seu ritmo.
Quando a janela de turnos recentes enche, incorpore as dez mais antigas ao resumo e descarte-as. A chamada de resumo pode usar uma temperatura baixa e um limite de 300 tokens. Os jogadores raramente percebem a compressão, desde que nomes, promessas e ameaças não resolvidas entrem no resumo. Diga isso explicitamente no prompt de resumo: "mantenha nomes, dívidas, ferimentos, perguntas em aberto".
Se você quiser que um personagem mantenha uma voz estável, dê a ele um cartão de estilo curto na camada fixada: três linhas de exemplo de sua fala e duas coisas que ele nunca diz. Exemplos curtos moldam o tom melhor que adjetivos.
O custo de uma sessão
O preço é $0,15 por 1M de tokens de entrada e $0,50 por 1M de tokens de saída, com crédito pré-pago e sem assinatura. Trabalhe através de uma sessão longa. Digamos 200 turnos, com a camada fixada, resumo e turnos recentes somando uma média de 6.000 tokens de entrada por requisição, e 400 tokens de saída por resposta (a resposta da história mais uma pequena parte para chamadas de estado e resumo).
Entrada: 200 x 6,000 = 1,200,000 tokens, que é 1.2 x $0.15 = $0.18. Saída: 200 x 400 = 80,000 tokens, que é 0.08 x $0.50 = $0.04. A sessão custa $0.22.
Compare a versão preguiçosa que reenvia todo o histórico bruto. Pelo turno 200, o histórico tem cerca de 56.000 tokens, e ao longo da sessão a entrada média é de cerca de 28.000 tokens por requisição. Isso é 200 x 28.000 = 5.600.000 tokens de entrada, ou 5,6 x $0,15 = $0,84, mais os mesmos $0,04 de saída, o que dá $0,88. Ainda acessível, mas quatro vezes a conta sem ganho de qualidade. Para um app com muitos jogadores, essa lacuna decide sua margem.
Requisições falhas ou recusadas não são cobradas, e o crédito nunca expira, então o saldo não utilizado simplesmente espera. Recargas variam de $20 a $500 em dólares inteiros, com um bônus de 5% a partir de $50, 10% a partir de $100 e 15% a partir de $200. Veja a página de preços para a lista completa de opções.
Executando muitos jogadores ao mesmo tempo
Uma chave completa permite 8 requisições simultâneas e 300 requisições por minuto. Uma requisição com streaming segura sua vaga até o fim da transmissão, então com 8 vagas e respostas que levam cerca de dez segundos, você pode servir cerca de 48 transmissões de resposta por minuto, assumindo que nada mais esteja rodando. Essa é uma regra aproximada, não uma figura medida, já que a velocidade varia com a carga e o comprimento da resposta. O ponto é planejar para isso: se o seu jogo tiver mais jogadores simultâneos do que vagas, faça fila as requisições no seu servidor e mostre um estado curto "narrador está pensando" em vez de disparar tudo de uma vez.
Quando você excede um limite, a API retorna HTTP 429. O código é rate_limited para requisições por minuto e concurrency para muitos ao mesmo tempo. Sobrecarga temporária também dá 429, com um cabeçalho Retry-After: 5. Uma simples nova tentativa que espera esses cinco segundos é suficiente para um jogo casual. Leia também os cabeçalhos de resposta: X-RateLimit-Limit-Requests e X-RateLimit-Limit-Concurrency mostram seus limites, e X-Balance-USD mostra o que resta, que você pode exibir em uma tela de administração.
Não execute a chamada de extração de estado em paralelo com o próximo turno do jogador. Espere por ela, ou o narrador escreverá a partir de fatos desatualizados. Um bom compromisso é iniciar a extração assim que o stream terminar e deixar o jogador ler enquanto ele roda.
Conectando cenas ao seu pipeline de imagem
Este site é feito para pessoas que usam modelos de texto em fluxos de trabalho com Stable Diffusion, e os cenários se encaixam bem nisso. A API em si é apenas de texto: ela não gera imagens, áudio ou vídeo. Mas o narrador pode escrever uma descrição de cena que seu pipeline transforma em um prompt de imagem. Adicione mais uma chamada JSON após uma grande mudança de cena, pedindo uma breve descrição visual do ambiente com iluminação, clima e ângulo de câmera, e depois passe essa string para qualquer ferramenta de imagem que você usar. A página de documentação mostra a forma da requisição para chamadas para gerar prompts, e o artigo no gerador de prompts SD entra mais a fundo nesse aspecto.
Para experimentar o motor, crie uma conta e solicite o teste. Novos usuários podem obter $0,50 de crédito de teste com um clique na página de chave de API, válido por 7 dias sem necessidade de cartão. Pelos números acima, isso é suficiente para um par de sessões completas. As chaves de teste são limitadas a 2 requisições simultâneas e 60 por minuto, o que se encaixa em testes individuais.
Mantenha a ficção na ficção. Cenários para adultos são uso lícito quando todos os envolvidos são adultos; a única regra que nunca muda é sem conteúdo sexual envolvendo menores de idade. Prompts enviados para a API não são usados para treinamento.
Mais guias
Perguntas e respostas
O modelo pode interpretar qualquer personagem ou gênero?
É um modelo de texto geral ajustado para não recusar solicitações adultas lícitas, então horror, crime, drama e comédia são todos aceitáveis. A única restrição rígida é conteúdo sexual envolvendo menores de idade.
Quão longa pode ser uma sessão antes que o contexto se preencha?
A janela é de 100.000 tokens para prompt mais geração. Com uma camada de resumo, você pode ir muito além disso na prática, já que as rodadas antigas são compactadas.
Devo transmitir as respostas?
Sim, para qualquer coisa que uma pessoa leia ao vivo. O streaming fornece o uso no final, e um stream ocupa um de seus slots de concorrência até terminar.
Existe uma maneira de fazer uma cena se repetir exatamente?
Passe a mesma seed com mensagens e configurações idênticas. É uma ajuda de teste e não garante saída idêntica byte a byte.
Sua chave está a um formulário de distância
Crie uma conta, copie a chave e altere a URL base. Essa é toda a configuração.