Por que seu agente de IA faz 40 chamadas para responder uma pergunta

MCP

Alguém fez ao assistente uma pergunta razoável:

Me dê um relatório das minhas tarefas desta semana, separado em Em andamento e Concluído.

Ele respondeu certo. Também levou uns quarenta segundos e mais de trinta chamadas de ferramentas para isso, porque listou as tarefas e depois abriu o histórico de cada uma, uma por vez, para descobrir quando a tarefa tinha sido movida e por quem.

Esse é o problema N+1. Todo engenheiro de backend já o encontrou num ORM: você carrega uma lista e depois dispara uma consulta por linha, porque o que você realmente precisava não estava na lista. Aqui a forma é a mesma, com um motor de consultas mais lento e mais caro (um modelo de linguagem) no lugar onde antes ficava o loop.

O agente não estava sendo burro. A API do BugDump não deu a ele outro jeito de responder.

O agente só pode perguntar o que você expôs

Um modelo com ferramentas faz algo parecido com planejamento de consultas. Ele lê as descrições das suas ferramentas, descobre quais chamadas podem produzir a resposta e as executa. Se o eixo de que ele precisa não é um filtro, ele reconstrói esse eixo à mão: busca linhas e examina cada uma.

O list_tasks do BugDump filtrava por responsável, por status, por coluna, por etiqueta, por qualquer propriedade personalizada e por um intervalo de datas de criação. O que ele não conseguia era responder quando isto foi concluído. A data de conclusão existia, numa coluna chamada resolvedAt, indexada e pronta. Só não era um filtro, e não vinha na resposta.

Então «o que eu concluí esta semana», do ponto de vista do modelo, se decompôs em: listar as tarefas fechadas, depois abrir o histórico de cada uma e procurar a mudança de coluna. Uma chamada por tarefa. A informação estava a uma cláusula WHERE de distância.

Três perguntas que valem para toda ferramenta

Desde então, auditei as ferramentas com três perguntas. Vale a pena roubá-las.

1. Quem chama consegue filtrar por todos os eixos que os dados têm?

Não «todos os campos», e sim todos os eixos sobre os quais alguém vai perguntar. O tempo quase sempre é um deles, e quase sempre é mais de um: criado, atualizado, concluído, mexido por último. Cada um é uma pergunta diferente. Uma ferramenta que expõe só createdAt obriga quem pergunta sobre os outros três a paginar pelas linhas.

A correção foram dois parâmetros, resolvedFrom e resolvedTo. Quatro linhas de código no servidor. Isso eliminou uma classe inteira de N+1 de todo relatório que um agente escreve.

2. A ferramenta sabe quem está chamando?

«Minhas tarefas» é a expressão mais comum nesses pedidos, e por muito tempo o agente não conseguia responder a isso sem chutar. get_current_project retornava os membros do projeto (nomes, funções, ids), mas nunca dizia em nome de qual deles este token age.

Veja o que um agente faz com isso. Ele olha a lista de membros, vê «Alex Rivera», lembra que o usuário disse «minhas» e escolhe aquele cujo nome parece mais próximo de algo que viu antes. Funciona até uma equipe ter duas pessoas chamadas Alex.

Agora o servidor retorna um objeto viewer: o id de usuário, o nome e a função do membro a quem o token pertence. Identidade não deveria ser uma dedução.

3. A lista retorna o que o detalhe retorna?

Se get_task inclui um campo e list_tasks o omite, você construiu um loop de buscas. O agente recebe vinte linhas, precisa daquele campo para as vinte e chama o endpoint de detalhe vinte vezes.

Os itens da lista do BugDump não traziam resolvedAt nem updatedAt, enquanto o detalhe trazia os dois. Acrescentá-los é uma mudança de duas linhas que elimina vinte idas e voltas.

A armadilha: um timestamp que mente

Enquanto adicionava os filtros, quase lancei um quarto (updatedFrom / updatedTo sobre updatedAt), porque é o jeito óbvio de perguntar «o que mudou esta semana».

Ele teria sido ativamente prejudicial, e o motivo vale em geral.

O updatedAt da tabela de tarefas do BugDump é mantido pelo ORM: qualquer UPDATE na linha o atualiza. Mas as tarefas guardam status, responsável, prioridade, etiquetas e data de entrega numa tabela de valores separada. Então:

  • atribuir uma tarefa não altera updatedAt;

  • mudar a prioridade dela não o altera;

  • movê-la entre duas colunas abertas não o altera;

  • um estranho votando nela no seu portal público altera.

Um filtro baseado nessa coluna teria respondido «em que eu trabalhei esta semana» com uma lista que excluía o trabalho e incluía o clique de um estranho. Pior: teria parecido plausível. O modo de falha de uma ferramenta ruim não é um erro, é uma resposta errada dada com confiança.

Deixei esse filtro de fora e disse isso na descrição da ferramenta: updatedAt marca quando a linha foi tocada, não quando houve atividade. A descrição de uma ferramenta é uma documentação que o modelo de fato lê. Use-a para descartar coisas, não só para explicá-las.

Quanto custa errar nisso

A versão de um endpoint lento vista por um agente não é uma página lenta. É:

  • Latência que o usuário sente. Trinta idas e voltas em sequência, cada uma esperando o modelo decidir a próxima.

  • Janela de contexto queimada. Cada resposta são tokens. Trinta históricos de tarefas podem espremer para fora aquilo que o usuário realmente pediu.

  • Dinheiro. São tokens cobrados, toda vez que alguém pergunta.

  • Truncamento silencioso. Um agente que fica sem espaço interrompe o trabalho antes da hora e resume o que tem, e a resposta dele não diz «só li onze das quarenta tarefas».

Esse último ponto é o motivo de isto ser um problema de correção, e não de desempenho.

O mesmo relatório, agora

Com as três correções no lugar, o relatório semanal fica assim:

  1. get_current_project — quem sou eu e quais são as colunas;

  2. list_tasks com assigneeId + a coluna Em andamento;

  3. list_tasks com assigneeId + status: "closed" + resolvedFrom.

Três chamadas, fixas, não importa quantas tarefas voltem. Cada linha já traz resolvedAt, então agrupar por dia não exige mais nada.

Uma ressalva que documento em vez de esconder: resolvedAt registra quando uma tarefa entrou pela última vez numa coluna fechada. Reclassifique uma coluna de aberta para fechada, e todas as tarefas nela recebem aquele momento como data, então o relatório de uma semana pode incluir trabalho que na verdade foi concluído meses antes. Agentes não conseguem deduzir ressalvas assim. Escreva-as onde o modelo vai lê-las.

Se você está construindo um servidor MCP

Faça uma pergunta real a um agente real e observe o log de chamadas. Não o caminho feliz que você desenhou, e sim a pergunta que um usuário realmente digitaria, com as palavras dele, numa sexta-feira à tarde.

Cada loop que você vê é um eixo que a sua API não expôs. A correção costuma ser uma cláusula WHERE, um campo na resposta de uma lista ou uma frase numa descrição. Sai muito mais barato do que o contorno que o modelo inventa.

O servidor MCP do BugDump é gratuito em todos os planos, inclusive no Grátis, e Usando o BugDump com o Claude Code (MCP) mostra como conectá-lo ao seu projeto.

Leia também