Saltar al contenido principal
La API de MKA1 proporciona una interfaz de voz en tiempo real a través de LiveKit. Esta guía cubre cómo obtener un token de sala, conectarse a una sesión de voz, enviar audio y texto como entrada, y capturar las respuestas del agente.

Descripción general

La integración de voz consta de tres componentes principales:
  1. Token de Sala: Un JWT que otorga acceso a una sala de LiveKit
  2. Conexión LiveKit: Comunicación en tiempo real basada en WebRTC
  3. Agente de Voz: Procesa entradas de audio/texto y genera respuestas habladas
El flujo de trabajo del agente es el siguiente:
  • STT (Speech-to-Text): El audio se transmite por WebSocket a 16kHz y se transcribe
  • LLM: El texto transcrito es procesado por la API de Respuestas de MKA1
  • TTS (Text-to-Speech): La salida del LLM se sintetiza a audio a 24kHz
Cada solicitud que el agente de voz envía a la API de Respuestas incluye automáticamente "voice_mode": "true" en el metadata de la solicitud. Esto te permite distinguir respuestas originadas por voz de las basadas en texto al revisar el uso o el historial de respuestas.

Obtener un token de sala

Para iniciar una sesión de voz, primero solicita un token de sala a la API de MKA1. El endpoint de token requiere una clave API y opcionalmente acepta X-On-Behalf-Of para identificar usuarios finales. Consulta Autenticación para más detalles.
import { SDK } from '@meetkai/mka1';

const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'gpt-5',
    reasoning: { effort: 'none' }
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional

console.log(session.token);    // Token JWT
console.log(session.url);      // URL de WebSocket
console.log(session.roomName); // Nombre de la sala

Parámetros

El cuerpo de la solicitud tiene dos objetos de nivel superior:
{
  "llm": { ... },   // Requerido — configuración del LLM
  "stt": { ... }    // Opcional — ajuste de speech-to-text
}

llm — Configuración del LLM (requerido)

El objeto llm acepta los mismos campos que el cuerpo de la solicitud de la API de Respuestas, menos los campos gestionados por el agente de voz (input, stream, store, background).
CampoRequeridoDescripción
modelModelo LLM a usar (por ejemplo, gpt-5)
instructionsNoInstrucciones personalizadas del sistema para el agente
previous_response_idNoEncadena esta sesión a una respuesta específica de una sesión anterior
conversationNoContinúa una conversación existente — pasa { "id": "conv_abc123..." } o el ID de la conversación como string
toolsNoArray de definiciones de herramientas (function, web_search, file_search, etc.)
tool_choiceNoCómo el modelo selecciona herramientas ("auto", "none", "required", o una herramienta específica)
parallel_tool_callsNoPermitir ejecución paralela de herramientas
max_tool_callsNoMáximo de llamadas a herramientas por respuesta (por defecto: 30)
temperatureNoTemperatura de muestreo (por ejemplo, 0.7)
max_output_tokensNoMáximo de tokens en la respuesta
reasoningNoConfiguración de razonamiento (por ejemplo, { "effort": "high" }). Establece { "effort": "none" } para sesiones de voz para minimizar la latencia — ver nota abajo.
top_pNoParámetro de muestreo nucleus
presence_penaltyNoPenalización de presencia para repetición de tokens
frequency_penaltyNoPenalización de frecuencia para repetición de tokens
truncationNo"auto" o "disabled" — controla el truncamiento de contexto
context_managementNoEstrategias de gestión de contexto para truncamiento de conversación
service_tierNo"auto", "default", "flex", o "priority"
promptNoReferencia a una plantilla de prompt y sus variables
textNoConfiguración de salida de texto (formato, verbosidad)
metadataNoMetadatos clave-valor enviados a la API de Respuestas
No puedes especificar ambos previous_response_id y conversation.
Los metadatos del token se incrustan en un JWT, que se pasa como cabecera HTTP. Mantén el payload total de llm por debajo de ~8 KB — los arrays grandes de tools pueden necesitar ser recortados.
Para sesiones de voz, desactiva el razonamiento configurando "reasoning": { "effort": "none" }. El razonamiento añade tiempo de pensamiento antes de que el modelo responda, lo que incrementa la latencia y crea pausas notables en la conversación. Desactivarlo mantiene las respuestas rápidas y naturales.

stt — Configuración de speech-to-text (opcional)

