99from typing import Any , AsyncIterator , Iterator
1010
1111from opentelemetry import context as context_api
12- from opentelemetry .trace import Span , SpanKind , Status , StatusCode , set_span_in_context
12+ from opentelemetry .trace import Span , SpanKind , Status , StatusCode , set_span_in_context , get_tracer
1313from opentelemetry .instrumentation .utils import _SUPPRESS_INSTRUMENTATION_KEY
1414
1515from agentops .logging import logger
1616from agentops .instrumentation .common .wrappers import _with_tracer_wrapper
1717from agentops .instrumentation .providers .openai .utils import is_metrics_enabled
18- from agentops .instrumentation .providers .openai .wrappers .chat import handle_chat_attributes
18+ from agentops .instrumentation .providers .openai .wrappers .chat import handle_chat_attributes , _create_tool_span
1919from agentops .semconv import SpanAttributes , LLMRequestTypeValues , MessageAttributes
2020
2121
@@ -30,17 +30,19 @@ class OpenaiStreamWrapper:
3030 - Chunk statistics
3131 """
3232
33- def __init__ (self , stream : Any , span : Span , request_kwargs : dict ):
33+ def __init__ (self , stream : Any , span : Span , request_kwargs : dict , tracer = None ):
3434 """Initialize the stream wrapper.
3535
3636 Args:
3737 stream: The original OpenAI stream object
3838 span: The OpenTelemetry span for tracking
3939 request_kwargs: Original request parameters for context
40+ tracer: The OpenTelemetry tracer for creating child spans
4041 """
4142 self ._stream = stream
4243 self ._span = span
4344 self ._request_kwargs = request_kwargs
45+ self ._tracer = tracer
4446 self ._start_time = time .time ()
4547 self ._first_token_time = None
4648 self ._chunk_count = 0
@@ -192,30 +194,11 @@ def _finalize_stream(self) -> None:
192194 if self ._finish_reason :
193195 self ._span .set_attribute (MessageAttributes .COMPLETION_FINISH_REASON .format (i = 0 ), self ._finish_reason )
194196
195- # Set tool calls
196- if len (self ._tool_calls ) > 0 :
197+ # Create tool spans for each tool call
198+ if len (self ._tool_calls ) > 0 and self . _tracer is not None :
197199 for idx , tool_call in self ._tool_calls .items ():
198- # Only set attributes if values are not None
199- if tool_call ["id" ] is not None :
200- self ._span .set_attribute (
201- MessageAttributes .COMPLETION_TOOL_CALL_ID .format (i = 0 , j = idx ), tool_call ["id" ]
202- )
203-
204- if tool_call ["type" ] is not None :
205- self ._span .set_attribute (
206- MessageAttributes .COMPLETION_TOOL_CALL_TYPE .format (i = 0 , j = idx ), tool_call ["type" ]
207- )
208-
209- if tool_call ["function" ]["name" ] is not None :
210- self ._span .set_attribute (
211- MessageAttributes .COMPLETION_TOOL_CALL_NAME .format (i = 0 , j = idx ), tool_call ["function" ]["name" ]
212- )
213-
214- if tool_call ["function" ]["arguments" ] is not None :
215- self ._span .set_attribute (
216- MessageAttributes .COMPLETION_TOOL_CALL_ARGUMENTS .format (i = 0 , j = idx ),
217- tool_call ["function" ]["arguments" ],
218- )
200+ # Create a child span for this tool call
201+ _create_tool_span (self ._span , tool_call , self ._tracer )
219202
220203 # Set usage if available from the API
221204 if self ._usage is not None :
@@ -254,17 +237,19 @@ def _finalize_stream(self) -> None:
254237class OpenAIAsyncStreamWrapper :
255238 """Async wrapper for OpenAI Chat Completions streaming responses."""
256239
257- def __init__ (self , stream : Any , span : Span , request_kwargs : dict ):
240+ def __init__ (self , stream : Any , span : Span , request_kwargs : dict , tracer = None ):
258241 """Initialize the async stream wrapper.
259242
260243 Args:
261244 stream: The original OpenAI async stream object
262245 span: The OpenTelemetry span for tracking
263246 request_kwargs: Original request parameters for context
247+ tracer: The OpenTelemetry tracer for creating child spans
264248 """
265249 self ._stream = stream
266250 self ._span = span
267251 self ._request_kwargs = request_kwargs
252+ self ._tracer = tracer
268253 self ._start_time = time .time ()
269254 self ._first_token_time = None
270255 self ._chunk_count = 0
@@ -371,10 +356,10 @@ def chat_completion_stream_wrapper(tracer, wrapped, instance, args, kwargs):
371356 if is_streaming :
372357 # Wrap the stream
373358 context_api .detach (token )
374- return OpenaiStreamWrapper (response , span , kwargs )
359+ return OpenaiStreamWrapper (response , span , kwargs , tracer )
375360 else :
376361 # Handle non-streaming response
377- response_attributes = handle_chat_attributes (kwargs = kwargs , return_value = response )
362+ response_attributes = handle_chat_attributes (kwargs = kwargs , return_value = response , span = span , tracer = tracer )
378363
379364 for key , value in response_attributes .items ():
380365 if key not in request_attributes : # Avoid overwriting request attributes
@@ -436,10 +421,10 @@ async def async_chat_completion_stream_wrapper(tracer, wrapped, instance, args,
436421 if is_streaming :
437422 # Wrap the stream
438423 context_api .detach (token )
439- return OpenAIAsyncStreamWrapper (response , span , kwargs )
424+ return OpenAIAsyncStreamWrapper (response , span , kwargs , tracer )
440425 else :
441426 # Handle non-streaming response
442- response_attributes = handle_chat_attributes (kwargs = kwargs , return_value = response )
427+ response_attributes = handle_chat_attributes (kwargs = kwargs , return_value = response , span = span , tracer = tracer )
443428
444429 for key , value in response_attributes .items ():
445430 if key not in request_attributes : # Avoid overwriting request attributes
0 commit comments