payi 0.1.0a72__py3-none-any.whl → 0.1.0a73__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 CHANGED
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "payi"
4
- __version__ = "0.1.0-alpha.72" # x-release-please-version
4
+ __version__ = "0.1.0-alpha.73" # x-release-please-version
@@ -11,7 +11,13 @@ from payi.types.ingest_units_params import Units
11
11
  from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
12
12
 
13
13
 
14
- class AnthropicIntrumentor:
14
+ class AnthropicInstrumentor:
15
+ @staticmethod
16
+ def is_vertex(instance: Any) -> bool:
17
+ from anthropic import AnthropicVertex, AsyncAnthropicVertex # type: ignore # noqa: I001
18
+
19
+ return isinstance(instance._client, (AsyncAnthropicVertex, AnthropicVertex))
20
+
15
21
  @staticmethod
16
22
  def instrument(instrumentor: _PayiInstrumentor) -> None:
17
23
  try:
@@ -20,25 +26,25 @@ class AnthropicIntrumentor:
20
26
  wrap_function_wrapper(
21
27
  "anthropic.resources.messages",
22
28
  "Messages.create",
23
- chat_wrapper(instrumentor),
29
+ messages_wrapper(instrumentor),
24
30
  )
25
31
 
26
32
  wrap_function_wrapper(
27
33
  "anthropic.resources.messages",
28
34
  "Messages.stream",
29
- chat_wrapper(instrumentor),
35
+ messages_wrapper(instrumentor),
30
36
  )
31
37
 
32
38
  wrap_function_wrapper(
33
39
  "anthropic.resources.messages",
34
40
  "AsyncMessages.create",
35
- achat_wrapper(instrumentor),
41
+ amessages_wrapper(instrumentor),
36
42
  )
37
43
 
38
44
  wrap_function_wrapper(
39
45
  "anthropic.resources.messages",
40
46
  "AsyncMessages.stream",
41
- achat_wrapper(instrumentor),
47
+ amessages_wrapper(instrumentor),
42
48
  )
43
49
 
44
50
  except Exception as e:
@@ -47,7 +53,7 @@ class AnthropicIntrumentor:
47
53
 
48
54
 
49
55
  @_PayiInstrumentor.payi_wrapper
