Três routers, três problemas diferentes: DSPy, Semantic Router customizado e Aurélio AI
Antes de construir o customizado avaliei uma biblioteca open-source que quase entrou no projeto. Esse post é o comparativo que eu queria ter lido antes de tomar essas decisões.
Nos posts anteriores mostrei como o DSPy substituiu o Agno/LangGraph como roteador cognitivo de um assistente, colocando contrato de output, métrica e processo de melhoria em cima do LLM.
O que não mostrei é que, no mesmo sistema, existe outro roteador com arquitetura completamente diferente. E que, antes de construir o customizado, avaliei uma biblioteca open-source que quase entrou no projeto.
Esse post é o comparativo que eu queria ter lido antes de tomar essas decisões.
O ponto de partida: roteamento não é um único problema
Quando você diz roteador de intenções, pode estar falando de coisas muito diferentes:
Problema A: O usuário digitou "quero cancelar meu pedido". É suporte, devolução, cancelamento pré-envio ou algo fora de escopo? Decisão entre poucos candidatos macro, com thresholds por intent e lógica de ambiguidade.
Problema B: O usuário digitou "tênis masculino Nike até R$ 300 com frete grátis e boa avaliação". É uma busca no catálogo. Mas com qual ferramenta? Com quais parâmetros? Scope + extração de 10+ filtros estruturados de uma frase livre.
Problema C: Você está começando um projeto, precisa rotear entre 8 intents e não quer construir nada do zero. Quer algo funcional em 20 minutos.
Cada um desses problemas pode tem um roteador diferente como resposta.
Roteador 1: Semantic Router customizado por embeddings
O que é
Um roteador que embeda a query do usuário, calcula similaridade coseno contra matrizes de exemplos positivos por intent, penaliza com exemplos negativos, e aplica uma pipeline de decisão com thresholds configuráveis.
Como funciona na prática
O scoring de cada intent:
Isso resolve o Problema A: classificação macro entre intents com semântica clara. Nenhuma chamada ao LLM, com latência de 20-50ms.
O pairwise reranker: onde o conhecimento de domínio entra
Para pares de intents que se confundem com frequência, um reranker heurístico decide com base em padrões de texto:
O pairwise reranker é o conhecimento de domínio codificado como regra. O embedding não distingue "quero ver o status do meu pedido" de "quero ver tênis masculino" - o score coseno dos dois pode ser próximo para busca_catalogo. A regra resolve isso de forma determinística e auditável.
CFG_PARAMS no Redis: hot reload sem deploy
Quando um comportamento de roteamento precisa ser ajustado em produção (um novo termo que começa a confundir intents, um threshold que precisa ser calibrado) você atualiza o CFG_PARAMS no Redis. Sem novo deploy, sem nova compilação.
O trabalho que ninguém documenta: calibração de thresholds
O semantic router customizado tem um problema que só aparece em produção: os thresholds são definidos manualmente, e a decisão de qual valor usar não tem respaldo matemático direto.
Como chegar nos valores certos? Trial and error com um conjunto de queries de teste, observando onde o roteador começa a aceitar queries que não deveria (threshold baixo demais) ou rejeitar queries legítimas caindo no fallback FAQ (threshold alto demais).
Isso é calibração manual. E tem um custo que piora com o tempo.
Quando o encoder muda - seja por novo modelo de embedding, nova versão do Azure OpenAI, migração de provider - a distribuição dos scores muda. Thresholds que funcionavam com text-embedding-3-small não funcionam com text-embedding-3-large. Você precisa recalibrar do zero.
Quando o catálogo de utterances cresce, acrescentando mais exemplos positivos por intent e mais diversidade semântica, a distribuição muda de novo.
O CFG_PARAMS no Redis resolve a operação de ajuste sem deploy. Não resolve o processo de descoberta do valor certo.
Quando usar
- Classificação macro entre intents bem definidas (5-20 intents)
- Latência é crítica (menos de 50ms por request)
- Você precisa de hot reload de configuração sem deploy
- Domínio tem pares de intents que se confundem de forma conhecida e previsível
- Você tem exemplos positivos e negativos por intent, mas não tem dataset anotado suficiente para LLM
Roteador 2: DSPy
O que é
Um roteador LLM-first que usa uma Signature declarativa para classificar a intenção e extrair parâmetros estruturados em uma única inferência, compilado com exemplos supervisionados e uma métrica de avaliação assimétrica.
O que o diferencia concretamente
A saída do semantic router customizado é:
A saída do DSPy router é:
Mesma query de entrada, mas com output totalmente diferente. O DSPy não só classifica: ele extrai todos os parâmetros que a tool de busca precisa para executar a query no Redis Stack.
Fazer isso com embedding é inviável. Você não consegue extrair preco_max=300.0 de um vetor coseno.
A métrica assimétrica: onde a prioridade de negócio vira código
O BootstrapFewShot rejeita qualquer demo com scope errado, independente de quantos campos acertaram. Isso é inviável de implementar num semantic router por embeddings, onde o conceito de scope como critério zero-tolerância não existe na similaridade coseno.
Como o DSPy resolve o problema de calibração
No DSPy, o conceito de threshold não existe da mesma forma, porque a decisão não é "se o score está acima de X?". A decisão é "qual é o melhor RouterOutput dado o texto, o histórico e os demos compilados?".
O que funciona como calibração é a própria métrica com o BootstrapFewShot. O optimizer, ao selecionar quais demos incluir no prompt compilado, está implicitamente calibrando o comportamento do router para o domínio, sem que você defina um threshold explicitamente.
Se você adicionar novos exemplos ao dataset e recompilar, o modelo se recalibra com os novos dados. A limitação: recompilar tem custo de LLM. Não é uma operação que você faz com hot reload em produção. É um processo offline, controlado, com validação antes do deploy.
A coerção: proteção contra a imprevisibilidade do LLM
O semantic router nunca precisa disso. Embedding + dot product sempre retorna um float. LLM pode retornar markdown, JSON malformado ou texto livre.
Quando usar
- O roteamento requer extração de parâmetros estruturados além da classificação de intent
- Você tem dataset supervisionado (50+ exemplos já ajudam)
- Precisa de portabilidade entre modelos (trocar GPT-4o por Claude sem reescrever)
- O output do router precisa de contrato verificável e versionável
- Latência de 2-3s por chamada é aceitável
Roteador 3: Aurélio AI semantic-router (avaliado, não implementado)
Antes de construir o roteador customizado, avaliei a biblioteca open-source do Aurelio AI, o grupo que inclui parte do time original do semantic-router. A API é limpa e o modelo mental é o mesmo: utterances por route, encoder configurável, similaridade coseno.
A biblioteca funciona. O que me fez construir o customizado foram três gaps específicos para o meu caso de uso:
- Ausência de pairwise reranker configurável: para pares de intents que se confundem no domínio (
busca_catalogovsdetalhe_produtoquando o usuário menciona um produto específico), não existe um mecanismo estruturado para injetar a lógica de desempate. Dynamic Routes (com LLM) são a alternativa, mas adicionam latência que eu não queria no caminho crítico. - Sem hot reload de configuração: para calibrar thresholds, adicionar FAQ force terms ou ajustar o comportamento de intents ambíguas em produção, eu precisaria de um novo deploy. O CFG_PARAMS no Redis era um requisito do projeto.
- Visibilidade reduzida da pipeline de decisão: o topk com scores de todos os candidatos, o pairwise reranker com sua regra, o
disambiguation_ruleno log estruturado... esses itens são importantes para debugar degradação em produção. A biblioteca retornaRouteChoicecomsimilarity_score. Suficiente para muitos casos, mas não para o nível de auditabilidade que eu precisava.
O que a biblioteca resolve melhor: calibração automática de thresholds
O ponto em que o Aurélio AI tem vantagem concreta sobre o customizado é exatamente o problema de calibração discutido acima. A biblioteca tem um método de fitting automático via dataset rotulado:
O fitting usa os próprios scores de similaridade para encontrar o ponto de corte ótimo por intent, sem trial and error manual. Quando o encoder muda, você roda o fitting de novo com o mesmo dataset e os thresholds se recalibram automaticamente.
A limitação: o fitting é tão bom quanto o dataset. Se ele não cobre os casos edge do domínio, os thresholds serão ótimos para o que você testou e silenciosamente ruins para o que não testou.
Para o meu caso, esse ganho não compensava os gaps nos outros três pontos. Para um projeto sem requisito de hot reload e com intents semanticamente bem separadas, pode ser suficiente.
Quando faz sentido
- Adoção rápida com intents simples e bem separadas semanticamente
- Volume baixo e poucos pares de intents confusos
- Calibração automática de thresholds via dataset é prioridade sobre controle da pipeline
- Time pequeno sem capacidade de manter pipeline de roteamento customizado
- Dynamic Routes são suficientes para os casos edge
O comparativo de calibração lado a lado
O problema de calibração de thresholds aparece de formas diferentes nos três roteadores. Vale ver lado a lado:
O semantic router customizado coloca o trabalho de calibração no engenheiro. O Aurélio AI o automatiza, mas ainda depende da qualidade do dataset de treino. O DSPy o elimina como problema explícito, mas transforma em outro: manter um dataset supervisionado e um processo de recompilação.
Nenhum dos três é isento de manutenção. A diferença é onde o trabalho fica.
A tabela de comparação
Como os dois coexistem em produção
No assistente, os dois roteadores existem em camadas diferentes:
Camada 1 - Semantic router customizado no supervisor: decide se a mensagem é busca de catálogo, detalhe de produto, suporte, rastreamento de pedido, ou fora de escopo. Classificação macro, sem LLM, latência extremamente baixa.
Camada 2 - DSPy router dentro do subgrafo de busca: quando a Camada 1 roteou para busca_catalogo, o DSPy decide qual das tools chamar e com quais parâmetros. É aqui que "tênis masculino Nike até R$ 300 com frete grátis" vira:
A divisão não é por preferência, é por problema. A Camada 1 precisa de velocidade e hot reload. A Camada 2 precisa de extração estruturada com contrato verificável.
Os trade-offs que a tabela não captura
- Degradação silenciosa: o semantic router customizado degrada quando os utterances ficam desatualizados em relação ao vocabulário real dos usuários. Você detecta pelo aumento da taxa de fallback. O DSPy degrada quando o modelo do provider é atualizado, você detecta rodando o validation set antes do deploy. O Aurélio AI degrada da mesma forma que o customizado, mas você tem
fit()para recalibrar. - Custo operacional real: o semantic router customizado cobra embedding por request (barato e previsível). O DSPy cobra embedding + LLM, mas a extração de parâmetros na mesma inferência elimina a chamada subsequente ao LLM para parsear a query, o custo líquido às vezes é comparável. O Aurélio AI tem o mesmo custo do customizado fora dos Dynamic Routes.
- Transferência de conhecimento: o semantic router customizado tem muitas linhas de pipeline de decisão. Cada linha é uma decisão com um motivo. Quando alguém novo entra no projeto, esse contexto precisa ser transferido explicitamente. O DSPy tem um JSON compilado e um dataset, o "porquê" está nos exemplos e nos pesos. O Aurélio AI tem documentação pública.
Resumindo
- Semantic router customizado: velocidade, controle total da pipeline e hot reload. O preço é calibração manual de thresholds e custo alto de manutenção.
- DSPy: quando a decisão de roteamento precisa extrair parâmetros estruturados, com contrato verificável, portabilidade entre modelos e calibração implícita via compilação.
- Aurélio AI: quando você quer o modelo mental do semantic router sem construir a pipeline do zero, com calibração automática via
fit(). Avalie se a ausência de hot reload e pairwise reranker é aceitável para o seu caso de uso antes de escolher.