payi 0.1.0a125__py3-none-any.whl → 0.1.0a127__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.

Files changed (31) hide show
  1. payi/_version.py +1 -1
  2. payi/lib/BedrockInstrumentor.py +35 -25
  3. payi/lib/helpers.py +12 -0
  4. payi/lib/instrument.py +136 -143
  5. payi/resources/categories/__init__.py +0 -14
  6. payi/resources/categories/categories.py +0 -32
  7. payi/resources/categories/resources.py +4 -0
  8. payi/resources/ingest.py +20 -8
  9. payi/resources/requests/request_id/properties.py +3 -3
  10. payi/resources/requests/response_id/properties.py +3 -3
  11. payi/resources/use_cases/properties.py +3 -3
  12. payi/types/__init__.py +2 -0
  13. payi/types/bulk_ingest_response.py +3 -20
  14. payi/types/categories/__init__.py +0 -1
  15. payi/types/categories/resource_list_params.py +5 -1
  16. payi/types/category_resource_response.py +31 -1
  17. payi/types/ingest_event_param.py +2 -2
  18. payi/types/ingest_units_params.py +2 -2
  19. payi/types/requests/request_id/property_update_params.py +2 -2
  20. payi/types/requests/response_id/property_update_params.py +2 -2
  21. payi/types/shared/__init__.py +2 -0
  22. payi/types/shared/api_error.py +18 -0
  23. payi/types/shared/properties_request.py +11 -0
  24. payi/types/shared/xproxy_result.py +2 -0
  25. payi/types/use_cases/property_update_params.py +2 -2
  26. {payi-0.1.0a125.dist-info → payi-0.1.0a127.dist-info}/METADATA +1 -1
  27. {payi-0.1.0a125.dist-info → payi-0.1.0a127.dist-info}/RECORD +29 -29
  28. payi/resources/categories/fixed_cost_resources.py +0 -196
  29. payi/types/categories/fixed_cost_resource_create_params.py +0 -22
  30. {payi-0.1.0a125.dist-info → payi-0.1.0a127.dist-info}/WHEEL +0 -0
  31. {payi-0.1.0a125.dist-info → payi-0.1.0a127.dist-info}/licenses/LICENSE +0 -0
payi/lib/instrument.py CHANGED
@@ -2,13 +2,14 @@ import os
2
2
  import json
3
3
  import time
4
4
  import uuid
5
+ import atexit
5
6
  import asyncio
6
7
  import inspect
7
8
  import logging
8
9
  import traceback
9
10
  from abc import abstractmethod
10
11
  from enum import Enum
11
- from typing import Any, Set, Union, Callable, Optional, Sequence, TypedDict
12
+ from typing import Any, Set, Union, Optional, Sequence, TypedDict, cast
12
13
  from datetime import datetime, timezone
13
14
  from dataclasses import dataclass
14
15
 
@@ -17,7 +18,7 @@ from wrapt import ObjectProxy # type: ignore
17
18
 
18
19
  from payi import Payi, AsyncPayi, APIStatusError, APIConnectionError, __version__ as _payi_version
19
20
  from payi.types import IngestUnitsParams
20
- from payi.lib.helpers import PayiHeaderNames
21
+ from payi.lib.helpers import PayiHeaderNames, PayiPropertyNames
21
22
  from payi.types.shared import XproxyResult
22
23
  from payi.types.ingest_response import IngestResponse
23
24
  from payi.types.ingest_units_params import Units, ProviderResponseFunctionCall
@@ -59,7 +60,7 @@ class _ProviderRequest:
59
60
  self._building_function_response: bool = False
60
61
  self._function_calls: Optional[list[ProviderResponseFunctionCall]] = None
61
62
  self._is_large_context: bool = False
62
- self._internal_request_properties: dict[str, str] = {}
63
+ self._internal_request_properties: dict[str, Optional[str]] = {}
63
64
 
64
65
  def process_chunk(self, _chunk: Any) -> _ChunkResult:
65
66
  return _ChunkResult(send_chunk_to_caller=True)
@@ -117,10 +118,10 @@ class _ProviderRequest:
117
118
  except Exception as _ex:
118
119
  pass
119
120
 
120
- self.add_internal_request_property('system.failure', exception_str)
121
+ self.add_internal_request_property(PayiPropertyNames.failure, exception_str)
121
122
  if fields:
122
123
  failure_description = ",".join(fields)
