payi 0.1.0a107__py3-none-any.whl → 0.1.0a137__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- payi/__init__.py +3 -1
- payi/_base_client.py +12 -12
- payi/_client.py +8 -8
- payi/_compat.py +48 -48
- payi/_models.py +87 -59
- payi/_qs.py +7 -7
- payi/_streaming.py +4 -6
- payi/_types.py +53 -12
- payi/_utils/__init__.py +9 -2
- payi/_utils/_compat.py +45 -0
- payi/_utils/_datetime_parse.py +136 -0
- payi/_utils/_sync.py +3 -31
- payi/_utils/_transform.py +13 -3
- payi/_utils/_typing.py +6 -1
- payi/_utils/_utils.py +5 -6
- payi/_version.py +1 -1
- payi/lib/AnthropicInstrumentor.py +83 -57
- payi/lib/BedrockInstrumentor.py +292 -57
- payi/lib/GoogleGenAiInstrumentor.py +18 -31
- payi/lib/OpenAIInstrumentor.py +56 -72
- payi/lib/ProviderRequest.py +216 -0
- payi/lib/StreamWrappers.py +379 -0
- payi/lib/VertexInstrumentor.py +18 -37
- payi/lib/VertexRequest.py +16 -2
- payi/lib/data/cohere_embed_english_v3.json +30706 -0
- payi/lib/helpers.py +62 -5
- payi/lib/instrument.py +433 -659
- payi/resources/categories/__init__.py +0 -14
- payi/resources/categories/categories.py +25 -53
- payi/resources/categories/resources.py +27 -23
- payi/resources/ingest.py +126 -132
- payi/resources/limits/__init__.py +14 -14
- payi/resources/limits/limits.py +58 -58
- payi/resources/limits/properties.py +171 -0
- payi/resources/requests/request_id/properties.py +8 -8
- payi/resources/requests/request_id/result.py +3 -3
- payi/resources/requests/response_id/properties.py +8 -8
- payi/resources/requests/response_id/result.py +3 -3
- payi/resources/use_cases/definitions/definitions.py +27 -27
- payi/resources/use_cases/definitions/kpis.py +23 -23
- payi/resources/use_cases/definitions/limit_config.py +14 -14
- payi/resources/use_cases/definitions/version.py +3 -3
- payi/resources/use_cases/kpis.py +15 -15
- payi/resources/use_cases/properties.py +6 -6
- payi/resources/use_cases/use_cases.py +7 -7
- payi/types/__init__.py +2 -0
- payi/types/bulk_ingest_response.py +3 -20
- payi/types/categories/__init__.py +0 -1
- payi/types/categories/resource_list_params.py +5 -1
- payi/types/category_list_resources_params.py +5 -1
- payi/types/category_resource_response.py +31 -1
- payi/types/ingest_event_param.py +7 -6
- payi/types/ingest_units_params.py +5 -4
- payi/types/limit_create_params.py +3 -3
- payi/types/limit_list_response.py +1 -3
- payi/types/limit_response.py +1 -3
- payi/types/limits/__init__.py +2 -9
- payi/types/limits/{tag_remove_params.py → property_update_params.py} +4 -5
- payi/types/limits/{tag_delete_response.py → property_update_response.py} +3 -3
- payi/types/requests/request_id/property_update_params.py +2 -2
- payi/types/requests/response_id/property_update_params.py +2 -2
- payi/types/shared/__init__.py +2 -0
- payi/types/shared/api_error.py +18 -0
- payi/types/shared/pay_i_common_models_budget_management_create_limit_base.py +3 -3
- payi/types/shared/properties_request.py +11 -0
- payi/types/shared/xproxy_result.py +2 -0
- payi/types/shared_params/pay_i_common_models_budget_management_create_limit_base.py +3 -3
- payi/types/use_cases/definitions/limit_config_create_params.py +3 -3
- payi/types/use_cases/property_update_params.py +2 -2
- {payi-0.1.0a107.dist-info → payi-0.1.0a137.dist-info}/METADATA +6 -6
- {payi-0.1.0a107.dist-info → payi-0.1.0a137.dist-info}/RECORD +73 -75
- payi/resources/categories/fixed_cost_resources.py +0 -196
- payi/resources/limits/tags.py +0 -507
- payi/types/categories/fixed_cost_resource_create_params.py +0 -21
- payi/types/limits/limit_tags.py +0 -16
- payi/types/limits/tag_create_params.py +0 -13
- payi/types/limits/tag_create_response.py +0 -10
- payi/types/limits/tag_list_response.py +0 -10
- payi/types/limits/tag_remove_response.py +0 -10
- payi/types/limits/tag_update_params.py +0 -13
- payi/types/limits/tag_update_response.py +0 -10
- {payi-0.1.0a107.dist-info → payi-0.1.0a137.dist-info}/WHEEL +0 -0
- {payi-0.1.0a107.dist-info → payi-0.1.0a137.dist-info}/licenses/LICENSE +0 -0
payi/lib/BedrockInstrumentor.py
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
import json
|
|
3
|
-
from typing import Any, Optional, Sequence
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence
|
|
4
6
|
from functools import wraps
|
|
5
7
|
from typing_extensions import override
|
|
6
8
|
|
|
7
9
|
from wrapt import ObjectProxy, wrap_function_wrapper # type: ignore
|
|
8
10
|
|
|
9
|
-
from payi.lib.helpers import PayiCategories, PayiHeaderNames, payi_aws_bedrock_url
|
|
11
|
+
from payi.lib.helpers import PayiCategories, PayiHeaderNames, PayiPropertyNames, payi_aws_bedrock_url
|
|
10
12
|
from payi.types.ingest_units_params import Units
|
|
11
|
-
from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
|
|
12
13
|
|
|
13
|
-
from .instrument import
|
|
14
|
+
from .instrument import (
|
|
15
|
+
PayiInstrumentAwsBedrockConfig,
|
|
16
|
+
_Context,
|
|
17
|
+
_IsStreaming,
|
|
18
|
+
_PayiInstrumentor,
|
|
19
|
+
)
|
|
14
20
|
from .version_helper import get_version_helper
|
|
21
|
+
from .ProviderRequest import _ChunkResult, _StreamingType, _ProviderRequest
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from tokenizers import Tokenizer # type: ignore
|
|
25
|
+
else:
|
|
26
|
+
Tokenizer = None
|
|
15
27
|
|
|
28
|
+
GUARDRAIL_SEMANTIC_FAILURE_DESCRIPTION = "Bedrock Guardrails intervened"
|
|
16
29
|
|
|
17
30
|
class BedrockInstrumentor:
|
|
18
31
|
_module_name: str = "boto3"
|
|
@@ -20,6 +33,37 @@ class BedrockInstrumentor:
|
|
|
20
33
|
|
|
21
34
|
_instrumentor: _PayiInstrumentor
|
|
22
35
|
|
|
36
|
+
_guardrail_trace: bool = True
|
|
37
|
+
|
|
38
|
+
_model_mapping: Dict[str, _Context] = {}
|
|
39
|
+
|
|
40
|
+
_add_streaming_xproxy_result: bool = False
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def get_mapping(model_id: Optional[str]) -> _Context:
|
|
44
|
+
if not model_id:
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
return BedrockInstrumentor._model_mapping.get(model_id, {})
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def configure(aws_config: Optional[PayiInstrumentAwsBedrockConfig]) -> None:
|
|
51
|
+
if not aws_config:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
trace = aws_config.get("guardrail_trace", True)
|
|
55
|
+
if trace is None:
|
|
56
|
+
trace = True
|
|
57
|
+
BedrockInstrumentor._guardrail_trace = trace
|
|
58
|
+
|
|
59
|
+
add_streaming_xproxy_result = aws_config.get("add_streaming_xproxy_result", False)
|
|
60
|
+
if add_streaming_xproxy_result:
|
|
61
|
+
BedrockInstrumentor._add_streaming_xproxy_result = add_streaming_xproxy_result
|
|
62
|
+
|
|
63
|
+
model_mappings = aws_config.get("model_mappings", [])
|
|
64
|
+
if model_mappings:
|
|
65
|
+
BedrockInstrumentor._model_mapping = _PayiInstrumentor._model_mapping_to_context_dict(model_mappings)
|
|
66
|
+
|
|
23
67
|
@staticmethod
|
|
24
68
|
def instrument(instrumentor: _PayiInstrumentor) -> None:
|
|
25
69
|
BedrockInstrumentor._instrumentor = instrumentor
|
|
@@ -51,10 +95,10 @@ def create_client_wrapper(instrumentor: _PayiInstrumentor, wrapped: Any, instanc
|
|
|
51
95
|
|
|
52
96
|
try:
|
|
53
97
|
client: Any = wrapped(*args, **kwargs)
|
|
54
|
-
client.invoke_model = wrap_invoke(instrumentor, client.invoke_model)
|
|
55
|
-
client.invoke_model_with_response_stream = wrap_invoke_stream(instrumentor, client.invoke_model_with_response_stream)
|
|
56
|
-
client.converse = wrap_converse(instrumentor, client.converse)
|
|
57
|
-
client.converse_stream = wrap_converse_stream(instrumentor, client.converse_stream)
|
|
98
|
+
client.invoke_model = wrap_invoke(instrumentor, client.invoke_model, client)
|
|
99
|
+
client.invoke_model_with_response_stream = wrap_invoke_stream(instrumentor, client.invoke_model_with_response_stream, client)
|
|
100
|
+
client.converse = wrap_converse(instrumentor, client.converse, client)
|
|
101
|
+
client.converse_stream = wrap_converse_stream(instrumentor, client.converse_stream, client)
|
|
58
102
|
|
|
59
103
|
instrumentor._logger.debug(f"Instrumented bedrock client")
|
|
60
104
|
|
|
@@ -100,17 +144,20 @@ def _redirect_to_payi(request: Any, event_name: str, **_: 'dict[str, Any]') -> N
|
|
|
100
144
|
for key, value in extra_headers.items():
|
|
101
145
|
request.headers[key] = value
|
|
102
146
|
|
|
103
|
-
|
|
104
147
|
class InvokeResponseWrapper(ObjectProxy): # type: ignore
|
|
148
|
+
_cohere_embed_english_v3_tokenizer: Optional['Tokenizer'] = None
|
|
149
|
+
|
|
105
150
|
def __init__(
|
|
106
151
|
self,
|
|
107
|
-
response: Any,
|
|
152
|
+
response: 'dict[str, Any]',
|
|
153
|
+
body: Any,
|
|
108
154
|
request: '_BedrockInvokeProviderRequest',
|
|
109
155
|
log_prompt_and_response: bool
|
|
110
156
|
) -> None:
|
|
111
157
|
|
|
112
|
-
super().__init__(
|
|
158
|
+
super().__init__(body) # type: ignore
|
|
113
159
|
self._response = response
|
|
160
|
+
self._body = body
|
|
114
161
|
self._request = request
|
|
115
162
|
self._log_prompt_and_response = log_prompt_and_response
|
|
116
163
|
|
|
@@ -160,14 +207,50 @@ class InvokeResponseWrapper(ObjectProxy): # type: ignore
|
|
|
160
207
|
|
|
161
208
|
bedrock_converse_process_synchronous_function_call(self._request, response)
|
|
162
209
|
|
|
210
|
+
elif self._request._is_amazon_titan_embed_text_v1:
|
|
211
|
+
input = response.get('inputTextTokenCount', 0)
|
|
212
|
+
units["text"] = Units(input=input, output=0)
|
|
213
|
+
|
|
214
|
+
elif self._request._is_cohere_embed_english_v3:
|
|
215
|
+
texts: list[str] = response.get("texts", [])
|
|
216
|
+
if texts and len(texts) > 0:
|
|
217
|
+
text = " ".join(texts)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
from tokenizers import Tokenizer # type: ignore
|
|
221
|
+
|
|
222
|
+
if self._cohere_embed_english_v3_tokenizer is None: # type: ignore
|
|
223
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
224
|
+
tokenizer_path = os.path.join(current_dir, "data", "cohere_embed_english_v3.json")
|
|
225
|
+
self._cohere_embed_english_v3_tokenizer = Tokenizer.from_file(tokenizer_path) # type: ignore
|
|
226
|
+
|
|
227
|
+
if self._cohere_embed_english_v3_tokenizer is not None and isinstance(self._cohere_embed_english_v3_tokenizer, Tokenizer): # type: ignore
|
|
228
|
+
tokens: list = self._cohere_embed_english_v3_tokenizer.encode(text, add_special_tokens=False).tokens # type: ignore
|
|
229
|
+
|
|
230
|
+
if tokens and isinstance(tokens, list):
|
|
231
|
+
units["text"] = Units(input=len(tokens), output=0) # type: ignore
|
|
232
|
+
|
|
233
|
+
except ImportError:
|
|
234
|
+
self._request._instrumentor._logger.warning("tokenizers module not found, caller must install the tokenizers module. Cannot record text tokens for Cohere embed english v3")
|
|
235
|
+
pass
|
|
236
|
+
except Exception as e:
|
|
237
|
+
self._request._instrumentor._logger.warning(f"Error processing Cohere embed english v3 response: {e}")
|
|
238
|
+
pass
|
|
239
|
+
|
|
163
240
|
if self._log_prompt_and_response:
|
|
164
241
|
ingest["provider_response_json"] = data.decode('utf-8') # type: ignore
|
|
165
|
-
|
|
166
|
-
|
|
242
|
+
|
|
243
|
+
guardrails = response.get("amazon-bedrock-trace", {}).get("guardrail", {}).get("input", {})
|
|
244
|
+
self._request.process_guardrails(guardrails)
|
|
245
|
+
|
|
246
|
+
self._request.process_stop_action(response.get("amazon-bedrock-guardrailAction", ""))
|
|
247
|
+
|
|
248
|
+
xproxy_result = self._request._instrumentor._ingest_units(self._request)
|
|
249
|
+
self._request.assign_xproxy_result(self._response, xproxy_result)
|
|
167
250
|
|
|
168
251
|
return data # type: ignore
|
|
169
252
|
|
|
170
|
-
def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
253
|
+
def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
171
254
|
@wraps(wrapped)
|
|
172
255
|
def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
|
|
173
256
|
modelId:str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -176,14 +259,14 @@ def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
176
259
|
_BedrockInvokeProviderRequest(instrumentor=instrumentor, model_id=modelId),
|
|
177
260
|
_IsStreaming.false,
|
|
178
261
|
wrapped,
|
|
179
|
-
|
|
262
|
+
instance,
|
|
180
263
|
args,
|
|
181
264
|
kwargs,
|
|
182
265
|
)
|
|
183
266
|
|
|
184
267
|
return invoke_wrapper
|
|
185
268
|
|
|
186
|
-
def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
269
|
+
def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
187
270
|
@wraps(wrapped)
|
|
188
271
|
def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
189
272
|
modelId: str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -193,14 +276,14 @@ def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
193
276
|
_BedrockInvokeProviderRequest(instrumentor=instrumentor, model_id=modelId),
|
|
194
277
|
_IsStreaming.true,
|
|
195
278
|
wrapped,
|
|
196
|
-
|
|
279
|
+
instance,
|
|
197
280
|
args,
|
|
198
281
|
kwargs,
|
|
199
282
|
)
|
|
200
283
|
|
|
201
284
|
return invoke_wrapper
|
|
202
285
|
|
|
203
|
-
def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
286
|
+
def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
204
287
|
@wraps(wrapped)
|
|
205
288
|
def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
|
|
206
289
|
modelId:str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -210,14 +293,14 @@ def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
210
293
|
_BedrockConverseProviderRequest(instrumentor=instrumentor),
|
|
211
294
|
_IsStreaming.false,
|
|
212
295
|
wrapped,
|
|
213
|
-
|
|
296
|
+
instance,
|
|
214
297
|
args,
|
|
215
298
|
kwargs,
|
|
216
299
|
)
|
|
217
300
|
|
|
218
301
|
return invoke_wrapper
|
|
219
302
|
|
|
220
|
-
def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
303
|
+
def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
221
304
|
@wraps(wrapped)
|
|
222
305
|
def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
223
306
|
modelId: str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -227,7 +310,7 @@ def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
227
310
|
_BedrockConverseProviderRequest(instrumentor=instrumentor),
|
|
228
311
|
_IsStreaming.true,
|
|
229
312
|
wrapped,
|
|
230
|
-
|
|
313
|
+
instance,
|
|
231
314
|
args,
|
|
232
315
|
kwargs,
|
|
233
316
|
)
|
|
@@ -235,6 +318,7 @@ def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
235
318
|
return invoke_wrapper
|
|
236
319
|
|
|
237
320
|
class _BedrockProviderRequest(_ProviderRequest):
|
|
321
|
+
|
|
238
322
|
def __init__(self, instrumentor: _PayiInstrumentor):
|
|
239
323
|
super().__init__(
|
|
240
324
|
instrumentor=instrumentor,
|
|
@@ -246,15 +330,40 @@ class _BedrockProviderRequest(_ProviderRequest):
|
|
|
246
330
|
)
|
|
247
331
|
|
|
248
332
|
@override
|
|
249
|
-
def process_request(self, instance: Any, extra_headers: 'dict[str, str]',
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
333
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
334
|
+
modelId = kwargs.get("modelId", "")
|
|
335
|
+
self._ingest["resource"] = modelId
|
|
336
|
+
|
|
337
|
+
if not self._price_as.resource and not self._price_as.category and BedrockInstrumentor._model_mapping:
|
|
338
|
+
deployment = BedrockInstrumentor._model_mapping.get(modelId, {})
|
|
339
|
+
self._price_as.category = deployment.get("price_as_category", "")
|
|
340
|
+
self._price_as.resource = deployment.get("price_as_resource", "")
|
|
341
|
+
self._price_as.resource_scope = deployment.get("resource_scope", None)
|
|
342
|
+
|
|
343
|
+
if self._price_as.resource_scope:
|
|
344
|
+
self._ingest["resource_scope"] = self._price_as.resource_scope
|
|
345
|
+
|
|
346
|
+
# override defaults
|
|
347
|
+
if self._price_as.category:
|
|
348
|
+
self._ingest["category"] = self._price_as.category
|
|
349
|
+
if self._price_as.resource:
|
|
350
|
+
self._ingest["resource"] = self._price_as.resource
|
|
351
|
+
|
|
253
352
|
return True
|
|
254
353
|
|
|
354
|
+
def process_response_metadata(self, metadata: 'dict[str, Any]') -> None:
|
|
355
|
+
request_id = metadata.get("RequestId", "")
|
|
356
|
+
if request_id:
|
|
357
|
+
self._ingest["provider_response_id"] = request_id
|
|
358
|
+
|
|
359
|
+
response_headers = metadata.get("HTTPHeaders", {})
|
|
360
|
+
if response_headers:
|
|
361
|
+
self.add_response_headers(response_headers)
|
|
362
|
+
|
|
255
363
|
@override
|
|
256
364
|
def process_initial_stream_response(self, response: Any) -> None:
|
|
257
|
-
|
|
365
|
+
super().process_initial_stream_response(response)
|
|
366
|
+
self.process_response_metadata(response.get("ResponseMetadata", {}))
|
|
258
367
|
|
|
259
368
|
@override
|
|
260
369
|
def process_exception(self, exception: Exception, kwargs: Any, ) -> bool:
|
|
@@ -281,12 +390,73 @@ class _BedrockProviderRequest(_ProviderRequest):
|
|
|
281
390
|
self._instrumentor._logger.debug(f"Error processing exception: {e}")
|
|
282
391
|
return False
|
|
283
392
|
|
|
393
|
+
def process_guardrails(self, guardrails: 'dict[str, Any]') -> None:
|
|
394
|
+
units = self._ingest["units"]
|
|
395
|
+
|
|
396
|
+
# while we iterate over the entire dict, only one guardrail is expected and supported
|
|
397
|
+
for _, value in guardrails.items():
|
|
398
|
+
# _ (key) is the guardrail id
|
|
399
|
+
if not isinstance(value, dict):
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
usage: dict[str, int] = value.get("invocationMetrics", {}).get("usage", {}) # type: ignore
|
|
403
|
+
if not usage:
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
topicPolicyUnits: int = usage.get("topicPolicyUnits", 0) # type: ignore
|
|
407
|
+
if topicPolicyUnits > 0:
|
|
408
|
+
units["guardrail_topic"] = Units(input=topicPolicyUnits, output=0) # type: ignore
|
|
409
|
+
|
|
410
|
+
contentPolicyUnits = usage.get("contentPolicyUnits", 0) # type: ignore
|
|
411
|
+
if contentPolicyUnits > 0:
|
|
412
|
+
units["guardrail_content"] = Units(input=contentPolicyUnits, output=0) # type: ignore
|
|
413
|
+
|
|
414
|
+
wordPolicyUnits = usage.get("wordPolicyUnits", 0) # type: ignore
|
|
415
|
+
if wordPolicyUnits > 0:
|
|
416
|
+
units["guardrail_word_free"] = Units(input=wordPolicyUnits, output=0) # type: ignore
|
|
417
|
+
|
|
418
|
+
automatedReasoningPolicyUnits = usage.get("automatedReasoningPolicyUnits", 0) # type: ignore
|
|
419
|
+
if automatedReasoningPolicyUnits > 0:
|
|
420
|
+
units["guardrail_automated_reasoning"] = Units(input=automatedReasoningPolicyUnits, output=0) # type: ignore
|
|
421
|
+
|
|
422
|
+
sensitiveInformationPolicyUnits = usage.get("sensitiveInformationPolicyUnits", 0) # type: ignore
|
|
423
|
+
if sensitiveInformationPolicyUnits > 0:
|
|
424
|
+
units["guardrail_sensitive_information"] = Units(input=sensitiveInformationPolicyUnits, output=0) # type: ignore
|
|
425
|
+
|
|
426
|
+
sensitiveInformationPolicyFreeUnits = usage.get("sensitiveInformationPolicyFreeUnits", 0) # type: ignore
|
|
427
|
+
if sensitiveInformationPolicyFreeUnits > 0:
|
|
428
|
+
units["guardrail_sensitive_information_free"] = Units(input=sensitiveInformationPolicyFreeUnits, output=0) # type: ignore
|
|
429
|
+
|
|
430
|
+
contextualGroundingPolicyUnits = usage.get("contextualGroundingPolicyUnits", 0) # type: ignore
|
|
431
|
+
if contextualGroundingPolicyUnits > 0:
|
|
432
|
+
units["guardrail_contextual_grounding"] = Units(input=contextualGroundingPolicyUnits, output=0) # type: ignore
|
|
433
|
+
|
|
434
|
+
contentPolicyImageUnits = usage.get("contentPolicyImageUnits", 0) # type: ignore
|
|
435
|
+
if contentPolicyImageUnits > 0:
|
|
436
|
+
units["guardrail_content_image"] = Units(input=contentPolicyImageUnits, output=0) # type: ignore
|
|
437
|
+
|
|
284
438
|
class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
|
|
285
439
|
def __init__(self, instrumentor: _PayiInstrumentor, model_id: str):
|
|
286
440
|
super().__init__(instrumentor=instrumentor)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
441
|
+
|
|
442
|
+
price_as_resource = BedrockInstrumentor._model_mapping.get(model_id, {}).get("price_as_resource", None)
|
|
443
|
+
if price_as_resource:
|
|
444
|
+
model_id = price_as_resource
|
|
445
|
+
|
|
446
|
+
self._is_anthropic: bool = False
|
|
447
|
+
self._is_nova: bool = False
|
|
448
|
+
self._is_meta: bool = False
|
|
449
|
+
self._is_amazon_titan_embed_text_v1: bool = False
|
|
450
|
+
self._is_cohere_embed_english_v3: bool = False
|
|
451
|
+
|
|
452
|
+
self._assign_model_state(model_id=model_id)
|
|
453
|
+
|
|
454
|
+
def _assign_model_state(self, model_id: str) -> None:
|
|
455
|
+
self._is_anthropic = 'anthropic' in model_id
|
|
456
|
+
self._is_nova = 'nova' in model_id
|
|
457
|
+
self._is_meta = 'meta' in model_id
|
|
458
|
+
self._is_amazon_titan_embed_text_v1 = 'amazon.titan-embed-text-v1' == model_id
|
|
459
|
+
self._is_cohere_embed_english_v3 = 'cohere.embed-english-v3' == model_id
|
|
290
460
|
|
|
291
461
|
@override
|
|
292
462
|
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
@@ -294,21 +464,54 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
|
|
|
294
464
|
|
|
295
465
|
super().process_request(instance, extra_headers, args, kwargs)
|
|
296
466
|
|
|
467
|
+
# super().process_request will assign price_as mapping from global state, so evaluate afterwards
|
|
468
|
+
if self._price_as.resource:
|
|
469
|
+
self._assign_model_state(model_id=self._price_as.resource)
|
|
470
|
+
|
|
471
|
+
guardrail_id = kwargs.get("guardrailIdentifier", "")
|
|
472
|
+
if guardrail_id:
|
|
473
|
+
self.add_internal_request_property(PayiPropertyNames.aws_bedrock_guardrail_id, guardrail_id)
|
|
474
|
+
|
|
475
|
+
guardrail_version = kwargs.get("guardrailVersion", "")
|
|
476
|
+
if guardrail_version:
|
|
477
|
+
self.add_internal_request_property(PayiPropertyNames.aws_bedrock_guardrail_version, guardrail_version)
|
|
478
|
+
|
|
479
|
+
if guardrail_id and guardrail_version and BedrockInstrumentor._guardrail_trace:
|
|
480
|
+
trace = kwargs.get("trace", None)
|
|
481
|
+
if not trace:
|
|
482
|
+
kwargs["trace"] = "ENABLED"
|
|
483
|
+
|
|
297
484
|
if self._is_anthropic:
|
|
298
485
|
try:
|
|
299
|
-
body = json.loads(
|
|
486
|
+
body = json.loads(kwargs.get("body", ""))
|
|
300
487
|
messages = body.get("messages", {})
|
|
301
488
|
if messages:
|
|
302
489
|
anthropic_has_image_and_get_texts(self, messages)
|
|
303
490
|
except Exception as e:
|
|
304
491
|
self._instrumentor._logger.debug(f"Bedrock invoke error processing request body: {e}")
|
|
305
|
-
|
|
492
|
+
elif self._is_cohere_embed_english_v3:
|
|
493
|
+
try:
|
|
494
|
+
body = json.loads(kwargs.get("body", ""))
|
|
495
|
+
input_type = body.get("input_type", "")
|
|
496
|
+
if input_type == 'image':
|
|
497
|
+
images = body.get("images", [])
|
|
498
|
+
if (len(images) > 0):
|
|
499
|
+
# only supports one image according to docs
|
|
500
|
+
self._ingest["units"]["vision"] = Units(input=1, output=0)
|
|
501
|
+
except Exception as e:
|
|
502
|
+
self._instrumentor._logger.debug(f"Bedrock invoke error processing request body: {e}")
|
|
306
503
|
return True
|
|
307
504
|
|
|
308
505
|
@override
|
|
309
506
|
def process_chunk(self, chunk: Any) -> _ChunkResult:
|
|
310
507
|
chunk_dict = json.loads(chunk)
|
|
311
508
|
|
|
509
|
+
guardrails = chunk_dict.get("amazon-bedrock-trace", {}).get("guardrail", {}).get("input", {})
|
|
510
|
+
if guardrails:
|
|
511
|
+
self.process_guardrails(guardrails)
|
|
512
|
+
|
|
513
|
+
self.process_stop_action(chunk_dict.get("amazon-bedrock-guardrailAction", ""))
|
|
514
|
+
|
|
312
515
|
if self._is_anthropic:
|
|
313
516
|
from .AnthropicInstrumentor import anthropic_process_chunk
|
|
314
517
|
return anthropic_process_chunk(self, chunk_dict, assign_id=False)
|
|
@@ -347,23 +550,23 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
|
|
|
347
550
|
log_prompt_and_response: bool,
|
|
348
551
|
kwargs: Any) -> Any:
|
|
349
552
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
request_id = metadata.get("RequestId", "")
|
|
353
|
-
if request_id:
|
|
354
|
-
self._ingest["provider_response_id"] = request_id
|
|
355
|
-
|
|
356
|
-
response_headers = metadata.get("HTTPHeaders", {}).copy()
|
|
357
|
-
if response_headers:
|
|
358
|
-
self._ingest["provider_response_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in response_headers.items()]
|
|
553
|
+
self.process_response_metadata(response.get("ResponseMetadata", {}))
|
|
359
554
|
|
|
360
555
|
response["body"] = InvokeResponseWrapper(
|
|
361
|
-
response=response
|
|
556
|
+
response=response,
|
|
557
|
+
body=response["body"],
|
|
362
558
|
request=self,
|
|
363
559
|
log_prompt_and_response=log_prompt_and_response)
|
|
364
560
|
|
|
365
561
|
return response
|
|
366
562
|
|
|
563
|
+
def process_stop_action(self, action: str) -> None:
|
|
564
|
+
# record both as a semantic failure and guardrail action so it is discoverable through both properties
|
|
565
|
+
if action == "INTERVENED":
|
|
566
|
+
self.add_internal_request_property(PayiPropertyNames.failure, action)
|
|
567
|
+
self.add_internal_request_property(PayiPropertyNames.failure_description, GUARDRAIL_SEMANTIC_FAILURE_DESCRIPTION)
|
|
568
|
+
self.add_internal_request_property(PayiPropertyNames.aws_bedrock_guardrail_action, action)
|
|
569
|
+
|
|
367
570
|
@override
|
|
368
571
|
def remove_inline_data(self, prompt: 'dict[str, Any]') -> bool:# noqa: ARG002
|
|
369
572
|
if not self._is_anthropic:
|
|
@@ -383,6 +586,27 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
|
|
|
383
586
|
return False
|
|
384
587
|
|
|
385
588
|
class _BedrockConverseProviderRequest(_BedrockProviderRequest):
|
|
589
|
+
@override
|
|
590
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
591
|
+
super().process_request(instance, extra_headers, args, kwargs)
|
|
592
|
+
|
|
593
|
+
guardrail_config = kwargs.get("guardrailConfig", {})
|
|
594
|
+
if guardrail_config:
|
|
595
|
+
guardrailIdentifier = guardrail_config.get("guardrailIdentifier", "")
|
|
596
|
+
if guardrailIdentifier:
|
|
597
|
+
self.add_internal_request_property(PayiPropertyNames.aws_bedrock_guardrail_id, guardrailIdentifier)
|
|
598
|
+
|
|
599
|
+
guardrailVersion = guardrail_config.get("guardrailVersion", "")
|
|
600
|
+
if guardrailVersion:
|
|
601
|
+
self.add_internal_request_property(PayiPropertyNames.aws_bedrock_guardrail_version, guardrailVersion)
|
|
602
|
+
|
|
603
|
+
if guardrailIdentifier and guardrailVersion and BedrockInstrumentor._guardrail_trace:
|
|
604
|
+
trace = guardrail_config.get("trace", None)
|
|
605
|
+
if not trace:
|
|
606
|
+
guardrail_config["trace"] = "enabled"
|
|
607
|
+
|
|
608
|
+
return True
|
|
609
|
+
|
|
386
610
|
@override
|
|
387
611
|
def process_synchronous_response(
|
|
388
612
|
self,
|
|
@@ -390,22 +614,14 @@ class _BedrockConverseProviderRequest(_BedrockProviderRequest):
|
|
|
390
614
|
log_prompt_and_response: bool,
|
|
391
615
|
kwargs: Any) -> Any:
|
|
392
616
|
|
|
393
|
-
usage = response
|
|
394
|
-
input = usage
|
|
395
|
-
output = usage
|
|
396
|
-
|
|
617
|
+
usage = response.get("usage", {})
|
|
618
|
+
input = usage.get("inputTokens", 0)
|
|
619
|
+
output = usage.get("outputTokens", 0)
|
|
620
|
+
|
|
397
621
|
units: dict[str, Units] = self._ingest["units"]
|
|
398
622
|
units["text"] = Units(input=input, output=output)
|
|
399
623
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
request_id = metadata.get("RequestId", "")
|
|
403
|
-
if request_id:
|
|
404
|
-
self._ingest["provider_response_id"] = request_id
|
|
405
|
-
|
|
406
|
-
response_headers = metadata.get("HTTPHeaders", {})
|
|
407
|
-
if response_headers:
|
|
408
|
-
self._ingest["provider_response_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in response_headers.items()]
|
|
624
|
+
self.process_response_metadata(response.get("ResponseMetadata", {}))
|
|
409
625
|
|
|
410
626
|
if log_prompt_and_response:
|
|
411
627
|
response_without_metadata = response.copy()
|
|
@@ -414,6 +630,12 @@ class _BedrockConverseProviderRequest(_BedrockProviderRequest):
|
|
|
414
630
|
|
|
415
631
|
bedrock_converse_process_synchronous_function_call(self, response)
|
|
416
632
|
|
|
633
|
+
guardrails = response.get("trace", {}).get("guardrail", {}).get("inputAssessment", {})
|
|
634
|
+
if guardrails:
|
|
635
|
+
self.process_guardrails(guardrails)
|
|
636
|
+
|
|
637
|
+
self.process_stop_reason(response.get("stopReason", ""))
|
|
638
|
+
|
|
417
639
|
return None
|
|
418
640
|
|
|
419
641
|
@override
|
|
@@ -422,17 +644,30 @@ class _BedrockConverseProviderRequest(_BedrockProviderRequest):
|
|
|
422
644
|
metadata = chunk.get("metadata", {})
|
|
423
645
|
|
|
424
646
|
if metadata:
|
|
425
|
-
usage = metadata
|
|
426
|
-
input = usage
|
|
427
|
-
output = usage
|
|
647
|
+
usage = metadata.get('usage', {})
|
|
648
|
+
input = usage.get("inputTokens", 0)
|
|
649
|
+
output = usage.get("outputTokens", 0)
|
|
428
650
|
self._ingest["units"]["text"] = Units(input=input, output=output)
|
|
429
651
|
|
|
652
|
+
guardrail = metadata.get("trace", {}).get("guardrail", {}).get("inputAssessment", {})
|
|
653
|
+
if guardrail:
|
|
654
|
+
self.process_guardrails(guardrail)
|
|
655
|
+
|
|
430
656
|
ingest = True
|
|
431
657
|
|
|
658
|
+
self.process_stop_reason(chunk.get("messageStop", {}).get("stopReason", ""))
|
|
659
|
+
|
|
432
660
|
bedrock_converse_process_streaming_for_function_call(self, chunk)
|
|
433
661
|
|
|
434
662
|
return _ChunkResult(send_chunk_to_caller=True, ingest=ingest)
|
|
435
663
|
|
|
664
|
+
def process_stop_reason(self, reason: str) -> None:
|
|
665
|
+
if reason == "guardrail_intervened":
|
|
666
|
+
# record both as a semantic failure and guardrail action so it is discoverable through both properties
|
|
667
|
+
self.add_internal_request_property(PayiPropertyNames.failure, reason)
|
|
668
|
+
self.add_internal_request_property(PayiPropertyNames.failure_description, GUARDRAIL_SEMANTIC_FAILURE_DESCRIPTION)
|
|
669
|
+
self.add_internal_request_property(PayiPropertyNames.aws_bedrock_guardrail_action, reason)
|
|
670
|
+
|
|
436
671
|
def bedrock_converse_process_streaming_for_function_call(request: _ProviderRequest, chunk: 'dict[str, Any]') -> None:
|
|
437
672
|
contentBlockStart = chunk.get("contentBlockStart", {})
|
|
438
673
|
tool_use = contentBlockStart.get("start", {}).get("toolUse", {})
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from typing import Any, List, Union, Sequence
|
|
2
4
|
from typing_extensions import override
|
|
3
5
|
|
|
4
6
|
from wrapt import wrap_function_wrapper # type: ignore
|
|
5
7
|
|
|
6
|
-
from .instrument import
|
|
8
|
+
from .instrument import _IsStreaming, _PayiInstrumentor
|
|
7
9
|
from .VertexRequest import _VertexRequest
|
|
8
10
|
from .version_helper import get_version_helper
|
|
11
|
+
from .ProviderRequest import _ChunkResult
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class GoogleGenAiInstrumentor:
|
|
@@ -14,36 +17,20 @@ class GoogleGenAiInstrumentor:
|
|
|
14
17
|
|
|
15
18
|
@staticmethod
|
|
16
19
|
def instrument(instrumentor: _PayiInstrumentor) -> None:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
wrap_function_wrapper(
|
|
33
|
-
"google.genai.models",
|
|
34
|
-
"AsyncModels.generate_content",
|
|
35
|
-
agenerate_wrapper(instrumentor),
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
wrap_function_wrapper(
|
|
39
|
-
"google.genai.models",
|
|
40
|
-
"AsyncModels.generate_content_stream",
|
|
41
|
-
agenerate_stream_wrapper(instrumentor),
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
except Exception as e:
|
|
45
|
-
instrumentor._logger.debug(f"Error instrumenting vertex: {e}")
|
|
46
|
-
return
|
|
20
|
+
GoogleGenAiInstrumentor._module_version = get_version_helper(GoogleGenAiInstrumentor._module_name)
|
|
21
|
+
|
|
22
|
+
wrappers = [
|
|
23
|
+
("google.genai.models", "Models.generate_content", generate_wrapper(instrumentor)),
|
|
24
|
+
("google.genai.models", "Models.generate_content_stream", generate_stream_wrapper(instrumentor)),
|
|
25
|
+
("google.genai.models", "AsyncModels.generate_content", agenerate_wrapper(instrumentor)),
|
|
26
|
+
("google.genai.models", "AsyncModels.generate_content_stream", agenerate_stream_wrapper(instrumentor)),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
for module, method, wrapper in wrappers:
|
|
30
|
+
try:
|
|
31
|
+
wrap_function_wrapper(module, method, wrapper)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
instrumentor._logger.debug(f"Error wrapping {module}.{method}: {e}")
|
|
47
34
|
|
|
48
35
|
@_PayiInstrumentor.payi_wrapper
|
|
49
36
|
def generate_wrapper(
|