Guía para desarrolladores sobre el uso del modelo Naia desde código. Después de ejecutar el modelo mediante 4.4 Descarga del modelo Naia, usa la API compatible con OpenAI servida localmente (sin pasarela, sin cola) tal cual. Con cualquier SDK o herramienta de OpenAI, solo tienes que apuntar la baseURL a este modelo.
No es exclusivo de naia-os/la interfaz — cualquier código que hable OpenAI Realtime/Chat/Audio/Embeddings se conecta tal cual, y puedes crear y ejecutar nuevas aplicaciones sobre este modelo.
1. Conexión · autenticación
- Base REST:
http://<host>:8892/v1(127.0.0.1en el mismo PC) - Realtime (WS):
ws://<host>:8892/v1/realtime(unws://<host>:8892desnudo también funciona — la ruta/v1/realtime+ el modelo predeterminado se aplican automáticamente) - Conexión: en local (
127.0.0.1) / Tailscale, no se requiere autenticación — el contenedor verifica su propia licencia. Los clientes que necesitan un campo de clave (SDK de OpenAI, etc.) pueden pasar cualquier valor (naia). Cuando lo expongas de forma remota, coloca §4.4 Tailscale/VPN por delante.
🔑 Una sola clave — la clave de suscripción
- Clave de suscripción — la clave de suscripción que obtienes del portal. Se usa solo en el momento de la ejecución del contenedor (activación) (
-e NAIA_ACCOUNT_TOKEN=<subscription-key>). Comprueba la suscripción y obtiene una licencia de tiempo limitado (certificado). - No hay una clave de conexión separada. Una vez activado, el contenedor se verifica a sí mismo localmente con el certificado, así que los clientes (naia-os, SDK de OpenAI) solo necesitan conectarse por URL —
127.0.0.1en el mismo PC, o Tailscale/VPN (§4.4) desde otro dispositivo. No llama a la pasarela por cada conexión. - El
api_keyde los ejemplos siguientes es un marcador de posición (el SDK de OpenAI requiere el campo) — el contenedor sin conexión no lo comprueba, así que cualquier valor como"naia"funciona.
2. Puntos de conexión (compatibles con OpenAI)
| Punto de conexión | Uso |
|---|---|
GET /health | Disponibilidad {"ready":true,"services":{tts,stt,llm},"vad":true} (sin autenticación) |
GET /v1/models | Lista de modelos |
WS /v1/realtime | Sesión de voz en tiempo real (VAD, barge-in, emoción) |
POST /v1/chat/completions | Chat (streaming) |
POST /v1/audio/speech | Texto a voz (TTS) |
POST /v1/audio/transcriptions | Voz a texto (STT) |
POST /v1/embeddings | Embeddings |
Chat (curl):
curl -s http://127.0.0.1:8892/v1/chat/completions \
-H "Authorization: Bearer naia" -H "Content-Type: application/json" \
-d '{"model":"naia-0.9-omni-24g","messages":[{"role":"user","content":"hi"}],"stream":false}'
SDK de OpenAI (Python) — solo cambia baseURL:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8892/v1", api_key="naia")
print(client.chat.completions.create(
model="naia-0.9-omni-24g",
messages=[{"role": "user", "content": "hi"}],
).choices[0].message.content)
Transcripción (STT):
curl -s http://127.0.0.1:8892/v1/audio/transcriptions \
-H "Authorization: Bearer naia" \
-F file=@sample.wav -F model=naia-0.9-omni-24g
3. Voz en tiempo real — flujo de conexión (WS)
El mismo flujo que usa la demo en vivo 4.3. (Sin conexión, comienza de inmediato, sin cola/asignación de la pasarela.)
-
Conectar — abre
ws://<host>:8892. -
Primer fotograma (autenticación · idioma) — los WebSockets del navegador no pueden enviar encabezados, así que envía esto como primer mensaje:
{ "setup": { "apiKey": "naia", "locale": "en" } } -
Cuando el servidor envía
session.created, configura la sesión consession.update:{ "type": "session.update", "session": { "modalities": ["text", "audio"], "input_audio_format": "pcm16", "output_audio_format": "pcm16", "instructions": "<persona instructions>", "turn_detection": { "type": "server_vad" }, "input_audio_transcription": { "language": "en" }, "ref_audio_url": "<URL of a voice sample to mimic (optional)>" } } -
Intercambio
Cliente → Servidor Entrada de voz {"type":"input_audio_buffer.append","audio":"<base64 PCM16 24kHz>"}(el VAD del servidor detecta el final del habla)Entrada de texto conversation.item.createy luegoresponse.createBarge-in response.cancelServidor → Cliente response.audio.deltafragmento de audio base64 PCM16 24kHz response.audio_transcript.delta/response.text.deltatexto de respuesta (streaming) conversation.item.input_audio_transcription.completedtranscripción de tu habla emotion.updatedetiqueta de emoción / prosodia (§5) response.donefin de un turno
4. Idiomas — 30 idiomas (predeterminado = auto/global)
El modelo admite 30 idiomas (árabe, birmano, chino, danés, neerlandés, inglés, finés, francés, alemán, griego, hebreo, hindi, indonesio, italiano, japonés, jemer, coreano, lao, malayo, noruego, polaco, portugués, ruso, español, suajili, sueco, tagalo, tailandés, turco, vietnamita).
- Predeterminado (sin definir) = global/auto — detecta el idioma que hablaste y responde en ese idioma (por turno).
- Para fijar un idioma específico, indica un código ISO-639-1 (p. ej.
ko/en/ja) ensetup.localeo eninput_audio_transcription.languagedesession.update.
5. Formato de salida (etiquetas de emoción · prosodia)
El formato de salida está ajustado para la conversación de voz — si el cliente lo conoce, puede expresarse de forma más rica.
- Etiquetas de prosodia: el texto de respuesta contiene etiquetas entre corchetes en inglés en minúsculas como
[laughing],[sigh],[breath],[pause],[hesitation]intercaladas donde cambia la emoción (para la prosodia del habla). El modelo recibe la instrucción de no usar etiquetas en coreano como[웃음], acotaciones entre paréntesis como(smiling), ni asteriscos como*smiles*. Vocabulario conocido:laughing/laugh/laughter/chuckle/giggle · sigh/exhale · breath/inhale · pause · hesitation · gasp/cough/sneeze/yawn/sniff/hum · cry/sob/moan/whisper/shout/cheer(las demás etiquetas se transmiten tal cual). - Por cada etiqueta, el servidor envía un evento
emotion.updateden correspondencia 1:1 (state== nombre de la etiqueta, en minúsculas):{ "type": "emotion.updated", "state": "laughing", "tag": "[laughing]", "known": true } - La ruta TTS conserva las etiquetas y las introduce en la síntesis para la prosodia del habla, mientras que el
text.deltadel chat envía texto limpio con las etiquetas eliminadas. (Sin emojis, markdown ni autonarración entre paréntesis en la salida.) - Mapeo del cliente (referencia naia-os): asigna
emotion.updated.state(etiqueta de prosodia) a las expresiones del avatar —laughing/chuckle/giggle/cheer → happy,sigh/exhale/cry/sob → sad,gasp → surprised,shout → angry,hesitation → think. La prosodia no emocional comobreath·pauseno cambia la expresión (mantén la anterior — para que no parpadee a neutral con cada respiración). - Se recomienda un manejo robusto: la salida del LLM no siempre es exacta. Prefiere
emotion.updated, pero si falta, detecta automáticamente las etiquetas en la propia transcripción (mayúsculas[HAPPY]/ etiquetas de prosodia en minúsculas) o las acotaciones filtradas ((smiles)·*sigh*) y refléjalas en la expresión; si no hay ninguna señal, mantén la expresión actual (cf. naia-osshell/src/lib/vrm/expression.tsextractExpression).
6. Cambiar el modelo de conversación · subir una nueva versión (operación)
Esta es la guía detallada para cambiarlo directamente desde la línea de comandos. Los suscriptores individuales pueden usarla tal cual (sin necesidad de clave), e incluye opciones de bloqueo para la operación compartida o de quiosco. El resumen sencillo está en 4.4 Sin conexión.
6.1 Cambiar el modelo de conversación (desde la 0.91)
Deja el contenedor tal cual y cambia, en tiempo de ejecución, solo el modelo que se encarga de la conversación. La voz (hablar y escuchar), la marca de agua y la verificación de la suscripción se mantienen.
Tres cosas que conviene saber primero:
- El modelo predeterminado es un LLM abierto integrado. Puedes volver al predeterminado en cualquier momento después de cambiarlo.
- El modelo que subas debe estar en formato GGUF. Además, como las funciones de voz consumen unos 10 GB de memoria, el modelo de conversación puede llegar a unos 14 GB aproximadamente. Los modelos más grandes se rechazan, y si la carga llega a fallar, se vuelve automáticamente al modelo que se estaba usando (la conversación no se interrumpe).
- Los suscriptores individuales no necesitan una clave aparte. La verificación de la suscripción (licencia) de tu propia máquina es ya tu autorización, así que basta con cambiarlo con los comandos de abajo — igual que la voz no necesita clave. (Solo en una caja compartida o de quiosco usada por varias personas, el operador puede poner un bloqueo en el arranque con
-e NAIA_ADMIN_KEY=contraseña_definida, y en ese caso debes enviar también-H "Authorization: Bearer contraseña_definida"en las solicitudes.)
Práctica — primero fija solo la dirección:
BASE=http://127.0.0.1:8892 # desde otro dispositivo, usa la dirección https de §4.4 (p. ej. ...:8443)
① Mira qué modelo hay ahora y cuánta memoria queda:
curl -s $BASE/admin/llm/status
② Cambia el modelo — solo sustituye la parte del modelo entre comillas y pégalo.
Puedes poner tal cual la dirección de la tarjeta de modelo de HuggingFace (https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF) o su id (Qwen/Qwen2.5-7B-Instruct-GGUF):
curl -s -X POST $BASE/admin/llm/swap \
-H "Content-Type: application/json" \
-d '{"model":"Qwen/Qwen2.5-7B-Instruct-GGUF","pull":true}'
El prefijo hf.co/ y la cuantización (quant) se añaden automáticamente (el predeterminado es Q4_K_M). Si quieres una cuantización concreta, escríbela al final, como Qwen/Qwen2.5-7B-Instruct-GGUF:Q5_K_M. La primera vez que se descarga un modelo puede tardar de unas decenas de segundos a unos minutos.
②-sin conexión — cambiar con un archivo GGUF que tengas, sin internet.
Cuando no hay internet, como en exposiciones o atención al cliente, en lugar de descargarlo de HuggingFace registras y usas un archivo GGUF que ya tienes. (Regla de distinción: si el nombre lleva una barra, como organización/repositorio, es HuggingFace en línea; si es un nombre simple sin barra, es un modelo local.)
Copia y pega línea por línea. En el lugar de mimodelo pon el nombre que quieras, y en el de mimodelo.gguf el nombre real del archivo:
podman cp ./mimodelo.gguf naia-omni:/app/models/mimodelo.gguf
podman exec naia-omni sh -lc 'printf "FROM /app/models/mimodelo.gguf\n" > /tmp/Modelfile && ollama create mimodelo -f /tmp/Modelfile'
curl -s -X POST $BASE/admin/llm/swap -H "Content-Type: application/json" -d '{"model":"mimodelo:latest","pull":false}'
⚠️ Un GGUF convertido o fusionado por ti mismo puede no incluir la plantilla de chat, lo que provoca respuestas incoherentes o cortadas. En ese caso, añade al Modelfile del paso 2 la plantilla de chat de esa familia de modelos (
TEMPLATE) y los tokens de parada (PARAMETER stop) al registrarlo — los detalles para desarrolladores están en [implementación de referencia §7]. (Los GGUF Instruct oficiales de HuggingFace suelen llevarla integrada y se pueden usar tal cual.)
③ Vuelve al modelo predeterminado:
curl -s -X POST $BASE/admin/llm/restore
En una caja compartida o de quiosco (cuando el operador ha puesto
NAIA_ADMIN_KEY), añade-H "Authorization: Bearer contraseña_definida"a cada comando de arriba. Los suscriptores individuales no lo necesitan.
Después de cambiarlo, las aplicaciones como naia-os pueden seguir conectándose a la misma dirección, tal cual (no hace falta reconectar). Si quieres que siga arrancando con ese modelo incluso tras reiniciar o actualizar, indica el modelo predeterminado al levantar el contenedor con -e NAIA_LLM_MODEL=Qwen/Qwen2.5-7B-Instruct-GGUF.
6.2 Actualizar a una nueva versión
Cuando salga una nueva versión, cambia solo la imagen (versión) y deja la suscripción y la configuración tal cual. La primera vez que enciendas la nueva versión, el contenedor se vuelve a autenticar automáticamente por internet (con la misma suscripción y el mismo dispositivo — no hace falta volver a introducir la clave a mano). Por eso, al actualizar debes estar conectado a internet.
podman pull ghcr.io/nextain/naia-0.9-omni-24g:latest # descargar la última versión
podman stop naia-omni && podman rm naia-omni # limpiar solo el contenedor (ver aviso abajo)
# Vuelve a ejecutar exactamente el mismo comando de arranque que usaste en la primera instalación — basta con montar el mismo volumen de licencia y listo.
⚠️ Al actualizar, no pulses "liberar dispositivo (release)". La liberación solo se usa cuando trasladas a otro ordenador el que estabas usando. Si liberas al intentar actualizar, tendrás que autenticarte desde cero. Para actualizar, basta con dejar tal cual el volumen de la licencia y se mantienen la suscripción y el registro del dispositivo.
Los usuarios que ya se hayan autenticado, con solo descargar la última versión y volver a encenderla como se ve arriba, pasan tal cual a la nueva versión que permite cambiar de modelo (la autenticación se mantiene). Si quieres descargar una versión concreta, en lugar de :latest usa el número de versión, como :0.91.
7. Véase también
- Implementación de referencia / código de ejemplo (código abierto): el cliente de voz de naia-os
shell/src/lib/voice/(Apache 2.0) — contiene el cliente real que se comunica con esta API (naia-omni.ts) y el manejo de emoción/prosodia (emotion-tags.ts; mapeo de expresiones y extracción robusta envrm/expression.ts). Úsalo como punto de partida para probar nuevos modelos y crear aplicaciones Tauri. Pruébalo en vivo en la demo en vivo 4.3. - Gama y precios: 4.1 Precios de los modelos
- Nube (prevista): 4.6 En línea </content>