Ir para o conteúdo

Roteador

src.roteador

Roteador de intencoes do Pede AI.

Classifica mensagens do usuario em intencoes pre-definidas utilizando cadeia de estrategias: lookup direto, RAG com embeddings e LLM como fallback.

Example
from src.roteador import ClassificadorIntencoes, ResultadoClassificacao

# classificador = ClassificadorIntencoes(llm, embedding_service, config, ...)
# resultado = classificador.classificar('quero um xbacon')
# resultado.intent  # 'pedir'

ResultadoClassificacao(intent: str, confidence: float, caminho: Literal['lookup', 'rag_forte', 'llm_rag', 'llm_fixo'], top1_texto: str, top1_intencao: str, mensagem_norm: str, metadados: dict = dict()) dataclass

Resultado completo da classificacao de intencao.

Attributes:

Name Type Description
intent str

Nome da intencao classificada.

confidence float

Nivel de confianca (0.0 a 1.0).

caminho Literal['lookup', 'rag_forte', 'llm_rag', 'llm_fixo']

Estrategia usada ('lookup', 'rag_forte', 'llm_rag', 'llm_fixo').

top1_texto str

Texto do exemplo mais similar.

top1_intencao str

Intencao do exemplo mais similar.

mensagem_norm str

Mensagem original normalizada.

metadados dict

Rastro de cada camada para debug (opcional).

__hash__() -> int

Hash baseado em campos hashable (ignora metadados dict).

Source code in src/roteador/modelos.py
def __hash__(self) -> int:
    """Hash baseado em campos hashable (ignora metadados dict)."""
    return hash(
        (
            self.intent,
            self.confidence,
            self.caminho,
            self.top1_texto,
            self.top1_intencao,
            self.mensagem_norm,
            tuple(sorted(self.metadados.items())),
        )
    )

ClassificadorIntencoes(llm: LLMProvider, embedding_service: EmbeddingService, config: RoteadorConfig, prompt_template: str, intencoes_validas: list[str])

Classificador de intencoes com cadeia lookup -> RAG -> LLM.

Orquestra tres estrategias de classificacao em ordem de confianca: 1. Lookup direto de tokens unicos (exato, confianca 1.0) 2. RAG com similaridade de embeddings (probabilistico) 3. LLM puro como fallback definitivo (sempre retorna algo)

Inicializa o classificador com todos os componentes.

Parameters:

Name Type Description Default
llm LLMProvider

Provider concreto de LLM para validacao e fallback.

required
embedding_service EmbeddingService

Servico de embeddings para busca RAG.

required
config RoteadorConfig

Configuracao com thresholds e prioridades.

required
prompt_template str

Template do prompt de classificacao LLM.

required
intencoes_validas list[str]

Lista de intents validas para validacao.

required
Source code in src/roteador/service.py
def __init__(
    self,
    llm: LLMProvider,
    embedding_service: EmbeddingService,
    config: RoteadorConfig,
    prompt_template: str,
    intencoes_validas: list[str],
) -> None:
    """Inicializa o classificador com todos os componentes.

    Args:
        llm: Provider concreto de LLM para validacao e fallback.
        embedding_service: Servico de embeddings para busca RAG.
        config: Configuracao com thresholds e prioridades.
        prompt_template: Template do prompt de classificacao LLM.
        intencoes_validas: Lista de intents validas para validacao.
    """
    self._lookup = ClassificadorLookup(TOKENS_UNICOS)
    self._rag = ClassificadorRAG(
        embedding_service=embedding_service,
        config=config,
        llm=llm,
        prompt_template=prompt_template,
        intencoes_validas=intencoes_validas,
    )
    self._llm = ClassificadorLLM(
        llm=llm,
        prompt_template=prompt_template,
        intencoes_validas=intencoes_validas,
    )
    self._config = config

classificar(mensagem: str) -> ResultadoClassificacao

Classifica a intencao da mensagem usando cadeia de estrategias.

Tenta lookup direto, depois RAG, e por fim LLM como fallback.

Parameters:

Name Type Description Default
mensagem str

Texto bruto da mensagem do usuario.

required

Returns:

Type Description
ResultadoClassificacao

ResultadoClassificacao com intent, confianca e metadata.