50
- def chat_wrapper(
56
+ def messages_wrapper(
51
57
  instrumentor: _PayiInstrumentor,
52
58
  wrapped: Any,
53
59
  instance: Any,
@@ -55,7 +61,7 @@ def chat_wrapper(
55
61
  **kwargs: Any,
56
62
  ) -> Any:
57
63
  return instrumentor.invoke_wrapper(
58
- _AnthropicProviderRequest(instrumentor),
64
+ _AnthropicProviderRequest(instrumentor, instance),
59
65
  _IsStreaming.kwargs,
60
66
  wrapped,
61
67
  instance,
@@ -64,7 +70,7 @@ def chat_wrapper(
64
70
  )
65
71
 
66
72
  @_PayiInstrumentor.payi_awrapper
67
- async def achat_wrapper(
73
+ async def amessages_wrapper(
68
74
  instrumentor: _PayiInstrumentor,
69
75
  wrapped: Any,
70
76
  instance: Any,
@@ -72,7 +78,7 @@ async def achat_wrapper(
72
78
  **kwargs: Any,
73
79
  ) -> Any:
74
80
  return await instrumentor.async_invoke_wrapper(
75
- _AnthropicProviderRequest(instrumentor),
81
+ _AnthropicProviderRequest(instrumentor, instance),
76
82
  _IsStreaming.kwargs,
77
83
  wrapped,
78
84
  instance,
@@ -81,8 +87,12 @@ async def achat_wrapper(
81
87
  )
82
88
 
83
89
  class _AnthropicProviderRequest(_ProviderRequest):
84
- def __init__(self, instrumentor: _PayiInstrumentor):
85
- super().__init__(instrumentor=instrumentor, category=PayiCategories.anthropic)
90
+ def __init__(self, instrumentor: _PayiInstrumentor, instance: Any = None) -> None:
91
+ self._vertex: bool = AnthropicInstrumentor.is_vertex(instance)
92
+ super().__init__(
93
+ instrumentor=instrumentor,
94
+ category=PayiCategories.google_vertex if self._vertex else PayiCategories.anthropic
95
+ )
86
96
 
87
97
  @override
88
98
  def process_chunk(self, chunk: Any) -> bool:
@@ -138,7 +148,8 @@ class _AnthropicProviderRequest(_ProviderRequest):
138
148
 
139
149
  @override
140
150
  def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
141
- self._ingest["resource"] = kwargs.get("model", "")
151
+ self._ingest["resource"] = ("anthropic." if self._vertex else "") + kwargs.get("model", "")
152
+
142
153
  messages = kwargs.get("messages")
143
154
  if messages:
144
155
  estimated_token_count = 0
@@ -7,7 +7,6 @@ from importlib.metadata import version
7
7
  import tiktoken # type: ignore
8
8
  from wrapt import wrap_function_wrapper # type: ignore
9
9
 
10
- from payi.types import IngestUnitsParams
11
10
  from payi.lib.helpers import PayiCategories, PayiHeaderNames
12
11
  from payi.types.ingest_units_params import Units
13
12
 
@@ -50,6 +49,18 @@ class OpenAiInstrumentor:
50
49
  aembeddings_wrapper(instrumentor),
51
50
  )
52
51
 
52
+ wrap_function_wrapper(
53
+ "openai.resources.responses",
54
+ "Responses.create",
55
+ responses_wrapper(instrumentor),
56
+ )
57
+
58
+ wrap_function_wrapper(
59
+ "openai.resources.responses",
60
+ "AsyncResponses.create",
61
+ aresponses_wrapper(instrumentor),
62
+ )
63
+
53
64
  except Exception as e:
54
65
  logging.debug(f"Error instrumenting openai: {e}")
55
66
  return
@@ -122,10 +133,55 @@ async def achat_wrapper(
122
133
  args,
123
134
  kwargs,
124
135
  )
136
+
137
+ @_PayiInstrumentor.payi_wrapper
138
+ def responses_wrapper(
139
+ instrumentor: _PayiInstrumentor,
140
+ wrapped: Any,
141
+ instance: Any,
142
+ *args: Any,
143
+ **kwargs: Any,
144
+ ) -> Any:
145
+ return instrumentor.invoke_wrapper(
146
+ _OpenAiResponsesProviderRequest(instrumentor),
147
+ _IsStreaming.kwargs,
148
+ wrapped,
149
+ instance,
150
+ args,
151
+ kwargs,
152
+ )
153
+
154
+ @_PayiInstrumentor.payi_awrapper
155
+ async def aresponses_wrapper(
156
+ instrumentor: _PayiInstrumentor,
157
+ wrapped: Any,
158
+ instance: Any,
159
+ *args: Any,
160
+ **kwargs: Any,
161
+ ) -> Any:
162
+ return await instrumentor.async_invoke_wrapper(
163
+ _OpenAiResponsesProviderRequest(instrumentor),
164
+ _IsStreaming.kwargs,
165
+ wrapped,
166
+ instance,
167
+ args,
168
+ kwargs,
169
+ )
125
170
 
126
171
  class _OpenAiProviderRequest(_ProviderRequest):
127
- def __init__(self, instrumentor: _PayiInstrumentor):
172
+ chat_input_tokens_key: str = "prompt_tokens"
173
+ chat_output_tokens_key: str = "completion_tokens"
174
+ chat_input_tokens_details_key: str = "prompt_tokens_details"
175
+
176
+ responses_input_tokens_key: str = "input_tokens"
177
+ responses_output_tokens_key: str = "output_tokens"
178
+ responses_input_tokens_details_key: str = "input_tokens_details"
179
+
180
+ def __init__(self, instrumentor: _PayiInstrumentor, input_tokens_key: str, output_tokens_key: str, input_tokens_details_key: str) -> None:
128
181
  super().__init__(instrumentor=instrumentor, category=PayiCategories.openai)
182
+ self._input_tokens_key = input_tokens_key
183
+ self._output_tokens_key = output_tokens_key
184
+ self._input_tokens_details_key = input_tokens_details_key
129
185
 
130
186
  @override
131
187
  def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool: # type: ignore
@@ -135,16 +191,19 @@ class _OpenAiProviderRequest(_ProviderRequest):
135
191
  return True
136
192
 
137
193
  context = self._instrumentor.get_context_safe()
138
- route_as_resource = extra_headers.get(PayiHeaderNames.route_as_resource) or context.get("route_as_resource")
194
+ price_as_category = extra_headers.get(PayiHeaderNames.price_as_category) or context.get("price_as_category")
195
+ price_as_resource = extra_headers.get(PayiHeaderNames.price_as_resource) or context.get("price_as_resource")
139
196
  resource_scope = extra_headers.get(PayiHeaderNames.resource_scope) or context.get("resource_scope")
140
197
 
141
- if PayiHeaderNames.route_as_resource in extra_headers:
142
- del extra_headers[PayiHeaderNames.route_as_resource]
198
+ if PayiHeaderNames.price_as_category in extra_headers:
199
+ del extra_headers[PayiHeaderNames.price_as_category]
200
+ if PayiHeaderNames.price_as_resource in extra_headers:
201
+ del extra_headers[PayiHeaderNames.price_as_resource]
143
202
  if PayiHeaderNames.resource_scope in extra_headers:
144
203
  del extra_headers[PayiHeaderNames.resource_scope]
145
204
 
146
- if not route_as_resource:
147
- logging.error("Azure OpenAI route as resource not found, not ingesting")
205
+ if not price_as_resource and not price_as_category:
206
+ logging.error("Azure OpenAI requires price as resource and/or category to be specified, not ingesting")
148
207
  return False
149
208
 
150
209
  if resource_scope:
@@ -157,8 +216,13 @@ class _OpenAiProviderRequest(_ProviderRequest):
157
216
  self._category = PayiCategories.azure_openai
158
217
 
159
218
  self._ingest["category"] = self._category
160
- self._ingest["resource"] = route_as_resource
161
-
219
+
220
+ if price_as_category:
221
+ # price as category overrides default
222
+ self._ingest["category"] = price_as_category
223
+ if price_as_resource:
224
+ self._ingest["resource"] = price_as_resource
225
+
162
226
  return True
163
227
 
164
228
  @override
@@ -193,9 +257,59 @@ class _OpenAiProviderRequest(_ProviderRequest):
193
257
 
194
258
  return True
195
259
 
260
+ def process_synchronous_response_worker(
261
+ self,
262
+ response: str,
263
+ log_prompt_and_response: bool,
264
+ ) -> Any:
265
+ response_dict = model_to_dict(response)
266
+
267
+ self.add_usage_units(response_dict.get("usage", {}))
268
+
269
+ if log_prompt_and_response:
270
+ self._ingest["provider_response_json"] = [json.dumps(response_dict)]
271
+
272
+ if "id" in response_dict:
273
+ self._ingest["provider_response_id"] = response_dict["id"]
274
+
275
+ return None
276
+
277
+ def add_usage_units(self, usage: "dict[str, Any]",) -> None:
278
+ units = self._ingest["units"]
279
+
280
+ input = usage[self._input_tokens_key] if self._input_tokens_key in usage else 0
281
+ output = usage[self._output_tokens_key] if self._output_tokens_key in usage else 0
282
+ input_cache = 0
283
+
284
+ prompt_tokens_details = usage.get(self._input_tokens_details_key)
285
+ if prompt_tokens_details:
286
+ input_cache = prompt_tokens_details.get("cached_tokens", 0)
287
+ if input_cache != 0:
288
+ units["text_cache_read"] = Units(input=input_cache, output=0)
289
+
290
+ input = _PayiInstrumentor.update_for_vision(input - input_cache, units, self._estimated_prompt_tokens)
291
+
292
+ units["text"] = Units(input=input, output=output)
293
+
294
+ @staticmethod
295
+ def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'list[Any]'], image_type: str = "image_url", text_type:str = "text") -> 'tuple[bool, int]':
296
+ if isinstance(content, str):
297
+ return False, 0
298
+ elif isinstance(content, list): # type: ignore
299
+ has_image = any(item.get("type") == image_type for item in content)
300
+ if has_image is False:
301
+ return has_image, 0
302
+
303
+ token_count = sum(len(encoding.encode(item.get("text", ""))) for item in content if item.get("type") == text_type)
304
+ return has_image, token_count
305
+
196
306
  class _OpenAiEmbeddingsProviderRequest(_OpenAiProviderRequest):
197
307
  def __init__(self, instrumentor: _PayiInstrumentor):
198
- super().__init__(instrumentor=instrumentor)
308
+ super().__init__(
309
+ instrumentor=instrumentor,
310
+ input_tokens_key=_OpenAiProviderRequest.chat_input_tokens_key,
311
+ output_tokens_key=_OpenAiProviderRequest.chat_output_tokens_key,
312
+ input_tokens_details_key=_OpenAiProviderRequest.chat_input_tokens_details_key)
199
313
 
200
314
  @override
201
315
  def process_synchronous_response(
@@ -203,11 +317,16 @@ class _OpenAiEmbeddingsProviderRequest(_OpenAiProviderRequest):
203
317
  response: Any,
204
318
  log_prompt_and_response: bool,
205
319
  kwargs: Any) -> Any:
206
- return process_chat_synchronous_response(response, self._ingest, log_prompt_and_response, self._estimated_prompt_tokens)
320
+ return self.process_synchronous_response_worker(response, log_prompt_and_response)
207
321
 
208
322
  class _OpenAiChatProviderRequest(_OpenAiProviderRequest):
209
323
  def __init__(self, instrumentor: _PayiInstrumentor):
210
- super().__init__(instrumentor=instrumentor)
324
+ super().__init__(
325
+ instrumentor=instrumentor,
326
+ input_tokens_key=_OpenAiProviderRequest.chat_input_tokens_key,
327
+ output_tokens_key=_OpenAiProviderRequest.chat_output_tokens_key,
328
+ input_tokens_details_key=_OpenAiProviderRequest.chat_input_tokens_details_key)
329
+
211
330
  self._include_usage_added = False
212
331
 
213
332
  @override
@@ -223,9 +342,9 @@ class _OpenAiChatProviderRequest(_OpenAiProviderRequest):
223
342
 
224
343
  usage = model.get("usage")
225
344
  if usage:
226
- add_usage_units(usage, self._ingest["units"], self._estimated_prompt_tokens)
345
+ self.add_usage_units(usage)
227
346
 
228
- # If we aded "include_usage" in the request on behalf of the client, do not return the extra
347
+ # If we added "include_usage" in the request on behalf of the client, do not return the extra
229
348
  # packet which contains the usage to the client as they are not expecting the data
230
349
  if self._include_usage_added:
231
350
  send_chunk_to_client = False
@@ -249,7 +368,7 @@ class _OpenAiChatProviderRequest(_OpenAiProviderRequest):
249
368
  enc = tiktoken.get_encoding("o200k_base") # type: ignore
250
369
 
251
370
  for message in messages:
252
- msg_has_image, msg_prompt_tokens = has_image_and_get_texts(enc, message.get('content', ''))
371
+ msg_has_image, msg_prompt_tokens = self.has_image_and_get_texts(enc, message.get('content', ''))
253
372
  if msg_has_image:
254
373
  has_image = True
255
374
  estimated_token_count += msg_prompt_tokens
@@ -276,20 +395,86 @@ class _OpenAiChatProviderRequest(_OpenAiProviderRequest):
276
395
  response: Any,
277
396
  log_prompt_and_response: bool,
278
397
  kwargs: Any) -> Any:
279
- process_chat_synchronous_response(response, self._ingest, log_prompt_and_response, self._estimated_prompt_tokens)
398
+ return self.process_synchronous_response_worker(response, log_prompt_and_response)
399
+
400
+ class _OpenAiResponsesProviderRequest(_OpenAiProviderRequest):
401
+ def __init__(self, instrumentor: _PayiInstrumentor):
402
+ super().__init__(
403
+ instrumentor=instrumentor,
404
+ input_tokens_key=_OpenAiProviderRequest.responses_input_tokens_key,
405
+ output_tokens_key=_OpenAiProviderRequest.responses_output_tokens_key,
406
+ input_tokens_details_key=_OpenAiProviderRequest.responses_input_tokens_details_key)
280
407
 
281
- def process_chat_synchronous_response(response: str, ingest: IngestUnitsParams, log_prompt_and_response: bool, estimated_prompt_tokens: Optional[int]) -> Any:
282
- response_dict = model_to_dict(response)
408
+ @override
409
+ def process_chunk(self, chunk: Any) -> bool:
410
+ model = model_to_dict(chunk)
411
+ response: dict[str, Any] = model.get("response", {})
412
+
413
+ if "provider_response_id" not in self._ingest:
414
+ response_id = response.get("id", None)
415
+ if response_id:
416
+ self._ingest["provider_response_id"] = response_id
417
+
418
+ usage = response.get("usage")
419
+ if usage:
420
+ self.add_usage_units(usage)
421
+
422
+ return True
283
423
 
284
- add_usage_units(response_dict.get("usage", {}), ingest["units"], estimated_prompt_tokens)
424
+ @override
425
+ def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
426
+ result = super().process_request(instance, extra_headers, args, kwargs)
427
+ if result is False:
428
+ return result
429
+
430
+ input: ResponseInputParam = kwargs.get("input", None) # type: ignore
431
+ if not input or isinstance(input, str) or not isinstance(input, list):
432
+ return True
433
+
434
+ estimated_token_count = 0
435
+ has_image = False
285
436
 
286
- if log_prompt_and_response:
287
- ingest["provider_response_json"] = [json.dumps(response_dict)]
437
+ try:
438
+ enc = tiktoken.encoding_for_model(kwargs.get("model")) # type: ignore
439
+ except KeyError:
440
+ enc = tiktoken.get_encoding("o200k_base") # type: ignore
441
+
442
+ # find each content..type="input_text" and count tokens
443
+ # input=[{
444
+ # "role": "user",
445
+ # "content": [
446
+ # {
447
+ # "type": "input_text",
448
+ # "text": "what's in this image?"
449
+ # },
450
+ # {
451
+ # "type": "input_image",
452
+ # "image_url": ...
453
+ # },
454
+ # ],
455
+ # }]
456
+ for item in input: # type: ignore
457
+ if isinstance(item, dict):
458
+ for key, value in item.items(): # type: ignore
459
+ if key == "content":
460
+ if isinstance(value, list):
461
+ msg_has_image, msg_prompt_tokens = self.has_image_and_get_texts(enc, value, image_type="input_image", text_type="input_text") # type: ignore
462
+ if msg_has_image:
463
+ has_image = True
464
+ estimated_token_count += msg_prompt_tokens
465
+
466
+ if has_image and estimated_token_count > 0:
467
+ self._estimated_prompt_tokens = estimated_token_count
288
468
 
289
- if "id" in response_dict:
290
- ingest["provider_response_id"] = response_dict["id"]
469
+ return True
291
470
 
292
- return None
471
+ @override
472
+ def process_synchronous_response(
473
+ self,
474
+ response: Any,
475
+ log_prompt_and_response: bool,
476
+ kwargs: Any) -> Any:
477
+ return self.process_synchronous_response_worker(response, log_prompt_and_response)
293
478
 
294
479
  def model_to_dict(model: Any) -> Any:
295
480
  if version("pydantic") < "2.0.0":
@@ -299,31 +484,4 @@ def model_to_dict(model: Any) -> Any:
299
484
  elif hasattr(model, "parse"): # Raw API response
300
485
  return model_to_dict(model.parse())
301
486
  else:
302
- return model
303
-
304
-
305
- def add_usage_units(usage: "dict[str, Any]", units: "dict[str, Units]", estimated_prompt_tokens: Optional[int]) -> None:
306
- input = usage["prompt_tokens"] if "prompt_tokens" in usage else 0
307
- output = usage["completion_tokens"] if "completion_tokens" in usage else 0
308
- input_cache = 0
309
-
310
- prompt_tokens_details = usage.get("prompt_tokens_details")
311
- if prompt_tokens_details:
312
- input_cache = prompt_tokens_details.get("cached_tokens", 0)
313
- if input_cache != 0:
314
- units["text_cache_read"] = Units(input=input_cache, output=0)
315
-
316
- input = _PayiInstrumentor.update_for_vision(input - input_cache, units, estimated_prompt_tokens)
317
-
318
- units["text"] = Units(input=input, output=output)
319
-
320
- def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'list[Any]']) -> 'tuple[bool, int]':
321
- if isinstance(content, str):
322
- return False, 0
323
- elif isinstance(content, list): # type: ignore
324
- has_image = any(item.get("type") == "image_url" for item in content)
325
- if has_image is False:
326
- return has_image, 0
327
-
328
- token_count = sum(len(encoding.encode(item.get("text", ""))) for item in content if item.get("type") == "text")
329
- return has_image, token_count
487
+ return model
payi/lib/helpers.py CHANGED
@@ -11,8 +11,10 @@ class PayiHeaderNames:
11
11
  use_case_id:str = "xProxy-UseCase-ID"
