Skip to main content

Biblioteca Python para comparação de similaridade de textos em Português Brasileiro, com suporte a entidades numéricas, fonética PT-BR e pipeline de pré-processamento modular.

Project description

Text Similarity PT-BR

CI Pipeline Docs PyPI Python Ruff License: MIT

Uma biblioteca Python otimizada e especializada na comparação de similaridade de textos em português brasileiro (PT-BR). Ideal para sistemas de NLP, chatbots, análise de sentimento e cruzamento de dados onde as peculiaridades do idioma, formatação de dinheiro, fonética regional e medidas influenciam a real intenção e semelhança dos textos.

Recursos Principais

  • Limpeza Especializada (TextCleaner): Expansão de contrações modernas ("vc" -> "você", "fds" -> "fim de semana") e tratamento de acentos focado no nosso idioma.
  • Detecção de Entidades (EntityNormalizer): Extração e preservação inteligente de grandezas antes da "limpeza bruta" que as destruiria. (Ex: converte R$ 30,00 para a tag única <money:30.0>).
    • Dinheiro (R$ 30,00, 30 reais)
    • Datas (12/03/2023, ontem)
    • Dimensões/Pesos (2kg, 10 m)
    • Modelos de Produto (S22 Ultra, iPhone 13 Pro)
  • Pré-processamento Avançado: Tokenização, remoção de stopwords do português, e Lematização (com suporte nativo ao SpaCy pt_core_news_sm).
  • Comparações Híbridas: Algoritmos combinados para ir além das palavras (Bag-of-Words).
    • Cosseno (TF-IDF): Para variação lexical.
    • Distância de Edição (Levenshtein): Rápido, usando rapidfuzz para detectar erros de digitação.
    • Fonética (Metaphone PT-BR adaptado): Trata "cassaa" e "caça" como pesos idênticos.
    • Interseção de Entidades: Lógica de "Curto-Circuito" que garante correspondência (score altíssimo) se a entidade de busca essencial (ex: GN500) for validada intacta em textos mais longos.
  • Pipeline Otimizada (Joblib Cache): Suporte a cache em disco nativo. Textos volumosos já mastigados nas etapas de Regex/SpaCy não gastam processamento de novo.

Requisitos

  • Python: >= 3.8

🚀 Instalação

pip install text-similarity-br

Com suporte a lematização via SpaCy (opcional):

pip install "text-similarity-br[nlp]"
python -m spacy download pt_core_news_sm

Com suporte a Similaridade Semântica (Word Embeddings / Redes Neurais):

pip install "text-similarity-br[semantic]"

📖 Como Usar

A API pública foi desenhada em torno da fachada Comparator, garantindo facilidade sem esconder o poder customizável.

Modo Básico (Rápido e Simples)

Opera apenas sobre Bag-of-Words e correções de grafia (Levenshtein/Cosseno). Ideal para volume de dados altos e textos curtos.

from text_similarity.api import Comparator

comp = Comparator.basic()

score = comp.compare("iphone 13 pro", "iphone pro 13")
print(f"Similaridade: {score:.2f}") # Output ~0.8 a 1.0 depending on weight

Modo "Smart" (Entidades e Fonética)

Ativa nativamente os extratores de Moeda, Data, Dimensões, Modelos de Produto e aplica cálculos fonéticos.

from text_similarity.api import Comparator

comp = Comparator.smart()

novo_score = comp.compare("Foi me cobrado 30 reais", "O preço é R$ 30,00")

print(f"Similaridade Smart: {novo_score:.2f}") 
# Resultado alto por conta da identificação da entidade financeira exata

# --- Novo Recurso: Interseção Perfeita de Modelos (Short-circuit) ---
score_modelo = comp.compare("GN500", "Temos as peças GN 500, GN 1000 e SK 200")
print(f"Score Modelo Embutido: {score_modelo:.2f}")
# Resultado: ~0.95. Ao localizar o modelo procurado "GN500" isolado no meio do 
# texto longo alvo, o algoritmo de intersecção assegura diretamente uma alta 
# pontuação, ignorando todo o resto da string longa que causaria diluição.

Filtrando Entidades Específicas

Por padrão, o modo smart ativa todos os extratores (money, date, dimension, number, product_model). Você pode restringir apenas às entidades relevantes para o seu domínio passando o parâmetro entities:

from text_similarity.api import Comparator

# Apenas modelos de produto — ideal para catálogos de peças técnicas
comp = Comparator.smart(entities=["product_model"])

