O Smart Context centraliza como o histórico de conversa é buscado, recortado e entregue para os prompts do Tolky Reasoning. O objetivo principal é reduzir custo de contexto (tokens e caracteres) sem perder dados importantes para a operação.

Por que existe

O histórico de conversa é consumido em dezenas de pontos do codebase. Sem centralização, cada ponto tinha sua própria regra de recorte e formatação, gerando:

  • Lógica de recorte duplicada
  • Diálogos maiores que o necessário para cada tipo de captura
  • Pouco aproveitamento do resumo existente nas conversas
  • Sem controle centralizado do custo de contexto

Arquitetura

O Smart Context resolve o problema com duas peças principais:

  1. getContext() — Função central que busca e entrega o contexto no formato e tamanho certo para quem chama, aplicando resumo e mensagens recentes quando necessário.
  2. SummaryManager — Mantém um resumo compacto e atualizado do diálogo em JSONB, registrando até qual mensagem o resumo cobre, para que as mensagens seguintes continuem em full.

Estrutura do módulo

ArquivoResponsabilidade
contextFetcher.jsBusca o diálogo da fonte correta e mensagens ancoradas de campanha
campaignPinnedFetcher.jsQuery de mensagens system com campaign_id ativo
contextFormatter.jsAplica recorte e monta a saída
summaryManager.jsGerencia o smart_context_summary (JSONB)
savingsReporter.jsCalcula economia de caracteres e tokens estimados

Fontes de diálogo (ordem de prioridade)

  1. globalData.conversations.conversation
  2. globalData.inputBody.originalDialogue
  3. globalData.inputBody.openaiDialogue
  4. Cache por conversationId
  5. Banco de dados (vw_conversations) via conversationId

As fontes 2 e 3 vêm do cliente e têm limite hard de entrada (max_input_chars). Se o diálogo recebido exceder o limite, é truncado antes de qualquer processamento.

Resumo inteligente (SummaryManager)

Estrutura JSONB

O resumo não é uma string simples. É um JSONB que guarda o texto resumido e o ID da última mensagem consumida:

{
  "summary": "Usuário informou que tem 3 filhos e mora em São Paulo. Está interessado no plano Premium.",
  "lastMessageId": "uuid-da-ultima-mensagem-consumida-pelo-resumo"
}
  • summary — Texto semântico compacto do diálogo até lastMessageId
  • lastMessageId — ID da última mensagem coberta pelo resumo; todas as mensagens após essa ID aparecem em full no retorno

Threshold com histerese

O SummaryManager é acionado por threshold com histerese — dois limiares distintos evitam oscilação em conversas que ficam perto do limite:

EventoThreshold
Acionar sumarização pela primeira vezthreshold_tokens * 1.15
Re-sumarizar quando já existe resumothreshold_tokens * 1.30

A última mensagem user e a última assistant nunca são consumidas pelo resumo — sempre aparecem em full.

Mensagens ancoradas de campanha

O início de uma conversa oriunda de campanha contém mensagens system com dados pré-coletados do contato (telefone, nome, variáveis do template). Quando a conversa cresce e o contexto sofre corte, essas mensagens poderiam desaparecer.

Solução: Mensagens system com campaign_id apontando para uma campanha ativa são sempre incluídas no contexto, independentemente de qualquer corte.

Campanha ativa: deleted = false AND (expire_at IS NULL OR expire_at >= CURRENT_DATE)

Estrutura da saída com mensagens ancoradas

[mensagens system de campanha ativa (ancoradas)]
+ [summary (se ativo)]
+ [mensagens user/assistant recentes (boxes)]

Economia de contexto (savings)

O savingsReporter torna a economia mensurável por requisição:

MedidaO que representa
originalCharCountTotal de caracteres do diálogo bruto, antes de qualquer recorte
returnedCharCountTotal de caracteres do conteúdo final entregue
savedCharCountoriginalCharCount - returnedCharCount
savedPercentPercentual de economia
estimatedOriginalTokenscharCount / 4 (proxy)
estimatedReturnedTokenscharCount / 4 (proxy)
estimatedSavedTokensTokens economizados estimados

Configuração via avatar_config

Campo JSONB smart_context em public.avatar_config:

{
  "summary_model": "qwen-3-32b",
  "summary_provider": "cerebras",
  "threshold_tokens": 3000,
  "max_input_chars": 50000,
  "max_single_message_tokens": 1200,
  "truncated_message_target_tokens": 700
}
ChaveTipoDescrição
summary_modelstringModelo de sumarização
summary_providerstringProvider do modelo
threshold_tokensnumberBase do threshold para acionar resumo (histerese: 1.15x / 1.30x)
max_input_charsnumberLimite hard de entrada para fontes do cliente
max_single_message_tokensnumberThreshold por mensagem para truncamento interno
truncated_message_target_tokensnumberAlvo de tokens após truncamento da mensagem grande

Prioridade de lookup: config do avatar → config do host (avatar_id = null) → config do domínio.

Próximos passos