ES ▾
Empezar ahora

API de ChatGPT sin filtros: streaming, herramientas y JSON

Una API de ChatGPT sin filtros solo es útil si admite lo que necesitan las aplicaciones reales: texto en streaming, llamadas a herramientas y salida estructurada. Este endpoint hace las tres cosas en el formato de OpenAI. A continuación hay código funcional para cada una y una lista clara de lo que no se admite.

Última actualización

Un flujo de partículas de luz en una cinta transportadora con un brazo robótico seleccionando bloques estructurados, para streaming de herramientas y JSON

Resumen

  1. Admitido: streaming sobre SSE, llamadas a herramientas con tool_calls en streaming, modo JSON y los ajustes de muestreo habituales.
  2. No admitido: generación de imágenes, audio o video, embeddings, ajuste fino.
  3. Los streams ocupan una ranura de concurrencia hasta que finalizan: 8 ranuras con una clave completa, 2 con una clave de prueba.
  4. Analiza los argumentos de las herramientas y las respuestas JSON de forma defensiva; los modelos pueden devolver salida malformada.

Lo que se admite y lo que no

FuncionalidadEstadoNotas
Completado de chatSíPOST /v1/chat/completions
RespuestasSíPOST /v1/responses
Streaming (SSE)Sístream: true, con el uso al final
Llamadas a funcionesSítools, tool_choice, streaming de tool_calls, resultados como role: "tool"
Modo JSONSíresponse_format: {"type":"json_object"}
Controles de muestreoSítemperature, top_p, stop, seed, penalizaciones
Imágenes, audio, vídeoNoSolo texto
Embeddings, ajuste finoNoNo ofrecido

El modelo es un modelo de texto de pesos abiertos con el ID uncensored, ejecutado por un proveedor independiente. Responde sin rechazos de contenido para uso adulto lícito y no es una API oficial de OpenAI ni de ningún otro proveedor. Cada ejemplo a continuación usa la URL base https://api.chatgptunfiltered.com/v1.

Todo lo que no esté en esta lista debe considerarse no disponible hasta que la documentación indique lo contrario. Esto incluye herramientas alojadas específicas del proveedor, que son diferentes de las llamadas a funciones que se muestran a continuación.

Transmisión

Establece stream: true e itera sobre los fragmentos. Imprime los fragmentos de texto a medida que llegan y lee el uso del fragmento final.

from openai import OpenAI

client = OpenAI(
    base_url="https://api.chatgptunfiltered.com/v1",
    api_key="YOUR_KEY",
)

stream = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "Write a four-line poem about a night train."}],
    stream=True,
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:
        print("\nusage:", chunk.usage)

Dos cosas a recordar. Primero, una petición en streaming ocupa su ranura de concurrencia hasta que finaliza el stream, por lo que un stream abandonado mantiene una ranura ocupada; ciérralo cuando el usuario se vaya. Segundo, el fragmento final puede contener uso y no texto, así que protege el acceso a choices como se indica arriba.

En una aplicación web, reenvía los fragmentos al navegador a medida que llegan, por ejemplo a través de tu propia ruta SSE, y permite que la página añada cada fragmento de texto al mensaje. Realiza la contabilidad del uso en el servidor una vez que el stream se cierra, donde el fragmento final te indica cuánto costó la petición.

Llamadas a funciones, de principio a fin

Declara una función, deja que el modelo la solicite, ejecútala tú mismo y envía el resultado con role: "tool".

import json

tools = [{
    "type": "function",
    "function": {
        "name": "get_order_status",
        "description": "Look up the status of an order by its ID.",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
}]

def get_order_status(order_id):
    return {"order_id": order_id, "status": "shipped"}

messages = [{"role": "user", "content": "Where is order A-1042?"}]
r = client.chat.completions.create(
    model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = r.choices[0].message

if msg.tool_calls:
    messages.append(msg.model_dump(exclude_none=True))
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_order_status(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result),
        })
    final = client.chat.completions.create(model="uncensored", messages=messages, tools=tools)
    print(final.choices[0].message.content)
else:
    print(msg.content)

El modelo decide si llamar a la función; tool_choice te permite influir en esa decisión. La función se ejecuta en tu código, nunca en el servidor, por lo que controlas qué puede tocar. Valida los argumentos antes de actuar sobre ellos, porque un modelo puede enviar un tipo incorrecto o un campo faltante.

Llamadas a funciones en streaming

Cuando combinas stream: true con tools, la llamada llega en fragmentos. El nombre de la función llega primero, y la cadena de argumentos llega en fragmentos que debes unir mediante index.

calls = {}

stream = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "Where is order A-1042?"}],
    tools=tools,
    stream=True,
)

for chunk in stream:
    if not chunk.choices:
        continue
    for tc in chunk.choices[0].delta.tool_calls or []:
        slot = calls.setdefault(tc.index, {"id": "", "name": "", "args": ""})
        if tc.id:
            slot["id"] = tc.id
        if tc.function and tc.function.name:
            slot["name"] = tc.function.name
        if tc.function and tc.function.arguments:
            slot["args"] += tc.function.arguments

for slot in calls.values():
    print(slot["name"], json.loads(slot["args"]))

Solo analiza args después de que el stream haya finalizado. Una cadena recibida a medias no es JSON válido. Después, ejecuta la función y envía el resultado exactamente como en el ejemplo sin streaming.

Modo JSON

Para respuestas estructuradas, activa el modo JSON y describe la forma que quieres en el prompt.

