feat(ai): pipeline assincrono — transcricao, analise, prompts (fase 20)
Sub-fase B do modulo de IA: novo servico apps/ai-worker (poll + FOR UPDATE SKIP LOCKED) processa AIJob de TRANSCRIPTION/ANALYSIS disparados automaticamente apos uma gravacao ficar disponivel, respeitando a cascata de privacidade Tenant>Queue>Campaign e o entitlement do Plan. Transcricao separa o WAV estereo em 2 canais (parser proprio, sem ffmpeg) e transcreve cada perna independente; analise sempre redige dados sensiveis antes de sair pro provider e valida o resultado contra o schema antes de persistir. CRUD de AIPromptTemplate/AIPromptVersion em apps/api. Testado ponta a ponta contra o worker real em Docker e Postgres real com RLS (cascata de privacidade em 3 cenarios, WAV sintetico real no object storage, claim/retry/dead-letter reais) — chamada de rede contra OpenAI/Anthropic continua nunca exercitada (mesma restricao de rede desde o Provider Layer). Detalhes em docs/AI_PIPELINE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1HxY46WGU4G1zmVDNKcWw
This commit is contained in:
57
TODO.md
57
TODO.md
@@ -497,11 +497,60 @@
|
||||
platform admin cria/apaga, BYOK isolado por RLS (tenant B nunca vê
|
||||
BYOK do tenant A, 404 em id direto), modelo de provider GLOBAL
|
||||
visível dos dois tenants, redactor com 5 tipos de dado sensível
|
||||
- [ ] Pipeline assíncrono pós-CALL_ENDED, transcrição/análise/prompts/
|
||||
scorecards/usage metering reais — PHASE 20
|
||||
- [x] Pipeline assíncrono pós-CALL_ENDED, transcrição/análise/prompts —
|
||||
PHASE 20 (scorecards/usage metering completo ficam pra PHASE 21)
|
||||
|
||||
## PHASE 20+ — ver `agente.md` seções 104 em diante (Transcrição, Análise,
|
||||
Scorecards, Usage Metering, Billing, Frontend, Security, Tests)
|
||||
## PHASE 20 — IA: Pipeline (agente.md secao 104-116) — ver docs/AI_PIPELINE.md
|
||||
- [x] `packages/ai`: `privacy.ts` (cascata Tenant>Queue>Campaign, secao
|
||||
122), `retry.ts` (backoff exponencial + dead-letter, secao 108),
|
||||
`call-analysis-schema.ts` (schema JSON + validação manual do
|
||||
resultado, secao 112-113), `wav-stereo-split.ts` (parser WAV RIFF
|
||||
próprio, separa estéreo em 2 mono pra transcrever cada canal
|
||||
independente, sem depender de diarização probabilística nem ffmpeg)
|
||||
- [x] `apps/freeswitch-events/src/ai-trigger.ts`: decide (entitlement do
|
||||
Plan + cascata de privacidade, as duas precisam passar) se uma
|
||||
gravação recém-`AVAILABLE` vira um `AIJob(TRANSCRIPTION)` —
|
||||
encadeado em `recording.ts` logo após `Recording.create`
|
||||
- [x] `packages/entitlements`: `isFeatureEnabled` (versão não-lançante de
|
||||
`assertFeatureEnabled`, pra decisões em background)
|
||||
- [x] `apps/ai-worker` (serviço novo, Docker): tick poll com `FOR UPDATE
|
||||
SKIP LOCKED` (mesmo padrão de `lead-reservation.ts`), resolve
|
||||
provider/modelo (BYOK do tenant > GLOBAL da plataforma, primeira
|
||||
capability compatível), processa TRANSCRIPTION (baixa do object
|
||||
storage, separa estéreo, transcreve os 2 canais, persiste
|
||||
`CallTranscription`+`CallTranscriptSegment`+`AIUsageRecord`,
|
||||
encadeia ANALYSIS se a privacidade permitir) e ANALYSIS (resolve
|
||||
`AIPromptTemplate` com override de Campaign, redige dados sensíveis
|
||||
SEMPRE antes de mandar pro provider — independente do nível de
|
||||
privacidade, que só controla SE roda —, valida o resultado, persiste
|
||||
`CallAIAnalysis`+3 `AIUsageRecord`)
|
||||
- [x] `apps/api/src/ai/ai-prompts.controller.ts`: CRUD de
|
||||
`AIPromptTemplate`/`AIPromptVersion` (secao 114-116), mesma RLS
|
||||
híbrida GLOBAL/tenant dos providers/models; versões nunca editadas
|
||||
in-place, `activeVersionId` aponta pra qual está em uso
|
||||
- [x] Testado ponta a ponta contra o `b2bcall-ai-worker` real (não mocks)
|
||||
e Postgres real com RLS: cascata de privacidade+entitlement em 3
|
||||
cenários reais (permissivo→job criado, tenant AI_OFF→nada,
|
||||
privacidade OK mas Plan sem IA→nada), WAV sintético real gravado no
|
||||
object storage + `Recording` real + `AIJob` real reservado via SKIP
|
||||
LOCKED pelo worker, download+split+resolução de provider
|
||||
bem-sucedidos, chamada de rede real tentada contra loopback fechado
|
||||
(nunca saiu da máquina), falha real, retry com backoff de 30s,
|
||||
dead-letter exatamente na tentativa configurada; `processAnalysisJob`
|
||||
com transcrição semeada manualmente seguiu o mesmo caminho até a
|
||||
falha de rede esperada
|
||||
- [ ] **Nunca exercitado**: chamada de rede real contra OpenAI/Anthropic
|
||||
(mesma restrição de rede desde o Provider Layer); encadeamento
|
||||
automático TRANSCRIPTION→ANALYSIS a partir de uma transcrição
|
||||
*bem-sucedida* (só testável a partir de uma transcrição semeada
|
||||
manualmente, já que nenhuma chamada real completa sem rede);
|
||||
convenção de canal 0=cliente/1=agente contra áudio real
|
||||
distinguível (só tons sintéticos)
|
||||
- [ ] Scorecards/QA (secao 117-118) e Usage Metering completo/reports de
|
||||
custo (secao 124) — PHASE 21
|
||||
|
||||
## PHASE 21+ — ver `agente.md` seções 117 em diante (Scorecards, Usage
|
||||
Metering, Billing, Frontend, Security, Tests)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user