ES ▾
Obtener clave de API

NsfwAiApiGuía

API LLM NSFW en Python: streaming, JSON y herramientas

Para usar una API LLM NSFW al estilo de Python, solo necesitas el paquete estándar openai y dos cambios: la URL base y la clave. Esta guía muestra una primera petición, streaming, modo JSON, llamadas a herramientas y un manejo limpio de los límites de peticiones.

Actualizado

Ventana de terminal emitiendo un flujo de tokens brillantes envueltos por una cinta de luz enrollada

Configuración: instala y haz la primera llamada

Necesitas Python 3 actual, el paquete openai (pip install openai) y una clave de API. Las cuentas nuevas pueden reclamar un crédito de prueba de $0.50 en la página de clave de API con un solo clic tras iniciar sesión; no necesita detalles de pago. Almacena la clave en una variable de entorno, no en el código fuente.

El servicio habla el protocolo de finalización de chat de OpenAI: POST /v1/chat/completions para la generación y GET /v1/models para la lista de modelos. La autenticación es un token bearer, y el ID del modelo es uncensored. Este es el script mínimo funcional:

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.nsfwaiapi.com/v1",
    api_key=os.environ["NSFWAIAPI_KEY"],
)

resp = client.chat.completions.create(
    model="uncensored",
    messages=[
        {"role": "system", "content": "You are a fiction co-writer for adult readers. Write vivid, non-explicit scenes."},
        {"role": "user", "content": "Open a noir story in a rainy harbor town, three sentences."},
    ],
    temperature=0.8,
)

print(resp.choices[0].message.content)
print(resp.usage)

Si prefieres verificar la conexión desde un shell primero, la misma petición funciona con curl:

curl https://api.nsfwaiapi.com/v1/chat/completions \
  -H "Authorization: Bearer $NSFWAIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "uncensored", "messages": [{"role": "user", "content": "Say hello in five words."}]}'

El modelo está ajustado para responder sin rechazos de contenido para uso adulto lícito. Eso afecta al tono de lo que escribirá, no a la forma de la API, por lo que el resto de tu código se parece a cualquier otra integración de OpenAI. Mantén los prompts para ficción, chat de personajes y otros proyectos adultos lícitos, y mantén a todos los personajes mayores de edad.

Algunos hábitos ayudan desde el primer día. Pon las instrucciones que nunca cambian en el mensaje del sistema, y mantén el mensaje del usuario para la parte que cambia en cada turno. Elige una temperatura para una tarea y déjala sola mientras comparas prompts, porque cambiar dos cosas a la vez no te enseña nada. Registra el ID de petición y los recuentos de tokens de resp.usage junto a cada llamada, para que cuando tu factura o tu salida te sorprendan, haya un registro con el que verificar.

Respuestas en streaming con SSE

Establece stream=True y el servidor envía tokens como eventos enviados por el servidor. Para aplicaciones de chat, esto es muy importante, porque el lector ve las primeras palabras rápidamente en lugar de esperar a la respuesta completa. Los totales de uso llegan al final del streaming, por lo que una petición en streaming aún puede facturarse y registrarse con precisión.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.nsfwaiapi.com/v1",
    api_key=os.environ["NSFWAIAPI_KEY"],
)

stream = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "Write a short scene: two rival smugglers share a lantern during a storm."}],
    stream=True,
)

text = ""
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        piece = chunk.choices[0].delta.content
        text += piece
        print(piece, end="", flush=True)
    usage = getattr(chunk, "usage", None)
    if usage:
        print("\n[usage]", usage)

Dos detalles ahorran tiempo de depuración. Primero, el último fragmento puede tener una lista choices vacía, por eso el bucle la verifica antes de indexar. Segundo, una petición en streaming mantiene su espacio de concurrencia hasta que termina el streaming, por lo que si tu código abandona un stream sin cerrarlo, ese espacio se queda ocupado. Usa with client.chat.completions.create(..., stream=True) as stream: o llama a stream.close() cuando dejes de leer antes.

Modo JSON para salida estructurada

Cuando tu programa necesita datos y no prosa, como una ficha de personaje, una lista de momentos de la trama o etiquetas para una historia, usa el modo JSON con response_format={"type": "json_object"}. También di "JSON" en el prompt y describe las claves que quieres. La configuración pide un objeto JSON, pero no puede prometer que tus claves estén presentes, así que verifícalas.

import json
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.nsfwaiapi.com/v1",
    api_key=os.environ["NSFWAIAPI_KEY"],
)

resp = client.chat.completions.create(
    model="uncensored",
    response_format={"type": "json_object"},
    messages=[
        {"role": "system", "content": "Reply with a JSON object with keys: name, age, goal, secret. All characters are adults."},
        {"role": "user", "content": "Create a ship's cook for a pirate adventure."},
    ],
    temperature=0.7,
)