123
- self.add_internal_request_property("system.failure.description", failure_description)
124
+ self.add_internal_request_property(PayiPropertyNames.failure_description, failure_description)
124
125
 
125
126
  if "http_status_code" not in self._ingest:
126
127
  # use a non existent http status code so when presented to the user, the origin is clear
@@ -148,6 +149,9 @@ class _ProviderRequest:
148
149
  class PayiInstrumentAwsBedrockConfig(TypedDict, total=False):
149
150
  guardrail_trace: bool
150
151
 
152
+ class PayiInstrumentOfflineInstrumentationConfig(TypedDict, total=False):
153
+ file_name: str
154
+
151
155
  class PayiInstrumentConfig(TypedDict, total=False):
152
156
  proxy: bool
153
157
  global_instrumentation: bool
@@ -157,24 +161,25 @@ class PayiInstrumentConfig(TypedDict, total=False):
157
161
  use_case_name: Optional[str]
158
162
  use_case_id: Optional[str]
159
163
  use_case_version: Optional[int]
160
- use_case_properties: Optional["dict[str, str]"]
164
+ use_case_properties: Optional["dict[str, Optional[str]]"]
161
165
  user_id: Optional[str]
162
166
  account_name: Optional[str]
163
167
  request_tags: Optional["list[str]"]
164
- request_properties: Optional["dict[str, str]"]
168
+ request_properties: Optional["dict[str, Optional[str]]"]
165
169
  aws_config: Optional[PayiInstrumentAwsBedrockConfig]
170
+ offline_instrumentation: Optional[PayiInstrumentOfflineInstrumentationConfig]
166
171
 
167
172
  class PayiContext(TypedDict, total=False):
168
173
  use_case_name: Optional[str]
169
174
  use_case_id: Optional[str]
170
175
  use_case_version: Optional[int]
171
176
  use_case_step: Optional[str]
172
- use_case_properties: Optional["dict[str, str]"]
177
+ use_case_properties: Optional["dict[str, Optional[str]]"]
173
178
  limit_ids: Optional['list[str]']
174
179
  user_id: Optional[str]
175
180
  account_name: Optional[str]
176
181
  request_tags: Optional["list[str]"]
177
- request_properties: Optional["dict[str, str]"]
182
+ request_properties: Optional["dict[str, Optional[str]]"]
178
183
  price_as_category: Optional[str]
179
184
  price_as_resource: Optional[str]
180
185
  resource_scope: Optional[str]
@@ -186,12 +191,11 @@ class _Context(TypedDict, total=False):
186
191
  use_case_id: Optional[str]
187
192
  use_case_version: Optional[int]
188
193
  use_case_step: Optional[str]
189
- use_case_properties: Optional["dict[str, str]"]
194
+ use_case_properties: Optional["dict[str, Optional[str]]"]
190
195
  limit_ids: Optional['list[str]']
191
196
  user_id: Optional[str]
192
197
  account_name: Optional[str]
193
- request_tags: Optional["list[str]"]
194
- request_properties: Optional["dict[str, str]"]
198
+ request_properties: Optional["dict[str, Optional[str]]"]
195
199
  price_as_category: Optional[str]
196
200
  price_as_resource: Optional[str]
197
201
  resource_scope: Optional[str]
@@ -236,9 +240,6 @@ class _PayiInstrumentor:
236
240
  instruments: Union[Set[str], None] = None,
237
241
  log_prompt_and_response: bool = True,
238
242
  logger: Optional[logging.Logger] = None,
239
- prompt_and_response_logger: Optional[
240
- Callable[[str, "dict[str, str]"], None]
241
- ] = None, # (request id, dict of data to store) -> None
242
243
  global_config: PayiInstrumentConfig = {},
243
244
  caller_filename: str = ""
244
245
  ):
@@ -257,7 +258,6 @@ class _PayiInstrumentor:
257
258
 
258
259
  self._context_stack: list[_Context] = [] # Stack of context dictionaries
259
260
  self._log_prompt_and_response: bool = log_prompt_and_response
260
- self._prompt_and_response_logger: Optional[Callable[[str, dict[str, str]], None]] = prompt_and_response_logger
261
261
 
262
262
  self._blocked_limits: set[str] = set()
263
263
  self._exceeded_limits: set[str] = set()
@@ -275,6 +275,17 @@ class _PayiInstrumentor:
275
275
 
