Existe um momento específico em que um app de anotações deixa de valer o que custa. Não é quando o armazenamento acaba. É a primeira vez em que você tem certeza de que anotou algo, procura, e o app diz que não há resultados.
Você anotou, sim. A nota está lá. Só que você buscou "aquela coisa sobre faixas de preço", e a nota diz "oferta em três patamares, ancorada no SKU do meio". Nenhuma palavra em comum.
É uma falha que piora à medida que o seu arquivo fica mais valioso. Com cem notas, você lembra as palavras. Com dez mil, lembra a ideia e mais nada. A busca por palavra-chave está no seu ponto mais fraco justamente quando você mais precisa dela.
O Notez faz duas buscas para cada consulta e junta os resultados. Este texto mostra como isso funciona de fato — inclusive as partes mais rudimentares do que você imaginaria.
Ramo um: palavra-chave
O ramo de palavra-chave é uma simples busca de substring em SQL. Sua consulta vira %query% e é comparada, sem diferenciar maiúsculas de minúsculas, com:
o título da nota
o texto puro extraído da nota (o documento de texto rico achatado em texto ao salvar)
os valores das propriedades personalizadas — os rótulos das opções de seleção e de seleção múltipla e o texto das propriedades do tipo texto
Este último item pesa mais do que parece. Se você tem uma propriedade Client com a opção Acme, buscar acme encontra todas as notas marcadas com ela, mesmo que a palavra nunca apareça no corpo.
Voltam até 50 resultados por palavra-chave, ordenados pela atualização mais recente.
No que este ramo é bom: encontrar exatamente o que você digitou. Identificadores, códigos de erro, nomes, uma frase que você lembra palavra por palavra. Se você digita NOTE_MODIFIED, quer a nota que contém essa string literal, em primeiro lugar, na hora.
No que é ruim: em todo o resto. Uma busca de substring não é aproximada — um erro de digitação na consulta não encontra nada. Sinônimos não encontram nada. Conceitos não encontram nada.
Ramo dois: semântico
O ramo semântico converte sua consulta num vetor e procura conteúdo de notas que fique perto dele nesse espaço.
Na prática: a consulta vira um embedding de 1.536 dimensões com o modelo text-embedding-3-small da OpenAI, e o Postgres (com pgvector) ordena os trechos de notas armazenados pela distância de cosseno até ele. Voltam até 20 notas.
No que este ramo é bom: no caso que quebrou a busca por palavra-chave. "Faixas de preço" e "oferta em três patamares" ficam perto uma da outra no espaço de embeddings, porque o modelo foi treinado com texto suficiente para saber que tratam do mesmo assunto.
No que é ruim: em precisão. Vetores não têm noção de "exatamente esta string". Busque um UUID e o ramo semântico vai entregar, cheio de confiança, algo de tema parecido e inútil.
Nenhum dos dois ramos basta sozinho. Esse é todo o argumento para rodar os dois.
O que de fato vira embedding
Não dá para transformar uma nota inteira num único vetor e esperar bons resultados. Uma nota de reunião de 4.000 palavras reduzida à média num único ponto do espaço não fica perto de nada em particular — é um borrão sobre todos os temas que ela toca.
Por isso as notas são divididas em trechos (chunks) antes do embedding, e a divisão segue a estrutura do próprio documento:
O título vai para o início do conteúdo antes de qualquer outro passo, para que as palavras do título façam parte do que é indexado, e não fiquem num campo separado que ninguém pesquisa.
Dividir nos títulos de seção. Cada título de seção começa uma nova seção. Um título de seção sem texto embaixo é indexado mesmo assim, sozinho, porque ele tem significado mesmo quando a seção está vazia.
Dividir seções longas em parágrafos. Tudo o que passa de 2.000 caracteres é cortado no limite entre parágrafos, e não no meio de uma frase.
Juntar trechos minúsculos aos vizinhos. Tudo o que tem menos de 100 caracteres é incorporado ao trecho adjacente em vez de ser descartado.
Essa última regra existe por causa de um bug. Uma versão anterior descartava os trechos pequenos demais, e uma nota feita de várias seções curtas — exatamente o formato de uma nota de reunião em tópicos — ficava com quase nada. A nota estava indexada, tecnicamente. E também era impossível de encontrar.
Cada trecho armazenado guarda o seu título de seção e a sua posição no documento, então um resultado semântico mostra qual parte de uma nota longa correspondeu à busca, e não só que a nota correspondeu em algum lugar.
Como uma nota longa gera muitos trechos, a consulta busca o triplo dos resultados de que precisa e depois mantém só o trecho com melhor pontuação de cada nota. Sem isso, uma única nota extensa ocuparia todas as posições dos resultados.
Os dois limites, e por que são diferentes
A distância de cosseno vai de 0 (mesma direção) para cima. Um resultado só conta como correspondência abaixo de um teto de distância — e o Notez usa dois tetos diferentes para duas tarefas diferentes.
A busca usa 0,62. Um teto rígido.
O painel Notas semelhantes usa 0,8. Um teto folgado.
A diferença não é descuido. Notas semelhantes é um apoio para navegar, no fim de uma nota que você já está lendo; uma correspondência folgada ali custa só uma olhada. Resultados de busca são uma afirmação de que aquelas notas respondem à sua consulta.
Com 0,8, a busca ficou inútil de um jeito que demorou a ser percebido. Uma consulta qualquer correspondia a quase qualquer nota curta — texto curto gera embeddings que ficam perto do meio de tudo —, e por isso um espaço de trabalho com notas nunca conseguia mostrar "Nenhuma nota encontrada". Toda busca retornava alguma coisa. Parte era lixo, e nada separava um caso do outro.
Uma busca que nunca consegue dizer "não tem nada aqui" não é uma busca. É um gerador de notas aleatórias com passos extras.
Como os dois ramos são combinados
Esta é a parte mais simples do que você imaginaria: os resultados por palavra-chave vêm primeiro, em ordem. Depois, os resultados semânticos que ainda não estão na lista são acrescentados ao final.
Só isso. Não há reranking com cross-encoder, nem fusão de pontuações, nem combinação por rank recíproco. Os dois ramos rodam em paralelo, as duplicatas são removidas pelo id da nota, e o ramo de palavra-chave ganha os empates por vir primeiro.
Digo isso com todas as letras porque tem uma consequência que você vai notar: uma correspondência semântica perfeita pode aparecer abaixo de uma correspondência medíocre por palavra-chave. Se a sua consulta for uma substring comum, o topo dos resultados vai ser das correspondências de substring.
Um ranking de fusão de verdade seria uma melhoria real, e não está implementado. O que está implementado é honesto sobre o que é: uma união, não um ranking.
Os filtros valem para os dois ramos
Qualquer filtro que você aplicou — tags, valores de propriedades personalizadas, a lixeira — restringe as duas buscas, não só a de palavra-chave. O ramo semântico leva essas condições para a mesma consulta que calcula a distância vetorial, então filtrar nunca descarta em silêncio a metade semântica dos seus resultados.
O que acontece quando o ramo semântico falha
Gerar o embedding de uma consulta exige uma chamada de rede a um modelo externo. Chamadas de rede falham.
Quando o ramo semântico lança uma exceção, o erro vai para o log e esse ramo devolve uma lista vazia. A busca não dá erro. Você recebe os resultados por palavra-chave, exatamente o que receberia de qualquer app de anotações convencional, e a página é renderizada.
É de propósito: a metade sofisticada de um recurso nunca deve conseguir derrubar a metade sem graça. Cair para a busca simples por palavra-chave é um dia ruim. Mostrar uma página de erro porque um banco de dados vetorial ficou lento é um dia pior.
Notas semelhantes, em resumo
Toda nota tem um painel Notas semelhantes, que funciona um pouco diferente da busca: em vez de gerar o embedding de uma consulta, ele pega o embedding do primeiro trecho da própria nota e encontra os vizinhos mais próximos no mesmo espaço de trabalho, excluindo a própria nota. Até cinco resultados, com o teto mais folgado, de 0,8.
É o recurso que traz à tona coisas que você nunca teria buscado, porque, para buscar, você já precisa suspeitar de que a conexão existe.
Aqui, o limite do espaço de trabalho é garantido por um join com a tabela de notas, em vez de confiar nas linhas de embeddings, que não têm coluna de espaço de trabalho própria. Erre nisso e o espaço de trabalho do seu emprego começa a recomendar suas notas pessoais.
Como buscar bem, na prática
Considerando como os dois ramos dividem o trabalho:
Para algo que você lembra exatamente — um nome, uma mensagem de erro, um identificador —, digite ao pé da letra. O ramo de palavra-chave vai acertar em cheio.
Para algo de que você só se lembra por alto, escreva uma frase curta descrevendo a ideia, não as palavras. "Por que tiramos o retry da fila" funciona melhor do que tentar adivinhar o vocabulário original.
Não escreva consultas longas. Consultas muito longas viram um embedding que é a média de vários temas e não correspondem bem a nada.
Combine busca e filtros. Restringir a uma tag antes de buscar transforma uma consulta ampla numa precisa, e a restrição vale para os dois ramos.
Se você encontrou a nota, mas não o trecho, abra-a e confira Notas semelhantes — muitas vezes é por esse painel que você encontra as outras três notas sobre o assunto.
O que não existe
Para você não descobrir do jeito difícil:
Nada de correspondência aproximada ou tolerante a erros de digitação. Erre a grafia e o ramo de palavra-chave fica em silêncio.
Nada de operadores de busca — sem frases entre aspas, sem
AND/OR, sem sintaxe por campo.Nada de ranking de relevância entre os dois ramos, como descrito acima.
Nada de buscas salvas. Os filtros ficam na URL e podem ir para os favoritos do navegador, mas não existe um recurso de visualizações com nome.
Nada disso é segredo, e nada disso é o motivo de a sua nota ser difícil de encontrar. O motivo quase sempre é o primeiro deste texto: você buscou as palavras, e o que anotou foi a ideia.
Duas buscas unidas resolvem esse problema específico. Não é mágica. É só reconhecer que a memória humana do que você mesmo escreveu é semântica, e uma consulta LIKE não é.
Continue lendo
Links entre anotações que não quebram ao renomear — por que o Notez guarda o link de uma nota como o id do destino, de modo que renomear uma nota não quebra nenhum dos links que apontam para ela.
Dois assistentes, um só conjunto de ferramentas — como o chat de IA e o servidor MCP se distanciaram até ter dezesseis e dez ferramentas, e o bug na árvore de notas que apareceu ao igualá-los.
Notez: um aplicativo de anotações feito para desenvolvedores — o que é o Notez, as formas de uma nota chegar até ele e o que cada plano inclui.