2424from agentops .instrumentation .openai .attributes .response import get_response_response_attributes
2525from agentops .instrumentation .openai_agents import LIBRARY_NAME , LIBRARY_VERSION
2626
27- from agentops .instrumentation .openai_agents .context import (
28- full_prompt_contextvar ,
29- agent_name_contextvar ,
30- agent_handoffs_contextvar ,
31- )
3227from agentops .instrumentation .openai_agents .attributes .model import (
3328 get_model_attributes ,
3429 get_model_config_attributes ,
4641}
4742
4843
44+ # Attribute mapping for FunctionSpanData
4945FUNCTION_TOOL_ATTRIBUTES : AttributeMap = {
5046 ToolAttributes .TOOL_NAME : "name" ,
5147 ToolAttributes .TOOL_PARAMETERS : "input" ,
5248 ToolAttributes .TOOL_RESULT : "output" ,
49+ # AgentAttributes.AGENT_NAME: "name",
50+ AgentAttributes .FROM_AGENT : "from_agent" ,
5351}
5452
5553
6866
6967# Attribute mapping for ResponseSpanData
7068RESPONSE_SPAN_ATTRIBUTES : AttributeMap = {
71- WorkflowAttributes .WORKFLOW_INPUT : "input" ,
69+ # Don't map input here as it causes double serialization
70+ # We handle prompts manually in get_response_span_attributes
71+ SpanAttributes .LLM_RESPONSE_MODEL : "model" ,
7272}
7373
7474
@@ -193,17 +193,12 @@ def get_agent_span_attributes(span_data: Any) -> AttributeMap:
193193
194194 attributes [SpanAttributes .AGENTOPS_SPAN_KIND ] = AgentOpsSpanKindValues .AGENT .value
195195
196- # Get agent name from contextvar (set by instrumentor wrapper)
197- ctx_agent_name = agent_name_contextvar .get ()
198- if ctx_agent_name :
199- attributes [AgentAttributes .AGENT_NAME ] = ctx_agent_name
200- elif hasattr (span_data , "name" ) and span_data .name :
196+ # Get agent name directly from span_data
197+ if hasattr (span_data , "name" ) and span_data .name :
201198 attributes [AgentAttributes .AGENT_NAME ] = str (span_data .name )
202199
203- ctx_handoffs = agent_handoffs_contextvar .get ()
204- if ctx_handoffs :
205- attributes [AgentAttributes .HANDOFFS ] = safe_serialize (ctx_handoffs )
206- elif hasattr (span_data , "handoffs" ) and span_data .handoffs :
200+ # Get handoffs directly from span_data
201+ if hasattr (span_data , "handoffs" ) and span_data .handoffs :
207202 attributes [AgentAttributes .HANDOFFS ] = safe_serialize (span_data .handoffs )
208203
209204 if hasattr (span_data , "tools" ) and span_data .tools :
@@ -278,37 +273,111 @@ def get_response_span_attributes(span_data: Any) -> AttributeMap:
278273 Returns:
279274 Dictionary of attributes for response span
280275 """
276+ # Debug logging
277+ import json
278+ print (f"\n [DEBUG] get_response_span_attributes called" )
279+ print (f"[DEBUG] span_data type: { type (span_data )} " )
280+ print (f"[DEBUG] span_data attributes: { [attr for attr in dir (span_data ) if not attr .startswith ('_' )]} " )
281+
282+ # Check what's in span_data.input
283+ if hasattr (span_data , "input" ):
284+ print (f"[DEBUG] span_data.input type: { type (span_data .input )} " )
285+ try :
286+ print (f"[DEBUG] span_data.input content: { json .dumps (span_data .input , indent = 2 ) if span_data .input else 'None' } " )
287+ except :
288+ print (f"[DEBUG] span_data.input content (repr): { repr (span_data .input )} " )
289+
290+ # Check for response and instructions
291+ if hasattr (span_data , "response" ) and span_data .response :
292+ print (f"[DEBUG] span_data.response type: { type (span_data .response )} " )
293+ if hasattr (span_data .response , "instructions" ):
294+ print (f"[DEBUG] span_data.response.instructions: { span_data .response .instructions } " )
295+
281296 # Get basic attributes from mapping
282297 attributes = _extract_attributes_from_mapping (span_data , RESPONSE_SPAN_ATTRIBUTES )
283298 attributes .update (get_common_attributes ())
284299
285- prompt_attributes_set = False
286-
287- # Read full prompt from contextvar (set by instrumentor's wrapper)
288- full_prompt_from_context = full_prompt_contextvar .get ()
289- if full_prompt_from_context :
290- attributes .update (_get_llm_messages_attributes (full_prompt_from_context , "gen_ai.prompt" ))
291- prompt_attributes_set = True
292- else :
293- if (
294- span_data .response
295- and hasattr (span_data .response , "request_messages" )
296- and span_data .response .request_messages
297- ):
298- prompt_messages_from_sdk = span_data .response .request_messages
299- attributes .update (_get_llm_messages_attributes (prompt_messages_from_sdk , "gen_ai.prompt" ))
300- prompt_attributes_set = True
300+ # Build complete prompt list from system instructions and conversation history
301+ prompt_messages = []
302+
303+ # Add system instruction as first message if available
304+ if span_data .response and hasattr (span_data .response , "instructions" ) and span_data .response .instructions :
305+ prompt_messages .append ({
306+ "role" : "system" ,
307+ "content" : span_data .response .instructions
308+ })
309+ print (f"[DEBUG] Added system message from instructions" )
310+
311+ # Add conversation history from span_data.input
312+ if hasattr (span_data , "input" ) and span_data .input :
313+ if isinstance (span_data .input , list ):
314+ for i , msg in enumerate (span_data .input ):
315+ print (f"[DEBUG] Processing message { i } : type={ type (msg )} " )
316+ if isinstance (msg , dict ):
317+ role = msg .get ("role" )
318+ content = msg .get ("content" )
319+ print (f"[DEBUG] Message { i } : role={ role } , content type={ type (content )} " )
320+
321+ # Handle different content formats
322+ if role and content is not None :
323+ # If content is a string, use it directly
324+ if isinstance (content , str ):
325+ prompt_messages .append ({
326+ "role" : role ,
327+ "content" : content
328+ })
329+ # If content is a list (complex assistant message), extract text
330+ elif isinstance (content , list ):
331+ text_parts = []
332+ for item in content :
333+ if isinstance (item , dict ):
334+ # Handle output_text type
335+ if item .get ("type" ) == "output_text" :
336+ text_parts .append (item .get ("text" , "" ))
337+ # Handle other text content
338+ elif "text" in item :
339+ text_parts .append (item .get ("text" , "" ))
340+ # Handle annotations with text
341+ elif "annotations" in item and "text" in item :
342+ text_parts .append (item .get ("text" , "" ))
343+
344+ if text_parts :
345+ prompt_messages .append ({
346+ "role" : role ,
347+ "content" : " " .join (text_parts )
348+ })
349+ # If content is a dict, try to extract text
350+ elif isinstance (content , dict ):
351+ if "text" in content :
352+ prompt_messages .append ({
353+ "role" : role ,
354+ "content" : content ["text" ]
355+ })
356+ elif isinstance (span_data .input , str ):
357+ # Single string input - assume it's a user message
358+ prompt_messages .append ({
359+ "role" : "user" ,
360+ "content" : span_data .input
361+ })
362+ print (f"[DEBUG] Added user message from string input" )
363+
364+ print (f"[DEBUG] Total prompt_messages: { len (prompt_messages )} " )
365+ for i , msg in enumerate (prompt_messages ):
366+ print (f"[DEBUG] prompt_messages[{ i } ]: role={ msg .get ('role' )} , content_len={ len (str (msg .get ('content' , '' )))} " )
367+
368+ # Format prompts using existing function
369+ if prompt_messages :
370+ attributes .update (_get_llm_messages_attributes (prompt_messages , "gen_ai.prompt" ))
301371
302372 # Process response attributes
303373 if span_data .response :
304374 openai_style_response_attrs = get_response_response_attributes (span_data .response )
305375
306- # Remove prompt attributes if already set from context
307- if prompt_attributes_set :
308- keys_to_remove = [k for k in openai_style_response_attrs if k .startswith ("gen_ai.prompt" )]
309- for key in keys_to_remove :
310- if key in openai_style_response_attrs :
311- del openai_style_response_attrs [key ]
376+ # Remove any prompt attributes from response processing since we handle them above
377+ keys_to_remove = [k for k in openai_style_response_attrs if k .startswith ("gen_ai.prompt" )]
378+ for key in keys_to_remove :
379+ if key in openai_style_response_attrs :
380+ del openai_style_response_attrs [key ]
312381
313382 # Remove tool definitions from response attributes
314383 if "gen_ai.request.tools" in openai_style_response_attrs :
@@ -317,6 +386,11 @@ def get_response_span_attributes(span_data: Any) -> AttributeMap:
317386 attributes .update (openai_style_response_attrs )
318387
319388 attributes [SpanAttributes .AGENTOPS_SPAN_KIND ] = AgentOpsSpanKindValues .LLM .value
389+
390+ print (f"[DEBUG] Final attributes keys: { list (attributes .keys ())} " )
391+ prompt_keys = [k for k in attributes .keys () if k .startswith ("gen_ai.prompt" )]
392+ print (f"[DEBUG] Prompt attribute keys: { prompt_keys } " )
393+
320394 return attributes
321395
322396
@@ -336,20 +410,8 @@ def get_generation_span_attributes(span_data: Any) -> AttributeMap:
336410 ) # This might set gen_ai.prompt from span_data.input
337411 attributes .update (get_common_attributes ())
338412
339- # Read full prompt from contextvar (set by instrumentor's wrapper)
340- full_prompt_from_context = full_prompt_contextvar .get ()
341-
342- if full_prompt_from_context :
343- # Clear any prompt set by _extract_attributes_from_mapping from span_data.input
344- prompt_keys_to_clear = [k for k in attributes if k .startswith ("gen_ai.prompt" )]
345- if SpanAttributes .LLM_PROMPTS in attributes :
346- prompt_keys_to_clear .append (SpanAttributes .LLM_PROMPTS )
347- for key in set (prompt_keys_to_clear ):
348- if key in attributes :
349- del attributes [key ]
350-
351- attributes .update (_get_llm_messages_attributes (full_prompt_from_context , "gen_ai.prompt" ))
352- elif SpanAttributes .LLM_PROMPTS in attributes : # Fallback to span_data.input if contextvar is empty
413+ # Process prompt from span_data.input
414+ if SpanAttributes .LLM_PROMPTS in attributes :
353415 raw_prompt_input = attributes .pop (SpanAttributes .LLM_PROMPTS )
354416 formatted_prompt_for_llm = []
355417 if isinstance (raw_prompt_input , str ):
0 commit comments