Agentes de IA podem consumir recursos de forma imprevisível. Por isso, limitar apenas o número de requisições nem sempre é suficiente. Neste artigo, apresento estratégias de rate limiting para controlar esse consumo e proteger a infraestrutura.
Agentes de IA baseados em large language models não se limitam a responder perguntas. Eles interpretam objetivos, decidem os próximos passos e utilizam ferramentas para executar tarefas.
Esses agentes podem atuar como assistentes de chat, trabalhar em background até entregar um artefato, reagir a eventos operacionais ou coordenar outros agentes especializados. Cada forma de execução apresenta um padrão de consumo diferente: chats exigem baixa latência, tarefas em background podem realizar muitas chamadas, eventos provocam picos de demanda e sistemas multi-agente multiplicam operações em paralelo.
Essa dinâmica torna o gerenciamento de recursos de IA generativa mais complexo do que em uma API tradicional. Uma única tarefa pode consumir várias requisições e uma quantidade de tokens difícil de prever antecipadamente.
AI Agents
Ainda não existe uma definição universal para o que é um agente. A OpenAI fez um bom trabalho simplificando no artigo A practical guide to building agents, que menciona duas características importantes, diferenciando um chat-bot simples de um AI agent.
- Utiliza um LLM para conduzir o workflow e tomar decisões, reconhecendo quando ele foi concluído e corrigindo suas ações quando necessário.
- Seleciona dinamicamente tools para obter contexto ou executar ações e, em caso de falha, interrompe a execução e devolve o controle ao usuário, sempre dentro de limites bem definidos.
O que limitar?
Não existe uma resposta única: a forma de implementar o rate-limit varia de acordo com o cenário e os requisitos técnicos e de negócio.
Em geral, um limitador cumpre duas funções:
- Prevenir abusos e evitar o consumo de recursos além da capacidade computacional disponível.
- Aplicar as regras definidas pelo modelo de uso e licenciamento.
Como discutido anteriormente, um AI agent é, antes de tudo, um sistema que geralmente se manifesta como um serviço web. No Azure AI Foundry Hosted Agents, por exemplo, as requisições podem chegar pelos seguintes protocolos de runtime:
- Responses
- Invocations
- Invocations (WebSocket)
Como esses protocolos apresentam padrões de interação distintos, a métrica usada pelo limitador precisa refletir o consumo de recursos. Existem técnicas maduras de rate-limit que podem ser reutilizadas no contexto de um AI agent.
Na maioria dos cenários, o tamanho da mensagem pode servir como uma aproximação inicial do consumo de tokens, permitindo definir limites por segundo, minuto ou hora. Embora não seja exata, essa estimativa costuma funcionar bem como ponto de partida.
Algoritmos conhecidos
Token bucket
Nesse algoritmo, o termo token representa uma unidade abstrata de capacidade e não deve ser confundido com os tokens processados por modelos de IA generativa.
O token bucket funciona como um reservatório com capacidade máxima definida. Tokens são adicionados continuamente a uma taxa fixa, enquanto cada requisição remove uma determinada quantidade. Quando não há tokens suficientes, a requisição precisa aguardar ou é rejeitada.
A capacidade do reservatório determina quantas requisições podem ser processadas em uma rajada, enquanto a taxa de reposição controla o consumo médio permitido ao longo do tempo. Por exemplo, um reservatório com capacidade para 100 tokens e reposição de 10 tokens por segundo permite uma rajada inicial de até 100 requisições, mas sustenta, em média, 10 requisições por segundo.
Em resumo, o token bucket é especialmente útil para controlar picos repentinos de tráfego e proteger o serviço contra sobrecarga. Ele permite rajadas dentro da capacidade configurada, mas impede que elas ultrapassem o volume que o sistema consegue absorver. Seu objetivo principal não é estabelecer uma cota exata por janela de tempo, mas preservar a estabilidade do serviço diante de variações bruscas na demanda.
Fixed window
A técnica de janela fixa divide o tempo em intervalos de duração predefinida, como um minuto ou uma hora. Cada intervalo é representado por uma chave gerada a partir do timestamp atual e mantém seu próprio contador de requisições.
Por exemplo, com um limite de 100 requisições por minuto, todas as requisições realizadas entre 10:00:00 e 10:00:59 incrementam o mesmo contador. Ao iniciar 10:01:00, uma nova chave é criada e a contagem recomeça.
Essa abordagem funciona bem em sistemas distribuídos quando o contador é armazenado em um serviço compartilhado, como o Redis, pois todas as instâncias da aplicação consultam e atualizam o mesmo valor. Além de simples e eficiente, a expiração da chave pode ser configurada para coincidir com o fim da janela.
Sua principal limitação está na transição entre janelas: um cliente pode consumir todo o limite no final de uma janela e repeti-lo logo no início da próxima, concentrando muitas requisições em um curto período.
Sliding Window Log
O sliding window log registra o timestamp de cada requisição em uma estrutura ordenada que preserve todos os eventos ainda dentro da janela. Para um limite de 10 requisições por minuto, por exemplo, o algoritmo precisa considerar todas as requisições realizadas nos 60 segundos anteriores ao instante atual.
A cada nova requisição, os registros anteriores ao início da janela são removidos. Em seguida, o algoritmo conta os registros restantes: se a quantidade estiver abaixo do limite, adiciona o novo timestamp e permite a operação; caso contrário, recusa a requisição.
Isso evita o pico artificial que pode ocorrer na transição entre duas janelas fixas e oferece um controle mais preciso do uso.
Essa precisão tem um custo: o algoritmo armazena um registro para cada requisição e precisa remover dados expirados e contar os registros válidos continuamente, por isso, o sliding window log é mais adequado quando a precisão do limite é prioritária e o número de eventos por janela permanece controlado.
Sliding Window Counter
O sliding window counter mantém dois valores: o total de requisições do minuto anterior e o total do minuto atual.
Às 10:01:15, a janela dos últimos 60 segundos começa às 10:00:15. Portanto, ela inclui os 45 segundos finais do minuto anterior e os 15 segundos do minuto atual.
Essa técnica usa menos memória que o sliding window log, mas produz uma estimativa porque não armazena o horário exato de cada requisição.
Decisão orientada a contexto
É impossível determinar, em um artigo curto, qual é a melhor solução para todos os cenários. Essa decisão depende de diversos fatores, como a arquitetura do sistema, o modelo de negócio, o volume de tráfego e o nível de precisão necessário.
Como exemplo, considere um agente de IA comercializado em larga escala por meio de um modelo de assinatura. Nesse contexto, eu adotaria a estratégia descrita a seguir.
Partindo do princípio de que o agente é um serviço web executado em um cluster Kubernetes, ele poderia escalar horizontalmente por meio de réplicas.
O tráfego passaria primeiro por um balanceador e seria distribuído entre essas réplicas. Para proteger cada instância contra picos de carga, faria sentido implementar um token bucket local, com limites proporcionais à capacidade de processamento disponível. Isso é especialmente importante em aplicações que consomem uma LLM, pois cada execução pode ser cara e ter duração variável; uma rajada de requisições pode esgotar rapidamente a capacidade de concorrência, aumentar a latência e comprometer a estabilidade do serviço.
Já para controlar a utilização do agente conforme a licença de cada usuário, eu abriria mão de uma contagem exata e adotaria o sliding window counter, usando a identidade do usuário autenticado como chave. Isso permitiria aplicar limites individuais sem elevar significativamente os custos de armazenamento e processamento. A menor precisão seria um compromisso aceitável nesse cenário.
O estado poderia ser estruturado como uma tabela com uma linha para cada combinação de usuário e janela:
user_id | window_start | request_count |
|---|---|---|
user-123 | 10:00:00 | 80 |
user-123 | 10:01:00 | 20 |
Como a aplicação utiliza uma LLM, a principal unidade para esses limites seria uma estimativa do consumo de tokens, calculada assim que a requisição entrasse no sistema. Dessa forma, o controle refletiria melhor o custo real de cada operação do que uma simples contagem de requisições.
Cache e estado distribuído
O rate limiting é aplicado assim que uma requisição entra no sistema. Mesmo que a aplicação seja composta por vários serviços e réplicas, a decisão de permitir ou recusar uma operação depende de um estado compartilhado e consistente.
Esse estado pode ser mantido em um armazenamento temporário distribuído, responsável por centralizar logicamente os contadores e torná-los acessíveis a todas as instâncias. Como cada requisição precisa consultá-lo e atualizá-lo, o armazenamento deve oferecer baixa latência, operações atômicas, alta disponibilidade e tolerância a falhas. Caso essa camada se torne lenta ou indisponível, ela pode comprometer a entrada de todo o sistema.
Existem diferentes tecnologias capazes de atender a esses requisitos, e algumas empresas até desenvolvem soluções próprias. Mais importante que escolher um produto específico, porém, é compreender as características necessárias e manter a implementação desacoplada da tecnologia de armazenamento adotada.
Conclusão
Projetar o rate limiting de um agente de IA vai além de definir uma quantidade máxima de requisições por minuto. Como cada execução pode acionar diferentes ferramentas, consumir volumes distintos de tokens e permanecer ativa por tempos imprevisíveis, o limite precisa representar tanto a capacidade técnica do sistema quanto as regras do produto.
Na prática, a solução mais equilibrada tende a combinar estratégias: um token bucket para absorver rajadas sem sobrecarregar as instâncias e um sliding window counter para controlar o consumo de cada usuário conforme sua licença. Um estado distribuído de baixa latência conecta essas decisões e mantém o comportamento consistente entre todas as réplicas.
Não existe uma configuração definitiva. Os limites devem ser definidos a partir de estimativas iniciais, observados em produção e ajustados conforme o padrão real de uso. Mais do que bloquear requisições, um bom rate limiter preserva a estabilidade do serviço, mantém os custos previsíveis e distribui os recursos de forma justa entre os usuários.