payi 0.1.0a133__py3-none-any.whl → 0.1.0a134__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.
Potentially problematic release.
This version of payi might be problematic. Click here for more details.
- payi/_version.py +1 -1
- payi/lib/AnthropicInstrumentor.py +14 -3
- payi/lib/BedrockInstrumentor.py +81 -27
- payi/lib/OpenAIInstrumentor.py +38 -27
- payi/lib/helpers.py +18 -1
- payi/lib/instrument.py +233 -64
- {payi-0.1.0a133.dist-info → payi-0.1.0a134.dist-info}/METADATA +1 -1
- {payi-0.1.0a133.dist-info → payi-0.1.0a134.dist-info}/RECORD +10 -10
- {payi-0.1.0a133.dist-info → payi-0.1.0a134.dist-info}/WHEEL +0 -0
- {payi-0.1.0a133.dist-info → payi-0.1.0a134.dist-info}/licenses/LICENSE +0 -0
payi/_version.py
CHANGED
|
@@ -195,15 +195,26 @@ class _AnthropicProviderRequest(_ProviderRequest):
|
|
|
195
195
|
|
|
196
196
|
return None
|
|
197
197
|
|
|
198
|
+
def _update_resource_name(self, model: str) -> str:
|
|
199
|
+
return ("anthropic." if self._is_vertex else "") + model
|
|
200
|
+
|
|
198
201
|
@override
|
|
199
|
-
def process_request(self, instance: Any, extra_headers: 'dict[str, str]',
|
|
200
|
-
self._ingest["resource"] =
|
|
202
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
203
|
+
self._ingest["resource"] = self._update_resource_name(kwargs.get("model", ""))
|
|
204
|
+
|
|
205
|
+
if self._price_as.resource_scope:
|
|
206
|
+
self._ingest["resource_scope"] = self._price_as.resource_scope
|
|
207
|
+
|
|
208
|
+
# override defaults
|
|
209
|
+
if self._price_as.category:
|
|
210
|
+
self._ingest["category"] = self._price_as.category
|
|
211
|
+
if self._price_as.resource:
|
|
212
|
+
self._ingest["resource"] = self._update_resource_name(self._price_as.resource)
|
|
201
213
|
|
|
202
214
|
self._instrumentor._logger.debug(f"Processing anthropic request: model {self._ingest['resource']}, category {self._category}")
|
|
203
215
|
|
|
204
216
|
messages = kwargs.get("messages")
|
|
205
217
|
if messages:
|
|
206
|
-
|
|
207
218
|
anthropic_has_image_and_get_texts(self, messages)
|
|
208
219
|
|
|
209
220
|
return True
|
payi/lib/BedrockInstrumentor.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Optional, Sequence
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from typing_extensions import override
|
|
6
6
|
|
|
@@ -12,6 +12,7 @@ from payi.types.pay_i_common_models_api_router_header_info_param import PayIComm
|
|
|
12
12
|
|
|
13
13
|
from .instrument import (
|
|
14
14
|
PayiInstrumentAwsBedrockConfig,
|
|
15
|
+
_Context,
|
|
15
16
|
_ChunkResult,
|
|
16
17
|
_IsStreaming,
|
|
17
18
|
_StreamingType,
|
|
@@ -35,8 +36,31 @@ class BedrockInstrumentor:
|
|
|
35
36
|
|
|
36
37
|
_guardrail_trace: bool = True
|
|
37
38
|
|
|
39
|
+
_model_mapping: Dict[str, _Context] = {}
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def get_mapping(model_id: Optional[str]) -> _Context:
|
|
43
|
+
if not model_id:
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
return BedrockInstrumentor._model_mapping.get(model_id, {})
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def configure(aws_config: Optional[PayiInstrumentAwsBedrockConfig]) -> None:
|
|
50
|
+
if not aws_config:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
trace = aws_config.get("guardrail_trace", True)
|
|
54
|
+
if trace is None:
|
|
55
|
+
trace = True
|
|
56
|
+
BedrockInstrumentor._guardrail_trace = trace
|
|
57
|
+
|
|
58
|
+
model_mappings = aws_config.get("model_mappings", [])
|
|
59
|
+
if model_mappings:
|
|
60
|
+
BedrockInstrumentor._model_mapping = _PayiInstrumentor._model_mapping_to_context_dict(model_mappings)
|
|
61
|
+
|
|
38
62
|
@staticmethod
|
|
39
|
-
def instrument(instrumentor: _PayiInstrumentor
|
|
63
|
+
def instrument(instrumentor: _PayiInstrumentor) -> None:
|
|
40
64
|
BedrockInstrumentor._instrumentor = instrumentor
|
|
41
65
|
|
|
42
66
|
BedrockInstrumentor._module_version = get_version_helper(BedrockInstrumentor._module_name)
|
|
@@ -58,9 +82,6 @@ class BedrockInstrumentor:
|
|
|
58
82
|
instrumentor._logger.debug(f"Error instrumenting bedrock: {e}")
|
|
59
83
|
return
|
|
60
84
|
|
|
61
|
-
if aws_config:
|
|
62
|
-
BedrockInstrumentor._guardrail_trace = aws_config.get("guardrail_trace", True)
|
|
63
|
-
|
|
64
85
|
@_PayiInstrumentor.payi_wrapper
|
|
65
86
|
def create_client_wrapper(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any, *args: Any, **kwargs: Any) -> Any: # noqa: ARG001
|
|
66
87
|
if kwargs.get("service_name") != "bedrock-runtime":
|
|
@@ -69,10 +90,10 @@ def create_client_wrapper(instrumentor: _PayiInstrumentor, wrapped: Any, instanc
|
|
|
69
90
|
|
|
70
91
|
try:
|
|
71
92
|
client: Any = wrapped(*args, **kwargs)
|
|
72
|
-
client.invoke_model = wrap_invoke(instrumentor, client.invoke_model)
|
|
73
|
-
client.invoke_model_with_response_stream = wrap_invoke_stream(instrumentor, client.invoke_model_with_response_stream)
|
|
74
|
-
client.converse = wrap_converse(instrumentor, client.converse)
|
|
75
|
-
client.converse_stream = wrap_converse_stream(instrumentor, client.converse_stream)
|
|
93
|
+
client.invoke_model = wrap_invoke(instrumentor, client.invoke_model, client)
|
|
94
|
+
client.invoke_model_with_response_stream = wrap_invoke_stream(instrumentor, client.invoke_model_with_response_stream, client)
|
|
95
|
+
client.converse = wrap_converse(instrumentor, client.converse, client)
|
|
96
|
+
client.converse_stream = wrap_converse_stream(instrumentor, client.converse_stream, client)
|
|
76
97
|
|
|
77
98
|
instrumentor._logger.debug(f"Instrumented bedrock client")
|
|
78
99
|
|
|
@@ -221,7 +242,7 @@ class InvokeResponseWrapper(ObjectProxy): # type: ignore
|
|
|
221
242
|
|
|
222
243
|
return data # type: ignore
|
|
223
244
|
|
|
224
|
-
def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
245
|
+
def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
225
246
|
@wraps(wrapped)
|
|
226
247
|
def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
|
|
227
248
|
modelId:str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -230,14 +251,14 @@ def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
230
251
|
_BedrockInvokeProviderRequest(instrumentor=instrumentor, model_id=modelId),
|
|
231
252
|
_IsStreaming.false,
|
|
232
253
|
wrapped,
|
|
233
|
-
|
|
254
|
+
instance,
|
|
234
255
|
args,
|
|
235
256
|
kwargs,
|
|
236
257
|
)
|
|
237
258
|
|
|
238
259
|
return invoke_wrapper
|
|
239
260
|
|
|
240
|
-
def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
261
|
+
def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
241
262
|
@wraps(wrapped)
|
|
242
263
|
def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
243
264
|
modelId: str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -247,14 +268,14 @@ def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
247
268
|
_BedrockInvokeProviderRequest(instrumentor=instrumentor, model_id=modelId),
|
|
248
269
|
_IsStreaming.true,
|
|
249
270
|
wrapped,
|
|
250
|
-
|
|
271
|
+
instance,
|
|
251
272
|
args,
|
|
252
273
|
kwargs,
|
|
253
274
|
)
|
|
254
275
|
|
|
255
276
|
return invoke_wrapper
|
|
256
277
|
|
|
257
|
-
def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
278
|
+
def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
258
279
|
@wraps(wrapped)
|
|
259
280
|
def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
|
|
260
281
|
modelId:str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -264,14 +285,14 @@ def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
264
285
|
_BedrockConverseProviderRequest(instrumentor=instrumentor),
|
|
265
286
|
_IsStreaming.false,
|
|
266
287
|
wrapped,
|
|
267
|
-
|
|
288
|
+
instance,
|
|
268
289
|
args,
|
|
269
290
|
kwargs,
|
|
270
291
|
)
|
|
271
292
|
|
|
272
293
|
return invoke_wrapper
|
|
273
294
|
|
|
274
|
-
def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
295
|
+
def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any) -> Any:
|
|
275
296
|
@wraps(wrapped)
|
|
276
297
|
def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
277
298
|
modelId: str = kwargs.get("modelId", "") # type: ignore
|
|
@@ -281,7 +302,7 @@ def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
281
302
|
_BedrockConverseProviderRequest(instrumentor=instrumentor),
|
|
282
303
|
_IsStreaming.true,
|
|
283
304
|
wrapped,
|
|
284
|
-
|
|
305
|
+
instance,
|
|
285
306
|
args,
|
|
286
307
|
kwargs,
|
|
287
308
|
)
|
|
@@ -301,10 +322,25 @@ class _BedrockProviderRequest(_ProviderRequest):
|
|
|
301
322
|
)
|
|
302
323
|
|
|
303
324
|
@override
|
|
304
|
-
def process_request(self, instance: Any, extra_headers: 'dict[str, str]',
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
325
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
326
|
+
modelId = kwargs.get("modelId", "")
|
|
327
|
+
self._ingest["resource"] = modelId
|
|
328
|
+
|
|
329
|
+
if not self._price_as.resource and not self._price_as.category and BedrockInstrumentor._model_mapping:
|
|
330
|
+
deployment = BedrockInstrumentor._model_mapping.get(modelId, {})
|
|
331
|
+
self._price_as.category = deployment.get("price_as_category", "")
|
|
332
|
+
self._price_as.resource = deployment.get("price_as_resource", "")
|
|
333
|
+
self._price_as.resource_scope = deployment.get("resource_scope", None)
|
|
334
|
+
|
|
335
|
+
if self._price_as.resource_scope:
|
|
336
|
+
self._ingest["resource_scope"] = self._price_as.resource_scope
|
|
337
|
+
|
|
338
|
+
# override defaults
|
|
339
|
+
if self._price_as.category:
|
|
340
|
+
self._ingest["category"] = self._price_as.category
|
|
341
|
+
if self._price_as.resource:
|
|
342
|
+
self._ingest["resource"] = self._price_as.resource
|
|
343
|
+
|
|
308
344
|
return True
|
|
309
345
|
|
|
310
346
|
@override
|
|
@@ -384,11 +420,25 @@ class _BedrockProviderRequest(_ProviderRequest):
|
|
|
384
420
|
class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
|
|
385
421
|
def __init__(self, instrumentor: _PayiInstrumentor, model_id: str):
|
|
386
422
|
super().__init__(instrumentor=instrumentor)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
423
|
+
|
|
424
|
+
price_as_resource = BedrockInstrumentor._model_mapping.get(model_id, {}).get("price_as_resource", None)
|
|
425
|
+
if price_as_resource:
|
|
426
|
+
model_id = price_as_resource
|
|
427
|
+
|
|
428
|
+
self._is_anthropic: bool = False
|
|
429
|
+
self._is_nova: bool = False
|
|
430
|
+
self._is_meta: bool = False
|
|
431
|
+
self._is_amazon_titan_embed_text_v1: bool = False
|
|
432
|
+
self._is_cohere_embed_english_v3: bool = False
|
|
433
|
+
|
|
434
|
+
self._assign_model_state(model_id=model_id)
|
|
435
|
+
|
|
436
|
+
def _assign_model_state(self, model_id: str) -> None:
|
|
437
|
+
self._is_anthropic = 'anthropic' in model_id
|
|
438
|
+
self._is_nova = 'nova' in model_id
|
|
439
|
+
self._is_meta = 'meta' in model_id
|
|
440
|
+
self._is_amazon_titan_embed_text_v1 = 'amazon.titan-embed-text-v1' == model_id
|
|
441
|
+
self._is_cohere_embed_english_v3 = 'cohere.embed-english-v3' == model_id
|
|
392
442
|
|
|
393
443
|
@override
|
|
394
444
|
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
@@ -396,6 +446,10 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
|
|
|
396
446
|
|
|
397
447
|
super().process_request(instance, extra_headers, args, kwargs)
|
|
398
448
|
|
|
449
|
+
# super().process_request will assign price_as mapping from global state, so evaluate afterwards
|
|
450
|
+
if self._price_as.resource:
|
|
451
|
+
self._assign_model_state(model_id=self._price_as.resource)
|
|
452
|
+
|
|
399
453
|
guardrail_id = kwargs.get("guardrailIdentifier", "")
|
|
400
454
|
if guardrail_id:
|
|
401
455
|
self.add_internal_request_property(PayiPropertyNames.aws_bedrock_guardrail_id, guardrail_id)
|
|
@@ -524,7 +578,7 @@ class _BedrockConverseProviderRequest(_BedrockProviderRequest):
|
|
|
524
578
|
@override
|
|
525
579
|
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
526
580
|
super().process_request(instance, extra_headers, args, kwargs)
|
|
527
|
-
|
|
581
|
+
|
|
528
582
|
guardrail_config = kwargs.get("guardrailConfig", {})
|
|
529
583
|
if guardrail_config:
|
|
530
584
|
guardrailIdentifier = guardrail_config.get("guardrailIdentifier", "")
|
payi/lib/OpenAIInstrumentor.py
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Any, Union, Optional, Sequence
|
|
2
|
+
from typing import Any, Dict, Union, Optional, Sequence
|
|
3
3
|
from typing_extensions import override
|
|
4
4
|
from importlib.metadata import version
|
|
5
5
|
|
|
6
6
|
import tiktoken # type: ignore
|
|
7
7
|
from wrapt import wrap_function_wrapper # type: ignore
|
|
8
8
|
|
|
9
|
-
from payi.lib.helpers import PayiCategories
|
|
9
|
+
from payi.lib.helpers import PayiCategories
|
|
10
10
|
from payi.types.ingest_units_params import Units
|
|
11
11
|
|
|
12
|
-
from .instrument import
|
|
12
|
+
from .instrument import (
|
|
13
|
+
PayiInstrumentAzureOpenAiConfig,
|
|
14
|
+
_Context,
|
|
15
|
+
_ChunkResult,
|
|
16
|
+
_IsStreaming,
|
|
17
|
+
_StreamingType,
|
|
18
|
+
_ProviderRequest,
|
|
19
|
+
_PayiInstrumentor,
|
|
20
|
+
)
|
|
13
21
|
from .version_helper import get_version_helper
|
|
14
22
|
|
|
15
23
|
|
|
@@ -17,12 +25,20 @@ class OpenAiInstrumentor:
|
|
|
17
25
|
_module_name: str = "openai"
|
|
18
26
|
_module_version: str = ""
|
|
19
27
|
|
|
28
|
+
_azure_openai_deployments: Dict[str, _Context] = {}
|
|
29
|
+
|
|
20
30
|
@staticmethod
|
|
21
31
|
def is_azure(instance: Any) -> bool:
|
|
22
32
|
from openai import AzureOpenAI, AsyncAzureOpenAI # type: ignore # noqa: I001
|
|
23
33
|
|
|
24
34
|
return isinstance(instance._client, (AsyncAzureOpenAI, AzureOpenAI))
|
|
25
35
|
|
|
36
|
+
@staticmethod
|
|
37
|
+
def configure(azure_openai_config: Optional[PayiInstrumentAzureOpenAiConfig]) -> None:
|
|
38
|
+
if azure_openai_config:
|
|
39
|
+
model_mappings = azure_openai_config.get("model_mappings", [])
|
|
40
|
+
OpenAiInstrumentor._azure_openai_deployments = _PayiInstrumentor._model_mapping_to_context_dict(model_mappings)
|
|
41
|
+
|
|
26
42
|
@staticmethod
|
|
27
43
|
def instrument(instrumentor: _PayiInstrumentor) -> None:
|
|
28
44
|
try:
|
|
@@ -52,7 +68,7 @@ class OpenAiInstrumentor:
|
|
|
52
68
|
aembeddings_wrapper(instrumentor),
|
|
53
69
|
)
|
|
54
70
|
except Exception as e:
|
|
55
|
-
instrumentor._logger.debug(f"Error instrumenting openai: {e}")
|
|
71
|
+
instrumentor._logger.debug(f"Error instrumenting openai completions: {e}")
|
|
56
72
|
|
|
57
73
|
# responses separately as they are relatively new and the client may not be using the latest openai module
|
|
58
74
|
try:
|
|
@@ -69,7 +85,7 @@ class OpenAiInstrumentor:
|
|
|
69
85
|
)
|
|
70
86
|
|
|
71
87
|
except Exception as e:
|
|
72
|
-
instrumentor._logger.debug(f"Error instrumenting openai: {e}")
|
|
88
|
+
instrumentor._logger.debug(f"Error instrumenting openai responses: {e}")
|
|
73
89
|
|
|
74
90
|
@_PayiInstrumentor.payi_wrapper
|
|
75
91
|
def embeddings_wrapper(
|
|
@@ -201,44 +217,39 @@ class _OpenAiProviderRequest(_ProviderRequest):
|
|
|
201
217
|
self._input_tokens_details_key = input_tokens_details_key
|
|
202
218
|
|
|
203
219
|
@override
|
|
204
|
-
def process_request(self, instance: Any, extra_headers: 'dict[str, str]',
|
|
205
|
-
|
|
220
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool: # type: ignore
|
|
221
|
+
model = kwargs.get("model", "")
|
|
206
222
|
|
|
207
223
|
if not (instance and hasattr(instance, "_client")) or OpenAiInstrumentor.is_azure(instance) is False:
|
|
224
|
+
self._ingest["resource"] = model
|
|
208
225
|
return True
|
|
209
226
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if PayiHeaderNames.price_as_resource in extra_headers:
|
|
218
|
-
del extra_headers[PayiHeaderNames.price_as_resource]
|
|
219
|
-
if PayiHeaderNames.resource_scope in extra_headers:
|
|
220
|
-
del extra_headers[PayiHeaderNames.resource_scope]
|
|
221
|
-
|
|
222
|
-
if not price_as_resource and not price_as_category:
|
|
227
|
+
if not self._price_as.resource and not self._price_as.category and OpenAiInstrumentor._azure_openai_deployments:
|
|
228
|
+
deployment = OpenAiInstrumentor._azure_openai_deployments.get(model, {})
|
|
229
|
+
self._price_as.category = deployment.get("price_as_category", None)
|
|
230
|
+
self._price_as.resource = deployment.get("price_as_resource", None)
|
|
231
|
+
self._price_as.resource_scope = deployment.get("resource_scope", None)
|
|
232
|
+
|
|
233
|
+
if not self._price_as.resource and not self._price_as.category:
|
|
223
234
|
self._instrumentor._logger.error("Azure OpenAI requires price as resource and/or category to be specified, not ingesting")
|
|
224
235
|
return False
|
|
225
236
|
|
|
226
|
-
if resource_scope:
|
|
227
|
-
if not(resource_scope in ["global", "datazone"] or resource_scope.startswith("region")):
|
|
237
|
+
if self._price_as.resource_scope:
|
|
238
|
+
if not (self._price_as.resource_scope in ["global", "datazone"] or self._price_as.resource_scope.startswith("region")):
|
|
228
239
|
self._instrumentor._logger.error("Azure OpenAI invalid resource scope, not ingesting")
|
|
229
240
|
return False
|
|
230
241
|
|
|
231
|
-
self._ingest["resource_scope"] = resource_scope
|
|
242
|
+
self._ingest["resource_scope"] = self._price_as.resource_scope
|
|
232
243
|
|
|
233
244
|
self._category = PayiCategories.azure_openai
|
|
234
245
|
|
|
235
246
|
self._ingest["category"] = self._category
|
|
236
247
|
|
|
237
|
-
if
|
|
248
|
+
if self._price_as.category:
|
|
238
249
|
# price as category overrides default
|
|
239
|
-
self._ingest["category"] =
|
|
240
|
-
if
|
|
241
|
-
self._ingest["resource"] =
|
|
250
|
+
self._ingest["category"] = self._price_as.category
|
|
251
|
+
if self._price_as.resource:
|
|
252
|
+
self._ingest["resource"] = self._price_as.resource
|
|
242
253
|
|
|
243
254
|
return True
|
|
244
255
|
|
payi/lib/helpers.py
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import os
|
|
2
|
-
|
|
2
|
+
import json
|
|
3
|
+
from typing import Any, Dict, List, Union
|
|
3
4
|
|
|
4
5
|
PAYI_BASE_URL = "https://api.pay-i.com"
|
|
5
6
|
|
|
6
7
|
class PayiHeaderNames:
|
|
7
8
|
limit_ids:str = "xProxy-Limit-IDs"
|
|
8
9
|
request_tags:str = "xProxy-Request-Tags"
|
|
10
|
+
request_properties:str = "xProxy-Request-Properties"
|
|
9
11
|
use_case_id:str = "xProxy-UseCase-ID"
|
|
10
12
|
use_case_name:str = "xProxy-UseCase-Name"
|
|
11
13
|
use_case_version:str = "xProxy-UseCase-Version"
|
|
12
14
|
use_case_step:str = "xProxy-UseCase-Step"
|
|
15
|
+
use_case_properties:str = "xProxy-UseCase-Properties"
|
|
13
16
|
user_id:str = "xProxy-User-ID"
|
|
14
17
|
account_name:str = "xProxy-Account-Name"
|
|
15
18
|
price_as_category:str = "xProxy-PriceAs-Category"
|
|
@@ -37,6 +40,11 @@ class PayiPropertyNames:
|
|
|
37
40
|
aws_bedrock_guardrail_version:str = "system.aws.bedrock.guardrail.version"
|
|
38
41
|
aws_bedrock_guardrail_action:str = "system.aws.bedrock.guardrail.action"
|
|
39
42
|
|
|
43
|
+
class PayiResourceScopes:
|
|
44
|
+
global_scope: str = "global"
|
|
45
|
+
datazone_scope: str = "datazone"
|
|
46
|
+
region_scope: str = "region"
|
|
47
|
+
|
|
40
48
|
def create_limit_header_from_ids(*, limit_ids: List[str]) -> Dict[str, str]:
|
|
41
49
|
if not isinstance(limit_ids, list): # type: ignore
|
|
42
50
|
raise TypeError("limit_ids must be a list")
|
|
@@ -53,6 +61,9 @@ def create_request_header_from_tags(*, request_tags: List[str]) -> Dict[str, str
|
|
|
53
61
|
|
|
54
62
|
return { PayiHeaderNames.request_tags: ",".join(valid_tags) } if valid_tags else {}
|
|
55
63
|
|
|
64
|
+
def _compact_json(data: Any) -> str:
|
|
65
|
+
return json.dumps(data, separators=(',', ':'))
|
|
66
|
+
|
|
56
67
|
def create_headers(
|
|
57
68
|
*,
|
|
58
69
|
limit_ids: Union[List[str], None] = None,
|
|
@@ -63,6 +74,8 @@ def create_headers(
|
|
|
63
74
|
use_case_name: Union[str, None] = None,
|
|
64
75
|
use_case_version: Union[int, None] = None,
|
|
65
76
|
use_case_step: Union[str, None] = None,
|
|
77
|
+
use_case_properties: Union[Dict[str, str], None] = None,
|
|
78
|
+
request_properties: Union[Dict[str, str], None] = None,
|
|
66
79
|
price_as_category: Union[str, None] = None,
|
|
67
80
|
price_as_resource: Union[str, None] = None,
|
|
68
81
|
resource_scope: Union[str, None] = None,
|
|
@@ -83,6 +96,10 @@ def create_headers(
|
|
|
83
96
|
headers.update({ PayiHeaderNames.use_case_name: use_case_name})
|
|
84
97
|
if use_case_version:
|
|
85
98
|
headers.update({ PayiHeaderNames.use_case_version: str(use_case_version)})
|
|
99
|
+
if use_case_properties:
|
|
100
|
+
headers.update({ PayiHeaderNames.use_case_properties: _compact_json(use_case_properties) })
|
|
101
|
+
if request_properties:
|
|
102
|
+
headers.update({ PayiHeaderNames.request_properties: _compact_json(request_properties) })
|
|
86
103
|
if use_case_step:
|
|
87
104
|
headers.update({ PayiHeaderNames.use_case_step: use_case_step})
|
|
88
105
|
if price_as_category:
|
payi/lib/instrument.py
CHANGED
|
@@ -19,7 +19,7 @@ from wrapt import ObjectProxy # type: ignore
|
|
|
19
19
|
|
|
20
20
|
from payi import Payi, AsyncPayi, APIStatusError, APIConnectionError, __version__ as _payi_version
|
|
21
21
|
from payi.types import IngestUnitsParams
|
|
22
|
-
from payi.lib.helpers import PayiHeaderNames, PayiPropertyNames
|
|
22
|
+
from payi.lib.helpers import PayiHeaderNames, PayiPropertyNames, _compact_json
|
|
23
23
|
from payi.types.shared import XproxyResult
|
|
24
24
|
from payi.types.ingest_response import IngestResponse
|
|
25
25
|
from payi.types.ingest_units_params import Units, ProviderResponseFunctionCall
|
|
@@ -36,7 +36,13 @@ _g_logger: logging.Logger = logging.getLogger("payi.instrument")
|
|
|
36
36
|
class _ChunkResult:
|
|
37
37
|
send_chunk_to_caller: bool
|
|
38
38
|
ingest: bool = False
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class PriceAs:
|
|
42
|
+
category: Optional[str]
|
|
43
|
+
resource: Optional[str]
|
|
44
|
+
resource_scope: Optional[str]
|
|
45
|
+
|
|
40
46
|
class _ProviderRequest:
|
|
41
47
|
def __init__(
|
|
42
48
|
self,
|
|
@@ -62,6 +68,7 @@ class _ProviderRequest:
|
|
|
62
68
|
self._function_calls: Optional[list[ProviderResponseFunctionCall]] = None
|
|
63
69
|
self._is_large_context: bool = False
|
|
64
70
|
self._internal_request_properties: dict[str, Optional[str]] = {}
|
|
71
|
+
self._price_as: PriceAs = PriceAs(category=None, resource=None, resource_scope=None)
|
|
65
72
|
|
|
66
73
|
def process_chunk(self, _chunk: Any) -> _ChunkResult:
|
|
67
74
|
return _ChunkResult(send_chunk_to_caller=True)
|
|
@@ -147,8 +154,20 @@ class _ProviderRequest:
|
|
|
147
154
|
self._ingest["provider_response_function_calls"] = self._function_calls
|
|
148
155
|
self._function_calls.append(ProviderResponseFunctionCall(name=name, arguments=arguments))
|
|
149
156
|
|
|
157
|
+
class PayiInstrumentModelMapping(TypedDict, total=False):
|
|
158
|
+
model: str
|
|
159
|
+
price_as_category: Optional[str]
|
|
160
|
+
price_as_resource: Optional[str]
|
|
161
|
+
# "global", "datazone", "region", "region.<region_name>"
|
|
162
|
+
resource_scope: Optional[str]
|
|
163
|
+
|
|
150
164
|
class PayiInstrumentAwsBedrockConfig(TypedDict, total=False):
|
|
151
|
-
guardrail_trace: bool
|
|
165
|
+
guardrail_trace: Optional[bool]
|
|
166
|
+
model_mappings: Optional[Sequence[PayiInstrumentModelMapping]]
|
|
167
|
+
|
|
168
|
+
class PayiInstrumentAzureOpenAiConfig(TypedDict, total=False):
|
|
169
|
+
# map deployment name known model
|
|
170
|
+
model_mappings: Sequence[PayiInstrumentModelMapping]
|
|
152
171
|
|
|
153
172
|
class PayiInstrumentOfflineInstrumentationConfig(TypedDict, total=False):
|
|
154
173
|
file_name: str
|
|
@@ -168,6 +187,7 @@ class PayiInstrumentConfig(TypedDict, total=False):
|
|
|
168
187
|
request_tags: Optional["list[str]"]
|
|
169
188
|
request_properties: Optional["dict[str, Optional[str]]"]
|
|
170
189
|
aws_config: Optional[PayiInstrumentAwsBedrockConfig]
|
|
190
|
+
azure_openai_config: Optional[PayiInstrumentAzureOpenAiConfig]
|
|
171
191
|
offline_instrumentation: Optional[PayiInstrumentOfflineInstrumentationConfig]
|
|
172
192
|
|
|
173
193
|
class PayiContext(TypedDict, total=False):
|
|
@@ -186,6 +206,19 @@ class PayiContext(TypedDict, total=False):
|
|
|
186
206
|
resource_scope: Optional[str]
|
|
187
207
|
last_result: Optional[Union[XproxyResult, XproxyError]]
|
|
188
208
|
|
|
209
|
+
class PayiInstanceDefaultContext(TypedDict, total=False):
|
|
210
|
+
use_case_name: Optional[str]
|
|
211
|
+
use_case_id: Optional[str]
|
|
212
|
+
use_case_version: Optional[int]
|
|
213
|
+
use_case_properties: Optional["dict[str, str]"]
|
|
214
|
+
limit_ids: Optional['list[str]']
|
|
215
|
+
user_id: Optional[str]
|
|
216
|
+
account_name: Optional[str]
|
|
217
|
+
request_properties: Optional["dict[str, str]"]
|
|
218
|
+
price_as_category: Optional[str]
|
|
219
|
+
price_as_resource: Optional[str]
|
|
220
|
+
resource_scope: Optional[str]
|
|
221
|
+
|
|
189
222
|
class _Context(TypedDict, total=False):
|
|
190
223
|
proxy: Optional[bool]
|
|
191
224
|
use_case_name: Optional[str]
|
|
@@ -277,7 +310,8 @@ class _PayiInstrumentor:
|
|
|
277
310
|
self._blocked_limits: set[str] = set()
|
|
278
311
|
self._exceeded_limits: set[str] = set()
|
|
279
312
|
|
|
280
|
-
|
|
313
|
+
# by not setting to time.time() the first connection error is always logged
|
|
314
|
+
self._api_connection_error_last_log_time: float = 0
|
|
281
315
|
self._api_connection_error_count: int = 0
|
|
282
316
|
self._api_connection_error_window: int = global_config.get("connection_error_logging_window", 60)
|
|
283
317
|
if self._api_connection_error_window < 0:
|
|
@@ -303,10 +337,21 @@ class _PayiInstrumentor:
|
|
|
303
337
|
|
|
304
338
|
global_instrumentation = global_config.pop("global_instrumentation", True)
|
|
305
339
|
|
|
340
|
+
# configure first, then instrument
|
|
341
|
+
aws_config = global_config.get("aws_config", None)
|
|
342
|
+
if aws_config:
|
|
343
|
+
from .BedrockInstrumentor import BedrockInstrumentor
|
|
344
|
+
BedrockInstrumentor.configure(aws_config=aws_config)
|
|
345
|
+
|
|
346
|
+
azure_openai_config = global_config.get("azure_openai_config", None)
|
|
347
|
+
if azure_openai_config:
|
|
348
|
+
from .OpenAIInstrumentor import OpenAiInstrumentor
|
|
349
|
+
OpenAiInstrumentor.configure(azure_openai_config=azure_openai_config)
|
|
350
|
+
|
|
306
351
|
if instruments is None or "*" in instruments:
|
|
307
|
-
self._instrument_all(
|
|
352
|
+
self._instrument_all()
|
|
308
353
|
else:
|
|
309
|
-
self._instrument_specific(instruments=instruments
|
|
354
|
+
self._instrument_specific(instruments=instruments)
|
|
310
355
|
|
|
311
356
|
if global_instrumentation:
|
|
312
357
|
if "proxy" not in global_config:
|
|
@@ -338,13 +383,13 @@ class _PayiInstrumentor:
|
|
|
338
383
|
context_keys = list(_Context.__annotations__.keys()) if hasattr(_Context, '__annotations__') else []
|
|
339
384
|
for key in context_keys:
|
|
340
385
|
if key in global_config:
|
|
341
|
-
context[key] = global_config[key] # type: ignore
|
|
386
|
+
context[key] = global_config[key] # type: ignore[literal-required]
|
|
342
387
|
|
|
343
388
|
self._init_current_context(**context)
|
|
344
389
|
|
|
345
390
|
# Store the initialized context as the global initial context (immutable after this point)
|
|
346
391
|
# All threads will inherit a copy of this context on their first access
|
|
347
|
-
current_context = self.
|
|
392
|
+
current_context = self._context
|
|
348
393
|
self._global_initial_context = current_context.copy() if current_context else None
|
|
349
394
|
|
|
350
395
|
def _ensure_payi_clients(self) -> None:
|
|
@@ -355,20 +400,20 @@ class _PayiInstrumentor:
|
|
|
355
400
|
self._payi = Payi()
|
|
356
401
|
self._apayi = AsyncPayi()
|
|
357
402
|
|
|
358
|
-
def _instrument_all(self
|
|
403
|
+
def _instrument_all(self) -> None:
|
|
359
404
|
self._instrument_openai()
|
|
360
405
|
self._instrument_anthropic()
|
|
361
|
-
self._instrument_aws_bedrock(
|
|
406
|
+
self._instrument_aws_bedrock()
|
|
362
407
|
self._instrument_google_vertex()
|
|
363
408
|
self._instrument_google_genai()
|
|
364
409
|
|
|
365
|
-
def _instrument_specific(self, instruments: Set[str]
|
|
410
|
+
def _instrument_specific(self, instruments: Set[str]) -> None:
|
|
366
411
|
if PayiCategories.openai in instruments or PayiCategories.azure_openai in instruments:
|
|
367
412
|
self._instrument_openai()
|
|
368
413
|
if PayiCategories.anthropic in instruments:
|
|
369
414
|
self._instrument_anthropic()
|
|
370
415
|
if PayiCategories.aws_bedrock in instruments:
|
|
371
|
-
self._instrument_aws_bedrock(
|
|
416
|
+
self._instrument_aws_bedrock()
|
|
372
417
|
if PayiCategories.google_vertex in instruments:
|
|
373
418
|
self._instrument_google_vertex()
|
|
374
419
|
self._instrument_google_genai()
|
|
@@ -391,11 +436,11 @@ class _PayiInstrumentor:
|
|
|
391
436
|
except Exception as e:
|
|
392
437
|
self._logger.error(f"Error instrumenting Anthropic: {e}")
|
|
393
438
|
|
|
394
|
-
def _instrument_aws_bedrock(self
|
|
439
|
+
def _instrument_aws_bedrock(self) -> None:
|
|
395
440
|
from .BedrockInstrumentor import BedrockInstrumentor
|
|
396
441
|
|
|
397
442
|
try:
|
|
398
|
-
BedrockInstrumentor.instrument(self
|
|
443
|
+
BedrockInstrumentor.instrument(self)
|
|
399
444
|
|
|
400
445
|
except Exception as e:
|
|
401
446
|
self._logger.error(f"Error instrumenting AWS bedrock: {e}")
|
|
@@ -418,6 +463,28 @@ class _PayiInstrumentor:
|
|
|
418
463
|
except Exception as e:
|
|
419
464
|
self._logger.error(f"Error instrumenting Google GenAi: {e}")
|
|
420
465
|
|
|
466
|
+
@staticmethod
|
|
467
|
+
def _model_mapping_to_context_dict(model_mappings: Sequence[PayiInstrumentModelMapping]) -> 'dict[str, _Context]':
|
|
468
|
+
context: dict[str, _Context] = {}
|
|
469
|
+
for mapping in model_mappings:
|
|
470
|
+
model = mapping.get("model", "")
|
|
471
|
+
if not model:
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
price_as_category = mapping.get("price_as_category", None)
|
|
475
|
+
price_as_resource = mapping.get("price_as_resource", None)
|
|
476
|
+
resource_scope = mapping.get("resource_scope", None)
|
|
477
|
+
|
|
478
|
+
if not price_as_category and not price_as_resource:
|
|
479
|
+
continue
|
|
480
|
+
|
|
481
|
+
context[model] = _Context(
|
|
482
|
+
price_as_category=price_as_category,
|
|
483
|
+
price_as_resource=price_as_resource,
|
|
484
|
+
resource_scope=resource_scope,
|
|
485
|
+
)
|
|
486
|
+
return context
|
|
487
|
+
|
|
421
488
|
def _write_offline_ingest_packets(self) -> None:
|
|
422
489
|
if not self._offline_instrumentation_file_name or not self._offline_ingest_packets:
|
|
423
490
|
return
|
|
@@ -458,7 +525,17 @@ class _PayiInstrumentor:
|
|
|
458
525
|
|
|
459
526
|
return log_ingest_units
|
|
460
527
|
|
|
461
|
-
def
|
|
528
|
+
def _merge_internal_request_properties(self, request: _ProviderRequest) -> None:
|
|
529
|
+
if not request._internal_request_properties:
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
properties = request._ingest.get("properties") or {}
|
|
533
|
+
request._ingest["properties"] = properties
|
|
534
|
+
for key, value in request._internal_request_properties.items():
|
|
535
|
+
if key not in properties:
|
|
536
|
+
properties[key] = value
|
|
537
|
+
|
|
538
|
+
def _after_invoke_update_request(
|
|
462
539
|
self,
|
|
463
540
|
request: _ProviderRequest,
|
|
464
541
|
extra_headers: 'dict[str, str]') -> None:
|
|
@@ -477,12 +554,7 @@ class _PayiInstrumentor:
|
|
|
477
554
|
if 'resource' not in ingest_units or ingest_units['resource'] == '':
|
|
478
555
|
ingest_units['resource'] = "system.unknown_model"
|
|
479
556
|
|
|
480
|
-
|
|
481
|
-
properties = ingest_units.get("properties") or {}
|
|
482
|
-
ingest_units["properties"] = properties
|
|
483
|
-
for key, value in request._internal_request_properties.items():
|
|
484
|
-
if key not in properties:
|
|
485
|
-
properties[key] = value
|
|
557
|
+
self._merge_internal_request_properties(request)
|
|
486
558
|
|
|
487
559
|
request_json = ingest_units.get('provider_request_json', "")
|
|
488
560
|
if request_json and self._instrument_inline_data is False:
|
|
@@ -491,7 +563,7 @@ class _PayiInstrumentor:
|
|
|
491
563
|
if request.remove_inline_data(prompt_dict):
|
|
492
564
|
self._logger.debug(f"Removed inline data from provider_request_json")
|
|
493
565
|
# store the modified dict back as JSON string
|
|
494
|
-
ingest_units['provider_request_json'] =
|
|
566
|
+
ingest_units['provider_request_json'] = _compact_json(prompt_dict)
|
|
495
567
|
|
|
496
568
|
except Exception as e:
|
|
497
569
|
self._logger.error(f"Error serializing provider_request_json: {e}")
|
|
@@ -544,7 +616,7 @@ class _PayiInstrumentor:
|
|
|
544
616
|
self._logger.debug(f"_aingest_units")
|
|
545
617
|
|
|
546
618
|
extra_headers: 'dict[str, str]' = {}
|
|
547
|
-
self.
|
|
619
|
+
self._after_invoke_update_request(request, extra_headers=extra_headers)
|
|
548
620
|
|
|
549
621
|
try:
|
|
550
622
|
if self._logger.isEnabledFor(logging.DEBUG):
|
|
@@ -668,7 +740,7 @@ class _PayiInstrumentor:
|
|
|
668
740
|
self._logger.debug(f"_ingest_units")
|
|
669
741
|
|
|
670
742
|
extra_headers: 'dict[str, str]' = {}
|
|
671
|
-
self.
|
|
743
|
+
self._after_invoke_update_request(request, extra_headers=extra_headers)
|
|
672
744
|
|
|
673
745
|
try:
|
|
674
746
|
if self._payi:
|
|
@@ -763,6 +835,56 @@ class _PayiInstrumentor:
|
|
|
763
835
|
else:
|
|
764
836
|
return value.copy()
|
|
765
837
|
|
|
838
|
+
def _set_instance_default_context(
|
|
839
|
+
self,
|
|
840
|
+
instance: Any,
|
|
841
|
+
context: PayiInstanceDefaultContext
|
|
842
|
+
) -> None:
|
|
843
|
+
if instance is None:
|
|
844
|
+
raise ValueError("instance cannot be None")
|
|
845
|
+
if not context:
|
|
846
|
+
raise ValueError("context_dict cannot be None or empty")
|
|
847
|
+
|
|
848
|
+
context = context.copy()
|
|
849
|
+
if "use_case_properties" in context and context["use_case_properties"] is not None:
|
|
850
|
+
context["use_case_properties"] = context["use_case_properties"].copy()
|
|
851
|
+
if "request_properties" in context and context["request_properties"] is not None:
|
|
852
|
+
context["request_properties"] = context["request_properties"].copy()
|
|
853
|
+
if "limit_ids" in context and context["limit_ids"] is not None:
|
|
854
|
+
context["limit_ids"] = context["limit_ids"].copy()
|
|
855
|
+
|
|
856
|
+
instance.__payi_default_context__ = context
|
|
857
|
+
self._logger.debug(f"payi_set_default_context: attached context to instance {type(instance).__name__}")
|
|
858
|
+
|
|
859
|
+
@staticmethod
|
|
860
|
+
def _get_instance_default_context(
|
|
861
|
+
instance: Any
|
|
862
|
+
) -> "Optional[PayiInstanceDefaultContext]":
|
|
863
|
+
if instance is None:
|
|
864
|
+
return None
|
|
865
|
+
|
|
866
|
+
context = getattr(instance, "__payi_default_context__", None)
|
|
867
|
+
if not context:
|
|
868
|
+
inner_instance = getattr(instance, "_client", None)
|
|
869
|
+
if inner_instance:
|
|
870
|
+
context = getattr(inner_instance, "__payi_default_context__", None)
|
|
871
|
+
|
|
872
|
+
# Return a copy to prevent external modifications
|
|
873
|
+
return context if context else None
|
|
874
|
+
|
|
875
|
+
@staticmethod
|
|
876
|
+
def _merge_context_instance_defaults(
|
|
877
|
+
context: _Context,
|
|
878
|
+
instance_defaults: Optional[PayiInstanceDefaultContext]
|
|
879
|
+
) -> _Context:
|
|
880
|
+
if instance_defaults:
|
|
881
|
+
context = context.copy()
|
|
882
|
+
for key, value in instance_defaults.items():
|
|
883
|
+
if value is not None and context.get(key, None) is None:
|
|
884
|
+
context[key] = value # type: ignore[literal-required]
|
|
885
|
+
|
|
886
|
+
return context
|
|
887
|
+
|
|
766
888
|
def _init_current_context(
|
|
767
889
|
self,
|
|
768
890
|
proxy: Optional[bool] = None,
|
|
@@ -781,7 +903,7 @@ class _PayiInstrumentor:
|
|
|
781
903
|
) -> None:
|
|
782
904
|
|
|
783
905
|
# there will always be a current context
|
|
784
|
-
context: _Context = self.
|
|
906
|
+
context: _Context = self._context # type: ignore
|
|
785
907
|
parent_context: _Context = self._context_stack[-2] if len(self._context_stack) > 1 else {}
|
|
786
908
|
|
|
787
909
|
parent_proxy = parent_context.get("proxy", self._proxy_default)
|
|
@@ -923,27 +1045,38 @@ class _PayiInstrumentor:
|
|
|
923
1045
|
if self._context_stack:
|
|
924
1046
|
self._context_stack.pop()
|
|
925
1047
|
|
|
926
|
-
|
|
1048
|
+
@property
|
|
1049
|
+
def _context(self) -> Optional[_Context]:
|
|
927
1050
|
# Return the current top of the stack
|
|
928
1051
|
return self._context_stack[-1] if self._context_stack else None
|
|
929
1052
|
|
|
930
|
-
|
|
1053
|
+
@property
|
|
1054
|
+
def _context_safe(self) -> _Context:
|
|
931
1055
|
# Return the current top of the stack
|
|
932
|
-
return self.
|
|
1056
|
+
return self._context or {}
|
|
1057
|
+
|
|
1058
|
+
def _extract_price_as(self, extra_headers: "dict[str, str]") -> PriceAs:
|
|
1059
|
+
context = self._context_safe
|
|
933
1060
|
|
|
934
|
-
|
|
1061
|
+
return PriceAs(
|
|
1062
|
+
category=extra_headers.pop(PayiHeaderNames.price_as_category, None) or context.get("price_as_category", None),
|
|
1063
|
+
resource=extra_headers.pop(PayiHeaderNames.price_as_resource, None) or context.get("price_as_resource", None),
|
|
1064
|
+
resource_scope=extra_headers.pop(PayiHeaderNames.resource_scope, None) or context.get("resource_scope", None),
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
def _before_invoke_update_request(
|
|
935
1068
|
self,
|
|
936
1069
|
request: _ProviderRequest,
|
|
937
|
-
context: _Context,
|
|
938
1070
|
ingest_extra_headers: "dict[str, str]", # do not conflict with potential kwargs["extra_headers"]
|
|
939
1071
|
args: Sequence[Any],
|
|
940
1072
|
kwargs: 'dict[str, Any]',
|
|
941
1073
|
) -> None:
|
|
942
1074
|
|
|
943
|
-
limit_ids = ingest_extra_headers.pop(PayiHeaderNames.limit_ids, None)
|
|
944
1075
|
# pop and ignore the request tags header since it is no longer processed
|
|
945
1076
|
ingest_extra_headers.pop(PayiHeaderNames.request_tags, None)
|
|
946
1077
|
|
|
1078
|
+
limit_ids = ingest_extra_headers.pop(PayiHeaderNames.limit_ids, None)
|
|
1079
|
+
|
|
947
1080
|
use_case_name = ingest_extra_headers.pop(PayiHeaderNames.use_case_name, None)
|
|
948
1081
|
use_case_id = ingest_extra_headers.pop(PayiHeaderNames.use_case_id, None)
|
|
949
1082
|
use_case_version = ingest_extra_headers.pop(PayiHeaderNames.use_case_version, None)
|
|
@@ -952,6 +1085,9 @@ class _PayiInstrumentor:
|
|
|
952
1085
|
user_id = ingest_extra_headers.pop(PayiHeaderNames.user_id, None)
|
|
953
1086
|
account_name = ingest_extra_headers.pop(PayiHeaderNames.account_name, None)
|
|
954
1087
|
|
|
1088
|
+
request_properties = ingest_extra_headers.pop(PayiHeaderNames.request_properties, "")
|
|
1089
|
+
use_case_properties = ingest_extra_headers.pop(PayiHeaderNames.use_case_properties, "")
|
|
1090
|
+
|
|
955
1091
|
if limit_ids:
|
|
956
1092
|
request._ingest["limit_ids"] = limit_ids.split(",")
|
|
957
1093
|
if use_case_name:
|
|
@@ -966,23 +1102,10 @@ class _PayiInstrumentor:
|
|
|
966
1102
|
request._ingest["user_id"] = user_id
|
|
967
1103
|
if account_name:
|
|
968
1104
|
request._ingest["account_name"] = account_name
|
|
969
|
-
|
|
970
|
-
request_properties = context.get("request_properties", None)
|
|
971
1105
|
if request_properties:
|
|
972
|
-
request._ingest["properties"] = request_properties
|
|
973
|
-
|
|
974
|
-
use_case_properties = context.get("use_case_properties", None)
|
|
1106
|
+
request._ingest["properties"] = json.loads(request_properties)
|
|
975
1107
|
if use_case_properties:
|
|
976
|
-
request._ingest["use_case_properties"] = use_case_properties
|
|
977
|
-
|
|
978
|
-
if request._internal_request_properties:
|
|
979
|
-
if "properties" in request._ingest and request._ingest["properties"] is not None:
|
|
980
|
-
# Merge internal request properties, but don't override existing keys
|
|
981
|
-
for key, value in request._internal_request_properties.items():
|
|
982
|
-
if key not in request._ingest["properties"]:
|
|
983
|
-
request._ingest["properties"][key] = value
|
|
984
|
-
else:
|
|
985
|
-
request._ingest["properties"] = request._internal_request_properties # Assign
|
|
1108
|
+
request._ingest["use_case_properties"] = json.loads(use_case_properties)
|
|
986
1109
|
|
|
987
1110
|
if len(ingest_extra_headers) > 0:
|
|
988
1111
|
request._ingest["provider_request_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in ingest_extra_headers.items()]
|
|
@@ -1006,7 +1129,7 @@ class _PayiInstrumentor:
|
|
|
1006
1129
|
request.process_request_prompt(provider_prompt, args, kwargs)
|
|
1007
1130
|
|
|
1008
1131
|
if self._log_prompt_and_response:
|
|
1009
|
-
request._ingest["provider_request_json"] =
|
|
1132
|
+
request._ingest["provider_request_json"] = _compact_json(provider_prompt)
|
|
1010
1133
|
|
|
1011
1134
|
request._ingest["event_timestamp"] = datetime.now(timezone.utc)
|
|
1012
1135
|
|
|
@@ -1021,7 +1144,7 @@ class _PayiInstrumentor:
|
|
|
1021
1144
|
) -> Any:
|
|
1022
1145
|
self._logger.debug(f"async_invoke_wrapper: instance {instance}, category {request._category}")
|
|
1023
1146
|
|
|
1024
|
-
context = self.
|
|
1147
|
+
context = self._context
|
|
1025
1148
|
|
|
1026
1149
|
# Bedrock client does not have an async method
|
|
1027
1150
|
|
|
@@ -1031,6 +1154,8 @@ class _PayiInstrumentor:
|
|
|
1031
1154
|
# wrapped function invoked outside of decorator scope
|
|
1032
1155
|
return await wrapped(*args, **kwargs)
|
|
1033
1156
|
|
|
1157
|
+
# context = self._merge_context_instance_defaults(context, self._get_instance_default_context(instance))
|
|
1158
|
+
|
|
1034
1159
|
# after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
|
|
1035
1160
|
extra_headers: Optional[dict[str, str]] = kwargs.get("extra_headers")
|
|
1036
1161
|
extra_headers = (extra_headers or {}).copy()
|
|
@@ -1046,12 +1171,16 @@ class _PayiInstrumentor:
|
|
|
1046
1171
|
self._logger.debug(f"async_invoke_wrapper: sending proxy request")
|
|
1047
1172
|
|
|
1048
1173
|
return await wrapped(*args, **kwargs)
|
|
1174
|
+
|
|
1175
|
+
request._price_as = self._extract_price_as(extra_headers)
|
|
1176
|
+
if not request.supports_extra_headers and "extra_headers" in kwargs:
|
|
1177
|
+
kwargs.pop("extra_headers", None)
|
|
1049
1178
|
|
|
1050
1179
|
current_frame = inspect.currentframe()
|
|
1051
1180
|
# f_back excludes the current frame, strip() cleans up whitespace and newlines
|
|
1052
1181
|
stack = [frame.strip() for frame in traceback.format_stack(current_frame.f_back)] # type: ignore
|
|
1053
1182
|
|
|
1054
|
-
request._ingest['properties'] = { 'system.stack_trace':
|
|
1183
|
+
request._ingest['properties'] = { 'system.stack_trace': _compact_json(stack) }
|
|
1055
1184
|
|
|
1056
1185
|
if request.process_request(instance, extra_headers, args, kwargs) is False:
|
|
1057
1186
|
self._logger.debug(f"async_invoke_wrapper: calling wrapped instance")
|
|
@@ -1068,7 +1197,7 @@ class _PayiInstrumentor:
|
|
|
1068
1197
|
stream = False
|
|
1069
1198
|
|
|
1070
1199
|
try:
|
|
1071
|
-
self.
|
|
1200
|
+
self._before_invoke_update_request(request, extra_headers, args, kwargs)
|
|
1072
1201
|
self._logger.debug(f"async_invoke_wrapper: calling wrapped instance (stream={stream})")
|
|
1073
1202
|
|
|
1074
1203
|
if "extra_headers" in kwargs:
|
|
@@ -1146,7 +1275,7 @@ class _PayiInstrumentor:
|
|
|
1146
1275
|
) -> Any:
|
|
1147
1276
|
self._logger.debug(f"invoke_wrapper: instance {instance}, category {request._category}")
|
|
1148
1277
|
|
|
1149
|
-
context = self.
|
|
1278
|
+
context = self._context
|
|
1150
1279
|
|
|
1151
1280
|
if not context:
|
|
1152
1281
|
if not request.supports_extra_headers:
|
|
@@ -1157,6 +1286,8 @@ class _PayiInstrumentor:
|
|
|
1157
1286
|
# wrapped function invoked outside of decorator scope
|
|
1158
1287
|
return wrapped(*args, **kwargs)
|
|
1159
1288
|
|
|
1289
|
+
# context = self._merge_context_instance_defaults(context, self._get_instance_default_context(instance))
|
|
1290
|
+
|
|
1160
1291
|
# after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
|
|
1161
1292
|
extra_headers: Optional[dict[str, str]] = kwargs.get("extra_headers")
|
|
1162
1293
|
extra_headers = (extra_headers or {}).copy()
|
|
@@ -1172,12 +1303,16 @@ class _PayiInstrumentor:
|
|
|
1172
1303
|
self._logger.debug(f"invoke_wrapper: sending proxy request")
|
|
1173
1304
|
|
|
1174
1305
|
return wrapped(*args, **kwargs)
|
|
1306
|
+
|
|
1307
|
+
request._price_as = self._extract_price_as(extra_headers)
|
|
1308
|
+
if not request.supports_extra_headers and "extra_headers" in kwargs:
|
|
1309
|
+
kwargs.pop("extra_headers", None)
|
|
1175
1310
|
|
|
1176
1311
|
current_frame = inspect.currentframe()
|
|
1177
1312
|
# f_back excludes the current frame, strip() cleans up whitespace and newlines
|
|
1178
1313
|
stack = [frame.strip() for frame in traceback.format_stack(current_frame.f_back)] # type: ignore
|
|
1179
1314
|
|
|
1180
|
-
request._ingest['properties'] = { 'system.stack_trace':
|
|
1315
|
+
request._ingest['properties'] = { 'system.stack_trace': _compact_json(stack) }
|
|
1181
1316
|
|
|
1182
1317
|
if request.process_request(instance, extra_headers, args, kwargs) is False:
|
|
1183
1318
|
self._logger.debug(f"invoke_wrapper: calling wrapped instance")
|
|
@@ -1194,7 +1329,7 @@ class _PayiInstrumentor:
|
|
|
1194
1329
|
stream = False
|
|
1195
1330
|
|
|
1196
1331
|
try:
|
|
1197
|
-
self.
|
|
1332
|
+
self._before_invoke_update_request(request, extra_headers, args, kwargs)
|
|
1198
1333
|
self._logger.debug(f"invoke_wrapper: calling wrapped instance (stream={stream})")
|
|
1199
1334
|
|
|
1200
1335
|
if "extra_headers" in kwargs:
|
|
@@ -1274,7 +1409,7 @@ class _PayiInstrumentor:
|
|
|
1274
1409
|
self
|
|
1275
1410
|
) -> 'dict[str, str]':
|
|
1276
1411
|
extra_headers: dict[str, str] = {}
|
|
1277
|
-
context = self.
|
|
1412
|
+
context = self._context
|
|
1278
1413
|
if context:
|
|
1279
1414
|
self._update_extra_headers(context, extra_headers)
|
|
1280
1415
|
|
|
@@ -1303,13 +1438,38 @@ class _PayiInstrumentor:
|
|
|
1303
1438
|
context_price_as_resource: Optional[str] = context.get("price_as_resource")
|
|
1304
1439
|
context_resource_scope: Optional[str] = context.get("resource_scope")
|
|
1305
1440
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1441
|
+
context_request_properties: Optional[dict[str, Optional[str]]] = context.get("request_properties")
|
|
1442
|
+
context_use_case_properties: Optional[dict[str, Optional[str]]] = context.get("use_case_properties")
|
|
1443
|
+
|
|
1444
|
+
if PayiHeaderNames.request_properties in extra_headers:
|
|
1445
|
+
headers_request_properties = extra_headers.get(PayiHeaderNames.request_properties, None)
|
|
1446
|
+
|
|
1447
|
+
if not headers_request_properties:
|
|
1448
|
+
# headers_request_properties is empty, remove it from extra_headers
|
|
1449
|
+
extra_headers.pop(PayiHeaderNames.request_properties, None)
|
|
1450
|
+
else:
|
|
1451
|
+
# leave the value in extra_headers
|
|
1452
|
+
...
|
|
1453
|
+
elif context_request_properties:
|
|
1454
|
+
extra_headers[PayiHeaderNames.request_properties] = _compact_json(context_request_properties)
|
|
1455
|
+
|
|
1456
|
+
if PayiHeaderNames.use_case_properties in extra_headers:
|
|
1457
|
+
headers_use_case_properties = extra_headers.get(PayiHeaderNames.use_case_properties, None)
|
|
1458
|
+
|
|
1459
|
+
if not headers_use_case_properties:
|
|
1460
|
+
# headers_use_case_properties is empty, remove it from extra_headers
|
|
1461
|
+
extra_headers.pop(PayiHeaderNames.use_case_properties, None)
|
|
1462
|
+
else:
|
|
1463
|
+
# leave the value in extra_headers
|
|
1464
|
+
...
|
|
1465
|
+
elif context_use_case_properties:
|
|
1466
|
+
extra_headers[PayiHeaderNames.use_case_properties] = _compact_json(context_use_case_properties)
|
|
1467
|
+
|
|
1308
1468
|
# If the caller specifies limit_ids in extra_headers, it takes precedence over the decorator
|
|
1309
1469
|
if PayiHeaderNames.limit_ids in extra_headers:
|
|
1310
1470
|
headers_limit_ids = extra_headers.get(PayiHeaderNames.limit_ids)
|
|
1311
1471
|
|
|
1312
|
-
if
|
|
1472
|
+
if not headers_limit_ids:
|
|
1313
1473
|
# headers_limit_ids is empty, remove it from extra_headers
|
|
1314
1474
|
extra_headers.pop(PayiHeaderNames.limit_ids, None)
|
|
1315
1475
|
else:
|
|
@@ -1320,7 +1480,7 @@ class _PayiInstrumentor:
|
|
|
1320
1480
|
|
|
1321
1481
|
if PayiHeaderNames.user_id in extra_headers:
|
|
1322
1482
|
headers_user_id = extra_headers.get(PayiHeaderNames.user_id, None)
|
|
1323
|
-
if
|
|
1483
|
+
if not headers_user_id:
|
|
1324
1484
|
# headers_user_id is empty, remove it from extra_headers
|
|
1325
1485
|
extra_headers.pop(PayiHeaderNames.user_id, None)
|
|
1326
1486
|
else:
|
|
@@ -1331,7 +1491,7 @@ class _PayiInstrumentor:
|
|
|
1331
1491
|
|
|
1332
1492
|
if PayiHeaderNames.account_name in extra_headers:
|
|
1333
1493
|
headers_account_name = extra_headers.get(PayiHeaderNames.account_name, None)
|
|
1334
|
-
if
|
|
1494
|
+
if not headers_account_name:
|
|
1335
1495
|
# headers_account_name is empty, remove it from extra_headers
|
|
1336
1496
|
extra_headers.pop(PayiHeaderNames.account_name, None)
|
|
1337
1497
|
else:
|
|
@@ -1342,7 +1502,7 @@ class _PayiInstrumentor:
|
|
|
1342
1502
|
|
|
1343
1503
|
if PayiHeaderNames.use_case_name in extra_headers:
|
|
1344
1504
|
headers_use_case_name = extra_headers.get(PayiHeaderNames.use_case_name, None)
|
|
1345
|
-
if
|
|
1505
|
+
if not headers_use_case_name:
|
|
1346
1506
|
# headers_use_case_name is empty, remove all use case related headers
|
|
1347
1507
|
extra_headers.pop(PayiHeaderNames.use_case_name, None)
|
|
1348
1508
|
extra_headers.pop(PayiHeaderNames.use_case_id, None)
|
|
@@ -1604,7 +1764,7 @@ class _StreamIteratorWrapper(ObjectProxy): # type: ignore
|
|
|
1604
1764
|
return chunk
|
|
1605
1765
|
else:
|
|
1606
1766
|
# assume dict
|
|
1607
|
-
return
|
|
1767
|
+
return _compact_json(chunk)
|
|
1608
1768
|
|
|
1609
1769
|
class _StreamManagerWrapper(ObjectProxy): # type: ignore
|
|
1610
1770
|
def __init__(
|
|
@@ -1678,7 +1838,7 @@ class _GeneratorWrapper: # type: ignore
|
|
|
1678
1838
|
|
|
1679
1839
|
if self._log_prompt_and_response:
|
|
1680
1840
|
dict = self._chunk_to_dict(chunk)
|
|
1681
|
-
self._responses.append(
|
|
1841
|
+
self._responses.append(_compact_json(dict))
|
|
1682
1842
|
|
|
1683
1843
|
return self._request.process_chunk(chunk)
|
|
1684
1844
|
|
|
@@ -1916,7 +2076,7 @@ def get_context() -> PayiContext:
|
|
|
1916
2076
|
"""
|
|
1917
2077
|
if not _instrumentor:
|
|
1918
2078
|
return PayiContext()
|
|
1919
|
-
internal_context = _instrumentor.
|
|
2079
|
+
internal_context = _instrumentor._context_safe
|
|
1920
2080
|
|
|
1921
2081
|
context_dict = {
|
|
1922
2082
|
key: value
|
|
@@ -1925,4 +2085,13 @@ def get_context() -> PayiContext:
|
|
|
1925
2085
|
}
|
|
1926
2086
|
if _instrumentor._last_result:
|
|
1927
2087
|
context_dict["last_result"] = _instrumentor._last_result
|
|
1928
|
-
return PayiContext(**dict(context_dict)) # type: ignore
|
|
2088
|
+
return PayiContext(**dict(context_dict)) # type: ignore
|
|
2089
|
+
|
|
2090
|
+
# def payi_set_default_context(
|
|
2091
|
+
# instance: Any,
|
|
2092
|
+
# context: PayiInstanceDefaultContext
|
|
2093
|
+
# ) -> None:
|
|
2094
|
+
# if not _instrumentor:
|
|
2095
|
+
# raise RuntimeError("payi_instrument() must be called before using payi_add_client_default_context()")
|
|
2096
|
+
|
|
2097
|
+
# _instrumentor._set_instance_default_context(instance, context)
|
|
@@ -11,7 +11,7 @@ payi/_resource.py,sha256=j2jIkTr8OIC8sU6-05nxSaCyj4MaFlbZrwlyg4_xJos,1088
|
|
|
11
11
|
payi/_response.py,sha256=rh9oJAvCKcPwQFm4iqH_iVrmK8bNx--YP_A2a4kN1OU,28776
|
|
12
12
|
payi/_streaming.py,sha256=r-qd1jFI1Df33CEelGocyNFdGg4C_NAK8TEX9GmxdOM,10141
|
|
13
13
|
payi/_types.py,sha256=d6xrZDG6rG6opphTN7UVYdEOis3977LrQQgpNtklXZE,7234
|
|
14
|
-
payi/_version.py,sha256=
|
|
14
|
+
payi/_version.py,sha256=PfQ5Xib4CNlYWPqPk3ILAQTZxnM8F2pDXmLoDhpmUCQ,166
|
|
15
15
|
payi/pagination.py,sha256=k2356QGPOUSjRF2vHpwLBdF6P-2vnQzFfRIJQAHGQ7A,1258
|
|
16
16
|
payi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
payi/_utils/__init__.py,sha256=7fch0GT9zpNnErbciSpUNa-SjTxxjY6kxHxKMOM4AGs,2305
|
|
@@ -27,15 +27,15 @@ payi/_utils/_transform.py,sha256=NjCzmnfqYrsAikUHQig6N9QfuTVbKipuP3ur9mcNF-E,159
|
|
|
27
27
|
payi/_utils/_typing.py,sha256=N_5PPuFNsaygbtA_npZd98SVN1LQQvFTKL6bkWPBZGU,4786
|
|
28
28
|
payi/_utils/_utils.py,sha256=0dDqauUbVZEXV0NVl7Bwu904Wwo5eyFCZpQThhFNhyA,12253
|
|
29
29
|
payi/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
|
|
30
|
-
payi/lib/AnthropicInstrumentor.py,sha256=
|
|
31
|
-
payi/lib/BedrockInstrumentor.py,sha256
|
|
30
|
+
payi/lib/AnthropicInstrumentor.py,sha256=AyVwflcprDt7ah0fO7gtQjcwLiElxX_W-GnL6e86K7g,16971
|
|
31
|
+
payi/lib/BedrockInstrumentor.py,sha256=wc3R838Sp1Gr5GTe_S1egx8rG6kHllVZsnPZhX2sSYQ,29464
|
|
32
32
|
payi/lib/GoogleGenAiInstrumentor.py,sha256=LHiEZ7G5IhCcDlpVzQlXW9Ok96MHLeq7emEhFzPBTm0,8836
|
|
33
|
-
payi/lib/OpenAIInstrumentor.py,sha256=
|
|
33
|
+
payi/lib/OpenAIInstrumentor.py,sha256=3rR7EndaUXZrT1HSzfhAWIVjX2ha172un56HOqBsQf0,23183
|
|
34
34
|
payi/lib/Stopwatch.py,sha256=7OJlxvr2Jyb6Zr1LYCYKczRB7rDVKkIR7gc4YoleNdE,764
|
|
35
35
|
payi/lib/VertexInstrumentor.py,sha256=OWuMPiW4LdLhj6DSAAy5qZiosVo8DSAuFWGxYpEucoE,7431
|
|
36
36
|
payi/lib/VertexRequest.py,sha256=42F7xCRYY6h3EMUZD1x4-_cwyAcVhnzT9M5zl4KwtE0,11801
|
|
37
|
-
payi/lib/helpers.py,sha256=
|
|
38
|
-
payi/lib/instrument.py,sha256=
|
|
37
|
+
payi/lib/helpers.py,sha256=kqKXVdHlTcK_OQgIwuDXKbtbmezDMxTv7Ax7O0oSTgk,5658
|
|
38
|
+
payi/lib/instrument.py,sha256=HhSQpAEwlEDwPhpZWZVTq2xHdZgy12W255DMlpkl24Y,85586
|
|
39
39
|
payi/lib/version_helper.py,sha256=v0lC3kuaXn6PBDolE3mkmwJiA8Ot3z4RkVR7wlBuZCs,540
|
|
40
40
|
payi/lib/data/cohere_embed_english_v3.json,sha256=YEWwjml3_i16cdsOx_7UKe6xpVFnxTEhP8T1n54R6gY,718306
|
|
41
41
|
payi/resources/__init__.py,sha256=B2bn1ZfCf6TbHlzZvy5TpFPtALnFcBRPYVKQH3S5qfQ,2457
|
|
@@ -135,7 +135,7 @@ payi/types/use_cases/definitions/kpi_retrieve_response.py,sha256=uQXliSvS3k-yDYw
|
|
|
135
135
|
payi/types/use_cases/definitions/kpi_update_params.py,sha256=jbawdWAdMnsTWVH0qfQGb8W7_TXe3lq4zjSRu44d8p8,373
|
|
136
136
|
payi/types/use_cases/definitions/kpi_update_response.py,sha256=zLyEoT0S8d7XHsnXZYT8tM7yDw0Aze0Mk-_Z6QeMtc8,459
|
|
137
137
|
payi/types/use_cases/definitions/limit_config_create_params.py,sha256=Y6RR7IYiTZHYJ4y6m2TmlAe4ArtbjEl7fIrAhnCvuPI,464
|
|
138
|
-
payi-0.1.
|
|
139
|
-
payi-0.1.
|
|
140
|
-
payi-0.1.
|
|
141
|
-
payi-0.1.
|
|
138
|
+
payi-0.1.0a134.dist-info/METADATA,sha256=Xgtk_iCsvrSL62RxLuRrqgOesuMmKsHpEgpBcWQWy6U,16324
|
|
139
|
+
payi-0.1.0a134.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
140
|
+
payi-0.1.0a134.dist-info/licenses/LICENSE,sha256=CQt03aM-P4a3Yg5qBg3JSLVoQS3smMyvx7tYg_6V7Gk,11334
|
|
141
|
+
payi-0.1.0a134.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|