33from typing import List , Optional
44
55import boto3
6+ from botocore .exceptions import BotoCoreError , ClientError
67
78from docs2vecs .subcommands .indexer .config .config import Config
89from docs2vecs .subcommands .indexer .document .document import Document
910from docs2vecs .subcommands .indexer .skills .skill import IndexerSkill
1011
12+ # Bedrock error codes that represent transient failures worth retrying.
13+ # Permanent errors (ValidationException, AccessDeniedException,
14+ # ResourceNotFoundException, etc.) surface immediately.
15+ _RETRYABLE_BEDROCK_CODES = frozenset ({
16+ "ThrottlingException" ,
17+ "TooManyRequestsException" ,
18+ "ServiceUnavailableException" ,
19+ "InternalServerException" ,
20+ "ModelTimeoutException" ,
21+ "ModelStreamErrorException" ,
22+ })
23+
1124
1225class BedrockTitanEmbeddingSkill (IndexerSkill ):
1326 DEFAULT_MODEL_ID = "amazon.titan-embed-text-v2:0"
@@ -27,6 +40,14 @@ def __init__(self, config: dict, global_config: Config):
2740 region_name = self ._config .get ("region" ),
2841 )
2942
43+ def _is_retryable (self , exc : Exception ) -> bool :
44+ if isinstance (exc , ClientError ):
45+ code = exc .response .get ("Error" , {}).get ("Code" , "" )
46+ status = exc .response .get ("ResponseMetadata" , {}).get ("HTTPStatusCode" , 0 )
47+ return code in _RETRYABLE_BEDROCK_CODES or 500 <= status < 600
48+ # BotoCoreError covers connection/read timeouts and other transport-level issues
49+ return isinstance (exc , BotoCoreError )
50+
3051 def _embed_text (self , content : str , chunk_id = None ):
3152 self .logger .debug (
3253 f"Requesting Bedrock embedding for chunk_id={ chunk_id } , content_length={ len (content )} "
@@ -51,8 +72,8 @@ def _embed_text(self, content: str, chunk_id=None):
5172 f"Successfully received embedding for chunk_id={ chunk_id } , embedding_dim={ len (embedding ) if embedding else 0 } "
5273 )
5374 return embedding
54- except Exception as exc :
55- if attempt == self ._max_retries - 1 :
75+ except ( ClientError , BotoCoreError ) as exc :
76+ if attempt == self ._max_retries - 1 or not self . _is_retryable ( exc ) :
5677 raise
5778 wait = self ._retry_backoff * (attempt + 1 )
5879 self .logger .warning (
0 commit comments