Example
resultado = classificador.classificar('oi')
resultado.intent  # 'saudacao'
resultado.caminho  # 'lookup'
Source code in src/roteador/service.py
def classificar(self, mensagem: str) -> ResultadoClassificacao:
    """Classifica a intencao da mensagem usando cadeia de estrategias.

    Tenta lookup direto, depois RAG, e por fim LLM como fallback.

    Args:
        mensagem: Texto bruto da mensagem do usuario.

    Returns:
        ResultadoClassificacao com intent, confianca e metadata.

    Example:
        ```python
        resultado = classificador.classificar('oi')
        resultado.intent  # 'saudacao'
        resultado.caminho  # 'lookup'
        ```
    """
    mensagem_norm = self._normalizar(mensagem)
    meta: dict = {}

    # Mensagem vazia ou so espacos: LLM fallback
    if not mensagem_norm or not mensagem_norm.strip():
        resultado = self._llm.classificar(mensagem)
        meta['llm_raw'] = resultado.metadados.get('llm_raw', '')
        meta['llm_intent'] = resultado.metadados.get('llm_intent', '')
        return ResultadoClassificacao(
            **{
                'intent': resultado.intent,
                'confidence': resultado.confidence,
                'caminho': resultado.caminho,
                'top1_texto': resultado.top1_texto,
                'top1_intencao': resultado.top1_intencao,
                'mensagem_norm': resultado.mensagem_norm,
                'metadados': meta,
            }
        )

    # 1. Lookup direto
    resultado = self._lookup.classificar(mensagem_norm)
    if resultado is not None:
        return resultado

    meta['lookup'] = None

    # 2. RAG
    resultado = self._rag.classificar(mensagem_norm)
    if resultado is not None:
        return resultado

    meta['rag'] = None

    # 3. LLM fallback
    resultado = self._llm.classificar(mensagem_norm)
    meta['llm_raw'] = resultado.metadados.get('llm_raw', '')
    meta['llm_intent'] = resultado.metadados.get('llm_intent', '')
    return ResultadoClassificacao(
        **{
            'intent': resultado.intent,
            'confidence': resultado.confidence,
            'caminho': resultado.caminho,
            'top1_texto': resultado.top1_texto,
            'top1_intencao': resultado.top1_intencao,
            'mensagem_norm': resultado.mensagem_norm,
            'metadados': meta,
        }
    )

classificar_simples(mensagem: str) -> str

API compativel — retorna so a intent.

Mantem compatibilidade com a API antiga classificar_intencao(mensagem) -> str.

Parameters:

Name Type Description Default
mensagem str

Texto bruto da mensagem do usuario.

required

Returns:

Type Description
str

Nome da intencao classificada.

Example
classificador.classificar_simples('quero xbacon')
'pedir'
Source code in src/roteador/service.py
def classificar_simples(self, mensagem: str) -> str:
    """API compativel — retorna so a intent.

    Mantem compatibilidade com a API antiga
    classificar_intencao(mensagem) -> str.

    Args:
        mensagem: Texto bruto da mensagem do usuario.

    Returns:
        Nome da intencao classificada.

    Example:
        ```python
        classificador.classificar_simples('quero xbacon')
        'pedir'
        ```
    """
    return self.classificar(mensagem).intent

src.roteador.service

Orquestrador do classificador de intencoes.

Coordena a cadeia de classificadores: lookup -> RAG -> LLM fallback. Ponto de entrada principal para classificacao de intencoes.

Example
from src.roteador.service import ClassificadorIntencoes

classificador = ClassificadorIntencoes(
    llm, embedding_service, config, prompt, intencoes
)
resultado = classificador.classificar('quero um xbacon')
resultado.intent  # 'pedir'

ClassificadorIntencoes(llm: LLMProvider, embedding_service: EmbeddingService, config: RoteadorConfig, prompt_template: str, intencoes_validas: list[str])

Classificador de intencoes com cadeia lookup -> RAG -> LLM.

Orquestra tres estrategias de classificacao em ordem de confianca: 1. Lookup direto de tokens unicos (exato, confianca 1.0) 2. RAG com similaridade de embeddings (probabilistico) 3. LLM puro como fallback definitivo (sempre retorna algo)

Inicializa o classificador com todos os componentes.

Parameters:

Name Type Description Default
llm LLMProvider

Provider concreto de LLM para validacao e fallback.

required
embedding_service EmbeddingService

Servico de embeddings para busca RAG.

required
config RoteadorConfig

Configuracao com thresholds e prioridades.

required
prompt_template str

Template do prompt de classificacao LLM.

required
intencoes_validas list[str]

Lista de intents validas para validacao.

required
Source code in src/roteador/service.py
def __init__(
    self,
    llm: LLMProvider,
    embedding_service: EmbeddingService,
    config: RoteadorConfig,
    prompt_template: str,
    intencoes_validas: list[str],
) -> None:
    """Inicializa o classificador com todos os componentes.

    Args:
        llm: Provider concreto de LLM para validacao e fallback.
        embedding_service: Servico de embeddings para busca RAG.
        config: Configuracao com thresholds e prioridades.
        prompt_template: Template do prompt de classificacao LLM.
        intencoes_validas: Lista de intents validas para validacao.
    """
    self._lookup = ClassificadorLookup(TOKENS_UNICOS)
    self._rag = ClassificadorRAG(
        embedding_service=embedding_service,
        config=config,
        llm=llm,
        prompt_template=prompt_template,
        intencoes_validas=intencoes_validas,
    )
    self._llm = ClassificadorLLM(
        llm=llm,
        prompt_template=prompt_template,
        intencoes_validas=intencoes_validas,
    )
    self._config = config

