Construindo uma CLI de Busca Semântica com Typer e Sentence-Transformers em Python
A busca textual baseada em correspondência exata de palavras-chave (como consultas SQL com LIKE ou expressões regulares) falha com frequência quando os usuários utilizam sinônimos, variações gramaticais ou conceitos correlatos. Para resolver essa limitação, a busca semântica mapeia textos em um espaço vetorial contínuo por meio de modelos de linguagem, permitindo comparar o significado real das sentenças através do cálculo de similaridade por cosseno.
Neste artigo, você aprenderá a estruturar uma aplicação de linha de comando (CLI) modular, idiomática e pronta para execuções automatizadas em lote utilizando a biblioteca Typer combinada ao ecossistema sentence-transformers.
1. Arquitetura da Solução
Uma ferramenta técnica voltada para rotinas automatizadas — como execuções noturnas para indexação ou auditoria de dados — exige três pilares fundamentais:
- Interface de linha de comando determinística: Tratamento de argumentos e opções com validação por tipagem estática (Typer + Pydantic/Click).
- Extração de embeddings eficiente: Geração e armazenamento vetorial com modelos leves e eficazes, como o
all-MiniLM-L6-v2. - Rastreabilidade e logging estruturado: Substituição de saídas de console genéricas (
print) por registros configurados via biblioteca padrãologging.
2. Implementação Passo a Passo
Estrutura do Módulo de Busca
O código abaixo implementa a carga do modelo, a geração de representações vetoriais e a consulta de similaridade com tratamento robusto de tipos e documentação clara.
python
import logging
from typing import List, Tuple
import numpy as np
from sentence_transformers import SentenceTransformer, util
Configuração central de logging
logging.basicConfig(
level=logging.INFO,
format=”%(asctime)s [%(levelname)s] %(name)s: %(message)s”
)
logger = logging.getLogger(“semantic_search”)
class SemanticSearchEngine:
“””Gerencia o ciclo de vida do modelo e executa buscas por similaridade semântica.”””
def __init__(self, model_name: str = "all-MiniLM-L6-v2") -> None:
"""Inicializa o motor de busca carregando o modelo sentence-transformers.
Args:
model_name (str): Identificador do modelo pré-treinado no Hugging Face.
"""
logger.info("Carregando modelo de embeddings: %s", model_name)
self.model = SentenceTransformer(model_name)
def search(
self, query: str, corpus: List[str], top_k: int = 3
) -> List[Tuple[str, float]]:
"""Calcula a similaridade de cosseno entre a query e a base de sentenças.
Args:
query (str): Texto da consulta submetido pelo usuário.
corpus (List[str]): Lista de documentos ou sentenças para comparação.
top_k (int): Quantidade de resultados mais próximos a retornar.
Returns:
List[Tuple[str, float]]: Pares contendo a sentença e seu respectivo score.
"""
if not corpus:
logger.warning("Corpus de busca vazio fornecido para consulta.")
return []
logger.info("Calculando embeddings para query e corpus (%d itens)...", len(corpus))
query_embedding = self.model.encode(query, convert_to_tensor=True)
corpus_embeddings = self.model.encode(corpus, convert_to_tensor=True)
# Cálculo de similaridade por cosseno com utilitários da biblioteca
scores = util.cos_sim(query_embedding, corpus_embeddings)[0]
top_results = np.argpartition(-scores.cpu().numpy(), range(min(top_k, len(corpus))))[:top_k]
results = [
(corpus[idx], float(scores[idx]))
for idx in top_results
]
results.sort(key=lambda item: item[1], reverse=True)
return results
Construção do CLI com Typer
A biblioteca Typer aproveita as anotações de tipo do Python moderno para gerar documentação de ajuda e validação sem redundância de código.
python
from pathlib import Path
from typing import Optional
import typer
app = typer.Typer(
help=”CLI para execução de busca semântica em lote ou interativa.”,
add_completion=False
)
@app.command()
def query(
searchterm: str = typer.Argument(…, help=”Termo de busca a ser analisado.”),
corpusfile: Path = typer.Option(
…,
“


