openlit 1.34.31__py3-none-any.whl → 1.34.33__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.
@@ -147,7 +147,7 @@ def common_agent_logic(
147
147
  scope._span.set_status(Status(StatusCode.OK))
148
148
 
149
149
  # Metrics
150
- if not disable_metrics and hasattr(scope, "_input_tokens"):
150
+ if not disable_metrics and metrics is not None and hasattr(scope, "_input_tokens"):
151
151
  record_completion_metrics(
152
152
  metrics,
153
153
  operation_type,
@@ -196,7 +196,7 @@ def process_agent_creation(
196
196
  scope._end_time = time.time()
197
197
  scope._span = span
198
198
  scope._agent_name = agent_name
199
- scope._request_model = llm_config.get("model", "gpt-4o")
199
+ scope._request_model = llm_config.get("model", "unknown")
200
200
  scope._response_model = scope._request_model
201
201
  scope._system_message = system_message
202
202
  scope._server_address, scope._server_port = server_address, server_port
@@ -276,3 +276,344 @@ def process_agent_run(
276
276
  )
277
277
 
278
278
  return response
279
+
280
+
281
+ def process_agent_generate_reply(
282
+ response,
283
+ agent_name,
284
+ request_model,
285
+ messages,
286
+ sender,
287
+ pricing_info,
288
+ server_port,
289
+ server_address,
290
+ environment,
291
+ application_name,
292
+ metrics,
293
+ start_time,
294
+ span,
295
+ capture_message_content=False,
296
+ disable_metrics=False,
297
+ version="1.0.0",
298
+ **kwargs,
299
+ ):
300
+ """
301
+ Process agent generate_reply and generate Telemetry
302
+ """
303
+
304
+ # Create scope object
305
+ scope = type("GenericScope", (), {})()
306
+
307
+ scope._start_time = start_time
308
+ scope._end_time = time.time()
309
+ scope._span = span
310
+ scope._agent_name = agent_name
311
+ scope._request_model = request_model
312
+ scope._response_model = request_model
313
+ scope._server_address, scope._server_port = server_address, server_port
314
+ scope._messages = messages
315
+ scope._sender_name = getattr(sender, "name", "Unknown") if sender else "Unknown"
316
+
317
+ # Set agent-specific attributes
318
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_MESSAGE_TYPE, "generate_reply")
319
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_SENDER, scope._sender_name)
320
+
321
+ # Process response content
322
+ if response and isinstance(response, str):
323
+ scope._response_content = response
324
+ elif response and hasattr(response, "content"):
325
+ scope._response_content = response.content
326
+ else:
327
+ scope._response_content = str(response) if response else ""
328
+
329
+ # Try to extract token information if available
330
+ try:
331
+ # Mock token calculation for generate_reply
332
+ scope._input_tokens = len(str(messages)) // 4 if messages else 0
333
+ scope._output_tokens = len(scope._response_content) // 4
334
+ scope._cost = get_chat_model_cost(
335
+ request_model, pricing_info, scope._input_tokens, scope._output_tokens
336
+ )
337
+ except Exception:
338
+ scope._input_tokens = 0
339
+ scope._output_tokens = 0
340
+ scope._cost = 0.0
341
+
342
+ common_agent_logic(
343
+ scope,
344
+ pricing_info,
345
+ environment,
346
+ application_name,
347
+ metrics,
348
+ capture_message_content,
349
+ disable_metrics,
350
+ version,
351
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
352
+ )
353
+
354
+ return response
355
+
356
+
357
+ def process_agent_receive(
358
+ message,
359
+ agent_name,
360
+ sender_name,
361
+ agent_instance,
362
+ pricing_info,
363
+ server_port,
364
+ server_address,
365
+ environment,
366
+ application_name,
367
+ metrics,
368
+ start_time,
369
+ span,
370
+ capture_message_content=False,
371
+ disable_metrics=False,
372
+ version="1.0.0",
373
+ **kwargs,
374
+ ):
375
+ """
376
+ Process agent receive and generate Telemetry
377
+ """
378
+
379
+ # Create scope object
380
+ scope = type("GenericScope", (), {})()
381
+
382
+ scope._start_time = start_time
383
+ scope._end_time = time.time()
384
+ scope._span = span
385
+ scope._agent_name = agent_name
386
+ scope._sender_name = sender_name
387
+ scope._server_address, scope._server_port = server_address, server_port
388
+ scope._message = message
389
+
390
+ # Extract model from agent instance
391
+ if hasattr(agent_instance, "llm_config") and isinstance(
392
+ agent_instance.llm_config, dict
393
+ ):
394
+ scope._request_model = agent_instance.llm_config.get("model", "unknown")
395
+ else:
396
+ scope._request_model = "unknown"
397
+ scope._response_model = scope._request_model
398
+
399
+ # Set agent-specific attributes
400
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_MESSAGE_TYPE, "receive")
401
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_SENDER, sender_name)
402
+
403
+ # Content capture for received message
404
+ if capture_message_content:
405
+ span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, str(message))
406
+
407
+ common_agent_logic(
408
+ scope,
409
+ pricing_info,
410
+ environment,
411
+ application_name,
412
+ metrics,
413
+ capture_message_content,
414
+ disable_metrics,
415
+ version,
416
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
417
+ )
418
+
419
+
420
+ def process_agent_send(
421
+ message,
422
+ agent_name,
423
+ recipient_name,
424
+ agent_instance,
425
+ pricing_info,
426
+ server_port,
427
+ server_address,
428
+ environment,
429
+ application_name,
430
+ metrics,
431
+ start_time,
432
+ span,
433
+ capture_message_content=False,
434
+ disable_metrics=False,
435
+ version="1.0.0",
436
+ **kwargs,
437
+ ):
438
+ """
439
+ Process agent send and generate Telemetry
440
+ """
441
+
442
+ # Create scope object
443
+ scope = type("GenericScope", (), {})()
444
+
445
+ scope._start_time = start_time
446
+ scope._end_time = time.time()
447
+ scope._span = span
448
+ scope._agent_name = agent_name
449
+ scope._recipient_name = recipient_name
450
+ scope._server_address, scope._server_port = server_address, server_port
451
+ scope._message = message
452
+
453
+ # Extract model from agent instance
454
+ if hasattr(agent_instance, "llm_config") and isinstance(
455
+ agent_instance.llm_config, dict
456
+ ):
457
+ scope._request_model = agent_instance.llm_config.get("model", "unknown")
458
+ else:
459
+ scope._request_model = "unknown"
460
+ scope._response_model = scope._request_model
461
+
462
+ # Set agent-specific attributes
463
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_MESSAGE_TYPE, "send")
464
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_RECIPIENT, recipient_name)
465
+
466
+ # Content capture for sent message
467
+ if capture_message_content:
468
+ span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, str(message))
469
+
470
+ common_agent_logic(
471
+ scope,
472
+ pricing_info,
473
+ environment,
474
+ application_name,
475
+ metrics,
476
+ capture_message_content,
477
+ disable_metrics,
478
+ version,
479
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
480
+ )
481
+
482
+
483
+ def process_groupchat_operation(
484
+ group_name,
485
+ participants,
486
+ messages,
487
+ sender,
488
+ max_turns,
489
+ request_model,
490
+ pricing_info,
491
+ server_port,
492
+ server_address,
493
+ environment,
494
+ application_name,
495
+ metrics,
496
+ start_time,
497
+ span,
498
+ capture_message_content=False,
499
+ disable_metrics=False,
500
+ version="1.0.0",
501
+ **kwargs,
502
+ ):
503
+ """
504
+ Process GroupChat operation and generate Telemetry
505
+ """
506
+
507
+ # Create scope object
508
+ scope = type("GenericScope", (), {})()
509
+
510
+ scope._start_time = start_time
511
+ scope._end_time = time.time()
512
+ scope._span = span
513
+ scope._group_name = group_name
514
+ scope._participants = participants
515
+ scope._server_address, scope._server_port = server_address, server_port
516
+ scope._sender_name = getattr(sender, "name", "Unknown") if sender else "Unknown"
517
+
518
+ # Add required model attributes for common_agent_logic
519
+ scope._request_model = request_model
520
+ scope._response_model = request_model
521
+
522
+ # Set agent name for groupchat
523
+ scope._agent_name = group_name
524
+
525
+ # Set GroupChat-specific attributes
526
+ span.set_attribute(
527
+ SemanticConvention.GEN_AI_GROUPCHAT_PARTICIPANTS, ",".join(participants)
528
+ )
529
+ span.set_attribute(
530
+ SemanticConvention.GEN_AI_WORKFLOW_AGENT_COUNT, len(participants)
531
+ )
532
+ span.set_attribute(SemanticConvention.GEN_AI_WORKFLOW_EXECUTION_TYPE, "groupchat")
533
+
534
+ if max_turns:
535
+ span.set_attribute(SemanticConvention.GEN_AI_GROUPCHAT_TURN_COUNT, max_turns)
536
+
537
+ # Content capture for GroupChat
538
+ if capture_message_content and messages:
539
+ span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, str(messages))
540
+
541
+ # Use framework operation type for GroupChat
542
+ common_agent_logic(
543
+ scope,
544
+ pricing_info,
545
+ environment,
546
+ application_name,
547
+ metrics,
548
+ capture_message_content,
549
+ disable_metrics,
550
+ version,
551
+ SemanticConvention.GEN_AI_OPERATION_TYPE_FRAMEWORK,
552
+ )
553
+
554
+
555
+ def process_speaker_selection(
556
+ last_speaker,
557
+ selected_speaker,
558
+ selector,
559
+ agents,
560
+ request_model,
561
+ pricing_info,
562
+ server_port,
563
+ server_address,
564
+ environment,
565
+ application_name,
566
+ metrics,
567
+ start_time,
568
+ span,
569
+ capture_message_content=False,
570
+ disable_metrics=False,
571
+ version="1.0.0",
572
+ **kwargs,
573
+ ):
574
+ """
575
+ Process speaker selection and generate Telemetry
576
+ """
577
+
578
+ # Create scope object
579
+ scope = type("GenericScope", (), {})()
580
+
581
+ scope._start_time = start_time
582
+ scope._end_time = time.time()
583
+ scope._span = span
584
+ scope._last_speaker = last_speaker
585
+ scope._selected_speaker = selected_speaker
586
+ scope._server_address, scope._server_port = server_address, server_port
587
+
588
+ # Add required model attributes for common_agent_logic
589
+ scope._request_model = request_model
590
+ scope._response_model = request_model
591
+
592
+ # Set agent name for speaker selection
593
+ scope._agent_name = "speaker_selection"
594
+
595
+ # Set speaker selection attributes
596
+ span.set_attribute(
597
+ SemanticConvention.GEN_AI_GROUPCHAT_SPEAKER_SELECTION, selected_speaker
598
+ )
599
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_SENDER, last_speaker)
600
+
601
+ if selector:
602
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_ROLE, "selector")
603
+
604
+ # Set agent count
605
+ if agents:
606
+ span.set_attribute(SemanticConvention.GEN_AI_WORKFLOW_AGENT_COUNT, len(agents))
607
+
608
+ # Use agent operation type for speaker selection
609
+ common_agent_logic(
610
+ scope,
611
+ pricing_info,
612
+ environment,
613
+ application_name,
614
+ metrics,
615
+ capture_message_content,
616
+ disable_metrics,
617
+ version,
618
+ SemanticConvention.GEN_AI_OPERATION_TYPE_AGENT,
619
+ )
@@ -8,6 +8,12 @@ from wrapt import wrap_function_wrapper
8
8
  from openlit.instrumentation.pydantic_ai.pydantic_ai import (
9
9
  agent_create,
10
10
  agent_run,
11
+ graph_execution,
12
+ user_prompt_processing,
13
+ model_request_processing,
14
+ tool_calls_processing,
15
+ )
16
+ from openlit.instrumentation.pydantic_ai.async_pydantic_ai import (
11
17
  async_agent_run,
12
18
  )
