Saltar para o conteúdo
EuroVPSEuroVPS

Pesquisar décadas de e-mail: primeiro as palavras-chave, depois o significado

A pesquisa com IA em e-mail falha com dados sujos. Como limpar, agrupar em threads e indexar um arquivo de e-mail para que a pesquisa por palavras-chave e a pesquisa semântica funcionem juntas, com fontes.

Por EuroVPS Engineering

5 min de leitura
Searching decades of email: keywords first, meaning second

Apontar um modelo de linguagem a uma caixa de correio e fazer-lhe perguntas soa a projeto de fim de semana. Depois diz-lhe com toda a confiança a data errada de um contrato, porque leu uma cópia citada de um e-mail antigo dentro de uma resposta mais recente, ou não consegue encontrar a fatura 10442 porque o número não tem «significado» a que uma pesquisa vetorial se possa agarrar. Uma boa pesquisa num arquivo de e-mail não tem sobretudo a ver com o modelo. Tem a ver com os dados que lhe dá e com a forma como recupera. É assim que a abordamos.

O e-mail é mais confuso do que parece

Um arquivo de e-mail que abrange anos, vários sistemas de correio e algumas migrações tem problemas que arruínam a pesquisa antes de qualquer IA entrar em cena:

  • Duplicados. A mesma mensagem está na Caixa de entrada, numa pasta de projeto e numa exportação antiga. A pesquisa devolve-a três vezes; uma IA conta-a como três fontes a concordar.
  • Histórico citado. Cada resposta transporta todo o thread por baixo. Indexe isso de forma ingénua e o e-mail mais antigo de um thread aparece em todas as respostas, fora de contexto e com a data errada.
  • Codificações. O correio mais antigo está cheio de conjuntos de caracteres legados, cabeçalhos codificados e partes mal etiquetadas. Descodifique-o mal e os nomes e o texto não inglês transformam-se em lixo.
  • Anexos. O contrato é o PDF, não o e-mail que diz «em anexo». Se não extrair o texto dos anexos, não consegue encontrar os documentos mais importantes.
  • Eliminações. O correio eliminado da caixa por razões de retenção tem de desaparecer também de todos os índices.

Limpe antes de indexar

Elimine duplicados pelo Message-ID, recorrendo a um hash dos cabeçalhos principais e do corpo quando ele falta. Guarde um documento por mensagem e mantenha como metadados a lista de pastas em que apareceu.

Agrupe em threads pelos References e In-Reply-To, não pelos assuntos. Os assuntos mudam e «Re: fatura» não é único. Com threading correto pode mostrar uma conversa inteira por ordem e deixar o passo de recuperação puxar um thread completo em vez de fragmentos isolados.

Separe o texto novo do texto citado. Para cada mensagem, indexe o corpo completo para a pesquisa por palavras-chave, mas faça embedding apenas da parte que o autor realmente escreveu. Um começo simples:

import re
from email import policy
from email.parser import BytesParser

QUOTE_MARKERS = [
    re.compile(r"^On .+ wrote:$", re.M),
    re.compile(r"^-{2,}\s*Original Message\s*-{2,}", re.M | re.I),
    re.compile(r"^From: .+\nSent: .+", re.M),
]

def new_text(raw: bytes) -> str:
    msg = BytesParser(policy=policy.default).parsebytes(raw)
    part = msg.get_body(preferencelist=("plain", "html"))
    text = part.get_content() if part else ""
    cut = len(text)
    for rx in QUOTE_MARKERS:
        m = rx.search(text)
        if m:
            cut = min(cut, m.start())
    lines = [l for l in text[:cut].splitlines() if not l.startswith(">")]
    return "\n".join(lines).strip()

O correio real precisa de mais marcadores do que estes, em todas as línguas em que a sua empresa escreve, mas o princípio mantém-se: o modelo deve ler o que cada pessoa disse, uma vez, com a data e o remetente certos.

Extraia o texto dos anexos com ferramentas padrão (pdftotext para PDF, parsers de bibliotecas para formatos de escritório) e indexe-o como parte da mensagem, mantendo uma ligação ao ficheiro original.

Dois índices, porque respondem a perguntas diferentes