# Apenas valores monetários — ideal para sistemas financeiros
comp_fin = Comparator.smart(entities=["money", "number"])

# Datas e dimensões — ideal para laudos e fichas técnicas
comp_lab = Comparator.smart(entities=["date", "dimension"])

Dica: Filtrar entidades melhora a precisão evitando falsos positivos. Um extrator de date ativo num catálogo de produtos pode mapear incorretamente SKUs contendo dígitos de ano.

Modo Semântico (Word Embeddings)

Para capturar a real intenção semântica entre sinônimos que não compartilham nenhuma letra (ex: "veículo" vs "carro"), você pode ativar o motor de Sentence-Transformers.

from text_similarity.api import Comparator

# Habilita o uso de modelos densos por debaixo dos panos
comp = Comparator.smart(use_embeddings=True)

score = comp.compare("automóvel bicombustível", "carro flex")
print(f"Similaridade Semântica: {score:.2f}") # Alto score, diferentemente do TF-IDF puro.

Atenção: A primeira chamada em cada processo isolado pode demorar alguns milisegundos a mais para carregar o modelo PyTorch na RAM. Nos métodos de Lote (compare_batch / strategy="parallel"), a Similaridade Semântica age como uma avaliação final super otimizada apenas nos top_n retornados pelo TF-IDF.

Processamento em Lote (Batch)

Para casos de uso onde é necessário comparar uma query contra centenas ou milhares de candidatos, utilize o método compare_batch. Ele é altamente otimizado aplicando matrizes esparsas via Scikit-Learn e descartes (short-circuit) matemáticos. Entregando resultados consolidados até ~48x mais rápido dependendo do volume.

from text_similarity.api import Comparator
comp = Comparator.smart()

busca = "Notebook Dell Inspiron 15"
candidatos = [
    "Dell Inspiron 15 polegadas i5",
    "Notebook Lenovo Thinkpad",
    "Mouse sem fio logitech",
    # ... 10,000 outros itens
]

# Filtra rapidamente por TF-IDF mínimo (0.1) e extrai os 5 melhores
resultados = comp.compare_batch(busca, candidatos, top_n=5, min_cosine=0.1)

for r in resultados:
    print(f"Score: {r['score']:.2f} | Match: {r['candidate']}")

Comparação Multi-Query (compare_many_to_many)

Quando você precisa comparar múltiplas buscas contra o mesmo catálogo de candidatos, use compare_many_to_many. Ele pré-computa a matriz TF-IDF dos candidatos uma única vez, eliminando recálculos redundantes e entregando speedups significativos em cenários de alto volume.

from text_similarity.api import Comparator
comp = Comparator.smart()

buscas = [
    "Notebook Dell Inspiron 15",
    "Mouse sem fio logitech",
    "Monitor Samsung 27 polegadas",
]
candidatos = [
    "Dell Inspiron 15 polegadas i5",
    "Notebook Lenovo Thinkpad",
    "Mouse logitech wireless",
    "Monitor Samsung 27'' 4K",
    # ... milhares de itens
]

# Retorna uma lista de resultados para CADA query
todos_resultados = comp.compare_many_to_many(
    buscas, candidatos, top_n=5, min_cosine=0.1
)

for query, resultados in zip(buscas, todos_resultados):
    print(f"\n🔍 Query: {query}")
    for r in resultados:
        print(f"  Score: {r['score']:.2f} | {r['candidate']}")

Quando usar qual?

  • compare_batch() → 1 query × N candidatos (ex: busca textual de um usuário).
  • compare_many_to_many() → M queries × N candidatos (ex: deduplicação em lote, cruzamento de bases).

Execução Paralela (strategy="parallel")

Para cenários de alto volume (50+ queries × 10k+ candidatos), ative a estratégia paralela que distribui as queries entre múltiplos processos via ProcessPoolExecutor:

from text_similarity.api import Comparator
comp = Comparator.smart()

# Distribui entre 4 processos (padrão: os.cpu_count())
resultados = comp.compare_many_to_many(
    buscas, candidatos, top_n=5, min_cosine=0.1,
    strategy="parallel", n_workers=4,
)

# Funciona também com compare_batch
resultado = comp.compare_batch(
    "busca única", candidatos, top_n=10,
    strategy="parallel", n_workers=4,
)

⚠️ Quando NÃO usar parallel: Para poucos queries (< 20) ou poucos candidatos (< 5k), o overhead de criação de processos pode superar o ganho. Use strategy="vectorized" (padrão) nesses casos.