13
19
 
@@ -77,6 +83,88 @@ class PydanticAIInstrumentor(BaseInstrumentor):
77
83
  ),
78
84
  )
79
85
 
86
+ # Enhanced instrumentation for richer span hierarchy
87
+ # These wrap internal Pydantic AI graph execution components
88
+ try:
89
+ # Agent.iter() - Graph execution iterator
90
+ wrap_function_wrapper(
91
+ "pydantic_ai.agent",
92
+ "Agent.iter",
93
+ graph_execution(
94
+ version,
95
+ environment,
96
+ application_name,
97
+ tracer,
98
+ pricing_info,
99
+ capture_message_content,
100
+ metrics,
101
+ disable_metrics,
102
+ ),
103
+ )
104
+ except Exception:
105
+ # If Agent.iter doesn't exist, skip this instrumentation
106
+ pass
107
+
108
+ try:
109
+ # UserPromptNode.run() - User prompt processing
110
+ wrap_function_wrapper(
111
+ "pydantic_ai._agent_graph",
112
+ "UserPromptNode.run",
113
+ user_prompt_processing(
114
+ version,
115
+ environment,
116
+ application_name,
117
+ tracer,
118
+ pricing_info,
119
+ capture_message_content,
120
+ metrics,
121
+ disable_metrics,
122
+ ),
123
+ )
124
+ except Exception:
125
+ # If UserPromptNode.run doesn't exist, skip this instrumentation
126
+ pass
127
+
128
+ try:
129
+ # ModelRequestNode.run() - Model request processing
130
+ wrap_function_wrapper(
131
+ "pydantic_ai._agent_graph",
132
+ "ModelRequestNode.run",
133
+ model_request_processing(
134
+ version,
135
+ environment,
136
+ application_name,
137
+ tracer,
138
+ pricing_info,
139
+ capture_message_content,
140
+ metrics,
141
+ disable_metrics,
142
+ ),
143
+ )
144
+ except Exception:
145
+ # If ModelRequestNode.run doesn't exist, skip this instrumentation
146
+ pass
147
+
148
+ try:
149
+ # CallToolsNode.run() - Tool calls processing
150
+ wrap_function_wrapper(
151
+ "pydantic_ai._agent_graph",
152
+ "CallToolsNode.run",
153
+ tool_calls_processing(
154
+ version,
155
+ environment,
156
+ application_name,
157
+ tracer,
158
+ pricing_info,
159
+ capture_message_content,
160
+ metrics,
161
+ disable_metrics,
162
+ ),
163
+ )
164
+ except Exception:
165
+ # If CallToolsNode.run doesn't exist, skip this instrumentation
166
+ pass
167
+
80
168
  def _uninstrument(self, **kwargs):