12
12
  use_case_name:str = "xProxy-UseCase-Name"
13
13
  use_case_version:str = "xProxy-UseCase-Version"
14
+ use_case_step:str = "xProxy-UseCase-Step"
14
15
  user_id:str = "xProxy-User-ID"
15
- route_as_resource:str = "xProxy-RouteAs-Resource"
16
+ price_as_category:str = "xProxy-PriceAs-Category"
17
+ price_as_resource:str = "xProxy-PriceAs-Resource"
16
18
  provider_base_uri = "xProxy-Provider-BaseUri"
17
19
  resource_scope:str = "xProxy-Resource-Scope"
18
20
  api_key:str = "xProxy-Api-Key"
@@ -32,7 +34,6 @@ def create_limit_header_from_ids(limit_ids: List[str]) -> Dict[str, str]:
32
34
 
33
35
  return { PayiHeaderNames.limit_ids: ",".join(valid_ids) } if valid_ids else {}
34
36
 
35
-
36
37
  def create_request_header_from_tags(request_tags: List[str]) -> Dict[str, str]:
37
38
  if not isinstance(request_tags, list): # type: ignore
38
39
  raise TypeError("request_tags must be a list")
@@ -50,7 +51,9 @@ def create_headers(
50
51
  use_case_id: Union[str, None] = None,
51
52
  use_case_name: Union[str, None] = None,
52
53
  use_case_version: Union[int, None] = None,
53
- route_as_resource: Union[str, None] = None,
54
+ use_case_step: Union[str, None] = None,
55
+ price_as_category: Union[str, None] = None,
56
+ price_as_resource: Union[str, None] = None,
54
57
  resource_scope: Union[str, None] = None,
55
58
  ) -> Dict[str, str]:
56
59
  headers: Dict[str, str] = {}
@@ -71,8 +74,12 @@ def create_headers(
71
74
  headers.update({ PayiHeaderNames.use_case_name: use_case_name})
72
75
  if use_case_version:
73
76
  headers.update({ PayiHeaderNames.use_case_version: str(use_case_version)})
74
- if route_as_resource:
75
- headers.update({ PayiHeaderNames.route_as_resource: route_as_resource})
77
+ if use_case_step:
78
+ headers.update({ PayiHeaderNames.use_case_step: use_case_step})
79
+ if price_as_category:
80
+ headers.update({ PayiHeaderNames.price_as_category: price_as_category})
81
+ if price_as_resource:
82
+ headers.update({ PayiHeaderNames.price_as_resource: price_as_resource})
76
83
  if resource_scope:
77
84
  headers.update({ PayiHeaderNames.resource_scope: resource_scope })
78
85
  return headers
@@ -99,3 +106,6 @@ def payi_azure_openai_url(payi_base_url: Union[str, None] = None) -> str:
99
106
 
100
107
  def payi_aws_bedrock_url(payi_base_url: Union[str, None] = None) -> str:
101
108
  return _resolve_payi_base_url(payi_base_url=payi_base_url) + "/api/v1/proxy/aws.bedrock"
109
+
110
+ # def payi_google_vertex_url(payi_base_url: Union[str, None] = None) -> str:
111
+ # return _resolve_payi_base_url(payi_base_url=payi_base_url) + "/api/v1/proxy/google.vertex"
payi/lib/instrument.py CHANGED
@@ -98,10 +98,12 @@ class _Context(TypedDict, total=False):
98
98
  use_case_name: Optional[str]
99
99
  use_case_id: Optional[str]
100
100
  use_case_version: Optional[int]
101
+ use_case_step: Optional[str]
101
102
  limit_ids: Optional['list[str]']
102
103
  user_id: Optional[str]
103
104
  request_tags: Optional["list[str]"]
104
- route_as_resource: Optional[str]
105
+ price_as_category: Optional[str]
106
+ price_as_resource: Optional[str]
105
107
  resource_scope: Optional[str]
106
108
 
107
109
  class _IsStreaming(Enum):
@@ -166,7 +168,7 @@ class _PayiInstrumentor:
166
168
  global_config["proxy"] = self._proxy_default
167
169
 
168
170
  # Use default clients if not provided for global ingest instrumentation
169
- if not self._payi and not self._apayi and global_config.get("proxy") is False:
171
+ if not self._payi and not self._apayi:
170
172
  self._payi = Payi()
171
173
  self._apayi = AsyncPayi()
172
174
 
@@ -211,10 +213,10 @@ class _PayiInstrumentor:
211
213
  logging.error(f"Error instrumenting OpenAI: {e}")
212
214
 
213
215
  def _instrument_anthropic(self) -> None:
214
- from .AnthropicInstrumentor import AnthropicIntrumentor
216
+ from .AnthropicInstrumentor import AnthropicInstrumentor
215
217
 
216
218
  try:
217
- AnthropicIntrumentor.instrument(self)
219
+ AnthropicInstrumentor.instrument(self)
218
220
 
219
221
  except Exception as e:
220
222
  logging.error(f"Error instrumenting Anthropic: {e}")
@@ -391,10 +393,12 @@ class _PayiInstrumentor:
391
393
  experience_id: Optional[str] = None,
392
394
  use_case_name: Optional[str]= None,
393
395
  use_case_id: Optional[str]= None,
394
- use_case_version: Optional[int]= None,
396
+ use_case_version: Optional[int]= None,
397
+ use_case_step: Optional[str]= None,
395
398
  user_id: Optional[str]= None,
396
399
  request_tags: Optional["list[str]"] = None,
397
- route_as_resource: Optional[str] = None,
400
+ price_as_category: Optional[str] = None,
401
+ price_as_resource: Optional[str] = None,
398
402
  resource_scope: Optional[str] = None,
399
403
  ) -> None:
400
404
 
@@ -441,6 +445,7 @@ class _PayiInstrumentor:
441
445
  context["use_case_name"] = None
442
446
  context["use_case_id"] = None
443
447
  context["use_case_version"] = None
448
+ context["use_case_step"] = None
444
449
  else:
445
450
  if use_case_name == parent_use_case_name:
446
451
  # Same use case name, use previous ID unless new one specified
@@ -474,10 +479,14 @@ class _PayiInstrumentor:
474
479
  else:
475
480
  context["user_id"] = user_id
476
481
 
482
+ if use_case_step and (context["use_case_name"] or context["use_case_id"]):
483
+ context["use_case_step"] = use_case_step
477
484
  if request_tags:
478
485
  context["request_tags"] = request_tags
479
- if route_as_resource:
480
- context["route_as_resource"] = route_as_resource
486
+ if price_as_category:
487
+ context["price_as_category"] = price_as_category
488
+ if price_as_resource:
489
+ context["price_as_resource"] = price_as_resource
481
490
  if resource_scope:
482
491
  context["resource_scope"] = resource_scope
483
492
 
@@ -497,14 +506,14 @@ class _PayiInstrumentor:
497
506
  ) -> Any:
498
507
  with self:
499
508
  self._init_current_context(
500
- proxy,
501
- limit_ids,
502
- experience_name,
503
- experience_id,
504
- use_case_name,
505
- use_case_id,
506
- use_case_version,
507
- user_id)
509
+ proxy=proxy,
510
+ limit_ids=limit_ids,
511
+ experience_name=experience_name,
512
+ experience_id=experience_id,
513
+ use_case_name=use_case_name,
514
+ use_case_id=use_case_id,
515
+ use_case_version=use_case_version,
516
+ user_id=user_id)
508
517
  return await func(*args, **kwargs)