classificar(mensagem: str) -> ResultadoClassificacao

Classifica a intencao da mensagem usando cadeia de estrategias.

Tenta lookup direto, depois RAG, e por fim LLM como fallback.

Parameters:

Name Type Description Default
mensagem str

Texto bruto da mensagem do usuario.

required

Returns:

Type Description
ResultadoClassificacao

ResultadoClassificacao com intent, confianca e metadata.

Example
resultado = classificador.classificar('oi')
resultado.intent  # 'saudacao'
resultado.caminho  # 'lookup'
Source code in src/roteador/service.py
def classificar(self, mensagem: str) -> ResultadoClassificacao:
    """Classifica a intencao da mensagem usando cadeia de estrategias.

    Tenta lookup direto, depois RAG, e por fim LLM como fallback.

    Args:
        mensagem: Texto bruto da mensagem do usuario.

    Returns:
        ResultadoClassificacao com intent, confianca e metadata.

    Example:
        ```python
        resultado = classificador.classificar('oi')
        resultado.intent  # 'saudacao'
        resultado.caminho  # 'lookup'
        ```
    """
    mensagem_norm = self._normalizar(mensagem)
    meta: dict = {}

    # Mensagem vazia ou so espacos: LLM fallback
    if not mensagem_norm or not mensagem_norm.strip():
        resultado = self._llm.classificar(mensagem)
        meta['llm_raw'] = resultado.metadados.get('llm_raw', '')
        meta['llm_intent'] = resultado.metadados.get('llm_intent', '')
        return ResultadoClassificacao(
            **{
                'intent': resultado.intent,
                'confidence': resultado.confidence,
                'caminho': resultado.caminho,
                'top1_texto': resultado.top1_texto,
                'top1_intencao': resultado.top1_intencao,
                'mensagem_norm': resultado.mensagem_norm,
                'metadados': meta,
            }
        )

    # 1. Lookup direto
    resultado = self._lookup.classificar(mensagem_norm)
    if resultado is not None:
        return resultado

    meta['lookup'] = None

    # 2. RAG
    resultado = self._rag.classificar(mensagem_norm)
    if resultado is not None:
        return resultado

    meta['rag'] = None

    # 3. LLM fallback
    resultado = self._llm.classificar(mensagem_norm)
    meta['llm_raw'] = resultado.metadados.get('llm_raw', '')
    meta['llm_intent'] = resultado.metadados.get('llm_intent', '')
    return ResultadoClassificacao(
        **{
            'intent': resultado.intent,
            'confidence': resultado.confidence,
            'caminho': resultado.caminho,
            'top1_texto': resultado.top1_texto,
            'top1_intencao': resultado.top1_intencao,
            'mensagem_norm': resultado.mensagem_norm,
            'metadados': meta,
        }
    )

classificar_simples(mensagem: str) -> str

API compativel — retorna so a intent.

Mantem compatibilidade com a API antiga classificar_intencao(mensagem) -> str.

Parameters:

Name Type Description Default
mensagem str

Texto bruto da mensagem do usuario.

required

Returns:

Type Description
str

Nome da intencao classificada.

Example
classificador.classificar_simples('quero xbacon')
'pedir'
Source code in src/roteador/service.py
def classificar_simples(self, mensagem: str) -> str:
    """API compativel — retorna so a intent.

    Mantem compatibilidade com a API antiga
    classificar_intencao(mensagem) -> str.

    Args:
        mensagem: Texto bruto da mensagem do usuario.

    Returns:
        Nome da intencao classificada.

    Example:
        ```python
        classificador.classificar_simples('quero xbacon')
        'pedir'
        ```
    """
    return self.classificar(mensagem).intent

src.roteador.modelos

Value objects imutaveis para classificacao de intencoes.

Todos os models sao frozen dataclasses — representam valores, nao entidades.

Example
from src.roteador.modelos import ResultadoClassificacao, ExemploSimilar

resultado = ResultadoClassificacao(
    intent='pedir',
    confidence=0.95,
    caminho='rag_forte',
    top1_texto='quero um xbacon',
    top1_intencao='pedir',
    mensagem_norm='quero um xbacon',
)
resultado.intent
'pedir'

ResultadoClassificacao(intent: str, confidence: float, caminho: Literal['lookup', 'rag_forte', 'llm_rag', 'llm_fixo'], top1_texto: str, top1_intencao: str, mensagem_norm: str, metadados: dict = dict()) dataclass

Resultado completo da classificacao de intencao.

Attributes:

Name Type Description
intent str

Nome da intencao classificada.

confidence float

Nivel de confianca (0.0 a 1.0).

caminho Literal['lookup', 'rag_forte', 'llm_rag', 'llm_fixo']

Estrategia usada ('lookup', 'rag_forte', 'llm_rag', 'llm_fixo').

top1_texto str

Texto do exemplo mais similar.

top1_intencao str

Intencao do exemplo mais similar.

