Dez arquivos não são um orçamento
Curadoria, tradução e análise: Redação Triplo Hub.
Eu tenho um servidor MCP que processa um repositório do GitHub em markdown para que um modelo possa lê-lo sem clonar nada. Árvore de diretórios, além do conteúdo dos arquivos que importam.
Eu apontei para um repositório real e recebi isso de volta:
Erro: resultado (109.668 caracteres) excede o máximo de tokens permitido
Não foi uma resposta truncada. Não foi um resumo parcial com um aviso. Nada. A chamada da ferramenta falhou e o chamador ficou com uma mensagem de erro onde um resumo do repositório deveria estar.
A causa foi uma constante que eu havia escrito meses antes sem pensar muito sobre isso:
MAX_FILE_BYTES = 32_000
TOP_N_FILES = 10
Dez arquivos, cada um limitado a 32.000 caracteres. Eu tinha raciocinado que dez arquivos é uma quantidade razoável de um repositório para mostrar. O que é verdade, e também não é um limite de tamanho de forma alguma. Dez arquivos variam de algumas centenas de caracteres a 320.000, dependendo de qual repositório você aponta.
A correção óbvia, e por que eu não fiz isso
A correção óbvia é adicionar um limite total de caracteres. Escolha um número, preencha, pare.
O problema é escolher o número. Eu poderia ter raciocinado até chegar a um. 50.000 parece bom. 32.000 também parece bom. 64.000 soa bem também. Qualquer um deles parece bom, o que é um bom sinal de que o raciocínio não é a ferramenta para o trabalho, e é exatamente assim que TOP_N_FILES = 10 chegou lá em primeiro lugar.
Então, antes de mudar qualquer coisa, eu medi. Seis repositórios, abrangendo a faixa que eu realmente encontraria: dois pequenos meus, um servidor MCP de tamanho médio, um repositório de lista pesado em documentos e dois grandes bases de código reais.
| repositório | arquivos | árvore | conteúdo do arquivo | total |
|---|---|---|---|---|
| GopherMCP/GopherCache | 5 | 89 | 4.505 | 4.594 |
| pyarchana/gopher | 22 | 591 | 23.640 | 24.231 |
| sktime/sktime-mcp | 104 | 3.296 | 100.490 | 103.786 |
| astral-sh/uv | 1.578 | 65.530 | 142.337 | 207.867 |
| modelcontextprotocol/python-sdk | 1.643 | 57.306 | 160.182 | 217.488 |
| punkpeye/awesome-mcp-servers | 10 | 173 | 228.137 | 228.310 |
Uma faixa de cinquenta vezes entre a menor e a maior saída, da mesma ferramenta com as mesmas configurações.
Três coisas surgiram daquela tabela que eu não teria adivinhado.
A árvore sozinha pode consumir tudo
Olhe para a coluna da árvore para uv e o SDK Python. 65.530 e 57.306 caracteres, apenas para listar nomes de arquivos.
Eu estava pensando no orçamento como um limite no conteúdo dos arquivos. Mas em um repositório com 1.600 arquivos, a listagem do diretório sozinha é maior do que qualquer total sensato. Se eu tivesse limitado apenas o conteúdo do arquivo, uv teria ultrapassado qualquer orçamento que eu estabeleci antes de buscar um único arquivo.
A árvore precisava de seu próprio limite, a uma fração do total, e uma nota dizendo quantas entradas ela deixou de fora.
A classificação estava escolhendo os arquivos errados
Este é o que realmente importava, e eu só percebi porque imprimi o que estava sendo selecionado em vez de apenas o quão grande era.
Aqui está o que minha ferramenta escolheu mostrar sobre uv, uma ferramenta de construção Rust:
27.194 test/ecosystem/airflow/pyproject.toml
32.048 test/ecosystem/home-assistant-core/pyproject.toml
27.517 test/ecosystem/pandas/pyproject.toml
9.156 test/ecosystem/jupyterlab/pyproject.toml
8.757 test/ecosystem/black/pyproject.toml
Oitenta e nove mil caracteres de listas de dependências de outros projetos, extraídos dos fixtures de teste do uv. O único código do uv que entrou foi três arquivos main.rs, todos eles pontos de entrada finos, e Cargo.toml ficou abaixo de todos aqueles fixtures.
E aqui está awesome-mcp-servers:
32.048 README-fa-ir.md
32.048 README-ja.md
32.048 README-ko.md
32.048 README-pt_BR.md
32.048 README-th.md
32.048 README-zh.md
32.048 README-zh_TW.md
O mesmo README em sete idiomas, 224.000 caracteres dele.
A causa foi uma linha:
if name in PRIORITY_NAMES:
score += 1000
Qualquer arquivo chamado pyproject.toml ganhou mil pontos, onde quer que estivesse. Uma cópia vendida seis diretórios abaixo em um fixture de teste pontuou exatamente o mesmo que a que está na raiz descrevendo o projeto real.
É por isso que a medição importava. Se eu tivesse enviado o orçamento por conta própria, o resumo teria passado de 207.867 caracteres dos arquivos errados para 40.000 caracteres dos arquivos errados. Menor, ainda inútil, e agora parecendo deliberado.
O arquivo que mais queria voltou vazio
Mais uma, que eu nunca teria encontrado lendo o código.
Na execução do awesome-mcp-servers, o arquivo mais bem classificado pontuou 1200 e retornou zero caracteres:
0 score=1200 README.md
32.048 score=200 README-fa-ir.md
A API de conteúdos do GitHub não servirá um arquivo acima de 1MB. Não retorna um erro. Retorna 200 OK com um campo content vazio e encoding: "none". Portanto, a decodificação base64 é bem-sucedida, produz uma string vazia e o arquivo desaparece silenciosamente.
Esse README tem 1.616.144 bytes. O arquivo mais importante do repositório estava sendo descartado sem uma palavra, e sete traduções dele estavam preenchendo o espaço em vez disso. A correção é notar encoding: "none" e buscar novamente através do endpoint de blobs, que serve até 100MB.
O que eu mudei
Um orçamento total, não uma contagem de arquivos. 40.000 caracteres, configurável. Gastos na árvore primeiro, depois arquivos em ordem de prioridade, até acabar. O número de arquivos agora resulta do que cabe em vez de ser fixado com antecedência.
Um limite na árvore, um quarto do total, com uma linha dizendo quantas entradas foram omitidas.
Um teto por arquivo de 40% do que resta, para que um arquivo grande não possa excluir tudo o mais.
Classificação por posição, não apenas por nome. O bônus de prioridade agora decai com a profundidade do diretório, então o manifesto raiz vence um vendored. Diretórios de teste e fixture perdem pontos, os vendored perdem mais, exemplos perdem apenas um pouco porque às vezes um diretório de exemplos é a melhor documentação que um projeto possui. Arquivos na linguagem principal do repositório, que a API já informa, ganham alguns pontos.
Aqui está a mesma tabela depois:

