Manter os turnos longos do agente dentro da janela de contexto
Um modelo só consegue ler uma certa quantidade de cada vez — a sua janela de contexto. O agente da Mythex faz trabalho longo: lê muitos ficheiros, executa comandos, e entrega tarefas limitadas a sub-agentes. Encontrámos três formas de o trabalho longo falhar: os sub-agentes cresciam até o modelo recusar todos os pedidos, um sub-agente parava cedo porque achava que já não tinha espaço, e a nossa contagem de tokens estava muito errada para texto em russo, chinês e japonês. Eis o que mudámos.
Alguns termos primeiro
- Tokens são os blocos que um modelo lê. Os limites e os custos são contados em tokens, não em caracteres ou palavras.
- Janela de contexto é o máximo que um modelo consegue ler num único pedido. Nós também definimos um orçamento próprio, mais baixo, para cada pedido, para que os custos e a velocidade se mantenham razoáveis.
- Resultados de ferramentas são o que o agente recebe de volta das suas ferramentas: o conteúdo de um ficheiro que leu, o resultado de um comando que executou. No trabalho longo, são a maior parte do que é enviado.
- Um sub-agente é um auxiliar que o agente principal lança para uma tarefa autocontida, como «verifica estes capítulos contra as notas». Recebe uma única instrução e depois trabalha por conta própria através de muitas chamadas de ferramentas.
Problema 1: os sub-agentes cresciam até o modelo os recusar
A cada passo, o agente envia ao modelo a conversa até ao momento. Quando isso ficava demasiado grande, o nosso código cortava-a — mas só no início de uma mensagem de utilizador, para que uma chamada de ferramenta e o seu resultado nunca fossem separados. Mantinha a primeira mensagem, acrescentava uma nota a dizer que a conversa anterior tinha sido cortada, e guardava os turnos completos mais recentes que coubessem.
Um sub-agente tem exatamente uma mensagem de utilizador: a sua instrução. Não havia onde cortar. Por isso enviava o histórico inteiro em cada passo, crescendo sempre, até o pedido ser maior do que o modelo aceita de todo — cerca de um milhão de tokens. Num teste com 8 desses sub-agentes, todos os 8 foram recusados.
A correção: limpar primeiro os resultados de ferramentas antigos
Os pedidos agora cabem no orçamento em três passos, tentando cada um apenas se o anterior não for suficiente:
- Limpar os resultados de ferramentas mais antigos. O seu conteúdo é substituído por uma nota curta: o resultado foi limpo para manter a conversa dentro do contexto do modelo, e o agente pode voltar a executar a ferramenta se precisar. Um ficheiro lido há uma hora é normalmente a coisa maior e menos útil no pedido, e o ficheiro continua lá para ser lido de novo. Os resultados mais recentes ficam inteiros.
- Manter turnos de utilizador completos. Como antes: a primeira mensagem, uma nota, e os turnos completos mais recentes que couberem.
- Manter rondas de ferramentas completas. Dentro de um turno longo — o único tipo que um sub-agente tem — manter a primeira mensagem, uma nota, e as rondas completas mais recentes de «chamar uma ferramenta, receber o resultado».
Isto só molda o que é enviado. O histórico guardado do agente mantém tudo. No mesmo teste, 0 de 8 foram recusados e os 8 terminaram.
Limpar em lotes, para que a cache continue a funcionar
Os fornecedores de modelos conseguem reutilizar trabalho no início de um pedido se for idêntico ao anterior — uma cache de prefixo. Se limpássemos mais um resultado em cada chamada, o início do pedido mudaria em cada chamada e a cache nunca ajudaria.
Por isso, os resultados mais antigos são limpos quatro de cada vez, e não um a um. A parte limpa mantém-se igual durante várias chamadas. Um resultado que não seja muito maior do que a nota fica intacto: limpá-lo pouparia quase nada e ainda assim mudaria o início do pedido. A quantidade que fica inteira é decidida pelo tamanho, e não pelo número: os resultados mais recentes que caibam num quarto do orçamento ficam, e pelo menos um fica sempre. Uma versão anterior mantinha fixos seis inteiros, o que era correto para saídas curtas de comandos, mas, com leituras longas de ficheiros, deixava a maior parte do orçamento para resultados antigos.
As recusas passam a ser explicadas
Um pedido que o modelo rejeita como inválido já não é repetido; enviar o mesmo pedido outra vez obtém a mesma resposta. Se a conversa for demasiado longa para o modelo, a pessoa vê: «Esta conversa ficou demasiado longa para o modelo a ler de uma vez. Comece um novo chat para o próximo passo — tudo o que foi construído até agora fica.» Isso cabe à pessoa resolver, não é uma avaria, por isso não aciona alertas para nós.
Problema 2: o agente parava para «poupar espaço»
Um sub-agente a quem foi pedido que lesse 40 capítulos longos parou depois de 2, dizendo que queria poupar espaço. Tinha usado cerca de 100.000 tokens de um orçamento de 400.000.
Não tinha forma de saber que os resultados antigos iam passar a ser limpos para ele. Por isso dissemos-lhe. As instruções do agente dizem agora que, quando uma conversa cresce além do que o modelo consegue aceitar, os resultados de ferramentas mais antigos são limpos e os mais recentes ficam inteiros — por isso não pare uma tarefa longa para poupar espaço; continue, e leia de novo um ficheiro se precisar do que foi limpo.
Uma correção que o modelo não vê não é uma correção do ponto de vista do modelo. O mecanismo só ajuda se o agente confiar nele.
Problema 3: contar tokens como se tudo fosse inglês
Estimávamos os tokens sem correr um tokenizador, como caracteres divididos por quatro. É uma boa regra prática para inglês. Não o é para outras escritas: o russo tem cerca de 2,2 caracteres por token, e o chinês ou o japonês cerca de um.
Por isso, um sub-agente a ler capítulos em russo enviava um pedido de cerca de 640.000 tokens, enquanto a nossa estimativa dizia que estava abaixo do orçamento de 400.000. A estimativa podia igualmente ter colocado um pedido para além do limite rígido do modelo.
A estimativa conta agora por escrita: ASCII simples a quatro caracteres por token, chinês, japonês e coreano a um, e tudo o resto — cirílico, grego, árabe, letras acentuadas, emojis — a dois. Está pensada para errar por excesso. Num capítulo em russo, estima agora 43.423 tokens contra cerca de 44.600 medidos; antes, dizia 25.023. Como uma mensagem nunca muda depois de escrita, a sua estimativa é calculada uma vez e memorizada, em vez de ser recalculada em cada passo.
O que tirámos disto
- Corte onde o tamanho realmente está. No trabalho com agentes, isso são os resultados de ferramentas, não as mensagens do utilizador.
- Diga ao agente quais são as suas redes de segurança. Caso contrário, planeia à volta de limites que já não se aplicam.
- «Caracteres ÷ 4» é uma suposição do inglês. Qualquer coisa que conte tokens para utilizadores multilingues tem de contar por escrita, ou usar um tokenizador real.
- Cuidado com a cache. Um pedido que muda no início em cada chamada custa mais e corre mais devagar.
Se está a criar funcionalidades de IA na sua própria app, os nossos guias sobre o que é um LLM e como usar APIs de LLM cobrem desde o início as janelas de contexto e os tokens. Mais sobre como os sub-agentes funcionam na Mythex está nos documentos sobre sub-agentes.