sheet = json.loads(resp.choices[0].message.content)
missing = [k for k in ("name", "age", "goal", "secret") if k not in sheet]
if missing:
    raise ValueError(f"model skipped keys: {missing}")
print(sheet["name"], "-", sheet["goal"])

Si una respuesta larga se corta por max_tokens, el JSON estará incompleto y json.loads fallará. Deja max_tokens sin establecer, ya que es opcional y la finalización puede usar el resto de la ventana, o establécelo generosamente.

Llamadas a funciones

Las llamadas a herramientas permiten que el modelo le pida a tu código que haga algo, como tirar dados, buscar una nota de personaje o obtener un precio. Pasas tools y, si quieres, tool_choice. La respuesta contiene tool_calls; los ejecutas y envías los resultados de nuevo como mensajes con role: "tool". Las respuestas en streaming llevan tool_calls en los deltas también.

import json
import os
import random
from openai import OpenAI

client = OpenAI(
    base_url="https://api.nsfwaiapi.com/v1",
    api_key=os.environ["NSFWAIAPI_KEY"],
)

tools = [{
    "type": "function",
    "function": {
        "name": "roll_die",
        "description": "Roll one die with the given number of sides",
        "parameters": {
            "type": "object",
            "properties": {"sides": {"type": "integer", "minimum": 2}},
            "required": ["sides"],
        },
    },
}]

messages = [
    {"role": "system", "content": "You run a tabletop fiction game. Use roll_die for any chance-based outcome."},
    {"role": "user", "content": "I try to pick the lock on the captain's cabin. Roll a d20."},
]

