first commit
This commit is contained in:
728
docs/api-overview.md
Normal file
728
docs/api-overview.md
Normal file
@@ -0,0 +1,728 @@
|
||||
# API Overview
|
||||
|
||||
## Status
|
||||
|
||||
Documento vivo. Descreve o estado atual da API e sera atualizado ao longo do refactor.
|
||||
|
||||
## O que esta API faz
|
||||
|
||||
Esta API implementa um fluxo de voz-to-voz para TIA:
|
||||
- recebe conexao websocket do cliente
|
||||
- recebe audio PCM do cliente
|
||||
- encaminha audio para o agent via LiveKit
|
||||
- transcreve, processa a pipeline de negocio e sintetiza resposta
|
||||
- devolve audio para o cliente
|
||||
- finaliza a chamada e exporta resultado
|
||||
|
||||
## Componentes principais
|
||||
|
||||
### Bridge
|
||||
|
||||
Arquivo principal:
|
||||
- `app/ws_gateway/main.py`
|
||||
- `app/ws_gateway/voice_client.html`
|
||||
|
||||
Responsabilidades:
|
||||
- aceitar conexao em `/ws/agent`
|
||||
- receber `start`
|
||||
- montar contexto da chamada
|
||||
- despachar agent para uma room LiveKit
|
||||
- mandar audio do cliente para LiveKit
|
||||
- devolver audio do agent para o cliente
|
||||
- encerrar websocket ao receber `DONE`
|
||||
- servir um cliente web de teste em `/voice-client`
|
||||
|
||||
### Agent
|
||||
|
||||
Arquivos principais:
|
||||
- `app/livekit/main.py`
|
||||
- `app/livekit/runtime/call_runtime.py`
|
||||
- `app/livekit/runtime/state.py`
|
||||
- `app/livekit/runtime/commands.py`
|
||||
- `app/livekit/runtime/command_executor.py`
|
||||
- `app/livekit/runtime/scheduler.py`
|
||||
- `app/livekit/policies/`
|
||||
- `app/livekit/adapters/`
|
||||
|
||||
Responsabilidades:
|
||||
- entrar na room do LiveKit
|
||||
- configurar STT, VAD e TTS
|
||||
- receber transcricao final do usuario
|
||||
- chamar o backend de IA configurado
|
||||
- vocalizar resposta
|
||||
- tratar interrupcao, idle nudge e finalizacao
|
||||
|
||||
Organizacao atual:
|
||||
- `main.py` faz o wiring do agent e das dependencias
|
||||
- `CallRuntime` coordena o ciclo de vida da chamada
|
||||
- `CallState` concentra o estado mutavel da sessao
|
||||
- `commands.py` define os comandos internos para side effects
|
||||
- `RuntimeCommandExecutor` executa bridge, export, speech, pipeline e start da sessao
|
||||
- `TimerScheduler` concentra os timers nomeados do runtime
|
||||
- `InterruptPolicy`, `IdlePolicy` e `FinalizationPolicy` concentram regras operacionais da chamada
|
||||
- adapters encapsulam acesso ao backend de IA, speech, bridge e export
|
||||
|
||||
### Pipeline de negocio
|
||||
|
||||
Backends disponiveis no runtime websocket:
|
||||
- `remote_ws`:
|
||||
- `app/livekit/adapters/remote_agent_ws_adapter.py`
|
||||
- `remote_sse`:
|
||||
- `app/livekit/adapters/remote_agent_sse_adapter.py`
|
||||
|
||||
Responsabilidades:
|
||||
- preparar dados de atendimento
|
||||
- controlar os estagios da chamada ou delegar esse controle ao agent remoto
|
||||
- gerar a resposta textual por etapa
|
||||
- registrar metadados e finalizar a conversa
|
||||
|
||||
Selecao atual:
|
||||
- `AGENT_BACKEND=remote_ws` envia cada turno transcrito para `REMOTE_AGENT_WS_URL`
|
||||
- `AGENT_BACKEND=remote_sse` usa contratos especificos por agente:
|
||||
- `conta` usa `GET /agent/sse` para inicializar a sessao e `POST /agent/sse` para executar cada acao
|
||||
- `oferta` usa `POST /agent/execute` por turno e consome os eventos SSE `schedule_message`, `message` e `done`
|
||||
- `AGENT_BACKEND=remote_ws_fake` usa um fake interno no proprio processo, sem abrir websocket local
|
||||
- o fluxo do runtime local continua o mesmo: STT produz texto, o backend de IA devolve texto e o TTS vocaliza
|
||||
- o roteamento entre agentes acontece pelo campo `data.agent`
|
||||
- quando `AGENT_BACKEND=remote_ws`, o adapter escolhe a URL por `agent` se existirem:
|
||||
- `REMOTE_AGENT_WS_URL_CONTA`
|
||||
- `REMOTE_AGENT_WS_URL_OFERTA`
|
||||
- `REMOTE_AGENT_WS_URL_COBRANCA` ou `REMOTE_AGENT_WS_URL_COBRA`
|
||||
- quando `AGENT_BACKEND=remote_sse`, o adapter exige a URL especifica do `agent`, sem fallback generico:
|
||||
- `REMOTE_AGENT_SSE_URL_CONTA`
|
||||
- `REMOTE_AGENT_SSE_URL_OFERTA`
|
||||
- `REMOTE_AGENT_SSE_URL_COBRANCA` ou `REMOTE_AGENT_SSE_URL_COBRA`
|
||||
|
||||
Contrato remoto atual por turno:
|
||||
- `timestamp`
|
||||
- `agent`
|
||||
- `RouterCallKeyDay`
|
||||
- `RouterCallKey`
|
||||
- `ANI`
|
||||
- `GSM`
|
||||
- `callIdGed`
|
||||
- `ID_FATURA` somente para `agent=conta`
|
||||
- `text`
|
||||
- `protocol`
|
||||
- `stage`
|
||||
|
||||
Contrato especifico atual de `conta`:
|
||||
- request:
|
||||
- na abertura da sessao, o transporte envia query string com `ani`, `channelId` e `uraCallId`
|
||||
- `action: "chat"`
|
||||
- `payload.message`
|
||||
- `payload.message_id` no transporte SSE de `conta`, com o mesmo UUID enviado na query string
|
||||
- `payload.channel` no transporte websocket
|
||||
- `payload.interruption` e `payload.events` quando existirem
|
||||
- response:
|
||||
- `type: "ready"` para a primeira fala ou fala que abre uma janela de resposta do cliente
|
||||
- a fala de `ready` nao e interrompivel; se o cliente falar enquanto o audio do `ready` ainda estiver tocando, a transcricao e descartada e registrada em log
|
||||
- apos o fim do audio de `ready`, o runtime mantem uma janela protegida de 750ms para absorver atraso de playback do cliente; fala iniciada nessa janela tambem e descartada
|
||||
- depois dessa janela protegida, o runtime passa a esperar resposta do cliente
|
||||
- `type: "result"`
|
||||
- `action: "chat"`
|
||||
- `result.type: "final"` para respostas intermediarias
|
||||
- `result.content` como texto para TTS
|
||||
- `type: "feedback"` ou `result.type: "feedback"` para mensagens de acompanhamento enquanto o backend continua processando o turno atual
|
||||
- a fala de `feedback` nao e interrompivel, nao abre novo turno, nao espera resposta do cliente e nao arma timeout de silencio do cliente
|
||||
- se o cliente falar durante `feedback`, o runtime descarta a transcricao, registra a tentativa em log e continua aguardando a resposta final do backend
|
||||
- `feedback` nao dispara `metadata.wait_retry_messages`, como mensagens do tipo "Voce esta ai?"
|
||||
- para finalizacoes esperadas, o runtime aguarda uma janela curta de silencio estavel do cliente, fala o `result.content` e depois envia `stop`; se uma nova transcricao final chegar durante a espera, a finalizacao anterior e descartada em favor do novo turno
|
||||
- falas de finalizacao esperada (`resolvido`, `nao_resolvido`, `erro_no_match` etc.) nao sao interrompiveis; qualquer fala do cliente durante a finalizacao e descartada
|
||||
- nesses casos o runtime nao chama finalizacao remota adicional (`end`/`end_service_once`)
|
||||
- quando o `ready` vier com `metadata.wait_timeout_seconds`, o runtime aguarda esse tempo apos falar a mensagem de `ready`
|
||||
- se tambem vier `metadata.wait_retry_messages`, o runtime fala cada item do array a cada novo estouro de `wait_timeout_seconds`; depois do ultimo item, aguarda mais um intervalo igual e envia
|
||||
`stop_silencio_longo` com `reason: "no_user_response"`
|
||||
- sem `metadata.wait_retry_messages`, o comportamento continua sendo encerrar direto no primeiro estouro de `wait_timeout_seconds`
|
||||
- enquanto aguarda processamento depois do fim de fala do usuario, o runtime toca somente o audio local longo de conforto; no padrao atual, usa intervalo de 12s e no maximo 6 vezes
|
||||
- se a resposta do backend remoto nao chegar em 300s, o runtime encerra com `stop_agent_backend_unavailable`
|
||||
- se o participante do agent LiveKit desconectar sem `DONE`, o bridge tenta redispatch na mesma room; se o agent nao voltar, encerra com `stop_agent_runtime_unavailable`
|
||||
|
||||
Mapeamento de finalizacoes esperadas de `conta`:
|
||||
- `result.type: "resolvido"` -> `stop_resolvido_e_finalizado`
|
||||
- `result.type: "nao_resolvido"` -> `stop_nao_resolvido`
|
||||
- `result.type: "resolvido_outros_assuntos"` -> `stop_outro_assunto`
|
||||
- `result.type: "outros_assuntos"` -> `stop_outro_assunto`
|
||||
- `result.type: "erro_falha_sistema"` -> `stop_falha_sistema`
|
||||
- `result.type: "erro_no_match"` -> `stop_no_match`
|
||||
|
||||
Contrato SSE atual de `conta`:
|
||||
- o runtime guarda o `session_id` retornado pelo backend nos eventos `ready`
|
||||
- abre `GET /agent/sse?msisdn=...&invoice_id=...&ani=...&protocol_id=...&session_id=...&message_id=...&channelId=ura&uraCallId=...` no `prepare`
|
||||
- consome o stream de inicializacao ate `ready` e ate o termino do prefetch (`prefetch_done`, `prefetch_skipped` ou `prefetch_failed`) ou fechamento da resposta
|
||||
- envia cada turno com `POST /agent/sse?session_id=...&ani=...&protocol_id=...&message_id=...&channelId=ura&uraCallId=...`
|
||||
- `message_id` e um UUID gerado por turno e independente de `session_id`; nos turnos de acao, `session_id` continua sendo o identificador de sessao retornado pelo backend de contas
|
||||
- body do `POST`:
|
||||
- `action: "chat"`
|
||||
- `payload.message`
|
||||
- `payload.message_id` com o mesmo UUID do parametro `message_id` da query string
|
||||
- `payload.interruption` e `payload.events` quando existirem
|
||||
- a resposta do proprio `POST` e `text/event-stream`; o runtime consome `ready`, `progress`, `result` e `error` ate receber o resultado da acao
|
||||
|
||||
Contrato SSE atual de `oferta`:
|
||||
- `prepare` nao abre stream remoto; o primeiro turno do agente chama `POST /agent/execute`
|
||||
- o turno inicial envia `message: "inicio_atendimento"` para o backend remoto produzir a primeira fala
|
||||
- cada turno usa body:
|
||||
- `messageId` gerado por turno como UUID
|
||||
- `message` com `inicio_atendimento` no primeiro turno ou a transcricao do cliente nos demais
|
||||
- `context.protocolNumber` e `context.protocolo` vindos de `data.protocolo`
|
||||
- `context.gsm` vindo de `data.gsm`
|
||||
- `context.uraId` vindo de `data.callIdGed`
|
||||
- `context.callIdGed`, `context.ani`, `context.routerCallKey`, `context.routerCallKeyDay`, `context.agent` e `context.assetId` quando existirem
|
||||
- `context.sessionId` e enviado a partir de `data.session_id`/`data.sessionId` recebido no `start`; protocolo, `callIdGed` e `routerCallKey` nao sao usados como fallback de `sessionId`
|
||||
- headers enviados: `Accept: text/event-stream`, `Content-Type: application/json` e `Channel-id` vindo de `channelId` ou `ura`
|
||||
- eventos recebidos:
|
||||
- `schedule_message`: fala imediatamente `scheduledMessage.message`
|
||||
- `message`: fala `response`
|
||||
- `done`: encerra o stream do turno; a chamada so e finalizada quando o status recebido indicar encerramento terminal
|
||||
- de/para do `done.additionalInformations.service_status`:
|
||||
- `RESOLVED` -> `stop_resolvido_e_finalizado`
|
||||
- `UNRESOLVED` ou ausente -> `stop_nao_resolvido`
|
||||
- `RESOLVED_WITH_NEW_REQUEST` -> nao envia `stop`; a conversa permanece aberta para o novo assunto
|
||||
- `done.status=transferred` e reconhecido, mas ainda nao orquestra transferencia; nesta versao encerra como nao resolvido e preserva metadados de handover para evolucao futura
|
||||
|
||||
Endpoint dev fornecido para `oferta`:
|
||||
- host: `https://agt-ai-atendimento-ofertas-dev.internal.timbrasil.com.br`
|
||||
- execute: `https://agt-ai-atendimento-ofertas-dev.internal.timbrasil.com.br/agent/execute`
|
||||
- health: nao configurado por enquanto; a readiness nao deve derivar nem chamar `/health` para oferta ate essa rota ser fornecida
|
||||
- enquanto o certificado interno nao estiver confiavel no ambiente local, `REMOTE_AGENT_SSE_TLS_VERIFY_OFERTA=0` permite testar o fluxo ignorando a validacao TLS do `httpx`
|
||||
- pod de referencia: `tim-ai-atend-agnt-sales-65764fcf8d-xpzmb`
|
||||
- IP de referencia: `http://10.153.35.23`
|
||||
- portas: `80:31332/TCP`, `443:30635/TCP`
|
||||
|
||||
Origem desses campos:
|
||||
- o bridge extrai esses valores do `data` recebido em `WS /ws/agent`
|
||||
- o agent local apenas reaproveita esse contexto para chamar o websocket remoto
|
||||
- `protocol`/`protocolo` e identificador de negocio; `session_id` e identificador explicito de sessao recebido no `start`. O runtime nao preenche `session_id` com protocolo, `callIdGed` ou `routerCallKey`.
|
||||
|
||||
Configuracao opcional por chamada:
|
||||
- o cliente pode enviar `callConfig` no `start`, mas esse bloco e opcional
|
||||
- objetivo atual: testes, homologacao e overrides tecnicos por sessao
|
||||
- `callConfig.agentBackend` faz override do backend websocket para aquela chamada
|
||||
- `callConfig.agentBackend` tambem pode selecionar o transporte SSE para a sessao
|
||||
- `callConfig.stt` pode ajustar `provider`, `initialPrompt`, `configOverride` e `minProbSingleWord`
|
||||
- `callConfig.tts` pode ajustar `provider`, `voiceId` e `modelId`
|
||||
- valores aceitos hoje em `callConfig.agentBackend`:
|
||||
- `remote_ws`
|
||||
- `remote_sse`
|
||||
- `remote_ws_fake`
|
||||
- hoje os providers suportados no agent local sao:
|
||||
- STT: `internal_http` (`Sofya Batch` no cliente de teste), `fake`
|
||||
- TTS: `elevenlabs`, `azure`, `xai`, `fake`
|
||||
|
||||
## Endpoints atuais
|
||||
|
||||
### `GET /health`
|
||||
|
||||
Retorna status simples de saude do bridge.
|
||||
|
||||
### `GET /health/resources`
|
||||
|
||||
Retorna o deep health do bridge com validacao dos recursos usados pelo `WS /ws/agent`.
|
||||
|
||||
Comportamento:
|
||||
- `200` quando os checks obrigatorios estao saudaveis
|
||||
- `503` quando algum recurso obrigatorio falha
|
||||
- inclui `active_connections`, `max_connections`, `cached`, `failed_resources` e `checks`
|
||||
- os checks atuais cobrem:
|
||||
- `agent_runtime`
|
||||
- `agent_backend`
|
||||
- `stt`
|
||||
- `tts`
|
||||
|
||||
### `GET /health/services`
|
||||
|
||||
Retorna o health consolidado dos servicos do TIA no formato consumido pela esteira de operacao.
|
||||
|
||||
Comportamento:
|
||||
- `200` quando todos os servicos monitorados estao saudaveis
|
||||
- `503` quando algum servico monitorado falha
|
||||
- `status` no corpo retorna `ok` ou `fail`
|
||||
- `htp_cod_status` preserva o status HTTP retornado pelo health do servico quando houver resposta HTTP
|
||||
- `hhtp_cod_desc` preserva a descricao retornada pelo health quando houver; se nao houver descricao no corpo, usa a reason phrase HTTP
|
||||
- ignora `AGENT_BACKEND=remote_ws_fake` e usa as rotas reais configuradas nos envs dos servicos
|
||||
- `checks` cobre apenas os servicos atualmente monitorados:
|
||||
- `agent_runtime`
|
||||
- `agent_backend.contas`: `REMOTE_AGENT_HEALTH_URL_CONTA`, `REMOTE_AGENT_HEALTH_URL_CONTAS` ou `REMOTE_AGENT_HEALTH_URL`
|
||||
- `agent_backend.oferta`: `REMOTE_AGENT_HEALTH_URL_OFERTA`, `REMOTE_AGENT_HEALTH_URL_OFERTAS` ou health derivado de `REMOTE_AGENT_SSE_URL_OFERTA`
|
||||
- `stt.sofya`: `STT_HEALTH_URL` ou health derivado de `STT_URL`
|
||||
- `tts.xAI`: provider xAI com `XAI_WEBSOCKET_URL`/`XAI_API_KEY`
|
||||
|
||||
Para checks sem resposta HTTP, como falha de conexao ou probe por WebSocket, a rota usa fallback `200`/`500` e a mensagem interna do erro ou sucesso.
|
||||
|
||||
Formato:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"checks": {
|
||||
"agent_runtime": {
|
||||
"htp_cod_status": 200,
|
||||
"hhtp_cod_desc": "SUCCESS"
|
||||
},
|
||||
"agent_backend": {
|
||||
"contas": {
|
||||
"htp_cod_status": 200,
|
||||
"hhtp_cod_desc": "SUCCESS"
|
||||
},
|
||||
"oferta": {
|
||||
"htp_cod_status": 200,
|
||||
"hhtp_cod_desc": "SUCCESS"
|
||||
}
|
||||
},
|
||||
"stt": {
|
||||
"sofya": {
|
||||
"htp_cod_status": 200,
|
||||
"hhtp_cod_desc": "SUCCESS"
|
||||
}
|
||||
},
|
||||
"tts": {
|
||||
"xAI": {
|
||||
"htp_cod_status": 200,
|
||||
"hhtp_cod_desc": "SUCCESS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /voice-client`
|
||||
|
||||
Cliente web de teste para capturar microfone, enviar audio para `WS /ws/agent`,
|
||||
reproduzir o audio do agent e configurar `STT`, `TTS` e `AGENT` por chamada.
|
||||
|
||||
### `WS /fake-agent/ws`
|
||||
|
||||
Websocket fake para homologacao local do backend remoto.
|
||||
|
||||
Uso esperado:
|
||||
- usar apenas para homologacao manual do contrato websocket fake
|
||||
- manter `agent=conta|oferta|cobranca`
|
||||
- deixar um cliente websocket externo chamar o fake diretamente quando precisar testar esse endpoint
|
||||
|
||||
Comportamento:
|
||||
- suporta o contrato `conta` com `action/payload`
|
||||
- suporta o contrato generico com `text/stage`
|
||||
- responde com progressao simples de stages
|
||||
- encerra quando recebe textos como `encerrar`, `obrigado` ou `tchau`
|
||||
|
||||
### `WS /ws/agent`
|
||||
|
||||
Fluxo principal de voz:
|
||||
1. cliente conecta
|
||||
2. envia mensagem `start`
|
||||
3. bridge valida capacidade e readiness dos recursos obrigatorios
|
||||
4. recebe `ready` ou `stop`
|
||||
5. se receber `ready`, envia audio binario
|
||||
6. recebe audio binario de resposta
|
||||
7. recebe `stop` ao fim da chamada ou em falha terminal
|
||||
|
||||
Contrato de inicio da chamada `type=start`:
|
||||
- a primeira mensagem de inicio deve ser um JSON textual com `type: "start"`; opcionalmente, `transferencia_session_id` pode chegar antes dela para informar somente o `session_id`
|
||||
- os campos de negocio devem ser enviados em `data`
|
||||
- chaves canonicas obrigatorias em `data`:
|
||||
- `agent`
|
||||
- `ani`
|
||||
- `gsm`
|
||||
- `session_id`
|
||||
- `routerCallKey`
|
||||
- `routerCallKeyDay`
|
||||
- `callIdGed`
|
||||
- chaves obrigatorias por agente:
|
||||
- `agentData.idFatura` quando `agent=conta`
|
||||
- `protocolo` quando `agent=oferta`
|
||||
- valores canonicos recomendados para `agent`:
|
||||
- `conta`
|
||||
- `oferta`
|
||||
- `cobranca`
|
||||
|
||||
Exemplo recomendado de `start` para `conta`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "start",
|
||||
"data": {
|
||||
"agent": "conta",
|
||||
"ani": "5511999990000",
|
||||
"gsm": "5511999990000",
|
||||
"session_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"routerCallKeyDay": "20260409",
|
||||
"routerCallKey": "RCK-001",
|
||||
"callIdGed": "GED-123456",
|
||||
"agentData": {
|
||||
"idFatura": "FAT-123"
|
||||
}
|
||||
},
|
||||
"audioFormat": {
|
||||
"encoding": "linear16",
|
||||
"sampleRateHz": 16000,
|
||||
"channels": 1
|
||||
},
|
||||
"callConfig": {
|
||||
"agentBackend": "remote_ws"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Exemplo recomendado de `start` para `oferta`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "start",
|
||||
"data": {
|
||||
"agent": "oferta",
|
||||
"ani": "5511999990000",
|
||||
"gsm": "5511999990000",
|
||||
"session_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"routerCallKeyDay": "20260409",
|
||||
"routerCallKey": "RCK-001",
|
||||
"callIdGed": "GED-123456",
|
||||
"protocolo": "PRT-20260409-0001"
|
||||
},
|
||||
"audioFormat": {
|
||||
"encoding": "linear16",
|
||||
"sampleRateHz": 16000,
|
||||
"channels": 1
|
||||
},
|
||||
"callConfig": {
|
||||
"agentBackend": "remote_sse"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Mensagem opcional aceita antes do `start` em cenarios de transferencia:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "transferencia_session_id",
|
||||
"data": {
|
||||
"session_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se essa mensagem chegar antes do `start`, o bridge usa esse valor apenas para preencher `data.session_id` quando o `start` ainda nao trouxer o campo. Em producao, o caminho recomendado e enviar `session_id` diretamente dentro de `data` no `start`.
|
||||
|
||||
Contrato de audio:
|
||||
- apos o `ready`, o cliente deve enviar audio binario bruto em `PCM16/LINEAR16`, `16000 Hz`, `1 canal`
|
||||
- o `ready` informa os parametros operacionais atuais do bridge:
|
||||
- `session_id`: eco do `data.session_id` aceito para a chamada
|
||||
- `sample_rate: 16000`
|
||||
- `channels: 1`
|
||||
- `frame_ms: 20`
|
||||
- `bytes_per_frame: 640`
|
||||
- no contrato atual, `ready` significa que o bridge esta pronto para receber os bytes de audio do cliente
|
||||
- quando `agent_starts_conversation` esta habilitado, o agent mantem a entrada do usuario desativada no `RoomIO` desde antes do `StartSession` ate o fim da primeira mensagem; esse gate impede que fala ou backlog vindo da URA alcance VAD/STT durante o setup e a saudacao
|
||||
- o gate e liberado no fim do primeiro turno do agent e tambem em falha do pipeline, finalizacao ou timeout de setup; chamadas em que o usuario inicia a conversa nao usam esse bloqueio
|
||||
- `audioFormat` no `start` e opcional e hoje funciona como campo informativo/reservado
|
||||
- enviar outro codec, sample rate ou numero de canais nesse campo nao reconfigura o bridge atualmente
|
||||
- se houver necessidade de outro formato, homologar com a equipe de desenvolvimento antes da integracao
|
||||
|
||||
Mensagens devolvidas pelo servidor:
|
||||
- `ready` quando a sessao foi aceita e o bridge esta pronto para receber audio
|
||||
- audio binario PCM16 durante a resposta do agent
|
||||
- `stop` como mensagem terminal em qualquer encerramento do `WS /ws/agent`
|
||||
|
||||
Recuperacao do participante LiveKit do agent:
|
||||
- quando o participant identificado como agent desconecta e a chamada ainda nao terminou, o bridge faz redispatch do mesmo `AGENT_NAME` na mesma room
|
||||
- quando o novo participant entra, o bridge reenvia o controle `client_audio_enabled` e passa a consumir o audio desse novo participant
|
||||
- se o redispatch nao trouxer um novo agent dentro do timeout configurado, o bridge envia `stop_agent_runtime_unavailable` com `reason: "agent_disconnected"`
|
||||
|
||||
Contrato de `stop`:
|
||||
- toda mensagem terminal em `WS /ws/agent` usa:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "stop",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
- bloqueio antes do `ready`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "stop",
|
||||
"data": {
|
||||
"status": "stop_stt_unavailable",
|
||||
"reason": "resource_unhealthy",
|
||||
"resource": "stt",
|
||||
"failed_resources": ["stt"],
|
||||
"phase": "pre_ready"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- falha de recurso durante a sessao:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "stop",
|
||||
"data": {
|
||||
"status": "stop_agent_backend_unavailable",
|
||||
"reason": "resource_unhealthy",
|
||||
"resource": "agent_backend",
|
||||
"failed_resources": ["agent_backend"],
|
||||
"phase": "in_session"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- fim normal da chamada:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "stop",
|
||||
"data": {
|
||||
"status": "stop_resolvido_e_finalizado",
|
||||
"reason": "stage_done",
|
||||
"phase": "in_session"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- falha terminal durante a sessao:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "stop",
|
||||
"data": {
|
||||
"status": "stop_bridge_failed",
|
||||
"reason": "bridge_failed",
|
||||
"resource": "bridge",
|
||||
"phase": "in_session"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Status terminais atualmente usados em `WS /ws/agent`:
|
||||
- `stop_capacity_tia`
|
||||
- `stop_agent_runtime_unavailable`
|
||||
- `stop_agent_backend_unavailable`
|
||||
- `stop_stt_unavailable`
|
||||
- `stop_tts_unavailable`
|
||||
- `stop_resolvido_e_finalizado`
|
||||
- `stop_nao_resolvido`
|
||||
- `stop_falha_sistema`
|
||||
- `stop_no_match`
|
||||
- `stop_outro_assunto`
|
||||
- `stop_silencio_longo`
|
||||
- `stop_bridge_failed`
|
||||
|
||||
Regra de interpretacao:
|
||||
- `phase: "pre_ready"` indica bloqueio antes de a sessao aceitar audio
|
||||
- `phase: "in_session"` indica falha terminal ou encerramento depois do `ready`
|
||||
- os status de recurso podem aparecer nas duas fases, dependendo de quando a indisponibilidade foi detectada
|
||||
|
||||
Campos opcionais de testes e homologacao:
|
||||
- `audioFormat` pode ser enviado no `start`, mas hoje nao altera o pipeline de audio
|
||||
- `callConfig` e opcional e existe para testes, smoke test local e homologacao tecnica
|
||||
- para integracao produtiva com cliente externo, o contrato pode omitir `callConfig`
|
||||
|
||||
Configuracao de chamada para teste de carga com STT Sofya, agente fake e TTS xAI:
|
||||
|
||||
```json
|
||||
{
|
||||
"debugEvents": true,
|
||||
"callConfig": {
|
||||
"agentBackend": "remote_ws_fake",
|
||||
"agentFake": {
|
||||
"delayMs": 2500,
|
||||
"responses": "Primeira resposta simulada com tamanho intermediario;Segunda resposta simulada com tamanho intermediario;Resposta final encerrando o atendimento simulado"
|
||||
},
|
||||
"stt": {
|
||||
"provider": "internal_http",
|
||||
"disableVosk": true
|
||||
},
|
||||
"tts": {
|
||||
"provider": "xai"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Regras desse modo:
|
||||
- `agentFake.responses` contem de 2 a 10 frases separadas por `;`; espacos laterais sao removidos e cada frase deve ter de 40 a 180 caracteres
|
||||
- `agentFake.delayMs` e aplicado antes de cada resposta, usa `2500` por padrao e aceita valores de `0` a `180000`
|
||||
- a saudacao inicial continua sendo o `intro` normal; cada fala posterior reconhecida pelo STT consome uma resposta fake, sem usar o texto transcrito para escolher a resposta
|
||||
- respostas anteriores as duas ultimas usam `ARGUMENTATION`, a penultima usa `FORMALIZATION` e a ultima usa `DONE`; chamadas posteriores ao fim recebem novamente o mesmo resultado terminal
|
||||
- a sequencia e isolada por sessao
|
||||
- `debugEvents=true` publica pelo websocket somente eventos tecnicos e valores agregados de fala, STT Sofya, TTS xAI e sheds do Bridge; o conteudo integral da conversa nao e incluido nas metricas
|
||||
- o endpoint nao possui uma autorizacao adicional especifica para o fake: qualquer cliente ja autorizado a abrir `/ws/agent` pode selecionar `remote_ws_fake` por `callConfig`
|
||||
|
||||
### `WS /ws/text`
|
||||
|
||||
Fluxo de texto sem audio para testes e integracao basica.
|
||||
|
||||
### `WS /ws/text_stream`
|
||||
|
||||
Fluxo textual com resposta em streaming.
|
||||
|
||||
## Fluxo ponta a ponta atual
|
||||
|
||||
1. Cliente abre websocket em `/ws/agent`
|
||||
2. Bridge recebe `start` e faz parse do contexto da chamada
|
||||
3. Bridge valida capacidade e readiness dos recursos
|
||||
4. Bridge cria room/token e envia `ready`
|
||||
5. Cliente envia audio para o bridge
|
||||
6. Bridge publica audio no LiveKit
|
||||
7. Agent recebe audio, STT produz texto
|
||||
8. Agent chama o backend de IA configurado
|
||||
9. Agent usa TTS para vocalizar resposta
|
||||
10. Bridge devolve audio ao cliente
|
||||
11. Agent sinaliza `DONE` ou ocorre erro terminal
|
||||
12. Bridge envia `stop` e fecha a chamada
|
||||
|
||||
## Dependencias externas relevantes
|
||||
|
||||
- LiveKit
|
||||
- STT interno HTTP
|
||||
- Vosk
|
||||
- ElevenLabs
|
||||
- modelo LLM da pipeline
|
||||
- API de fidelizacao
|
||||
|
||||
## Timeline de chamada
|
||||
|
||||
O projeto agora gera uma timeline estruturada por chamada em formato `jsonl`.
|
||||
|
||||
Configuracao:
|
||||
- `CALL_TIMELINE_ENABLED=1` ativa a escrita da timeline
|
||||
- `CALL_TIMELINE_CONSOLE=1` replica os eventos tambem no stdout
|
||||
- `CALL_TIMELINE_DIR=./timeline` define o diretorio dos arquivos
|
||||
- `CALL_TIMELINE_QUEUE_MAX=10000` limita eventos pendentes para escrita assincrona
|
||||
- `CALL_LOG_QUEUE_MAX=20000` limita registros pendentes dos arquivos por chamada
|
||||
- `ASYNC_IO_WARNING_INTERVAL_S=60` limita a frequencia dos avisos de descarte/erro
|
||||
|
||||
A timeline e o arquivo de log por chamada sao gravados por threads de fundo para
|
||||
nao executar I/O de disco no event loop de audio. Quando uma fila atinge o limite,
|
||||
o evento e descartado em vez de bloquear o audio e um warning rate-limited registra
|
||||
o total acumulado. Os limites aceitos ficam entre `1` e `1000000`.
|
||||
|
||||
Os spans estruturados usam `BatchSpanProcessor`: `span.end()` apenas enfileira o
|
||||
span, enquanto o envio OTLP acontece em lote fora da thread chamadora. O provider
|
||||
faz flush e shutdown no encerramento normal do processo.
|
||||
|
||||
Fake remoto:
|
||||
- `remote_ws_fake` usa um fake interno em memoria e nao depende de `REMOTE_AGENT_WS_FAKE_URL`
|
||||
- o endpoint `ws://127.0.0.1:8000/fake-agent/ws` continua disponivel apenas para testes manuais do contrato websocket
|
||||
|
||||
Logs de websocket remoto:
|
||||
- o adapter `remote_ws` agora registra no stdout eventos `REMOTE_AGENT_WS_CONNECT_OPEN`, `REMOTE_AGENT_WS_CONNECT_OK`, `REMOTE_AGENT_WS_REQUEST`, `REMOTE_AGENT_WS_RESPONSE` e falhas `*_FAIL`
|
||||
- os logs incluem `instance` (hostname/pod), `agent`, `url`, `host`, `stage`, `protocol` e metadados do payload para facilitar comparar pods com erro de DNS/host
|
||||
|
||||
Mock de encerramento apos primeiro audio:
|
||||
- `MOCK_STOP_AFTER_FIRST_AUDIO_ENABLED=1` faz o bridge enviar o `stop` terminal configurado para o `reason` depois que o primeiro audio real do agente for entregue ao cliente e a saida ficar em silencio pelo intervalo configurado
|
||||
- `MOCK_STOP_AFTER_FIRST_AUDIO_SILENCE_S=0.35` controla quanto tempo de silencio o bridge espera antes de disparar o `stop`
|
||||
- `MOCK_STOP_AFTER_FIRST_AUDIO_REASON=stage_done` define o `reason` exato enviado no `stop`
|
||||
|
||||
Status terminais configuraveis por env:
|
||||
- `FINAL_STOP_STATUS_RESOLVED=stop_resolvido_e_finalizado`
|
||||
- `FINAL_STOP_STATUS_UNRESOLVED=stop_nao_resolvido`
|
||||
- `FINAL_STOP_STATUS_OTHER_SUBJECT=stop_outro_assunto`
|
||||
- `FINAL_STOP_STATUS_LONG_SILENCE=stop_silencio_longo`
|
||||
- `FINAL_STOP_DEFAULT_KIND=resolved` define o fallback quando o `reason` nao bater em nenhum valor configurado
|
||||
- `FINAL_STOP_REASON_RESOLVED=stage_done`
|
||||
- `FINAL_STOP_REASON_UNRESOLVED=nao_resolvido`
|
||||
- `FINAL_STOP_REASON_OTHER_SUBJECT=outro_assunto`
|
||||
- `FINAL_STOP_REASON_LONG_SILENCE=no_user_response`
|
||||
- o mapeamento agora usa comparacao exata do `reason`, sem aliases
|
||||
|
||||
Espera de resposta do backend remoto:
|
||||
- `REMOTE_AGENT_INFLIGHT_WAIT_INTERVAL_S=12`
|
||||
- `REMOTE_AGENT_INFLIGHT_WAIT_SHORT_AUDIO_DIR=src/app/livekit/assets/comfort/short`
|
||||
- `REMOTE_AGENT_INFLIGHT_WAIT_LONG_AUDIO_DIR=src/app/livekit/assets/comfort/long`
|
||||
- o primeiro audio de conforto usa um WAV aleatorio da pasta `short`; a partir do segundo, usa WAVs aleatorios da pasta `long`
|
||||
- o intervalo dos audios de conforto conta a partir do fim do audio anterior
|
||||
- `REMOTE_AGENT_INFLIGHT_WAIT_TIMEOUT_S=180`
|
||||
- `REMOTE_AGENT_INFLIGHT_WAIT_MAX_NOTICES=0` (`0` mantém os confortos sem limite de quantidade até o timeout)
|
||||
- `REMOTE_AGENT_INFLIGHT_WAIT_TEXT=Um momento, ainda estou consultando para te ajudar.`
|
||||
- este timeout e tecnico: limita quanto tempo o runtime aguarda o backend remoto processar um turno
|
||||
- ele e diferente do timeout de resposta do cliente configurado por `metadata.wait_timeout_seconds`
|
||||
- mensagens `feedback`, tanto top-level quanto `result.type: "feedback"`, usam a espera tecnica do backend, mas nao contam silencio do cliente nem disparam `metadata.wait_retry_messages`
|
||||
- em um turno normal, depois que o agent termina de falar e o cliente responde, o fim de fala detectado pelo VAD pode antecipar o primeiro conforto curto enquanto o STT conclui a transcricao
|
||||
- o conforto antecipado do VAD nao e agendado enquanto o agent esta falando; se outra fala do agent ocupar o `say_lock` depois do agendamento, o conforto tambem e abortado para nao sair colado ao fim da mensagem
|
||||
- se o cliente interromper uma fala interrompivel do agent, o VAD nao enfileira conforto atras dessa fala; o novo pipeline ainda pode usar os confortos normais caso o processamento demore
|
||||
- se o cliente falar por mais de `800ms` enquanto o backend ja processa outro turno e o agent esta em silencio, o conforto curto especulativo do VAD e suprimido e a transcricao abre uma interrupcao diferida
|
||||
- cada interrupcao diferida toca `src/app/livekit/assets/comfort/interruption/01.wav`, correspondente a "Ouvi o que voce falou, um instante", e invalida a resposta anterior quando ela chegar
|
||||
- no pipeline substituto, o conforto dedicado conta como o primeiro aviso ja consumido: o audio `short` normal nao toca logo depois; se o processamento continuar, o proximo aviso permitido e `long` e respeita o intervalo configurado
|
||||
- o estado de interrupcao e rearmado antes de iniciar o pipeline substituto: cada nova fala valida durante o novo processamento repete o conforto dedicado e substitui novamente a resposta em voo, sem descartar a nova transcricao nem deixar silencio na chamada
|
||||
- falas de ate `800ms` durante processamento sao tratadas como ruido ou backchannel curto e nao abrem interrupcao diferida
|
||||
- os logs principais desse fluxo sao `pre_backend_wait_notice_skipped` (`agent_speaking` ou `backend_processing`), `deferred_interruption_accumulated`, `deferred_interruption_dispatched` e os estagios TTS `AGENT_BACKEND_WAIT`/`INTERRUPTION_COMFORT`
|
||||
|
||||
Recuperacao do participant LiveKit do agent:
|
||||
- `AGENT_RECONNECT_ENABLED=1`
|
||||
- `AGENT_RECONNECT_MAX_ATTEMPTS=1`
|
||||
- `AGENT_RECONNECT_TIMEOUT_S=10`
|
||||
|
||||
Protecao de reenvio TTS:
|
||||
- `TTS_EMPTY_FRAME_RETRY_TIMEOUT_S=3` interrompe uma tentativa de TTS sem primeiro frame de audio ou com gap entre frames no meio da fala apos o timeout e reenvia o mesmo texto uma vez
|
||||
- antes do reenvio, toca `src/app/livekit/assets/comfort/fails/tts_fail_recovery.wav`
|
||||
- quando o reenvio tem sucesso, registra um unico `envio msg` com `http_cod_status=200` e `erro_msg=TTS_regerado`
|
||||
|
||||
STT fake:
|
||||
- `STT_PROVIDER=fake` dispensa `STT_URL`
|
||||
- usa `FAKE_STT_TRANSCRIPTS` como fila de falas por turno
|
||||
- `FAKE_STT_MODE=repeat_last|cycle` controla o comportamento ao consumir a fila
|
||||
|
||||
TTS fake:
|
||||
- `TTS_PROVIDER=fake` dispensa credenciais externas
|
||||
- gera audio PCM sintetico local para smoke tests do pipeline
|
||||
|
||||
Formato:
|
||||
- um arquivo por chamada
|
||||
- nome do arquivo baseado no `room`
|
||||
- eventos do `bridge` e do `agent` entram no mesmo arquivo
|
||||
- cada linha contem:
|
||||
- `ts`
|
||||
- `t_rel_ms`
|
||||
- `component`
|
||||
- `event`
|
||||
- `protocol`
|
||||
- `room`
|
||||
- campos especificos do evento
|
||||
|
||||
Eventos relevantes:
|
||||
- `bridge`:
|
||||
- `call_start`
|
||||
- `ready_sent`
|
||||
- `dispatch_started`
|
||||
- `livekit_room_connected`
|
||||
- `agent_join`
|
||||
- `client_audio_first_frame_received`
|
||||
- `client_audio_first_frame_published`
|
||||
- `client_audio_enabled`
|
||||
- `done_packet_received`
|
||||
- `stop_sent`
|
||||
- `call_end`
|
||||
- `agent`:
|
||||
- `call_start`
|
||||
- `room_enter`
|
||||
- `session_start_requested`
|
||||
- `stt_recognize_started`
|
||||
- `stt_http_completed`
|
||||
- `user_transcript_final`
|
||||
- `pipeline_run_started`
|
||||
- `pipeline_run_completed`
|
||||
- `remote_agent_request`
|
||||
- `remote_agent_response`
|
||||
- `tts_stage_started`
|
||||
- `tts_stage_result`
|
||||
- `interrupt_marked`
|
||||
- `finalize_started`
|
||||
- `finalize_completed`
|
||||
|
||||
Uso pratico:
|
||||
1. iniciar a chamada normalmente
|
||||
2. identificar no terminal o `room` ou o `protocol`
|
||||
3. abrir o arquivo correspondente em `./timeline`
|
||||
4. ler os eventos em ordem de `t_rel_ms`
|
||||
|
||||
## Ponto de atencao
|
||||
|
||||
Durante o refactor, este documento deve continuar descrevendo:
|
||||
- comportamento publico
|
||||
- contrato do websocket
|
||||
- responsabilidades de cada camada
|
||||
|
||||
Na versao final, ele deve evoluir para a documentacao oficial da API.
|
||||
Reference in New Issue
Block a user