Documentação

Base de Agentes
para WhatsApp

Um framework TypeScript para criar agentes de IA conversacionais no WhatsApp. Um codebase, múltiplos clientes — cada agente é deployado e configurado de forma independente.

3

níveis de agente

16

capabilities

N

clientes replicáveis

Stack

BunRuntime
TypeScriptLinguagem
OpenAIModelo de IA
UAZAPICanal WhatsApp
RabbitMQMensageria
Vittal HubRegistro & logs
HonoHTTP server
ZodValidação

Arquitetura — Clean Architecture + DDD

01Apresentação
Webhook /POST
Hono HTTP
02Aplicação
ProcessMessageUseCase
AgentRuntime
ToolRegistry
PolicyRegistry
03Domínio
AgentManifest
AgentLevel
AgentCapability
AgentTool
04Infraestrutura
OpenAI Provider
RabbitMQ
UAZAPI Adapter
Vector Store

Um clone por cliente

Execute git clone https://github.com/VittalWeb/codebase-para-agentes nome-do-cliente e edite prompt.md , agent.manifest.json e .env na raiz.

Conceitos · 01

Níveis de Agentes

O nível define a complexidade, as capabilities disponíveis e o runtime de processamento. Cada nível é um superconjunto do anterior.

01

Informacional

informational

Responde, não age

Q&A, busca semântica e qualificação. Sem side-effects, sem mutações em sistemas externos. O modelo simplesmente responde.

Pode fazer

  • Responder perguntas frequentes (FAQ)
  • Busca semântica em documentos (RAG)
  • Qualificar leads com perguntas
  • Transferir para atendente (handoff)
  • Follow-up automático de re-engajamento

Não pode fazer

  • Criar ou cancelar agendamentos
  • Processar pagamentos
  • Atualizar CRM

Runtime

prompt.md → OpenAI → sanitizar → RabbitMQ

Capabilities exclusivas

faq
rag
lead_qualification
handoff
info_followup
02

Transacional

transactional

Age em um sistema

Uma mutação em um sistema externo. Tool calling com structured output JSON obrigatório. O modelo decide quando e como usar a ferramenta.

Pode fazer

  • Consultar agendamentos (lookup)
  • Criar / reagendar / cancelar consultas
  • Gerar link ou cobrança de pagamento
  • Atualizar registro de lead no CRM
  • Tudo do nível Informacional

Não pode fazer

  • Dois sistemas externos diferentes na mesma call
  • Flows multi-step com LangGraph
  • Aprovação humana explícita

Runtime

JSON { text, commands[] } → PolicyRegistry → ToolRegistry → execute()

Capabilities exclusivas

appointment_create
appointment_reschedule
payment_create
crm_update
03

Orquestrador

orchestrator

Coordena múltiplos sistemas

Múltiplos sistemas, flows longos, raciocínio multi-step. Usa LangGraph como state machine para controlar a orquestração.

Pode fazer

  • Coordenar CRM + Agendamento + Pagamento
  • Aprovação humana em etapas do flow
  • Raciocínio multi-step com estado
  • Tudo dos níveis anteriores

Não pode fazer

  • (sem restrições — nível máximo)

Runtime

LangGraph state machine → nós → execução paralela → síntese

Capabilities exclusivas

human_approval
multi_system_orchestration

Comparativo de recursos

RecursoInformacionalTransacionalOrquestrador
Tool calling (structured output)
Mutação de sistema externo1 sistemailimitado
LangGraph / state machine
Aprovação humana
RAG + FAQ
Handoff para atendente

Validação em startup: O framework rejeita manifests com capabilities acima do nível declarado. O erro é claro e imediato — sem comportamento silencioso.

Conceitos · 02

Capabilities

São as habilidades disponíveis para um agente. Declaradas no agent.manifest.json e validadas contra o nível na inicialização.

QueryLeitura — sem side-effects
FAQ
info+

Responde perguntas frequentes via prompt e base de conhecimento.

faq
RAG
info+

Busca semântica em documentos via embeddings OpenAI.

rag
Lead Qualification
info+

Coleta e qualifica dados de leads durante a conversa.

