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
Arquitetura — Clean Architecture + DDD
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.
Informacional
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
Transacional
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
Orquestrador
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
Comparativo de recursos
| Recurso | Informacional | Transacional | Orquestrador |
|---|---|---|---|
| Tool calling (structured output) | |||
| Mutação de sistema externo | 1 sistema | ilimitado | |
| 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.
Responde perguntas frequentes via prompt e base de conhecimento.
faqBusca semântica em documentos via embeddings OpenAI.
ragColeta e qualifica dados de leads durante a conversa.
lead_qualificationConsulta agendamentos existentes (somente leitura).
appointment_lookupCria novos agendamentos no sistema de calendário.
appointment_createReagenda consultas existentes.
appointment_rescheduleCancela agendamentos existentes.
appointment_cancelGera links ou cobranças de pagamento.
payment_createAtualiza registros de lead/cliente no CRM.
crm_updateEnvia notificações em grupos internos de WhatsApp.
group_notificationRe-engajamento automático após período de silêncio.
info_followupTransfere conversa para atendente humano (round-robin).
handoffPausa o flow aguardando aprovação humana explícita.
human_approvalCoordena mutações em múltiplos sistemas via LangGraph.
multi_system_orchestration{
"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 únicocapabilities[] declaradastools[] disponíveispolicies[] aplicadas
Tool
Ação que o modelo pode invocar. Input validado por Zod, execute() recebe contexto completo.
inputSchema (Zod)kind: query | mutationexecute(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 toolAcessa ctx completo
AgentRuntime — Instalador Atômico
Valida todos os componentes antes de registrar qualquer um
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
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
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
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.
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.
# 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 okConfiguraçã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.
Um túnel criptografado entre o Cloudflare e o seu localhost. Sem abrir portas no roteador.
Após configurado, a URL não muda entre sessões — configure o webhook no UAZAPI uma vez só.
O teste sempre é feito enviando mensagem no WhatsApp do número conectado à instância UAZAPI.
Instalar o cloudflared
Instalação única na máquina — não repete por projeto.
brew install cloudflare/cloudflare/cloudflared
cloudflared --versionCriar 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
Navegue até:
Escolha Cloudflared como tipo de conector e clique em Next. No campo de nome, use o padrão: joao-api (seu nome + -api).
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:
# 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)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.
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:
joao-api(seu nome + -api)vittalweb.comHTTPlocalhost:3000Após salvar, sua URL pública permanente é: https://joao-api.vittalweb.com— não muda entre sessões.
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
# 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/webhookNo 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.
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.
[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:
bun run dev
servidor local na porta 3000
tunnel ativo automaticamente
cloudflared service já instalado
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.
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
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
clinica-dra-anaUse o slug do agente — fica mais fácil de identificar
WhatsApp BusinessSempre Business para clientes
(preencher depois — passo 05)Deixe em branco por ora
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
- 1Abrir WhatsApp → ⋮ → Aparelhos conectados
- 2Tocar em Conectar aparelho
- 3Apontar câmera para o QR code
- 4Aguardar sincronização (10–30s)
Status esperado
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.
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_URLfixoValor fixo da Vittal — sempre o mesmo para todos
https://uazapi.vittalweb.comUAZAPI_TOKENpor instânciaPainel → Instância → Detalhes → Token
uaz_••••••••••••••••WEBHOOK_SECRETvocê defineVocê define — qualquer string segura, anote para usar no Dokploy
wh_s3cr3t_c1inica# 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 DokployConfigurar 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
https://joao-api.vittalweb.com/webhook
# formato: https://<seu-nome>-api.vittalweb.com/webhookURL criada no guia Cloudflare Tunnel. Configure uma vez só — não muda entre sessões.
https://clinica-dra-ana.vittalweb.com/webhookDomí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.
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
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
# ─ 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 UAZAPIGuia Prático · 02
Fluxo de Mensagem
O caminho completo de uma mensagem, do webhook ao WhatsApp do usuário.
1–3s
Sem tools
gpt-4o-mini
3–8s
Com tool calling
inclui execução externa
30s
Timeout de lock
fallback anti-deadlock
Webhook recebido
UAZAPI chama o endpoint com o payload da mensagem. WEBHOOK_SECRET valida a assinatura.
Lock por conversa
Serializa processamento de mensagens do mesmo chatId para evitar race conditions.
Filtros e guards
Série de verificações que decidem se a mensagem deve ser processada.
Composição do prompt
Prompt base carregado e enriquecido com chunks RAG se FEATURE_RAG=true.
Processamento de mídia
Áudio transcrito com Whisper. Imagens analisadas com GPT-4o Vision. Resultado injetado como texto.
Chamada ao OpenAI
O modelo recebe prompt montado, histórico e — para agentes transacionais — as ferramentas disponíveis.
Parse da resposta
Tenta structured JSON primeiro, fallback para tags legadas, fallback para texto puro.
Execução de comandos
Para cada comando: avalia policies, valida schema Zod do input, executa a tool, loga no Hub.
Publicação da resposta
Resposta sanitizada publicada no exchange. Consumer UAZAPI entrega ao usuário no WhatsApp.
Log no Vittal Hub
Toda execução registrada: steps, tokens, duração, estado final 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.
{
// 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
.envcom credenciais — o.gitignorejá os exclui - • Use
.env.examplepara documentar variáveis sem valores reais - • Credenciais de produção ficam exclusivamente no painel do Dokploy