first = client.chat.completions.create(
    model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = first.choices[0].message

if msg.tool_calls:
    messages.append(msg)
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = random.randint(1, args["sides"])
        messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
    final = client.chat.completions.create(
        model="uncensored", messages=messages, tools=tools
    )
    print(final.choices[0].message.content)
else:
    print(msg.content)

Trata los argumentos como entrada no confiable. Valídalos antes de actuar sobre ellos, exactamente como lo harías con un formulario web. Los modelos a veces envían un campo faltante o un tipo incorrecto, y una pequeña protección alrededor de json.loads evita que una mala llamada termine tu sesión.

Límites de peticiones, reintentos y tu saldo

Los límites protegen el servicio y te dan un comportamiento predecible. Una clave completa permite 8 peticiones simultáneas y 300 peticiones por minuto. Una clave de prueba permite 2 y 60. El cuerpo máximo es de 8 MB. Dos tipos de error de límite vuelven como HTTP 429: el código rate_limited significa demasiadas peticiones por minuto, y concurrency significa demasiadas a la vez. Una sobrecarga temporal también devuelve 429, con un encabezado Retry-After: 5.

El SDK de OpenAI reintenta algunos errores por defecto. Para el control, desactívalo y escribe un pequeño bucle que respete Retry-After:

import os
import time
from openai import OpenAI, RateLimitError

client = OpenAI(
    base_url="https://api.nsfwaiapi.com/v1",
    api_key=os.environ["NSFWAIAPI_KEY"],
    max_retries=0,
)

def ask(messages, tries=5):
    for attempt in range(tries):
        try:
            raw = client.chat.completions.with_raw_response.create(
                model="uncensored", messages=messages
            )
            print("balance:", raw.headers.get("X-Balance-USD"), "request id:", raw.headers.get("X-Request-Id"))
            return raw.parse()
        except RateLimitError as err:
            wait = float(err.response.headers.get("Retry-After", 2 ** attempt))
            time.sleep(wait)
    raise RuntimeError("still rate limited after retries")

reply = ask([{"role": "user", "content": "Describe a market at dawn in two sentences."}])
print(reply.choices[0].message.content)

Cada respuesta lleva X-Request-Id, que es lo que citas si contactas con soporte, y X-Balance-USD, que muestra tu crédito restante. Los encabezados X-RateLimit-Limit-Requests y X-RateLimit-Limit-Concurrency te dicen qué límites se aplican a tu clave. Para ejecutar muchos trabajos a la vez, limita la concurrencia en tu propio código con un semáforo del tamaño de tu clave, 8 para una clave completa y 2 para una clave de prueba, para que rara vez veas un 429.

Ejecutando muchas peticiones en paralelo

Los trabajos por lotes, como generar etiquetas para unos cientos de fragmentos de historia, son donde importan los límites de concurrencia. Una clave completa acepta 8 peticiones a la vez, así que ejecuta el trabajo a través de un cliente asíncrono y un semáforo de ese tamaño. Si estás en una clave de prueba, usa 2.

import asyncio
import os
from openai import AsyncOpenAI

client = AsyncOpenAI(
    base_url="https://api.nsfwaiapi.com/v1",
    api_key=os.environ["NSFWAIAPI_KEY"],
)
gate = asyncio.Semaphore(8)

async def summarize(fragment):
    async with gate:
        resp = await client.chat.completions.create(
            model="uncensored",
            messages=[
                {"role": "system", "content": "Summarize the passage in one sentence."},
                {"role": "user", "content": fragment},
            ],
            temperature=0.3,
        )
    return resp.choices[0].message.content

async def main(fragments):
    return await asyncio.gather(*(summarize(f) for f in fragments))

results = asyncio.run(main(["The tide came in early.", "Marta hid the ledger under the floor."]))
print(results)

El semáforo es todo el truco. Sin él, una lista de 200 fragmentos dispara 200 peticiones juntas, y la mayoría vuelven como errores 429 con el código concurrency. Con él, el trabajo fluye a la velocidad que permite tu clave y nada se desperdicia. Si también ejecutas un presupuesto por minuto, 300 peticiones por minuto en una clave completa equivalen a cinco por segundo, por lo que las peticiones muy cortas pueden alcanzar el límite por minuto antes que el límite de concurrencia.

Problemas comunes y soluciones

  • 401 o errores de autenticación. La clave falta, está truncada o se envía al host incorrecto. Imprime client.base_url y comprueba la variable de entorno. El encabezado debe leer Authorization: Bearer seguido de tu clave.
  • Modelo no encontrado. El único ID de modelo es uncensored. Un ejemplo copiado que dice otro nombre fallará, así que llama a GET /v1/models si no estás seguro.
  • Respuestas vacías o cortas en un bucle de historia. Verifica que añadas la respuesta del asistente a la lista de mensajes antes del siguiente turno. Sin ello, el modelo solo ve tu último mensaje.
  • Respuestas que se repiten. Aumenta frequency_penalty un poco, por ejemplo a 0,3, o reduce temperature si el texto se desvía. Las penalizaciones, top_p, stop y seed se pasan todas.
  • Diferente salida en cada ejecución. Establece seed y una temperature baja cuando necesites resultados lo más repetibles posible mientras depuras.
  • Petición demasiado grande. El límite del cuerpo es de 8 MB, y el prompt más la finalización deben caber en 100.000 tokens. Recorta o resume el contexto antiguo antes de que alcance alguno de los límites.

Cuando algo parece incorrecto en el lado del servidor, guarda el encabezado X-Request-Id de la respuesta y envíalo al contacto de la página de soporte junto con una breve descripción. Esto permite a los operadores encontrar tu petición exacta.

Qué cuesta y qué no puede hacer

La tarifa es de $0,15 por 1M de tokens de entrada y $0,50 por 1M de tokens de salida, desde crédito prepago sin suscripción. Las peticiones fallidas o rechazadas no se cobran, y el crédito no caduca. Como ejemplo práctico, un script que envía 3.000 tokens y recibe 800 paga $0,00045 por entrada más $0,0004 por salida, lo que es $0,00085 por llamada, así que 1.000 llamadas cuestan $0,85. Las recargas van de $20 a $500, con bonificaciones de +5% desde $50, +10% desde $100 y +15% desde $200. Este sitio solo acepta cripto, en USDT en TRC20 o USDC en Base. Consulta la página de precios para la tabla completa, y la documentación para la referencia de endpoints.

Los límites son claros. Es un único modelo de pesos abiertos, ejecutado por un proveedor independiente, y no la API oficial de ningún proveedor de modelos. La ventana de contexto es de 100.000 tokens para prompt más finalización. No genera imágenes, audio ni video, no proporciona embeddings y no admite ajuste fino. Úsalo solo para uso adulto lícito, y ten en cuenta el límite estricto: sin contenido sexual que involucre a menores de edad. Los prompts no se usan para entrenamiento.

Preguntas y respuestas

¿Necesito un SDK especial para llamar a esta API desde Python?

No. El paquete oficial openai funciona. Establece base_url en https://api.nsfwaiapi.com/v1, pasa tu clave y usa el ID de modelo uncensored.

¿El streaming funciona igual que con OpenAI?

Sí. Pasa stream=True y lee los eventos enviados por el servidor por fragmentos. El uso se informa al final del streaming.

¿Qué debo hacer cuando recibo un error 429?

Espera y reintenta. Si la respuesta tiene un encabezado Retry-After, respétalo. El código de error rate_limited significa demasiadas peticiones por minuto, y concurrency significa demasiadas peticiones a la vez.

¿Puedo probarlo sin pagar?

Sí. Tras registrarte, puedes reclamar $0,50 de crédito de prueba en la página de clave de API. No requiere datos de pago, es válido por 7 días y es uno por persona.

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

NsfwAiApiAPI de LLM sin censura para generación de texto NSFW