lead_qualification
Appointment Lookup
trans+

Consulta agendamentos existentes (somente leitura).

appointment_lookup
MutationEscreve em sistemas externos
Appointment Create
trans+

Cria novos agendamentos no sistema de calendário.

appointment_create
Appointment Reschedule
trans+

Reagenda consultas existentes.

appointment_reschedule
Appointment Cancel
trans+

Cancela agendamentos existentes.

appointment_cancel
Payment Create
trans+

Gera links ou cobranças de pagamento.

payment_create
CRM Update
trans+

Atualiza registros de lead/cliente no CRM.

crm_update
NotificationHandoff, follow-up, avisos
Group Notification
info+

Envia notificações em grupos internos de WhatsApp.

group_notification
Info Follow-up
info+

Re-engajamento automático após período de silêncio.

info_followup
Handoff
info+

Transfere conversa para atendente humano (round-robin).

handoff
AdvancedExclusivo do Orquestrador
Human Approval
orch

Pausa o flow aguardando aprovação humana explícita.

human_approval
Multi-system Orchestration
orch

Coordena mutações em múltiplos sistemas via LangGraph.

multi_system_orchestration
agent.manifest.json · capabilities
{
  "level": "transactional",
  "capabilities": [
    "faq",
    "rag",
    "appointment_lookup",
    "appointment_create",
    "handoff"
  ]
}

Conceitos · 03

Sistema de Plugins

Plugins agrupam ferramentas e políticas atomicamente. A instalação é transacional — se um componente falhar, nada é registrado.

Plugin

Container que agrupa capabilities, tools e policies em uma unidade coesa e instalável.

  • id único
  • capabilities[] declaradas
  • tools[] disponíveis
  • policies[] aplicadas

Tool

Ação que o modelo pode invocar. Input validado por Zod, execute() recebe contexto completo.

  • inputSchema (Zod)
  • kind: query | mutation
  • execute(input, ctx)
  • provider declarado

Policy

Guarda que roda antes de qualquer tool. Bloqueia execução quando condições não são atendidas.

  • evaluate() → { allowed, reason }
  • Roda antes da tool
  • Acessa ctx completo

AgentRuntime — Instalador Atômico

Valida todos os componentes antes de registrar qualquer um

src/application/plugins/

PluginRegistry

Indexa plugins por ID

ToolRegistry

Valida capabilities vs nível, executa tools com ctx

PolicyRegistry

Avalia policies antes de cada execução

Invariante: Se plugin A instala ok mas plugin B falha na validação, nenhum é registrado. O sistema falha rápido e com mensagem clara.

Interface do Plugin

Estrutura base que todo plugin deve implementar

AgentPlugin.ts
interface AgentPlugin {
  id: string                     // "builtin.handoff" | "crm.update"
  name: string
  capabilities: AgentCapability[]  // capabilities que o plugin habilita
  tools?: AgentTool[]            // ferramentas disponíveis
  policies?: AgentPolicy[]       // guardas de execução
  metadata?: Record<string, unknown>
}

Interface da Tool

Schema Zod valida o input, execute() recebe contexto completo

AgentTool.ts
interface AgentTool<TInput, TOutput> {
  name: string         // usado pelo modelo para invocar
  description: string  // o modelo lê isso para decidir quando usar
  kind: "query" | "mutation" | "notification"
  provider: string     // "vittal-hub" | "crm" | "openai"
  capabilities: AgentCapability[]
  inputSchema: ZodSchema   // validado antes de executar
  execute(input: TInput, ctx: AgentToolContext): Promise<TOutput>
}

// Contexto disponível em todo execute()
interface AgentToolContext {
  level: AgentLevel
  capabilities: AgentCapability[]
  conversationId: string
  senderPhone: string
  services: { conversationRepo, publisher, featureFlags, attendantSelector }
  logger: Logger
}

Exemplo completo — Plugin de CRM

Tool + Policy integrados em um plugin instalável

