Pular para o conteúdo principal
A API MKA1 oferece uma interface de voz em tempo real através do LiveKit. Este guia cobre como obter um token de sala, conectar-se a uma sessão de voz, enviar áudio e texto como entrada e capturar as respostas do agente.

Visão geral

A integração de voz consiste em três componentes principais:
  1. Token de Sala: Um JWT que concede acesso a uma sala do LiveKit
  2. Conexão LiveKit: Comunicação em tempo real baseada em WebRTC
  3. Agente de Voz: Processa entrada de áudio/texto e gera respostas faladas
O pipeline do agente funciona da seguinte forma:
  • STT (Fala para Texto): O áudio é transmitido via WebSocket a 16kHz e transcrito
  • LLM: O texto transcrito é processado pela API de Respostas MKA1
  • TTS (Texto para Fala): A saída do LLM é sintetizada em áudio a 24kHz
Toda requisição que o agente de voz envia para a API de Respostas inclui automaticamente "voice_mode": "true" no metadata da requisição. Isso permite distinguir respostas originadas por voz das baseadas em texto ao revisar o uso ou o histórico de respostas.

Obtendo um token de sala

Para iniciar uma sessão de voz, primeiro solicite um token de sala à API MKA1. O endpoint de token requer uma chave de API e aceita opcionalmente X-On-Behalf-Of para identificar usuários finais. Veja Autenticação para detalhes.
import { SDK } from '@meetkai/mka1';

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

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

console.log(session.token);    // Token JWT
console.log(session.url);      // URL WebSocket
console.log(session.roomName); // Nome da sala

Parâmetros

O corpo da requisição possui dois objetos de nível superior:
{
  "llm": { ... },   // Obrigatório — configuração do LLM
  "stt": { ... }    // Opcional — ajuste do speech-to-text
}

llm — Configuração do LLM (obrigatório)

O objeto llm aceita os mesmos campos do corpo da requisição da API de Respostas, exceto campos gerenciados pelo agente de voz (input, stream, store, background).
CampoObrigatórioDescrição
modelSimModelo LLM a ser usado (ex: auto)
instructionsNãoInstruções de sistema personalizadas para o agente
previous_response_idNãoEncadeia esta sessão a uma resposta específica de uma sessão anterior
conversationNãoContinua uma conversa existente — passe { "id": "conv_abc123..." } ou apenas o ID da conversa como string
toolsNãoArray de definições de ferramentas (function, web_search, file_search, etc.)
tool_choiceNãoComo o modelo seleciona ferramentas ("auto", "none", "required" ou uma ferramenta específica)
parallel_tool_callsNãoPermite execução paralela de ferramentas
max_tool_callsNãoNúmero máximo de chamadas de ferramenta por resposta (padrão: 30)
temperatureNãoTemperatura de amostragem (ex: 0.7)
max_output_tokensNãoMáximo de tokens na resposta
reasoningNãoConfiguração de raciocínio (ex: { "effort": "high" }). Defina { "effort": "none" } para sessões de voz para minimizar latência — veja nota abaixo.
top_pNãoParâmetro de amostragem nucleus
presence_penaltyNãoPenalidade de presença para repetição de tokens
frequency_penaltyNãoPenalidade de frequência para repetição de tokens
truncationNão"auto" ou "disabled" — controla truncamento de contexto
context_managementNãoEstratégias de gerenciamento de contexto para truncamento de conversas
service_tierNão"auto", "default", "flex" ou "priority"
promptNãoReferência a um template de prompt e suas variáveis
textNãoConfiguração de saída de texto (formato, verbosidade)
metadataNãoMetadados chave-valor passados para a API de Respostas
Você não pode especificar ambos previous_response_id e conversation.
Os metadados do token são incorporados em um JWT, que é passado como um header HTTP. Mantenha o payload total de llm abaixo de ~8 KB — arrays grandes de tools podem precisar ser reduzidos.
Para sessões de voz, desative o raciocínio definindo "reasoning": { "effort": "none" }. O raciocínio adiciona tempo de “pensamento” antes da resposta do modelo, o que aumenta a latência e cria pausas perceptíveis na conversa. Desativando-o, as respostas ficam rápidas e naturais.

