Construindo uma CLI de Busca Semântica com Typer e Sentence-Transformers em Python

Aprenda a criar uma ferramenta CLI em Python usando Typer e sentence-transformers para busca semântica robusta, tipada e com logging profissional.

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:

  1. Interface de linha de comando determinística: Tratamento de argumentos e opções com validação por tipagem estática (Typer + Pydantic/Click).
  2. Extração de embeddings eficiente: Geração e armazenamento vetorial com modelos leves e eficazes, como o all-MiniLM-L6-v2.
  3. Rastreabilidade e logging estruturado: Substituição de saídas de console genéricas (print) por registros configurados via biblioteca padrão logging.

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.”),
corpus
file: Path = typer.Option(
…,
“

Preencha o formulário abaixo para que eu consiga entrar em contato com você.