276
276
  self._last_result: Optional[Union[XproxyResult, XproxyError]] = None
277
277
 
278
+ self._offline_instrumentation = global_config.pop("offline_instrumentation", None)
279
+ self._offline_ingest_packets: list[IngestUnitsParams] = []
280
+ self._offline_instrumentation_file_name: Optional[str] = None
281
+
282
+ if self._offline_instrumentation is not None:
283
+ timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
284
+ self._offline_instrumentation_file_name = self._offline_instrumentation.get("file_name", f"payi_instrumentation_{timestamp}.json")
285
+
286
+ # Register exit handler to write packets when process exits
287
+ atexit.register(lambda: self._write_offline_ingest_packets())
288
+
278
289
  global_instrumentation = global_config.pop("global_instrumentation", True)
279
290
 
280
291
  if instruments is None or "*" in instruments:
@@ -287,9 +298,7 @@ class _PayiInstrumentor:
287
298
  global_config["proxy"] = self._proxy_default
288
299
 
289
300
  # Use default clients if not provided for global ingest instrumentation
290
- if not self._payi and not self._apayi:
291
- self._payi = Payi()
292
- self._apayi = AsyncPayi()
301
+ self._ensure_payi_clients()
293
302
 
294
303
  if "use_case_name" not in global_config and caller_filename:
295
304
  description = f"Default use case for {caller_filename}.py"
@@ -298,6 +307,9 @@ class _PayiInstrumentor:
298
307
  self._payi.use_cases.definitions.create(name=caller_filename, description=description)
299
308
  elif self._apayi:
300
309
  self._call_async_use_case_definition_create(use_case_name=caller_filename, use_case_description=description)
310
+ else:
311
+ # in the case of _local_instrumentation is not None
312
+ pass
301
313
  global_config["use_case_name"] = caller_filename
302
314
  except Exception as e:
303
315
  self._logger.error(f"Error creating default use case definition based on file name {caller_filename}: {e}")
@@ -315,6 +327,14 @@ class _PayiInstrumentor:
315
327
 
316
328
  self._init_current_context(**context)
317
329
 
330
+ def _ensure_payi_clients(self) -> None:
331
+ if self._offline_instrumentation is not None:
332
+ return
333
+
334
+ if not self._payi and not self._apayi:
335
+ self._payi = Payi()
336
+ self._apayi = AsyncPayi()
337
+
318
338
  def _instrument_all(self, global_config: PayiInstrumentConfig) -> None:
319
339
  self._instrument_openai()
320
340
  self._instrument_anthropic()
@@ -378,6 +398,30 @@ class _PayiInstrumentor:
378
398
  except Exception as e:
379
399
  self._logger.error(f"Error instrumenting Google GenAi: {e}")
380
400
 
401
+ def _write_offline_ingest_packets(self) -> None:
402
+ if not self._offline_instrumentation_file_name or not self._offline_ingest_packets:
403
+ return
404
+
405
+ try:
406
+ # Convert datetime objects to ISO strings for JSON serialization
407
+ serializable_packets: list[IngestUnitsParams] = []
408
+ for packet in self._offline_ingest_packets:
409
+ serializable_packet = packet.copy()
410
+
411
+ # Convert datetime fields to ISO format strings
412
+ if 'event_timestamp' in serializable_packet and isinstance(serializable_packet['event_timestamp'], datetime):
413
+ serializable_packet['event_timestamp'] = serializable_packet['event_timestamp'].isoformat()
414
+
415
+ serializable_packets.append(serializable_packet)
416
+
417
+ with open(self._offline_instrumentation_file_name, 'w', encoding='utf-8') as f:
418
+ json.dump(serializable_packets, f)
419
+
420
+ self._logger.debug(f"Written {len(self._offline_ingest_packets)} ingest packets to {self._offline_instrumentation_file_name}")
421
+
422
+ except Exception as e:
423
+ self._logger.error(f"Error writing offline ingest packets to {self._offline_instrumentation_file_name}: {e}")
424
+
381
425
  @staticmethod
