Lo que se admite y lo que no
| Funcionalidad | Estado | Notas |
|---|---|---|
| Completado de chat | Sí | POST /v1/chat/completions |
| Respuestas | Sí | POST /v1/responses |
| Streaming (SSE) | Sí | stream: true, con el uso al final |
| Llamadas a funciones | Sí | tools, tool_choice, streaming de tool_calls, resultados como role: "tool" |
| Modo JSON | Sí | response_format: {"type":"json_object"} |
| Controles de muestreo | Sí | temperature, top_p, stop, seed, penalizaciones |
| Imágenes, audio, vídeo | No | Solo texto |
| Embeddings, ajuste fino | No | No 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_limitedyconcurrencysignifican que superaste un límite; una sobrecarga temporal devuelve 429 conRetry-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.loadsen 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-Idse 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.