stt — Configuração de fala para texto (opcional)

Controla a detecção de atividade de voz (VAD) e o comportamento de finalização no servidor.
CampoObrigatórioDescrição
silence_timeout_msNãoMilissegundos de silêncio antes de finalizar a fala (100–5000)
initial_silence_timeout_msNãoTempo limite antes de qualquer fala ser detectada (1000–30000)

Configuração avançada

Você pode passar ferramentas, instruções personalizadas e ajuste de STT em uma única requisição de token:
const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'auto',
    instructions: 'Você é um assistente de viagens útil. Seja conciso nas respostas de voz.',
    temperature: 0.7,
    tools: [
      {
        type: 'web_search',
        user_location: { country: 'US' }
      },
      {
        type: 'function',
        name: 'book_flight',
        description: 'Book a flight for the user',
        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
  }
});

Resposta

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "url": "wss://apigw.mka1.com/api/v1/livekit",
  "roomName": "550e8400-e29b-41d4-a716-446655440000"
}
CampoDescrição
tokenToken de acesso JWT (TTL de 5 minutos) com permissões de entrada, publicação e assinatura na sala
urlURL WebSocket do LiveKit para conexão
roomNameUUID gerado automaticamente para esta sessão
O token inclui metadados que o agente de voz usa para configurar a sessão.

Continuando uma sessão

Para continuar a partir de uma resposta anterior:
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'auto',
    previousResponseId: 'resp_abc123...'
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, mas deve corresponder se a sessão original usou
Para continuar uma conversa existente:
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'auto',
    conversation: { id: 'conv_abc123...' }
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, mas deve corresponder se a sessão original usou
Ao continuar uma sessão, a chave de API e o header X-On-Behalf-Of (se usado) devem corresponder à sessão original. O agente de voz criptografa ambos no token da sala e os repassa para todos os serviços MKA1 subsequentes. Se não corresponderem, o agente não terá acesso ao contexto anterior.

Conectando-se a uma sala

Depois de obter um token, use o SDK do LiveKit para conectar-se à sala.
import { Room, RoomEvent, Track } from 'livekit-client';

const room = new Room();

// Conectar à sala
await room.connect(session.url, session.token);

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

Enviando entrada de áudio

O agente aceita entrada de áudio através da trilha de áudio da sala do LiveKit. O áudio é processado a uma taxa de amostragem de 16kHz.
import { createLocalAudioTrack } from 'livekit-client';

// Cria uma trilha de áudio local do microfone
const audioTrack = await createLocalAudioTrack({
  echoCancellation: true,
  noiseSuppression: true,
  autoGainControl: true
});

// Publica a trilha na sala
await room.localParticipant.publishTrack(audioTrack);

Comportamento do áudio

  • Detecção de Atividade de Voz (VAD): O VAD é tratado no servidor pelo agente MKA1, não localmente. O agente detecta automaticamente quando você para de falar e inicia o processamento.
  • Taxa de amostragem: O áudio é transmitido a 16kHz para o serviço de STT.
  • Finalização: O agente usa finalização no servidor para determinar quando a fala termina. Não há atraso de finalização local.

Enviando entrada de texto

Você também pode enviar mensagens de texto diretamente para o agente sem falar.
// Envia uma mensagem de texto para o agente
const message = JSON.stringify({
  type: 'user_message',
  content: 'Qual é a capital da França?'
});

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

Recebendo respostas do agente

O agente responde de três maneiras:
  1. Saída de áudio: Fala sintetizada via trilha de áudio
  2. Transcrição: Texto do que o agente está dizendo (para legendas)
  3. Metadados da resposta: ID da resposta e ID da conversa via canal de dados