CRMPlugin.ts
export function createCRMPlugin(config: { apiUrl: string }): AgentPlugin {
  return {
    id: "crm.update-lead",
    name: "CRM Lead Updater",
    capabilities: ["crm_update"],

    tools: [{
      name: "update_lead",
      description: "Atualiza status do lead no CRM após qualificação",
      kind: "mutation",
      provider: "crm",
      capabilities: ["crm_update"],
      inputSchema: z.object({
        leadId: z.string(),
        status: z.enum(["qualificado", "descartado", "em_negociacao"]),
        notes: z.string().optional(),
      }),
      async execute(input, ctx) {
        const res = await fetch(`${config.apiUrl}/leads/${input.leadId}`, {
          method: "PATCH",
          body: JSON.stringify(input),
        })
        ctx.logger.info({ leadId: input.leadId }, "lead atualizado")
        return { updated: res.ok }
      },
    }],

    policies: [{
      id: "crm.scope-check",
      name: "CRM Scope Check",
      async evaluate(command, ctx) {
        if (!ctx.capabilities.includes("crm_update")) {
          return { allowed: false, reason: "Agente sem permissão de CRM" }
        }
        return { allowed: true }
      },
    }],
  }
}

Guia Prático · 01

Do Zero ao Deploy

Guia completo para criar e colocar em produção um agente para um novo cliente — do ambiente local até o container rodando no Dokploy.

01clone + install
03create-agent
06sessões de config
09test local
10deploy Dokploy

Antes de configurar o agente, garanta que você tem acesso a todos os serviços externos.

Bun v1.1+

bun.sh — runtime JavaScript

OpenAI API Key

platform.openai.com → API Keys

Vittal Hub

HUB_BASE_URL + HUB_API_KEY (equipe) + HUB_AGENT_KEY (passo 03)

UAZAPI

Instância do WhatsApp configurada e ativa

RabbitMQ

URL amqp:// + nome do exchange

Acesso ao repositório

https://github.com/VittalWeb/codebase-para-agentes

Credenciais do Hub, UAZAPI e RabbitMQ ficam no 1Password da equipe. Peça acesso ao tech lead antes de começar.

bash
# Instalar o Bun (se não tiver)
$ curl -fsSL https://bun.sh/install | bash
$ bun --version    # deve ser 1.1.x ou maior

# Clonar o repositório — este clone é o seu agente
$ git clone https://github.com/VittalWeb/codebase-para-agentes nome-do-agente
$ cd nome-do-agente
$ bun install
$ bun run check    # typecheck — sem erros = ambiente ok

Configuração · 01

Cloudflare Tunnel

O UAZAPI precisa de uma URL pública HTTPS para enviar webhooks ao agente. Como em desenvolvimento o servidor roda em localhost:3000, o Cloudflare Tunnel cria uma ponte pública permanente — você configura uma vez e usa sempre.

O que é

Um túnel criptografado entre o Cloudflare e o seu localhost. Sem abrir portas no roteador.

URL fixa

Após configurado, a URL não muda entre sessões — configure o webhook no UAZAPI uma vez só.

Teste pelo WhatsApp

O teste sempre é feito enviando mensagem no WhatsApp do número conectado à instância UAZAPI.

1

Instalar o cloudflared

Instalação única na máquina — não repete por projeto.

bash
brew install cloudflare/cloudflare/cloudflared
cloudflared --version
2

Criar o seu tunnel no painel Cloudflare

Acesse o painel com a conta Vittal e crie o tunnel com o seu nome no padrão <nome>-api.

Painel Cloudflare Zero Trust

one.dash.cloudflare.com — conta vittaldev01

Solicitar acesso ao tech lead

Navegue até:

Zero TrustNetworksTunnelsCreate a tunnel

Escolha Cloudflared como tipo de conector e clique em Next. No campo de nome, use o padrão: joao-api (seu nome + -api).

3

Instalar o conector com o token gerado

Após criar o tunnel, o Cloudflare exibe o comando com o token. Cole no terminal e rode.

Na tela de instalação do conector, o Cloudflare mostra o comando completo com o token já preenchido. Copie e execute no seu terminal:

bash
# Comando exibido pelo painel — o token é gerado pelo Cloudflare
cloudflared service install SEU_TOKEN_AQUI

