fix: bedrock throttling exception on maintainers processing [CM-740]#3913
fix: bedrock throttling exception on maintainers processing [CM-740]#3913
Conversation
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
|
|
There was a problem hiding this comment.
Pull request overview
This PR improves robustness of the Bedrock invocation path used by maintainer processing by adding explicit retry/backoff behavior for Bedrock throttling, and simplifying error handling around response parsing/validation.
Changes:
- Add throttling retry loop with exponential backoff + jitter around
bedrock_client.invoke_model. - Introduce module-level retry/backoff constants and new imports to support retry sleep and jitter.
- Restructure response parsing/validation error handling and logging.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| logger.error(f"Amazon Bedrock API error: {e}") | ||
| logger.error("Failed to parse the response as JSON. Raw response:") | ||
| logger.error(response_body["content"][0]["text"]) | ||
| raise e |
There was a problem hiding this comment.
raise e resets the traceback and makes debugging harder. Use a bare raise here so the original exception context is preserved.
| raise e | |
| raise |
| # Validate output with the provided model if it exists | ||
| try: | ||
| body_bytes = await response["body"].read() | ||
| response_body = json.loads(body_bytes.decode("utf-8")) | ||
| raw_text = response_body["content"][0]["text"].replace('"""', "").strip() | ||
|
|
||
| # Expect pure JSON - no markdown handling | ||
| output = json.loads(raw_text) | ||
|
|
||
| # Calculate cost | ||
| input_tokens = response_body["usage"]["input_tokens"] | ||
| output_tokens = response_body["usage"]["output_tokens"] | ||
| input_cost = (input_tokens / 1000) * 0.003 | ||
| output_cost = (output_tokens / 1000) * 0.015 | ||
| total_cost = input_cost + output_cost | ||
|
|
||
| # Validate output with the provided model if it exists | ||
| try: | ||
| validated_output = pydantic_model.model_validate(output, strict=True) | ||
| except ValidationError as ve: | ||
| logger.error(f"Output validation failed: {ve}") | ||
| raise ve | ||
|
|
||
| return BedrockResponse[T](output=validated_output, cost=total_cost) | ||
| except Exception as e: | ||
| logger.error("Failed to parse the response as JSON. Raw response:") | ||
| logger.error(response_body["content"][0]["text"]) | ||
| raise e | ||
| validated_output = pydantic_model.model_validate(output, strict=True) | ||
| except ValidationError as ve: | ||
| logger.error(f"Output validation failed: {ve}") | ||
| raise ve | ||
|
|
||
| return BedrockResponse[T](output=validated_output, cost=total_cost) | ||
| except Exception as e: | ||
| logger.error(f"Amazon Bedrock API error: {e}") | ||
| logger.error("Failed to parse the response as JSON. Raw response:") | ||
| logger.error(response_body["content"][0]["text"]) | ||
| raise e |
There was a problem hiding this comment.
The outer except Exception will also catch ValidationError raised from the validation block and log it as a JSON parsing failure (and potentially log raw model text). Consider narrowing this handler to JSON/KeyError-related failures (or adding a dedicated except ValidationError: raise) so validation errors aren’t misclassified.
| for attempt in range(1, MAX_THROTTLE_RETRIES + 1): | ||
| try: | ||
| response = await bedrock_client.invoke_model( | ||
| body=body, modelId=modelId, accept=accept, contentType=contentType | ||
| ) | ||
| break |
There was a problem hiding this comment.
This adds a custom retry loop on throttling, but the client Config already enables botocore retries (retries={"max_attempts": 3}), which can compound the total number of attempts and overall wait time. Consider consolidating retry behavior (either rely on botocore retries, or reduce/disable them when using this explicit backoff loop) to keep worst-case latency bounded and predictable.
| except Exception as e: | ||
| logger.error(f"Amazon Bedrock API error: {e}") | ||
| logger.error("Failed to parse the response as JSON. Raw response:") | ||
| logger.error(response_body["content"][0]["text"]) | ||
| raise e |
There was a problem hiding this comment.
The except block logs response_body["content"][0]["text"], but response_body may be undefined if JSON decoding fails (or if response["body"].read()/json.loads(...) raises). This can mask the original error with an UnboundLocalError. Consider initializing response_body/raw text to a safe default and logging body_bytes (decoded) when available, or using logger.exception without indexing into response_body in the failure path.
| except ValidationError as ve: | ||
| logger.error(f"Output validation failed: {ve}") | ||
| raise ve |
There was a problem hiding this comment.
raise ve resets the traceback/context. If the goal is just to rethrow after logging, use a bare raise to preserve the original stack trace.
This pull request improves the robustness of the
invoke_bedrockfunction by adding retry logic to handle throttling errors from the Amazon Bedrock API. It introduces exponential backoff with jitter for retries and cleans up error handling to avoid redundant exception logging.Error handling and reliability improvements:
ThrottlingExceptionerrors when calling the Bedrock API, allowing up to 5 retries with increasing delays. [1] [2] [3]Dependency and import updates:
asyncio,random, andClientErrorto support the new retry and delay logic.Note
Medium Risk
Adds retry/backoff and new sleep delays around Bedrock model invocation, which can change latency and failure behavior for maintainer processing if errors are misclassified or retries exhaust.
Overview
Improves
invoke_bedrockrobustness by retryingbedrock_client.invoke_modelon AWSThrottlingExceptionup to 5 times using exponential backoff with jitter, logging a warning and delaying between attempts.Refactors response parsing/validation to run after the retry loop and adjusts error logging to focus on JSON parse failures (logging the raw model text) rather than a generic “Bedrock API error” message.
Written by Cursor Bugbot for commit 49f2b90. This will update automatically on new commits. Configure here.