payi 0.1.0a110__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 +53 -1
- payi/lib/instrument.py +404 -668
- 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.0a110.dist-info → payi-0.1.0a137.dist-info}/METADATA +6 -6
- {payi-0.1.0a110.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.0a110.dist-info → payi-0.1.0a137.dist-info}/WHEEL +0 -0
- {payi-0.1.0a110.dist-info → payi-0.1.0a137.dist-info}/licenses/LICENSE +0 -0
payi/lib/OpenAIInstrumentor.py
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
|
-
from typing import Any, Union, Optional, Sequence
|
|
4
|
+
from typing import Any, Dict, Union, Optional, Sequence
|
|
3
5
|
from typing_extensions import override
|
|
4
6
|
from importlib.metadata import version
|
|
5
7
|
|
|
6
8
|
import tiktoken # type: ignore
|
|
7
9
|
from wrapt import wrap_function_wrapper # type: ignore
|
|
8
10
|
|
|
9
|
-
from payi.lib.helpers import PayiCategories
|
|
11
|
+
from payi.lib.helpers import PayiCategories
|
|
10
12
|
from payi.types.ingest_units_params import Units
|
|
11
13
|
|
|
12
|
-
from .instrument import
|
|
14
|
+
from .instrument import (
|
|
15
|
+
PayiInstrumentAzureOpenAiConfig,
|
|
16
|
+
_Context,
|
|
17
|
+
_IsStreaming,
|
|
18
|
+
_PayiInstrumentor,
|
|
19
|
+
)
|
|
13
20
|
from .version_helper import get_version_helper
|
|
21
|
+
from .ProviderRequest import _ChunkResult, _StreamingType, _ProviderRequest
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
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
|
|
@@ -24,52 +34,31 @@ class OpenAiInstrumentor:
|
|
|
24
34
|
return isinstance(instance._client, (AsyncAzureOpenAI, AzureOpenAI))
|
|
25
35
|
|
|
26
36
|
@staticmethod
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
wrap_function_wrapper(
|
|
32
|
-
"openai.resources.chat.completions",
|
|
33
|
-
"Completions.create",
|
|
34
|
-
chat_wrapper(instrumentor),
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
wrap_function_wrapper(
|
|
38
|
-
"openai.resources.chat.completions",
|
|
39
|
-
"AsyncCompletions.create",
|
|
40
|
-
achat_wrapper(instrumentor),
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
wrap_function_wrapper(
|
|
44
|
-
"openai.resources.embeddings",
|
|
45
|
-
"Embeddings.create",
|
|
46
|
-
embeddings_wrapper(instrumentor),
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
wrap_function_wrapper(
|
|
50
|
-
"openai.resources.embeddings",
|
|
51
|
-
"AsyncEmbeddings.create",
|
|
52
|
-
aembeddings_wrapper(instrumentor),
|
|
53
|
-
)
|
|
54
|
-
except Exception as e:
|
|
55
|
-
instrumentor._logger.debug(f"Error instrumenting openai: {e}")
|
|
56
|
-
|
|
57
|
-
# responses separately as they are relatively new and the client may not be using the latest openai module
|
|
58
|
-
try:
|
|
59
|
-
wrap_function_wrapper(
|
|
60
|
-
"openai.resources.responses",
|
|
61
|
-
"Responses.create",
|
|
62
|
-
responses_wrapper(instrumentor),
|
|
63
|
-
)
|
|
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)
|
|
64
41
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
42
|
+
@staticmethod
|
|
43
|
+
def instrument(instrumentor: _PayiInstrumentor) -> None:
|
|
44
|
+
OpenAiInstrumentor._module_version = get_version_helper(OpenAiInstrumentor._module_name)
|
|
45
|
+
|
|
46
|
+
wrappers = [
|
|
47
|
+
("openai._base_client", "AsyncAPIClient._process_response", _ProviderRequest.aprocess_response_wrapper),
|
|
48
|
+
("openai._base_client", "SyncAPIClient._process_response", _ProviderRequest.process_response_wrapper),
|
|
49
|
+
("openai.resources.chat.completions", "Completions.create", chat_wrapper(instrumentor)),
|
|
50
|
+
("openai.resources.chat.completions", "AsyncCompletions.create", achat_wrapper(instrumentor)),
|
|
51
|
+
("openai.resources.embeddings", "Embeddings.create", embeddings_wrapper(instrumentor)),
|
|
52
|
+
("openai.resources.embeddings", "AsyncEmbeddings.create", aembeddings_wrapper(instrumentor)),
|
|
53
|
+
("openai.resources.responses", "Responses.create", responses_wrapper(instrumentor)),
|
|
54
|
+
("openai.resources.responses", "AsyncResponses.create", aresponses_wrapper(instrumentor)),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
for module, method, wrapper in wrappers:
|
|
58
|
+
try:
|
|
59
|
+
wrap_function_wrapper(module, method, wrapper)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
instrumentor._logger.debug(f"Error wrapping {module}.{method}: {e}")
|
|
73
62
|
|
|
74
63
|
@_PayiInstrumentor.payi_wrapper
|
|
75
64
|
def embeddings_wrapper(
|
|
@@ -201,44 +190,39 @@ class _OpenAiProviderRequest(_ProviderRequest):
|
|
|
201
190
|
self._input_tokens_details_key = input_tokens_details_key
|
|
202
191
|
|
|
203
192
|
@override
|
|
204
|
-
def process_request(self, instance: Any, extra_headers: 'dict[str, str]',
|
|
205
|
-
|
|
193
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool: # type: ignore
|
|
194
|
+
model = kwargs.get("model", "")
|
|
206
195
|
|
|
207
196
|
if not (instance and hasattr(instance, "_client")) or OpenAiInstrumentor.is_azure(instance) is False:
|
|
197
|
+
self._ingest["resource"] = model
|
|
208
198
|
return True
|
|
209
199
|
|
|
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:
|
|
200
|
+
if not self._price_as.resource and not self._price_as.category and OpenAiInstrumentor._azure_openai_deployments:
|
|
201
|
+
deployment = OpenAiInstrumentor._azure_openai_deployments.get(model, {})
|
|
202
|
+
self._price_as.category = deployment.get("price_as_category", None)
|
|
203
|
+
self._price_as.resource = deployment.get("price_as_resource", None)
|
|
204
|
+
self._price_as.resource_scope = deployment.get("resource_scope", None)
|
|
205
|
+
|
|
206
|
+
if not self._price_as.resource and not self._price_as.category:
|
|
223
207
|
self._instrumentor._logger.error("Azure OpenAI requires price as resource and/or category to be specified, not ingesting")
|
|
224
208
|
return False
|
|
225
209
|
|
|
226
|
-
if resource_scope:
|
|
227
|
-
if not(resource_scope in ["global", "datazone"] or resource_scope.startswith("region")):
|
|
210
|
+
if self._price_as.resource_scope:
|
|
211
|
+
if not (self._price_as.resource_scope in ["global", "datazone"] or self._price_as.resource_scope.startswith("region")):
|
|
228
212
|
self._instrumentor._logger.error("Azure OpenAI invalid resource scope, not ingesting")
|
|
229
213
|
return False
|
|
230
214
|
|
|
231
|
-
self._ingest["resource_scope"] = resource_scope
|
|
215
|
+
self._ingest["resource_scope"] = self._price_as.resource_scope
|
|
232
216
|
|
|
233
217
|
self._category = PayiCategories.azure_openai
|
|
234
218
|
|
|
235
219
|
self._ingest["category"] = self._category
|
|
236
220
|
|
|
237
|
-
if
|
|
221
|
+
if self._price_as.category:
|
|
238
222
|
# price as category overrides default
|
|
239
|
-
self._ingest["category"] =
|
|
240
|
-
if
|
|
241
|
-
self._ingest["resource"] =
|
|
223
|
+
self._ingest["category"] = self._price_as.category
|
|
224
|
+
if self._price_as.resource:
|
|
225
|
+
self._ingest["resource"] = self._price_as.resource
|
|
242
226
|
|
|
243
227
|
return True
|
|
244
228
|
|
|
@@ -304,7 +288,7 @@ class _OpenAiProviderRequest(_ProviderRequest):
|
|
|
304
288
|
if input_cache != 0:
|
|
305
289
|
units["text_cache_read"] = Units(input=input_cache, output=0)
|
|
306
290
|
|
|
307
|
-
input =
|
|
291
|
+
input = self.update_for_vision(input - input_cache)
|
|
308
292
|
|
|
309
293
|
units["text"] = Units(input=input, output=output)
|
|
310
294
|
|
|
@@ -614,4 +598,4 @@ def model_to_dict(model: Any) -> Any:
|
|
|
614
598
|
elif hasattr(model, "parse"): # Raw API response
|
|
615
599
|
return model_to_dict(model.parse())
|
|
616
600
|
else:
|
|
617
|
-
return model
|
|
601
|
+
return model
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Optional, Sequence
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from payi.types import IngestUnitsParams
|
|
10
|
+
from payi.lib.helpers import PayiPropertyNames
|
|
11
|
+
from payi.types.ingest_units_params import ProviderResponseFunctionCall
|
|
12
|
+
from payi.types.shared.xproxy_error import XproxyError
|
|
13
|
+
from payi.types.shared.xproxy_result import XproxyResult
|
|
14
|
+
from payi.types.shared_params.ingest_units import IngestUnits
|
|
15
|
+
from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
|
|
16
|
+
|
|
17
|
+
from .helpers import _set_attr_safe
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .instrument import _PayiInstrumentor
|
|
21
|
+
|
|
22
|
+
class _StreamingType(Enum):
|
|
23
|
+
generator = 0
|
|
24
|
+
iterator = 1
|
|
25
|
+
stream_manager = 2
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class _ChunkResult:
|
|
29
|
+
send_chunk_to_caller: bool
|
|
30
|
+
ingest: bool = False
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class PriceAs:
|
|
34
|
+
category: Optional[str]
|
|
35
|
+
resource: Optional[str]
|
|
36
|
+
resource_scope: Optional[str]
|
|
37
|
+
|
|
38
|
+
class _ProviderRequest:
|
|
39
|
+
excluded_headers = {
|
|
40
|
+
"transfer-encoding",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_instrumented_response_headers_attr = "_instrumented_response_headers"
|
|
44
|
+
_xproxy_result_attr = "xproxy_result"
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
instrumentor: _PayiInstrumentor,
|
|
49
|
+
category: str,
|
|
50
|
+
streaming_type: _StreamingType,
|
|
51
|
+
module_name: str,
|
|
52
|
+
module_version: str,
|
|
53
|
+
is_aws_client: Optional[bool] = None,
|
|
54
|
+
is_google_vertex_or_genai_client: Optional[bool] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
self._instrumentor: _PayiInstrumentor = instrumentor
|
|
57
|
+
self._module_name: str = module_name
|
|
58
|
+
self._module_version: str = module_version
|
|
59
|
+
self._estimated_prompt_tokens: Optional[int] = None
|
|
60
|
+
self._category: str = category
|
|
61
|
+
self._ingest: IngestUnitsParams = { "category": category, "units": {} } # type: ignore
|
|
62
|
+
self._streaming_type: '_StreamingType' = streaming_type
|
|
63
|
+
self._is_aws_client: Optional[bool] = is_aws_client
|
|
64
|
+
self._is_google_vertex_or_genai_client: Optional[bool] = is_google_vertex_or_genai_client
|
|
65
|
+
self._function_call_builder: Optional[dict[int, ProviderResponseFunctionCall]] = None
|
|
66
|
+
self._building_function_response: bool = False
|
|
67
|
+
self._function_calls: Optional[list[ProviderResponseFunctionCall]] = None
|
|
68
|
+
self._is_large_context: bool = False
|
|
69
|
+
self._internal_request_properties: dict[str, Optional[str]] = {}
|
|
70
|
+
self._price_as: PriceAs = PriceAs(category=None, resource=None, resource_scope=None)
|
|
71
|
+
|
|
72
|
+
def process_chunk(self, _chunk: Any) -> _ChunkResult:
|
|
73
|
+
return _ChunkResult(send_chunk_to_caller=True)
|
|
74
|
+
|
|
75
|
+
def process_synchronous_response(self, response: Any, log_prompt_and_response: bool, kwargs: Any) -> Optional[object]: # noqa: ARG002
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
def process_request_prompt(self, prompt: 'dict[str, Any]', args: Sequence[Any], kwargs: 'dict[str, Any]') -> None:
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
def process_initial_stream_response(self, response: Any) -> None:
|
|
86
|
+
self.add_instrumented_response_headers(response)
|
|
87
|
+
|
|
88
|
+
def remove_inline_data(self, prompt: 'dict[str, Any]') -> bool:# noqa: ARG002
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def is_aws_client(self) -> bool:
|
|
93
|
+
return self._is_aws_client if self._is_aws_client is not None else False
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def is_google_vertex_or_genai_client(self) -> bool:
|
|
97
|
+
return self._is_google_vertex_or_genai_client if self._is_google_vertex_or_genai_client is not None else False
|
|
98
|
+
|
|
99
|
+
def process_exception(self, exception: Exception, kwargs: Any, ) -> bool: # noqa: ARG002
|
|
100
|
+
self.exception_to_semantic_failure(exception)
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def supports_extra_headers(self) -> bool:
|
|
105
|
+
return not self.is_aws_client and not self.is_google_vertex_or_genai_client
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def streaming_type(self) -> '_StreamingType':
|
|
109
|
+
return self._streaming_type
|
|
110
|
+
|
|
111
|
+
def add_internal_request_property(self, key: str, value: str) -> None:
|
|
112
|
+
self._internal_request_properties[key] = value
|
|
113
|
+
|
|
114
|
+
def exception_to_semantic_failure(self, e: Exception) -> None:
|
|
115
|
+
exception_str = f"{type(e).__name__}"
|
|
116
|
+
|
|
117
|
+
fields: list[str] = []
|
|
118
|
+
|
|
119
|
+
for attr in dir(e):
|
|
120
|
+
if not attr.startswith("__"):
|
|
121
|
+
try:
|
|
122
|
+
value = getattr(e, attr)
|
|
123
|
+
if value and not inspect.ismethod(value) and not inspect.isfunction(value) and not callable(value):
|
|
124
|
+
fields.append(f"{attr}={value}")
|
|
125
|
+
except Exception as _ex:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
self.add_internal_request_property(PayiPropertyNames.failure, exception_str)
|
|
129
|
+
if fields:
|
|
130
|
+
failure_description = ",".join(fields)
|
|
131
|
+
self.add_internal_request_property(PayiPropertyNames.failure_description, failure_description)
|
|
132
|
+
|
|
133
|
+
if "http_status_code" not in self._ingest:
|
|
134
|
+
# use a non existent http status code so when presented to the user, the origin is clear
|
|
135
|
+
self._ingest["http_status_code"] = 299
|
|
136
|
+
|
|
137
|
+
def add_streaming_function_call(self, index: int, name: Optional[str], arguments: Optional[str]) -> None:
|
|
138
|
+
if not self._function_call_builder:
|
|
139
|
+
self._function_call_builder = {}
|
|
140
|
+
|
|
141
|
+
if not index in self._function_call_builder:
|
|
142
|
+
self._function_call_builder[index] = ProviderResponseFunctionCall(name=name or "", arguments=arguments or "")
|
|
143
|
+
else:
|
|
144
|
+
function = self._function_call_builder[index]
|
|
145
|
+
if name:
|
|
146
|
+
function["name"] = function["name"] + name
|
|
147
|
+
if arguments:
|
|
148
|
+
function["arguments"] = (function.get("arguments", "") or "") + arguments
|
|
149
|
+
|
|
150
|
+
def add_synchronous_function_call(self, name: str, arguments: Optional[str]) -> None:
|
|
151
|
+
if not self._function_calls:
|
|
152
|
+
self._function_calls = []
|
|
153
|
+
self._ingest["provider_response_function_calls"] = self._function_calls
|
|
154
|
+
self._function_calls.append(ProviderResponseFunctionCall(name=name, arguments=arguments))
|
|
155
|
+
|
|
156
|
+
def add_instrumented_response_headers(self, response: Any) -> None:
|
|
157
|
+
response_headers = getattr(response, _ProviderRequest._instrumented_response_headers_attr, {})
|
|
158
|
+
if response_headers:
|
|
159
|
+
self.add_response_headers(response_headers)
|
|
160
|
+
|
|
161
|
+
def add_response_headers(self, response_headers: 'dict[str, Any]') -> None:
|
|
162
|
+
self._ingest["provider_response_headers"] = [
|
|
163
|
+
PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v)
|
|
164
|
+
for k, v in response_headers.items()
|
|
165
|
+
if (k_lower := k.lower()) not in _ProviderRequest.excluded_headers and not k_lower.startswith("content-")
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
def merge_internal_request_properties(self) -> None:
|
|
169
|
+
if not self._internal_request_properties:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
properties = self._ingest.get("properties") or {}
|
|
173
|
+
self._ingest["properties"] = properties
|
|
174
|
+
for key, value in self._internal_request_properties.items():
|
|
175
|
+
if key not in properties:
|
|
176
|
+
properties[key] = value
|
|
177
|
+
|
|
178
|
+
def update_for_vision(self, input: int) -> int:
|
|
179
|
+
if self._estimated_prompt_tokens:
|
|
180
|
+
vision = input - self._estimated_prompt_tokens
|
|
181
|
+
if (vision > 0):
|
|
182
|
+
key = "vision_large_context" if self._is_large_context else "vision"
|
|
183
|
+
self._ingest["units"][key] = IngestUnits(input=vision, output=0)
|
|
184
|
+
input = self._estimated_prompt_tokens
|
|
185
|
+
|
|
186
|
+
return input
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def assign_xproxy_result(o: Any, xproxy_result: XproxyResult | XproxyError| None) -> None:
|
|
190
|
+
if xproxy_result:
|
|
191
|
+
_set_attr_safe(o, _ProviderRequest._xproxy_result_attr, xproxy_result)
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def process_response_wrapper(wrapped: Any, _instance: Any, args: Any, kwargs: Any) -> Any:
|
|
195
|
+
httpResponse = kwargs.get("response", None)
|
|
196
|
+
|
|
197
|
+
r = wrapped(*args, **kwargs)
|
|
198
|
+
|
|
199
|
+
if httpResponse:
|
|
200
|
+
headers = getattr(httpResponse, "headers", None)
|
|
201
|
+
_set_attr_safe(r, _ProviderRequest._instrumented_response_headers_attr, dict(headers) if headers else {})
|
|
202
|
+
|
|
203
|
+
return r
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
async def aprocess_response_wrapper(wrapped: Any, _instance: Any, args: Any, kwargs: Any) -> Any:
|
|
207
|
+
httpResponse = kwargs.get("response", None)
|
|
208
|
+
|
|
209
|
+
r = await wrapped(*args, **kwargs)
|
|
210
|
+
|
|
211
|
+
if httpResponse:
|
|
212
|
+
headers = getattr(httpResponse, "headers", None)
|
|
213
|
+
_set_attr_safe(r, _ProviderRequest._instrumented_response_headers_attr, dict(headers) if headers else {})
|
|
214
|
+
|
|
215
|
+
return r
|
|
216
|
+
|