mensagem_norm str

Mensagem original normalizada.

metadados dict

Rastro de cada camada para debug (opcional).

__hash__() -> int

Hash baseado em campos hashable (ignora metadados dict).

Source code in src/roteador/modelos.py
def __hash__(self) -> int:
    """Hash baseado em campos hashable (ignora metadados dict)."""
    return hash(
        (
            self.intent,
            self.confidence,
            self.caminho,
            self.top1_texto,
            self.top1_intencao,
            self.mensagem_norm,
            tuple(sorted(self.metadados.items())),
        )
    )

ExemploClassificacao(texto: str, intencao: str) dataclass

Exemplo de treinamento para RAG.

Attributes:

Name Type Description
texto str

Texto da mensagem de exemplo.

intencao str

Intencao correta rotulada.

ExemploSimilar(texto: str, intencao: str, similaridade: float) dataclass

Exemplo similar encontrado via embedding.

Attributes:

Name Type Description
texto str

Texto da mensagem de exemplo.

intencao str

Intencao rotulada do exemplo.

similaridade float

Similaridade cosseno com a query (0.0 a 1.0).

src.roteador.protocolos

Protocolos (interfaces) para providers de LLM e Embedding.

Define contratos que qualquer implementacao concreta deve seguir. Usa typing.Protocol para duck typing — nao requer heranca.

Example
from src.roteador.protocolos import LLMProvider


class MeuProvider:
    def completar(self, prompt: str, max_tokens: int = 10) -> str:
        return 'minha resposta'


# MeuProvider e compativel com LLMProvider automaticamente
def usar(provider: LLMProvider) -> str:
    return provider.completar('teste')

LLMProvider

Bases: Protocol

Interface para qualquer provedor de LLM.

Qualquer classe com o metodo 'completar' e compativel, independente de heranca (duck typing via Protocol).

completar(prompt: str, max_tokens: int = 10) -> str

Envia prompt ao LLM e retorna resposta texto.

Parameters:

Name Type Description Default
prompt str

Texto do prompt para o LLM.

required
max_tokens int

Maximo de tokens na resposta.

10

Returns:

Type Description
str

Texto da resposta do LLM.

Source code in src/roteador/protocolos.py
def completar(self, prompt: str, max_tokens: int = 10) -> str:
    """Envia prompt ao LLM e retorna resposta texto.

    Args:
        prompt: Texto do prompt para o LLM.
        max_tokens: Maximo de tokens na resposta.

    Returns:
        Texto da resposta do LLM.
    """
    ...

EmbeddingProvider

Bases: Protocol

Interface para qualquer servico de embeddings.

Qualquer classe com os metodos 'embed' e 'embed_batch' e compativel, independente de heranca.

embed(texto: str) -> list[float]

Gera embedding para um texto.

Parameters:

Name Type Description Default
texto str

Texto para gerar embedding.

required

Returns:

Type Description
list[float]

Lista de floats representando o embedding.

Source code in src/roteador/protocolos.py
def embed(self, texto: str) -> list[float]:
    """Gera embedding para um texto.

    Args:
        texto: Texto para gerar embedding.

    Returns:
        Lista de floats representando o embedding.
    """
    ...

embed_batch(textos: list[str]) -> list[list[float]]

Gera embeddings para multiplos textos.

Parameters:

Name Type Description Default
textos list[str]

Lista de textos para gerar embeddings.

required

Returns:

Type Description
list[list[float]]

Lista de embeddings (lista de listas de floats).

Source code in src/roteador/protocolos.py
def embed_batch(self, textos: list[str]) -> list[list[float]]:
    """Gera embeddings para multiplos textos.

    Args:
        textos: Lista de textos para gerar embeddings.

    Returns:
        Lista de embeddings (lista de listas de floats).
    """
    ...

src.roteador.embedding_service

Servico de embeddings com cache e busca por similaridade.

Gerencia exemplos, embeddings cacheados e busca por similaridade cosseno via numpy.

Example
from src.infra.embedding_providers import SentenceTransformerEmbeddings
from src.roteador.embedding_service import EmbeddingService

provider = SentenceTransformerEmbeddings()
service = EmbeddingService(provider, exemplos_path, cache_path)
service.buscar_similares('quero um lanche')

EmbeddingService(provider: EmbeddingProvider, exemplos_path: Path, cache_path: Path)

Gerencia exemplos, embeddings e busca por similaridade.

Carrega exemplos de JSON e embeddings do cache na inicializacao. Gera embeddings sob demanda via provider injetado.

Inicializa o servico de embeddings.

Parameters:

Name Type Description Default
provider EmbeddingProvider

Provider concreto de embeddings.

required
exemplos_path Path

Caminho do arquivo JSON de exemplos.

required
cache_path Path

Caminho do arquivo JSON de embeddings cacheados.