382
426
  def _create_logged_ingest_units(
383
427
  ingest_units: IngestUnitsParams,
@@ -397,7 +441,6 @@ class _PayiInstrumentor:
397
441
  def _process_ingest_units(
398
442
  self,
399
443
  request: _ProviderRequest,
400
- log_data: 'dict[str, str]',
401
444
  extra_headers: 'dict[str, str]') -> None:
402
445
  ingest_units = request._ingest
403
446
 
@@ -408,6 +451,9 @@ class _PayiInstrumentor:
408
451
  # convert the function call builder to a list of function calls
409
452
  ingest_units["provider_response_function_calls"] = list(request._function_call_builder.values())
410
453
 
454
+ if "provider_response_id" not in ingest_units or not ingest_units["provider_response_id"]:
455
+ ingest_units["provider_response_id"] = f"payi_{uuid.uuid4()}"
456
+
411
457
  if 'resource' not in ingest_units or ingest_units['resource'] == '':
412
458
  ingest_units['resource'] = "system.unknown_model"
413
459
 
@@ -435,19 +481,6 @@ class _PayiInstrumentor:
435
481
  if not units or all(unit.get("input", 0) == 0 and unit.get("output", 0) == 0 for unit in units.values()):
436
482
  self._logger.info('ingesting with no token counts')
437
483
 
438
- if self._log_prompt_and_response and self._prompt_and_response_logger:
439
- response_json = ingest_units.pop("provider_response_json", None)
440
- request_json = ingest_units.pop("provider_request_json", None)
441
- stack_trace = ingest_units.get("properties", {}).pop("system.stack_trace", None) # type: ignore
442
-
443
- if response_json is not None:
444
- # response_json is a list of strings, convert a single json string
445
- log_data["provider_response_json"] = json.dumps(response_json)
446
- if request_json is not None:
447
- log_data["provider_request_json"] = request_json
448
- if stack_trace is not None:
449
- log_data["stack_trace"] = stack_trace
450
-
451
484
  def _process_ingest_units_response(self, ingest_response: IngestResponse) -> None:
452
485
  if ingest_response.xproxy_result.limits:
453
486
  for limit_id, state in ingest_response.xproxy_result.limits.items():
@@ -490,11 +523,8 @@ class _PayiInstrumentor:
490
523
 
491
524
  self._logger.debug(f"_aingest_units")
492
525
 
493
- # return early if there are no units to ingest and on a successul ingest request
494
- log_data: 'dict[str,str]' = {}
495
526
  extra_headers: 'dict[str, str]' = {}
496
-
497
- self._process_ingest_units(request, log_data=log_data, extra_headers=extra_headers)
527
+ self._process_ingest_units(request, extra_headers=extra_headers)
498
528
 
499
529
  try:
500
530
  if self._logger.isEnabledFor(logging.DEBUG):
@@ -504,6 +534,18 @@ class _PayiInstrumentor:
504
534
  ingest_response = await self._apayi.ingest.units(**ingest_units, extra_headers=extra_headers)
505
535
  elif self._payi:
506
536
  ingest_response = self._payi.ingest.units(**ingest_units, extra_headers=extra_headers)
537
+ elif self._offline_instrumentation is not None:
538
+ self._offline_ingest_packets.append(ingest_units.copy())
539
+
540
+ # simulate a successful ingest for local instrumentation
541
+ now=datetime.now(timezone.utc)
542
+ ingest_response = IngestResponse(
543
+ event_timestamp=now,
544
+ ingest_timestamp=now,
545
+ request_id="local_instrumentation",
546
+ xproxy_result=XproxyResult(request_id="local_instrumentation"))
547
+ pass
548
+
507
549
  else:
508
550
  self._logger.error("No payi instance to ingest units")
509
551
  return XproxyError(code="configuration_error", message="No Payi or AsyncPayi instance configured for ingesting units")
@@ -513,10 +555,6 @@ class _PayiInstrumentor:
513
555
  if ingest_response:
514
556
  self._process_ingest_units_response(ingest_response)
515
557
 
516
- if ingest_response and self._log_prompt_and_response and self._prompt_and_response_logger:
517
- request_id = ingest_response.xproxy_result.request_id
518
- self._prompt_and_response_logger(request_id, log_data) # type: ignore
519
-
520
558
  return ingest_response.xproxy_result
521
559
 
522
560
  except APIConnectionError as api_ex:
@@ -609,10 +647,8 @@ class _PayiInstrumentor:
609
647
 
610
648
  self._logger.debug(f"_ingest_units")
611
649
 
612
- # return early if there are no units to ingest and on a successul ingest request
613
- log_data: 'dict[str,str]' = {}
614
650
  extra_headers: 'dict[str, str]' = {}
615
- self._process_ingest_units(request, log_data=log_data, extra_headers=extra_headers)
651
+ self._process_ingest_units(request, extra_headers=extra_headers)
616
652
 
617
653
  try:
618
654
  if self._payi:
@@ -624,16 +660,18 @@ class _PayiInstrumentor:
624
660
 
625
661
  self._process_ingest_units_response(ingest_response)
626
662
 
627
- if self._log_prompt_and_response and self._prompt_and_response_logger:
628
- request_id = ingest_response.xproxy_result.request_id
629
- self._prompt_and_response_logger(request_id, log_data) # type: ignore
630
-
631
663
  return ingest_response.xproxy_result
632
664
  elif self._apayi:
633
665
  # task runs async. aingest_units will invoke the callback and post process
634
666
  sync_response = self._call_aingest_sync(request)
635
667
  self._logger.debug(f"_ingest_units: apayi success ({sync_response})")
636
668
  return sync_response
669
+ elif self._offline_instrumentation is not None:
670
+ self._offline_ingest_packets.append(ingest_units.copy())
671
+
672
+ # simulate a successful ingest for local instrumentation
673
+ return XproxyResult(request_id="local_instrumentation")
674
+
637
675
  else:
638
676
  self._logger.error("No payi instance to ingest units")
639
677
  return XproxyError(code="configuration_error", message="No Payi or AsyncPayi instance configured for ingesting units")
@@ -661,6 +699,31 @@ class _PayiInstrumentor:
661
699
 
662
700
  return {}
663
701
 
702
+ @staticmethod
703
+ def _valid_str_or_none(value: Optional[str], default: Optional[str] = None) -> Optional[str]:
704
+ if value is None:
705
+ return default
706
+ elif len(value) == 0:
707
+ # an empty string explicitly blocks the default value
708
+ return None
709
+ else:
710
+ return value
711
+
712
+ @staticmethod
713
+ def _valid_properties_or_none(value: Optional["dict[str, Optional[str]]"], default: Optional["dict[str, Optional[str]]"] = None) -> Optional["dict[str, Optional[str]]"]:
714
+ if value is None:
715
+ return default.copy() if default else None
716
+ elif len(value) == 0:
717
+ # an empty dictionary explicitly blocks the default value
718
+ return None
719
+ elif default:
720
+ # merge dictionaries, child overrides parent keys
721
+ merged = default.copy()
722
+ merged.update(value)
723
+ return merged
724
+ else:
725
+ return value.copy()
726
+
664
727
  def _init_current_context(
665
728
  self,
666
729
  proxy: Optional[bool] = None,
@@ -671,9 +734,8 @@ class _PayiInstrumentor:
671
734
  use_case_step: Optional[str]= None,
672
735
  user_id: Optional[str]= None,
673
736
  account_name: Optional[str]= None,
674
- request_tags: Optional["list[str]"] = None,
675
- request_properties: Optional["dict[str, str]"] = None,
676
- use_case_properties: Optional["dict[str, str]"] = None,
737
+ request_properties: Optional["dict[str, Optional[str]]"] = None,
738
+ use_case_properties: Optional["dict[str, Optional[str]]"] = None,
677
739
  price_as_category: Optional[str] = None,
678
740
  price_as_resource: Optional[str] = None,
679
741
  resource_scope: Optional[str] = None,
@@ -689,7 +751,6 @@ class _PayiInstrumentor:
689
751
  parent_use_case_name = parent_context.get("use_case_name", None)
690
752
  parent_use_case_id = parent_context.get("use_case_id", None)
691
753
  parent_use_case_version = parent_context.get("use_case_version", None)
692
- parent_use_case_step = parent_context.get("use_case_step", None)
693
754
 
694
755
  assign_use_case_values = False
695
756
 
@@ -721,26 +782,12 @@ class _PayiInstrumentor:
721
782
  assign_use_case_values = True
722
783
 
723
784
  if assign_use_case_values:
724
- context["use_case_id"] = use_case_id if use_case_id else parent_use_case_id
725
- context["use_case_version"] = use_case_version if use_case_version else parent_use_case_version
726
- context["use_case_step"] = use_case_step if use_case_step else parent_use_case_step
785
+ context["use_case_version"] = use_case_version if use_case_version is not None else parent_use_case_version
786
+ context["use_case_id"] = self._valid_str_or_none(use_case_id, parent_use_case_id)
787
+ context["use_case_step"] = self._valid_str_or_none(use_case_step, None)
727
788
 
728
789
  parent_use_case_properties = parent_context.get("use_case_properties", None)
729
- if use_case_properties is not None:
730
- if not use_case_properties:
731
- # an empty dictionary explicitly blocks inheriting from the parent state
732
- context["use_case_properties"] = None
733
- else:
734
- if parent_use_case_properties:
735
- # merge dictionaries, child overrides parent keys
736
- merged = parent_use_case_properties.copy()
737
- merged.update(use_case_properties)
738
- context["use_case_properties"] = merged
739
- else:
740
- context["use_case_properties"] = use_case_properties.copy()
741
- elif parent_use_case_properties:
742
- # use the parent use_case_properties if it exists
743
- context["use_case_properties"] = parent_use_case_properties.copy()
790
+ context["use_case_properties"] = self._valid_properties_or_none(use_case_properties, parent_use_case_properties)
744
791
 
745
792
  parent_limit_ids = parent_context.get("limit_ids", None)
746
793
  if limit_ids is None:
@@ -754,56 +801,13 @@ class _PayiInstrumentor:
754
801
  context["limit_ids"] = list(set(limit_ids) | set(parent_limit_ids)) if parent_limit_ids else limit_ids.copy()
755
802
 
756
803
  parent_user_id = parent_context.get("user_id", None)
757
- if user_id is None:
758
- # use the parent user_id if it exists
759
- context["user_id"] = parent_user_id
760
- elif len(user_id) == 0:
761
- # caller passing an empty string explicitly blocks inheriting from the parent state
762
- context["user_id"] = None
763
- else:
764
- context["user_id"] = user_id
804
+ context["user_id"] = self._valid_str_or_none(user_id, parent_user_id)
765
805
 
766
806
  parent_account_name = parent_context.get("account_name", None)
767
- if account_name is None:
768
- # use the parent account_name if it exists
769
- context["account_name"] = parent_account_name
770
- elif len(account_name) == 0:
771
- # caller passing an empty string explicitly blocks inheriting from the parent state
772
- context["account_name"] = None
773
- else:
774
- context["account_name"] = account_name
775
-
776
- parent_request_tags = parent_context.get("request_tags", None)
777
- if request_tags is not None:
778
- if len(request_tags) == 0:
779
- # caller passing an empty list explicitly blocks inheriting from the parent state
780
- context["request_tags"] = None
781
- else:
782
- if parent_request_tags:
783
- # union of new and parent lists if the parent context contains request tags
784
- context["request_tags"] = list(set(request_tags) | set(parent_request_tags))
785
- else:
786
- context["request_tags"] = request_tags.copy()
787
- elif parent_request_tags:
788
- # use the parent request_tags if it exists
789
- context["request_tags"] = parent_request_tags.copy()
807
+ context["account_name"] = self._valid_str_or_none(account_name, parent_account_name)
790
808
 
791
809
  parent_request_properties = parent_context.get("request_properties", None)
792
- if request_properties is not None:
793
- if not request_properties:
794
- # an empty dictionary explicitly blocks inheriting from the parent state
795
- context["request_properties"] = None
796
- else:
797
- if parent_request_properties:
798
- # merge dictionaries, child overrides parent keys
799
- merged = parent_request_properties.copy()
800
- merged.update(request_properties)
801
- context["request_properties"] = merged
802
- else:
803
- context["request_properties"] = request_properties.copy()
804
- elif parent_request_properties:
805
- # use the parent request_properties if it exists
806
- context["request_properties"] = parent_request_properties.copy()
810
+ context["request_properties"] = self._valid_properties_or_none(request_properties, parent_request_properties)
807
811
 
808
812
  if price_as_category:
809
813
  context["price_as_category"] = price_as_category
@@ -822,9 +826,8 @@ class _PayiInstrumentor:
822
826
  use_case_version: Optional[int],
823
827
  user_id: Optional[str],
824
828
  account_name: Optional[str],
825
- request_tags: Optional["list[str]"] = None,
826
- request_properties: Optional["dict[str, str]"] = None,
827
- use_case_properties: Optional["dict[str, str]"] = None,
829
+ request_properties: Optional["dict[str, Optional[str]]"] = None,
830
+ use_case_properties: Optional["dict[str, Optional[str]]"] = None,
828
831
  *args: Any,
829
832
  **kwargs: Any,
830
833
  ) -> Any:
@@ -837,7 +840,6 @@ class _PayiInstrumentor:
837
840
  use_case_version=use_case_version,
838
841
  user_id=user_id,
839
842
  account_name=account_name,
840
- request_tags=request_tags,
841
843
  request_properties=request_properties,
842
844
  use_case_properties=use_case_properties
843
845
  )
