Query Advisor
Um modelo de linguagem recebe uma consulta lenta e a tarefa de acelerá-la. Depois o banco decide se ele conseguiu.
- .NET 10
- Microsoft.Extensions.AI
- MCP
- PostgreSQL
- Evals
O modelo propõe, o banco dispõe
Cinco perguntas, cada uma respondida por uma instrução correta e lenta. O modelo recebe a pergunta, a instrução lenta e três ferramentas: o schema, o planejador e um executor somente-leitura. Ele propõe uma reescrita e explica o raciocínio.
Aí roda a parte que não é um modelo de linguagem. As duas instruções executam
contra 1.000.000 de pedidos e 1.500.001 linhas de pedido. Cada célula dos dois
conjuntos de resultado é hasheada e comparada. Os dois planos são capturados com
EXPLAIN (ANALYZE, BUFFERS). O veredicto sai dessa comparação e de mais nada.
Nada aqui pede para um segundo modelo dar nota ao primeiro.
Por que isso importa mais do que parece
Nove em cada dez times com agentes em produção têm observabilidade sobre eles. Cerca de metade tem avaliação. A lacuna não é preguiça — é que a saída de um agente costuma ser prosa, e o único juiz disponível para prosa é outro modelo, de forma que a medição herda justamente aquilo que deveria verificar.
SQL não tem esse problema. Duas instruções ou devolvem as mesmas linhas ou não devolvem, e uma delas ou é mais rápida ou não é. Este demo pega a única tarefa de agente em que existe um oráculo determinístico e constrói a avaliação que ninguém tem como contestar.
São quatro veredictos, e três deles são falhas por causas diferentes. Confirmado é mesmas linhas, comprovadamente mais rápido. Linhas erradas é resposta errada. Sem ganho é uma reescrita honesta que não comprou nada. Inválido nunca chegou a rodar. Reduzir isso a um booleano jogaria fora a metade mais interessante do que foi medido — uma resposta errada e rápida e uma correta e inútil não são o mesmo achado.
O que foi medido
| Haiku 4.5 | Opus 5 | |
|---|---|---|
| Receita por status em um mês | confirmado, 12,3× | confirmado, 8,1× |
| As linhas de pedido de um cliente | sem ganho, 1,0× | confirmado, 62,2× |
| Os dez maiores clientes | sem ganho, 0,6× | sem ganho, 0,8× |
| O valor de vinte pedidos | confirmado, 8,1× | confirmado, 2.982× |
| Página 25.001 da lista de pedidos | confirmado, 6.458× | confirmado, 3.893× |
| 3 de 5 · $0,17 | 4 de 5 · $0,62 |
A última pergunta foi escrita para ser a difícil: uma reescrita por keyset precisa de um cursor, e o cursor daquela página é uma linha que o modelo tem que ir procurar. Os dois acharam. A difícil acabou sendo a terceira — o tipo de coisa que só se descobre rodando o conjunto, e não raciocinando sobre ele.
A pergunta que os dois erraram
Dez clientes, ordenados por quantidade de pedidos, a partir de um count(*)
correlacionado avaliado uma vez por linha candidata. A correção de manual é
trocar a subconsulta correlacionada por uma agregação com GROUP BY, e foi
exatamente isso que os dois fizeram.
Os dois ficaram mais lentos. 213ms viraram 329ms.
O baseline só contava os clientes que casavam com o filtro, usando o índice em
customer_id. A reescrita agrupou a tabela inteira de um milhão de pedidos
primeiro e filtrou depois — estritamente mais trabalho, obtido ao aplicar uma
regra que quase sempre está certa.
Existe uma reescrita que ganha: filtrar os clientes antes de agregar, e ela roda em 146ms. Nenhum dos dois modelos a encontrou. E nenhuma leitura das explicações deles teria revelado isso, porque as duas explicações estão corretas sobre por que o original era lento. Só executando as duas instruções é que se sabe qual é mais rápida.
Essa pergunta fica no conjunto exatamente como está. Um golden set cuja resposta é sempre a que todo mundo já sabe mede memória, não julgamento.
O bug que importou mais do que as notas
A primeira execução reportou que os dois modelos responderam errado a uma das perguntas. Não responderam. O baseline daquela pergunta estourou o orçamento de cinco segundos do papel de leitura, então nunca rodou — e a comparação, que checava a proposta e nunca o baseline, confrontou um hash real com um vazio e culpou o modelo.
Um harness que culpa o modelo pela própria falha é pior do que harness nenhum, porque produz números com cara de achado. A correção é um veredicto próprio — o baseline falhou — verificado antes de qualquer cobrança à resposta, e uma pergunta reescrita para que a versão lenta continue ruinosa e ainda assim termine.
Foi preciso rodar uma consulta na mão contra o banco para perceber. Esse é o argumento para rodar uma avaliação contra dados reais antes de acreditar no que ela imprime, e é por isso que as notas acima valem leitura: elas sobreviveram ao harness ter errado uma vez.
Dois papéis que não fazem o trabalho um do outro
As ferramentas do modelo rodam sob um papel com SELECT em cinco tabelas,
somente-leitura no nível da transação e com orçamento de cinco segundos por
instrução. O medidor de orçamento roda sob outro papel, que pode inserir no
ledger de gasto e não enxerga os dados de jeito nenhum.
Um modelo que alcança a tabela que registra o próprio custo não está sendo
medido por ela. O CI assere os quatro privilégios em vez de confiar no arquivo
que os concedeu — basta um GRANT para este parágrafo virar ficção.
O orçamento, e o que acontece quando acaba
Este é um endpoint público que gasta dinheiro quando alguém clica. Rate limiting limita a velocidade do gasto, não a quantidade dele, então existe um teto rígido por dia.
Alcançado o teto, o serviço recusa e diz isso. Ele não serve uma resposta guardada, pelo mesmo motivo que o laboratório de SQL não serve: no instante em que um visitante não consegue distinguir uma medição de uma gravação, todo número da página deixa de valer alguma coisa.
p95 ao vivo -uptime -