required
Source code in src/roteador/embedding_service.py
def __init__(
    self,
    provider: EmbeddingProvider,
    exemplos_path: Path,
    cache_path: Path,
) -> None:
    """Inicializa o servico de embeddings.

    Args:
        provider: Provider concreto de embeddings.
        exemplos_path: Caminho do arquivo JSON de exemplos.
        cache_path: Caminho do arquivo JSON de embeddings cacheados.
    """
    self._provider = provider
    self._exemplos_path = exemplos_path
    self._cache_path = cache_path
    self._exemplos: list[ExemploClassificacao] = []
    self._embeddings_dict: dict[str, list[float]] = {}
    self._embeddings: list[list[float]] = []
    self._exemplo_indices: list[int] = []
    self._carregar()

exemplos: list[ExemploClassificacao] property

Retorna lista de exemplos carregados.

tem_embeddings: bool property

Retorna True se ha embeddings carregados.

buscar_similares(mensagem: str, top_k: int = 5, min_similarity: float = 0.55) -> list[ExemploSimilar]

Busca top-k exemplos mais similares a mensagem.

Parameters:

Name Type Description Default
mensagem str

Texto da mensagem do usuario.

required
top_k int

Numero de resultados a retornar.

5
min_similarity float

Similaridade minima para incluir exemplo.

0.55

Returns:

Type Description
list[ExemploSimilar]

Lista de ExemploSimilar ordenada por similaridade decrescente.

Source code in src/roteador/embedding_service.py
def buscar_similares(
    self,
    mensagem: str,
    top_k: int = 5,
    min_similarity: float = 0.55,
) -> list[ExemploSimilar]:
    """Busca top-k exemplos mais similares a mensagem.

    Args:
        mensagem: Texto da mensagem do usuario.
        top_k: Numero de resultados a retornar.
        min_similarity: Similaridade minima para incluir exemplo.

    Returns:
        Lista de ExemploSimilar ordenada por similaridade decrescente.
    """
    if not self._exemplos or not self._embeddings:
        return []

    query_emb = np.array(self._provider.embed(mensagem))
    embeddings_arr = np.array(self._embeddings)

    dot = np.dot(embeddings_arr, query_emb)
    norms = np.linalg.norm(embeddings_arr, axis=1) * np.linalg.norm(query_emb)
    similarities = np.divide(dot, norms, out=np.zeros_like(dot), where=norms != 0)

    top_indices = np.argsort(similarities)[::-1][:top_k]

    results = [
        ExemploSimilar(
            texto=self._exemplos[self._exemplo_indices[idx]].texto,
            intencao=self._exemplos[self._exemplo_indices[idx]].intencao,
            similaridade=float(similarities[idx]),
        )
        for idx in top_indices
    ]

    return [r for r in results if r.similaridade >= min_similarity]

gerar_embedding(texto: str) -> list[float]

Gera embedding para um texto via provider.

Parameters:

Name Type Description Default
texto str

Texto para gerar embedding.

required

Returns:

Type Description
list[float]

Lista de floats representando o embedding.

Source code in src/roteador/embedding_service.py
def gerar_embedding(self, texto: str) -> list[float]:
    """Gera embedding para um texto via provider.

    Args:
        texto: Texto para gerar embedding.

    Returns:
        Lista de floats representando o embedding.
    """
    return self._provider.embed(texto)

atualizar_cache() -> None

Regenera embeddings faltando e salva cache.

Gera embeddings apenas para exemplos cujos hashes nao estao no cache, e salva no formato 2: {"format": 2, "embeddings": {hash: emb, ...}}.

Source code in src/roteador/embedding_service.py
def atualizar_cache(self) -> None:
    """Regenera embeddings faltando e salva cache.

    Gera embeddings apenas para exemplos cujos hashes nao estao no cache,
    e salva no formato 2: ``{"format": 2, "embeddings": {hash: emb, ...}}``.
    """
    # Descobrir quais exemplos nao tem embedding
    faltantes: list[ExemploClassificacao] = []
    for ex in self._exemplos:
        h = _hash_texto(ex.texto)
        if h not in self._embeddings_dict:
            faltantes.append(ex)

    if not faltantes:
        return

    # Gera embeddings faltantes
    textos_faltantes = [ex.texto for ex in faltantes]
    novos = self._provider.embed_batch(textos_faltantes)

    # Adiciona ao dict
    for ex, emb in zip(faltantes, novos, strict=True):
        h = _hash_texto(ex.texto)
        self._embeddings_dict[h] = emb

    # Re-monta lista alinhada
    self._embeddings, self._exemplo_indices = self._montar_lista_alinhada()

    # Salva cache no formato 2
    self._cache_path.parent.mkdir(parents=True, exist_ok=True)
    with open(self._cache_path, 'w', encoding='utf-8') as f:
        json.dump({'format': 2, 'embeddings': self._embeddings_dict}, f)

src.roteador.voting

Estrategia de votacao consolidada para classificacao RAG.

