-
Notifications
You must be signed in to change notification settings - Fork 35
Add exponential backoff retry logic for AI service failures #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Copilot
wants to merge
5
commits into
main
Choose a base branch
from
copilot/fix-ai-service-error-handling
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
dea1df1
Initial plan
Copilot d0ac18a
Add retry logic with exponential backoff to AI services
Copilot 075cc74
Add comprehensive tests for retry logic with exponential backoff
Copilot a13903a
Add manual integration test for retry logic
Copilot bd49c76
Move import statements to top of files per code review
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,12 @@ | |
| import httpx | ||
| from PIL import Image | ||
| import asyncio | ||
| from retry_utils import exponential_backoff_retry | ||
| import logging | ||
| import base64 | ||
|
Comment on lines
+6
to
+8
|
||
|
|
||
| # Configure logging | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # HF_TOKEN is optional for public models but recommended for higher limits | ||
| token = os.environ.get("HF_TOKEN") | ||
|
|
@@ -19,8 +25,12 @@ async def query_hf_api(image_bytes, labels, client=None): | |
| async with httpx.AsyncClient() as new_client: | ||
| return await _make_request(new_client, image_bytes, labels) | ||
|
|
||
| async def _make_request(client, image_bytes, labels): | ||
| import base64 | ||
| @exponential_backoff_retry(max_retries=3, base_delay=1.0, max_delay=10.0) | ||
| async def _make_request_with_retry(client, image_bytes, labels): | ||
| """ | ||
| Internal function that makes HF API request with retry logic. | ||
| Raises exception on failure to allow retry decorator to work. | ||
| """ | ||
| image_base64 = base64.b64encode(image_bytes).decode('utf-8') | ||
|
|
||
| payload = { | ||
|
|
@@ -30,19 +40,28 @@ async def _make_request(client, image_bytes, labels): | |
| } | ||
| } | ||
|
|
||
| response = await client.post(API_URL, headers=headers, json=payload, timeout=20.0) | ||
| if response.status_code != 200: | ||
| error_msg = f"HF API Error: {response.status_code} - {response.text}" | ||
| logger.error(error_msg) | ||
| raise Exception(error_msg) | ||
| return response.json() | ||
|
|
||
|
|
||
| async def _make_request(client, image_bytes, labels): | ||
| """ | ||
| Makes request to Hugging Face API with retry logic and proper error handling. | ||
| """ | ||
| try: | ||
| response = await client.post(API_URL, headers=headers, json=payload, timeout=20.0) | ||
| if response.status_code != 200: | ||
| print(f"HF API Error: {response.status_code} - {response.text}") | ||
| return [] | ||
| return response.json() | ||
| return await _make_request_with_retry(client, image_bytes, labels) | ||
| except Exception as e: | ||
| print(f"HF API Request Exception: {e}") | ||
| logger.error(f"HF API Request failed after all retries: {e}", exc_info=True) | ||
| return [] | ||
|
|
||
| async def detect_vandalism_clip(image: Image.Image, client: httpx.AsyncClient = None): | ||
| """ | ||
| Detects vandalism/graffiti using Zero-Shot Image Classification with CLIP (Async). | ||
| Includes retry logic with exponential backoff for transient failures. | ||
| """ | ||
| try: | ||
| labels = ["graffiti", "vandalism", "spray paint", "street art", "clean wall", "public property", "normal street"] | ||
|
|
@@ -69,10 +88,14 @@ async def detect_vandalism_clip(image: Image.Image, client: httpx.AsyncClient = | |
| }) | ||
| return detected | ||
| except Exception as e: | ||
| print(f"HF Detection Error: {e}") | ||
| logger.error(f"HF Vandalism Detection Error: {e}", exc_info=True) | ||
| return [] | ||
|
|
||
| async def detect_infrastructure_clip(image: Image.Image, client: httpx.AsyncClient = None): | ||
| """ | ||
| Detects infrastructure damage using Zero-Shot Image Classification with CLIP (Async). | ||
| Includes retry logic with exponential backoff for transient failures. | ||
| """ | ||
| try: | ||
| labels = ["broken streetlight", "damaged traffic sign", "fallen tree", "damaged fence", "pothole", "clean street", "normal infrastructure"] | ||
|
|
||
|
|
@@ -97,10 +120,14 @@ async def detect_infrastructure_clip(image: Image.Image, client: httpx.AsyncClie | |
| }) | ||
| return detected | ||
| except Exception as e: | ||
| print(f"HF Detection Error: {e}") | ||
| logger.error(f"HF Infrastructure Detection Error: {e}", exc_info=True) | ||
| return [] | ||
|
|
||
| async def detect_flooding_clip(image: Image.Image, client: httpx.AsyncClient = None): | ||
| """ | ||
| Detects flooding/waterlogging using Zero-Shot Image Classification with CLIP (Async). | ||
| Includes retry logic with exponential backoff for transient failures. | ||
| """ | ||
| try: | ||
| labels = ["flooded street", "waterlogging", "blocked drain", "heavy rain", "dry street", "normal road"] | ||
|
|
||
|
|
@@ -125,5 +152,5 @@ async def detect_flooding_clip(image: Image.Image, client: httpx.AsyncClient = N | |
| }) | ||
| return detected | ||
| except Exception as e: | ||
| print(f"HF Detection Error: {e}") | ||
| logger.error(f"HF Flooding Detection Error: {e}", exc_info=True) | ||
| return [] | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The json import at module level is redundant since it was already used in the original code within the try block. Consider keeping it at module level throughout for consistency.