509
518
 
510
519
  def _call_func(
@@ -523,14 +532,14 @@ class _PayiInstrumentor:
523
532
  ) -> Any:
524
533
  with self:
525
534
  self._init_current_context(
526
- proxy,
527
- limit_ids,
528
- experience_name,
529
- experience_id,
530
- use_case_name,
531
- use_case_id,
532
- use_case_version,
533
- user_id)
535
+ proxy=proxy,
536
+ limit_ids=limit_ids,
537
+ experience_name=experience_name,
538
+ experience_id=experience_id,
539
+ use_case_name=use_case_name,
540
+ use_case_id=use_case_id,
541
+ use_case_version=use_case_version,
542
+ user_id=user_id)
534
543
  return func(*args, **kwargs)
535
544
 
536
545
  def __enter__(self) -> Any:
@@ -558,13 +567,18 @@ class _PayiInstrumentor:
558
567
  args: Sequence[Any],
559
568
  kwargs: 'dict[str, Any]',
560
569
  ) -> None:
570
+
561
571
  limit_ids = ingest_extra_headers.pop(PayiHeaderNames.limit_ids, None)
562
572
  request_tags = ingest_extra_headers.pop(PayiHeaderNames.request_tags, None)
573
+
563
574
  experience_name = ingest_extra_headers.pop(PayiHeaderNames.experience_name, None)
564
575
  experience_id = ingest_extra_headers.pop(PayiHeaderNames.experience_id, None)
576
+
565
577
  use_case_name = ingest_extra_headers.pop(PayiHeaderNames.use_case_name, None)
566
578
  use_case_id = ingest_extra_headers.pop(PayiHeaderNames.use_case_id, None)
567
579
  use_case_version = ingest_extra_headers.pop(PayiHeaderNames.use_case_version, None)
580
+ use_case_step = ingest_extra_headers.pop(PayiHeaderNames.use_case_step, None)
581
+
568
582
  user_id = ingest_extra_headers.pop(PayiHeaderNames.user_id, None)
569
583
 
570
584
  if limit_ids:
@@ -581,6 +595,8 @@ class _PayiInstrumentor:
581
595
  request._ingest["use_case_id"] = use_case_id
582
596
  if use_case_version:
583
597
  request._ingest["use_case_version"] = int(use_case_version)
598
+ if use_case_step:
599
+ request._ingest["use_case_step"] = use_case_step
584
600
  if user_id:
585
601
  request._ingest["user_id"] = user_id
586
602
 
@@ -628,7 +644,9 @@ class _PayiInstrumentor:
628
644
  return await wrapped(*args, **kwargs)
629
645
 
630
646
  # after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
631
- extra_headers = kwargs.get("extra_headers", {})
647
+ extra_headers: Optional[dict[str, str]] = kwargs.get("extra_headers")
648
+ if extra_headers is None:
649
+ extra_headers = {}
632
650
  self._update_extra_headers(context, extra_headers)
633
651
 
634
652
  if context.get("proxy", self._proxy_default):
@@ -729,7 +747,9 @@ class _PayiInstrumentor:
729
747
  return wrapped(*args, **kwargs)
730
748
 
731
749
  # after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
732
- extra_headers = kwargs.get("extra_headers", {})
750
+ extra_headers: Optional[dict[str, str]] = kwargs.get("extra_headers")
751
+ if extra_headers is None:
752
+ extra_headers = {}
733
753
  self._update_extra_headers(context, extra_headers)
734
754
 
735
755
  if context.get("proxy", self._proxy_default):
@@ -836,14 +856,20 @@ class _PayiInstrumentor:
836
856
  extra_headers: "dict[str, str]",