Inscrevendo-se na saída de áudio

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 é a saída de áudio do agente
    const audioElement = track.attach();
    document.body.appendChild(audioElement);
  }
});

Recebendo transcrições

O agente publica transcrições de sua fala. Você pode usá-las para legendas ou registro.
room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {
  for (const segment of segments) {
    console.log(`Agente disse: ${segment.text}`);
  }
});

Recebendo metadados da resposta

O agente publica o response_id e o conversation_id (se aplicável) quando começa a gerar uma resposta. Salve o response_id para encadear futuras sessões 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 da resposta:', data.response_id);
      console.log('ID da conversa:', data.conversation_id); // presente se estiver usando uma conversa
      // Salve o response_id para encadear futuras sessões com previous_response_id
    }
  }
});

Continuidade de conversas

O agente suporta conversas de múltiplas interações com memória persistente. Cada resposta recebe automaticamente um response_id, enquanto conversas devem ser explicitamente criadas e gerenciadas pela API de Conversas. Existem duas formas de continuar uma conversa: llm.previous_response_id encadeia uma nova sessão a uma resposta específica. O agente recebe o contexto dessa resposta e de todas as anteriores na cadeia. Use quando:
  • Você quer continuar de um ponto específico em uma conversa
  • Está construindo um fluxo de conversa linear
  • Deseja ramificar a partir de uma resposta específica
llm.conversation referencia uma conversa criada via API de Conversas. Use quando:
  • Precisa gerenciar metadados da conversa (títulos, tags, etc.)
  • Deseja listar ou buscar conversas passadas
  • Está construindo uma interface de chat com histórico persistente
  • Múltiplos clientes precisam acessar a mesma conversa

Iniciando uma nova sessão

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

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

// 1. Obtenha um token para uma nova sessão
const session = await mka1.llm.speech.livekitToken({
  llm: { model: 'auto' }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional

// 2. Conecte-se à sala
const room = new Room();
await room.connect(session.url, session.token);

// 3. Acompanhe o ID da resposta quando o agente responder
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. Converse...
// 5. Desconecte ao terminar
room.disconnect();

Continuando a partir de uma resposta anterior

Use previous_response_id para encadear uma nova sessão à última resposta, preservando o contexto da conversa:
// Use a mesma chave de API e X-On-Behalf-Of da sessão original
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

// 1. Obtenha um novo token encadeado à resposta anterior
const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'auto',
    previousResponseId: lastResponseId
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, mas deve corresponder se a sessão original usou

// 2. Conecte-se à nova sala
const room = new Room();
await room.connect(session.url, session.token);

// 3. O agente agora tem contexto da sessão anterior
// Usuário: "O que eu te perguntei antes?"
// Agente: "Você perguntou sobre a capital da França..."

Continuando a partir de uma conversa

Use conversation_id para continuar uma conversa existente criada pela API de Conversas:
// Use a mesma chave de API e X-On-Behalf-Of da sessão original
const mka1 = new SDK({
  bearerAuth: `Bearer ${YOUR_API_KEY}`,
});

// 1. Obtenha um novo token com o ID da conversa
const session = await mka1.llm.speech.livekitToken({
  llm: {
    model: 'auto',
    conversation: { id: conversationId }
  }
}, { headers: { 'X-On-Behalf-Of': 'user-123' } }); // Opcional, mas deve corresponder se a sessão original usou

// 2. Conecte-se à nova sala
const room = new Room();
await room.connect(session.url, session.token);

// 3. O agente agora tem contexto de todo o histórico da conversa
Ao continuar uma conversa, a chave de API e o header X-On-Behalf-Of (se usado) devem corresponder à sessão original. O contexto é restrito à identidade autenticada.

Lidando com desconexão

Os tokens expiram após 5 minutos. Se precisar de sessões mais longas, implemente lógica de reconexão:
room.on(RoomEvent.Disconnected, async () => {
  console.log('Desconectado da sala');

  // Obtenha um novo token (continuando da última resposta)
  const newSession = await mka1.llm.speech.livekitToken({
    llm: {
      model: 'auto',
      previousResponseId: savedResponseId
    }
  });

  // Reconecte
  await room.connect(newSession.url, newSession.token);
});

Exemplo completo

Aqui está um exemplo completo juntando tudo:
import { Room, RoomEvent, Track, createLocalAudioTrack } from 'livekit-client';
import { SDK } from '@meetkai/mka1';

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

  // Obtenha credenciais da sala
  const session = await mka1.llm.speech.livekitToken({ llm: { model } });

  // Crie e conecte à sala
  const room = new Room();

  let lastResponseId: string | undefined;

  // Lide com saída de áudio do agente
  room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
    if (track.kind === Track.Kind.Audio) {
      const audio = track.attach();
      document.body.appendChild(audio);
    }
  });

  // Lide com transcrições
  room.on(RoomEvent.TranscriptionReceived, (segments) => {
    for (const segment of segments) {
      console.log('Agente:', segment.text);
    }
  });

  // Lide com metadados da resposta
  room.on(RoomEvent.DataReceived, (payload, participant) => {
    const data = JSON.parse(new TextDecoder().decode(payload));
    if (data.response_id) {
      lastResponseId = data.response_id;
    }
  });

  // Conecte-se à sala
  await room.connect(session.url, session.token);

  // Capture e publique o microfone
  const audioTrack = await createLocalAudioTrack({
    echoCancellation: true,
    noiseSuppression: true
  });
  await room.localParticipant.publishTrack(audioTrack);

  // O agente irá cumprimentá-lo automaticamente
  // Comece a falar para interagir!

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