r = client.chat.completions.create(
    model="uncensored",
    temperature=0.2,
    response_format={"type": "json_object"},
    messages=[
        {"role": "system", "content": "Reply with JSON only: {\"mood\": string, \"intensity\": integer from 1 to 5}."},
        {"role": "user", "content": "Rate this line: The door slammed and nobody spoke for an hour."},
    ],
)

try:
    data = json.loads(r.choices[0].message.content)
except json.JSONDecodeError:
    data = None  # retry once, or fall back
print(data)

El modo JSON hace que el JSON válido sea mucho más probable, pero no aplica tu esquema. Verifica las claves y los tipos tú mismo y mantén una ruta de reintento. Una temperatura baja ayuda para la extracción. Las configuraciones de muestreo como temperature, top_p, stop, seed y las penalizaciones se pasan a través.

Un patrón útil es combinar el modo JSON con una pequeña función de validación que devuelva un mensaje de error claro, luego alimenta ese mensaje de vuelta al modelo en un solo reintento: "Tu última respuesta faltaba la clave intensity. Vuelve a responder con JSON válido." Los modelos suelen corregirse en el segundo intento, y la petición extra cuesta una fracción de centavo.

Manejo de fallos durante un streaming

Los streams fallan de formas que las peticiones normales no hacen: la conexión puede caerse después de la mitad del texto. Planifícalo.

import time
from openai import RateLimitError, APIConnectionError

def stream_reply(messages):
    for attempt in range(3):
        parts = []
        try:
            stream = client.chat.completions.create(
                model="uncensored", messages=messages, stream=True
            )
            for chunk in stream:
                if chunk.choices and chunk.choices[0].delta.content:
                    parts.append(chunk.choices[0].delta.content)
            return "".join(parts)
        except RateLimitError:
            time.sleep(5)
        except APIConnectionError:
            time.sleep(1)
    raise RuntimeError("stream failed after retries")

Un 429 aquí significa un límite o una sobrecarga temporal, y la acción correcta es esperar 5 segundos, lo que coincide con Retry-After: 5 que envía la API. Si un stream se rompe a medias, tienes una respuesta parcial. Decide si descartarla y reintentar, o conservarla y pedirle al modelo que continúe. Para las interfaces de chat, descartar y reintentar suele ser menos confuso para los usuarios.

Límites y coste a tener en cuenta

  • Tasa: 300 peticiones por minuto con una clave completa, 60 con una clave de prueba.
  • Concurrencia: 8 con una clave completa, 2 con una clave de prueba. Recuerda que cada stream abierto cuenta.
  • Tamaño: cuerpos de petición de hasta 8 MB; ventana de contexto de 100.000 tokens, prompt más respuesta.
  • Manejo de 429: los códigos rate_limited y concurrency significan que superaste un límite; una sobrecarga temporal devuelve 429 con Retry-After: 5.

Ejemplo de coste: un intercambio con llamadas a funciones con dos peticiones, 1.200 tokens de entrada y 80 de salida para la primera, 1.400 de entrada y 150 de salida para la segunda. La entrada es 2.600 x $0,15 / 1M = $0,00039. La salida es 230 x $0,50 / 1M = $0,000115. Total $0,000505. Las peticiones fallidas o rechazadas no se cobran. Las tasas están en precios, y la lista completa de métodos está en la documentación. Comienza con el crédito de prueba de $0,50 desde la página de clave de API. Para información sobre el modelo, consulta comprensión de modelos ChatGPT sin censura. El límite estricto se aplica en todos los modos: está prohibido el contenido sexual que involucre a menores de edad.

Una lista de verificación previa al lanzamiento

  • Los argumentos de las herramientas se analizan con json.loads en un bloque try, y se verifican contra las claves y tipos esperados antes de que se ejecute cualquier cosa.
  • Las funciones que expones solo pueden hacer lo que dejarías hacer a un usuario anónimo. El modelo elige cuándo llamarlas, no tú.
  • Las respuestas JSON se validan, con un reintento en caso de fallo.
  • Los streams se cierran cuando el usuario abandona la página, por lo que no ocupan una ranura de concurrencia.
  • Tu límite de concurrencia en el código está en o por debajo del límite de la clave: 8 para una clave completa.
  • Los IDs de petición de X-Request-Id se registran.
  • La clave vive en el servidor y se puede regenerar; la antigua deja de funcionar al instante.

El servicio es solo de texto, así que una función que necesite una imagen o un embedding debe provenir de otro proveedor.

Preguntas frecuentes

¿El endpoint transmite respuestas?

Sí. Envía stream: true para recibir Server-Sent Events, con el uso reportado al final del streaming.

¿Admite llamadas a funciones?

Sí. Usa tools y tool_choice, lee tool_calls de la respuesta y envía los resultados de vuelta como mensajes con role tool. También se admiten tool_calls en streaming.

¿El modo JSON devolverá siempre JSON válido?

Hace que el JSON válido sea mucho más probable, pero no es una garantía y no impone un esquema. Analiza dentro de un bloque try y reintenta en caso de fallo.

¿Puedo hacer streaming y usar herramientas en la misma petición?

Sí. Los nombres de las llamadas a funciones y los argumentos llegan en fragmentos, así que únelos por índice y analiza los argumentos después de que termine el streaming.

Obtén tu clave de API

Regístrate, copia el token, actualiza tu Base URL. Eso es todo.

Comenzar ahora