Modelo de Dados¶
Diagrama de Relacionamentos¶
┌─────────────────┐
│ Tenant │
└────────┬────────┘
│
├──────────────┬──────────────┬──────────────┬──────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Agent │ │WhatsAppInstance│ │ AiModel │ │ LlmUsage │
└──────────┘ └────┬─────┘ └───────┬───────┘ └──────────┘ └──────────┘
│ │
┌────────────┼────────────┬──────────────┬────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│Knowledge │ │ FAQ │ │Escalation│ │ Agent │ │
│ Block │ │ │ │ Rule │ │ Step │ │
└──────────┘ └──────────┘ └──────────┘ └─────┬────┘ │
│ │
┌────┴─────┐ │
│ Prompt │ │
│ Version │◄─────┘
└──────────┘
│
┌────────┴───────┐
│ Conversation │
└────────┬───────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Message │ │Escalation│
└────┬─────┘ └──────────┘
│
▼
┌──────────┐
│ Reaction │
└──────────┘
┌─────────────────┐ ┌─────────────────┐
│InstanceWhitelist│ │InstanceBlacklist│
└─────────────────┘ └─────────────────┘
▲ ▲
└────────┬───────────────┘
│
WhatsAppInstance
Nota: LlmUsage também referencia opcionalmente Agent, Conversation e Message
Entidades¶
Tenant¶
Empresa/organização que utiliza a plataforma (multi-tenancy).
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| Name | string | Nome da empresa |
| Slug | string | Identificador URL-safe único |
| Settings | string? | Configurações JSON específicas |
| IsActive | bool | Se o tenant está ativo |
| CreatedAt | DateTime | Data de criação |
| UpdatedAt | DateTime | Última atualização |
Relacionamentos: - 1:N Users - 1:N Agents - 1:N WhatsAppInstances - 1:N AiModels
User¶
Usuários do portal administrativo.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| TenantId | Guid | FK Tenant (obrigatório) |
| string | Login único | |
| PasswordHash | string | Senha BCrypt |
| Name | string | Nome de exibição |
| IsAdmin | bool | Admin do tenant |
| IsSuperAdmin | bool | Admin global (acesso a páginas admin) |
| IsActive | bool | Se pode fazer login |
| MustChangePassword | bool | Força troca de senha no próximo login |
| CreatedAt | DateTime | Data de criação |
| LastLoginAt | DateTime? | Último acesso |
Regras de acesso:
- IsSuperAdmin = true: Acesso a páginas admin (Tenants, LLM Usage, Settings) — dados filtrados pelo seu tenant
- IsAdmin = true: Gerencia usuários e recursos do tenant
- IsAdmin = false: Acesso limitado ao tenant
Restrições: - Email deve ser único globalmente (não apenas por tenant)
Agent¶
Agentes IA que processam conversas.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| TenantId | Guid | FK Tenant |
| Name | string | Nome do agente ("Suporte TI") |
| Description | string? | Descrição interna |
| SystemPrompt | string | Prompt principal do agente |
| OptimizedSystemPrompt | string? | Versão otimizada do prompt (gerada automaticamente) |
| IsActive | bool | Se está processando mensagens |
| IsArchived | bool | Se o agente está arquivado |
| ArchivedAt | DateTime? | Data de arquivamento |
| ErrorMessage | string? | Mensagem customizada de erro |
| WelcomeMessage | string? | Mensagem de boas-vindas |
| RateLimitMessage | string? | Mensagem de rate limit |
| OutOfHoursMessage | string? | Mensagem fora do horário |
| Tone | AgentTone | Tom de comunicação (Professional) |
| Language | AgentLanguage | Idioma das respostas (PtBr) |
| AllowEmojis | bool | Permite emojis nas respostas (true) |
| CreatedAt | DateTime | Data de criação |
| UpdatedAt | DateTime | Última atualização |
Otimização de Prompt:
- O OptimizedSystemPrompt é gerado automaticamente ao criar/atualizar o agente
- Remove redundâncias, compacta linguagem e reorganiza sem perder semântica
- Reduz consumo de tokens em ~30-35% por conversa
- Veja prompt-optimization.md para detalhes
Relacionamentos: - 1:N WhatsAppInstances - 1:N AgentKnowledge - 1:N AgentFaq - 1:N AgentStep - 1:N EscalationRules - 1:N PromptVersions
AgentStep¶
Etapas de atendimento que guiam a IA por um fluxo estruturado. Quando configuradas, a IA segue as instruções da etapa atual e avança conforme os objetivos são cumpridos.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| AgentId | Guid | FK Agent |
| Name | string | Nome da etapa (máx 100) |
| Instructions | string | Instruções da etapa (máx 20000) |
| OptimizedInstructions | string? | Versão otimizada das instruções |
| Order | int | Posição na sequência |
| IsActive | bool | Se está ativa no fluxo |
| CreatedAt | DateTime | Data de criação |
| UpdatedAt | DateTime | Última atualização |
Comportamento: - Sem etapas: agente responde livremente usando apenas o prompt base - Com etapas: IA segue a etapa atual, pode avançar/voltar conforme necessidade - IA pode pular etapas se todas as anteriores foram atendidas - Ao completar todas as etapas, a conversa é resolvida automaticamente - Instruções otimizadas são geradas automaticamente ao criar/atualizar
Relacionamentos: - N:1 Agent (cascade delete) - 1:N PromptVersions (via AgentStepId)
PromptVersion¶
Snapshot automático de prompts e instruções, criado a cada atualização de Agent.SystemPrompt ou AgentStep.Instructions.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| AgentId | Guid | FK Agent |
| AgentStepId | Guid? | FK AgentStep (null = prompt do Agent) |
| Version | int | Número sequencial da versão |
| Content | string | Conteúdo do prompt/instrução no momento do snapshot |
| OptimizedContent | string? | Versão otimizada no momento do snapshot |
| CreatedAt | DateTime | Data de criação |
Discriminação:
- AgentStepId == null: versão do Agent.SystemPrompt
- AgentStepId != null: versão do AgentStep.Instructions
Comportamento: - Snapshot criado automaticamente ANTES de cada update (captura estado anterior) - Restaurar uma versão aplica o conteúdo e cria um novo snapshot do estado atual - Versões são sequenciais por escopo (por Agent ou por AgentStep)
Índices:
- UNIQUE (agent_id, agent_step_id, version) — sequencialidade
- (agent_id) WHERE agent_step_id IS NULL — histórico do agent
- (agent_step_id) WHERE agent_step_id IS NOT NULL — histórico do step
Relacionamentos: - N:1 Agent (cascade delete) - N:1 AgentStep (cascade delete, opcional)
AgentKnowledge¶
Base de conhecimento em blocos para compor o contexto do agente. Utiliza busca semântica com pgvector para recuperar apenas os blocos relevantes para cada pergunta.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| AgentId | Guid | FK Agent |
| Title | string | Título do bloco |
| Content | string | Conteúdo (markdown, máx 25.000 caracteres) |
| DisplayOrder | int | Ordem de exibição |
| IsActive | bool | Se está incluído no prompt |
| Embedding | Vector? | Vetor de 1536 dimensões para busca semântica |
| EmbeddingUpdatedAt | DateTime? | Última atualização do embedding |
| CreatedAt | DateTime | Data de criação |
| UpdatedAt | DateTime | Última atualização |
Busca Semântica: - Embeddings gerados automaticamente ao criar/atualizar blocos - Índice HNSW para busca eficiente por similaridade - Retorna os 5 blocos mais relevantes para cada pergunta - Veja knowledge-retrieval.md para detalhes
AgentFaq¶
Perguntas frequentes do agente. Cada FAQ é um par pergunta/resposta atômico que participa da busca semântica junto com a base de conhecimento.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| AgentId | Guid | FK Agent |
| Question | string | Pergunta (máx 500 caracteres) |
| Answer | string | Resposta |
| DisplayOrder | int | Ordem de exibição |
| IsActive | bool | Se está incluída na busca |
| Embedding | Vector? | Vetor de 1536 dimensões para busca semântica |
| EmbeddingUpdatedAt | DateTime? | Última atualização do embedding |
| CreatedAt | DateTime | Data de criação |
| UpdatedAt | DateTime | Última atualização |
Diferença de AgentKnowledge: - AgentKnowledge: Blocos de texto/documentos que são divididos em chunks para embedding - AgentFaq: Pares Q&A atômicos com embedding direto na entidade (não precisa de chunks)
Busca Semântica:
- Embeddings gerados no formato: "Pergunta: {Question}\nResposta: {Answer}"
- Participam da mesma busca semântica que AgentKnowledge
- Resultados combinados por relevância (distância de cosseno)
AiModel¶
Modelos de IA disponíveis para uso.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| TenantId | Guid | FK Tenant |
| DisplayName | string | Nome amigável ("Claude Sonnet") |
| ModelId | string | ID do modelo na API ("claude-sonnet-4-20250514") |
| IsActive | bool | Se está disponível para uso |
| CreatedAt | DateTime | Data de criação |
| UpdatedAt | DateTime | Última atualização |
WhatsAppInstance¶
Instância de conexão com WhatsApp via Evolution API.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único (usado como nome da instância na Evolution API) |
| TenantId | Guid | FK Tenant |
| Name | string | Nome da instância |
| PhoneNumber | string? | Número conectado |
| Status | ConnectionStatus | Estado da conexão |
| AgentId | Guid? | FK Agent vinculado |
| ModelOverride | string? | ID do modelo LLM (sobrescreve o padrão do agent) |
| StartsWithAI | bool | Inicia conversas em modo IA |
| AiAvailabilityHoursJson | string? | Horários de atendimento da IA (JSONB) |
| SignatureText | string? | Texto da assinatura nas mensagens |
| SignaturePosition | SignaturePosition | Posição da assinatura (Disabled) |
| ShowTyping | bool | Mostra indicador de digitação (true) |
| DelaySeconds | int | Delay antes de enviar resposta (0-10) |
| IsArchived | bool | Se a instância está arquivada |
| ArchivedAt | DateTime? | Data de arquivamento |
| CreatedAt | DateTime | Data de criação |
| UpdatedAt | DateTime | Última atualização |
Relacionamentos: - N:1 Agent - 1:N Conversations - 1:N InstanceWhitelist - 1:N InstanceBlacklist
InstanceWhitelist¶
Lista de contatos autorizados a interagir com a instância.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| InstanceId | Guid | FK WhatsAppInstance |
| PhoneNumber | string | Número no formato E.164 |
| ContactName | string? | Nome do contato |
| Notes | string? | Observações |
| IsActive | bool | Se está ativo |
| CreatedAt | DateTime | Data de criação |
Lógica: Se a whitelist estiver vazia ou todos desativados, aceita qualquer número.
InstanceBlacklist¶
Lista de contatos bloqueados (mensagens ignoradas).
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| InstanceId | Guid | FK WhatsAppInstance |
| PhoneNumber | string | Número no formato E.164 |
| Reason | string? | Motivo do bloqueio |
| BlockedAt | DateTime | Quando foi bloqueado |
| BlockedBy | Guid? | FK User que bloqueou |
Prioridade: Blacklist é verificada ANTES da whitelist.
Conversation¶
Sessão de atendimento entre um contato e a instância.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| InstanceId | Guid | FK WhatsAppInstance |
| AgentId | Guid? | FK Agent (pode ser null) |
| ContactPhone | string | Número do contato |
| ContactName | string? | Nome (pushName) |
| ContactProfilePictureUrl | string? | URL da foto de perfil |
| Status | ConversationStatus | Active, Resolved, Escalated |
| Mode | ConversationMode | AI ou Manual |
| ModeChangedAt | DateTime? | Quando mudou de modo |
| StartedAt | DateTime | Início da conversa |
| LastMessageAt | DateTime | Última mensagem |
| LastReadAt | DateTime? | Última leitura do operador |
| ResolvedAt | DateTime? | Quando foi resolvida |
| Summary | string? | Resumo da conversa |
| CurrentStepId | Guid? | FK AgentStep — etapa atual do atendimento |
Relacionamentos: - 1:N Messages - 1:1 Escalation (opcional) - N:1 AgentStep (opcional, SetNull on delete)
Message¶
Mensagem individual em uma conversa.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| ConversationId | Guid | FK Conversation |
| Role | MessageRole | User, Assistant, System |
| Type | MessageType | Text, Image, Audio, etc. |
| Content | string? | Conteúdo textual / caption original |
| ExtractedText | string? | Texto extraído (transcrição ou descrição de imagem) |
| MediaUrl | string? | URL da mídia (R2) |
| MediaMimeType | string? | MIME type do arquivo |
| MediaFileName | string? | Nome do arquivo |
| WhatsAppMessageId | string? | ID do WhatsApp (para status) |
| Status | MessageStatus | Pending, Sent, Delivered, Read, Failed |
| SentAt | DateTime? | Quando foi enviada |
| DeliveredAt | DateTime? | Quando foi entregue |
| ReadAt | DateTime? | Quando foi lida |
| CreatedAt | DateTime | Data de criação |
Tracking de Tokens:
- O tracking de tokens foi movido para a entidade LlmUsage
- Permite tracking granular por operação, modelo e tipo de uso
- Veja llm-usage.md para detalhes
Processamento de Mídia:
- Áudios são transcritos via OpenAI Whisper → resultado em ExtractedText
- Imagens são descritas via Claude Vision → resultado em ExtractedText
- Veja media-processing.md para detalhes
Relacionamentos: - 1:N Reactions
Reaction¶
Reação (emoji) a uma mensagem.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| MessageId | Guid | FK Message |
| Emoji | string | Emoji da reação |
| SenderPhone | string | Quem reagiu |
| FromMe | bool | Se foi enviada pelo sistema |
| CreatedAt | DateTime | Data de criação |
EscalationRule¶
Regras para escalação automática de conversas.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| AgentId | Guid | FK Agent |
| Keywords | string? | Palavras-chave (vírgula separadas) |
| MaxMessages | int? | Escalar após N mensagens |
| MaxDurationMinutes | int? | Escalar após N minutos |
| IsActive | bool | Se está ativa |
| CreatedAt | DateTime | Data de criação |
Escalation¶
Registro de escalação de uma conversa.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| ConversationId | Guid | FK Conversation |
| RuleId | Guid | FK EscalationRule |
| Reason | string | Motivo da escalação |
| EscalatedAt | DateTime | Quando escalou |
| ResolvedAt | DateTime? | Quando foi resolvida |
| Notes | string? | Observações |
| ResolvedBy | string? | Quem resolveu |
SystemSettings¶
Configurações globais do sistema (singleton).
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | ID fixo (singleton) |
| DefaultErrorMessage | string | Mensagem padrão de erro |
| DefaultWelcomeMessage | string | Mensagem padrão de boas-vindas |
| DefaultRateLimitMessage | string? | Mensagem padrão de rate limit |
| DefaultOutOfHoursMessage | string? | Mensagem padrão fora do horário |
| UpdatedAt | DateTime | Última atualização |
LlmUsage¶
Registro de uso de LLM para tracking de tokens e custos.
| Campo | Tipo | Descrição |
|---|---|---|
| Id | Guid | Identificador único |
| TenantId | Guid | FK Tenant |
| Provider | LlmProvider | Anthropic, OpenAI |
| Model | string | ID do modelo (claude-3-5-sonnet-20241022) |
| OperationType | LlmOperationType | Tipo da operação |
| InputTokens | int | Tokens de entrada |
| OutputTokens | int | Tokens de saída |
| CacheCreationInputTokens | int | Tokens de criação de cache (prompt caching) |
| CacheReadInputTokens | int | Tokens lidos do cache (prompt caching) |
| EstimatedCost | decimal? | Custo estimado em USD (inclui custo de cache) |
| MessageId | Guid? | FK Message (opcional) |
| ConversationId | Guid? | FK Conversation (opcional) |
| AgentId | Guid? | FK Agent (opcional) |
| ReferenceContext | string? | Contexto adicional (JSONB) |
| CreatedAt | DateTime | Data de criação |
Relacionamentos: - N:1 Tenant - N:1 Message (opcional) - N:1 Conversation (opcional) - N:1 Agent (opcional)
Índices:
- (tenant_id, created_at) - Consultas por período
- (tenant_id, operation_type, created_at) - Consultas por tipo
- (agent_id, created_at) - Consultas por agente
- (conversation_id) - Consultas por conversa
Enums¶
LlmProvider¶
Provider de LLM.
| Valor | Descrição |
|---|---|
| Anthropic | Claude API |
| OpenAI | OpenAI API |
LlmOperationType¶
Tipo de operação com LLM.
| Valor | Descrição |
|---|---|
| Chat | Resposta em conversa |
| Vision | Descrição de imagem |
| Transcription | Transcrição de áudio |
| Embedding | Geração de embeddings |
| Chunking | Processamento de knowledge |
| Playground | Testes no playground |
ConnectionStatus¶
Status de conexão da instância WhatsApp.
| Valor | Descrição |
|---|---|
| Disconnected | Desconectado |
| Connecting | Conectando |
| Connected | Conectado |
| Open | Conexão aberta |
| Close | Conexão fechada |
ConversationStatus¶
Status da conversa.
| Valor | Descrição |
|---|---|
| Active | Conversa ativa |
| Resolved | Conversa resolvida |
| Escalated | Conversa escalada |
ConversationMode¶
Modo de atendimento da conversa.
| Valor | Descrição |
|---|---|
| AI | IA está atendendo |
| Manual | Humano está atendendo |
MessageRole¶
Papel do autor da mensagem.
| Valor | Descrição |
|---|---|
| User | Mensagem do usuário (cliente) |
| Assistant | Mensagem do assistente (IA ou operador) |
| System | Mensagem do sistema |
MessageType¶
Tipo de conteúdo da mensagem.
| Valor | Descrição |
|---|---|
| Text | Texto |
| Image | Imagem |
| Audio | Áudio (voice message) |
| Video | Vídeo |
| Document | Documento |
| Sticker | Sticker |
MessageStatus¶
Status de entrega da mensagem no WhatsApp.
| Valor | Descrição |
|---|---|
| Pending | Aguardando envio |
| Sent | Enviada (✓) |
| Delivered | Entregue (✓✓) |
| Read | Lida (✓✓ azul) |
| Failed | Falhou |
AgentTone¶
Tom de comunicação do agente.
| Valor | Descrição |
|---|---|
| Formal | Linguagem corporativa e respeitosa |
| Professional | Profissional mas acolhedor (padrão) |
| Friendly | Amigável e acessível |
AgentLanguage¶
Idioma das respostas do agente.
| Valor | Descrição |
|---|---|
| PtBr | Português do Brasil (padrão) |
| PtPt | Português de Portugal |
| En | Inglês |
| Es | Espanhol |
SignaturePosition¶
Posição da assinatura nas mensagens.
| Valor | Descrição |
|---|---|
| Disabled | Sem assinatura (padrão) |
| StartSameLine | Início da mensagem, mesma linha |
| StartNewLine | Início da mensagem, nova linha |
| EndSameLine | Final da mensagem, mesma linha |
| EndNewLine | Final da mensagem, nova linha |
Multi-tenancy¶
O sistema implementa multi-tenancy via query filters globais no EF Core:
// AppDbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Filtro global por TenantId
modelBuilder.Entity<Agent>()
.HasQueryFilter(a => a.TenantId == _tenantProvider.TenantId);
}
- Todas as entidades com
TenantIdsão automaticamente filtradas - Todo usuário (incluindo SuperAdmin) tem
TenantIdobrigatório — dados filtrados pelo tenant do JWT TenantIdé extraído do JWT viaITenantProvider
Convenções de Banco¶
| Contexto | Convenção |
|---|---|
| Tabelas e colunas | snake_case (via NamingConventions) |
| Chaves primárias | id (Guid) |
| Chaves estrangeiras | {entidade}_id |
| Timestamps | created_at, updated_at |
| Soft delete | is_active (não usa deleted_at) |