@@ -853,9 +855,8 @@ class _PayiInstrumentor:
853
855
  use_case_version: Optional[int],
854
856
  user_id: Optional[str],
855
857
  account_name: Optional[str],
856
- request_tags: Optional["list[str]"] = None,
857
- request_properties: Optional["dict[str, str]"] = None,
858
- use_case_properties: Optional["dict[str, str]"] = None,
858
+ request_properties: Optional["dict[str, Optional[str]]"] = None,
859
+ use_case_properties: Optional["dict[str, Optional[str]]"] = None,
859
860
  *args: Any,
860
861
  **kwargs: Any,
861
862
  ) -> Any:
@@ -868,7 +869,6 @@ class _PayiInstrumentor:
868
869
  use_case_version=use_case_version,
869
870
  user_id=user_id,
870
871
  account_name=account_name,
871
- request_tags=request_tags,
872
872
  request_properties=request_properties,
873
873
  use_case_properties=use_case_properties)
874
874
  return func(*args, **kwargs)
@@ -901,7 +901,8 @@ class _PayiInstrumentor:
901
901
  ) -> None:
902
902
 
903
903
  limit_ids = ingest_extra_headers.pop(PayiHeaderNames.limit_ids, None)
904
- request_tags = ingest_extra_headers.pop(PayiHeaderNames.request_tags, None)
904
+ # pop and ignore the request tags header since it is no longer processed
905
+ ingest_extra_headers.pop(PayiHeaderNames.request_tags, None)
905
906
 
