
Construindo Agentes para APIs de Fronteira: Uma Abordagem Prática
Resumo: Frameworks de agentes assumem silenciosamente um modelo obediente: JSON limpo, instruções seguidas na primeira vez, contexto de sobra. Execute um modelo quantizado de 7B–35B em uma janela de 16K e cada uma dessas suposições morre. Passei meses construindo a camada de aplicação que mantém modelos locais pequenos produtivos de qualquer maneira — tudo código aberto, MIT. Aqui está o que realmente quebra e o que consertou.
O cenário
Chaty é um espaço de trabalho de IA totalmente offline para desktop — Rust / Tauri 2, llama.cpp mais MLX nativo em Apple Silicon. O agente de codificação dentro dele funciona com qualquer modelo GGUF ou MLX que você tenha: 7B, 8B, um MoE de 35B. Sem fallback na nuvem, sem orçamento de nova tentativa. Se o loop desperdiçar rodadas, você pessoalmente verá sua GPU aquecer a sala para nada.
Três restrições moldam tudo abaixo:
- Contexto de 16K. Cada linha de documento de ferramenta é um imposto pago em cada turno.
- Aderência ao formato é probabilística. O modelo geralmente emite chamadas de ferramenta válidas. "Geralmente" não é um contrato de engenharia.
- Modelos pequenos imitam o que está no contexto — incluindo seus próprios erros. Este é um ponto crucial. Lembre-se disso; explica metade do design.
1. Analise o que os modelos realmente emitem
Aqui está uma chamada de ferramenta real de um MoE de 35B, capturada de um despejo bruto:
{"name": "write_file", "path": "index.html", "content": "<!doctype html>...", "arguments": {}
Campos planos, além de um objeto vazio arguments espúrio — e o modelo frequentemente atinge EOS logo após a string de conteúdo, antes da chave de fechamento. Um parser rigoroso rejeita isso, o loop pergunta novamente, e o modelo regenera todo o arquivo HTML. Dez rodadas disso em uma página.
As correções, em ordem de importância:
-
Aceitar campos planos. E criticamente: um
arguments: {}vazio não deve ofuscar eles. Tomar o{}ao pé da letra transformou cada escrita desse tipo em um loop de nova tentativa por argumento ausente. -
Reparar fechamentos ausentes — nunca no meio de uma string. Um scanner ciente de strings anexa
}/]somente quando cada string está terminada. Completar automaticamente uma string truncada escreveria silenciosamente um arquivo corrompido, o que é estritamente pior do que tentar novamente. - Construir fixtures de parser a partir de despejos brutos dos seus modelos-alvo, não a partir da especificação. A especificação é o que os modelos foram treinados para; os despejos são o que eles fazem.
Depois disso, a mesma página que queimou dez rodadas foi concluída em uma escrita de 76 segundos.
2. Uma correção repetida não é uma correção
Um modelo pequeno que envia search_code {} o enviará novamente, não importa quão educadamente você repita a mesma mensagem de erro. Realizamos a autópsia em uma dessas espirais: a velha guarda repetiu uma correção idêntica três vezes, então desistiu e executou a chamada vazia — o que plantou a chamada de argumentos vazios do modelo no transcript como um exemplo a imitar. Veja a restrição crucial acima.
A substituição é uma escada escalonada, contada por ferramenta:
- Escorregão 1: mostre um exemplo válido concreto para esta ferramenta.
-
Escorregão 2: pare de sugerir a ferramenta — desvie: "tente
list_dir/read_file/grepem vez disso." (Também aumentamos a temperatura de amostragem aqui; um atrator preso às vezes precisa de ruído, não palavras.) - Escorregão 3: a ferramenta é declarada desativada para este turno, e o estado preso recebe um cartão de erro visível na interface do usuário.
- Escorregão 5: pause o loop com uma razão legível por humanos em vez de girar.
Chamadas vazias são nunca executadas, e uma única chamada válida redefine o contador. O princípio: cada degrau deve adicionar nova informação. Se duas correções consecutivas são idênticas em bytes, você não tem uma correção — você tem um mantra.
3. Servidores não devem travar o loop
Modelos iniciam servidores de desenvolvimento em shells de primeiro plano. Comportamento ingênuo do suporte: bloqueia por 120 segundos, expira e mata o próprio servidor que o modelo foi solicitado a iniciar.
A detecção é de duas vias, porque uma via nos mentiu:
-
Correspondência de banner é o caminho rápido — vite e amigos imprimem
http://localhost:5173instantaneamente. -
Um socket de escuta é a verdade fundamental.
python3 -m http.serverbloqueia o banner sob um pipe; o texto nunca chega. Então, após um período de graça de ~10s, também perguntamos ao SO se o grupo de processos do comando possui um socket TCP de escuta (~a cada 3s).
Qualquer sinal → o processo é movido para um trabalho em segundo plano rastreado, não morto: buffer de saída transportado, id do trabalho retornado ao modelo com "o servidor está ativo — navegue até ele, verifique os logs com bg_output."
Uma classe de zumbis a mais: um shell que sai após server & costumava deixar o sobrevivente ocupando a porta para sempre, invisível. Agora o grupo de processos é verificado na saída do shell e os sobreviventes são adotados como [desanexados] — visíveis, matáveis, colhidos na troca de espaço de trabalho e saída do aplicativo.
4. Traga o console para o modelo
Agentes adoram "verificar" uma página tirando uma captura de tela do DOM desatualizado. Uma captura de tela prova que a página parece com algo; não prova nada sobre o comportamento.
-
Interações em suas próprias páginas auto-anexam novos erros de console. Clique, digite, navegue em localhost ou em um arquivo local → qualquer nova linha
[error]/[exception]/[dialog]acompanha o resultado da ferramenta, deduplicada por um cursor, rotulada honestamente (um diálogo de confirmação que seu clique acionou não é um "erro"). Sites externos permanecem livres de ruído — você não quer o spam de console de alguma rede de anúncios em um contexto de 16K. -
Um verbo de atualização real.
browser_refreshfaz um verdadeiro hard reload (cache ignorado). Antes de existir, os modelos diziam "atualizando a página" e então apenas tiravam outra captura de tela do DOM antigo. - Um portão de encerramento. Quando o modelo tenta encerrar o turno com tarefas pendentes não finalizadas, ou com arquivos da web editados mas nunca rechecados no navegador, ele é rejeitado — uma vez. Um empurrão corretivo, então sua próxima resposta fica. Zero loops de importunação.
5. Quebradores de loop precisam de julgamento, não apenas um martelo
Interceptar chamadas repetidas idênticas é o básico — ls . cinco vezes não ajuda ninguém. Mas um quebrador genérico mata trabalho legítimo: clicar em "Próximo" três vezes é
Empresas brasileiras podem se beneficiar da implementação de agentes de IA locais, melhorando a eficiência e reduzindo a dependência de serviços em nuvem. A abordagem prática apresentada pode ser adaptada para otimizar processos internos e aumentar a produtividade em tarefas automatizadas.

