From 932009fd1d554db8e97151efef42c2751e6f4393 Mon Sep 17 00:00:00 2001 From: Supernova Date: Tue, 14 Jul 2026 18:39:49 +0000 Subject: [PATCH] Map Bedrock 'too many tokens, please wait' 429 to RateLimitError Bedrock returns a 429 with "Too many tokens, please wait before trying again" for token-rate throttling. The message matches the generic "too many tokens" context-length pattern, so the 429 branch was misclassifying it as ContextLengthExceededError instead of a retryable RateLimitError. Add a rate-limit pattern that matches the throttle phrasing so it is caught before the context-length check. Co-Authored-By: Claude Opus 4.8 Co-authored-by: Sam Boland --- lib/ruby_llm/error_middleware.rb | 3 ++- spec/ruby_llm/error_middleware_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/ruby_llm/error_middleware.rb b/lib/ruby_llm/error_middleware.rb index 8ebc98508..2a560f22f 100644 --- a/lib/ruby_llm/error_middleware.rb +++ b/lib/ruby_llm/error_middleware.rb @@ -49,7 +49,8 @@ class << self /rate limit/i, /per minute/i, /per hour/i, - /per day/i + /per day/i, + /too many tokens.*please wait/i ].freeze def parse_error(provider:, response:) diff --git a/spec/ruby_llm/error_middleware_spec.rb b/spec/ruby_llm/error_middleware_spec.rb index e9afd70a8..3dbb53d63 100644 --- a/spec/ruby_llm/error_middleware_spec.rb +++ b/spec/ruby_llm/error_middleware_spec.rb @@ -74,6 +74,26 @@ end.to raise_error(RubyLLM::RateLimitError) end + it "maps Bedrock's 'too many tokens, please wait' 429 throttle to RateLimitError, not context length" do + msg = 'Too many tokens, please wait before trying again.' + response = Struct.new(:status, :body).new(429, %({"error":{"message":"#{msg}"}})) + provider = instance_double(RubyLLM::Provider, parse_error: msg) + + expect do + described_class.parse_error(provider: provider, response: response) + end.to raise_error(RubyLLM::RateLimitError) + end + + it "still maps a 400 'too many tokens' error to ContextLengthExceededError" do + msg = 'Too many tokens in the request' + response = Struct.new(:status, :body).new(400, %({"error":{"message":"#{msg}"}})) + provider = instance_double(RubyLLM::Provider, parse_error: msg) + + expect do + described_class.parse_error(provider: provider, response: response) + end.to raise_error(RubyLLM::ContextLengthExceededError) + end + it 'maps context-length-like 400 errors to ContextLengthExceededError' do msg = "This model's maximum context length is 8192 tokens." response = Struct.new(:status, :body).new(400, %({"error":{"message":"#{msg}"}}))