906
907
  use_case_name = ingest_extra_headers.pop(PayiHeaderNames.use_case_name, None)
907
908
  use_case_id = ingest_extra_headers.pop(PayiHeaderNames.use_case_id, None)
@@ -913,8 +914,6 @@ class _PayiInstrumentor:
913
914
 
914
915
  if limit_ids:
915
916
  request._ingest["limit_ids"] = limit_ids.split(",")
916
- if request_tags:
917
- request._ingest["request_tags"] = request_tags.split(",")
918
917
  if use_case_name:
919
918
  request._ingest["use_case_name"] = use_case_name
920
919
  if use_case_id:
@@ -1259,7 +1258,6 @@ class _PayiInstrumentor:
1259
1258
 
1260
1259
  context_user_id: Optional[str] = context.get("user_id")
1261
1260
  context_account_name: Optional[str] = context.get("account_name")
1262
- context_request_tags: Optional[list[str]] = context.get("request_tags")
1263
1261
 
1264
1262
  context_price_as_category: Optional[str] = context.get("price_as_category")
1265
1263
  context_price_as_resource: Optional[str] = context.get("price_as_resource")
@@ -1320,10 +1318,7 @@ class _PayiInstrumentor:
1320
1318
  if context_use_case_version is not None:
1321
1319
  extra_headers[PayiHeaderNames.use_case_version] = str(context_use_case_version)