# Isso instala o tunnel como serviço no sistema
# (inicia automaticamente, não precisa de terminal aberto)
saída esperada
INFO[0000] Installing cloudflared Windows service
INFO[0000] cloudflared agent is running
INFO[0000] Registered tunnel connection connIndex=0 location=GRU

O tunnel se conecta ao localhost:3000. Ele só encaminha tráfego quando bun run dev estiver rodando — mas pode ficar instalado permanentemente sem problema.

4

Configurar o hostname público

De volta ao painel, vincule o subdomínio ao seu localhost. Isso define a URL permanente do tunnel.

No painel, após instalar o conector:

seu tunnelPublic HostnameAdd a hostname
CampoValor
Subdomainjoao-api(seu nome + -api)
Domainvittalweb.com
Service TypeHTTP
URLlocalhost:3000

Após salvar, sua URL pública permanente é: https://joao-api.vittalweb.com— não muda entre sessões.

5

Configurar o webhook no UAZAPI

Cole a URL do tunnel no painel UAZAPI na instância do cliente que você está testando. Faz isso uma vez só.

Webhook URL para UAZAPI

bash
# Cole este valor no campo de webhook da instância no painel UAZAPI
https://joao-api.vittalweb.com/webhook

# Formato: https://<nome>-api.vittalweb.com/webhook

No painel UAZAPI, acesse a instância do cliente → campo Webhook URL → cole a URL acima. O WEBHOOK_SECRET deve ser o mesmo que está no .env do agente.

6

Testar via WhatsApp

O teste sempre é feito pelo WhatsApp — mande uma mensagem para o número da instância e observe os logs.

Com o servidor rodando (bun run dev) e o tunnel ativo, envie uma mensagem pelo WhatsApp para o número conectado à instância UAZAPI. Acompanhe os logs no terminal.

logs esperados ao receber mensagem
[webhook] POST /webhook — 200 OK
[agent] Processing message from +5511999990001
[agent] Response generated in 1.3s
[agent] Sending response via RabbitMQ...

Nenhum log aparecendo? Verifique:

  • • O serviço cloudflared está rodando? (cloudflared tunnel list)
  • • O webhook no UAZAPI aponta para a URL correta?
  • • O WEBHOOK_SECRET é igual nos dois lados?
  • • O servidor local está rodando? (bun run dev)

Fluxo diário de desenvolvimento

Após a configuração inicial, o dia a dia é simples — o tunnel já está instalado como serviço:

1

bun run dev

servidor local na porta 3000

2

tunnel ativo automaticamente

cloudflared service já instalado

3

WhatsApp → resposta nos logs

UAZAPI já configurado

Configuração · 02

Configurando UAZAPI

UAZAPI é o gateway que conecta o agente ao WhatsApp. Uma instância por número de telefone do cliente.

Instância = Número

Cada instância UAZAPI representa um número de WhatsApp Business. Um cliente = uma instância.

Recebe mensagens

Quando o cliente manda mensagem, o UAZAPI faz um POST no webhook do agente.

Envia respostas

O agente publica a resposta no RabbitMQ. O UAZAPI consome e entrega ao WhatsApp.

1

Acessar o painel UAZAPI

Credenciais fornecidas pelo tech lead (1Password → UAZAPI Admin).

A URL do painel UAZAPI é interna à equipe. Acesse com as credenciais do 1Password → UAZAPI Admin. Se não tiver acesso, peça ao tech lead.

Tela inicial do painel

Instâncias ativas3
clinica-dra-anaConectado
consultorio-saudeConectado
nova-instanciaDesconectado
2

Criar nova instância

Uma instância por cliente. Use o slug do agente como nome (ex: clinica-dra-ana).

Campos ao criar instância

Nome da instância
clinica-dra-ana

Use o slug do agente — fica mais fácil de identificar

Tipo
WhatsApp Business

Sempre Business para clientes

Webhook URL
(preencher depois — passo 05)

Deixe em branco por ora

3

Conectar o número via QR code

Após criar, o painel exibe um QR code. Leia com o WhatsApp do número do cliente.