Controla la detección de actividad de voz (VAD) y el comportamiento de endpointing en el servidor.
CampoRequeridoDescripción
silence_timeout_msNoMilisegundos de silencio antes de finalizar el habla (100–5000)
initial_silence_timeout_msNoTiempo de espera antes de detectar cualquier habla (1000–30000)

Configuración avanzada

Puedes pasar herramientas, instrucciones personalizadas y ajuste de STT en una sola solicitud de token:
const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'gpt-5',
    instructions: 'Eres un asistente de viajes útil. Sé conciso en las respuestas de voz.',
    temperature: 0.7,
    tools: [
      {
        type: 'web_search',
        user_location: { country: 'US' }
      },
      {
        type: 'function',
        name: 'book_flight',
        description: 'Reservar un vuelo para el usuario',
        parameters: {
          type: 'object',
          properties: {
            origin: { type: 'string' },
            destination: { type: 'string' },
            date: { type: 'string' }
          },
          required: ['origin', 'destination', 'date']
        }
      }
    ],
    tool_choice: 'auto'
  },
  stt: {
    silence_timeout_ms: 500,
    initial_silence_timeout_ms: 10000
  }
});

Respuesta

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "url": "wss://apigw.mka1.com/api/v1/livekit",
  "roomName": "550e8400-e29b-41d4-a716-446655440000"
}
CampoDescripción
tokenToken de acceso JWT (TTL de 5 minutos) con permisos para unirse, publicar y suscribirse a la sala
urlURL de WebSocket de LiveKit para conectarse
roomNameUUID autogenerado para esta sesión
El token incluye metadatos que el agente de voz utiliza para configurar la sesión.

Continuar una sesión

Para continuar desde una respuesta anterior:
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'gpt-5',
    previousResponseId: 'resp_abc123...'
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, pero debe coincidir si la sesión original lo usó
Para continuar una conversación existente:
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'gpt-5',
    conversation: { id: 'conv_abc123...' }
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, pero debe coincidir si la sesión original lo usó
Al continuar una sesión, la clave API y la cabecera X-On-Behalf-Of (si se usa) deben coincidir con la sesión original. El agente de voz cifra ambos en el token de sala y los pasa a todos los servicios MKA1 aguas abajo. Si no coinciden, el agente no tendrá acceso al contexto anterior.

Conectarse a una sala

Una vez que tengas un token, usa el SDK de LiveKit para conectarte a la sala.
import { Room, RoomEvent, Track } from 'livekit-client';

const room = new Room();

// Conéctate a la sala
await room.connect(session.url, session.token);

console.log('Conectado a la sala:', room.name);

Enviar entrada de audio

El agente acepta entrada de audio a través de la pista de audio de la sala de LiveKit. El audio se procesa a una frecuencia de muestreo de 16kHz.
import { createLocalAudioTrack } from 'livekit-client';

// Crea una pista de audio local desde el micrófono
const audioTrack = await createLocalAudioTrack({
  echoCancellation: true,
  noiseSuppression: true,
  autoGainControl: true
});

// Publica la pista en la sala
await room.localParticipant.publishTrack(audioTrack);

Comportamiento del audio

  • Detección de Actividad de Voz (VAD): El VAD se maneja en el servidor por el agente MKA1, no localmente. El agente detecta automáticamente cuando dejas de hablar y comienza el procesamiento.
  • Frecuencia de muestreo: El audio se transmite a 16kHz al servicio STT.
  • Endpointing: El agente utiliza endpointing del lado del servidor para determinar cuándo termina el habla. No hay retardo de endpointing local.

Enviar entrada de texto

También puedes enviar mensajes de texto directamente al agente sin hablar.
// Envía un mensaje de texto al agente
const message = JSON.stringify({
  type: 'user_message',
  content: '¿Cuál es la capital de Francia?'
});

await room.localParticipant.publishData(
  new TextEncoder().encode(message),
  { reliable: true, topic: 'lk.chat' }
);

Recibir respuestas del agente

El agente responde de tres maneras:
  1. Salida de audio: Voz sintetizada a través de una pista de audio
  2. Transcripción: Texto de lo que el agente está diciendo (para subtítulos)
  3. Metadatos de respuesta: ID de respuesta e ID de conversación a través del canal de datos

Suscribirse a la salida de audio

import { RoomEvent, Track } from 'livekit-client';

room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
  if (track.kind === Track.Kind.Audio && participant.identity !== room.localParticipant.identity) {
    // Esta es la salida de audio del agente
    const audioElement = track.attach();
    document.body.appendChild(audioElement);
  }
});

