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