payi 0.1.0a68__py3-none-any.whl → 0.1.0a69__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/_base_client.py +175 -239
- payi/_client.py +1 -4
- payi/_models.py +2 -2
- payi/_utils/_utils.py +9 -1
- payi/_version.py +1 -1
- payi/lib/AnthropicInstrumentor.py +52 -20
- payi/lib/BedrockInstrumentor.py +103 -25
- payi/lib/OpenAIInstrumentor.py +108 -36
- payi/lib/helpers.py +2 -1
- payi/lib/instrument.py +308 -167
- payi/resources/categories/resources.py +1 -4
- payi/resources/experiences/properties.py +1 -4
- payi/resources/experiences/types/limit_config.py +1 -4
- payi/resources/experiences/types/types.py +1 -4
- payi/resources/ingest.py +1 -5
- payi/resources/limits/limits.py +1 -4
- payi/resources/limits/tags.py +1 -4
- payi/resources/requests/properties.py +1 -4
- payi/resources/use_cases/definitions/definitions.py +1 -4
- payi/resources/use_cases/definitions/kpis.py +1 -4
- payi/resources/use_cases/definitions/limit_config.py +1 -4
- payi/resources/use_cases/kpis.py +1 -4
- payi/resources/use_cases/properties.py +1 -4
- {payi-0.1.0a68.dist-info → payi-0.1.0a69.dist-info}/METADATA +1 -1
- {payi-0.1.0a68.dist-info → payi-0.1.0a69.dist-info}/RECORD +27 -27
- {payi-0.1.0a68.dist-info → payi-0.1.0a69.dist-info}/WHEEL +0 -0
- {payi-0.1.0a68.dist-info → payi-0.1.0a69.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Any, Union
|
|
2
|
+
from typing import Any, Union, Optional
|
|
3
3
|
from typing_extensions import override
|
|
4
4
|
|
|
5
5
|
import tiktoken
|
|
@@ -54,7 +54,6 @@ def chat_wrapper(
|
|
|
54
54
|
**kwargs: Any,
|
|
55
55
|
) -> Any:
|
|
56
56
|
return instrumentor.chat_wrapper(
|
|
57
|
-
"system.anthropic",
|
|
58
57
|
_AnthropicProviderRequest(instrumentor),
|
|
59
58
|
_IsStreaming.kwargs,
|
|
60
59
|
wrapped,
|
|
@@ -72,7 +71,6 @@ async def achat_wrapper(
|
|
|
72
71
|
**kwargs: Any,
|
|
73
72
|
) -> Any:
|
|
74
73
|
return await instrumentor.achat_wrapper(
|
|
75
|
-
"system.anthropic",
|
|
76
74
|
_AnthropicProviderRequest(instrumentor),
|
|
77
75
|
_IsStreaming.kwargs,
|
|
78
76
|
wrapped,
|
|
@@ -82,6 +80,9 @@ async def achat_wrapper(
|
|
|
82
80
|
)
|
|
83
81
|
|
|
84
82
|
class _AnthropicProviderRequest(_ProviderRequest):
|
|
83
|
+
def __init__(self, instrumentor: _PayiInstrumentor):
|
|
84
|
+
super().__init__(instrumentor=instrumentor, category="system.anthropic")
|
|
85
|
+
|
|
85
86
|
@override
|
|
86
87
|
def process_chunk(self, chunk: Any) -> bool:
|
|
87
88
|
if chunk.type == "message_start":
|
|
@@ -135,25 +136,56 @@ class _AnthropicProviderRequest(_ProviderRequest):
|
|
|
135
136
|
return None
|
|
136
137
|
|
|
137
138
|
@override
|
|
138
|
-
def process_request(self, kwargs: Any) ->
|
|
139
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', kwargs: Any) -> bool:
|
|
140
|
+
self._ingest["resource"] = kwargs.get("model", "")
|
|
139
141
|
messages = kwargs.get("messages")
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
if messages:
|
|
143
|
+
estimated_token_count = 0
|
|
144
|
+
has_image = False
|
|
145
|
+
|
|
146
|
+
enc = tiktoken.get_encoding("cl100k_base")
|
|
147
|
+
|
|
148
|
+
for message in messages:
|
|
149
|
+
msg_has_image, msg_prompt_tokens = has_image_and_get_texts(enc, message.get('content', ''))
|
|
150
|
+
if msg_has_image:
|
|
151
|
+
has_image = True
|
|
152
|
+
estimated_token_count += msg_prompt_tokens
|
|
153
|
+
|
|
154
|
+
if has_image and estimated_token_count > 0:
|
|
155
|
+
self._estimated_prompt_tokens = estimated_token_count
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
@override
|
|
159
|
+
def process_exception(self, exception: Exception, kwargs: Any, ) -> bool:
|
|
160
|
+
try:
|
|
161
|
+
status_code: Optional[int] = None
|
|
162
|
+
|
|
163
|
+
if hasattr(exception, "status_code"):
|
|
164
|
+
status_code = getattr(exception, "status_code", None)
|
|
165
|
+
if isinstance(status_code, int):
|
|
166
|
+
self._ingest["http_status_code"] = status_code
|
|
167
|
+
|
|
168
|
+
if not status_code:
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
if hasattr(exception, "request_id"):
|
|
172
|
+
request_id = getattr(exception, "request_id", None)
|
|
173
|
+
if isinstance(request_id, str):
|
|
174
|
+
self._ingest["provider_response_id"] = request_id
|
|
175
|
+
|
|
176
|
+
if hasattr(exception, "response"):
|
|
177
|
+
response = getattr(exception, "response", None)
|
|
178
|
+
if hasattr(response, "text"):
|
|
179
|
+
text = getattr(response, "text", None)
|
|
180
|
+
if isinstance(text, str):
|
|
181
|
+
self._ingest["provider_response_json"] = text
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logging.debug(f"Error processing exception: {e}")
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
return True
|
|
145
188
|
|
|
146
|
-
enc = tiktoken.get_encoding("cl100k_base")
|
|
147
|
-
|
|
148
|
-
for message in messages:
|
|
149
|
-
msg_has_image, msg_prompt_tokens = has_image_and_get_texts(enc, message.get('content', ''))
|
|
150
|
-
if msg_has_image:
|
|
151
|
-
has_image = True
|
|
152
|
-
estimated_token_count += msg_prompt_tokens
|
|
153
|
-
|
|
154
|
-
if not has_image or estimated_token_count == 0:
|
|
155
|
-
return
|
|
156
|
-
self._estimated_prompt_tokens = estimated_token_count
|
|
157
189
|
|
|
158
190
|
def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'list[Any]']) -> 'tuple[bool, int]':
|
|
159
191
|
if isinstance(content, str):
|
payi/lib/BedrockInstrumentor.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
from typing import Any
|
|
@@ -6,15 +7,21 @@ from typing_extensions import override
|
|
|
6
7
|
|
|
7
8
|
from wrapt import ObjectProxy, wrap_function_wrapper # type: ignore
|
|
8
9
|
|
|
10
|
+
from payi.lib.helpers import PayiHeaderNames, payi_aws_bedrock_url
|
|
9
11
|
from payi.types.ingest_units_params import Units, IngestUnitsParams
|
|
10
12
|
from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
|
|
11
13
|
|
|
12
14
|
from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
|
|
13
15
|
|
|
16
|
+
_supported_model_prefixes = ["meta.llama3", "anthropic.", "amazon.nova-pro", "amazon.nova-lite", "amazon.nova-micro"]
|
|
14
17
|
|
|
15
18
|
class BedrockInstrumentor:
|
|
19
|
+
_instrumentor: _PayiInstrumentor
|
|
20
|
+
|
|
16
21
|
@staticmethod
|
|
17
22
|
def instrument(instrumentor: _PayiInstrumentor) -> None:
|
|
23
|
+
BedrockInstrumentor._instrumentor = instrumentor
|
|
24
|
+
|
|
18
25
|
try:
|
|
19
26
|
import boto3 # type: ignore # noqa: F401 I001
|
|
20
27
|
|
|
@@ -46,12 +53,48 @@ def create_client_wrapper(instrumentor: _PayiInstrumentor, wrapped: Any, instanc
|
|
|
46
53
|
client.converse = wrap_converse(instrumentor, client.converse)
|
|
47
54
|
client.converse_stream = wrap_converse_stream(instrumentor, client.converse_stream)
|
|
48
55
|
|
|
56
|
+
if BedrockInstrumentor._instrumentor._proxy_default:
|
|
57
|
+
# Register client callbacks to handle the Pay-i extra_headers parameter in the inference calls and redirect the request to the Pay-i endpoint
|
|
58
|
+
_register_bedrock_client_callbacks(client)
|
|
59
|
+
|
|
49
60
|
return client
|
|
50
61
|
except Exception as e:
|
|
51
62
|
logging.debug(f"Error instrumenting bedrock client: {e}")
|
|
52
63
|
|
|
53
64
|
return wrapped(*args, **kwargs)
|
|
65
|
+
|
|
66
|
+
BEDROCK_REQUEST_NAMES = [
|
|
67
|
+
'request-created.bedrock-runtime.Converse',
|
|
68
|
+
'request-created.bedrock-runtime.ConverseStream',
|
|
69
|
+
'request-created.bedrock-runtime.InvokeModel',
|
|
70
|
+
'request-created.bedrock-runtime.InvokeModelWithResponseStream',
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def _register_bedrock_client_callbacks(client: Any) -> None:
|
|
74
|
+
# Pass a unqiue_id to avoid registering the same callback multiple times in case this cell executed more than once
|
|
75
|
+
# Redirect the request to the Pay-i endpoint after the request has been signed.
|
|
76
|
+
client.meta.events.register_last('request-created', _redirect_to_payi, unique_id=_redirect_to_payi)
|
|
77
|
+
|
|
78
|
+
def _redirect_to_payi(request: Any, event_name: str, **_: 'dict[str, Any]') -> None:
|
|
79
|
+
from urllib3.util import parse_url
|
|
80
|
+
from urllib3.util.url import Url
|
|
81
|
+
|
|
82
|
+
if not event_name in BEDROCK_REQUEST_NAMES:
|
|
83
|
+
return
|
|
54
84
|
|
|
85
|
+
parsed_url: Url = parse_url(request.url)
|
|
86
|
+
route_path = parsed_url.path
|
|
87
|
+
request.url = f"{payi_aws_bedrock_url()}{route_path}"
|
|
88
|
+
|
|
89
|
+
request.headers[PayiHeaderNames.api_key] = os.environ.get("PAYI_API_KEY", "")
|
|
90
|
+
request.headers[PayiHeaderNames.provider_base_uri] = parsed_url.scheme + "://" + parsed_url.host # type: ignore
|
|
91
|
+
|
|
92
|
+
extra_headers = BedrockInstrumentor._instrumentor._create_extra_headers()
|
|
93
|
+
|
|
94
|
+
for key, value in extra_headers.items():
|
|
95
|
+
request.headers[key] = value
|
|
96
|
+
|
|
97
|
+
|
|
55
98
|
class InvokeResponseWrapper(ObjectProxy): # type: ignore
|
|
56
99
|
def __init__(
|
|
57
100
|
self,
|
|
@@ -67,10 +110,10 @@ class InvokeResponseWrapper(ObjectProxy): # type: ignore
|
|
|
67
110
|
self._ingest = ingest
|
|
68
111
|
self._log_prompt_and_response = log_prompt_and_response
|
|
69
112
|
|
|
70
|
-
def read(self, amt: Any =None): # type: ignore
|
|
113
|
+
def read(self, amt: Any =None) -> Any: # type: ignore
|
|
71
114
|
# data is array of bytes
|
|
72
|
-
data:
|
|
73
|
-
response = json.loads(data)
|
|
115
|
+
data: bytes = self.__wrapped__.read(amt) # type: ignore
|
|
116
|
+
response = json.loads(data) # type: ignore
|
|
74
117
|
|
|
75
118
|
resource = self._ingest["resource"]
|
|
76
119
|
if not resource:
|
|
@@ -90,27 +133,29 @@ class InvokeResponseWrapper(ObjectProxy): # type: ignore
|
|
|
90
133
|
units["text"] = Units(input=input, output=output)
|
|
91
134
|
|
|
92
135
|
if self._log_prompt_and_response:
|
|
93
|
-
self._ingest["provider_response_json"] = data.decode('utf-8')
|
|
136
|
+
self._ingest["provider_response_json"] = data.decode('utf-8') # type: ignore
|
|
94
137
|
|
|
95
138
|
self._instrumentor._ingest_units(self._ingest)
|
|
96
139
|
|
|
97
|
-
return data
|
|
140
|
+
return data # type: ignore
|
|
141
|
+
|
|
142
|
+
def _is_supported_model(modelId: str) -> bool:
|
|
143
|
+
return any(prefix in modelId for prefix in _supported_model_prefixes)
|
|
98
144
|
|
|
99
145
|
def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
100
146
|
@wraps(wrapped)
|
|
101
147
|
def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
|
|
102
148
|
modelId:str = kwargs.get("modelId", "") # type: ignore
|
|
103
149
|
|
|
104
|
-
if
|
|
150
|
+
if _is_supported_model(modelId):
|
|
105
151
|
return instrumentor.chat_wrapper(
|
|
106
|
-
|
|
107
|
-
_BedrockInvokeSynchronousProviderRequest(instrumentor),
|
|
152
|
+
_BedrockInvokeSynchronousProviderRequest(instrumentor=instrumentor),
|
|
108
153
|
_IsStreaming.false,
|
|
109
154
|
wrapped,
|
|
110
155
|
None,
|
|
111
156
|
args,
|
|
112
157
|
kwargs,
|
|
113
|
-
|
|
158
|
+
)
|
|
114
159
|
return wrapped(*args, **kwargs)
|
|
115
160
|
|
|
116
161
|
return invoke_wrapper
|
|
@@ -118,12 +163,11 @@ def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
118
163
|
def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
119
164
|
@wraps(wrapped)
|
|
120
165
|
def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
121
|
-
|
|
166
|
+
modelId: str = kwargs.get("modelId", "") # type: ignore
|
|
122
167
|
|
|
123
|
-
if
|
|
168
|
+
if _is_supported_model(modelId):
|
|
124
169
|
return instrumentor.chat_wrapper(
|
|
125
|
-
|
|
126
|
-
_BedrockInvokeStreamingProviderRequest(instrumentor, model_id),
|
|
170
|
+
_BedrockInvokeStreamingProviderRequest(instrumentor=instrumentor, model_id=modelId),
|
|
127
171
|
_IsStreaming.true,
|
|
128
172
|
wrapped,
|
|
129
173
|
None,
|
|
@@ -139,10 +183,9 @@ def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
139
183
|
def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
|
|
140
184
|
modelId:str = kwargs.get("modelId", "") # type: ignore
|
|
141
185
|
|
|
142
|
-
if
|
|
186
|
+
if _is_supported_model(modelId):
|
|
143
187
|
return instrumentor.chat_wrapper(
|
|
144
|
-
|
|
145
|
-
_BedrockConverseSynchronousProviderRequest(instrumentor),
|
|
188
|
+
_BedrockConverseSynchronousProviderRequest(instrumentor=instrumentor),
|
|
146
189
|
_IsStreaming.false,
|
|
147
190
|
wrapped,
|
|
148
191
|
None,
|
|
@@ -156,12 +199,11 @@ def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
156
199
|
def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
157
200
|
@wraps(wrapped)
|
|
158
201
|
def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
159
|
-
|
|
202
|
+
modelId: str = kwargs.get("modelId", "") # type: ignore
|
|
160
203
|
|
|
161
|
-
if
|
|
204
|
+
if _is_supported_model(modelId):
|
|
162
205
|
return instrumentor.chat_wrapper(
|
|
163
|
-
|
|
164
|
-
_BedrockConverseStreamingProviderRequest(instrumentor),
|
|
206
|
+
_BedrockConverseStreamingProviderRequest(instrumentor=instrumentor),
|
|
165
207
|
_IsStreaming.true,
|
|
166
208
|
wrapped,
|
|
167
209
|
None,
|
|
@@ -172,9 +214,45 @@ def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
|
|
|
172
214
|
|
|
173
215
|
return invoke_wrapper
|
|
174
216
|
|
|
175
|
-
class
|
|
217
|
+
class _BedrockProviderRequest(_ProviderRequest):
|
|
218
|
+
def __init__(self, instrumentor: _PayiInstrumentor):
|
|
219
|
+
super().__init__(instrumentor=instrumentor, category="system.aws.bedrock")
|
|
220
|
+
|
|
221
|
+
@override
|
|
222
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', kwargs: Any) -> bool:
|
|
223
|
+
# boto3 doesn't allow extra_headers
|
|
224
|
+
kwargs.pop("extra_headers", None)
|
|
225
|
+
self._ingest["resource"] = kwargs.get("modelId", "")
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
@override
|
|
229
|
+
def process_exception(self, exception: Exception, kwargs: Any, ) -> bool:
|
|
230
|
+
try:
|
|
231
|
+
if hasattr(exception, "response"):
|
|
232
|
+
response: dict[str, Any] = getattr(exception, "response", {})
|
|
233
|
+
status_code: int = response.get('ResponseMetadata', {}).get('HTTPStatusCode', 0)
|
|
234
|
+
if status_code == 0:
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
self._ingest["http_status_code"] = status_code
|
|
238
|
+
|
|
239
|
+
request_id = response.get('ResponseMetadata', {}).get('RequestId', "")
|
|
240
|
+
if request_id:
|
|
241
|
+
self._ingest["provider_response_id"] = request_id
|
|
242
|
+
|
|
243
|
+
error = response.get('Error', "")
|
|
244
|
+
if error:
|
|
245
|
+
self._ingest["provider_response_json"] = json.dumps(error)
|
|
246
|
+
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
logging.debug(f"Error processing exception: {e}")
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
class _BedrockInvokeStreamingProviderRequest(_BedrockProviderRequest):
|
|
176
254
|
def __init__(self, instrumentor: _PayiInstrumentor, model_id: str):
|
|
177
|
-
super().__init__(instrumentor)
|
|
255
|
+
super().__init__(instrumentor=instrumentor)
|
|
178
256
|
self._is_anthropic: bool = model_id.startswith("anthropic.")
|
|
179
257
|
|
|
180
258
|
@override
|
|
@@ -220,7 +298,7 @@ class _BedrockInvokeStreamingProviderRequest(_ProviderRequest):
|
|
|
220
298
|
|
|
221
299
|
return True
|
|
222
300
|
|
|
223
|
-
class _BedrockInvokeSynchronousProviderRequest(
|
|
301
|
+
class _BedrockInvokeSynchronousProviderRequest(_BedrockProviderRequest):
|
|
224
302
|
@override
|
|
225
303
|
def process_synchronous_response(
|
|
226
304
|
self,
|
|
@@ -246,7 +324,7 @@ class _BedrockInvokeSynchronousProviderRequest(_ProviderRequest):
|
|
|
246
324
|
|
|
247
325
|
return response
|
|
248
326
|
|
|
249
|
-
class _BedrockConverseSynchronousProviderRequest(
|
|
327
|
+
class _BedrockConverseSynchronousProviderRequest(_BedrockProviderRequest):
|
|
250
328
|
@override
|
|
251
329
|
def process_synchronous_response(
|
|
252
330
|
self,
|
|
@@ -278,7 +356,7 @@ class _BedrockConverseSynchronousProviderRequest(_ProviderRequest):
|
|
|
278
356
|
|
|
279
357
|
return None
|
|
280
358
|
|
|
281
|
-
class _BedrockConverseStreamingProviderRequest(
|
|
359
|
+
class _BedrockConverseStreamingProviderRequest(_BedrockProviderRequest):
|
|
282
360
|
@override
|
|
283
361
|
def process_chunk(self, chunk: 'dict[str, Any]') -> bool:
|
|
284
362
|
metadata = chunk.get("metadata", {})
|
payi/lib/OpenAIInstrumentor.py
CHANGED
|
@@ -8,6 +8,7 @@ import tiktoken # type: ignore
|
|
|
8
8
|
from wrapt import wrap_function_wrapper # type: ignore
|
|
9
9
|
|
|
10
10
|
from payi.types import IngestUnitsParams
|
|
11
|
+
from payi.lib.helpers import PayiCategories, PayiHeaderNames
|
|
11
12
|
from payi.types.ingest_units_params import Units
|
|
12
13
|
|
|
13
14
|
from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
|
|
@@ -63,7 +64,6 @@ def embeddings_wrapper(
|
|
|
63
64
|
**kwargs: Any,
|
|
64
65
|
) -> Any:
|
|
65
66
|
return instrumentor.chat_wrapper(
|
|
66
|
-
"system.openai",
|
|
67
67
|
_OpenAiEmbeddingsProviderRequest(instrumentor),
|
|
68
68
|
_IsStreaming.false,
|
|
69
69
|
wrapped,
|
|
@@ -81,7 +81,6 @@ async def aembeddings_wrapper(
|
|
|
81
81
|
**kwargs: Any,
|
|
82
82
|
) -> Any:
|
|
83
83
|
return await instrumentor.achat_wrapper(
|
|
84
|
-
"system.openai",
|
|
85
84
|
_OpenAiEmbeddingsProviderRequest(instrumentor),
|
|
86
85
|
_IsStreaming.false,
|
|
87
86
|
wrapped,
|
|
@@ -99,7 +98,6 @@ def chat_wrapper(
|
|
|
99
98
|
**kwargs: Any,
|
|
100
99
|
) -> Any:
|
|
101
100
|
return instrumentor.chat_wrapper(
|
|
102
|
-
"system.openai",
|
|
103
101
|
_OpenAiChatProviderRequest(instrumentor),
|
|
104
102
|
_IsStreaming.kwargs,
|
|
105
103
|
wrapped,
|
|
@@ -117,7 +115,6 @@ async def achat_wrapper(
|
|
|
117
115
|
**kwargs: Any,
|
|
118
116
|
) -> Any:
|
|
119
117
|
return await instrumentor.achat_wrapper(
|
|
120
|
-
"system.openai",
|
|
121
118
|
_OpenAiChatProviderRequest(instrumentor),
|
|
122
119
|
_IsStreaming.kwargs,
|
|
123
120
|
wrapped,
|
|
@@ -126,7 +123,79 @@ async def achat_wrapper(
|
|
|
126
123
|
kwargs,
|
|
127
124
|
)
|
|
128
125
|
|
|
129
|
-
class
|
|
126
|
+
class _OpenAiProviderRequest(_ProviderRequest):
|
|
127
|
+
def __init__(self, instrumentor: _PayiInstrumentor):
|
|
128
|
+
super().__init__(instrumentor=instrumentor, category="system.openai")
|
|
129
|
+
|
|
130
|
+
@override
|
|
131
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', kwargs: Any) -> bool:
|
|
132
|
+
self._ingest["resource"] = kwargs.get("model", "")
|
|
133
|
+
|
|
134
|
+
if not (instance and hasattr(instance, "_client")) or OpenAiInstrumentor.is_azure(instance) is False:
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
context = self._instrumentor.get_context_safe()
|
|
138
|
+
route_as_resource = extra_headers.get(PayiHeaderNames.route_as_resource) or context.get("route_as_resource")
|
|
139
|
+
resource_scope = extra_headers.get(PayiHeaderNames.resource_scope) or context.get("resource_scope")
|
|
140
|
+
|
|
141
|
+
if PayiHeaderNames.route_as_resource in extra_headers:
|
|
142
|
+
del extra_headers[PayiHeaderNames.route_as_resource]
|
|
143
|
+
if PayiHeaderNames.resource_scope in extra_headers:
|
|
144
|
+
del extra_headers[PayiHeaderNames.resource_scope]
|
|
145
|
+
|
|
146
|
+
if not route_as_resource:
|
|
147
|
+
logging.error("Azure OpenAI route as resource not found, not ingesting")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
if resource_scope:
|
|
151
|
+
if not(resource_scope in ["global", "datazone"] or resource_scope.startswith("region")):
|
|
152
|
+
logging.error("Azure OpenAI invalid resource scope, not ingesting")
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
self._ingest["resource_scope"] = resource_scope
|
|
156
|
+
|
|
157
|
+
self._category = PayiCategories.azure_openai
|
|
158
|
+
|
|
159
|
+
self._ingest["category"] = self._category
|
|
160
|
+
self._ingest["resource"] = route_as_resource
|
|
161
|
+
|
|
162
|
+
return True
|
|
163
|
+
|
|
164
|
+
@override
|
|
165
|
+
def process_exception(self, exception: Exception, kwargs: Any, ) -> bool:
|
|
166
|
+
try:
|
|
167
|
+
status_code: Optional[int] = None
|
|
168
|
+
|
|
169
|
+
if hasattr(exception, "status_code"):
|
|
170
|
+
status_code = getattr(exception, "status_code", None)
|
|
171
|
+
if isinstance(status_code, int):
|
|
172
|
+
self._ingest["http_status_code"] = status_code
|
|
173
|
+
|
|
174
|
+
if not status_code:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
if hasattr(exception, "request_id"):
|
|
178
|
+
request_id = getattr(exception, "request_id", None)
|
|
179
|
+
if isinstance(request_id, str):
|
|
180
|
+
self._ingest["provider_response_id"] = request_id
|
|
181
|
+
|
|
182
|
+
if hasattr(exception, "response"):
|
|
183
|
+
response = getattr(exception, "response", None)
|
|
184
|
+
if hasattr(response, "text"):
|
|
185
|
+
text = getattr(response, "text", None)
|
|
186
|
+
if isinstance(text, str):
|
|
187
|
+
self._ingest["provider_response_json"] = text
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logging.debug(f"Error processing exception: {e}")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
return True
|
|
194
|
+
|
|
195
|
+
class _OpenAiEmbeddingsProviderRequest(_OpenAiProviderRequest):
|
|
196
|
+
def __init__(self, instrumentor: _PayiInstrumentor):
|
|
197
|
+
super().__init__(instrumentor=instrumentor)
|
|
198
|
+
|
|
130
199
|
@override
|
|
131
200
|
def process_synchronous_response(
|
|
132
201
|
self,
|
|
@@ -135,9 +204,9 @@ class _OpenAiEmbeddingsProviderRequest(_ProviderRequest):
|
|
|
135
204
|
kwargs: Any) -> Any:
|
|
136
205
|
return process_chat_synchronous_response(response, self._ingest, log_prompt_and_response, self._estimated_prompt_tokens)
|
|
137
206
|
|
|
138
|
-
class _OpenAiChatProviderRequest(
|
|
207
|
+
class _OpenAiChatProviderRequest(_OpenAiProviderRequest):
|
|
139
208
|
def __init__(self, instrumentor: _PayiInstrumentor):
|
|
140
|
-
super().__init__(instrumentor)
|
|
209
|
+
super().__init__(instrumentor=instrumentor)
|
|
141
210
|
self._include_usage_added = False
|
|
142
211
|
|
|
143
212
|
@override
|
|
@@ -163,39 +232,42 @@ class _OpenAiChatProviderRequest(_ProviderRequest):
|
|
|
163
232
|
return send_chunk_to_client
|
|
164
233
|
|
|
165
234
|
@override
|
|
166
|
-
def process_request(self, kwargs: Any) ->
|
|
167
|
-
|
|
168
|
-
if
|
|
169
|
-
return
|
|
170
|
-
|
|
171
|
-
estimated_token_count = 0
|
|
172
|
-
has_image = False
|
|
173
|
-
|
|
174
|
-
try:
|
|
175
|
-
enc = tiktoken.encoding_for_model(kwargs.get("model")) # type: ignore
|
|
176
|
-
except KeyError:
|
|
177
|
-
enc = tiktoken.get_encoding("o200k_base") # type: ignore
|
|
235
|
+
def process_request(self, instance: Any, extra_headers: 'dict[str, str]', kwargs: Any) -> bool:
|
|
236
|
+
result = super().process_request(instance, extra_headers, kwargs)
|
|
237
|
+
if result is False:
|
|
238
|
+
return result
|
|
178
239
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
240
|
+
messages = kwargs.get("messages", None)
|
|
241
|
+
if messages:
|
|
242
|
+
estimated_token_count = 0
|
|
243
|
+
has_image = False
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
enc = tiktoken.encoding_for_model(kwargs.get("model")) # type: ignore
|
|
247
|
+
except KeyError:
|
|
248
|
+
enc = tiktoken.get_encoding("o200k_base") # type: ignore
|
|
249
|
+
|
|
250
|
+
for message in messages:
|
|
251
|
+
msg_has_image, msg_prompt_tokens = has_image_and_get_texts(enc, message.get('content', ''))
|
|
252
|
+
if msg_has_image:
|
|
253
|
+
has_image = True
|
|
254
|
+
estimated_token_count += msg_prompt_tokens
|
|
255
|
+
|
|
256
|
+
if has_image and estimated_token_count > 0:
|
|
257
|
+
self._estimated_prompt_tokens = estimated_token_count
|
|
187
258
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
259
|
+
stream: bool = kwargs.get("stream", False)
|
|
260
|
+
if stream:
|
|
261
|
+
add_include_usage = True
|
|
191
262
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
263
|
+
stream_options: dict[str, Any] = kwargs.get("stream_options", None)
|
|
264
|
+
if stream_options and "include_usage" in stream_options:
|
|
265
|
+
add_include_usage = stream_options["include_usage"] == False
|
|
195
266
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
267
|
+
if add_include_usage:
|
|
268
|
+
kwargs['stream_options'] = {"include_usage": True}
|
|
269
|
+
self._include_usage_added = True
|
|
270
|
+
return True
|
|
199
271
|
|
|
200
272
|
@override
|
|
201
273
|
def process_synchronous_response(
|
payi/lib/helpers.py
CHANGED
|
@@ -15,7 +15,8 @@ class PayiHeaderNames:
|
|
|
15
15
|
route_as_resource:str = "xProxy-RouteAs-Resource"
|
|
16
16
|
provider_base_uri = "xProxy-Provider-BaseUri"
|
|
17
17
|
resource_scope:str = "xProxy-Resource-Scope"
|
|
18
|
-
|
|
18
|
+
api_key:str = "xProxy-Api-Key"
|
|
19
|
+
|
|
19
20
|
class PayiCategories:
|
|
20
21
|
anthropic:str = "system.anthropic"
|
|
21
22
|
openai:str = "system.openai"
|