payi 0.1.0a41__py3-none-any.whl → 0.1.0a43__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/lib/instrument.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import uuid
3
- import asyncio
4
3
  import inspect
5
4
  import logging
6
5
  import traceback
@@ -11,6 +10,7 @@ from wrapt import ObjectProxy # type: ignore
11
10
 
12
11
  from payi import Payi, AsyncPayi
13
12
  from payi.types import IngestUnitsParams
13
+ from payi.types.ingest_response import IngestResponse
14
14
  from payi.types.ingest_units_params import Units
15
15
  from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
16
16
 
@@ -88,79 +88,92 @@ class PayiInstrumentor:
88
88
  except Exception as e:
89
89
  logging.error(f"Error instrumenting AWS bedrock: {e}")
90
90
 
91
- def _ingest_units(self, ingest_units: IngestUnitsParams) -> None:
92
- # return early if there are no units to ingest and on a successul ingest request
91
+ def _process_ingest_units(self, ingest_units: IngestUnitsParams, log_data: 'dict[str, str]') -> bool:
93
92
  if int(ingest_units.get("http_status_code") or 0) < 400:
94
93
  units = ingest_units.get("units", {})
95
94
  if not units or all(unit.get("input", 0) == 0 and unit.get("output", 0) == 0 for unit in units.values()):
96
95
  logging.error(
97
96
  'No units to ingest. For OpenAI streaming calls, make sure you pass stream_options={"include_usage": True}'
98
97
  )
99
- return
98
+ return False
99
+
100
+ if self._log_prompt_and_response and self._prompt_and_response_logger:
101
+ response_json = ingest_units.pop("provider_response_json", None)
102
+ request_json = ingest_units.pop("provider_request_json", None)
103
+ stack_trace = ingest_units.get("properties", {}).pop("system.stack_trace", None) # type: ignore
104
+
105
+ if response_json is not None:
106
+ # response_json is a list of strings, convert a single json string
107
+ log_data["provider_response_json"] = json.dumps(response_json)
108
+ if request_json is not None:
109
+ log_data["provider_request_json"] = request_json
110
+ if stack_trace is not None:
111
+ log_data["stack_trace"] = stack_trace
112
+
113
+ return True
114
+
115
+ def _process_ingest_units_response(self, ingest_response: IngestResponse) -> None:
116
+ if ingest_response.xproxy_result.limits:
117
+ for limit_id, state in ingest_response.xproxy_result.limits.items():
118
+ removeBlockedId: bool = False
119
+
120
+ if state.state == "blocked":
121
+ self._blocked_limits.add(limit_id)
122
+ elif state.state == "exceeded":
123
+ self._exceeded_limits.add(limit_id)
124
+ removeBlockedId = True
125
+ elif state.state == "ok":
126
+ removeBlockedId = True
127
+
128
+ # opportunistically remove blocked limits
129
+ if removeBlockedId:
130
+ self._blocked_limits.discard(limit_id)
131
+
132
+ async def _aingest_units(self, ingest_units: IngestUnitsParams) -> None:
133
+ # return early if there are no units to ingest and on a successul ingest request
134
+ log_data: 'dict[str,str]' = {}
135
+ if not self._process_ingest_units(ingest_units, log_data):
136
+ return
100
137
 
101
138
  try:
102
139
  if isinstance(self._payi, AsyncPayi):
103
- loop = asyncio.new_event_loop()
104
- asyncio.set_event_loop(loop)
105
- try:
106
- ingest_result = loop.run_until_complete(self._payi.ingest.units(**ingest_units))
107
- finally:
108
- loop.close()
109
- elif isinstance(self._payi, Payi):
110
- ingest_result = self._payi.ingest.units(**ingest_units)
140
+ ingest_response= await self._payi.ingest.units(**ingest_units)
141
+
142
+ self._process_ingest_units_response(ingest_response)
143
+
144
+ if self._log_prompt_and_response and self._prompt_and_response_logger:
145
+ request_id = ingest_response.xproxy_result.request_id
146
+ self._prompt_and_response_logger(request_id, log_data) # type: ignore
111
147
  else:
112
148
  logging.error("No payi instance to ingest units")
113
149
  return
150
+ except Exception as e:
151
+ logging.error(f"Error Pay-i ingesting result: {e}")
114
152
 
115
- if ingest_result.xproxy_result.limits:
116
- for limit_id, state in ingest_result.xproxy_result.limits.items():
117
- removeBlockedId: bool = False
118
-
119
- if state.state == "blocked":
120
- self._blocked_limits.add(limit_id)
121
- elif state.state == "exceeded":
122
- self._exceeded_limits.add(limit_id)
123
- removeBlockedId = True
124
- elif state.state == "ok":
125
- removeBlockedId = True
126
-
127
- # opportunistically remove blocked limits
128
- if removeBlockedId:
129
- self._blocked_limits.discard(limit_id)
130
-
131
- if self._log_prompt_and_response and self._prompt_and_response_logger:
132
- request_id = ingest_result.xproxy_result.request_id
133
-
134
- log_data = {}
135
- response_json = ingest_units.pop("provider_response_json", None)
136
- request_json = ingest_units.pop("provider_request_json", None)
137
- stack_trace = ingest_units.get("properties", {}).pop("system.stack_trace", None) # type: ignore
153
+ def _ingest_units(self, ingest_units: IngestUnitsParams) -> None:
154
+ # return early if there are no units to ingest and on a successul ingest request
155
+ log_data: 'dict[str,str]' = {}
156
+ if not self._process_ingest_units(ingest_units, log_data):
157
+ return
138
158
 
139
- if response_json is not None:
140
- # response_json is a list of strings, convert a single json string
141
- log_data["provider_response_json"] = json.dumps(response_json)
142
- if request_json is not None:
143
- log_data["provider_request_json"] = request_json
144
- if stack_trace is not None:
145
- log_data["stack_trace"] = stack_trace
159
+ try:
160
+ if isinstance(self._payi, Payi):
161
+ ingest_response = self._payi.ingest.units(**ingest_units)
146
162
 
147
- self._prompt_and_response_logger(request_id, log_data) # type: ignore
163
+ self._process_ingest_units_response(ingest_response)
148
164
 
165
+ if self._log_prompt_and_response and self._prompt_and_response_logger:
166
+ request_id = ingest_response.xproxy_result.request_id
167
+ self._prompt_and_response_logger(request_id, log_data) # type: ignore
168
+ else:
169
+ logging.error("No payi instance to ingest units")
170
+ return
149
171
  except Exception as e:
150
172
  logging.error(f"Error Pay-i ingesting result: {e}")
151
173
 
152
- def _call_func(
153
- self,
154
- func: Any,
155
- proxy: bool,
156
- limit_ids: Optional["list[str]"],
157
- request_tags: Optional["list[str]"],
158
- experience_name: Optional[str],
159
- experience_id: Optional[str],
160
- user_id: Optional[str],
161
- *args: Any,
162
- **kwargs: Any,
163
- ) -> Any:
174
+ def _setup_call_func(
175
+ self
176
+ ) -> 'tuple[dict[str, Any], Optional[str], Optional[str]]':
164
177
  if len(self._context_stack) > 0:
165
178
  # copy current context into the upcoming context
166
179
  context = self._context_stack[-1].copy()
@@ -171,36 +184,100 @@ class PayiInstrumentor:
171
184
  context = {}
172
185
  previous_experience_name = None
173
186
  previous_experience_id = None
187
+ return (context, previous_experience_name, previous_experience_id)
174
188
 