837
857
  ) -> None:
838
858
  context_limit_ids: Optional[list[str]] = context.get("limit_ids")
859
+
839
860
  context_experience_name: Optional[str] = context.get("experience_name")
840
861
  context_experience_id: Optional[str] = context.get("experience_id")
862
+
841
863
  context_use_case_name: Optional[str] = context.get("use_case_name")
842
864
  context_use_case_id: Optional[str] = context.get("use_case_id")
843
865
  context_use_case_version: Optional[int] = context.get("use_case_version")
866
+ context_use_case_step: Optional[str] = context.get("use_case_step")
867
+
844
868
  context_user_id: Optional[str] = context.get("user_id")
845
869
  context_request_tags: Optional[list[str]] = context.get("request_tags")
846
- context_route_as_resource: Optional[str] = context.get("route_as_resource")
870
+
871
+ context_price_as_category: Optional[str] = context.get("price_as_category")
872
+ context_price_as_resource: Optional[str] = context.get("price_as_resource")
847
873
  context_resource_scope: Optional[str] = context.get("resource_scope")
848
874
 
849
875
  # headers_limit_ids = extra_headers.get(PayiHeaderNames.limit_ids, None)
@@ -879,6 +905,7 @@ class _PayiInstrumentor:
879
905
  extra_headers.pop(PayiHeaderNames.use_case_name, None)
880
906
  extra_headers.pop(PayiHeaderNames.use_case_id, None)
881
907
  extra_headers.pop(PayiHeaderNames.use_case_version, None)
908
+ extra_headers.pop(PayiHeaderNames.use_case_step, None)
882
909
  else:
883
910
  # leave the value in extra_headers
884
911
  ...
@@ -888,6 +915,8 @@ class _PayiInstrumentor:
888
915
  extra_headers[PayiHeaderNames.use_case_id] = context_use_case_id
889
916
  if context_use_case_version is not None:
890
917
  extra_headers[PayiHeaderNames.use_case_version] = str(context_use_case_version)
918
+ if context_use_case_step is not None:
919
+ extra_headers[PayiHeaderNames.use_case_step] = str(context_use_case_step)
891
920
 
892
921
  if PayiHeaderNames.experience_name in extra_headers:
893
922
  headers_experience_name = extra_headers.get(PayiHeaderNames.experience_name, None)
@@ -906,8 +935,11 @@ class _PayiInstrumentor:
906
935
  if PayiHeaderNames.request_tags not in extra_headers and context_request_tags:
907
936
  extra_headers[PayiHeaderNames.request_tags] = ",".join(context_request_tags)
908
937
 
909
- if PayiHeaderNames.route_as_resource not in extra_headers and context_route_as_resource:
910
- extra_headers[PayiHeaderNames.route_as_resource] = context_route_as_resource
938
+ if PayiHeaderNames.price_as_category not in extra_headers and context_price_as_category:
939
+ extra_headers[PayiHeaderNames.price_as_category] = context_price_as_category
940
+
941
+ if PayiHeaderNames.price_as_resource not in extra_headers and context_price_as_resource:
942
+ extra_headers[PayiHeaderNames.price_as_resource] = context_price_as_resource
911
943
 
912
944
  if PayiHeaderNames.resource_scope not in extra_headers and context_resource_scope:
913
945
  extra_headers[PayiHeaderNames.resource_scope] = context_resource_scope
@@ -1276,27 +1308,35 @@ def track_context(
1276
1308
  use_case_name: Optional[str] = None,
1277
1309
  use_case_id: Optional[str] = None,
1278
1310
  use_case_version: Optional[int] = None,
1311
+ use_case_step: Optional[str] = None,
1279
1312
  user_id: Optional[str] = None,
1280
1313
  request_tags: Optional["list[str]"] = None,
1281
- route_as_resource: Optional[str] = None,
1314
+ price_as_category: Optional[str] = None,
1315
+ price_as_resource: Optional[str] = None,
1282
1316
  resource_scope: Optional[str] = None,
1283
1317
  proxy: Optional[bool] = None,
1284
1318
  ) -> _TrackContext:
1285
1319
  # Create a new context for tracking
1286
1320
  context: _Context = {}
1321
+
1322
+ context["proxy"] = proxy
1323
+
1287
1324
  context["limit_ids"] = limit_ids
1325
+
1288
1326
  context["use_case_name"] = use_case_name
1289
1327
  context["use_case_id"] = use_case_id
1290
1328
  context["use_case_version"] = use_case_version
1329
+ context["use_case_step"] = use_case_step
1330
+
1291
1331
  context["user_id"] = user_id
1292
1332
  context["request_tags"] = request_tags
1293
- context["route_as_resource"] = route_as_resource
1333
+
1334
+ context["price_as_category"] = price_as_category
1335
+ context["price_as_resource"] = price_as_resource
1294
1336
  context["resource_scope"] = resource_scope
1295
- context["proxy"] = proxy
1296
1337
 
1297
1338
  return _TrackContext(context)
1298
1339
 
1299
-
1300
1340
  @deprecated("@ingest() is deprecated. Use @track() instead")