81
169
  # Proper uninstrumentation logic to revert patched methods
82
170
  pass
@@ -0,0 +1,38 @@
1
+ """
2
+ Module for monitoring async Pydantic AI API calls.
3
+ """
4
+
5
+ from openlit.instrumentation.pydantic_ai.utils import (
6
+ common_agent_run_async,
7
+ )
8
+
9
+
10
+ def async_agent_run(
11
+ version,
12
+ environment,
13
+ application_name,
14
+ tracer,
15
+ pricing_info,
16
+ capture_message_content,
17
+ metrics,
18
+ disable_metrics,
19
+ ):
20
+ """
21
+ Generates a telemetry wrapper for async GenAI function call
22
+ """
23
+
24
+ async def wrapper(wrapped, instance, args, kwargs):
25
+ return await common_agent_run_async(
26
+ wrapped,
27
+ instance,
28
+ args,
29
+ kwargs,
30
+ tracer,
31
+ version,
32
+ environment,
33
+ application_name,
34
+ capture_message_content,
35
+ pricing_info=pricing_info,
36
+ )
37
+
38
+ return wrapper
@@ -5,6 +5,10 @@ Module for monitoring Pydantic AI API calls.
5
5
  from openlit.instrumentation.pydantic_ai.utils import (
6
6
  common_agent_run,
7
7
  common_agent_create,
8
+ common_graph_execution,
9
+ common_user_prompt_processing,
10
+ common_model_request_processing,
11
+ common_tool_calls_processing,
8
12
  )