Recibir transcripciones

El agente publica transcripciones de su habla. Puedes usarlas para subtítulos o registros.
room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {
  for (const segment of segments) {
    console.log(`El agente dijo: ${segment.text}`);
  }
});

Recibir metadatos de respuesta

El agente publica el response_id y conversation_id (si aplica) cuando comienza a generar una respuesta. Guarda el response_id para encadenar futuras sesiones usando previous_response_id.
room.on(RoomEvent.DataReceived, (payload, participant) => {
  if (participant.identity !== room.localParticipant.identity) {
    const data = JSON.parse(new TextDecoder().decode(payload));

    if (data.response_id) {
      console.log('ID de respuesta:', data.response_id);
      console.log('ID de conversación:', data.conversation_id); // presente si se usa una conversación
      // Guarda response_id para encadenar futuras sesiones con previous_response_id
    }
  }
});

Continuidad de la conversación

El agente soporta conversaciones de varios turnos con memoria persistente. Cada respuesta recibe automáticamente un response_id, mientras que las conversaciones deben ser creadas y gestionadas explícitamente a través de la API de Conversaciones. Hay dos formas de continuar una conversación: llm.previous_response_id encadena una nueva sesión a una respuesta específica. El agente recibe el contexto de esa respuesta y todas las respuestas previas en la cadena. Úsalo cuando:
  • Quieres continuar desde un punto específico en una conversación
  • Estás construyendo un flujo de conversación lineal
  • Quieres ramificar desde una respuesta específica
llm.conversation hace referencia a una conversación creada mediante la API de Conversaciones. Úsalo cuando:
  • Necesitas gestionar metadatos de la conversación (títulos, etiquetas, etc.)
  • Quieres listar o buscar conversaciones pasadas
  • Estás construyendo una interfaz de chat con historial de conversación persistente
  • Múltiples clientes necesitan acceder a la misma conversación

Iniciar una nueva sesión

import { Room, RoomEvent } from 'livekit-client';
import { SDK } from '@meetkai/mka1';

const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

// 1. Obtén un token para una nueva sesión
const session = await mka1.llm.speech.livekitToken({
  llm: { model: 'gpt-5' }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional

// 2. Conéctate a la sala
const room = new Room();
await room.connect(session.url, session.token);

// 3. Rastrea el ID de respuesta cuando el agente responda
let lastResponseId: string;
room.on(RoomEvent.DataReceived, (payload, participant) => {
  const data = JSON.parse(new TextDecoder().decode(payload));
  if (data.response_id) {
    lastResponseId = data.response_id;
  }
});

// 4. Conversa...
// 5. Desconéctate cuando termines
room.disconnect();

Continuar desde una respuesta anterior

Usa previous_response_id para encadenar una nueva sesión a la última respuesta, preservando el contexto de la conversación:
// Usa la misma clave API y X-On-Behalf-Of que la sesión original
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

// 1. Obtén un nuevo token encadenado a la respuesta anterior
const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'gpt-5',
    previousResponseId: lastResponseId
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, pero debe coincidir si la sesión original lo usó

// 2. Conéctate a la nueva sala
const room = new Room();
await room.connect(session.url, session.token);

// 3. El agente ahora tiene contexto de la sesión anterior
// Usuario: "¿Qué te pregunté antes?"
// Agente: "Me preguntaste sobre la capital de Francia..."

Continuar desde una conversación

Usa conversation_id para continuar una conversación existente creada mediante la API de Conversaciones:
// Usa la misma clave API y X-On-Behalf-Of que la sesión original
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

// 1. Obtén un nuevo token con el ID de la conversación
const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'gpt-5',
    conversation: { id: conversationId }
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, pero debe coincidir si la sesión original lo usó

// 2. Conéctate a la nueva sala
const room = new Room();
await room.connect(session.url, session.token);

// 3. El agente ahora tiene contexto de todo el historial de la conversación
Al continuar una conversación, la clave API y la cabecera X-On-Behalf-Of (si se usa) deben coincidir con la sesión original. El contexto está limitado a la identidad autenticada.

Manejo de desconexiones

Los tokens expiran después de 5 minutos. Si necesitas sesiones más largas, implementa lógica de reconexión:
room.on(RoomEvent.Disconnected, async () => {
  console.log('Desconectado de la sala');

  // Obtén un nuevo token (continuando desde la última respuesta)
  const newSession = await mka1.llm.speech.livekitToken({
    llm: {
      model: 'gpt-5',
      previousResponseId: savedResponseId
    }
  });

  // Reconéctate
  await room.connect(newSession.url, newSession.token);
});

Ejemplo completo

Aquí tienes un ejemplo completo integrando todo:
import { Room, RoomEvent, Track, createLocalAudioTrack } from 'livekit-client';
import { SDK } from '@meetkai/mka1';

async function startVoiceSession(model: string = 'gpt-5') {
  const mka1 = new SDK({ bearerAuth: `Bearer ${YOUR_API_KEY}` });

  // Obtén credenciales de sala
  const session = await mka1.llm.speech.livekitToken({ llm: { model } });

  // Crea y conéctate a la sala
  const room = new Room();

  let lastResponseId: string | undefined;

  // Maneja la salida de audio del agente
  room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
    if (track.kind === Track.Kind.Audio) {
      const audio = track.attach();
      document.body.appendChild(audio);
    }
  });

  // Maneja transcripciones
  room.on(RoomEvent.TranscriptionReceived, (segments) => {
    for (const segment of segments) {
      console.log('Agente:', segment.text);
    }
  });

  // Maneja metadatos de respuesta
  room.on(RoomEvent.DataReceived, (payload, participant) => {
    const data = JSON.parse(new TextDecoder().decode(payload));
    if (data.response_id) {
      lastResponseId = data.response_id;
    }
  });

  // Conéctate a la sala
  await room.connect(session.url, session.token);

  // Captura y publica el micrófono
  const audioTrack = await createLocalAudioTrack({
    echoCancellation: true,
    noiseSuppression: true
  });
  await room.localParticipant.publishTrack(audioTrack);

  // El agente te saludará automáticamente
  // ¡Comienza a hablar para interactuar!

  return { room, getLastResponseId: () => lastResponseId };
}

