Repository navigation
Mensagem de erro promete rate limit que a execução não trata como rate limit #243
Description
Desde o #203, is_rate_limit_error decide pela classe quando o erro é um ProviderError do pacote: só o ProviderOverloadedError conta como rate limit, e um "429" no texto não reduz os workers. get_friendly_error_message ainda decide pelo texto. Um ProviderError com "429" na mensagem recebe a caixa "LIMITE DE REQUISIÇÕES ATINGIDO", que manda reduzir parallel_requests e aumentar rate_limit_delay, enquanto a execução segue com os mesmos workers. Isso afeta quem usa codex ou claude_code: no primeiro, o turno que falha sem código estruturado e com "429 Too Many Requests" só no texto; no segundo, o result de erro do SDK, que traz texto do modelo e pode citar, por exemplo, o art. 429 do CPC. Nos dois, o usuário lê um diagnóstico que não é o da execução.
import warnings
from unittest.mock import patch
import pandas as pd
from pydantic import BaseModel
from dataframeit import dataframeit
from dataframeit.errors import (
ProviderError,
ProviderTransientError,
get_friendly_error_message,
is_rate_limit_error,
)
def titulo(mensagem):
"""A primeira linha com texto da caixa da mensagem amigável."""
return next(t for linha in mensagem.splitlines() if (t := linha.strip("║╔╗╚╝╠╣═ ")))
# Mensagens iguais às que `_raise_turn_error` (codex) e `_raise_for_result_error`
# (claude_code) montam nesses casos.
erros = {
"codex": ProviderError("Turno Codex falhou: 429 Too Many Requests"),
"claude_code": ProviderTransientError(
"Claude Code SDK retornou erro (error_during_execution, status None): "
"Não foi possível concluir: o art. 429 do CPC exige..."
),
}
for provider, erro in erros.items():
print(provider, is_rate_limit_error(erro), titulo(get_friendly_error_message(erro, provider)))
# codex False LIMITE DE REQUISIÇÕES ATINGIDO
# claude_code False LIMITE DE REQUISIÇÕES ATINGIDO
class Campo(BaseModel):
campo: str
with patch("dataframeit.core.call_langchain", side_effect=erros["codex"]), patch(
"dataframeit.core.validate_provider_dependencies"
), warnings.catch_warnings(record=True) as avisos:
warnings.simplefilter("always")
dataframeit(pd.DataFrame({"texto": ["a", "b"]}), Campo, "{texto}",
parallel_requests=2, max_retries=1, track_tokens=False)
for aviso in avisos:
linhas = str(aviso.message).splitlines()
print(linhas[0], titulo("\n".join(linhas[1:])))
# Falha ao processar linha 0. LIMITE DE REQUISIÇÕES ATINGIDO
# Falha ao processar linha 1. LIMITE DE REQUISIÇÕES ATINGIDO
# (nenhum aviso "Rate limit detectado! Reduzindo workers")Medido na main em 97a5100. Conferi também os dois erros produzidos pelos caminhos reais, _raise_turn_error com um TurnError(message="429 Too Many Requests", codexErrorInfo=None) e _raise_for_result_error com um ResultMessage falso de is_error=True, e o resultado é o mesmo.
A divergência também corre no sentido oposto: um ProviderOverloadedError sem "429" no texto reduz os workers, mas recebe a caixa genérica.
from dataframeit.errors import ProviderOverloadedError, get_friendly_error_message, is_rate_limit_error
erro = ProviderOverloadedError("Turno Codex falhou: server overloaded")
print(is_rate_limit_error(erro), get_friendly_error_message(erro, "codex").splitlines()[1].strip("║ "))
# True ERRO NO PROCESSAMENTOCausa
Em errors.py, is_rate_limit_error devolve False para todo ProviderError e ProviderOutputError que não seja ProviderOverloadedError, antes de olhar o texto. get_friendly_error_message só trata pela classe o ProviderRejectedOutputError e o ProviderOutputError; os demais ProviderError caem em is_category, que, sem status HTTP estruturado, procura "429" no texto da exceção e escolhe a caixa de rate limit. As outras categorias decididas pelo texto (credencial, permissão, timeout) têm o mesmo risco para o ProviderError cuja mensagem traz texto do modelo, mas aqui a divergência é direta com uma decisão que o pacote já tomou pela classe.
Proposta
Fazer a mensagem decidir o rate limit pela mesma regra: para ProviderError, a caixa de rate limit sai só quando is_rate_limit_error devolve True, isto é, para o ProviderOverloadedError, e o restante cai na mensagem genérica, que mostra o tipo e o começo do texto do erro. Um teste parametrizado com os dois erros acima, que exija que a caixa de rate limit e a redução de workers apareçam juntas ou não apareçam, impede que as duas regras se separem de novo.
Activity