Consolida as 4 funcoes antigas (votacao_max, hybrid, com_prioridade, wrapper) em uma unica funcao com logica clara.

Example
from src.roteador.voting import votar_com_prioridade
from src.roteador.modelos import ExemploSimilar

exemplos = [
    ExemploSimilar('oi', 'saudacao', 0.90),
    ExemploSimilar('quero lanche', 'pedir', 0.75),
]
votar_com_prioridade(exemplos, {'pedir', 'carrinho'})
'saudacao'

votar_com_prioridade(exemplos: list[ExemploSimilar], alta_prioridade: frozenset[str], min_similarity: float = 0.55) -> str

Votacao com prioridade de intents no top-K.

Regras (em ordem): 1. Se top-1 >= 0.98: confia direto no top-1 (match quase identico). 2. Se ha intent de alta prioridade no top-K com similaridade >= min_similarity: retorna a de maior similaridade entre as prioritarias. 3. Fallback: maioria simples (voto majoritario).

Parameters:

Name Type Description Default
exemplos list[ExemploSimilar]

Lista de exemplos similares ordenados por similaridade decrescente.

required
alta_prioridade frozenset[str]

Conjunto de intents de alta prioridade.

required
min_similarity float

Similaridade minima para considerar intent prioritaria.

0.55

Returns:

Type Description
str

Nome da intencao vencedora ou 'desconhecido' se lista vazia.

Example
exemplos = [
    ExemploSimilar('bom dia', 'saudacao', 0.92),
    ExemploSimilar('quero xbacon', 'pedir', 0.78),
]
votar_com_prioridade(exemplos, {'pedir', 'carrinho'})
'pedir'
Source code in src/roteador/voting.py
def votar_com_prioridade(
    exemplos: list[ExemploSimilar],
    alta_prioridade: frozenset[str],
    min_similarity: float = 0.55,
) -> str:
    """Votacao com prioridade de intents no top-K.

    Regras (em ordem):
    1. Se top-1 >= 0.98: confia direto no top-1 (match quase identico).
    2. Se ha intent de alta prioridade no top-K com similaridade >= min_similarity:
       retorna a de maior similaridade entre as prioritarias.
    3. Fallback: maioria simples (voto majoritario).

    Args:
        exemplos: Lista de exemplos similares ordenados por similaridade decrescente.
        alta_prioridade: Conjunto de intents de alta prioridade.
        min_similarity: Similaridade minima para considerar intent prioritaria.

    Returns:
        Nome da intencao vencedora ou 'desconhecido' se lista vazia.

    Example:
        ```python
        exemplos = [
            ExemploSimilar('bom dia', 'saudacao', 0.92),
            ExemploSimilar('quero xbacon', 'pedir', 0.78),
        ]
        votar_com_prioridade(exemplos, {'pedir', 'carrinho'})
        'pedir'
        ```
    """
    if not exemplos:
        return 'desconhecido'

    # Regra 1: confianca absoluta no top-1
    if exemplos[0].similaridade >= _TOP1_CONFIANCA:
        return exemplos[0].intencao

    # Regra 2: alta prioridade no top-K
    melhor_prioritaria: str | None = None
    melhor_sim_prioritaria = 0.0

    for ex in exemplos:
        if ex.intencao in alta_prioridade and ex.similaridade > melhor_sim_prioritaria:
            melhor_prioritaria = ex.intencao
            melhor_sim_prioritaria = ex.similaridade

    if melhor_prioritaria and melhor_sim_prioritaria >= min_similarity:
        return melhor_prioritaria

    # Regra 3: maioria simples
    votos = Counter(ex.intencao for ex in exemplos)
    return votos.most_common(1)[0][0]

src.roteador.classificadores.base

Classificadores de intencao — base abstrata.

Define a interface comum para todos os classificadores.

Example
from abc import ABC, abstractmethod
from src.roteador.classificadores.base import ClassificadorBase

ClassificadorBase

Bases: ABC

Base para classificadores de intencao.

Cada classificador implementa a logica de uma estrategia especifica. Retorna ResultadoClassificacao se conseguir classificar, None se a mensagem nao se encaixa nesta estrategia.

classificar(mensagem: str) -> ResultadoClassificacao | None abstractmethod

Classifica a mensagem.

Parameters:

Name Type Description Default
mensagem str

Texto normalizado do usuario.

required

Returns:

Type Description
ResultadoClassificacao | None

ResultadoClassificacao se conseguir classificar,

ResultadoClassificacao | None

None se nao for responsabilidade deste classificador.

Source code in src/roteador/classificadores/base.py
@abstractmethod
def classificar(self, mensagem: str) -> ResultadoClassificacao | None:
    """Classifica a mensagem.

    Args:
        mensagem: Texto normalizado do usuario.

    Returns:
        ResultadoClassificacao se conseguir classificar,
        None se nao for responsabilidade deste classificador.
    """
    ...

src.roteador.classificadores.lookup

Classificador por lookup direto de tokens unicos.