Manejo de errores

Errores del endpoint de token

Estos se devuelven como respuestas HTTP al solicitar un token de sala:
ErrorCausaSolución
400 Bad RequestFalta el parámetro requerido llm.modelIncluye model dentro del objeto llm
400 Bad RequestSe especificaron ambos previous_response_id y conversationUsa solo uno, no ambos
401 UnauthorizedClave API inválida o ausenteVerifica que tu clave API sea válida

Errores en sesión

Durante una sesión de voz activa, el agente publica errores a través del canal de datos de LiveKit. Escúchalos junto con los metadatos de respuesta:
room.on(RoomEvent.DataReceived, (payload, participant) => {
  if (participant.identity === room.localParticipant.identity) return;

  const data = JSON.parse(new TextDecoder().decode(payload));

  if (data.error) {
    console.error(`[${data.error.service}] ${data.error.code}: ${data.error.message}`);
    // data.error.details puede contener información adicional para depuración
  }

  if (data.response_id) {
    lastResponseId = data.response_id;
  }
});
La estructura del payload de error:
{
  "error": {
    "code": "rate_limited",
    "message": "HTTP 429",
    "service": "llm",
    "details": "..."
  }
}
CampoDescripción
codeCódigo de error (ver tabla abajo)
messageDescripción corta del error
serviceQué parte del pipeline falló: llm, stt, o tts
detailsContexto adicional para depuración (opcional)
Códigos de error:
CódigoServicioCausa
invalid_sessionFaltan campos de metadatos requeridos (sub, llm)
auth_errorFallo al descifrar credenciales del token de sala
session_errorEl agente no pudo iniciar la sesión de voz
invalid_requestllmSolicitud incorrecta a la API de Respuestas (HTTP 400)
auth_errorllmClave API inválida (HTTP 401)
access_deniedllmPermisos insuficientes (HTTP 403)
rate_limitedllmLímite de tasa excedido (HTTP 429)
service_errorllmError interno del servidor (HTTP 500)
service_unavailablellmServicio upstream no disponible (HTTP 502/503)
timeoutllmTiempo de espera excedido (HTTP 504)
connection_errorllmFallo al conectar con la API de Respuestas
transcription_errorsttFallo en el procesamiento de speech-to-text
speech_errorttsFallo en la síntesis de text-to-speech

Errores de conexión

ProblemaCausaSolución
Tiempo de espera de conexiónProblemas de red o token inválidoObtén un token nuevo y reintenta
Token expiradoSesión excedió los 5 minutosObtén un nuevo token con previous_response_id para continuar

Próximos pasos