Um índice de texto integral responde a perguntas com pegas exatas: um remetente, um domínio, um intervalo de datas, um número de fatura, uma frase, «tem um PDF». É rápido, preciso e explicável. Use um com stemming que conheça a língua, para que «faturas» encontre «fatura», e com sinónimos entre as línguas que a sua empresa usa, para que uma pesquisa numa língua encontre correio escrito na outra.

Um índice vetorial responde a perguntas de significado: «quando é que o fornecedor contestou pela primeira vez o calendário de entrega?» Pode não haver uma única palavra-chave para isso. Um modelo de embedding transforma cada mensagem limpa num vetor, e a pesquisa encontra mensagens próximas em significado da pergunta.

Nenhum chega por si só. Os vetores são fracos em identificadores e números; as palavras-chave são fracas em paráfrases. Corra ambos, e combine:

  1. Extraia primeiro filtros da pergunta: pessoas, domínios, datas, «com anexo».
  2. Aplique esses filtros às duas pesquisas, para que a pesquisa vetorial nunca saia do período ou das pessoas a que a pergunta diz respeito.
  3. Funda as duas listas de resultados. O reciprocal rank fusion é simples e funciona bem: pontue cada mensagem pela soma de 1 / (k + rank) nas duas listas.
  4. Expanda os melhores resultados aos seus threads, para que o modelo leia conversas, não fragmentos.

Faça o modelo mostrar o seu trabalho

Só então entra o modelo de linguagem. Dê-lhe as mensagens recuperadas, cada uma com ID, data e remetente, e instrua-o a responder apenas a partir delas e a citar o ID em cada afirmação. Na interface, transforme cada citação numa ligação que abre o e-mail original.

As citações fazem dois trabalhos. O utilizador pode verificar cada afirmação com um clique, que é a única forma responsável de usar um modelo sobre registos de negócio. E vê-se de imediato quando a recuperação falhou: uma resposta que cita as mensagens erradas é um problema de recuperação, não um problema do modelo, e diz-lhe onde procurar.

Diga claramente aos utilizadores que um modelo pode interpretar mal. Com as fontes a um clique de distância, esse é um risco gerível.

Mantenha os dados dentro do edifício

Todos os componentes deste desenho correm confortavelmente no seu próprio hardware: o índice de texto integral em servidores comuns, a base de dados vetorial ao lado, e o modelo de embedding e o modelo de linguagem em GPU servers que a sua empresa controla. O correio, as perguntas e as respostas nunca precisam de sair da sua rede.

Teste-o como um sistema, não como uma demonstração

Antes de confiar nele, construa um pequeno conjunto de perguntas com respostas conhecidas: uma fatura específica, o primeiro e-mail de um litígio conhecido, uma mensagem escrita na sua segunda língua, uma mensagem eliminada no mês passado (não deve ser encontrada). Corra-as depois de cada alteração à limpeza, à indexação ou aos prompts, e guarde os resultados. Se uma alteração piorar perguntas conhecidas, vai vê-lo.

E vigie o próprio pipeline. Uma sincronização que parou em silêncio, um índice que não recuperou o atraso, ou uma fila de vetores depois de um reinício parecem todos «a pesquisa funciona» até alguém precisar do correio da semana passada. Verifique toda a cadeia de ponta a ponta, com regularidade, e alerte quando qualquer etapa ficar para trás.

Em suma

  • Elimine duplicados, agrupe em threads, descodifique e retire o texto citado antes de indexar o que quer que seja.
  • Indexe os anexos; é lá que estão os contratos.
  • Corra a pesquisa por palavras-chave e a pesquisa vetorial juntas, com filtros aplicados a ambas.
  • Responda apenas a partir do correio recuperado, com uma citação em cada afirmação.
  • Mantenha todos os componentes em hardware que controla, e teste com perguntas conhecidas.

Construímos arquivos de e-mail privados assim, com a IA a correr nas suas próprias GPU privadas. Se está a planear um, teremos todo o gosto em ajudar.

Fale com um engenheiro

Relacionados: Arquivo de e-mail privado com pesquisa por IA local · Managed Nextcloud para documentos de escritório

Publicado em 15 de outubro de 2026.

Todas as notas de campo