Para palavras isoladas como 'sim', 'nao', 'oi', 'cancela' — match exato e mais confiavel que embedding.

Example
from src.roteador.classificadores.lookup import ClassificadorLookup

lookup = ClassificadorLookup({'oi': 'saudacao', 'sim': 'confirmar'})
resultado = lookup.classificar('oi')
resultado.intent  # 'saudacao'

ClassificadorLookup(tokens_unicos: dict[str, str] | None = None)

Bases: ClassificadorBase

Lookup direto de tokens unicos.

Faz match exato da mensagem normalizada contra um dicionario de tokens unicos. Se encontrar, retorna com confianca 1.0.

Inicializa o classificador de lookup.

Parameters:

Name Type Description Default
tokens_unicos dict[str, str] | None

Dicionario token -> intencao. Usa TOKENS_UNICOS padrao se None.

None
Source code in src/roteador/classificadores/lookup.py
def __init__(self, tokens_unicos: dict[str, str] | None = None) -> None:
    """Inicializa o classificador de lookup.

    Args:
        tokens_unicos: Dicionario token -> intencao.
            Usa TOKENS_UNICOS padrao se None.
    """
    self._tokens = tokens_unicos or TOKENS_UNICOS

classificar(mensagem: str) -> ResultadoClassificacao | None

Tenta classificar por match exato.

Parameters:

Name Type Description Default
mensagem str

Texto normalizado do usuario.

required

Returns:

Type Description
ResultadoClassificacao | None

ResultadoClassificacao se match encontrado, None caso contrario.

Example
lookup = ClassificadorLookup()
lookup.classificar('oi')
ResultadoClassificacao(intent='saudacao', confidence=1.0, ...)
Source code in src/roteador/classificadores/lookup.py
def classificar(self, mensagem: str) -> ResultadoClassificacao | None:
    """Tenta classificar por match exato.

    Args:
        mensagem: Texto normalizado do usuario.

    Returns:
        ResultadoClassificacao se match encontrado, None caso contrario.

    Example:
        ```python
        lookup = ClassificadorLookup()
        lookup.classificar('oi')
        ResultadoClassificacao(intent='saudacao', confidence=1.0, ...)
        ```
    """
    texto = mensagem.strip().lower()

    if texto not in self._tokens:
        return None

    intencao = self._tokens[texto]

    return ResultadoClassificacao(
        intent=intencao,
        confidence=1.0,
        caminho='lookup',
        top1_texto=texto,
        top1_intencao=intencao,
        mensagem_norm=texto,
        metadados={'lookup': intencao},
    )

src.roteador.classificadores.rag

Classificador por similaridade de embeddings (RAG).

Busca exemplos similares via embedding, vota com prioridade, e valida com LLM quando confianca e media.

Example
from src.roteador.classificadores.rag import ClassificadorRAG

rag = ClassificadorRAG(embedding_service, config, llm)
resultado = rag.classificar('quero um lanche')

ClassificadorRAG(embedding_service: EmbeddingService, config: RoteadorConfig, llm: LLMProvider, prompt_template: str, intencoes_validas: list[str])

Bases: ClassificadorBase

Classificacao por similaridade de embeddings (RAG).

Fluxo: 1. Busca top-K exemplos similares via EmbeddingService. 2. Se nenhum similar >= min_similarity: retorna None (proximo classificador). 3. Se confidence >= rag_forte_threshold: vota e retorna direto. 4. Se confidence >= rag_fraco_threshold: valida com LLM e retorna. 5. Se confidence < rag_fraco_threshold: retorna None (LLM fallback assume).

Inicializa o classificador RAG.

Parameters:

Name Type Description Default
embedding_service EmbeddingService

Servico de embeddings para busca.

required
config RoteadorConfig

Configuracao do roteador com thresholds.

required
llm LLMProvider

Provider LLM para validacao.

required
prompt_template str

Template do prompt de classificacao.

required
intencoes_validas list[str]

Lista de intents validas.

required
Source code in src/roteador/classificadores/rag.py
def __init__(
    self,
    embedding_service: EmbeddingService,
    config: RoteadorConfig,
    llm: LLMProvider,
    prompt_template: str,
    intencoes_validas: list[str],
) -> None:
    """Inicializa o classificador RAG.

    Args:
        embedding_service: Servico de embeddings para busca.
        config: Configuracao do roteador com thresholds.
        llm: Provider LLM para validacao.
        prompt_template: Template do prompt de classificacao.
        intencoes_validas: Lista de intents validas.
    """
    self._embedding_service = embedding_service
    self._config = config
    self._llm = llm
    self._prompt_template = prompt_template
    self._intencoes_validas = intencoes_validas
    self._votar = votar_com_prioridade

classificar(mensagem: str) -> ResultadoClassificacao | None

Classifica via RAG com similaridade de embeddings.

Parameters:

Name Type Description Default
mensagem str

Texto normalizado do usuario.

required

Returns:

