Você vai construir uma avaliação mínima para uma busca semântica. No fim, terá uma planilha com consultas reais, documentos esperados, resultados recuperados e uma taxa de acerto que pode ser discutida sem depender de impressões.
A ideia é simples: antes de prometer que a busca encontra informação, faça perguntas para as quais você já sabe a resposta. Depois, compare variações do sistema, como diferentes tamanhos de trecho e diferentes modelos de embedding. O resultado não prova a qualidade de forma geral; nesta avaliação inicial, ele serve como uma referência para comparar versões.
Comece pelo conjunto de perguntas
Não comece escolhendo o modelo. Comece escolhendo os casos que a busca precisa resolver.
Crie um arquivo chamado evaluation.json com consultas que representem o uso real. Cada item precisa ter uma pergunta e uma lista de identificadores de documentos aceitáveis. Se mais de um trecho responder corretamente à pergunta, inclua todos eles.
[
{
"query": "Como solicitar reembolso de uma compra?",
"relevantIds": ["refund-policy"]
},
{
"query": "Posso alterar meu endereço depois do pedido?",
"relevantIds": ["order-address", "order-changes"]
}
]
Comece com um conjunto pequeno de consultas representativas se o acervo for pequeno. O importante é cobrir intenções diferentes, não atingir um número mágico.
Inclua perguntas com palavras exatas do documento e perguntas formuladas de outro jeito. Inclua também consultas ambíguas, erros comuns e casos em que a busca lexical deveria vencer. Uma busca semântica não substitui toda correspondência literal. Códigos, nomes internos e identificadores costumam exigir a camada lexical.
Para cada consulta, registre manualmente quais documentos realmente ajudam. Essa revisão é a parte mais importante do conjunto. Se a resposta esperada estiver errada, uma métrica precisa apenas mede um julgamento ruim com aparência matemática.
Faça a versão mínima funcionar
Nesta implementação, cada documento e pergunta é transformado em um vetor; a recuperação compara esses vetores e retorna os mais próximos. A similaridade por cosseno é suficiente para o primeiro teste.
Separe os documentos em um arquivo documents.json:
[
{
"id": "refund-policy",
"text": "Pedidos de reembolso devem ser solicitados em até 30 dias..."
},
{
"id": "order-address",
"text": "O endereço pode ser alterado enquanto o pedido ainda não foi enviado..."
},
{
"id": "order-changes",
"text": "Alguns dados do pedido podem ser corrigidos antes da separação..."
}
]
Agora gere os embeddings e calcule a proximidade localmente. O código abaixo usa o fetch nativo de uma versão recente do Node.js e uma API de embeddings compatível.
import fs from "node:fs/promises";
const apiKey = process.env.EMBEDDING_API_KEY;
if (!apiKey) throw new Error("Set EMBEDDING_API_KEY before running the evaluation");
const documents = JSON.parse(await fs.readFile("documents.json", "utf8"));
const evaluation = JSON.parse(await fs.readFile("evaluation.json", "utf8"));
async function createEmbeddings(input) {
const response = await fetch("https://api.exemplo.com/v1/embeddings", {
method: "POST",
headers: {
Authorization: "Bearer " + apiKey,
"Content-Type": "application/json"
},
body: JSON.stringify({ model: "embedding-model", input })
});
if (!response.ok) throw new Error(await response.text());
const data = await response.json();
return data.data.map((item) => item.embedding);
}
function cosineSimilarity(left, right) {
let dot = 0;
let leftNorm = 0;
let rightNorm = 0;
for (let index = 0; index < left.length; index += 1) {
dot += left[index] * right[index];
leftNorm += left[index] ** 2;
rightNorm += right[index] ** 2;
}
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
}
const documentVectors = await createEmbeddings(documents.map((item) => item.text));
const queryVectors = await createEmbeddings(evaluation.map((item) => item.query));
let hits = 0;
const rows = [];
for (let index = 0; index < evaluation.length; index += 1) {
const ranked = documents
.map((document, documentIndex) => ({
id: document.id,
score: cosineSimilarity(queryVectors[index], documentVectors[documentIndex])
}))
.sort((left, right) => right.score - left.score);
const topResult = ranked[0];
const hit = evaluation[index].relevantIds.includes(topResult.id);
if (hit) hits += 1;
rows.push({
query: evaluation[index].query,
expected: evaluation[index].relevantIds,
returned: topResult.id,
score: topResult.score,
hit
});
}
console.table(rows);
console.log(`Top-1 accuracy: ${hits}/${evaluation.length}`);
A saída final é a sua primeira planilha de acertos. Em um exemplo hipotético, doze acertos em vinte consultas correspondem a 60%. Esse percentual é uma medição do seu conjunto, não uma garantia para todo usuário.
Meça mais do que o primeiro resultado
Top-1 accuracy responde a uma pergunta específica: o primeiro resultado estava entre os documentos aceitáveis?
Para uma interface que mostra vários resultados, calcule também o acerto em top-3 ou top-5. Uma consulta conta como acerto em top-3 quando qualquer documento relevante aparece entre os três primeiros. Essa comparação pode indicar que a recuperação encontra o conteúdo, mas o classifica mal.
Registre ainda os falsos positivos. Um resultado pode parecer relacionado e mesmo assim não responder à pergunta. Esse erro é comum quando dois documentos compartilham vocabulário e o embedding aproxima os temas, mas não distingue a ação necessária.
A qualidade depende da tarefa. Para uma busca que alimenta uma resposta automática, um documento correto em terceiro lugar pode ser insuficiente se o sistema só enviar o primeiro trecho ao modelo de linguagem. Para uma tela que exibe vários resultados, top-5 pode representar melhor a experiência.
Compare segmentações antes de trocar tudo
O tamanho dos trechos muda o comportamento da busca. Trechos longos preservam contexto, mas misturam assuntos. Trechos curtos isolam detalhes, mas podem perder a explicação necessária para interpretar uma frase.
Crie duas versões do mesmo acervo. Na primeira, use parágrafos inteiros. Na segunda, divida por seções menores, preservando título e contexto quando possível. Rode exatamente as mesmas consultas e mantenha a mesma métrica.
Se a segmentação menor aumentar top-1 e reduzir top-5 nesse conjunto, isso não demonstrará um vencedor universal; indicará uma troca entre os dois comportamentos.
Faça o mesmo com os embeddings. Troque apenas uma variável por vez. Se mudar o modelo e o tamanho do trecho simultaneamente, você não saberá o que causou a diferença.
Quando o número já é bom o bastante?
Não existe um percentual universal que autorize o deploy. Uma busca interna de baixo risco tolera uma revisão humana. Uma busca que dispara ações ou alimenta respostas para clientes exige um padrão mais alto e testes específicos para os casos perigosos.
Defina antes o que acontece quando a busca falha. O sistema mostra uma mensagem? Tenta uma busca lexical? Pede esclarecimento? Encaminha para uma pessoa? Sem esse caminho, uma métrica alta pode esconder um erro grave em uma consulta crítica.
Valide também fora da amostra usada para ajustar o sistema. Separe algumas consultas para a verificação final. Se você altera segmentação e modelo até decorar as perguntas conhecidas, a planilha deixa de medir generalização.
O custo merece uma conta simples. Considere um cenário hipotético em que as consultas são enviadas em lotes; o tempo total dependerá da latência e da quantidade de chamadas. Se você gerar embeddings dos documentos a cada execução, pagará essa espera repetidamente; por isso, armazene os vetores dos documentos e recalcule-os apenas quando o conteúdo mudar.
Transforme a planilha em rotina
Salve, para cada execução, a versão da segmentação, o modelo usado, a data da avaliação e os resultados por consulta. A data registra o estado do teste; a análise sobre o que melhorar continua válida depois.
Como próximo passo, adicione consultas reais e revise-as para remover dados sensíveis. Depois, classifique a gravidade de cada erro. Uma queda pequena na média pode ser aceitável se os casos críticos melhorarem. O número que decide o produto não é o mais bonito na coluna final. É o que mostra, com clareza, onde a busca ajuda e onde ainda precisa de uma pessoa olhando.




