Repository navigation
Resposta recusada com número no texto não vira erro de credencial - #203
Conversation
No claude_code, a falha de parsing ou de validação da resposta saía como ValidationError ou ValueError com o texto da resposta na mensagem, e a classificação procurava 401, 403 e 404 nesse texto. Com "art. 401 da CLT" na resposta, a linha falhava sem nova tentativa e a mensagem mandava reconfigurar a autenticação. Agora a falha levanta ProviderRejectedOutputError, transitória pela classe. No codex, a mensagem amigável do ProviderOutputError passa a ser decidida pela classe, e a mensagem do erro de schema deixa de trazer o valor recusado. validation_error_summary, em errors.py, monta o caminho e a regra de cada erro sem o valor, para o LangChain, o claude_code e o codex. Closes #192
is_rate_limit_error passa a decidir pela classe para ProviderError, ProviderConfigurationError e ProviderOutputError. Um '429' no caminho de um erro de validação, como a chave de um dict na resposta do codex, contava como rate limit e reduzia os workers. A mensagem amigável do ProviderOutputError descreve também a resposta vazia e o turno interrompido, em que nenhuma regra falhou. A docstring de validation_error_summary, o guia de erros e o CHANGELOG dizem que caminho e regra ainda trazem texto da resposta quando o Pydantic o põe ali. O teto de erros listados vira uma constante só, usada pelo resumo e pelo pedido de correção do LangChain. Testes novos cobrem o ramo do ProviderOutputError na mensagem amigável com número no caminho e o rate limit decidido pela classe.
Revisão independente: correção confirmada, PR ainda não pronto para mergeUm subagente novo revisou A correção da issue 192 funciona. Na reprodução da issue, a Achados abertos
Fora do diff, e relevante para o Um 401 real do |
Conflito só no CHANGELOG.md, em Corrigido, onde os dois lados inseriram entradas no mesmo ponto; ficam todas.
A entrada do extra codex citava o pin 0.159.2; o pyproject.toml fixa openai-codex==0.160.1, que instala o runtime 0.160.1.
A tupla tinha ganhado ProviderError e ProviderConfigurationError além de ProviderOutputError. Com ProviderError nela, um turno do codex que falha com "429 Too Many Requests" no texto e sem código classificável deixava de reduzir os workers, embora a mensagem amigável dissesse limite de requisições. Tirar ProviderConfigurationError não derrubava teste. Fica o que a issue pede: a resposta fora do contrato, cujo resumo de validação traz texto do modelo, decide pela classe, e o ProviderError definitivo volta a ser lido pelo texto. Um teste prende cada lado.
Dois comentários diziam que a mensagem não traz texto da resposta. Traz: o resumo da validação leva a chave de dict recusada e a mensagem de validador próprio, e por isso a classificação decide pela classe. A mensagem amigável nova não tinha teste. Voltar o texto que prometia o erro de volta ao modelo, ou tirar o detalhe do erro da mensagem de ProviderOutputError, passava na suíte; agora cada mudança derruba um teste.
…o resumo O codex recusa modelo com dict de chaves dinâmicas antes de processar, e o exemplo da chave de dict só acontece no claude_code. O CHANGELOG, o guia de erros e a fixture dos testes passam a usar a mensagem de um validador próprio para o codex. Os READMEs dizem que a resposta recusada volta ao modelo só nos providers do LangChain. Testes novos: o resumo da validação conta todos os erros e lista até o teto, e a mensagem da resposta fora do contrato diz que a linha não é tentada de novo.
Segunda revisão independente: sem achado que bloqueie; uma decisão de desenho abertaOs achados da primeira revisão foram reproduzidos e corrigidos em O que confirmou. A correção da #192 funciona: resposta com "art. 401" na chave de um dict, validador com "Súmula 403" e JSON truncado passam de 1 tentativa com erro de credencial para nova tentativa com Quatro achados menores, corrigidos em
Fora do escopo, com issue própria: o status HTTP que o SDK do Claude Code entrega em Aberto, e trava o merge: como |
# Conflicts: # CHANGELOG.md
Rate limit do pacote é só ProviderOverloadedError. A mensagem de um ProviderError do claude_code leva o campo result do SDK, que pode trazer texto do modelo, e um "429" ali reduzia os workers sem haver limite de requisições. Os demais ProviderError deixam de ser lidos pelo texto, como o ProviderOutputError já não era. Um turno do codex que falha só com "429 Too Many Requests" no texto, sem código classificável, deixa de reduzir os workers. O teste que prendia o comportamento anterior troca de lado, e um teste novo monta o erro do claude_code como _raise_for_result_error o monta.
Evidência:
|
main (c0cc0ce8) |
branch antes (8c09e3dd) |
branch agora (8ebaee8e) |
|
|---|---|---|---|
| exceção | ProviderError |
ProviderError |
ProviderError |
is_rate_limit_error |
True |
True |
False |
| linhas com erro | 4 | 4 | 4 |
| avisos de redução de workers | 2 (4 para 2, 2 para 1) | 2 (4 para 2, 2 para 1) | 0 |
| mensagem amigável, primeira linha | LIMITE DE REQUISIÇÕES ATINGIDO | LIMITE DE REQUISIÇÕES ATINGIDO | LIMITE DE REQUISIÇÕES ATINGIDO |
Mensagem da exceção nos três: Claude Code SDK retornou erro (error_max_turns, status None): Não terminei a análise do fundamento do art. 429 do CPC.
Tabela de classificação, só as linhas que mudam
11 classes (as 8 exceções de errors.py, mais ValueError, RuntimeError e OutputParserException do LangChain) por 4 textos ("429 Too Many Requests", "art. 429 do CPC", "rate limit exceeded", "401"), 44 linhas por árvore. Colunas: is_rate_limit_error / is_recoverable_error / primeira linha com texto de get_friendly_error_message. A primeira linha é a mesma com provider="codex" e provider="claude_code" nas 44 linhas das três árvores.
| Classe | Texto | main |
branch antes | branch agora |
|---|---|---|---|---|
ProviderError |
429 Too Many Requests | True / False / LIMITE DE REQUISIÇÕES | True / False / LIMITE DE REQUISIÇÕES | False / False / LIMITE DE REQUISIÇÕES |
ProviderError |
art. 429 do CPC | True / False / LIMITE DE REQUISIÇÕES | True / False / LIMITE DE REQUISIÇÕES | False / False / LIMITE DE REQUISIÇÕES |
ProviderAbortError |
429 Too Many Requests | True / False / LIMITE DE REQUISIÇÕES | True / False / LIMITE DE REQUISIÇÕES | False / False / LIMITE DE REQUISIÇÕES |
ProviderAbortError |
art. 429 do CPC | True / False / LIMITE DE REQUISIÇÕES | True / False / LIMITE DE REQUISIÇÕES | False / False / LIMITE DE REQUISIÇÕES |
ProviderUsageLimitError |
429 Too Many Requests | True / False / LIMITE DE REQUISIÇÕES | True / False / LIMITE DE REQUISIÇÕES | False / False / LIMITE DE REQUISIÇÕES |
ProviderUsageLimitError |
art. 429 do CPC | True / False / LIMITE DE REQUISIÇÕES | True / False / LIMITE DE REQUISIÇÕES | False / False / LIMITE DE REQUISIÇÕES |
ProviderOutputError |
429 Too Many Requests | True / False / LIMITE DE REQUISIÇÕES | False / False / RESPOSTA FORA DO CONTRATO | False / False / RESPOSTA FORA DO CONTRATO |
ProviderOutputError |
art. 429 do CPC | True / False / LIMITE DE REQUISIÇÕES | False / False / RESPOSTA FORA DO CONTRATO | False / False / RESPOSTA FORA DO CONTRATO |
ProviderOutputError |
rate limit exceeded | False / False / ERRO NO PROCESSAMENTO | False / False / RESPOSTA FORA DO CONTRATO | False / False / RESPOSTA FORA DO CONTRATO |
ProviderOutputError |
401 | False / False / ERRO DE AUTENTICAÇÃO | False / False / RESPOSTA FORA DO CONTRATO | False / False / RESPOSTA FORA DO CONTRATO |
Entre o branch antes e agora mudam só as seis primeiras linhas, todas na coluna is_rate_limit_error; is_recoverable_error e a mensagem amigável não mudam em nenhuma das 44. Em core.py, ProviderAbortError e as subclasses são capturadas antes da chamada a is_rate_limit_error, e por isso as quatro linhas delas não mudam o comportamento da execução paralela. ProviderConfigurationError com "429" no texto continua True, igual à main.
A mensagem amigável ainda diz "LIMITE DE REQUISIÇÕES ATINGIDO" para um ProviderError com "429" no texto, inclusive o "art. 429 do CPC" do caso acima, que agora não reduz os workers; get_friendly_error_message não foi alterada, e o ProviderTransientError com "429" no texto já tinha essa combinação na main.
Mutações
Cada mutação trocou a linha da tupla em errors.py, rodou test_errors.py, test_classificacao_de_erro.py, test_parallel_requests.py, test_erros_de_provider.py e test_claude_code.py (145 testes) com PYTHONDONTWRITEBYTECODE=1 e __pycache__ apagado, e restaurou o arquivo, conferido por hash.
| Mutação | Resultado |
|---|---|
tupla anterior, (ProviderTransientError, ProviderOutputError) |
3 falham: test_429_no_result_do_claude_code_nao_conta_como_rate_limit e os dois casos de test_provider_error_com_429_na_mensagem_nao_conta_como_rate_limit |
(ProviderError,), sem ProviderOutputError |
1 falha: test_numero_no_resumo_da_validacao_nao_conta_como_rate_limit |
| sem a checagem da tupla | 4 falham: as três acima e a do ProviderOutputError |
tupla com ProviderConfigurationError a mais |
145 passam: mutante equivalente para a suíte, e por isso a classe ficou fora |
Testes executados e pulados, main ao lado do branch, no mesmo ambiente
A suíte da main rodou da árvore de c0cc0ce8 com PYTHONPATH no src dela, conferido por dataframeit.__file__, no mesmo venv da suíte do branch.
| Job | main (c0cc0ce8) |
branch (8ebaee8e) |
|---|---|---|
| unit, Python 3.13 | 1184 passaram, 105 pulados | 1195 passaram, 106 pulados |
| unit, Python 3.11 | 1184 passaram, 105 pulados | 1195 passaram, 106 pulados |
| codex-provider, Python 3.13 | 1175 passaram, 135 pulados | 1187 passaram, 135 pulados |
| codex-provider, Python 3.11 | 1175 passaram, 135 pulados | 1187 passaram, 135 pulados |
| lowest-direct, sem extras | 1048 passaram, 241 pulados | 1059 passaram, 242 pulados |
lowest-direct, all excel polars search-all |
1183 passaram, 106 pulados | 1194 passaram, 107 pulados |
Nenhuma falha. Os motivos de pulo são os mesmos nos dois lados; o pulo a mais no unit e no lowest-direct é o teste novo test_number_in_rejected_response_is_not_a_credential_error, de test_codex.py, que pede openai_codex e roda no job do codex (103 pulos por falta de openai_codex na main, 104 no branch).
- Cobertura de ramos combinada dos quatro arquivos (unit e codex-provider, 3.11 e 3.13), com o
fail_under = 100dopyproject.toml: 3044 linhas, 1190 ramos, 0 faltando, 100%. - Job
qualitycom--extra dev --extra all --extra search-all --extra polars --extra excel --extra codexem 3.13:ruff format --check src tests,ruff check src tests,ty check,vultureedeptry srcpassam. mkdocs buildcom--extra docspassa, com os mesmos dois avisos domkdocs_static_i18ne do Material que amaindá.
SHA revisado: 8ebaee8
# Conflicts: # CHANGELOG.md
…adedError O is_retryable_error do SDK reconhece só server_overloaded, em snake_case, no data do erro JSON-RPC. Um CodexRpcError com rate_limit_exceeded saía ProviderError e, com is_rate_limit_error decidindo pela classe, deixava de reduzir os workers. O data passa a ser lido pela mesma regra do turno que falha (_is_overload_info), nas duas grafias do código, sem ler a mensagem.
FAQ, guia de performance, nas duas línguas, e a docstring de dataframeit() diziam que os workers caem a cada 429. Eles caem quando o provider identifica o rate limit pelo status ou pelo código do erro.
Evidência: código de rate limit no erro JSON-RPC do
|
| Árvore | Reduções de workers | Avisos de nova tentativa | _error_details |
|---|---|---|---|
main (c6e99a10) |
2 | 0 | [Erro não-recuperável] ProviderError: CodexRpcError: JSON-RPC error -32000: 429 Too Many Requests |
8ebaee8e |
0 | 0 | [Erro não-recuperável] ProviderError: CodexRpcError: JSON-RPC error -32000: 429 Too Many Requests |
fbd05cd9 |
2 | 6 | [Falhou após 2 tentativa(s)] ProviderOverloadedError: CodexRpcError: JSON-RPC error -32000: 429 Too Many Requests |
Na main, os workers caíam pelo texto, mas a linha falhava sem nova tentativa. Agora os workers caem pelo código, e a linha é repetida como no turno que falha com rate_limit_exceeded (X2).
Reduções de workers por caso, nas três árvores
Mesmo script, 18 casos: os do claude_code e do LangChain com o SDK e o modelo falsos no venv com --extra dev --extra all --extra search-all, os do codex no venv do caso X5. Cada célula traz reduções de workers / avisos de nova tentativa. Nas 54 execuções, as 6 linhas terminam com status error.
| Caso | main |
8ebaee8e |
fbd05cd9 |
|---|---|---|---|
| claude_code: C1 is_error max_turns, result 'art. 429 do CPC' | 2 / 0 | 0 / 0 | 0 / 0 |
| claude_code: C2 is_error api_error_status=429 | 2 / 6 | 2 / 6 | 2 / 6 |
| claude_code: C3 is_error api_error_status=529 | 2 / 6 | 2 / 6 | 2 / 6 |
| claude_code: C4 query() levanta ResultError status 429 | 2 / 6 | 2 / 6 | 2 / 6 |
| claude_code: C5 resposta com confianca='art. 429 do CPC' | 2 / 6 | 0 / 6 | 0 / 6 |
| claude_code: C6 is_error status 400, result com '429' | 2 / 0 | 0 / 0 | 0 / 0 |
| claude_code: C7 is_error sem status, result '429 Too Many Requests' | 0 / 6 | 0 / 6 | 0 / 6 |
| langchain(openai): L1 openai.RateLimitError 429 | 2 / 6 | 2 / 6 | 2 / 6 |
| langchain(openai): L2 ModelRateLimitError | 2 / 6 | 2 / 6 | 2 / 6 |
| langchain(openai): L3 RuntimeError '429 Too Many Requests' | 2 / 6 | 2 / 6 | 2 / 6 |
| langchain(openai): L4 parsing_error com 'art. 429' | 0 / 6 | 0 / 6 | 0 / 6 |
| langchain(openai): L5 OutputParserException com '429' no llm_output | 0 / 6 | 0 / 6 | 0 / 6 |
| codex: X1 turno failed 'other', mensagem '429 Too Many Requests' | 2 / 0 | 0 / 0 | 0 / 0 |
| codex: X2 turno failed rate_limit_exceeded | 2 / 6 | 2 / 6 | 2 / 6 |
| codex: X3 turno failed HTTP 429 | 2 / 6 | 2 / 6 | 2 / 6 |
| codex: X4 resposta com confianca='art. 429 do CPC' | 2 / 0 | 0 / 0 | 0 / 0 |
| codex: X5 thread_start levanta CodexRpcError com rate_limit_exceeded | 2 / 0 | 0 / 0 | 2 / 6 |
| codex: X6 turno failed bad_request, mensagem com 'art. 429' | 2 / 0 | 0 / 0 | 0 / 0 |
Entre 8ebaee8e e fbd05cd9 muda só o X5. O X1 é a consequência aceita da decisão pela classe: o turno que falha com código other e '429' só no texto não reduz os workers. A tabela de classificação de is_rate_limit_error, is_recoverable_error e get_friendly_error_message (16 classes por 9 textos, 144 linhas) é idêntica em 8ebaee8e e fbd05cd9; em errors.py mudou só o comentário acima da tupla.
Testes novos
Em tests/test_codex.py, 15 casos, todos com o SDK real e sem rede:
test_rpc_rate_limit_code_is_overload_and_reduces_workers: o caso X5 nobackend.invoke, com nova tentativa,ProviderOverloadedErroreis_rate_limit_errorverdadeiro.test_rpc_overload_code_in_data_is_overload, 6 formas dodata:rate_limit_exceededem snake_case,rateLimitExceededem camelCase,flex_unavailablena chaveerrorInfo, a variantehttpConnectionFailedcom status 429, o código num dict aninhado e numa lista.test_rpc_error_without_overload_code_is_definitive, 7 formas, todas com "429 Too Many Requests" na mensagem:datanulo, o código como texto solto, o código numa chave que não é de código,badRequest, um código desconhecido, a variante HTTP com status 401 eunauthorizednuma lista. SaemProviderErrordefinitivo, eis_rate_limit_erroré falso.test_non_rpc_error_data_is_not_read: uma exceção que não é erro JSON-RPC, comdatade rate limit, continuaProviderError.
Com os testes novos sobre o src de 8ebaee8e: 7 falham e 8 passam. Falham o X5 e as 6 formas positivas, inclusive [{"codexErrorInfo": "serverOverloaded"}], porque o SDK só reconhece a grafia em snake_case. Passam os 8 casos que conferem o comportamento definitivo, que 8ebaee8e já tinha.
Mutações
Cada mutante numa cópia limpa da árvore, com PYTHONDONTWRITEBYTECODE=1, -p no:cacheprovider, -o pythonpath no src da cópia e o __file__ de dataframeit.codex conferido. Rodam tests/test_codex.py e tests/test_errors.py, e a base sem mutação passa com 248 testes.
Mutante em codex.py |
Testes que falham |
|---|---|
_raise_classified_sdk_error sem a leitura do data |
7 |
sem a guarda isinstance(error, JsonRpcError) |
1 (test_non_rpc_error_data_is_not_read) |
lista no data não é percorrida |
1 |
data que não é dict conta como sobrecarga |
7 |
| código reconhecido na chave não decide | 7 |
| dict aninhado não é percorrido | 1 |
| sem a grafia em snake_case | 3 |
código desconhecido vira rate_limit_exceeded |
1 |
rate_limit_exceeded fora do conjunto de sobrecarga |
5 |
| HTTP 429 da variante deixa de ser sobrecarga | 2 |
_raise_turn_error não consulta _is_overload_info |
8 |
| variante HTTP não reconhecida | 4 |
sem a chave errorInfo |
1 |
| status 4xx da variante HTTP do turno vira transitório | 1 |
| variante HTTP do turno cai nos códigos | 2 |
Nenhum mutante sobrevive.
Testes executados e pulados, main ao lado do branch, no mesmo ambiente
A suíte da main rodou da árvore de c6e99a10, com PYTHONPATH no src dela, conferido por dataframeit.__file__, no mesmo venv da suíte do branch. Nas versões mínimas, cada árvore foi instalada em modo editável no venv, como o job faz.
| Job | main (c6e99a10) |
branch (fbd05cd9) |
|---|---|---|
| unit, Python 3.13 | 1220 passaram, 105 pulados | 1231 passaram, 121 pulados |
| unit, Python 3.11 | 1220 passaram, 105 pulados | 1231 passaram, 121 pulados |
| codex-provider, Python 3.13 | 1175 passaram, 171 pulados | 1202 passaram, 171 pulados |
| codex-provider, Python 3.11 | 1175 passaram, 171 pulados | 1202 passaram, 171 pulados |
| lowest-direct, sem extras | 1048 passaram, 277 pulados | 1059 passaram, 293 pulados |
lowest-direct, all excel polars search-all |
1219 passaram, 106 pulados | 1230 passaram, 122 pulados |
Nenhuma falha. Os pulos a mais no unit e no lowest-direct estão todos em tests/test_codex.py (104 na main, 120 no branch, no unit): são os 15 testes novos e test_number_in_rejected_response_is_not_a_credential_error, que pedem openai_codex e rodam no job do codex. No job do codex, os pulos são os mesmos, arquivo a arquivo.
- Cobertura de ramos combinada dos quatro arquivos (unit e codex-provider, 3.11 e 3.13), com o
fail_under = 100dopyproject.toml: 3094 linhas, 1212 ramos, 0 faltando, 100%. - Job
qualitycom--extra dev --extra all --extra search-all --extra polars --extra excel --extra codexem 3.13:ruff format --check src tests,ruff check src tests,ty check,vultureedeptry srcpassam. mkdocs buildcom--extra docspassa; os avisos são o aviso da equipe do Material sobre o MkDocs 2.0 e os dois domkdocs_static_i18nsobre o seletor de idioma.
SHA revisado: fbd05cd
O app-server 0.160.1 recusa com o código JSON-RPC -32001, mensagem "Server overloaded; retry later." e data nulo, o pedido que chega com a fila de entrada cheia. O is_retryable_error do SDK procura a sobrecarga só no data, e esse erro saía ProviderError definitivo: a linha falhava na primeira tentativa e os workers não caíam. _raise_classified_sdk_error passa a levantar ProviderOverloadedError pelo código, sem ler a mensagem. Sai a leitura de códigos de rate limit no data do erro JSON-RPC, que nenhum erro do app-server envia, com os helpers que só ela usava; _raise_turn_error volta ao que era antes dela. Os testes da falha HTTP sem status ou 5xx e dos códigos transitórios do turno conferem que o erro não conta como rate limit. A documentação diz que os workers caem quando a linha falha depois de todas as tentativas, que o codex reconhece o rate limit pelo código ou pelo status do erro e que os providers do LangChain também leem o texto da mensagem quando o erro não traz status HTTP.
# Conflicts: # CHANGELOG.md
Evidência: erro -32001 do app-server do
|
| Arquivo | Antes | Depois |
|---|---|---|
src/dataframeit/codex.py |
+97 −29 | +12 −2 |
src/dataframeit/errors.py |
+3 −2 | +4 −3 |
src/dataframeit/core.py |
+2 −1 | +4 −1 |
tests/test_codex.py |
+71 −0 | +110 −19 |
| docs (FAQ e performance, pt e en) | +4 −4 | +8 −4 |
CHANGELOG.md |
+1 −1 | +1 −1 |
O -32001 de ponta a ponta
dataframeit() com 6 linhas, parallel_requests=4, max_retries=2, provider="codex" e o backend do codex com SDK falso, montado pelos helpers de tests/test_codex.py. O thread_start levanta, em toda chamada, o erro que o SDK monta para a resposta do app-server:
map_jsonrpc_error(-32001, "Server overloaded; retry later.", None)Cada árvore rodou com PYTHONPATH no próprio src, conferido por dataframeit.__file__, no mesmo venv (Python 3.13, --extra dev --extra codex e os demais extras). Nenhuma chamada de rede.
| Árvore | Reduções de workers | Avisos de nova tentativa | Chamadas a thread_start |
_error_details |
|---|---|---|---|---|
main (8dc97892) |
0 | 0 | 6 | [Erro não-recuperável] ProviderError: CodexRpcError: JSON-RPC error -32001: Server overloaded; retry later. |
8ebaee8e |
0 | 0 | 6 | igual à main |
fbd05cd9 |
0 | 0 | 6 | igual à main |
92cbe791 |
2 | 6 | 12 | [Falhou após 2 tentativa(s)] ProviderOverloadedError: CodexRpcError: JSON-RPC error -32001: Server overloaded; retry later. |
Os mesmos casos de antes, no mesmo script (reduções / avisos de nova tentativa):
| Caso | main |
fbd05cd9 |
92cbe791 |
|---|---|---|---|
CodexRpcError(-32000, "429 Too Many Requests", None), '429' só no texto |
2 / 0 | 0 / 0 | 0 / 0 |
CodexRpcError(-32000, "429 Too Many Requests", {"codex_error_info": "rate_limit_exceeded"}), o formato inventado |
2 / 0 | 2 / 6 | 0 / 0 |
Tabela de classificação
594 entradas por árvore: os TurnError de todos os códigos do CodexErrorInfoValue e das quatro variantes HTTP com sete status, com mensagem neutra e com "429 Too Many Requests"; os erros JSON-RPC montados pelo map_jsonrpc_error com os códigos -32000, -32001, -32603 e -32600, quatro mensagens e 31 formas de data; e seis exceções avulsas. Cada entrada traz a classe levantada, is_rate_limit_error e is_recoverable_error.
8ebaee8econtra92cbe791: nenhum dos 92 turnos muda, nem as seis exceções avulsas. Mudam 84 entradas, todas de código -32001, deProviderErrordefinitivo paraProviderOverloadedErrorcomis_rate_limit_errorverdadeiro. As 124 entradas de -32001 saemProviderOverloadedError; as outras 40 já saíam, porque o SDK as entrega comoServerBusyError.fbd05cd9contra92cbe791: além das entradas de -32001, 122 voltam deProviderOverloadedErrorparaProviderErrordefinitivo; são as formas dodataque só o formato inventado reconhecia.
Documentação conferida rodando
Mesmos scripts, árvore 92cbe791, com o modelo ou o SDK falso:
| Frase | Caso | Reduções / tentativas |
|---|---|---|
| Os workers caem só quando a linha falha depois de todas as tentativas | codex, -32001 na primeira tentativa de cada linha e sucesso na segunda |
0 / 6, as 6 linhas processed |
| idem | codex, -32001 em toda tentativa |
2 / 6 |
O codex não lê a mensagem |
codex, -32000 com "429 Too Many Requests" e data nulo |
0 / 0 |
| O LangChain lê o texto quando o erro não traz status | RuntimeError("429 Too Many Requests") |
2 / 6 |
| idem, e o status decide quando existe | erro com status_code=500 e "429 Too Many Requests" no texto |
0 / 6 |
| idem | erro com status_code=429 e texto "falhou" |
2 / 6 |
O claude_code ficou fora da frase "pelo código ou pelo status". O ResultMessage com is_error é classificado pelo status, mas o ResultError que o SDK (claude-agent-sdk 0.2.165) levanta de dentro do query() não é ProviderError, e is_rate_limit_error lê o texto dele: com query() levantando ResultError de api_error_status=500 e "art. 429 do CPC" no texto, os workers caem (2 / 6); com api_error_status=529 e "API Error: Overloaded", não caem (0 / 6). É o caso da #227, fora deste PR.
Mutações
Cada mutante numa cópia limpa da árvore, com PYTHONDONTWRITEBYTECODE=1, -p no:cacheprovider, -o pythonpath no src da cópia e o __file__ de dataframeit.codex conferido. Rodam tests/test_codex.py e tests/test_errors.py; a base sem mutação passa com 239 testes.
Mutante em codex.py |
Testes que falham |
|---|---|
sem o ramo do -32001 (o comportamento de 8ebaee8e) |
2 (test_fila_cheia_do_app_server_e_sobrecarga, test_fila_cheia_reduz_workers_e_repete_a_linha) |
sem a guarda isinstance(error, JsonRpcError) |
1 (test_codigo_fora_de_erro_json_rpc_nao_e_lido) |
| constante -32000 no lugar de -32001 | 3 |
error.code <= -32001, faixa no lugar do código |
1 (o -32002) |
| decide pela mensagem, "overloaded" no texto | 2 (o -32000 e o -32002) |
| HTTP sem status vira sobrecarga | 1; com os testes de 8ebaee8e, 0 |
| HTTP 5xx vira sobrecarga | 1; com os testes de 8ebaee8e, 0 |
| códigos transitórios do turno viram sobrecarga | 2; com os testes de 8ebaee8e, 0 |
Nenhum mutante sobrevive. Os três últimos sobreviviam à suíte anterior, porque os testes da falha HTTP e do código transitório só conferiam a nova tentativa, que a sobrecarga também faz.
Testes executados e pulados, main ao lado do branch, no mesmo ambiente
A suíte da main rodou da árvore de 8dc97892, com PYTHONPATH no src dela, conferido por dataframeit.__file__, no mesmo venv da suíte do branch. Nas versões mínimas, cada árvore foi instalada em modo editável no venv, como o job faz.
| Job | main (8dc97892) |
branch (92cbe791) |
|---|---|---|
| unit, Python 3.13 | 1233 passaram, 105 pulados | 1244 passaram, 112 pulados |
| unit, Python 3.11 | 1233 passaram, 105 pulados | 1244 passaram, 112 pulados |
| codex-provider, Python 3.13 | 1175 passaram, 184 pulados | 1193 passaram, 184 pulados |
| codex-provider, Python 3.11 | 1175 passaram, 184 pulados | 1193 passaram, 184 pulados |
lowest-direct, all excel polars search-all |
1232 passaram, 106 pulados | 1243 passaram, 113 pulados |
Nenhuma falha. O branch tem 18 testes a mais (7 em tests/test_codex.py, 8 em tests/test_errors.py e 3 em tests/test_claude_code.py). Os 7 pulos a mais no unit e no lowest-direct são os de tests/test_codex.py, que pedem openai_codex e rodam no job do codex, onde os pulos são os mesmos nas duas árvores.
- Cobertura de ramos combinada dos quatro arquivos (unit e codex-provider, 3.11 e 3.13), com o
fail_under = 100dopyproject.toml: 3075 linhas, 1204 ramos, 0 faltando, 100%. - Job
qualitycom--extra dev --extra all --extra search-all --extra polars --extra excel --extra codexem 3.13:ruff format --check src tests,ruff check src tests,ty check,vultureedeptry srcpassam. mkdocs buildcom--extra docspassa; os avisos são o da equipe do Material sobre o MkDocs 2.0 e os dois domkdocs_static_i18nsobre o seletor de idioma.
SHA revisado: 92cbe79
Revisão independente da rodada final (
|
# Conflicts: # CHANGELOG.md # tests/test_codex.py
Problema
Quando a resposta do modelo falhava no parse ou na validação e o texto continha
401,403ou404, a falha era classificada como erro de credencial ou de permissão. Em texto jurídico isso é comum: "art. 401 da CLT", "Súmula 403 do STJ".ValidationErrorou oValueErrordoparse_jsonsaía com o valor recusado na mensagem, eis_recoverable_errorprocurava os códigos HTTP nesse texto. A linha falhava na primeira tentativa, e a mensagem amigável mandava reconfigurar a autenticação. Com "art. 12" no lugar, a mesma falha era tentada de novo.ProviderOutputErrorlevava o valor recusado na mensagem, e a mensagem amigável, classificada pelo texto, mandava rodarcodex login.O que muda
claude_code: a falha de parsing ou de validação levantaProviderRejectedOutputError, transitória pela classe, como no caminho LangChain.get_friendly_error_messagedecide pela classe também paraProviderOutputError, antes de qualquer busca no texto.is_rate_limit_errordecide pela classe para todoProviderErrore para oProviderOutputError, e entre eles só oProviderOverloadedErrorconta como rate limit. A mensagem dessas exceções pode trazer texto do modelo: o resumo da validação, noProviderOutputErrordo codex, e o camporesultdo SDK, noProviderErrordoclaude_code. Um '429' nesse texto reduzia os workers sem haver limite de requisições. O texto da mensagem deProviderRejectedOutputErrordeixa de afirmar que o erro foi levado de volta ao modelo, o que só vale para o LangChain.codex: o erro JSON-RPC de código -32001, com que o app-server 0.160.1 recusa o pedido quando a fila de entrada está cheia ("Server overloaded; retry later.",datanulo), levantaProviderOverloadedError, pelo código e sem ler a mensagem. Ois_retryable_errordo SDK procura a sobrecarga só nodata, e esse erro saíaProviderError: namain, a linha falhava na primeira tentativa e os workers não caíam; agora a linha é repetida e, se falhar em todas as tentativas, os workers caem. O erro docodex, de turno ou JSON-RPC, que traz "429 Too Many Requests" só no texto não reduz os workers: o rate limit é reconhecido na origem, pelo código do erro.validation_error_summary, emerrors.py, monta o caminho e a regra de cada erro sem o valor recusado (oinputdo Pydantic). O LangChain já gravava assim; agora oclaude_codee ocodexusam a mesma função. Caminho e regra ainda trazem texto da resposta quando o Pydantic o põe ali (chave de dict, tag de união, mensagem de validador próprio), e por isso a classificação decide pela classe.dataframeit()deixam de dizer que os workers caem a cada 429: eles caem pela metade quando uma linha falha depois de todas as tentativas com erro de rate limit ou de sobrecarga. Ocodexreconhece o rate limit pelo código ou pelo status do erro, sem ler a mensagem, e os providers do LangChain também pelo texto da mensagem quando o erro não traz status HTTP.O pedido de correção com retorno no
claude_codee a nova tentativa nocodexficam para a refatoração descrita em #193.Verificação
tests/test_claude_code.py,tests/test_codex.pyetests/test_errors.py, que falham sem a correção (inclusive o ramo doProviderOutputErrorcom número no resumo da validação e oProviderErrorcom "429" no texto, montado também como_raise_for_result_errormonta o erro doclaude_code, que não reduz os workers): resposta com "art. 401" e JSON truncado com "Súmula 403" são tentados de novo noclaude_code; nocodex, a mensagem não traz o valor e a amigável não fala em credencial.codex, o erro -32001 comdatanulo, montado pelomap_jsonrpc_errordo SDK, viraProviderOverloadedError; de ponta a ponta, comparallel_requests=4, cada linha é repetida e os workers caem de 4 para 2 e de 2 para 1. Os códigos -32000 e -32002 com a mesma mensagem continuam definitivos. A falha HTTP sem status ou 5xx e os códigos transitórios do turno são repetidos sem contar como rate limit. Oito mutações decodex.pyderrubam algum teste, entre elas as três que tornavam sobrecarga a falha HTTP sem status, a 5xx e os códigos transitórios, que sobreviviam à suíte anterior.mainatual (8dc97892): 1244 testes passam em Python 3.13 e em 3.11 no job unit, e 1193 no job docodex, nas duas versões; cobertura de ramos combinada em 100%; 1243 nas versões mínimas comall excel polars search-all.ruff format,ruff check,ty,vulture,deptryemkdocs buildpassam.Closes #192