No WhatsApp do cliente

  1. 1Abrir WhatsApp → ⋮ → Aparelhos conectados
  2. 2Tocar em Conectar aparelho
  3. 3Apontar câmera para o QR code
  4. 4Aguardar sincronização (10–30s)

Status esperado

Antes da leituraDesconectado
Escaneando...Conectando...
SincronizadoConectado

O QR code expira em 60 segundos. Se expirar, clique em Recarregar no painel para gerar um novo. O número não pode estar conectado em outro aparelho — desconecte antes se necessário.

4

Obter as credenciais da instância

Com a instância conectada, colete as variáveis para o .env do agente.

Onde encontrar cada variável

UAZAPI_BASE_URLfixo

Valor fixo da Vittal — sempre o mesmo para todos

https://uazapi.vittalweb.com
UAZAPI_TOKENpor instância

Painel → Instância → Detalhes → Token

uaz_••••••••••••••••
WEBHOOK_SECRETvocê define

Você define — qualquer string segura, anote para usar no Dokploy

wh_s3cr3t_c1inica
bash
# Cole no .env do agente:
UAZAPI_BASE_URL=https://uazapi.vittalweb.com   # fixo — igual para toda a equipe
UAZAPI_TOKEN=uaz_...                            # único por instância/número
WEBHOOK_SECRET=wh_s3cr3t_...                   # você define — anote para o Dokploy
5

Configurar o webhook na instância

Cole a URL pública do agente para que o UAZAPI saiba para onde enviar as mensagens recebidas.

URL do webhook por ambiente

Testes locais (Cloudflare Tunnel)URL permanente do tunnel
bash
https://joao-api.vittalweb.com/webhook
# formato: https://<seu-nome>-api.vittalweb.com/webhook

URL criada no guia Cloudflare Tunnel. Configure uma vez só — não muda entre sessões.

Produção (Dokploy)após deploy
bash
https://clinica-dra-ana.vittalweb.com/webhook

Domínio criado no Dokploy após o deploy — ver passo 9 do Guia Criando um Agente.

Além da URL, preencha o campo WEBHOOK_SECRET no painel UAZAPI com o mesmo valor definido no .env do agente.

6

Testar a conexão

Envie uma mensagem de teste para confirmar que o fluxo completo está funcionando.

Checklist antes de testar

  • Instância status: Conectado
  • Webhook URL configurada
  • WEBHOOK_SECRET igual no .env e no painel
  • Servidor do agente rodando (bun run dev)
  • Tunnel ativo (se for dev local)

Logs esperados

servidor
POST /webhook 200 OK
[agent] recv: "Olá!"
[agent] processing...
[rmq] published response
[uazapi] delivered ✓

Se o agente responder no WhatsApp, a configuração está completa. Em produção, substitua a URL do tunnel pela URL do Dokploy no painel UAZAPI.

Variáveis no .env

bash
# ─ UAZAPI ─────────────────────────────────────────────────────────
UAZAPI_BASE_URL=https://uazapi.vittalweb.com    # fixo — igual para toda a equipe
UAZAPI_TOKEN=uaz_...                             # único por instância (número do cliente)
WEBHOOK_SECRET=wh_s3cr3t_...                    # você define — mesmo valor no painel UAZAPI

Guia Prático · 02

Fluxo de Mensagem

O caminho completo de uma mensagem, do webhook ao WhatsApp do usuário.

condicional= ativado por feature flag

1–3s

Sem tools

gpt-4o-mini

3–8s

Com tool calling

inclui execução externa

30s

Timeout de lock

fallback anti-deadlock

01

Webhook recebido

POST /webhook

UAZAPI chama o endpoint com o payload da mensagem. WEBHOOK_SECRET valida a assinatura.

Autenticação timing-safe via WEBHOOK_SECRETPayload converte para IncomingMessageDTOHono roteia para ProcessMessageUseCase
02

Lock por conversa

ChatId mutex

Serializa processamento de mensagens do mesmo chatId para evitar race conditions.

Map<chatId, Promise> serializa execuçãoTimeout de 30s previne deadlockFila automática para mensagens simultâneas
03

Filtros e guards

Verificações de pré-condição

Série de verificações que decidem se a mensagem deve ser processada.