Type Description
ResultadoClassificacao | None

ResultadoClassificacao se classificou, None se nao ha confianca.

Source code in src/roteador/classificadores/rag.py
def classificar(self, mensagem: str) -> ResultadoClassificacao | None:
    """Classifica via RAG com similaridade de embeddings.

    Args:
        mensagem: Texto normalizado do usuario.

    Returns:
        ResultadoClassificacao se classificou, None se nao ha confianca.
    """
    if not self._embedding_service.tem_embeddings:
        return None

    similares = self._embedding_service.buscar_similares(
        mensagem,
        top_k=self._config.top_k,
        min_similarity=self._config.min_similarity,
    )

    if not similares:
        return None

    confidence = similares[0].similaridade
    top1_texto = similares[0].texto
    top1_intencao = similares[0].intencao

    # RAG forte: confianca acima do threshold, usa direto
    if confidence >= self._config.rag_forte_threshold:
        intent = self._votar(similares, self._config.alta_prioridade)
        return ResultadoClassificacao(
            intent=intent,
            confidence=confidence,
            caminho='rag_forte',
            top1_texto=top1_texto,
            top1_intencao=top1_intencao,
            mensagem_norm=mensagem,
        )

    # RAG fraco: abaixo do threshold minimo, delega para LLM fallback
    if confidence < self._config.rag_fraco_threshold:
        return None

    # RAG medio: valida com LLM
    intent = self._validar_com_llm(mensagem, similares)

    return ResultadoClassificacao(
        intent=intent,
        confidence=confidence,
        caminho='llm_rag',
        top1_texto=top1_texto,
        top1_intencao=top1_intencao,
        mensagem_norm=mensagem,
        metadados={
            'rag_top1': top1_texto,
            'rag_sim': round(confidence, 4),
            'rag_intent': top1_intencao,
        },
    )

src.roteador.classificadores.llm

Classificador LLM puro — fallback definitivo.

Quando RAG e lookup nao conseguem classificar, o LLM assume. Sempre retorna um resultado — nunca retorna None.

Example
from src.roteador.classificadores.llm import ClassificadorLLM

llm = ClassificadorLLM(provider, prompt_template, intencoes_validas)
resultado = llm.classificar('mensagem estranha')
resultado.intent  # 'desconhecido' ou intencao valida

ClassificadorLLM(llm: LLMProvider, prompt_template: str, intencoes_validas: list[str])

Bases: ClassificadorBase

Fallback com LLM puro.

Usa prompt fixo para classificar a mensagem. Nunca retorna None — e o fallback definitivo.

Inicializa o classificador LLM.

Parameters:

Name Type Description Default
llm LLMProvider

Provider concreto de LLM.

required
prompt_template str

Template do prompt com placeholder {mensagem}.

required
intencoes_validas list[str]

Lista de intents validas para validacao.

required
Source code in src/roteador/classificadores/llm.py
def __init__(
    self,
    llm: LLMProvider,
    prompt_template: str,
    intencoes_validas: list[str],
) -> None:
    """Inicializa o classificador LLM.

    Args:
        llm: Provider concreto de LLM.
        prompt_template: Template do prompt com placeholder {mensagem}.
        intencoes_validas: Lista de intents validas para validacao.
    """
    self._llm = llm
    self._prompt_template = prompt_template
    self._intencoes_validas = intencoes_validas

classificar(mensagem: str) -> ResultadoClassificacao

Classifica via LLM puro — sempre retorna algo.

Parameters:

Name Type Description Default
mensagem str

Texto da mensagem do usuario.

required

Returns:

Type Description
ResultadoClassificacao

ResultadoClassificacao com intent do LLM.

ResultadoClassificacao

Confidence 1.0 (confianca total no fallback).

Example
llm = ClassificadorLLM(provider, prompt, intencoes)
llm.classificar('xyz123')
ResultadoClassificacao(intent='desconhecido', ...)
Source code in src/roteador/classificadores/llm.py
def classificar(self, mensagem: str) -> ResultadoClassificacao:
    """Classifica via LLM puro — sempre retorna algo.

    Args:
        mensagem: Texto da mensagem do usuario.

    Returns:
        ResultadoClassificacao com intent do LLM.
        Confidence 1.0 (confianca total no fallback).

    Example:
        ```python
        llm = ClassificadorLLM(provider, prompt, intencoes)
        llm.classificar('xyz123')
        ResultadoClassificacao(intent='desconhecido', ...)
        ```
    """
    prompt = self._prompt_template.format(mensagem=mensagem)
    resposta = self._llm.completar(prompt, max_tokens=10)

    intencao = self._extrair_intencao(resposta)

    return ResultadoClassificacao(
        intent=intencao,
        confidence=1.0,
        caminho='llm_fixo',
        top1_texto='',
        top1_intencao='',
        mensagem_norm=mensagem,
        metadados={
            'llm_raw': resposta.strip(),
            'llm_intent': intencao,
        },
    )