175
- with self:
176
- context["proxy"] = proxy
177
-
178
- # Handle experience name and ID logic
179
- if not experience_name:
180
- # If no experience_name specified, use previous values
181
- context["experience_name"] = previous_experience_name
182
- context["experience_id"] = previous_experience_id
189
+ def _init_context(
190
+ self,
191
+ context: "dict[str, Any]",
192
+ previous_experience_name: Optional[str],
193
+ previous_experience_id: Optional[str],
194
+ proxy: bool,
195
+ limit_ids: Optional["list[str]"],
196
+ request_tags: Optional["list[str]"],
197
+ experience_name: Optional[str],
198
+ experience_id: Optional[str],
199
+ user_id: Optional[str],
200
+ ) -> None:
201
+ context["proxy"] = proxy
202
+
203
+ # Handle experience name and ID logic
204
+ if not experience_name:
205
+ # If no experience_name specified, use previous values
206
+ context["experience_name"] = previous_experience_name
207
+ context["experience_id"] = previous_experience_id
208
+ else:
209
+ # If experience_name is specified
210
+ if experience_name == previous_experience_name:
211
+ # Same experience name, use previous ID unless new one specified
212
+ context["experience_name"] = experience_name
213
+ context["experience_id"] = experience_id if experience_id else previous_experience_id
183
214
  else:
184
- # If experience_name is specified
185
- if experience_name == previous_experience_name:
186
- # Same experience name, use previous ID unless new one specified
187
- context["experience_name"] = experience_name
188
- context["experience_id"] = experience_id if experience_id else previous_experience_id
189
- else:
190
- # Different experience name, use specified ID or generate one
191
- context["experience_name"] = experience_name
192
- context["experience_id"] = experience_id if experience_id else str(uuid.uuid4())
215
+ # Different experience name, use specified ID or generate one
216
+ context["experience_name"] = experience_name
217
+ context["experience_id"] = experience_id if experience_id else str(uuid.uuid4())
218
+
219
+ # set any values explicitly passed by the caller, otherwise use what is already in the context
220
+ if limit_ids:
221
+ context["limit_ids"] = limit_ids
222
+ if request_tags:
223
+ context["request_tags"] = request_tags
224
+ if user_id:
225
+ context["user_id"] = user_id
226
+
227
+ self.set_context(context)
228
+
229
+ async def _acall_func(
230
+ self,
231
+ func: Any,
232
+ proxy: bool,
233
+ limit_ids: Optional["list[str]"],
234
+ request_tags: Optional["list[str]"],
235
+ experience_name: Optional[str],
236
+ experience_id: Optional[str],
237
+ user_id: Optional[str],
238
+ *args: Any,
239
+ **kwargs: Any,
240
+ ) -> Any:
241
+ context, previous_experience_name, previous_experience_id = self._setup_call_func()
193
242
 
194
- # set any values explicitly passed by the caller, otherwise use what is already in the context
195
- if limit_ids:
196
- context["limit_ids"] = limit_ids
197
- if request_tags:
198
- context["request_tags"] = request_tags
199
- if user_id:
200
- context["user_id"] = user_id
243
+ with self:
244
+ self._init_context(
245
+ context,
246
+ previous_experience_name,
247
+ previous_experience_id,
248
+ proxy,
249
+ limit_ids,
250
+ request_tags,
251
+ experience_name,
252
+ experience_id,
253
+ user_id)
254
+ return await func(*args, **kwargs)
201
255
 
202
- self.set_context(context)
256
+ def _call_func(
257
+ self,
258
+ func: Any,
259
+ proxy: bool,
260
+ limit_ids: Optional["list[str]"],
261
+ request_tags: Optional["list[str]"],
262
+ experience_name: Optional[str],
263
+ experience_id: Optional[str],
264
+ user_id: Optional[str],
265
+ *args: Any,
266
+ **kwargs: Any,
267
+ ) -> Any:
268
+ context, previous_experience_name, previous_experience_id = self._setup_call_func()
203
269
 
