Coverage for src / qdrant_loader_core / llm / providers / bedrock_utils.py: 59%
91 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-11 09:34 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-11 09:34 +0000
1from __future__ import annotations
3import logging
4import math
5from typing import Any
7from ..errors import (
8 AuthError,
9 InvalidRequestError,
10 LLMError,
11 RateLimitedError,
12 ServerError,
13)
14from ..types import TokenCounter
16logger = logging.getLogger(__name__)
18try:
19 from botocore.exceptions import (
20 BotoCoreError,
21 ClientError,
22 EndpointConnectionError,
23 NoCredentialsError,
24 )
25except ImportError:
27 class _BedrockBaseError(Exception):
28 pass
30 class _BedrockClientError(_BedrockBaseError):
31 pass
33 class _BedrockNoCredentialsError(_BedrockBaseError):
34 pass
36 class _BedrockEndpointConnectionError(_BedrockBaseError):
37 pass
39 class _BedrockBotoCoreError(_BedrockBaseError):
40 pass
42 BotoCoreError = _BedrockBotoCoreError
43 ClientError = _BedrockClientError
44 EndpointConnectionError = _BedrockEndpointConnectionError
45 NoCredentialsError = _BedrockNoCredentialsError
48def _map_bedrock_exception(exc: Exception) -> LLMError:
49 """Map a botocore/boto3 exception into a qdrant_loader_core LLMError."""
50 if isinstance(exc, NoCredentialsError):
51 return AuthError(str(exc))
53 error_code = ""
54 status_code = None
55 if hasattr(exc, "response"):
56 try:
57 error_code = exc.response.get("Error", {}).get("Code", "")
58 status_code = exc.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
59 except Exception:
60 pass
62 if error_code in ("ThrottlingException", "TooManyRequestsException", "Throttling"):
63 return RateLimitedError(str(exc))
64 if error_code in (
65 "AccessDeniedException",
66 "UnrecognizedClientException",
67 "InvalidSignatureException",
68 ):
69 return AuthError(str(exc))
70 if error_code in (
71 "ValidationException",
72 "InvalidParameterException",
73 "ResourceNotFoundException",
74 "UnsupportedOperationException",
75 ):
76 return InvalidRequestError(str(exc))
77 if error_code in ("ModelErrorException", "ModelTimeoutException"):
78 return ServerError(str(exc))
79 if isinstance(exc, ClientError):
80 if status_code in (408, 424):
81 return ServerError(str(exc))
82 if status_code == 429:
83 return RateLimitedError(str(exc))
84 if status_code in (401, 403):
85 return AuthError(str(exc))
86 if isinstance(status_code, int) and status_code >= 500:
87 return ServerError(str(exc))
88 if isinstance(status_code, int) and status_code >= 400:
89 return InvalidRequestError(str(exc))
90 return ServerError(str(exc))
92 if isinstance(exc, EndpointConnectionError):
93 return ServerError(str(exc))
95 if isinstance(exc, BotoCoreError):
96 return ServerError(str(exc))
98 return ServerError(str(exc))
101def _extract_embeddings(response_payload: Any) -> list[list[float]]:
102 """Normalize Bedrock embedding response payloads into a list of float vectors."""
103 if not isinstance(response_payload, (dict, list)):
104 raise InvalidRequestError("Bedrock response has unexpected format")
106 raw_embeddings: list[Any]
108 if isinstance(response_payload, list):
109 raw_embeddings = response_payload
111 elif "embedding" in response_payload:
112 raw_embeddings = [response_payload["embedding"]]
114 elif "embeddings" in response_payload:
115 raw_embeddings = response_payload["embeddings"]
117 elif "data" in response_payload and isinstance(response_payload["data"], list):
118 raw_embeddings = [
119 item.get("embedding", item) if isinstance(item, dict) else item
120 for item in response_payload["data"]
121 ]
123 else:
124 raise InvalidRequestError("Bedrock response did not contain embeddings")
126 normalized: list[list[float]] = []
128 for vector in raw_embeddings:
129 if isinstance(vector, dict):
130 vector = vector.get("embedding", vector)
132 if not isinstance(vector, list):
133 raise InvalidRequestError(
134 "Bedrock embedding payload must be a list of floats"
135 )
137 try:
138 parsed_vector = [float(value) for value in vector]
139 except (TypeError, ValueError) as exc:
140 raise InvalidRequestError(
141 f"Invalid embedding element from Bedrock: {exc}"
142 ) from exc
143 if not all(math.isfinite(value) for value in parsed_vector):
144 raise InvalidRequestError(
145 "Invalid embedding element from Bedrock: non-finite value"
146 )
147 normalized.append(parsed_vector)
149 return normalized
152class BedrockTokenizer(TokenCounter):
153 """
154 Temporary fallback tokenizer for Bedrock providers.
156 This implementation approximates token counts using character length
157 and is NOT model-accurate. Counts may differ significantly from
158 actual Bedrock model token usage.
159 """
161 def __init__(self) -> None:
162 logger.warning(
163 "BedrockTokenizer is using a character-count fallback. "
164 "Token counts are approximate and may not match actual "
165 "Bedrock model tokenization."
166 )
168 def count(self, text: str) -> int:
169 return len(text)