payi 0.1.0a63__py3-none-any.whl → 0.1.0a65__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 +22 -0
- payi/_version.py +1 -1
- payi/lib/AnthropicInstrumentor.py +60 -60
- payi/lib/BedrockInstrumentor.py +102 -92
- payi/lib/OpenAIInstrumentor.py +90 -58
- payi/lib/helpers.py +3 -0
- payi/lib/instrument.py +214 -238
- {payi-0.1.0a63.dist-info → payi-0.1.0a65.dist-info}/METADATA +1 -1
- {payi-0.1.0a63.dist-info → payi-0.1.0a65.dist-info}/RECORD +11 -11
- {payi-0.1.0a63.dist-info → payi-0.1.0a65.dist-info}/WHEEL +0 -0
- {payi-0.1.0a63.dist-info → payi-0.1.0a65.dist-info}/licenses/LICENSE +0 -0
payi/lib/instrument.py
CHANGED
|
@@ -21,10 +21,25 @@ from .helpers import PayiCategories
|
|
|
21
21
|
from .Stopwatch import Stopwatch
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
class _ProviderRequest:
|
|
25
|
+
def __init__(self, instrumentor: '_PayiInstrumentor'):
|
|
26
|
+
self._instrumentor: '_PayiInstrumentor' = instrumentor
|
|
27
|
+
self._estimated_prompt_tokens: Optional[int] = None
|
|
28
|
+
self._ingest: IngestUnitsParams
|
|
29
|
+
|
|
30
|
+
def process_request(self, _kwargs: Any) -> None:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
def process_chunk(self, _chunk: Any) -> bool:
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
def process_synchronous_response(self, response: Any, log_prompt_and_response: bool, kwargs: Any) -> Optional[object]: # noqa: ARG002
|
|
37
|
+
return None
|
|
38
|
+
|
|
24
39
|
class PayiInstrumentConfig(TypedDict, total=False):
|
|
25
40
|
proxy: bool
|
|
41
|
+
global_instrumentation_enabled: bool
|
|
26
42
|
limit_ids: Optional["list[str]"]
|
|
27
|
-
request_tags: Optional["list[str]"]
|
|
28
43
|
experience_name: Optional[str]
|
|
29
44
|
experience_id: Optional[str]
|
|
30
45
|
use_case_name: Optional[str]
|
|
@@ -40,26 +55,14 @@ class _Context(TypedDict, total=False):
|
|
|
40
55
|
use_case_id: Optional[str]
|
|
41
56
|
use_case_version: Optional[int]
|
|
42
57
|
limit_ids: Optional['list[str]']
|
|
43
|
-
request_tags: Optional['list[str]']
|
|
44
58
|
user_id: Optional[str]
|
|
45
59
|
|
|
46
|
-
class _ParentState(TypedDict, total=False):
|
|
47
|
-
experience_name: Optional[str]
|
|
48
|
-
experience_id: Optional[str]
|
|
49
|
-
use_case_name: Optional[str]
|
|
50
|
-
use_case_id: Optional[str]
|
|
51
|
-
use_case_version: Optional[int]
|
|
52
|
-
limit_ids: Optional['list[str]']
|
|
53
|
-
request_tags: Optional['list[str]']
|
|
54
|
-
|
|
55
60
|
class _IsStreaming(Enum):
|
|
56
61
|
false = 0
|
|
57
62
|
true = 1
|
|
58
63
|
kwargs = 2
|
|
59
64
|
|
|
60
65
|
class _PayiInstrumentor:
|
|
61
|
-
estimated_prompt_tokens: str = "estimated_prompt_tokens"
|
|
62
|
-
|
|
63
66
|
def __init__(
|
|
64
67
|
self,
|
|
65
68
|
payi: Optional[Payi],
|
|
@@ -86,11 +89,23 @@ class _PayiInstrumentor:
|
|
|
86
89
|
else:
|
|
87
90
|
self._instrument_specific(instruments)
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
global_instrumentation_enabled = global_config.pop("global_instrumentation_enabled", True) if global_config else True
|
|
93
|
+
|
|
94
|
+
if global_instrumentation_enabled:
|
|
95
|
+
if global_config is None:
|
|
96
|
+
global_config = {}
|
|
97
|
+
if "proxy" not in global_config:
|
|
98
|
+
global_config["proxy"] = False
|
|
99
|
+
|
|
100
|
+
# Use default clients if not provided for global ingest instrumentation
|
|
101
|
+
if not self._payi and not self._apayi and global_config.get("proxy") == False:
|
|
102
|
+
self._payi = Payi()
|
|
103
|
+
self._apayi = AsyncPayi()
|
|
104
|
+
|
|
90
105
|
context: _Context = {}
|
|
91
106
|
self._context_stack.append(context)
|
|
92
107
|
# init_context will update the currrent context stack location
|
|
93
|
-
self._init_context(context=context,
|
|
108
|
+
self._init_context(context=context, parentContext={}, **global_config) # type: ignore
|
|
94
109
|
|
|
95
110
|
def _instrument_all(self) -> None:
|
|
96
111
|
self._instrument_openai()
|
|
@@ -252,31 +267,24 @@ class _PayiInstrumentor:
|
|
|
252
267
|
|
|
253
268
|
def _setup_call_func(
|
|
254
269
|
self
|
|
255
|
-
) -> 'tuple[_Context,
|
|
270
|
+
) -> 'tuple[_Context, _Context]':
|
|
256
271
|
context: _Context = {}
|
|
257
|
-
|
|
272
|
+
parentContext: _Context = {}
|
|
258
273
|
|
|
259
274
|
if len(self._context_stack) > 0:
|
|
260
275
|
# copy current context into the upcoming context
|
|
261
276
|
context = self._context_stack[-1].copy()
|
|
262
277
|
context.pop("proxy")
|
|
263
|
-
|
|
264
|
-
parentState["experience_id"] = context.get("experience_id", None)
|
|
265
|
-
parentState["use_case_name"] = context.get("use_case_name", None)
|
|
266
|
-
parentState["use_case_id"] = context.get("use_case_id", None)
|
|
267
|
-
parentState["use_case_version"] = context.get("use_case_version", None)
|
|
268
|
-
parentState["limit_ids"] = context.get("limit_ids", None)
|
|
269
|
-
parentState["request_tags"] = context.get("request_tags", None)
|
|
278
|
+
parentContext = {**context}
|
|
270
279
|
|
|
271
|
-
return (context,
|
|
280
|
+
return (context, parentContext)
|
|
272
281
|
|
|
273
282
|
def _init_context(
|
|
274
283
|
self,
|
|
275
284
|
context: _Context,
|
|
276
|
-
|
|
285
|
+
parentContext: _Context,
|
|
277
286
|
proxy: bool,
|
|
278
287
|
limit_ids: Optional["list[str]"] = None,
|
|
279
|
-
request_tags: Optional["list[str]"] = None,
|
|
280
288
|
experience_name: Optional[str] = None,
|
|
281
289
|
experience_id: Optional[str] = None,
|
|
282
290
|
use_case_name: Optional[str]= None,
|
|
@@ -286,56 +294,72 @@ class _PayiInstrumentor:
|
|
|
286
294
|
) -> None:
|
|
287
295
|
context["proxy"] = proxy
|
|
288
296
|
|
|
289
|
-
|
|
297
|
+
parent_experience_name = parentContext.get("experience_name", None)
|
|
298
|
+
parent_experience_id = parentContext.get("experience_id", None)
|
|
290
299
|
|
|
291
|
-
|
|
292
|
-
if not experience_name:
|
|
300
|
+
if experience_name is None:
|
|
293
301
|
# If no experience_name specified, use previous values
|
|
294
|
-
context["experience_name"] =
|
|
295
|
-
context["experience_id"] =
|
|
302
|
+
context["experience_name"] = parent_experience_name
|
|
303
|
+
context["experience_id"] = parent_experience_id
|
|
304
|
+
elif len(experience_name) == 0:
|
|
305
|
+
# Empty string explicitly blocks inheriting from the parent state
|
|
306
|
+
context["experience_name"] = None
|
|
307
|
+
context["experience_id"] = None
|
|
296
308
|
else:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# If experience_name is specified
|
|
301
|
-
if experience_name == previous_experience_name:
|
|
309
|
+
# Check if experience_name is the same as the previous one
|
|
310
|
+
if experience_name == parent_experience_name:
|
|
302
311
|
# Same experience name, use previous ID unless new one specified
|
|
303
312
|
context["experience_name"] = experience_name
|
|
304
|
-
context["experience_id"] = experience_id if experience_id else
|
|
313
|
+
context["experience_id"] = experience_id if experience_id else parent_experience_id
|
|
305
314
|
else:
|
|
306
315
|
# Different experience name, use specified ID or generate one
|
|
307
316
|
context["experience_name"] = experience_name
|
|
308
317
|
context["experience_id"] = experience_id if experience_id else str(uuid.uuid4())
|
|
309
318
|
|
|
310
|
-
|
|
311
|
-
|
|
319
|
+
parent_use_case_name = parentContext.get("use_case_name", None)
|
|
320
|
+
parent_use_case_id = parentContext.get("use_case_id", None)
|
|
321
|
+
parent_use_case_version = parentContext.get("use_case_version", None)
|
|
322
|
+
|
|
323
|
+
if use_case_name is None:
|
|
312
324
|
# If no use_case_name specified, use previous values
|
|
313
|
-
context["use_case_name"] =
|
|
314
|
-
context["use_case_id"] =
|
|
315
|
-
context["use_case_version"] =
|
|
325
|
+
context["use_case_name"] = parent_use_case_name
|
|
326
|
+
context["use_case_id"] = parent_use_case_id
|
|
327
|
+
context["use_case_version"] = parent_use_case_version
|
|
328
|
+
elif len(use_case_name) == 0:
|
|
329
|
+
# Empty string explicitly blocks inheriting from the parent state
|
|
330
|
+
context["use_case_name"] = None
|
|
331
|
+
context["use_case_id"] = None
|
|
332
|
+
context["use_case_version"] = None
|
|
316
333
|
else:
|
|
317
|
-
|
|
318
|
-
previous_use_case_id = parentState.get("use_case_id", None)
|
|
319
|
-
previous_use_case_version = parentState.get("use_case_version", None)
|
|
320
|
-
|
|
321
|
-
# If use_case_name is specified
|
|
322
|
-
if use_case_name == previous_use_case_name:
|
|
334
|
+
if use_case_name == parent_use_case_name:
|
|
323
335
|
# Same use case name, use previous ID unless new one specified
|
|
324
336
|
context["use_case_name"] = use_case_name
|
|
325
|
-
context["use_case_id"] = use_case_id if use_case_id else
|
|
326
|
-
context["use_case_version"] = use_case_version if use_case_version else
|
|
337
|
+
context["use_case_id"] = use_case_id if use_case_id else parent_use_case_id
|
|
338
|
+
context["use_case_version"] = use_case_version if use_case_version else parent_use_case_version
|
|
327
339
|
else:
|
|
328
|
-
# Different
|
|
340
|
+
# Different use case name, use specified ID or generate one
|
|
329
341
|
context["use_case_name"] = use_case_name
|
|
330
342
|
context["use_case_id"] = use_case_id if use_case_id else str(uuid.uuid4())
|
|
331
|
-
context["use_case_version"] = use_case_version
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if limit_ids:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
context["use_case_version"] = use_case_version if use_case_version else None
|
|
344
|
+
|
|
345
|
+
parent_limit_ids = parentContext.get("limit_ids", None)
|
|
346
|
+
if limit_ids is None:
|
|
347
|
+
# use the parent limit_ids if it exists
|
|
348
|
+
context["limit_ids"] = parent_limit_ids
|
|
349
|
+
elif len(limit_ids) == 0:
|
|
350
|
+
# caller passing an empty array explicitly blocks inheriting from the parent state
|
|
351
|
+
context["limit_ids"] = None
|
|
352
|
+
else:
|
|
353
|
+
# union of new and parent lists if the parent context contains limit ids
|
|
354
|
+
context["limit_ids"] = list(set(limit_ids) | set(parent_limit_ids)) if parent_limit_ids else limit_ids
|
|
355
|
+
|
|
356
|
+
if user_id is None:
|
|
357
|
+
# use the parent user_id if it exists
|
|
358
|
+
context["user_id"] = parentContext.get("user_id", None)
|
|
359
|
+
elif len(user_id) == 0:
|
|
360
|
+
# caller passing an empty string explicitly blocks inheriting from the parent state
|
|
361
|
+
context["user_id"] = None
|
|
362
|
+
else:
|
|
339
363
|
context["user_id"] = user_id
|
|
340
364
|
|
|
341
365
|
self.set_context(context)
|
|
@@ -345,7 +369,6 @@ class _PayiInstrumentor:
|
|
|
345
369
|
func: Any,
|
|
346
370
|
proxy: bool,
|
|
347
371
|
limit_ids: Optional["list[str]"],
|
|
348
|
-
request_tags: Optional["list[str]"],
|
|
349
372
|
experience_name: Optional[str],
|
|
350
373
|
experience_id: Optional[str],
|
|
351
374
|
use_case_name: Optional[str],
|
|
@@ -355,15 +378,14 @@ class _PayiInstrumentor:
|
|
|
355
378
|
*args: Any,
|
|
356
379
|
**kwargs: Any,
|
|
357
380
|
) -> Any:
|
|
358
|
-
context,
|
|
381
|
+
context, parentContext = self._setup_call_func()
|
|
359
382
|
|
|
360
383
|
with self:
|
|
361
384
|
self._init_context(
|
|
362
385
|
context,
|
|
363
|
-
|
|
386
|
+
parentContext,
|
|
364
387
|
proxy,
|
|
365
388
|
limit_ids,
|
|
366
|
-
request_tags,
|
|
367
389
|
experience_name,
|
|
368
390
|
experience_id,
|
|
369
391
|
use_case_name,
|
|
@@ -377,7 +399,6 @@ class _PayiInstrumentor:
|
|
|
377
399
|
func: Any,
|
|
378
400
|
proxy: bool,
|
|
379
401
|
limit_ids: Optional["list[str]"],
|
|
380
|
-
request_tags: Optional["list[str]"],
|
|
381
402
|
experience_name: Optional[str],
|
|
382
403
|
experience_id: Optional[str],
|
|
383
404
|
use_case_name: Optional[str],
|
|
@@ -387,15 +408,14 @@ class _PayiInstrumentor:
|
|
|
387
408
|
*args: Any,
|
|
388
409
|
**kwargs: Any,
|
|
389
410
|
) -> Any:
|
|
390
|
-
context,
|
|
411
|
+
context, parentContext = self._setup_call_func()
|
|
391
412
|
|
|
392
413
|
with self:
|
|
393
414
|
self._init_context(
|
|
394
415
|
context,
|
|
395
|
-
|
|
416
|
+
parentContext,
|
|
396
417
|
proxy,
|
|
397
418
|
limit_ids,
|
|
398
|
-
request_tags,
|
|
399
419
|
experience_name,
|
|
400
420
|
experience_id,
|
|
401
421
|
use_case_name,
|
|
@@ -477,9 +497,7 @@ class _PayiInstrumentor:
|
|
|
477
497
|
async def achat_wrapper(
|
|
478
498
|
self,
|
|
479
499
|
category: str,
|
|
480
|
-
|
|
481
|
-
process_request: Optional[Callable[[IngestUnitsParams, Any, Any], None]],
|
|
482
|
-
process_synchronous_response: Any,
|
|
500
|
+
provider: _ProviderRequest,
|
|
483
501
|
is_streaming: _IsStreaming,
|
|
484
502
|
wrapped: Any,
|
|
485
503
|
instance: Any,
|
|
@@ -496,7 +514,7 @@ class _PayiInstrumentor:
|
|
|
496
514
|
|
|
497
515
|
# after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
|
|
498
516
|
extra_headers = kwargs.get("extra_headers", {})
|
|
499
|
-
self.
|
|
517
|
+
self._update_extra_headers(context, extra_headers)
|
|
500
518
|
|
|
501
519
|
if context.get("proxy", True):
|
|
502
520
|
if "extra_headers" not in kwargs:
|
|
@@ -504,8 +522,8 @@ class _PayiInstrumentor:
|
|
|
504
522
|
|
|
505
523
|
return await wrapped(*args, **kwargs)
|
|
506
524
|
|
|
507
|
-
|
|
508
|
-
|
|
525
|
+
provider._ingest = {"category": category, "units": {}} # type: ignore
|
|
526
|
+
provider._ingest["resource"] = kwargs.get("model", "")
|
|
509
527
|
|
|
510
528
|
if category == PayiCategories.openai and instance and hasattr(instance, "_client"):
|
|
511
529
|
from .OpenAIInstrumentor import OpenAiInstrumentor # noqa: I001
|
|
@@ -523,21 +541,20 @@ class _PayiInstrumentor:
|
|
|
523
541
|
logging.error("Azure OpenAI invalid resource scope, not ingesting")
|
|
524
542
|
return wrapped(*args, **kwargs)
|
|
525
543
|
|
|
526
|
-
|
|
544
|
+
provider._ingest["resource_scope"] = resource_scope
|
|
527
545
|
|
|
528
546
|
category = PayiCategories.azure_openai
|
|
529
547
|
|
|
530
|
-
|
|
531
|
-
|
|
548
|
+
provider._ingest["category"] = category
|
|
549
|
+
provider._ingest["resource"] = route_as_resource
|
|
532
550
|
|
|
533
551
|
current_frame = inspect.currentframe()
|
|
534
552
|
# f_back excludes the current frame, strip() cleans up whitespace and newlines
|
|
535
553
|
stack = [frame.strip() for frame in traceback.format_stack(current_frame.f_back)] # type: ignore
|
|
536
554
|
|
|
537
|
-
|
|
555
|
+
provider._ingest['properties'] = { 'system.stack_trace': json.dumps(stack) }
|
|
538
556
|
|
|
539
|
-
|
|
540
|
-
process_request(ingest, (), instance)
|
|
557
|
+
provider.process_request(kwargs)
|
|
541
558
|
|
|
542
559
|
sw = Stopwatch()
|
|
543
560
|
stream: bool = False
|
|
@@ -550,7 +567,7 @@ class _PayiInstrumentor:
|
|
|
550
567
|
stream = False
|
|
551
568
|
|
|
552
569
|
try:
|
|
553
|
-
self._prepare_ingest(
|
|
570
|
+
self._prepare_ingest(provider._ingest, extra_headers, **kwargs)
|
|
554
571
|
sw.start()
|
|
555
572
|
response = await wrapped(*args, **kwargs)
|
|
556
573
|
|
|
@@ -568,9 +585,8 @@ class _PayiInstrumentor:
|
|
|
568
585
|
instance=instance,
|
|
569
586
|
instrumentor=self,
|
|
570
587
|
log_prompt_and_response=self._log_prompt_and_response,
|
|
571
|
-
ingest=ingest,
|
|
572
588
|
stopwatch=sw,
|
|
573
|
-
|
|
589
|
+
provider=provider,
|
|
574
590
|
is_bedrock=False,
|
|
575
591
|
)
|
|
576
592
|
|
|
@@ -578,28 +594,25 @@ class _PayiInstrumentor:
|
|
|
578
594
|
|
|
579
595
|
sw.stop()
|
|
580
596
|
duration = sw.elapsed_ms_int()
|
|
581
|
-
|
|
582
|
-
|
|
597
|
+
provider._ingest["end_to_end_latency_ms"] = duration
|
|
598
|
+
provider._ingest["http_status_code"] = 200
|
|
583
599
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return return_result
|
|
600
|
+
return_result: Any = provider.process_synchronous_response(
|
|
601
|
+
response=response,
|
|
602
|
+
log_prompt_and_response=self._log_prompt_and_response,
|
|
603
|
+
kwargs=kwargs)
|
|
604
|
+
|
|
605
|
+
if return_result:
|
|
606
|
+
return return_result
|
|
592
607
|
|
|
593
|
-
await self._aingest_units(
|
|
608
|
+
await self._aingest_units(provider._ingest)
|
|
594
609
|
|
|
595
610
|
return response
|
|
596
611
|
|
|
597
612
|
def chat_wrapper(
|
|
598
613
|
self,
|
|
599
614
|
category: str,
|
|
600
|
-
|
|
601
|
-
process_request: Optional[Callable[[IngestUnitsParams, Any, Any], None]],
|
|
602
|
-
process_synchronous_response: Any,
|
|
615
|
+
provider: _ProviderRequest,
|
|
603
616
|
is_streaming: _IsStreaming,
|
|
604
617
|
wrapped: Any,
|
|
605
618
|
instance: Any,
|
|
@@ -620,7 +633,7 @@ class _PayiInstrumentor:
|
|
|
620
633
|
|
|
621
634
|
# after _udpate_headers, all metadata to add to ingest is in extra_headers, keyed by the xproxy-xxx header name
|
|
622
635
|
extra_headers = kwargs.get("extra_headers", {})
|
|
623
|
-
self.
|
|
636
|
+
self._update_extra_headers(context, extra_headers)
|
|
624
637
|
|
|
625
638
|
if context.get("proxy", True):
|
|
626
639
|
if "extra_headers" not in kwargs:
|
|
@@ -628,13 +641,13 @@ class _PayiInstrumentor:
|
|
|
628
641
|
|
|
629
642
|
return wrapped(*args, **kwargs)
|
|
630
643
|
|
|
631
|
-
|
|
644
|
+
provider._ingest = {"category": category, "units": {}} # type: ignore
|
|
632
645
|
if is_bedrock:
|
|
633
646
|
# boto3 doesn't allow extra_headers
|
|
634
647
|
kwargs.pop("extra_headers", None)
|
|
635
|
-
|
|
648
|
+
provider._ingest["resource"] = kwargs.get("modelId", "")
|
|
636
649
|
else:
|
|
637
|
-
|
|
650
|
+
provider._ingest["resource"] = kwargs.get("model", "")
|
|
638
651
|
|
|
639
652
|
if category == PayiCategories.openai and instance and hasattr(instance, "_client"):
|
|
640
653
|
from .OpenAIInstrumentor import OpenAiInstrumentor # noqa: I001
|
|
@@ -652,21 +665,20 @@ class _PayiInstrumentor:
|
|
|
652
665
|
logging.error("Azure OpenAI invalid resource scope, not ingesting")
|
|
653
666
|
return wrapped(*args, **kwargs)
|
|
654
667
|
|
|
655
|
-
|
|
668
|
+
provider._ingest["resource_scope"] = resource_scope
|
|
656
669
|
|
|
657
670
|
category = PayiCategories.azure_openai
|
|
658
671
|
|
|
659
|
-
|
|
660
|
-
|
|
672
|
+
provider._ingest["category"] = category
|
|
673
|
+
provider._ingest["resource"] = route_as_resource
|
|
661
674
|
|
|
662
675
|
current_frame = inspect.currentframe()
|
|
663
676
|
# f_back excludes the current frame, strip() cleans up whitespace and newlines
|
|
664
677
|
stack = [frame.strip() for frame in traceback.format_stack(current_frame.f_back)] # type: ignore
|
|
665
678
|
|
|
666
|
-
|
|
679
|
+
provider._ingest['properties'] = { 'system.stack_trace': json.dumps(stack) }
|
|
667
680
|
|
|
668
|
-
|
|
669
|
-
process_request(ingest, (), kwargs)
|
|
681
|
+
provider.process_request(kwargs)
|
|
670
682
|
|
|
671
683
|
sw = Stopwatch()
|
|
672
684
|
stream: bool = False
|
|
@@ -679,7 +691,7 @@ class _PayiInstrumentor:
|
|
|
679
691
|
stream = False
|
|
680
692
|
|
|
681
693
|
try:
|
|
682
|
-
self._prepare_ingest(
|
|
694
|
+
self._prepare_ingest(provider._ingest, extra_headers, **kwargs)
|
|
683
695
|
sw.start()
|
|
684
696
|
response = wrapped(*args, **kwargs)
|
|
685
697
|
|
|
@@ -697,9 +709,8 @@ class _PayiInstrumentor:
|
|
|
697
709
|
instance=instance,
|
|
698
710
|
instrumentor=self,
|
|
699
711
|
log_prompt_and_response=self._log_prompt_and_response,
|
|
700
|
-
ingest=ingest,
|
|
701
712
|
stopwatch=sw,
|
|
702
|
-
|
|
713
|
+
provider=provider,
|
|
703
714
|
is_bedrock=is_bedrock,
|
|
704
715
|
)
|
|
705
716
|
|
|
@@ -714,114 +725,97 @@ class _PayiInstrumentor:
|
|
|
714
725
|
|
|
715
726
|
sw.stop()
|
|
716
727
|
duration = sw.elapsed_ms_int()
|
|
717
|
-
|
|
718
|
-
|
|
728
|
+
provider._ingest["end_to_end_latency_ms"] = duration
|
|
729
|
+
provider._ingest["http_status_code"] = 200
|
|
719
730
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
if return_result:
|
|
727
|
-
return return_result
|
|
731
|
+
return_result: Any = provider.process_synchronous_response(
|
|
732
|
+
response=response,
|
|
733
|
+
log_prompt_and_response=self._log_prompt_and_response,
|
|
734
|
+
kwargs=kwargs)
|
|
735
|
+
if return_result:
|
|
736
|
+
return return_result
|
|
728
737
|
|
|
729
|
-
self._ingest_units(
|
|
738
|
+
self._ingest_units(provider._ingest)
|
|
730
739
|
|
|
731
740
|
return response
|
|
732
741
|
|
|
733
742
|
@staticmethod
|
|
734
|
-
def
|
|
743
|
+
def _update_extra_headers(
|
|
735
744
|
context: _Context,
|
|
736
745
|
extra_headers: "dict[str, str]",
|
|
737
746
|
) -> None:
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
747
|
+
context_limit_ids: Optional[list[str]] = context.get("limit_ids")
|
|
748
|
+
context_experience_name: Optional[str] = context.get("experience_name")
|
|
749
|
+
context_experience_id: Optional[str] = context.get("experience_id")
|
|
750
|
+
context_use_case_name: Optional[str] = context.get("use_case_name")
|
|
751
|
+
context_use_case_id: Optional[str] = context.get("use_case_id")
|
|
752
|
+
context_use_case_version: Optional[int] = context.get("use_case_version")
|
|
753
|
+
context_user_id: Optional[str] = context.get("user_id")
|
|
754
|
+
|
|
755
|
+
# headers_limit_ids = extra_headers.get(PayiHeaderNames.limit_ids, None)
|
|
756
|
+
|
|
757
|
+
# If the caller specifies limit_ids in extra_headers, it takes precedence over the decorator
|
|
758
|
+
if PayiHeaderNames.limit_ids in extra_headers:
|
|
759
|
+
headers_limit_ids = extra_headers.get(PayiHeaderNames.limit_ids)
|
|
760
|
+
|
|
761
|
+
if headers_limit_ids is None or len(headers_limit_ids) == 0:
|
|
762
|
+
# headers_limit_ids is empty, remove it from extra_headers
|
|
763
|
+
extra_headers.pop(PayiHeaderNames.limit_ids, None)
|
|
764
|
+
else:
|
|
765
|
+
# leave the value in extra_headers
|
|
766
|
+
...
|
|
767
|
+
elif context_limit_ids:
|
|
768
|
+
extra_headers[PayiHeaderNames.limit_ids] = ",".join(context_limit_ids)
|
|
769
|
+
|
|
770
|
+
if PayiHeaderNames.user_id in extra_headers:
|
|
771
|
+
headers_user_id = extra_headers.get(PayiHeaderNames.user_id, None)
|
|
772
|
+
if headers_user_id is None or len(headers_user_id) == 0:
|
|
773
|
+
# headers_user_id is empty, remove it from extra_headers
|
|
774
|
+
extra_headers.pop(PayiHeaderNames.user_id, None)
|
|
753
775
|
else:
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
extra_headers
|
|
776
|
+
# leave the value in extra_headers
|
|
777
|
+
...
|
|
778
|
+
elif context_user_id:
|
|
779
|
+
extra_headers[PayiHeaderNames.user_id] = context_user_id
|
|
780
|
+
|
|
781
|
+
if PayiHeaderNames.use_case_name in extra_headers:
|
|
782
|
+
headers_use_case_name = extra_headers.get(PayiHeaderNames.use_case_name, None)
|
|
783
|
+
if headers_use_case_name is None or len(headers_use_case_name) == 0:
|
|
784
|
+
# headers_use_case_name is empty, remove all use case related headers
|
|
785
|
+
extra_headers.pop(PayiHeaderNames.use_case_name, None)
|
|
786
|
+
extra_headers.pop(PayiHeaderNames.use_case_id, None)
|
|
787
|
+
extra_headers.pop(PayiHeaderNames.use_case_version, None)
|
|
764
788
|
else:
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
extra_headers
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
elif
|
|
784
|
-
|
|
785
|
-
if
|
|
786
|
-
extra_headers[PayiHeaderNames.
|
|
787
|
-
|
|
788
|
-
else:
|
|
789
|
-
# use the inner experience name and id as-is
|
|
790
|
-
...
|
|
791
|
-
|
|
792
|
-
# inner extra_headers use_casee_name and use_case_id take precedence over outer decorator use_case_name and use_case_id
|
|
793
|
-
# if either inner value is specified, ignore outer decorator values
|
|
794
|
-
if PayiHeaderNames.use_case_name not in extra_headers and PayiHeaderNames.use_case_id not in extra_headers:
|
|
795
|
-
|
|
796
|
-
# use decorator values
|
|
797
|
-
if use_case_name is not None:
|
|
798
|
-
extra_headers[PayiHeaderNames.use_case_name] = use_case_name
|
|
799
|
-
if use_case_id is not None:
|
|
800
|
-
extra_headers[PayiHeaderNames.use_case_id] = use_case_id
|
|
801
|
-
if use_case_version is not None:
|
|
802
|
-
extra_headers[PayiHeaderNames.use_case_version] = str(use_case_version)
|
|
803
|
-
|
|
804
|
-
elif PayiHeaderNames.use_case_id in extra_headers and PayiHeaderNames.use_case_name not in extra_headers:
|
|
805
|
-
# use the decorator experience name and the inner experience id
|
|
806
|
-
if use_case_name is not None:
|
|
807
|
-
extra_headers[PayiHeaderNames.use_case_name] = use_case_name
|
|
808
|
-
|
|
809
|
-
# use the decorator experience version and the inner experience id
|
|
810
|
-
if use_case_version is not None:
|
|
811
|
-
extra_headers[PayiHeaderNames.use_case_version] = str(use_case_version) # TODO use case
|
|
812
|
-
|
|
813
|
-
else:
|
|
814
|
-
# use the inner experience name and id as-is
|
|
815
|
-
...
|
|
789
|
+
# leave the value in extra_headers
|
|
790
|
+
...
|
|
791
|
+
elif context_use_case_name:
|
|
792
|
+
extra_headers[PayiHeaderNames.use_case_name] = context_use_case_name
|
|
793
|
+
if context_use_case_id is not None:
|
|
794
|
+
extra_headers[PayiHeaderNames.use_case_id] = context_use_case_id
|
|
795
|
+
if context_use_case_version is not None:
|
|
796
|
+
extra_headers[PayiHeaderNames.use_case_version] = str(context_use_case_version)
|
|
797
|
+
|
|
798
|
+
if PayiHeaderNames.experience_name in extra_headers:
|
|
799
|
+
headers_experience_name = extra_headers.get(PayiHeaderNames.experience_name, None)
|
|
800
|
+
if headers_experience_name is None or len(headers_experience_name) == 0:
|
|
801
|
+
# headers_experience_name is empty, remove all experience related headers
|
|
802
|
+
extra_headers.pop(PayiHeaderNames.experience_name, None)
|
|
803
|
+
extra_headers.pop(PayiHeaderNames.experience_id, None)
|
|
804
|
+
else:
|
|
805
|
+
# leave the value in extra_headers
|
|
806
|
+
...
|
|
807
|
+
elif context_experience_name is not None:
|
|
808
|
+
extra_headers[PayiHeaderNames.experience_name] = context_experience_name
|
|
809
|
+
if context_experience_id is not None:
|
|
810
|
+
extra_headers[PayiHeaderNames.experience_id] = context_experience_id
|
|
816
811
|
|
|
817
812
|
@staticmethod
|
|
818
|
-
def update_for_vision(input: int, units: 'dict[str, Units]') -> int:
|
|
819
|
-
if
|
|
820
|
-
|
|
821
|
-
vision = input - prompt_token_estimate
|
|
813
|
+
def update_for_vision(input: int, units: 'dict[str, Units]', estimated_prompt_tokens: Optional[int]) -> int:
|
|
814
|
+
if estimated_prompt_tokens:
|
|
815
|
+
vision = input - estimated_prompt_tokens
|
|
822
816
|
if (vision > 0):
|
|
823
817
|
units["vision"] = Units(input=vision, output=0)
|
|
824
|
-
input =
|
|
818
|
+
input = estimated_prompt_tokens
|
|
825
819
|
|
|
826
820
|
return input
|
|
827
821
|
|
|
@@ -863,16 +857,15 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
|
|
|
863
857
|
response: Any,
|
|
864
858
|
instance: Any,
|
|
865
859
|
instrumentor: _PayiInstrumentor,
|
|
866
|
-
ingest: IngestUnitsParams,
|
|
867
860
|
stopwatch: Stopwatch,
|
|
868
|
-
|
|
861
|
+
provider: _ProviderRequest,
|
|
869
862
|
log_prompt_and_response: bool = True,
|
|
870
863
|
is_bedrock: bool = False,
|
|
871
864
|
) -> None:
|
|
872
865
|
|
|
873
866
|
bedrock_from_stream: bool = False
|
|
874
867
|
if is_bedrock:
|
|
875
|
-
|
|
868
|
+
provider._ingest["provider_response_id"] = response["ResponseMetadata"]["RequestId"]
|
|
876
869
|
stream = response.get("stream", None)
|
|
877
870
|
|
|
878
871
|
if stream:
|
|
@@ -889,11 +882,10 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
|
|
|
889
882
|
|
|
890
883
|
self._instrumentor = instrumentor
|
|
891
884
|
self._stopwatch: Stopwatch = stopwatch
|
|
892
|
-
self._ingest: IngestUnitsParams = ingest
|
|
893
885
|
self._log_prompt_and_response: bool = log_prompt_and_response
|
|
894
886
|
self._responses: list[str] = []
|
|
895
887
|
|
|
896
|
-
self.
|
|
888
|
+
self._provider: _ProviderRequest = provider
|
|
897
889
|
|
|
898
890
|
self._first_token: bool = True
|
|
899
891
|
self._is_bedrock: bool = is_bedrock
|
|
@@ -913,7 +905,7 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
|
|
|
913
905
|
|
|
914
906
|
def __iter__(self) -> Any:
|
|
915
907
|
if self._is_bedrock:
|
|
916
|
-
# MUST
|
|
908
|
+
# MUST reside in a separate function so that the yield statement (e.g. the generator) doesn't implicitly return its own iterator and overriding self
|
|
917
909
|
return self._iter_bedrock()
|
|
918
910
|
return self
|
|
919
911
|
|
|
@@ -942,7 +934,9 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
|
|
|
942
934
|
self._stop_iteration()
|
|
943
935
|
raise e
|
|
944
936
|
else:
|
|
945
|
-
self._evaluate_chunk(chunk)
|
|
937
|
+
if self._evaluate_chunk(chunk) == False:
|
|
938
|
+
return self.__next__()
|
|
939
|
+
|
|
946
940
|
return chunk
|
|
947
941
|
|
|
948
942
|
async def __anext__(self) -> Any:
|
|
@@ -953,35 +947,35 @@ class ChatStreamWrapper(ObjectProxy): # type: ignore
|
|
|
953
947
|
await self._astop_iteration()
|
|
954
948
|
raise e
|
|
955
949
|
else:
|
|
956
|
-
self._evaluate_chunk(chunk)
|
|
950
|
+
if self._evaluate_chunk(chunk) == False:
|
|
951
|
+
return await self.__anext__()
|
|
957
952
|
return chunk
|
|
958
953
|
|
|
959
|
-
def _evaluate_chunk(self, chunk: Any) ->
|
|
954
|
+
def _evaluate_chunk(self, chunk: Any) -> bool:
|
|
960
955
|
if self._first_token:
|
|
961
|
-
self._ingest["time_to_first_token_ms"] = self._stopwatch.elapsed_ms_int()
|
|
956
|
+
self._provider._ingest["time_to_first_token_ms"] = self._stopwatch.elapsed_ms_int()
|
|
962
957
|
self._first_token = False
|
|
963
958
|
|
|
964
959
|
if self._log_prompt_and_response:
|
|
965
960
|
self._responses.append(self.chunk_to_json(chunk))
|
|
966
961
|
|
|
967
|
-
|
|
968
|
-
self._process_chunk(chunk, self._ingest)
|
|
962
|
+
return self._provider.process_chunk(chunk)
|
|
969
963
|
|
|
970
964
|
def _process_stop_iteration(self) -> None:
|
|
971
965
|
self._stopwatch.stop()
|
|
972
|
-
self._ingest["end_to_end_latency_ms"] = self._stopwatch.elapsed_ms_int()
|
|
973
|
-
self._ingest["http_status_code"] = 200
|
|
966
|
+
self._provider._ingest["end_to_end_latency_ms"] = self._stopwatch.elapsed_ms_int()
|
|
967
|
+
self._provider._ingest["http_status_code"] = 200
|
|
974
968
|
|
|
975
969
|
if self._log_prompt_and_response:
|
|
976
|
-
self._ingest["provider_response_json"] = self._responses
|
|
970
|
+
self._provider._ingest["provider_response_json"] = self._responses
|
|
977
971
|
|
|
978
972
|
async def _astop_iteration(self) -> None:
|
|
979
973
|
self._process_stop_iteration()
|
|
980
|
-
await self._instrumentor._aingest_units(self._ingest)
|
|
974
|
+
await self._instrumentor._aingest_units(self._provider._ingest)
|
|
981
975
|
|
|
982
976
|
def _stop_iteration(self) -> None:
|
|
983
977
|
self._process_stop_iteration()
|
|
984
|
-
self._instrumentor._ingest_units(self._ingest)
|
|
978
|
+
self._instrumentor._ingest_units(self._provider._ingest)
|
|
985
979
|
|
|
986
980
|
@staticmethod
|
|
987
981
|
def chunk_to_json(chunk: Any) -> str:
|
|
@@ -1023,18 +1017,6 @@ def payi_instrument(
|
|
|
1023
1017
|
elif isinstance(p, AsyncPayi): # type: ignore
|
|
1024
1018
|
apayi_param = p
|
|
1025
1019
|
|
|
1026
|
-
global_context_ingest: Optional[bool] = None
|
|
1027
|
-
|
|
1028
|
-
if config is not None:
|
|
1029
|
-
if "proxy" not in config:
|
|
1030
|
-
config["proxy"] = False
|
|
1031
|
-
global_context_ingest = config.get("proxy") == False
|
|
1032
|
-
|
|
1033
|
-
# Use default clients if not provided for global ingest instrumentation
|
|
1034
|
-
if not payi_param and not apayi_param and global_context_ingest:
|
|
1035
|
-
payi_param = Payi()
|
|
1036
|
-
apayi_param = AsyncPayi()
|
|
1037
|
-
|
|
1038
1020
|
# allow for both payi and apayi to be None for the @proxy case
|
|
1039
1021
|
_instrumentor = _PayiInstrumentor(
|
|
1040
1022
|
payi=payi_param,
|
|
@@ -1047,7 +1029,6 @@ def payi_instrument(
|
|
|
1047
1029
|
|
|
1048
1030
|
def ingest(
|
|
1049
1031
|
limit_ids: Optional["list[str]"] = None,
|
|
1050
|
-
request_tags: Optional["list[str]"] = None,
|
|
1051
1032
|
experience_name: Optional[str] = None,
|
|
1052
1033
|
experience_id: Optional[str] = None,
|
|
1053
1034
|
use_case_name: Optional[str] = None,
|
|
@@ -1066,7 +1047,6 @@ def ingest(
|
|
|
1066
1047
|
func,
|
|
1067
1048
|
False,
|
|
1068
1049
|
limit_ids,
|
|
1069
|
-
request_tags,
|
|
1070
1050
|
experience_name,
|
|
1071
1051
|
experience_id,
|
|
1072
1052
|
use_case_name,
|
|
@@ -1085,7 +1065,6 @@ def ingest(
|
|
|
1085
1065
|
func,
|
|
1086
1066
|
False,
|
|
1087
1067
|
limit_ids,
|
|
1088
|
-
request_tags,
|
|
1089
1068
|
experience_name,
|
|
1090
1069
|
experience_id,
|
|
1091
1070
|
use_case_name,
|
|
@@ -1100,7 +1079,6 @@ def ingest(
|
|
|
1100
1079
|
|
|
1101
1080
|
def proxy(
|
|
1102
1081
|
limit_ids: Optional["list[str]"] = None,
|
|
1103
|
-
request_tags: Optional["list[str]"] = None,
|
|
1104
1082
|
experience_name: Optional[str] = None,
|
|
1105
1083
|
experience_id: Optional[str] = None,
|
|
1106
1084
|
use_case_id: Optional[str] = None,
|
|
@@ -1118,7 +1096,6 @@ def proxy(
|
|
|
1118
1096
|
func,
|
|
1119
1097
|
True,
|
|
1120
1098
|
limit_ids,
|
|
1121
|
-
request_tags,
|
|
1122
1099
|
experience_name,
|
|
1123
1100
|
experience_id,
|
|
1124
1101
|
use_case_name,
|
|
@@ -1138,7 +1115,6 @@ def proxy(
|
|
|
1138
1115
|
func,
|
|
1139
1116
|
True,
|
|
1140
1117
|
limit_ids,
|
|
1141
|
-
request_tags,
|
|
1142
1118
|
experience_name,
|
|
1143
1119
|
experience_id,
|
|
1144
1120
|
use_case_name,
|