|
12 | 12 | from idp_common import s3 |
13 | 13 | from assessment_validator import AssessmentValidator |
14 | 14 |
|
| 15 | +# Custom exception for throttling scenarios |
| 16 | +class ThrottlingException(Exception): |
| 17 | + """Exception raised when throttling is detected in document processing results""" |
| 18 | + pass |
| 19 | + |
| 20 | +# Throttling detection constants |
| 21 | +THROTTLING_KEYWORDS = [ |
| 22 | + "throttlingexception", |
| 23 | + "provisionedthroughputexceededexception", |
| 24 | + "servicequotaexceededexception", |
| 25 | + "toomanyrequestsexception", |
| 26 | + "requestlimitexceeded", |
| 27 | + "too many tokens", |
| 28 | + "please wait before trying again", |
| 29 | + "reached max retries" |
| 30 | +] |
| 31 | + |
| 32 | +THROTTLING_EXCEPTIONS = [ |
| 33 | + "ThrottlingException", |
| 34 | + "ProvisionedThroughputExceededException", |
| 35 | + "ServiceQuotaExceededException", |
| 36 | + "TooManyRequestsException", |
| 37 | + "RequestLimitExceeded" |
| 38 | +] |
| 39 | + |
15 | 40 | # Configuration will be loaded in handler function |
16 | 41 |
|
17 | 42 | logger = logging.getLogger() |
18 | 43 | logger.setLevel(os.environ.get("LOG_LEVEL", "INFO")) |
19 | 44 | logging.getLogger('idp_common.bedrock.client').setLevel(os.environ.get("BEDROCK_LOG_LEVEL", "INFO")) |
20 | 45 |
|
| 46 | +def is_throttling_exception(exception): |
| 47 | + """ |
| 48 | + Check if an exception is related to throttling. |
| 49 | + |
| 50 | + Args: |
| 51 | + exception: The exception to check |
| 52 | + |
| 53 | + Returns: |
| 54 | + bool: True if the exception is throttling-related, False otherwise |
| 55 | + """ |
| 56 | + from botocore.exceptions import ClientError |
| 57 | + |
| 58 | + if isinstance(exception, ClientError): |
| 59 | + error_code = exception.response.get('Error', {}).get('Code', '') |
| 60 | + return error_code in THROTTLING_EXCEPTIONS |
| 61 | + |
| 62 | + exception_name = type(exception).__name__ |
| 63 | + exception_message = str(exception).lower() |
| 64 | + |
| 65 | + return ( |
| 66 | + exception_name in THROTTLING_EXCEPTIONS or |
| 67 | + any(keyword in exception_message for keyword in THROTTLING_KEYWORDS) |
| 68 | + ) |
| 69 | + |
| 70 | +def check_document_for_throttling_errors(document): |
| 71 | + """ |
| 72 | + Check if a document has throttling errors in its errors field. |
| 73 | + |
| 74 | + Args: |
| 75 | + document: The document object to check |
| 76 | + |
| 77 | + Returns: |
| 78 | + tuple: (has_throttling_errors: bool, first_throttling_error: str or None) |
| 79 | + """ |
| 80 | + if document.status != Status.FAILED or not document.errors: |
| 81 | + return False, None |
| 82 | + |
| 83 | + for error_msg in document.errors: |
| 84 | + error_lower = str(error_msg).lower() |
| 85 | + if any(keyword in error_lower for keyword in THROTTLING_KEYWORDS): |
| 86 | + return True, error_msg |
| 87 | + |
| 88 | + return False, None |
| 89 | + |
21 | 90 | def handler(event, context): |
22 | 91 | """ |
23 | 92 | Lambda handler for document assessment. |
@@ -85,55 +154,39 @@ def handler(event, context): |
85 | 154 | t1 = time.time() |
86 | 155 | logger.info(f"Total assessment time: {t1-t0:.2f} seconds") |
87 | 156 |
|
88 | | - # Check for failed assessment tasks that might require retry |
89 | | - if (hasattr(updated_document, 'metadata') and |
90 | | - updated_document.metadata and |
91 | | - 'failed_assessment_tasks' in updated_document.metadata): |
92 | | - |
93 | | - failed_tasks = updated_document.metadata['failed_assessment_tasks'] |
94 | | - throttling_tasks = { |
95 | | - task_id: task_info for task_id, task_info in failed_tasks.items() |
96 | | - if task_info.get('is_throttling', False) |
97 | | - } |
98 | | - |
99 | | - logger.warning( |
100 | | - f"Assessment completed with {len(failed_tasks)} failed tasks, " |
101 | | - f"{len(throttling_tasks)} due to throttling" |
102 | | - ) |
103 | | - |
104 | | - if throttling_tasks: |
105 | | - logger.info( |
106 | | - f"Throttling detected in {len(throttling_tasks)} tasks. " |
107 | | - f"Successful tasks have been cached for retry." |
| 157 | + # Check for failed assessment tasks that might require retry (granular assessment) |
| 158 | + if hasattr(updated_document, 'metadata') and updated_document.metadata: |
| 159 | + failed_tasks = updated_document.metadata.get('failed_assessment_tasks', {}) |
| 160 | + if failed_tasks: |
| 161 | + throttling_tasks = { |
| 162 | + task_id: task_info for task_id, task_info in failed_tasks.items() |
| 163 | + if task_info.get('is_throttling', False) |
| 164 | + } |
| 165 | + |
| 166 | + logger.warning( |
| 167 | + f"Assessment completed with {len(failed_tasks)} failed tasks, " |
| 168 | + f"{len(throttling_tasks)} due to throttling" |
108 | 169 | ) |
| 170 | + |
| 171 | + if throttling_tasks: |
| 172 | + logger.info( |
| 173 | + f"Throttling detected in {len(throttling_tasks)} tasks. " |
| 174 | + f"Successful tasks have been cached for retry." |
| 175 | + ) |
| 176 | + |
| 177 | + # Check for throttling errors in document status and errors field |
| 178 | + has_throttling, throttling_error = check_document_for_throttling_errors(updated_document) |
| 179 | + if has_throttling: |
| 180 | + logger.error(f"Throttling error detected in document errors: {throttling_error}") |
| 181 | + logger.error("Raising ThrottlingException to trigger Step Functions retry") |
| 182 | + raise ThrottlingException(f"Throttling detected in document processing: {throttling_error}") |
109 | 183 |
|
110 | 184 | except Exception as e: |
111 | 185 | t1 = time.time() |
112 | 186 | logger.error(f"Assessment failed after {t1-t0:.2f} seconds: {str(e)}") |
113 | 187 |
|
114 | 188 | # Check if this is a throttling exception that should trigger retry |
115 | | - from botocore.exceptions import ClientError |
116 | | - throttling_exceptions = [ |
117 | | - "ThrottlingException", |
118 | | - "ProvisionedThroughputExceededException", |
119 | | - "ServiceQuotaExceededException", |
120 | | - "TooManyRequestsException", |
121 | | - "RequestLimitExceeded" |
122 | | - ] |
123 | | - |
124 | | - is_throttling = False |
125 | | - if isinstance(e, ClientError): |
126 | | - error_code = e.response.get('Error', {}).get('Code', '') |
127 | | - is_throttling = error_code in throttling_exceptions |
128 | | - else: |
129 | | - exception_name = type(e).__name__ |
130 | | - exception_message = str(e).lower() |
131 | | - is_throttling = ( |
132 | | - exception_name in throttling_exceptions or |
133 | | - any(throttle_term.lower() in exception_message for throttle_term in throttling_exceptions) |
134 | | - ) |
135 | | - |
136 | | - if is_throttling: |
| 189 | + if is_throttling_exception(e): |
137 | 190 | logger.error(f"Throttling exception detected: {type(e).__name__}. This will trigger state machine retry.") |
138 | 191 | # Update document status before re-raising |
139 | 192 | document_service.update_document(docStatus) |
|
0 commit comments