Tratamento de erros

Erros do endpoint de token

Estes são retornados como respostas HTTP ao solicitar um token de sala:
ErroCausaSolução
400 Bad RequestParâmetro obrigatório llm.model ausenteInclua model dentro do objeto llm
400 Bad RequestAmbos previous_response_id e conversation especificadosUse apenas um, não ambos
401 UnauthorizedChave de API inválida ou ausenteVerifique se sua chave de API é válida

Erros durante a sessão

Durante uma sessão de voz ativa, o agente publica erros via canal de dados do LiveKit. Escute-os junto com os metadados da resposta:
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 pode conter informações adicionais de depuração
  }

  if (data.response_id) {
    lastResponseId = data.response_id;
  }
});
A estrutura do payload de erro:
{
  "error": {
    "code": "rate_limited",
    "message": "HTTP 429",
    "service": "llm",
    "details": "..."
  }
}
CampoDescrição
codeCódigo do erro (veja tabela abaixo)
messageDescrição curta do erro
serviceQual parte do pipeline falhou: llm, stt ou tts
detailsContexto adicional para depuração (opcional)
Códigos de erro:
CódigoServiçoCausa
invalid_sessionCampos de metadados obrigatórios ausentes (sub, llm)
auth_errorFalha ao descriptografar credenciais do token da sala
session_errorFalha ao iniciar a sessão de voz
invalid_requestllmRequisição inválida para a API de Respostas (HTTP 400)
auth_errorllmChave de API inválida (HTTP 401)
access_deniedllmPermissões insuficientes (HTTP 403)
rate_limitedllmLimite de requisições excedido (HTTP 429)
service_errorllmErro interno do servidor (HTTP 500)
service_unavailablellmServiço upstream indisponível (HTTP 502/503)
timeoutllmTempo limite da requisição (HTTP 504)
connection_errorllmFalha ao conectar à API de Respostas
transcription_errorsttFalha no processamento de fala para texto
speech_errorttsFalha na síntese de texto para fala

Erros de conexão

ProblemaCausaSolução
Tempo limite de conexãoProblemas de rede ou token inválidoObtenha um novo token e tente novamente
Token expiradoSessão excedeu 5 minutosObtenha um novo token com previous_response_id para continuar

Próximos passos