payi 0.1.0a114__py3-none-any.whl → 0.1.0a116__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/_utils/_transform.py CHANGED
@@ -16,18 +16,20 @@ from ._utils import (
16
16
  lru_cache,
17
17
  is_mapping,
18
18
  is_iterable,
19
+ is_sequence,
19
20
  )
20
21
  from .._files import is_base64_file_input
22
+ from ._compat import get_origin, is_typeddict
21
23
  from ._typing import (
22
24
  is_list_type,
23
25
  is_union_type,
24
26
  extract_type_arg,
25
27
  is_iterable_type,
26
28
  is_required_type,
29
+ is_sequence_type,
27
30
  is_annotated_type,
28
31
  strip_annotated_type,
29
32
  )
30
- from .._compat import get_origin, model_dump, is_typeddict
31
33
 
32
34
  _T = TypeVar("_T")
33
35
 
@@ -167,6 +169,8 @@ def _transform_recursive(
167
169
 
168
170
  Defaults to the same value as the `annotation` argument.
169
171
  """
172
+ from .._compat import model_dump
173
+
170
174
  if inner_type is None:
171
175
  inner_type = annotation
172
176
 
@@ -184,6 +188,8 @@ def _transform_recursive(
184
188
  (is_list_type(stripped_type) and is_list(data))
185
189
  # Iterable[T]
186
190
  or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
191
+ # Sequence[T]
192
+ or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
187
193
  ):
188
194
  # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
189
195
  # intended as an iterable, so we don't transform it.
@@ -329,6 +335,8 @@ async def _async_transform_recursive(
329
335
 
330
336
  Defaults to the same value as the `annotation` argument.
331
337
  """
338
+ from .._compat import model_dump
339
+
332
340
  if inner_type is None:
333
341
  inner_type = annotation
334
342
 
@@ -346,6 +354,8 @@ async def _async_transform_recursive(
346
354
  (is_list_type(stripped_type) and is_list(data))
347
355
  # Iterable[T]
348
356
  or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
357
+ # Sequence[T]
358
+ or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
349
359
  ):
350
360
  # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
351
361
  # intended as an iterable, so we don't transform it.
payi/_utils/_typing.py CHANGED
@@ -15,7 +15,7 @@ from typing_extensions import (
15
15
 
16
16
  from ._utils import lru_cache
17
17
  from .._types import InheritsGeneric
18
- from .._compat import is_union as _is_union
18
+ from ._compat import is_union as _is_union
19
19
 
20
20
 
21
21
  def is_annotated_type(typ: type) -> bool:
@@ -26,6 +26,11 @@ def is_list_type(typ: type) -> bool:
26
26
  return (get_origin(typ) or typ) == list
27
27
 
28
28
 
29
+ def is_sequence_type(typ: type) -> bool:
30
+ origin = get_origin(typ) or typ
31
+ return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence
32
+
33
+
29
34
  def is_iterable_type(typ: type) -> bool:
30
35
  """If the given type is `typing.Iterable[T]`"""
31
36
  origin = get_origin(typ) or typ
payi/_utils/_utils.py CHANGED
@@ -22,7 +22,6 @@ from typing_extensions import TypeGuard
22
22
  import sniffio
23
23
 
24
24
  from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike
25
- from .._compat import parse_date as parse_date, parse_datetime as parse_datetime
26
25
 
27
26
  _T = TypeVar("_T")
28
27
  _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
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.114" # x-release-please-version
4
+ __version__ = "0.1.0-alpha.116" # x-release-please-version
@@ -253,7 +253,7 @@ def anthropic_process_compute_input_cost(request: _ProviderRequest, usage: 'dict
253
253
 
254
254
  total_input_tokens = input + cache_creation_input_tokens + cache_read_input_tokens
255
255
 
256
- request._is_large_context = total_input_tokens > 200000
256
+ request._is_large_context = total_input_tokens >= 200000
257
257
  large_context = "_large_context" if request._is_large_context else ""
258
258
 
259
259
  cache_creation: dict[str, int] = usage.get("cache_creation", {})
@@ -11,9 +11,21 @@ from payi.lib.helpers import PayiCategories, PayiHeaderNames, payi_aws_bedrock_u
11
11
  from payi.types.ingest_units_params import Units
12
12
  from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
13
13
 
14
- from .instrument import _ChunkResult, _IsStreaming, _StreamingType, _ProviderRequest, _PayiInstrumentor
14
+ from .instrument import (
15
+ PayiInstrumentAwsBedrockConfig,
16
+ _ChunkResult,
17
+ _IsStreaming,
18
+ _StreamingType,
19
+ _ProviderRequest,
20
+ _PayiInstrumentor,
21
+ )
15
22
  from .version_helper import get_version_helper
16
23
 
24
+ GUARDRAIL_ID = "system.aws.bedrock.guardrail.id"
25
+ GUARDRAIL_VERSION = "system.aws.bedrock.guardrail.version"
26
+ GUARDRAIL_ACTION = "system.aws.bedrock.guardrail.action"
27
+
28
+ GUARDRAIL_SEMANTIC_FAILURE_DESCRIPTION = "Bedrock Guardrails intervened"
17
29
 
18
30
  class BedrockInstrumentor:
19
31
  _module_name: str = "boto3"
@@ -21,8 +33,10 @@ class BedrockInstrumentor:
21
33
 
22
34
  _instrumentor: _PayiInstrumentor
23
35
 
36
+ _guardrail_trace: bool = True
37
+
24
38
  @staticmethod
25
- def instrument(instrumentor: _PayiInstrumentor) -> None:
39
+ def instrument(instrumentor: _PayiInstrumentor, aws_config: Optional[PayiInstrumentAwsBedrockConfig]) -> None:
26
40
  BedrockInstrumentor._instrumentor = instrumentor
27
41
 
28
42
  BedrockInstrumentor._module_version = get_version_helper(BedrockInstrumentor._module_name)
@@ -44,6 +58,9 @@ class BedrockInstrumentor:
44
58
  instrumentor._logger.debug(f"Error instrumenting bedrock: {e}")
45
59
  return
46
60
 
61
+ if aws_config:
62
+ BedrockInstrumentor._guardrail_trace = aws_config.get("guardrail_trace", True)
63
+
47
64
  @_PayiInstrumentor.payi_wrapper
48
65
  def create_client_wrapper(instrumentor: _PayiInstrumentor, wrapped: Any, instance: Any, *args: Any, **kwargs: Any) -> Any: # noqa: ARG001
49
66
  if kwargs.get("service_name") != "bedrock-runtime":
@@ -184,7 +201,12 @@ class InvokeResponseWrapper(ObjectProxy): # type: ignore
184
201
 
185
202
  if self._log_prompt_and_response:
186
203
  ingest["provider_response_json"] = data.decode('utf-8') # type: ignore
187
-
204
+
205
+ guardrails = response.get("amazon-bedrock-trace", {}).get("guardrail", {}).get("input", {})
206
+ self._request.process_guardrails(guardrails)
207
+
208
+ self._request.process_stop_action(response.get("amazon-bedrock-guardrailAction", ""))
209
+
188
210
  self._request._instrumentor._ingest_units(self._request)
189
211
 
190
212
  return data # type: ignore
@@ -257,6 +279,7 @@ def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
257
279
  return invoke_wrapper
258
280
 
259
281
  class _BedrockProviderRequest(_ProviderRequest):
282
+
260
283
  def __init__(self, instrumentor: _PayiInstrumentor):
261
284
  super().__init__(
262
285
  instrumentor=instrumentor,
@@ -303,6 +326,51 @@ class _BedrockProviderRequest(_ProviderRequest):
303
326
  self._instrumentor._logger.debug(f"Error processing exception: {e}")
304
327
  return False
305
328
 
329
+ def process_guardrails(self, guardrails: 'dict[str, Any]') -> None:
330
+ units = self._ingest["units"]
331
+
332
+ # while we iterate over the entire dict, only one guardrail is expected and supported
333
+ for _, value in guardrails.items():
334
+ # _ (key) is the guardrail id
335
+ if not isinstance(value, dict):
336
+ continue
337
+
338
+ usage: dict[str, int] = value.get("invocationMetrics", {}).get("usage", {}) # type: ignore
339
+ if not usage:
340
+ continue
341
+
342
+ topicPolicyUnits: int = usage.get("topicPolicyUnits", 0) # type: ignore
343
+ if topicPolicyUnits > 0:
344
+ units["guardrail_topic"] = Units(input=topicPolicyUnits, output=0) # type: ignore
345
+
346
+ contentPolicyUnits = usage.get("contentPolicyUnits", 0) # type: ignore
347
+ if contentPolicyUnits > 0:
348
+ units["guardrail_content"] = Units(input=contentPolicyUnits, output=0) # type: ignore
349
+
350
+ wordPolicyUnits = usage.get("wordPolicyUnits", 0) # type: ignore
351
+ if wordPolicyUnits > 0:
352
+ units["guardrail_word_free"] = Units(input=wordPolicyUnits, output=0) # type: ignore
353
+
354
+ automatedReasoningPolicyUnits = usage.get("automatedReasoningPolicyUnits", 0) # type: ignore
355
+ if automatedReasoningPolicyUnits > 0:
356
+ units["guardrail_automated_reasoning"] = Units(input=automatedReasoningPolicyUnits, output=0) # type: ignore
357
+
358
+ sensitiveInformationPolicyUnits = usage.get("sensitiveInformationPolicyUnits", 0) # type: ignore
359
+ if sensitiveInformationPolicyUnits > 0:
360
+ units["guardrail_sensitive_information"] = Units(input=sensitiveInformationPolicyUnits, output=0) # type: ignore
361
+
362
+ sensitiveInformationPolicyFreeUnits = usage.get("sensitiveInformationPolicyFreeUnits", 0) # type: ignore
363
+ if sensitiveInformationPolicyFreeUnits > 0:
364
+ units["guardrail_sensitive_information_free"] = Units(input=sensitiveInformationPolicyFreeUnits, output=0) # type: ignore
365
+
366
+ contextualGroundingPolicyUnits = usage.get("contextualGroundingPolicyUnits", 0) # type: ignore
367
+ if contextualGroundingPolicyUnits > 0:
368
+ units["guardrail_contextual_grounding"] = Units(input=contextualGroundingPolicyUnits, output=0) # type: ignore
369
+
370
+ contentPolicyImageUnits = usage.get("contentPolicyImageUnits", 0) # type: ignore
371
+ if contentPolicyImageUnits > 0:
372
+ units["guardrail_content_image"] = Units(input=contentPolicyImageUnits, output=0) # type: ignore
373
+
306
374
  class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
307
375
  def __init__(self, instrumentor: _PayiInstrumentor, model_id: str):
308
376
  super().__init__(instrumentor=instrumentor)
@@ -318,9 +386,22 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
318
386
 
319
387
  super().process_request(instance, extra_headers, args, kwargs)
320
388
 
389
+ guardrail_id = kwargs.get("guardrailIdentifier", "")
390
+ if guardrail_id:
391
+ self.add_internal_request_property(GUARDRAIL_ID, guardrail_id)
392
+
393
+ guardrail_version = kwargs.get("guardrailVersion", "")
394
+ if guardrail_version:
395
+ self.add_internal_request_property(GUARDRAIL_VERSION, guardrail_version)
396
+
397
+ if guardrail_id and guardrail_version and BedrockInstrumentor._guardrail_trace:
398
+ trace = kwargs.get("trace", None)
399
+ if not trace:
400
+ kwargs["trace"] = "ENABLED"
401
+
321
402
  if self._is_anthropic:
322
403
  try:
323
- body = json.loads( kwargs.get("body", ""))
404
+ body = json.loads(kwargs.get("body", ""))
324
405
  messages = body.get("messages", {})
325
406
  if messages:
326
407
  anthropic_has_image_and_get_texts(self, messages)
@@ -328,7 +409,7 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
328
409
  self._instrumentor._logger.debug(f"Bedrock invoke error processing request body: {e}")
329
410
  elif self._is_cohere_embed_english_v3:
330
411
  try:
331
- body = json.loads( kwargs.get("body", ""))
412
+ body = json.loads(kwargs.get("body", ""))
332
413
  input_type = body.get("input_type", "")
333
414
  if input_type == 'image':
334
415
  images = body.get("images", [])
@@ -343,6 +424,12 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
343
424
  def process_chunk(self, chunk: Any) -> _ChunkResult:
344
425
  chunk_dict = json.loads(chunk)
345
426
 
427
+ guardrails = chunk_dict.get("amazon-bedrock-trace", {}).get("guardrail", {}).get("input", {})
428
+ if guardrails:
429
+ self.process_guardrails(guardrails)
430
+
431
+ self.process_stop_action(chunk_dict.get("amazon-bedrock-guardrailAction", ""))
432
+
346
433
  if self._is_anthropic:
347
434
  from .AnthropicInstrumentor import anthropic_process_chunk
348
435
  return anthropic_process_chunk(self, chunk_dict, assign_id=False)
@@ -398,6 +485,13 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
398
485
 
399
486
  return response
400
487
 
488
+ def process_stop_action(self, action: str) -> None:
489
+ # record both as a semantic failure and guardrail action so it is discoverable through both properties
490
+ if action == "INTERVENED":
491
+ self.add_internal_request_property('system.failure', action)
492
+ self.add_internal_request_property('system.failure.description', GUARDRAIL_SEMANTIC_FAILURE_DESCRIPTION)
493
+ self.add_internal_request_property(GUARDRAIL_ACTION, action)
494
+
401
495
  @override
402
496
  def remove_inline_data(self, prompt: 'dict[str, Any]') -> bool:# noqa: ARG002
403
497
  if not self._is_anthropic:
@@ -417,6 +511,25 @@ class _BedrockInvokeProviderRequest(_BedrockProviderRequest):
417
511
  return False
418
512
 
419
513
  class _BedrockConverseProviderRequest(_BedrockProviderRequest):
514
+ @override
515
+ def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
516
+ guardrail_config = kwargs.get("guardrailConfig", {})
517
+ if guardrail_config:
518
+ guardrailIdentifier = guardrail_config.get("guardrailIdentifier", "")
519
+ if guardrailIdentifier:
520
+ self.add_internal_request_property(GUARDRAIL_ID, guardrailIdentifier)
521
+
522
+ guardrailVersion = guardrail_config.get("guardrailVersion", "")
523
+ if guardrailVersion:
524
+ self.add_internal_request_property(GUARDRAIL_VERSION, guardrailVersion)
525
+
526
+ if guardrailIdentifier and guardrailVersion and BedrockInstrumentor._guardrail_trace:
527
+ trace = guardrail_config.get("trace", None)
528
+ if not trace:
529
+ guardrail_config["trace"] = "enabled"
530
+
531
+ return True
532
+
420
533
  @override
421
534
  def process_synchronous_response(
422
535
  self,
@@ -448,6 +561,12 @@ class _BedrockConverseProviderRequest(_BedrockProviderRequest):
448
561
 
449
562
  bedrock_converse_process_synchronous_function_call(self, response)
450
563
 
564
+ guardrails = response.get("trace", {}).get("guardrail", {}).get("inputAssessment", {})
565
+ if guardrails:
566
+ self.process_guardrails(guardrails)
567
+
568
+ self.process_stop_reason(response.get("stopReason", ""))
569
+
451
570
  return None
452
571
 
453
572
  @override
@@ -461,12 +580,25 @@ class _BedrockConverseProviderRequest(_BedrockProviderRequest):
461
580
  output = usage.get("outputTokens", 0)
462
581
  self._ingest["units"]["text"] = Units(input=input, output=output)
463
582
 
583
+ guardrail = metadata.get("trace", {}).get("guardrail", {}).get("inputAssessment", {})
584
+ if guardrail:
585
+ self.process_guardrails(guardrail)
586
+
464
587
  ingest = True
465
588
 
589
+ self.process_stop_reason(chunk.get("messageStop", {}).get("stopReason", ""))
590
+
466
591
  bedrock_converse_process_streaming_for_function_call(self, chunk)
467
592
 
468
593
  return _ChunkResult(send_chunk_to_caller=True, ingest=ingest)
469
594
 
595
+ def process_stop_reason(self, reason: str) -> None:
596
+ if reason == "guardrail_intervened":
597
+ # record both as a semantic failure and guardrail action so it is discoverable through both properties
598
+ self.add_internal_request_property('system.failure', reason)
599
+ self.add_internal_request_property('system.failure.description', GUARDRAIL_SEMANTIC_FAILURE_DESCRIPTION)
600
+ self.add_internal_request_property(GUARDRAIL_ACTION, reason)
601
+
470
602
  def bedrock_converse_process_streaming_for_function_call(request: _ProviderRequest, chunk: 'dict[str, Any]') -> None:
471
603
  contentBlockStart = chunk.get("contentBlockStart", {})
472
604
  tool_use = contentBlockStart.get("start", {}).get("toolUse", {})
payi/lib/instrument.py CHANGED
@@ -59,6 +59,7 @@ class _ProviderRequest:
59
59
  self._building_function_response: bool = False
60
60
  self._function_calls: Optional[list[ProviderResponseFunctionCall]] = None
61
61
  self._is_large_context: bool = False
62
+ self._internal_request_properties: dict[str, str] = {}
62
63
 
63
64
  def process_chunk(self, _chunk: Any) -> _ChunkResult:
64
65
  return _ChunkResult(send_chunk_to_caller=True)
@@ -99,6 +100,9 @@ class _ProviderRequest:
99
100
  def streaming_type(self) -> '_StreamingType':
100
101
  return self._streaming_type
101
102
 
103
+ def add_internal_request_property(self, key: str, value: str) -> None:
104
+ self._internal_request_properties[key] = value
105
+
102
106
  def exception_to_semantic_failure(self, e: Exception) -> None:
103
107
  exception_str = f"{type(e).__name__}"
104
108
 
@@ -113,16 +117,10 @@ class _ProviderRequest:
113
117
  except Exception as _ex:
114
118
  pass
115
119
 
116
- existing_properties = self._ingest.get("properties", None)
117
- if not existing_properties:
118
- existing_properties = {}
119
-
120
- existing_properties['system.failure'] = exception_str
120
+ self.add_internal_request_property('system.failure', exception_str)
121
121
  if fields:
122
122
  failure_description = ",".join(fields)
123
- existing_properties["system.failure.description"] = failure_description[:128]
124
-
125
- self._ingest["properties"] = existing_properties
123
+ self.add_internal_request_property("system.failure.description", failure_description)
126
124
 
127
125
  if "http_status_code" not in self._ingest:
128
126
  # use a non existent http status code so when presented to the user, the origin is clear
@@ -147,6 +145,9 @@ class _ProviderRequest:
147
145
  self._ingest["provider_response_function_calls"] = self._function_calls
148
146
  self._function_calls.append(ProviderResponseFunctionCall(name=name, arguments=arguments))
149
147
 
148
+ class PayiInstrumentAwsBedrockConfig(TypedDict, total=False):
149
+ guardrail_trace: bool
150
+
150
151
  class PayiInstrumentConfig(TypedDict, total=False):
151
152
  proxy: bool
152
153
  global_instrumentation: bool
@@ -161,6 +162,7 @@ class PayiInstrumentConfig(TypedDict, total=False):
161
162
  account_name: Optional[str]
162
163
  request_tags: Optional["list[str]"]
163
164
  request_properties: Optional["dict[str, str]"]
165
+ aws_config: Optional[PayiInstrumentAwsBedrockConfig]
164
166
 
165
167
  class PayiContext(TypedDict, total=False):
166
168
  use_case_name: Optional[str]
@@ -276,9 +278,9 @@ class _PayiInstrumentor:
276
278
  global_instrumentation = global_config.pop("global_instrumentation", True)
277
279
 
278
280
  if instruments is None or "*" in instruments:
279
- self._instrument_all()
281
+ self._instrument_all(global_config=global_config)
280
282
  else:
281
- self._instrument_specific(instruments)
283
+ self._instrument_specific(instruments=instruments, global_config=global_config)
282
284
 
283
285
  if global_instrumentation:
284
286
  if "proxy" not in global_config:
@@ -313,20 +315,20 @@ class _PayiInstrumentor:
313
315
 
314
316
  self._init_current_context(**context)
315
317
 
316
- def _instrument_all(self) -> None:
318
+ def _instrument_all(self, global_config: PayiInstrumentConfig) -> None:
317
319
  self._instrument_openai()
318
320
  self._instrument_anthropic()
319
- self._instrument_aws_bedrock()
321
+ self._instrument_aws_bedrock(global_config.get("aws_config", None))
320
322
  self._instrument_google_vertex()
321
323
  self._instrument_google_genai()
322
324
 
323
- def _instrument_specific(self, instruments: Set[str]) -> None:
325
+ def _instrument_specific(self, instruments: Set[str], global_config: PayiInstrumentConfig) -> None:
324
326
  if PayiCategories.openai in instruments or PayiCategories.azure_openai in instruments:
325
327
  self._instrument_openai()
326
328
  if PayiCategories.anthropic in instruments:
327
329
  self._instrument_anthropic()
328
330
  if PayiCategories.aws_bedrock in instruments:
329
- self._instrument_aws_bedrock()
331
+ self._instrument_aws_bedrock(global_config.get("aws_config", None))
330
332
  if PayiCategories.google_vertex in instruments:
331
333
  self._instrument_google_vertex()
332
334
  self._instrument_google_genai()
@@ -349,11 +351,11 @@ class _PayiInstrumentor:
349
351
  except Exception as e:
350
352
  self._logger.error(f"Error instrumenting Anthropic: {e}")
351
353
 
352
- def _instrument_aws_bedrock(self) -> None:
354
+ def _instrument_aws_bedrock(self, aws_config: Optional[PayiInstrumentAwsBedrockConfig]) -> None:
353
355
  from .BedrockInstrumentor import BedrockInstrumentor
354
356
 
355
357
  try:
356
- BedrockInstrumentor.instrument(self)
358
+ BedrockInstrumentor.instrument(self, aws_config=aws_config)
357
359
 
358
360
  except Exception as e:
359
361
  self._logger.error(f"Error instrumenting AWS bedrock: {e}")
@@ -393,9 +395,10 @@ class _PayiInstrumentor:
393
395
  return log_ingest_units
394
396
 
395
397
  def _process_ingest_units(
396
- self,
397
- request: _ProviderRequest, log_data: 'dict[str, str]',
398
- extra_headers: 'dict[str, str]') -> None:
398
+ self,
399
+ request: _ProviderRequest,
400
+ log_data: 'dict[str, str]',
401
+ extra_headers: 'dict[str, str]') -> None:
399
402
  ingest_units = request._ingest
400
403
 
401
404
  if request._module_version:
@@ -408,6 +411,9 @@ class _PayiInstrumentor:
408
411
  if 'resource' not in ingest_units or ingest_units['resource'] == '':
409
412
  ingest_units['resource'] = "system.unknown_model"
410
413
 
414
+ if request._internal_request_properties:
415
+ ingest_units["properties"] = request._internal_request_properties
416
+
411
417
  request_json = ingest_units.get('provider_request_json', "")
412
418
  if request_json and self._instrument_inline_data is False:
413
419
  try:
@@ -925,6 +931,15 @@ class _PayiInstrumentor:
925
931
  if use_case_properties:
926
932
  request._ingest["use_case_properties"] = use_case_properties
927
933
 
934
+ if request._internal_request_properties:
935
+ if "properties" in request._ingest and request._ingest["properties"] is not None:
936
+ # Merge internal request properties, but don't override existing keys
937
+ for key, value in request._internal_request_properties.items():
938
+ if key not in request._ingest["properties"]:
939
+ request._ingest["properties"][key] = value
940
+ else:
941
+ request._ingest["properties"] = request._internal_request_properties # Assign
942
+
928
943
  if len(ingest_extra_headers) > 0:
929
944
  request._ingest["provider_request_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in ingest_extra_headers.items()]
930
945
 
@@ -2,12 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import List, Union
5
+ from typing import Union
6
6
  from datetime import datetime
7
7
 
8
8
  import httpx
9
9
 
10
- from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
10
+ from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr
11
11
  from ..._utils import maybe_transform, async_maybe_transform
12
12
  from ..._compat import cached_property
13
13
  from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -49,7 +49,7 @@ class FixedCostResourcesResource(SyncAPIResource):
49
49
  resource: str,
50
50
  *,
51
51
  category: str,
52
- units: List[str],
52
+ units: SequenceNotStr[str],
53
53
  cost_per_hour: float | NotGiven = NOT_GIVEN,
54
54
  start_timestamp: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
55
55
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -117,7 +117,7 @@ class AsyncFixedCostResourcesResource(AsyncAPIResource):
117
117
  resource: str,
118
118
  *,
119
119
  category: str,
120
- units: List[str],
120
+ units: SequenceNotStr[str],
121
121
  cost_per_hour: float | NotGiven = NOT_GIVEN,
122
122
  start_timestamp: Union[str, datetime, None] | NotGiven = NOT_GIVEN,
123
123
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
payi/resources/ingest.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Dict, List, Union, Iterable, Optional
5
+ from typing import Dict, Union, Iterable, Optional
6
6
  from datetime import datetime
7
7
 
8
8
  import httpx
@@ -10,7 +10,7 @@ import httpx
10
10
  from payi._utils._utils import is_given
11
11
 
12
12
  from ..types import ingest_units_params
13
- from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
13
+ from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr
14
14
  from .._utils import maybe_transform, strip_not_given, async_maybe_transform
15
15
  from .._compat import cached_property
16
16
  from .._resource import SyncAPIResource, AsyncAPIResource
@@ -98,7 +98,7 @@ class IngestResource(SyncAPIResource):
98
98
  | NotGiven = NOT_GIVEN,
99
99
  provider_response_headers: Optional[Iterable[PayICommonModelsAPIRouterHeaderInfoParam]] | NotGiven = NOT_GIVEN,
100
100
  provider_response_id: Optional[str] | NotGiven = NOT_GIVEN,
101
- provider_response_json: Union[str, List[str], None] | NotGiven = NOT_GIVEN,
101
+ provider_response_json: Union[str, SequenceNotStr[str], None] | NotGiven = NOT_GIVEN,
102
102
  provider_uri: Optional[str] | NotGiven = NOT_GIVEN,
103
103
  resource: Optional[str] | NotGiven = NOT_GIVEN,
104
104
  time_to_first_completion_token_ms: Optional[int] | NotGiven = NOT_GIVEN,
@@ -328,7 +328,7 @@ class AsyncIngestResource(AsyncAPIResource):
328
328
  | NotGiven = NOT_GIVEN,
329
329
  provider_response_headers: Optional[Iterable[PayICommonModelsAPIRouterHeaderInfoParam]] | NotGiven = NOT_GIVEN,
330
330
  provider_response_id: Optional[str] | NotGiven = NOT_GIVEN,
331
- provider_response_json: Union[str, List[str], None] | NotGiven = NOT_GIVEN,
331
+ provider_response_json: Union[str, SequenceNotStr[str], None] | NotGiven = NOT_GIVEN,
332
332
  provider_uri: Optional[str] | NotGiven = NOT_GIVEN,
333
333
  resource: Optional[str] | NotGiven = NOT_GIVEN,
334
334
  time_to_first_completion_token_ms: Optional[int] | NotGiven = NOT_GIVEN,
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import List, Union, Optional
5
+ from typing import Union, Optional
6
6
  from datetime import datetime
7
7
  from typing_extensions import Literal
8
8
 
@@ -17,7 +17,7 @@ from .tags import (
17
17
  AsyncTagsResourceWithStreamingResponse,
18
18
  )
19
19
  from ...types import limit_list_params, limit_reset_params, limit_create_params, limit_update_params
20
- from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
20
+ from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr
21
21
  from ..._utils import maybe_transform, async_maybe_transform
22
22
  from ..._compat import cached_property
23
23
  from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -67,7 +67,7 @@ class LimitsResource(SyncAPIResource):
67
67
  limit_name: str,
68
68
  max: float,
69
69
  limit_id: Optional[str] | NotGiven = NOT_GIVEN,
70
- limit_tags: Optional[List[str]] | NotGiven = NOT_GIVEN,
70
+ limit_tags: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN,
71
71
  limit_type: Literal["block", "allow"] | NotGiven = NOT_GIVEN,
72
72
  threshold: Optional[float] | NotGiven = NOT_GIVEN,
73
73
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -334,7 +334,7 @@ class AsyncLimitsResource(AsyncAPIResource):
334
334
  limit_name: str,
335
335
  max: float,
336
336
  limit_id: Optional[str] | NotGiven = NOT_GIVEN,
337
- limit_tags: Optional[List[str]] | NotGiven = NOT_GIVEN,
337
+ limit_tags: Optional[SequenceNotStr[str]] | NotGiven = NOT_GIVEN,
338
338
  limit_type: Literal["block", "allow"] | NotGiven = NOT_GIVEN,
339
339
  threshold: Optional[float] | NotGiven = NOT_GIVEN,
340
340
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -2,11 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import List
6
-
7
5
  import httpx
8
6
 
9
- from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
7
+ from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr
10
8
  from ..._utils import maybe_transform, async_maybe_transform
11
9
  from ..._compat import cached_property
12
10
  from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -51,7 +49,7 @@ class TagsResource(SyncAPIResource):
51
49
  self,
52
50
  limit_id: str,
53
51
  *,
54
- limit_tags: List[str],
52
+ limit_tags: SequenceNotStr[str],
55
53
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
56
54
  # The extra values given here take precedence over values defined on the client or passed to this method.
57
55
  extra_headers: Headers | None = None,
@@ -88,7 +86,7 @@ class TagsResource(SyncAPIResource):
88
86
  self,
89
87
  limit_id: str,
90
88
  *,
91
- limit_tags: List[str],
89
+ limit_tags: SequenceNotStr[str],
92
90
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
93
91
  # The extra values given here take precedence over values defined on the client or passed to this method.
94
92
  extra_headers: Headers | None = None,
@@ -191,7 +189,7 @@ class TagsResource(SyncAPIResource):
191
189
  self,
192
190
  limit_id: str,
193
191
  *,
194
- limit_tags: List[str],
192
+ limit_tags: SequenceNotStr[str],
195
193
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
196
194
  # The extra values given here take precedence over values defined on the client or passed to this method.
197
195
  extra_headers: Headers | None = None,
@@ -249,7 +247,7 @@ class AsyncTagsResource(AsyncAPIResource):
249
247
  self,
250
248
  limit_id: str,
251
249
  *,
252
- limit_tags: List[str],
250
+ limit_tags: SequenceNotStr[str],
253
251
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
254
252
  # The extra values given here take precedence over values defined on the client or passed to this method.
255
253
  extra_headers: Headers | None = None,
@@ -286,7 +284,7 @@ class AsyncTagsResource(AsyncAPIResource):
286
284
  self,
287
285
  limit_id: str,
288
286
  *,
289
- limit_tags: List[str],
287
+ limit_tags: SequenceNotStr[str],
290
288
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
291
289
  # The extra values given here take precedence over values defined on the client or passed to this method.
292
290
  extra_headers: Headers | None = None,
@@ -389,7 +387,7 @@ class AsyncTagsResource(AsyncAPIResource):
389
387
  self,
390
388
  limit_id: str,
391
389
  *,
392
- limit_tags: List[str],
390
+ limit_tags: SequenceNotStr[str],
393
391
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
394
392
  # The extra values given here take precedence over values defined on the client or passed to this method.
395
393
  extra_headers: Headers | None = None,