Integração Async (FastAPI, aiohttp)

Para web servers assíncronos, use os métodos _async que offloadam o trabalho CPU-bound para um ProcessPoolExecutor, mantendo o event loop livre:

from fastapi import FastAPI
from text_similarity import Comparator

app = FastAPI()
comp = Comparator.smart()

@app.post("/search")
async def search(query: str, candidates: list[str]):
    results = await comp.compare_batch_async(
        query, candidates, top_n=10, n_workers=4
    )
    return {"results": results}

@app.post("/bulk-search")
async def bulk_search(queries: list[str], candidates: list[str]):
    results = await comp.compare_many_to_many_async(
        queries, candidates, top_n=5, n_workers=4
    )
    return {"results": results}

Métodos async disponíveis: compare_batch_async() e compare_many_to_many_async(). Ambos usam strategy="parallel" internamente.

Entendendo "Por que" deram Match (Explain)

Às vezes você precisa debugar a intenção do usuário ou mostrar evidências de que o cruzamento de algoritmos detectou semelhança. Use o .explain():

from text_similarity.api import Comparator
comp = Comparator.smart()

detalhes = comp.explain("televisão samsung 55 polegadas", "tv samsung 55\"")

print(detalhes["score"])
# 0.85
print(detalhes["details"])
# {'cosine': 0.82, 'edit': 0.80, 'phonetic': 0.95} -> Foneticamente altíssimo e detectada dimensão de 55.

Comportamento com strings vazias: explain("", "qualquer texto") retorna {"score": 0.0, "details": {}} sem lançar exceção.

Short-circuit no explain(): Quando uma entidade é detectada com interseção total (ex: busca por <productmodel:GN500> encontrada no texto alvo), explain() retorna {"score": 0.95, "details": {"entity": {..., "short_circuit": True}}}, igualmente ao compare().

compare_batch() com lista vazia: comp.compare_batch("qualquer", []) retorna [] imediatamente, sem processamento.

🎯 Interpretação dos Scores

O score retornado varia entre 0.0 (completamente diferentes) e 1.0 (idênticos).

Faixa Interpretação
>= 0.85 Match muito forte — provável duplicata ou variação mínima de descrição
0.60 – 0.84 Match provável — mesmo item com descrição diferente (ex: código com/sem espaço)
0.35 – 0.59 Match incerto — requer revisão manual
< 0.35 Sem relação semântica relevante

Dica: Para domínios com códigos de produto (materiais, SKUs, peças técnicas), um threshold de >= 0.60 é um bom ponto de partida. Calibre com pares conhecidos do seu domínio para ajustar precisão × recall.

Uso Apenas para Tratamento de Texto

Se o seu objetivo não for realizar comparações, mas apenas aproveitar o robusto motor de processamento em português (para limpar bases de dados, treinar modelos, remover acentos, expandir contrações e lematizar), você pode instanciar as etapas da Pipeline de forma autônoma e oficial:

from text_similarity.pipeline.pipeline import PreprocessingPipeline
from text_similarity.pipeline.backends import CleanTextStage, TokenizerStage, StopwordsStage

# Monte seu pipeline customizado apenas com o que precisa:
pipeline = PreprocessingPipeline([
    CleanTextStage(),  # Expansão de contrações ("vc" -> "você"), sem acentos, lowercase
    TokenizerStage(),  # Tokenização segura
    StopwordsStage()   # Remoção de conectivos inúteis do PT-BR
])

texto_bruto = "Limpando meeu texto, crz... vc viu a promo???"
texto_tratado, stats = pipeline.process(texto_bruto)

print(texto_tratado)
# Saída esperada (bag of words tratado): "limpar texto crz ver promo"

📈 Calibração de Pesos (Grid Search)

Para obter a melhor precisão em domínios específicos, você pode calibrar os pesos do algoritmo HybridSimilarity usando o WeightCalibrator. Ele permite testar múltiplas combinações de pesos contra um dataset "Gold Standard" (anotado manualmente) e gera um relatório detalhado de performance comparativa entre precisão e custo de tempo (latência).

from text_similarity.api import Comparator
from text_similarity.tuning.calibrator import WeightCalibrator

comp = Comparator.smart()

# Dataset de teste (Gold Standard)
gold_standard = [
    {"query": "casa", "target": "caza", "match": True},
    {"query": "celular", "target": "fone", "match": False},
]

