@@ -510,101 +510,84 @@ def capture_structured_output_hook(event: AfterToolInvocationEvent) -> None:
510
510
if tool_input :
511
511
captured_result = output_model (** tool_input )
512
512
513
- # Add the callback temporarily (use add_callback, not add_hook)
514
513
self .hooks .add_callback (AfterToolInvocationEvent , capture_structured_output_hook )
515
514
added_callback = capture_structured_output_hook
516
515
517
- try :
518
- with self .tracer .tracer .start_as_current_span (
519
- "execute_structured_output" , kind = trace_api .SpanKind .CLIENT
520
- ) as structured_output_span :
521
- try :
522
- if not self .messages and not prompt :
523
- raise ValueError ("No conversation history or prompt provided" )
524
-
525
- # Create temporary messages array if prompt is provided
526
- message : Message
527
- if prompt :
528
- content : list [ContentBlock ] = [{"text" : prompt }] if isinstance (prompt , str ) else prompt
529
- message = {"role" : "user" , "content" : content }
530
- else :
531
- # Use existing conversation history
532
- message = {
533
- "role" : "user" ,
534
- "content" : [
535
- {
536
- "text" : "Please provide the information from our conversation in the requested "
537
- "structured format."
538
- }
539
- ],
540
- }
541
-
542
- structured_output_span .set_attributes (
543
- {
544
- "gen_ai.system" : "strands-agents" ,
545
- "gen_ai.agent.name" : self .name ,
546
- "gen_ai.agent.id" : self .agent_id ,
547
- "gen_ai.operation.name" : "execute_structured_output" ,
548
- }
549
- )
550
-
551
- # Add tracing for messages
552
- messages_to_trace = self .messages if not prompt else self .messages + [message ]
553
- for msg in messages_to_trace :
554
- structured_output_span .add_event (
555
- f"gen_ai.{ msg ['role' ]} .message" ,
556
- attributes = {"role" : msg ["role" ], "content" : serialize (msg ["content" ])},
557
- )
516
+ # Create message for tracing
517
+ message : Message
518
+ if prompt :
519
+ content : list [ContentBlock ] = [{"text" : prompt }] if isinstance (prompt , str ) else prompt
520
+ message = {"role" : "user" , "content" : content }
521
+ else :
522
+ # Use existing conversation history
523
+ message = {
524
+ "role" : "user" ,
525
+ "content" : [
526
+ {"text" : "Please provide the information from our conversation in the requested structured format." }
527
+ ],
528
+ }
558
529
559
- if self .system_prompt :
560
- structured_output_span .add_event (
561
- "gen_ai.system.message" ,
562
- attributes = {"role" : "system" , "content" : serialize ([{"text" : self .system_prompt }])},
563
- )
530
+ # Start agent trace span (same as stream_async)
531
+ self .trace_span = self ._start_agent_trace_span (message )
564
532
565
- invocation_state = {
566
- "structured_output_mode" : True ,
567
- "structured_output_model" : output_model ,
568
- }
533
+ try :
534
+ with trace_api . use_span ( self . trace_span ):
535
+ if not self . messages and not prompt :
536
+ raise ValueError ( "No conversation history or prompt provided" )
569
537
570
- # Run the event loop
571
- async for event in self . _run_loop ( message = message , invocation_state = invocation_state ):
572
- if "stop" in event :
573
- break
538
+ invocation_state = {
539
+ "structured_output_mode" : True ,
540
+ "structured_output_model" : output_model ,
541
+ }
574
542
575
- # Return the captured structured result if we got it from the tool
576
- if captured_result :
577
- structured_output_span .add_event (
578
- "gen_ai.choice" , attributes = {"message" : serialize (captured_result .model_dump ())}
543
+ # Run the event loop
544
+ async for event in self ._run_loop (message = message , invocation_state = invocation_state ):
545
+ if "stop" in event :
546
+ break
547
+
548
+ # Return the captured structured result if we got it from the tool
549
+ if captured_result :
550
+ self ._end_agent_trace_span (
551
+ response = AgentResult (
552
+ message = {"role" : "assistant" , "content" : [{"text" : str (captured_result )}]},
553
+ stop_reason = "end_turn" ,
554
+ metrics = self .event_loop_metrics ,
555
+ state = {},
579
556
)
580
- return captured_result
581
-
582
- # Fallback: Use the original model.structured_output approach
583
- # This maintains backward compatibility with existing tests and implementations
584
- # Use original_messages to get clean message state, or self.messages if preserve_conversation=True
585
- base_messages = original_messages if original_messages is not None else self .messages
586
- temp_messages = base_messages if not prompt else base_messages + [message ]
557
+ )
558
+ return captured_result
587
559
588
- events = self .model .structured_output (output_model , temp_messages , system_prompt = self .system_prompt )
589
- async for event in events :
590
- if "callback" in event :
591
- self .callback_handler (** cast (dict , event ["callback" ]))
560
+ # Fallback: Use the original model.structured_output approach
561
+ # This maintains backward compatibility with existing tests and implementations
562
+ # Use original_messages to get clean message state, or self.messages if preserve_conversation=True
563
+ base_messages = original_messages if original_messages is not None else self .messages
564
+ temp_messages = base_messages if not prompt else base_messages + [message ]
592
565
593
- structured_output_span .add_event (
594
- "gen_ai.choice" , attributes = {"message" : serialize (event ["output" ].model_dump ())}
566
+ events = self .model .structured_output (output_model , temp_messages , system_prompt = self .system_prompt )
567
+ async for event in events :
568
+ if "callback" in event :
569
+ self .callback_handler (** cast (dict , event ["callback" ]))
570
+
571
+ self ._end_agent_trace_span (
572
+ response = AgentResult (
573
+ message = {"role" : "assistant" , "content" : [{"text" : str (event ["output" ])}]},
574
+ stop_reason = "end_turn" ,
575
+ metrics = self .event_loop_metrics ,
576
+ state = {},
595
577
)
596
- return cast (T , event ["output" ])
597
-
598
- except Exception as e :
599
- structured_output_span .record_exception (e )
600
- raise
578
+ )
579
+ return cast (T , event ["output" ])
601
580
581
+ except Exception as e :
582
+ self ._end_agent_trace_span (error = e )
583
+ raise
602
584
finally :
603
585
# Clean up what we added - remove the callback
604
- if added_callback is not None and AfterToolInvocationEvent in self .hooks ._registered_callbacks :
605
- callbacks = self .hooks ._registered_callbacks [AfterToolInvocationEvent ]
606
- if added_callback in callbacks :
607
- callbacks .remove (added_callback )
586
+ if added_callback is not None :
587
+ with suppress (ValueError , KeyError ):
588
+ callbacks = self .hooks ._registered_callbacks .get (AfterToolInvocationEvent , [])
589
+ if added_callback in callbacks :
590
+ callbacks .remove (added_callback )
608
591
609
592
# Remove the tool we added
610
593
if added_tool_name :
0 commit comments