270
+ with self:
271
+ self._init_context(
272
+ context,
273
+ previous_experience_name,
274
+ previous_experience_id,
275
+ proxy,
276
+ limit_ids,
277
+ request_tags,
278
+ experience_name,
279
+ experience_id,
280
+ user_id)
204
281
  return func(*args, **kwargs)
205
282
 
206
283
  def __enter__(self) -> Any:
@@ -222,11 +299,50 @@ class PayiInstrumentor:
222
299
  # Return the current top of the stack
223
300
  return self._context_stack[-1] if self._context_stack else None
224
301
 
225
- def chat_wrapper(
302
+
303
+ def _prepare_ingest(
304
+ self,
305
+ ingest: IngestUnitsParams,
306
+ ingest_extra_headers: "dict[str, str]", # do not coflict potential kwargs["extra_headers"]
307
+ **kwargs: Any,
308
+ ) -> None:
309
+ limit_ids = ingest_extra_headers.pop("xProxy-Limit-IDs", None)
310
+ request_tags = ingest_extra_headers.pop("xProxy-Request-Tags", None)
311
+ experience_name = ingest_extra_headers.pop("xProxy-Experience-Name", None)
312
+ experience_id = ingest_extra_headers.pop("xProxy-Experience-ID", None)
313
+ user_id = ingest_extra_headers.pop("xProxy-User-ID", None)
314
+
315
+ if limit_ids:
316
+ ingest["limit_ids"] = limit_ids.split(",")
317
+ if request_tags:
318
+ ingest["request_tags"] = request_tags.split(",")
319
+ if experience_name:
320
+ ingest["experience_name"] = experience_name
321
+ if experience_id:
322
+ ingest["experience_id"] = experience_id
323
+ if user_id:
324
+ ingest["user_id"] = user_id
325
+
326
+ if len(ingest_extra_headers) > 0:
327
+ ingest["provider_request_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in ingest_extra_headers.items()]
328
+
329
+ provider_prompt = {}
330
+ for k, v in kwargs.items():
331
+ if k == "messages":
332
+ provider_prompt[k] = [m.model_dump() if hasattr(m, "model_dump") else m for m in v]
333
+ elif k in ["extra_headers", "extra_query"]:
334
+ pass
335
+ else:
336
+ provider_prompt[k] = v
337
+
338
+ if self._log_prompt_and_response:
339
+ ingest["provider_request_json"] = json.dumps(provider_prompt)
340
+
341
+ async def achat_wrapper(
226
342
  self,
227
343
  category: str,
228
344
  process_chunk: Optional[Callable[[Any, IngestUnitsParams], None]],
229
- process_request: Optional[Callable[[IngestUnitsParams, Any], None]],
345
+ process_request: Optional[Callable[[IngestUnitsParams, Any, Any], None]],
230
346
  process_synchronous_response: Any,
231
347
  is_streaming: IsStreaming,
232
348
  wrapped: Any,
@@ -244,7 +360,7 @@ class PayiInstrumentor:
244
360
  kwargs.pop("extra_headers", None)
245
361
 
246
362
  # wrapped function invoked outside of decorator scope
247
- return wrapped(*args, **kwargs)
363
+ return await wrapped(*args, **kwargs)
248
364
 
249
365
  # after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
250
366
  extra_headers = kwargs.get("extra_headers", {})
@@ -254,7 +370,7 @@ class PayiInstrumentor:
254
370
  if "extra_headers" not in kwargs:
255
371
  kwargs["extra_headers"] = extra_headers
256
372
 
257
- return wrapped(*args, **kwargs)
373
+ return await wrapped(*args, **kwargs)
258
374
 
259
375
  ingest: IngestUnitsParams = {"category": category, "units": {}} # type: ignore
260
376
  if is_bedrock:
@@ -271,7 +387,7 @@ class PayiInstrumentor:
271
387
  ingest['properties'] = { 'system.stack_trace': json.dumps(stack) }
272
388
 
273
389
  if process_request:
274
- process_request(ingest, kwargs)
390
+ process_request(ingest, (), instance)
275
391
 
276
392
  sw = Stopwatch()
277
393
  stream: bool = False
@@ -284,40 +400,122 @@ class PayiInstrumentor:
284
400
  stream = False
285
401
 
286
402
  try:
287
- limit_ids = extra_headers.pop("xProxy-Limit-IDs", None)
288
- request_tags = extra_headers.pop("xProxy-Request-Tags", None)
289
- experience_name = extra_headers.pop("xProxy-Experience-Name", None)
290
- experience_id = extra_headers.pop("xProxy-Experience-ID", None)
291
- user_id = extra_headers.pop("xProxy-User-ID", None)
292
-
293
- if limit_ids:
294
- ingest["limit_ids"] = limit_ids.split(",")
295
- if request_tags:
296
- ingest["request_tags"] = request_tags.split(",")
297
- if experience_name:
298
- ingest["experience_name"] = experience_name
299
- if experience_id:
300
- ingest["experience_id"] = experience_id
301
- if user_id:
302
- ingest["user_id"] = user_id
303
-
304
- if len(extra_headers) > 0:
305
- ingest["provider_request_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in extra_headers.items()]
306
-
307
- provider_prompt = {}
308
- for k, v in kwargs.items():
309
- if k == "messages":
310
- provider_prompt[k] = [m.model_dump() if hasattr(m, "model_dump") else m for m in v]
311
- elif k in ["extra_headers", "extra_query"]:
312
- pass
403
+ self._prepare_ingest(ingest, extra_headers, **kwargs)
404
+ sw.start()
405
+ response = await wrapped(*args, **kwargs)
406
+
407
+ except Exception as e: # pylint: disable=broad-except
408
+ sw.stop()
409
+ duration = sw.elapsed_ms_int()
410
+
411
+ # TODO ingest error
412
+
413
+ raise e
414
+
415
+ if stream:
416
+ stream_result = ChatStreamWrapper(
417
+ response=response,
418
+ instance=instance,
419
+ instrumentor=self,
420
+ log_prompt_and_response=self._log_prompt_and_response,
421
+ ingest=ingest,
422
+ stopwatch=sw,
423
+ process_chunk=process_chunk,
424
+ is_bedrock=is_bedrock,
425
+ )
426
+
427
+ if is_bedrock:
428
+ if "body" in response:
429
+ response["body"] = stream_result
313
430
  else:
314
- provider_prompt[k] = v
431
+ response["stream"] = stream_result
432
+ return response
433
+
434
+ return stream_result
435
+
436
+ sw.stop()
437
+ duration = sw.elapsed_ms_int()
438
+ ingest["end_to_end_latency_ms"] = duration
439
+ ingest["http_status_code"] = 200
440
+
441
+ if process_synchronous_response:
442
+ return_result: Any = process_synchronous_response(
443
+ response=response,
444
+ ingest=ingest,
445
+ log_prompt_and_response=self._log_prompt_and_response,
446
+ instrumentor=self)
447
+ if return_result:
448
+ return return_result
449
+
450
+ await self._aingest_units(ingest)
451
+
452
+ return response
453
+
454
+ def chat_wrapper(
455
+ self,
456
+ category: str,
457
+ process_chunk: Optional[Callable[[Any, IngestUnitsParams], None]],
458
+ process_request: Optional[Callable[[IngestUnitsParams, Any, Any], None]],
459
+ process_synchronous_response: Any,
460
+ is_streaming: IsStreaming,
461
+ wrapped: Any,
462
+ instance: Any,
463
+ args: Any,
464
+ kwargs: Any,
465
+ ) -> Any:
466
+ context = self.get_context()
467
+
468
+ is_bedrock:bool = category == "system.aws.bedrock"
469
+
470
+ if not context:
471
+ if is_bedrock:
472
+ # boto3 doesn't allow extra_headers
473
+ kwargs.pop("extra_headers", None)
474
+
475
+ # wrapped function invoked outside of decorator scope
476
+ return wrapped(*args, **kwargs)
477
+
478
+ # after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
479
+ extra_headers = kwargs.get("extra_headers", {})
480
+ self._update_headers(context, extra_headers)
481
+
482
+ if context.get("proxy", True):
483
+ if "extra_headers" not in kwargs:
484
+ kwargs["extra_headers"] = extra_headers
485
+
486
+ return wrapped(*args, **kwargs)
487
+
488
+ ingest: IngestUnitsParams = {"category": category, "units": {}} # type: ignore
489
+ if is_bedrock:
490
+ # boto3 doesn't allow extra_headers
491
+ kwargs.pop("extra_headers", None)
492
+ ingest["resource"] = kwargs.get("modelId", "")
493
+ else:
494
+ ingest["resource"] = kwargs.get("model", "")
495
+
496
+ current_frame = inspect.currentframe()
497
+ # f_back excludes the current frame, strip() cleans up whitespace and newlines
498
+ stack = [frame.strip() for frame in traceback.format_stack(current_frame.f_back)] # type: ignore
499
+
500
+ ingest['properties'] = { 'system.stack_trace': json.dumps(stack) }
315
501
 
316
- if self._log_prompt_and_response:
317
- ingest["provider_request_json"] = json.dumps(provider_prompt)
502
+ if process_request:
503
+ process_request(ingest, (), kwargs)
504
+
505
+ sw = Stopwatch()
506
+ stream: bool = False
507
+
508
+ if is_streaming == IsStreaming.kwargs:
509
+ stream = kwargs.get("stream", False)
510
+ elif is_streaming == IsStreaming.true:
511
+ stream = True
512
+ else:
513
+ stream = False
318
514
 
515
+ try:
516
+ self._prepare_ingest(ingest, extra_headers, **kwargs)
319
517
  sw.start()
320
- response = wrapped(*args, **kwargs.copy())
518
+ response = wrapped(*args, **kwargs)
321
519
 
322
520
  except Exception as e: # pylint: disable=broad-except
323
521
  sw.stop()
@@ -431,14 +629,30 @@ class PayiInstrumentor:
431
629
  o,
432
630
  wrapped,
433
631
  instance,
434
- args,
435
- kwargs,
632
+ *args,
633
+ **kwargs,
436
634
  )
437
635
 
438
636
  return wrapper
439
637
 
440
638
  return _payi_wrapper
441
639
 
640
+ @staticmethod
641
+ def payi_awrapper(func: Any) -> Any:
642
+ def _payi_awrapper(o: Any) -> Any:
643
+ async def wrapper(wrapped: Any, instance: Any, args: Any, kwargs: Any) -> Any:
644
+ return await func(
645
+ o,
646
+ wrapped,
647
+ instance,
648
+ *args,
649
+ **kwargs,
650
+ )
651
+
652
+ return wrapper
653
+
654
+ return _payi_awrapper
655
+
442
656
  class ChatStreamWrapper(ObjectProxy): # type: ignore
443
657
  def __init__(
444
658
  self,
@@ -530,7 +744,7 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
530
744
  chunk: Any = await self.__wrapped__.__anext__() # type: ignore
531
745
  except Exception as e:
532
746
  if isinstance(e, StopAsyncIteration):
533
- self._stop_iteration()
747
+ await self._astop_iteration()
534
748
  raise e
535
749
  else:
536
750
  self._evaluate_chunk(chunk)
@@ -547,7 +761,7 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
547
761
  if self._process_chunk:
548
762
  self._process_chunk(chunk, self._ingest)
549
763
 
550
- def _stop_iteration(self) -> None:
764
+ def _process_stop_iteration(self) -> None:
551
765
  self._stopwatch.stop()
552
766
  self._ingest["end_to_end_latency_ms"] = self._stopwatch.elapsed_ms_int()
553
767
  self._ingest["http_status_code"] = 200
@@ -555,6 +769,12 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
555
769
  if self._log_prompt_and_response:
556
770
  self._ingest["provider_response_json"] = self._responses
557
771
 
772
+ async def _astop_iteration(self) -> None:
773
+ self._process_stop_iteration()
774
+ await self._instrumentor._aingest_units(self._ingest)
775
+
776
+ def _stop_iteration(self) -> None:
777
+ self._process_stop_iteration()
558
778
  self._instrumentor._ingest_units(self._ingest)
559
779
 
560
780
  @staticmethod
@@ -586,7 +806,6 @@ def payi_instrument(
586
806
  prompt_and_response_logger=prompt_and_response_logger,
587
807
  )
588
808
 
589
-
590
809
  def ingest(
591
810
  limit_ids: Optional["list[str]"] = None,
592
811
  request_tags: Optional["list[str]"] = None,
@@ -595,21 +814,36 @@ def ingest(
595
814
  user_id: Optional[str] = None,
596
815
  ) -> Any:
597
816
  def _ingest(func: Any) -> Any:
598
- def _ingest_wrapper(*args: Any, **kwargs: Any) -> Any:
599
- return _instrumentor._call_func(
600
- func,
601
- False, # false -> ingest
602
- limit_ids,
603
- request_tags,
604
- experience_name,
605
- experience_id,
606
- user_id,
607
- *args,
608
- **kwargs,
609
- )
610
-
611
- return _ingest_wrapper
612
-
817
+ import asyncio
818
+ if asyncio.iscoroutinefunction(func):
819
+ async def awrapper(*args: Any, **kwargs: Any) -> Any:
820
+ # Call the instrumentor's _call_func for async functions
821
+ return await _instrumentor._acall_func(
822
+ func,
823
+ False,
824
+ limit_ids,
825
+ request_tags,
826
+ experience_name,
827
+ experience_id,
828
+ user_id,
829
+ *args,
830
+ *kwargs,
831
+ )
832
+ return awrapper
833
+ else:
834
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
835
+ return _instrumentor._call_func(
836
+ func,
837
+ False,
838
+ limit_ids,
839
+ request_tags,
840
+ experience_name,
841
+ experience_id,
842
+ user_id,
843
+ *args,
844
+ **kwargs,
845
+ )
846
+ return wrapper
613
847
  return _ingest
614
848
 
615
849
  def proxy(
@@ -620,11 +854,36 @@ def proxy(
620
854
  user_id: Optional[str] = None,
621
855
  ) -> Any:
622
856
  def _proxy(func: Any) -> Any:
623
- def _proxy_wrapper(*args: Any, **kwargs: Any) -> Any:
624
- return _instrumentor._call_func(
625
- func, True, limit_ids, request_tags, experience_name, experience_id, user_id, *args, **kwargs
626
- )
857
+ import asyncio
858
+ if asyncio.iscoroutinefunction(func):
859
+ async def _proxy_awrapper(*args: Any, **kwargs: Any) -> Any:
860
+ return await _instrumentor._call_func(
861
+ func,
862
+ True,
863
+ limit_ids,
864
+ request_tags,
865
+ experience_name,
866
+ experience_id,
867
+ user_id,
868
+ *args,
869
+ **kwargs
870
+ )
871
+
872
+ return _proxy_awrapper
873
+ else:
874
+ def _proxy_wrapper(*args: Any, **kwargs: Any) -> Any:
875
+ return _instrumentor._call_func(
876
+ func,
877
+ True,
878
+ limit_ids,
879
+ request_tags,
880
+ experience_name,
881
+ experience_id,
882
+ user_id,
883
+ *args,
884
+ **kwargs
885
+ )
627
886
 
628
- return _proxy_wrapper
887
+ return _proxy_wrapper
629
888
 
630
889
  return _proxy