9
13
 
10
14
 
@@ -23,7 +27,6 @@ def agent_create(
23
27
  """
24
28
 
25
29
  def wrapper(wrapped, instance, args, kwargs):
26
- response = wrapped(*args, **kwargs)
27
30
  return common_agent_create(
28
31
  wrapped,
29
32
  instance,
@@ -34,7 +37,6 @@ def agent_create(
34
37
  environment,
35
38
  application_name,
36
39
  capture_message_content,
37
- response=response,
38
40
  )
39
41
 
40
42
  return wrapper
@@ -55,7 +57,6 @@ def agent_run(
55
57
  """
56
58
 
57
59
  def wrapper(wrapped, instance, args, kwargs):
58
- response = wrapped(*args, **kwargs)
59
60
  return common_agent_run(
60
61
  wrapped,
61
62
  instance,
@@ -66,13 +67,13 @@ def agent_run(
66
67
  environment,
67
68
  application_name,
68
69
  capture_message_content,
69
- response=response,
70
+ pricing_info=pricing_info,
70
71
  )
71
72
 
72
73
  return wrapper
73
74
 
74
75
 
75
- def async_agent_run(
76
+ def graph_execution(
76
77
  version,
77
78
  environment,
78
79
  application_name,
@@ -83,12 +84,101 @@ def async_agent_run(
83
84
  disable_metrics,
84
85
  ):
85
86
  """
86
- Generates a telemetry wrapper for GenAI function call
87
+ Generates a telemetry wrapper for Pydantic AI graph execution
87
88
  """
88
89
 
89
- async def wrapper(wrapped, instance, args, kwargs):
90
- response = await wrapped(*args, **kwargs)
91
- return common_agent_run(
90
+ def wrapper(wrapped, instance, args, kwargs):
91
+ return common_graph_execution(
92
+ wrapped,
93
+ instance,
94
+ args,
95
+ kwargs,
96
+ tracer,
97
+ version,
98
+ environment,
99
+ application_name,
100
+ capture_message_content,
101
+ )
102
+
103
+ return wrapper
104
+
105
+
106
+ def user_prompt_processing(
107
+ version,
108
+ environment,
109
+ application_name,
110
+ tracer,
111
+ pricing_info,
112
+ capture_message_content,
113
+ metrics,
114
+ disable_metrics,
115
+ ):
116
+ """
117
+ Generates a telemetry wrapper for Pydantic AI user prompt processing
118
+ """
119
+
120
+ def wrapper(wrapped, instance, args, kwargs):
121
+ return common_user_prompt_processing(
122
+ wrapped,
123
+ instance,
124
+ args,
125
+ kwargs,
126
+ tracer,
127
+ version,
128
+ environment,
129
+ application_name,
130
+ capture_message_content,
131
+ )
132
+
133
+ return wrapper
134
+
135
+
136
+ def model_request_processing(
137
+ version,
138
+ environment,
139
+ application_name,
140
+ tracer,
141
+ pricing_info,
142
+ capture_message_content,
143
+ metrics,
144
+ disable_metrics,
145
+ ):
146
+ """
147
+ Generates a telemetry wrapper for Pydantic AI model request processing
148
+ """
149
+
150
+ def wrapper(wrapped, instance, args, kwargs):
151
+ return common_model_request_processing(
152
+ wrapped,
153
+ instance,
154
+ args,
155
+ kwargs,
156
+ tracer,
157
+ version,
158
+ environment,
159
+ application_name,
160
+ capture_message_content,
161
+ )
162
+
163
+ return wrapper
164
+
165
+
166
+ def tool_calls_processing(
167
+ version,
168
+ environment,
169
+ application_name,
170
+ tracer,
171
+ pricing_info,
172
+ capture_message_content,
173
+ metrics,
174
+ disable_metrics,
175
+ ):
176
+ """
177
+ Generates a telemetry wrapper for Pydantic AI tool calls processing
178
+ """
179
+
180
+ def wrapper(wrapped, instance, args, kwargs):
181
+ return common_tool_calls_processing(
92
182
  wrapped,
93
183
  instance,
94
184
  args,
@@ -98,7 +188,6 @@ def async_agent_run(
98
188
  environment,
99
189
  application_name,
100
190
  capture_message_content,
101
- response=response,
102
191
  )
103
192
 
104
193
  return wrapper