1322
1320
  if context_use_case_step is not None:
1323
- extra_headers[PayiHeaderNames.use_case_step] = str(context_use_case_step)
1324
-
1325
- if PayiHeaderNames.request_tags not in extra_headers and context_request_tags:
1326
- extra_headers[PayiHeaderNames.request_tags] = ",".join(context_request_tags)
1321
+ extra_headers[PayiHeaderNames.use_case_step] = context_use_case_step
1327
1322
 
1328
1323
  if PayiHeaderNames.price_as_category not in extra_headers and context_price_as_category:
1329
1324
  extra_headers[PayiHeaderNames.price_as_category] = context_price_as_category
@@ -1730,7 +1725,6 @@ def payi_instrument(
1730
1725
  payi: Optional[Union[Payi, AsyncPayi, 'list[Union[Payi, AsyncPayi]]']] = None,
1731
1726
  instruments: Optional[Set[str]] = None,
1732
1727
  log_prompt_and_response: bool = True,
1733
- prompt_and_response_logger: Optional[Callable[[str, "dict[str, str]"], None]] = None,
1734
1728
  config: Optional[PayiInstrumentConfig] = None,
1735
1729
  logger: Optional[logging.Logger] = None,
1736
1730
  ) -> None:
@@ -1763,7 +1757,6 @@ def payi_instrument(
1763
1757
  instruments=instruments,
1764
1758
  log_prompt_and_response=log_prompt_and_response,
1765
1759
  logger=logger,
1766
- prompt_and_response_logger=prompt_and_response_logger,
1767
1760
  global_config=config if config else PayiInstrumentConfig(),
1768
1761
  caller_filename=caller_filename
1769
1762
  )
