Pular para o conteúdo

Explicacao de Decisao

Cada decisao de roteamento na Floopy ja carrega um decision_trace estruturado: candidatos, pesos, motivos de filtragem, vencedor, confianca e (a partir do v2) evidence. Esse e o registro legivel por maquina. A feature de Explicacao de Decisao renderiza o mesmo registro em um paragrafo curto de prosa simples, na lingua do cliente, sob demanda.

Intencionalmente nao e uma explicacao gerada por LLM. Ha uma taxonomia fechada de templates, um schema fechado de parametros tipados e uma funcao de render deterministica. A mesma decisao renderizada duas vezes produz a mesma prosa byte por byte.

Quando voce le uma decisao via GET /v1/decisions/{request_id} ou POST /v1/routing/explain, a resposta agora inclui:

{
"explanation": {
"text": "A Floopy roteou esta requisicao para openai/gpt-5.4-mini com base em 412 amostras historicas e uma confianca moderada de 0,78. O proximo candidato pontuou dentro de 0,07 pontos e a variancia dos resultados tem se mantido estavel. Uma regressao foi registrada nos ultimos 7 dias.",
"template_id": "feedback_driven_moderate_confidence"
}
}

E a resposta carrega um cabecalho HTTP:

Content-Language: pt

A locale e ecoada como cabecalho em vez de campo JSON para que o formato do wire fique compacto e a locale seja comunicada pelo mecanismo HTTP padrao.

As explicacoes sao compostas a partir de um conjunto fechado de 15 templates. Cada decisao escolhe exatamente um template com base na sua estrategia e resultado:

Template idQuando dispara
cache_hitA resposta veio do cache; nenhum router foi invocado.
fallback_onlyTodos os candidatos foram filtrados; a requisicao caiu para o modelo default.
no_router_invokedUm caminho de short-circuit assumiu (modelo legado, regra com candidato unico).
feedback_driven_high_confidenceO router Feedback-Driven selecionou com confidence >= 0,8.
feedback_driven_moderate_confidenceSelecionou com confidence em [0,5; 0,8).
feedback_driven_low_confidenceSelecionou com confidence < 0,5.
smart_cost_selectedO router Smart Cost escolheu o candidato mais barato que atendeu a barra de qualidade.
constraint_rejected_max_cost_increaseO vencedor pretendido foi filtrado por max_cost_increase.
constraint_rejected_max_regressionFiltrado por max_regression.
constraint_rejected_min_samplesFiltrado por min_samples_before_promotion.
constraint_rejected_cost_drop_requires_validationFiltrado por max_cost_drop_without_validation.
constraint_rejected_high_varianceFiltrado por max_outcome_variance.
constraint_rejected_shadow_requiredFiltrado por require_shadow_before_live.
firewall_blockedO firewall LLM bloqueou a requisicao antes do roteamento concluir.
fallbackUm caminho generico de fallback assumiu (erro de provedor, circuit breaker, rate limit).

O enum template_id e fechado e estavel. Adicionar um novo template e uma mudanca deliberada e versionada. Uma decisao cuja estrategia ainda nao esta coberta gera erro de compilacao no gateway, nao um fallback em runtime para “nao sei”.

A locale usada no texto renderizado e resolvida em tempo de leitura, em cada requisicao, na seguinte ordem:

  1. O cabecalho HTTP Accept-Language da requisicao.
  2. Se ausente, malformado ou de outra forma nao parseavel: en.

Nao ha locale default por organizacao nem preferencia por usuario embutida na resposta. A decisao depende exclusivamente do cabecalho da requisicao. A locale resolvida e entao ecoada pelo cabecalho de resposta Content-Language para que clientes possam verificar qual locale receberam.

O parser de Accept-Language e endurecido contra abuso:

  • O cabecalho e limitado a 256 bytes.
  • Apenas ASCII — bytes nao-ASCII disparam fallback para en.
  • Uma allowlist regex estrita rejeita fatores de qualidade malformados e locales desconhecidas.
  • As locales suportadas sao en e pt; qualquer outra cai para en.

A funcao de render e um match exaustivo sobre (template_id, locale). Todo template suportado tem uma string para toda locale suportada, garantido em tempo de compilacao. Adicionar uma traducao pt faltante ou desatualizada quebra o build, nao gera fallback em runtime.

O conjunto atual de locales e {en, pt}. Adicionar uma nova locale e um release explicito do gateway, nao um toggle de configuracao.

Este e o contrato mais importante: o texto da explicacao nunca referencia o conteudo do prompt. A funcao de render recebe um par (template_id, params) onde params e um sub-schema fechado de escalares tipados — contagens de amostras, valores de confianca, gap para o segundo colocado, contagem bucketizada de regressoes e identificadores de provider/model. Nenhum desses campos carrega texto controlado pelo chamador.

Strings de provider e model passam por um sanitizador antes de serem interpoladas no template:

  • Qualquer coisa fora de [a-zA-Z0-9._/-] e descartada.
  • O resultado e limitado para que um nome de modelo hostil nao exploda a prosa.

A saida tambem e defensivamente truncada em 600 caracteres na borda do renderer, testada com fuzz contra caracteres de controle e bloqueada contra caracteres de controle de Markdown. O dashboard renderiza o texto dentro de um <p> como texto plano — sem HTML, sem Markdown.

Duas razoes.

Primeiro, seguranca e armazenamento: se a prosa fosse persistida em decision_trace, cada byte de cada decisao carregaria uma superficie de ataque para injecao de texto armazenado para sempre. Mantendo os artefatos persistidos estritamente tipados (explanation_template_id mais explanation_params), a linha de auditoria fica pequena, amigavel a maquina e livre de texto livre.

Segundo, internacionalizacao: renderizar em tempo de leitura significa que um cliente que le a mesma decisao em en hoje e em pt amanha recebe dois paragrafos diferentes a partir de uma unica decisao armazenada. Nao ha passo de migracao quando lancamos uma nova locale ou refinamos um template — toda decisao existente passa a refletir a nova prosa imediatamente.

O campo de explicacao nao e adicionado a:

  • GET /v1/decisions (endpoint de listagem) — muito verboso para uma lista paginada.
  • GET /v1/export/decisions — exportacoes sao consumidas por maquina; o template_id persistido fica intencionalmente fora do allowlist da exportacao para que o formato do wire seja estavel entre clientes independentemente de suporte a locale.

Se voce quer a explicacao, pegue a decisao individual: GET /v1/decisions/{request_id}.

O passo “shadow setup” do onboarding da Floopy usa o catalogo interno de modelos da Floopy para escolher uma alternativa mais barata sensata para o modelo que o cliente acabou de conectar. Quando o experimento desse passo grava as primeiras decisoes, o template de explicacao tipicamente sera um dos ramos de Smart Cost ou Feedback-Driven, dependendo se o cliente ja esta usando roteamento Feedback-Driven.