Estado paused? → early returnWHITELIST_NUMBERS / WHITELIST_GROUPSTRIGGER_WORD obrigatório?Deduplicação por messageId (TTL 5min)
04

Composição do prompt

AgentPromptComposer

Prompt base carregado e enriquecido com chunks RAG se FEATURE_RAG=true.

Lê prompt.md do diretório do agenteBusca semântica em knowledge/ via embeddingsHistórico de conversa carregado do Vittal HubAgentPromptComposer concatena base + RAG chunks
05

Processamento de mídia

Whisper · GPT-4o Vision
condicional

Áudio transcrito com Whisper. Imagens analisadas com GPT-4o Vision. Resultado injetado como texto.

FEATURE_PROCESSAR_AUDIO → Whisper APIFEATURE_PROCESSAR_IMAGEM → GPT-4o VisionOutput substitui a mensagem original
06

Chamada ao OpenAI

Modelo + tools

O modelo recebe prompt montado, histórico e — para agentes transacionais — as ferramentas disponíveis.

prompt + histórico + mensagem atualtools[] para agentes transacional/orquestradortemperature, top_p, model configuráveis
07

Parse da resposta

DefaultAgentResponseParser

Tenta structured JSON primeiro, fallback para tags legadas, fallback para texto puro.

1º: JSON { text, commands[] }2º: tags [ASSISTENTE_HUMANO] (legado)3º: texto inteiro, commands: []
08

Execução de comandos

AgentCommandExecutor
condicional

Para cada comando: avalia policies, valida schema Zod do input, executa a tool, loga no Hub.

PolicyRegistry.evaluate() → block se negadoinputSchema.safeParse() → valida antes de executartool.execute(input, ctx) com context completoLoga steps individuais no Vittal Hub
09

Publicação da resposta

RabbitMQ → UAZAPI

Resposta sanitizada publicada no exchange. Consumer UAZAPI entrega ao usuário no WhatsApp.

Remove JSON interno, tags técnicasFormata no envelope (uazapi | crm)Publica no RabbitMQ exchangeConsumer entrega via UAZAPI
10

Log no Vittal Hub

Observabilidade

Toda execução registrada: steps, tokens, duração, estado final da conversa.

execution log com timestampSteps individuais (tool calls)Tokens de entrada/saídaAtualiza estado da conversa

Referência · 01

Configuração

Referência completa do manifest, variáveis de ambiente e feature flags.

Fonte de verdade do agente. Validado via Zod na inicialização — um manifest inválido impede o agente de subir.

agent.manifest.json
{
  // Metadados
  "version": "1.0.0",
  "name": "Nome do Agente",
  "slug": "nome-do-agente",          // kebab-case, único

  // Nível e capabilities — validados na inicialização
  "level": "transactional",          // "informational" | "transactional" | "orchestrator"
  "capabilities": [
    "faq", "rag", "appointment_create", "handoff"
  ],

  // Plugins instalados atomicamente
  "plugins": [{
    "id": "builtin.handoff",
    "config": {
      "handoffAnnouncementGroup": "120363XXXXXXXX@g.us"
    },
    "requiredEnv": ["HANDOFF_ATTENDANTS"]
  }],

  // Publisher
  "publisher": {
    "transport": "rabbitmq",
    "envelope": "uazapi",            // "uazapi" | "crm"
    "source": "nome-do-agente",
    "requiredEnv": ["RABBITMQ_URL", "RABBITMQ_EXCHANGE"]
  },

  // Prompts
  "prompts": {
    "main": "prompt.md"              // único arquivo que você escreve
  },

  // RAG — documentos em knowledge/
  "knowledge": { "path": "knowledge" },

  // Modelo (opcional — usa OPENAI_MODEL do .env por padrão)
  "model": {
    "provider": "openai",
    "name": "gpt-4o"
  }
}

Segurança

  • • Nunca commite .env com credenciais — o .gitignore já os exclui
  • • Use .env.example para documentar variáveis sem valores reais
  • • Credenciais de produção ficam exclusivamente no painel do Dokploy

Vittal — Documentação interna · Base de Agentes IA