Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/strands/telemetry/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,12 @@ def end_tool_call_span(self, span: Span, tool_result: ToolResult | None, error:
},
)

# If no explicit error but tool_result indicates an error, synthesize one so the span gets StatusCode.ERROR
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of fixing it here, shouldn't we fix it downstream so that the exception is propagated up the chain? Otherwise, we are doing exception -> text in the tool, and then text -> exception here.

if error is None and tool_result is not None and tool_result.get("status") == "error":
content = tool_result.get("content", [])
error_message = content[0].get("text", "Tool call failed") if content else "Tool call failed"
error = Exception(error_message)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Missing test coverage for this new error-handling logic.

Suggestion: Add unit tests to verify:

  1. end_tool_call_span sets StatusCode.ERROR when tool_result["status"] == "error"
  2. The error message is correctly extracted from content[0]["text"]
  3. The default message "Tool call failed" is used when content is empty

Example test structure:

def test_end_tool_call_span_with_error_status(mock_span):
    """Test that tool spans get StatusCode.ERROR when tool result status is error."""
    tracer = Tracer()
    tool_result = {
        "status": "error",
        "content": [{"text": "Error: ValueError - something failed"}],
        "toolUseId": "test-id"
    }
    
    tracer.end_tool_call_span(mock_span, tool_result)
    
    mock_span.set_status.assert_called_once_with(
        StatusCode.ERROR, 
        "Error: ValueError - something failed"
    )
    mock_span.record_exception.assert_called_once()


def test_end_tool_call_span_with_error_status_empty_content(mock_span):
    """Test default error message when content is empty."""
    tracer = Tracer()
    tool_result = {"status": "error", "content": [], "toolUseId": "test-id"}
    
    tracer.end_tool_call_span(mock_span, tool_result)
    
    mock_span.set_status.assert_called_once_with(StatusCode.ERROR, "Tool call failed")


self._end_span(span, attributes, error)

def start_event_loop_cycle_span(
Expand Down