1301
1341
  def ingest(
1302
1342
  limit_ids: Optional["list[str]"] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: payi
3
- Version: 0.1.0a72
3
+ Version: 0.1.0a73
4
4
  Summary: The official Python library for the payi API
5
5
  Project-URL: Homepage, https://github.com/Pay-i/pay-i-python
6
6
  Project-URL: Repository, https://github.com/Pay-i/pay-i-python
@@ -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=Z_wIyo206T6Jqh2rolFg2VXZgX24PahLmpURp0-NssU,10092
13
13
  payi/_types.py,sha256=2mbMK86K3W1aMTW7sOGQ-VND6-A2IuXKm8p4sYFztBU,6141
14
- payi/_version.py,sha256=hfVsfey6OwITxk6zykXfOoMOxM8ZCWk-trl1AbSPzKg,165
14
+ payi/_version.py,sha256=rRHwLoOBgzQ6pHhvDPPLBSNIluq8Ov7y64GSnMSegaQ,165
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=PNZ_QJuzZEgyYXqkO1HVhGkj5IU9bglVUcw7H-Knjzw,2062
@@ -25,13 +25,13 @@ payi/_utils/_transform.py,sha256=n7kskEWz6o__aoNvhFoGVyDoalNe6mJwp-g7BWkdj88,156
25
25
  payi/_utils/_typing.py,sha256=D0DbbNu8GnYQTSICnTSHDGsYXj8TcAKyhejb0XcnjtY,4602
26
26
  payi/_utils/_utils.py,sha256=ts4CiiuNpFiGB6YMdkQRh2SZvYvsl7mAF-JWHCcLDf4,12312
27
27
  payi/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
28
- payi/lib/AnthropicInstrumentor.py,sha256=RMl_E5rfyMT9q3bGljFA4ba3RWyURPWfgWnZe0cHV_U,7123
28
+ payi/lib/AnthropicInstrumentor.py,sha256=o_20JNhvgRJIsGHfR0EKtWH8yLIBkafj4jKyzMDdFeI,7643
29
29
  payi/lib/BedrockInstrumentor.py,sha256=4IfOwq_MSGlvlSMxAocVu0G-Ctoir6nSL9KtpGfga8I,13969
30
- payi/lib/OpenAIInstrumentor.py,sha256=62aulfbZ0grdiYTDUHOiCKRHTwlrq5NHIRe6zUEMlPI,11618
30
+ payi/lib/OpenAIInstrumentor.py,sha256=e93o253yGO-L2qiLg69BO8mc9Q2_NAcvwRFDv9qSSm8,17404
31
31
  payi/lib/Stopwatch.py,sha256=7OJlxvr2Jyb6Zr1LYCYKczRB7rDVKkIR7gc4YoleNdE,764
32
32
  payi/lib/VertexInstrumentor.py,sha256=R0s3aEW7haj4aC5t1lMeA9siOgYvxlKQShLJ-B5iiNM,11642
33
- payi/lib/helpers.py,sha256=2Wy_CR9PZ8rn817R5sjRJDDdJ1gItNywk0t0wtaEvmc,4058
34
- payi/lib/instrument.py,sha256=H4cDhMD5MKrfhjfPGKqbZBerOukUp8y3jYx853W8cEA,54072
33
+ payi/lib/helpers.py,sha256=K1KAfWrpPT1UUGNxspLe1lHzQjP3XV5Pkh9IU4pKMok,4624
34
+ payi/lib/instrument.py,sha256=nUUEnyDJPNMgVvT1gRgSVX1T9D8IXvBG8UzUcmfM9ws,55743
35
35
  payi/resources/__init__.py,sha256=1rtrPLWbNt8oJGOp6nwPumKLJ-ftez0B6qwLFyfcoP4,2972
36
36
  payi/resources/ingest.py,sha256=8HNHEyfgIyJNqCh0rOhO9msoc61-8IyifJ6AbxjCrDg,22612
37
37
  payi/resources/categories/__init__.py,sha256=w5gMiPdBSzJA_qfoVtFBElaoe8wGf_O63R7R1Spr6Gk,1093
@@ -141,7 +141,7 @@ payi/types/use_cases/definitions/kpi_retrieve_response.py,sha256=uQXliSvS3k-yDYw
141
141
  payi/types/use_cases/definitions/kpi_update_params.py,sha256=jbawdWAdMnsTWVH0qfQGb8W7_TXe3lq4zjSRu44d8p8,373
142
142
  payi/types/use_cases/definitions/kpi_update_response.py,sha256=zLyEoT0S8d7XHsnXZYT8tM7yDw0Aze0Mk-_Z6QeMtc8,459
143
143
  payi/types/use_cases/definitions/limit_config_create_params.py,sha256=pzQza_16N3z8cFNEKr6gPbFvuGFrwNuGxAYb--Kbo2M,449
144
- payi-0.1.0a72.dist-info/METADATA,sha256=6hbBjGCYyCvoUpQZnptSPqVUty2HmtPeAN8azuMtvUA,15290
145
- payi-0.1.0a72.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
146
- payi-0.1.0a72.dist-info/licenses/LICENSE,sha256=CQt03aM-P4a3Yg5qBg3JSLVoQS3smMyvx7tYg_6V7Gk,11334
147
- payi-0.1.0a72.dist-info/RECORD,,
144
+ payi-0.1.0a73.dist-info/METADATA,sha256=mqN0CMTKKA4ABurkrddTjGtEhu9ahrtAAwkRFjY4Nk8,15290
145
+ payi-0.1.0a73.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
146
+ payi-0.1.0a73.dist-info/licenses/LICENSE,sha256=CQt03aM-P4a3Yg5qBg3JSLVoQS3smMyvx7tYg_6V7Gk,11334
147
+ payi-0.1.0a73.dist-info/RECORD,,