openlit 1.34.28__py3-none-any.whl → 1.34.30__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.
@@ -22,6 +22,16 @@ from openlit.__helpers import (
22
22
  )
23
23
  from openlit.semcov import SemanticConvention
24
24
 
25
+ def handle_not_given(value, default=None):
26
+ """
27
+ Handle OpenAI's NotGiven values and None values by converting them to appropriate defaults.
28
+ """
29
+ if hasattr(value, '__class__') and value.__class__.__name__ == 'NotGiven':
30
+ return default
31
+ if value is None:
32
+ return default
33
+ return value
34
+
25
35
  def format_content(messages):
26
36
  """
27
37
  Format the messages into a string for span events.
@@ -248,9 +258,15 @@ def common_response_logic(scope, pricing_info, environment, application_name, me
248
258
  environment, application_name, is_stream, scope._tbt, scope._ttft, version)
249
259
 
250
260
  # Span Attributes for Request parameters specific to responses API
251
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, scope._kwargs.get("temperature", 1.0))
252
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, scope._kwargs.get("top_p", 1.0))
253
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, scope._kwargs.get("max_output_tokens", -1))
261
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, handle_not_given(scope._kwargs.get("temperature"), 1.0))
262
+ scope._span.set_attribute(
263
+ SemanticConvention.GEN_AI_REQUEST_TOP_P,
264
+ handle_not_given(scope._kwargs.get("top_p"), 1.0)
265
+ )
266
+ scope._span.set_attribute(
267
+ SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS,
268
+ handle_not_given(scope._kwargs.get("max_output_tokens"), -1)
269
+ )
254
270
 
255
271
  # Reasoning parameters
256
272
  reasoning = scope._kwargs.get("reasoning", {})
@@ -427,20 +443,30 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
427
443
  cost = get_chat_model_cost(request_model, pricing_info, input_tokens, output_tokens)
428
444
 
429
445
  # Common Span Attributes
430
- common_span_attributes(scope,
431
- SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
432
- scope._server_address, scope._server_port, request_model, scope._response_model,
433
- environment, application_name, is_stream, scope._tbt, scope._ttft, version)
446
+ common_span_attributes(
447
+ scope,
448
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
449
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
450
+ scope._server_address, scope._server_port, request_model,
451
+ scope._response_model, environment, application_name,
452
+ is_stream, scope._tbt, scope._ttft, version
453
+ )
434
454
 
435
455
  # Span Attributes for Request parameters
436
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_SEED, str(scope._kwargs.get("seed", "")))
437
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY, scope._kwargs.get("frequency_penalty", 0.0))
438
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, scope._kwargs.get("max_tokens", -1))
439
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY, scope._kwargs.get("presence_penalty", 0.0))
440
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, scope._kwargs.get("stop", []))
441
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, scope._kwargs.get("temperature", 1.0))
442
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, scope._kwargs.get("top_p", 1.0))
443
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, scope._kwargs.get("user", ""))
456
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_SEED, str(handle_not_given(scope._kwargs.get("seed"), "")))
457
+ scope._span.set_attribute(
458
+ SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY,
459
+ handle_not_given(scope._kwargs.get("frequency_penalty"), 0.0)
460
+ )
461
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, handle_not_given(scope._kwargs.get("max_tokens"), -1))
462
+ scope._span.set_attribute(
463
+ SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY,
464
+ handle_not_given(scope._kwargs.get("presence_penalty"), 0.0)
465
+ )
466
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, handle_not_given(scope._kwargs.get("stop"), []))
467
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, handle_not_given(scope._kwargs.get("temperature"), 1.0))
468
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, handle_not_given(scope._kwargs.get("top_p"), 1.0))
469
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
444
470
 
445
471
  # Span Attributes for Response parameters
446
472
  scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, scope._response_id)
@@ -571,8 +597,11 @@ def common_embedding_logic(scope, request_model, pricing_info, environment, appl
571
597
  environment, application_name, False, scope._tbt, scope._ttft, version)
572
598
 
573
599
  # Span Attributes for Request parameters
574
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_ENCODING_FORMATS, [scope._kwargs.get("encoding_format", "float")])
575
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, scope._kwargs.get("user", ""))
600
+ scope._span.set_attribute(
601
+ SemanticConvention.GEN_AI_REQUEST_ENCODING_FORMATS,
602
+ [handle_not_given(scope._kwargs.get("encoding_format"), "float")]
603
+ )
604
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
576
605
 
577
606
  # Span Attributes for Cost and Tokens
578
607
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
@@ -611,9 +640,12 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
611
640
  environment, application_name, False, scope._tbt, scope._ttft, version)
612
641
 
613
642
  # Span Attributes for Request parameters
614
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IMAGE_SIZE, scope._kwargs.get("size", "1024x1024"))
615
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IMAGE_QUALITY, scope._kwargs.get("quality", "standard"))
616
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, scope._kwargs.get("user", ""))
643
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IMAGE_SIZE, handle_not_given(scope._kwargs.get("size"), "1024x1024"))
644
+ scope._span.set_attribute(
645
+ SemanticConvention.GEN_AI_REQUEST_IMAGE_QUALITY,
646
+ handle_not_given(scope._kwargs.get("quality"), "standard")
647
+ )
648
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
617
649
 
618
650
  # Extract response data
619
651
  response_dict = scope._response_dict
@@ -709,9 +741,12 @@ def common_audio_logic(scope, request_model, pricing_info, environment, applicat
709
741
  environment, application_name, False, scope._tbt, scope._ttft, version)
710
742
 
711
743
  # Span Attributes for Request parameters
712
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_VOICE, scope._kwargs.get("voice", "alloy"))
713
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_RESPONSE_FORMAT, scope._kwargs.get("response_format", "mp3"))
714
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_SPEED, scope._kwargs.get("speed", 1.0))
744
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_VOICE, handle_not_given(scope._kwargs.get("voice"), "alloy"))
745
+ scope._span.set_attribute(
746
+ SemanticConvention.GEN_AI_REQUEST_AUDIO_RESPONSE_FORMAT,
747
+ handle_not_given(scope._kwargs.get("response_format"), "mp3")
748
+ )
749
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_SPEED, handle_not_given(scope._kwargs.get("speed"), 1.0))
715
750
 
716
751
  # Span Attributes for Cost
717
752
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
@@ -1,42 +1,62 @@
1
- """Initializer of Auto Instrumentation of OpenAI Agents Functions"""
1
+ """
2
+ OpenLIT OpenAI Agents Instrumentation
3
+ """
2
4
 
3
5
  from typing import Collection
4
6
  import importlib.metadata
5
7
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
6
- from wrapt import wrap_function_wrapper
7
8
 
8
- from openlit.instrumentation.openai_agents.openai_agents import (
9
- create_agent
10
- )
9
+ from openlit.instrumentation.openai_agents.processor import OpenLITTracingProcessor
11
10
 
12
- _instruments = ('openai-agents >= 0.0.3',)
11
+ _instruments = ("openai-agents >= 0.0.3",)
13
12
 
14
13
  class OpenAIAgentsInstrumentor(BaseInstrumentor):
15
- """
16
- An instrumentor for openai-agents's client library.
17
- """
14
+ """OpenLIT instrumentor for OpenAI Agents using native tracing system"""
18
15
 
19
16
  def instrumentation_dependencies(self) -> Collection[str]:
20
17
  return _instruments
21
18
 
22
19
  def _instrument(self, **kwargs):
23
- application_name = kwargs.get('application_name', 'default')
24
- environment = kwargs.get('environment', 'default')
25
- tracer = kwargs.get('tracer')
26
- event_provider = kwargs.get('event_provider')
27
- metrics = kwargs.get('metrics_dict')
28
- pricing_info = kwargs.get('pricing_info', {})
29
- capture_message_content = kwargs.get('capture_message_content', False)
30
- disable_metrics = kwargs.get('disable_metrics')
31
- version = importlib.metadata.version('openai-agents')
32
-
33
- wrap_function_wrapper(
34
- 'agents.agent',
35
- 'Agent.__init__',
36
- create_agent(version, environment, application_name,
37
- tracer, event_provider, pricing_info, capture_message_content, metrics, disable_metrics),
20
+ version = importlib.metadata.version("openai-agents")
21
+ environment = kwargs.get("environment", "default")
22
+ application_name = kwargs.get("application_name", "default")
23
+ tracer = kwargs.get("tracer")
24
+ pricing_info = kwargs.get("pricing_info", {})
25
+ capture_message_content = kwargs.get("capture_message_content", False)
26
+ metrics = kwargs.get("metrics_dict")
27
+ disable_metrics = kwargs.get("disable_metrics")
28
+ detailed_tracing = kwargs.get("detailed_tracing", False)
29
+
30
+ # Create our processor with OpenLIT enhancements
31
+ processor = OpenLITTracingProcessor(
32
+ tracer=tracer,
33
+ version=version,
34
+ environment=environment,
35
+ application_name=application_name,
36
+ pricing_info=pricing_info,
37
+ capture_message_content=capture_message_content,
38
+ metrics=metrics,
39
+ disable_metrics=disable_metrics,
40
+ detailed_tracing=detailed_tracing
38
41
  )
39
42
 
43
+ # Integrate with OpenAI Agents' native tracing system
44
+ try:
45
+ from agents import set_trace_processors
46
+ # Replace existing processors with our enhanced processor
47
+ set_trace_processors([processor])
48
+ except ImportError:
49
+ # Fallback: Add our processor to existing ones
50
+ try:
51
+ from agents import add_trace_processor
52
+ add_trace_processor(processor)
53
+ except ImportError:
54
+ pass # Agents package may not have tracing
55
+
40
56
  def _uninstrument(self, **kwargs):
41
- # Proper uninstrumentation logic to revert patched methods
42
- pass
57
+ # Clear our processors
58
+ try:
59
+ from agents import set_trace_processors
60
+ set_trace_processors([])
61
+ except ImportError:
62
+ pass
@@ -0,0 +1,452 @@
1
+ """
2
+ OpenLIT OpenAI Agents Instrumentation - Native TracingProcessor Implementation
3
+ """
4
+
5
+ import time
6
+ from typing import Any, Dict, TYPE_CHECKING
7
+
8
+ from opentelemetry.trace import SpanKind, Status, StatusCode, set_span_in_context
9
+
10
+ from openlit.__helpers import (
11
+ common_framework_span_attributes,
12
+ handle_exception,
13
+ get_chat_model_cost
14
+ )
15
+ from openlit.semcov import SemanticConvention
16
+
17
+ # Try to import agents framework components with fallback
18
+ try:
19
+ from agents import TracingProcessor
20
+ if TYPE_CHECKING:
21
+ from agents import Trace, Span
22
+ TRACING_AVAILABLE = True
23
+ except ImportError:
24
+ # Create dummy class for when agents is not available
25
+ class TracingProcessor:
26
+ """Dummy TracingProcessor class for when agents is not available"""
27
+
28
+ def force_flush(self):
29
+ """Dummy force_flush method"""
30
+ return None
31
+
32
+ def shutdown(self):
33
+ """Dummy shutdown method"""
34
+ return None
35
+
36
+ if TYPE_CHECKING:
37
+ # Type hints only - these don't exist at runtime when agents unavailable
38
+ Trace = Any
39
+ Span = Any
40
+
41
+ TRACING_AVAILABLE = False
42
+
43
+
44
+ class OpenLITTracingProcessor(TracingProcessor):
45
+ """
46
+ OpenAI Agents tracing processor that integrates with OpenLIT observability.
47
+
48
+ This processor enhances OpenAI Agents' native tracing system with OpenLIT's
49
+ comprehensive observability features including business intelligence,
50
+ cost tracking, and performance metrics.
51
+ """
52
+
53
+ def __init__(self, tracer, version, environment, application_name,
54
+ pricing_info, capture_message_content, metrics,
55
+ disable_metrics, detailed_tracing, **kwargs):
56
+ """Initialize the OpenLIT tracing processor."""
57
+ super().__init__()
58
+
59
+ # Core configuration
60
+ self.tracer = tracer
61
+ self.version = version
62
+ self.environment = environment
63
+ self.application_name = application_name
64
+ self.pricing_info = pricing_info
65
+ self.capture_message_content = capture_message_content
66
+ self.metrics = metrics
67
+ self.disable_metrics = disable_metrics
68
+ self.detailed_tracing = detailed_tracing
69
+
70
+ # Internal tracking
71
+ self.active_spans = {}
72
+ self.span_stack = []
73
+
74
+ def start_trace(self, trace_id: str, name: str, **kwargs):
75
+ """
76
+ Start a new trace with OpenLIT enhancements.
77
+
78
+ Args:
79
+ trace_id: Unique trace identifier
80
+ name: Trace name
81
+ **kwargs: Additional trace metadata
82
+ """
83
+ try:
84
+ # Generate span name using OpenTelemetry conventions
85
+ span_name = self._get_span_name(name, **kwargs)
86
+
87
+ # Start root span with OpenLIT context
88
+ span = self.tracer.start_as_current_span(
89
+ span_name,
90
+ kind=SpanKind.CLIENT,
91
+ attributes={
92
+ SemanticConvention.GEN_AI_SYSTEM: "openai_agents",
93
+ SemanticConvention.GEN_AI_OPERATION:
94
+ SemanticConvention.GEN_AI_OPERATION_TYPE_WORKFLOW,
95
+ "trace.id": trace_id,
96
+ "trace.name": name,
97
+ }
98
+ )
99
+
100
+ # Create scope for common attributes
101
+ scope = type("GenericScope", (), {})()
102
+ scope._span = span # pylint: disable=protected-access
103
+ scope._start_time = time.time() # pylint: disable=protected-access
104
+ scope._end_time = None # pylint: disable=protected-access
105
+
106
+ # Apply common framework attributes
107
+ common_framework_span_attributes(
108
+ scope,
109
+ "openai_agents",
110
+ "api.openai.com",
111
+ 443,
112
+ self.environment,
113
+ self.application_name,
114
+ self.version,
115
+ name
116
+ )
117
+
118
+ # Track active span
119
+ self.active_spans[trace_id] = span
120
+ self.span_stack.append(span)
121
+
122
+ return span
123
+
124
+ except Exception as e: # pylint: disable=broad-exception-caught
125
+ # Graceful degradation
126
+ handle_exception(None, e)
127
+ return None
128
+
129
+ def end_trace(self, trace_id: str, **kwargs):
130
+ """
131
+ End an active trace.
132
+
133
+ Args:
134
+ trace_id: Trace identifier to end
135
+ **kwargs: Additional metadata
136
+ """
137
+ try:
138
+ span = self.active_spans.get(trace_id)
139
+ if span:
140
+ # Set final attributes and status
141
+ span.set_status(Status(StatusCode.OK))
142
+
143
+ # End span
144
+ span.end()
145
+
146
+ # Cleanup tracking
147
+ if trace_id in self.active_spans:
148
+ del self.active_spans[trace_id]
149
+ if span in self.span_stack:
150
+ self.span_stack.remove(span)
151
+
152
+ except Exception as e: # pylint: disable=broad-exception-caught
153
+ handle_exception(span if span else None, e)
154
+
155
+ def _get_span_name(self, operation_name: str, **metadata) -> str:
156
+ """
157
+ Generate OpenTelemetry-compliant span names.
158
+
159
+ Args:
160
+ operation_name: Base operation name
161
+ **metadata: Additional context for naming
162
+
163
+ Returns:
164
+ Formatted span name following semantic conventions
165
+ """
166
+ # Extract context for naming
167
+ agent_name = metadata.get('agent_name', '')
168
+ model_name = metadata.get('model_name', '')
169
+ tool_name = metadata.get('tool_name', '')
170
+ workflow_name = metadata.get('workflow_name', '')
171
+
172
+ # Apply OpenTelemetry semantic conventions for GenAI agents
173
+ if 'agent' in operation_name.lower():
174
+ if agent_name:
175
+ return f"invoke_agent {agent_name}"
176
+ return "invoke_agent"
177
+ if 'chat' in operation_name.lower():
178
+ if model_name:
179
+ return f"chat {model_name}"
180
+ return "chat response"
181
+ if 'tool' in operation_name.lower():
182
+ if tool_name:
183
+ return f"execute_tool {tool_name}"
184
+ return "execute_tool"
185
+ if 'handoff' in operation_name.lower():
186
+ target_agent = metadata.get('target_agent', 'unknown')
187
+ return f"invoke_agent {target_agent}"
188
+ if 'workflow' in operation_name.lower():
189
+ if workflow_name:
190
+ return f"workflow {workflow_name}"
191
+ return "workflow"
192
+
193
+ # Default case
194
+ return operation_name
195
+
196
+ def span_start(self, span_data, trace_id: str):
197
+ """
198
+ Handle span start events from OpenAI Agents.
199
+
200
+ Args:
201
+ span_data: Span data from agents framework
202
+ trace_id: Associated trace identifier
203
+ """
204
+ try:
205
+ # Extract span information
206
+ span_name = getattr(span_data, 'name', 'unknown_operation')
207
+ span_type = getattr(span_data, 'type', 'unknown')
208
+
209
+ # Generate enhanced span name
210
+ enhanced_name = self._get_span_name(
211
+ span_name,
212
+ agent_name=getattr(span_data, 'agent_name', None),
213
+ model_name=getattr(span_data, 'model_name', None),
214
+ tool_name=getattr(span_data, 'tool_name', None)
215
+ )
216
+
217
+ # Determine span operation type
218
+ operation_type = self._get_operation_type(span_type, span_name)
219
+
220
+ # Start span with proper context
221
+ parent_span = self.span_stack[-1] if self.span_stack else None
222
+ context = set_span_in_context(parent_span) if parent_span else None
223
+
224
+ span = self.tracer.start_as_current_span(
225
+ enhanced_name,
226
+ kind=SpanKind.CLIENT,
227
+ context=context,
228
+ attributes={
229
+ SemanticConvention.GEN_AI_SYSTEM: "openai_agents",
230
+ SemanticConvention.GEN_AI_OPERATION: operation_type,
231
+ "span.type": span_type,
232
+ "span.id": getattr(span_data, 'span_id', ''),
233
+ }
234
+ )
235
+
236
+ # Process specific span types
237
+ self._process_span_attributes(span, span_data, span_type)
238
+
239
+ # Track span
240
+ span_id = getattr(span_data, 'span_id', len(self.span_stack))
241
+ self.active_spans[f"{trace_id}:{span_id}"] = span
242
+ self.span_stack.append(span)
243
+
244
+ except Exception as e: # pylint: disable=broad-exception-caught
245
+ handle_exception(None, e)
246
+
247
+ def _get_operation_type(self, span_type: str, span_name: str) -> str:
248
+ """Get operation type based on span characteristics."""
249
+ type_mapping = {
250
+ 'agent': SemanticConvention.GEN_AI_OPERATION_TYPE_AGENT,
251
+ 'generation': SemanticConvention.GEN_AI_OPERATION_CHAT,
252
+ 'function': SemanticConvention.GEN_AI_OPERATION_CHAT,
253
+ 'tool': SemanticConvention.GEN_AI_OPERATION_CHAT,
254
+ 'handoff': SemanticConvention.GEN_AI_OPERATION_TYPE_AGENT,
255
+ }
256
+
257
+ # Check span type first
258
+ for key, operation in type_mapping.items():
259
+ if key in span_type.lower():
260
+ return operation
261
+
262
+ # Check span name
263
+ for key, operation in type_mapping.items():
264
+ if key in span_name.lower():
265
+ return operation
266
+
267
+ return SemanticConvention.GEN_AI_OPERATION_CHAT
268
+
269
+ def _process_span_attributes(self, span, span_data, span_type: str):
270
+ """Process and set span attributes based on span type."""
271
+ try:
272
+ # Common attributes
273
+ if hasattr(span_data, 'agent_name'):
274
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_NAME,
275
+ span_data.agent_name)
276
+
277
+ if hasattr(span_data, 'model_name'):
278
+ span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MODEL,
279
+ span_data.model_name)
280
+
281
+ # Agent-specific attributes
282
+ if span_type == 'agent':
283
+ self._process_agent_span(span, span_data)
284
+
285
+ # Generation-specific attributes
286
+ elif span_type == 'generation':
287
+ self._process_generation_span(span, span_data)
288
+
289
+ # Function/Tool-specific attributes
290
+ elif span_type in ['function', 'tool']:
291
+ self._process_function_span(span, span_data)
292
+
293
+ # Handoff-specific attributes
294
+ elif span_type == 'handoff':
295
+ self._process_handoff_span(span, span_data)
296
+
297
+ except Exception as e: # pylint: disable=broad-exception-caught
298
+ handle_exception(span, e)
299
+
300
+ def _process_agent_span(self, span, agent_span):
301
+ """Process agent span data (unused parameter)."""
302
+ # Agent-specific processing
303
+ if hasattr(agent_span, 'instructions'):
304
+ span.set_attribute(SemanticConvention.GEN_AI_AGENT_DESCRIPTION,
305
+ str(agent_span.instructions)[:500])
306
+
307
+ if hasattr(agent_span, 'model'):
308
+ span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MODEL,
309
+ agent_span.model)
310
+
311
+ def _process_generation_span(self, span, generation_span):
312
+ """Process generation span data."""
313
+ # Set generation-specific attributes
314
+ if hasattr(generation_span, 'prompt'):
315
+ span.set_attribute(SemanticConvention.GEN_AI_PROMPT,
316
+ str(generation_span.prompt)[:1000])
317
+
318
+ if hasattr(generation_span, 'completion'):
319
+ span.set_attribute(SemanticConvention.GEN_AI_COMPLETION,
320
+ str(generation_span.completion)[:1000])
321
+
322
+ if hasattr(generation_span, 'usage'):
323
+ usage = generation_span.usage
324
+ if hasattr(usage, 'prompt_tokens'):
325
+ span.set_attribute(SemanticConvention.GEN_AI_USAGE_PROMPT_TOKENS,
326
+ usage.prompt_tokens)
327
+ if hasattr(usage, 'completion_tokens'):
328
+ span.set_attribute(
329
+ SemanticConvention.GEN_AI_USAGE_COMPLETION_TOKENS,
330
+ usage.completion_tokens
331
+ )
332
+
333
+ def _process_function_span(self, span, function_span):
334
+ """Process function/tool span data."""
335
+ if hasattr(function_span, 'function_name'):
336
+ span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME,
337
+ function_span.function_name)
338
+
339
+ if hasattr(function_span, 'arguments'):
340
+ span.set_attribute("gen_ai.tool.arguments",
341
+ str(function_span.arguments)[:500])
342
+
343
+ if hasattr(function_span, 'result'):
344
+ span.set_attribute("gen_ai.tool.result",
345
+ str(function_span.result)[:500])
346
+
347
+ def _process_handoff_span(self, span, handoff_span):
348
+ """Process handoff span data."""
349
+ if hasattr(handoff_span, 'target_agent'):
350
+ span.set_attribute("gen_ai.handoff.target_agent",
351
+ handoff_span.target_agent)
352
+
353
+ if hasattr(handoff_span, 'reason'):
354
+ span.set_attribute("gen_ai.handoff.reason",
355
+ str(handoff_span.reason)[:200])
356
+
357
+ def span_end(self, span_data, trace_id: str):
358
+ """Handle span end events."""
359
+ try:
360
+ span_id = getattr(span_data, 'span_id', '')
361
+ span_key = f"{trace_id}:{span_id}"
362
+
363
+ span = self.active_spans.get(span_key)
364
+ if span:
365
+ # Set final status
366
+ if hasattr(span_data, 'error') and span_data.error:
367
+ span.set_status(Status(StatusCode.ERROR,
368
+ str(span_data.error)))
369
+ else:
370
+ span.set_status(Status(StatusCode.OK))
371
+
372
+ # End span
373
+ span.end()
374
+
375
+ # Cleanup
376
+ if span_key in self.active_spans:
377
+ del self.active_spans[span_key]
378
+ if span in self.span_stack:
379
+ self.span_stack.remove(span)
380
+
381
+ except Exception as e: # pylint: disable=broad-exception-caught
382
+ handle_exception(span if 'span' in locals() else None, e)
383
+
384
+ def force_flush(self):
385
+ """Force flush all pending spans."""
386
+ try:
387
+ # End any remaining spans
388
+ for span in list(self.active_spans.values()):
389
+ span.end()
390
+
391
+ self.active_spans.clear()
392
+ self.span_stack.clear()
393
+
394
+ except Exception as e: # pylint: disable=broad-exception-caught
395
+ handle_exception(None, e)
396
+
397
+ def shutdown(self):
398
+ """Shutdown the processor."""
399
+ self.force_flush()
400
+
401
+ def _extract_model_info(self, span_data) -> Dict[str, Any]:
402
+ """Extract model information from span data."""
403
+ model_info = {}
404
+
405
+ if hasattr(span_data, 'model'):
406
+ model_info['model'] = span_data.model
407
+ if hasattr(span_data, 'model_name'):
408
+ model_info['model'] = span_data.model_name
409
+
410
+ return model_info
411
+
412
+ def _calculate_cost(self, model: str, prompt_tokens: int,
413
+ completion_tokens: int) -> float:
414
+ """Calculate cost based on token usage."""
415
+ try:
416
+ return get_chat_model_cost(
417
+ model, self.pricing_info, prompt_tokens, completion_tokens
418
+ )
419
+ except Exception: # pylint: disable=broad-exception-caught
420
+ return 0.0
421
+
422
+ # Abstract method implementations required by OpenAI Agents framework
423
+ def on_trace_start(self, trace):
424
+ """Called when a trace starts - required by OpenAI Agents framework"""
425
+ try:
426
+ self.start_trace(getattr(trace, 'trace_id', 'unknown'),
427
+ getattr(trace, 'name', 'workflow'))
428
+ except Exception: # pylint: disable=broad-exception-caught
429
+ pass
430
+
431
+ def on_trace_end(self, trace):
432
+ """Called when a trace ends - required by OpenAI Agents framework"""
433
+ try:
434
+ self.end_trace(getattr(trace, 'trace_id', 'unknown'))
435
+ except Exception: # pylint: disable=broad-exception-caught
436
+ pass
437
+
438
+ def on_span_start(self, span):
439
+ """Called when a span starts - required by OpenAI Agents framework"""
440
+ try:
441
+ trace_id = getattr(span, 'trace_id', 'unknown')
442
+ self.span_start(span, trace_id)
443
+ except Exception: # pylint: disable=broad-exception-caught
444
+ pass
445
+
446
+ def on_span_end(self, span):
447
+ """Called when a span ends - required by OpenAI Agents framework"""
448
+ try:
449
+ trace_id = getattr(span, 'trace_id', 'unknown')
450
+ self.span_end(span, trace_id)
451
+ except Exception: # pylint: disable=broad-exception-caught
452
+ pass