@@ -1781,6 +1774,7 @@ def track(
1781
1774
  use_case_properties: Optional["dict[str, str]"] = None,
1782
1775
  proxy: Optional[bool] = None,
1783
1776
  ) -> Any:
1777
+ _ = request_tags
1784
1778
 
1785
1779
  def _track(func: Any) -> Any:
1786
1780
  import asyncio
@@ -1801,9 +1795,8 @@ def track(
1801
1795
  use_case_version,
1802
1796
  user_id,
1803
1797
  account_name,
1804
- request_tags,
1805
- request_properties,
1806
- use_case_properties,
1798
+ cast(Optional['dict[str, Optional[str]]'], request_properties),
1799
+ cast(Optional['dict[str, Optional[str]]'], use_case_properties),
1807
1800
  *args,
1808
1801
  **kwargs,
1809
1802
  )
@@ -1825,9 +1818,8 @@ def track(
1825
1818
  use_case_version,
1826
1819
  user_id,
1827
1820
  account_name,
1828
- request_tags,
1829
- request_properties,
1830
- use_case_properties,
1821
+ cast(Optional['dict[str, Optional[str]]'], request_properties),
1822
+ cast(Optional['dict[str, Optional[str]]'], use_case_properties),
1831
1823
  *args,
1832
1824
  **kwargs,
1833
1825
  )
@@ -1865,14 +1857,15 @@ def track_context(
1865
1857
 
1866
1858
  context["user_id"] = user_id
1867
1859
  context["account_name"] = account_name
1868
- context["request_tags"] = request_tags
1869
1860
 
1870
1861
  context["price_as_category"] = price_as_category
1871
1862
  context["price_as_resource"] = price_as_resource
1872
1863
  context["resource_scope"] = resource_scope
1873
1864
 
1874
- context["request_properties"] = request_properties
1875
- context["use_case_properties"] = use_case_properties
1865
+ context["request_properties"] = cast(Optional['dict[str, Optional[str]]'], request_properties)
1866
+ context["use_case_properties"] = cast(Optional['dict[str, Optional[str]]'], use_case_properties)
1867
+
1868
+ _ = request_tags
1876
1869
 
1877
1870
  return _InternalTrackContext(context)
1878
1871
 
@@ -16,14 +16,6 @@ from .categories import (
16
16
  CategoriesResourceWithStreamingResponse,
17
17
  AsyncCategoriesResourceWithStreamingResponse,
18
18
  )
19
- from .fixed_cost_resources import (
20
- FixedCostResourcesResource,
21
- AsyncFixedCostResourcesResource,
22
- FixedCostResourcesResourceWithRawResponse,
23
- AsyncFixedCostResourcesResourceWithRawResponse,
24
- FixedCostResourcesResourceWithStreamingResponse,
25
- AsyncFixedCostResourcesResourceWithStreamingResponse,
26
- )
27
19
 
28
20
  __all__ = [
29
21
  "ResourcesResource",
@@ -32,12 +24,6 @@ __all__ = [
32
24
  "AsyncResourcesResourceWithRawResponse",
33
25
  "ResourcesResourceWithStreamingResponse",
34
26
  "AsyncResourcesResourceWithStreamingResponse",
35
- "FixedCostResourcesResource",
36
- "AsyncFixedCostResourcesResource",
37
- "FixedCostResourcesResourceWithRawResponse",
38
- "AsyncFixedCostResourcesResourceWithRawResponse",
39
- "FixedCostResourcesResourceWithStreamingResponse",
40
- "AsyncFixedCostResourcesResourceWithStreamingResponse",
41
27
  "CategoriesResource",
42
28
  "AsyncCategoriesResource",
43
29
  "CategoriesResourceWithRawResponse",