# Configurações de pesos que você deseja comparar
configs = [
    {"cosine": 0.5, "edit": 0.5},
    {"edit": 1.0},
    {"phonetic": 0.8, "cosine": 0.2},
]

calibrator = WeightCalibrator(comp, configs)
report = calibrator.evaluate(gold_standard)

# Exibe o dashboard de resultados (requer extra 'tuning')
report.summary()

Para habilitar a visualização rica (rich terminal dashboard):

pip install "text-similarity-br[tuning]"

⚙️ Configuração do Cache

A biblioteca mantém um cache in-memory (SHA-256) para evitar reprocessar o mesmo texto várias vezes pelo pipeline. Por padrão, o cache está ativado.

from text_similarity.api import Comparator

# Cache ativado por padrão (padrão)
comp = Comparator.smart(use_cache=True)

# Desativar o cache (útil em ambientes com memória limitada ou testes)
comp_no_cache = Comparator.smart(use_cache=False)

Limpando o Cache Manualmente

Use clear_cache() quando precisar forçar o reprocessamento — por exemplo, depois de alterar as entidades ativas ou ao liberar memória após um lote grande:

comp = Comparator.smart()

# Processa e armazena em cache
comp.compare("produto A", "produto B")

# Libera toda a memória do cache in-memory e limpa o cache em disco (Joblib)
comp.clear_cache()

🔌 Extensibilidade — Registrando Entidades Customizadas

A biblioteca expõe o ExtractorRegistry para registrar extratores de entidade personalizados, sem precisar alterar o código-fonte:

from text_similarity.entities.base import EntityExtractor, EntityMatch
from text_similarity.entities.registry import ExtractorRegistry

class CPFExtractor(EntityExtractor):
    """Exemplo: extrator de CPF para sistemas de RH."""

    def extract(self, text: str) -> list[EntityMatch]:
        import re
        matches = []
        for m in re.finditer(r"\d{3}\.\d{3}\.\d{3}-\d{2}", text):
            matches.append(EntityMatch(
                entity_type="cpf",
                text_matched=m.group(),
                value=m.group().replace(".", "").replace("-", ""),
                start=m.start(),
                end=m.end(),
            ))
        return matches

# Registra o extrator customizado
ExtractorRegistry.register("cpf", CPFExtractor)

# Instancia o Comparator ativando apenas o seu extrator
comp = Comparator.smart(entities=["cpf"])
score = comp.compare("019.283.847-09", "documento cpf 01928384709")

Extratores disponíveis por padrão:

Nome Exemplos detectados
money R$ 30,00, 50 reais, USD 100
date 12/03/2023, ontem, amanhã, 25 de abril
dimension 2kg, 1.5l, 30cm, 10m²
number 3, três, 1000
product_model S22 Ultra, iPhone 13, XJ-900

Contribuindo

Padrões de Qualidade seguidos rigorosamente: Ruff (Lint+Format) e MyPy (Tipoção Forte). Para garantir suas alterações, digite:

uv run ruff check src tests
uv run pytest tests/

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

text_similarity_br-0.3.0.tar.gz (420.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

text_similarity_br-0.3.0-py3-none-any.whl (50.3 kB view details)

Uploaded Python 3

File details

Details for the file text_similarity_br-0.3.0.tar.gz.

File metadata

  • Download URL: text_similarity_br-0.3.0.tar.gz
  • Upload date:
  • Size: 420.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for text_similarity_br-0.3.0.tar.gz
Algorithm Hash digest
SHA256 db21d8e99e8282c7837c7dee16a197237f82d5ac8a603c001ad81961eb1062be
MD5 92d83072499d5746ca64f15cc8eab65a
BLAKE2b-256 d91388ec2a03c647df6b1fb4353581a8526e888620110ffe801997ce467be66a

See more details on using hashes here.

Provenance

The following attestation bundles were made for text_similarity_br-0.3.0.tar.gz:

Publisher: publish.yaml on joscelino/text_similarity

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file text_similarity_br-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for text_similarity_br-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3d27768f61470a195b13229d134522f6beb7116838b2efe741a9b5ffc00e8063
MD5 2ae03e8d1f1c2e6d3141963bf3a287b6
BLAKE2b-256 714fc0ab1bdef172767866942b7634319858eee0fdb9e7d291e3d1ce0d9d1944

See more details on using hashes here.

Provenance

The following attestation bundles were made for text_similarity_br-0.3.0-py3-none-any.whl:

Publisher: publish.yaml on joscelino/text_similarity

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page