@@ -239,19 +239,111 @@ def on_span_end(self, span):
239239 input_data = getattr (span_data , 'input' , [])
240240 if input_data :
241241 for i , message in enumerate (input_data ):
242- if hasattr (message , 'role' ) and hasattr (message , 'content' ):
243- otel_span .set_attribute (f"{ SpanAttributes .LLM_PROMPTS } .{ i } .role" , message .role )
244- content = message .content
242+ prefix = f"{ SpanAttributes .LLM_PROMPTS } .{ i } "
243+
244+ # Convert message to dict for unified handling
245+ if isinstance (message , dict ):
246+ msg = message
247+ else :
248+ # Convert object to dict
249+ msg = {}
250+ for attr in [
251+ "role" ,
252+ "content" ,
253+ "tool_call_id" ,
254+ "tool_calls" ,
255+ "type" ,
256+ "name" ,
257+ "arguments" ,
258+ "call_id" ,
259+ "output" ,
260+ ]:
261+ if hasattr (message , attr ):
262+ msg [attr ] = getattr (message , attr )
263+
264+ # Determine message format and extract data
265+ role = None
266+ content = None
267+ tool_call_id = None
268+ tool_calls = None
269+
270+ if 'role' in msg :
271+ # Standard OpenAI chat format
272+ role = msg ['role' ]
273+ content = msg .get ('content' )
274+ tool_call_id = msg .get ('tool_call_id' )
275+ tool_calls = msg .get ('tool_calls' )
276+ elif 'type' in msg :
277+ # OpenAI Agents SDK format
278+ msg_type = msg ['type' ]
279+ if msg_type == 'function_call' :
280+ # Tool calls are assistant messages
281+ role = 'assistant'
282+ # Create tool_calls structure matching OpenAI SDK format
283+ tool_calls = [{
284+ 'id' : msg .get ('id' , '' ),
285+ 'name' : msg .get ('name' , '' ),
286+ 'arguments' : msg .get ('arguments' , '' )
287+ }]
288+ elif msg_type == 'function_call_output' :
289+ # Tool outputs are tool messages
290+ role = 'tool'
291+ content = msg .get ('output' )
292+ tool_call_id = msg .get ('call_id' )
293+
294+ # Set role attribute
295+ if role :
296+ otel_span .set_attribute (f"{ prefix } .role" , role )
297+
298+ # Set content attribute
299+ if content is not None :
245300 if not isinstance (content , str ):
246301 content = json .dumps (content )
247- otel_span .set_attribute (f"{ SpanAttributes .LLM_PROMPTS } .{ i } .content" , content )
248- elif isinstance (message , dict ):
249- if 'role' in message and 'content' in message :
250- otel_span .set_attribute (f"{ SpanAttributes .LLM_PROMPTS } .{ i } .role" , message ['role' ])
251- content = message ['content' ]
252- if isinstance (content , dict ):
253- content = json .dumps (content )
254- otel_span .set_attribute (f"{ SpanAttributes .LLM_PROMPTS } .{ i } .content" , content )
302+ otel_span .set_attribute (f"{ prefix } .content" , content )
303+
304+ # Set tool_call_id for tool result messages
305+ if tool_call_id :
306+ otel_span .set_attribute (f"{ prefix } .tool_call_id" , tool_call_id )
307+
308+ # Set tool_calls for assistant messages with tool calls
309+ if tool_calls :
310+ for j , tool_call in enumerate (tool_calls ):
311+ # Convert to dict if needed
312+ if not isinstance (tool_call , dict ):
313+ tc_dict = {}
314+ if hasattr (tool_call , 'id' ):
315+ tc_dict ['id' ] = tool_call .id
316+ if hasattr (tool_call , 'function' ):
317+ func = tool_call .function
318+ if hasattr (func , 'name' ):
319+ tc_dict ['name' ] = func .name
320+ if hasattr (func , 'arguments' ):
321+ tc_dict ['arguments' ] = func .arguments
322+ elif hasattr (tool_call , 'name' ):
323+ tc_dict ['name' ] = tool_call .name
324+ if hasattr (tool_call , 'arguments' ):
325+ tc_dict ['arguments' ] = tool_call .arguments
326+ tool_call = tc_dict
327+
328+ # Extract function details if nested (standard OpenAI format)
329+ if 'function' in tool_call :
330+ function = tool_call ['function' ]
331+ tool_call = {
332+ 'id' : tool_call .get ('id' ),
333+ 'name' : function .get ('name' ),
334+ 'arguments' : function .get ('arguments' )
335+ }
336+
337+ # Set tool call attributes
338+ if tool_call .get ('id' ):
339+ otel_span .set_attribute (f"{ prefix } .tool_calls.{ j } .id" , tool_call ['id' ])
340+ if tool_call .get ('name' ):
341+ otel_span .set_attribute (f"{ prefix } .tool_calls.{ j } .name" , tool_call ['name' ])
342+ if tool_call .get ('arguments' ):
343+ args = tool_call ['arguments' ]
344+ if not isinstance (args , str ):
345+ args = json .dumps (args )
346+ otel_span .set_attribute (f"{ prefix } .tool_calls.{ j } .arguments" , args )
255347
256348 # Add function/tool specifications to the request using OpenAI semantic conventions
257349 response = getattr (span_data , 'response' , None )
0 commit comments