Python: fix(claude): handle API errors in run_stream() method (#3653)

* fix(claude): handle API errors in run_stream() method

- Import AssistantMessage and TextBlock from claude_agent_sdk
- Check AssistantMessage.error and raise ServiceException with descriptive message
- Check ResultMessage.is_error and raise ServiceException with error details
- Add tests for error handling in run_stream()

Fixes #3652

* fix: add defensive check for message.content before iterating

Address PR review feedback - add null check for message.content to prevent
potential AttributeError if content is None.

* chore: refresh uv.lock

* chore: fix import sorting

* chore: refresh uv.lock
This commit is contained in:
Dineshsuriya D
2026-02-05 11:10:32 +05:30
committed by GitHub
Unverified
parent 0daa7700c6
commit 9e51e2f0bc
3 changed files with 100 additions and 18 deletions
@@ -23,15 +23,16 @@ from agent_framework import (
from agent_framework._types import normalize_tools
from agent_framework.exceptions import ServiceException, ServiceInitializationError
from claude_agent_sdk import (
ClaudeAgentOptions as SDKOptions,
)
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ResultMessage,
SdkMcpTool,
create_sdk_mcp_server,
)
from claude_agent_sdk.types import StreamEvent
from claude_agent_sdk import (
ClaudeAgentOptions as SDKOptions,
)
from claude_agent_sdk.types import StreamEvent, TextBlock
from pydantic import ValidationError
from ._settings import ClaudeAgentSettings
@@ -639,7 +640,33 @@ class ClaudeAgent(BaseAgent, Generic[TOptions]):
contents=[Content.from_text_reasoning(text=thinking, raw_representation=message)],
raw_representation=message,
)
elif isinstance(message, AssistantMessage):
# Handle AssistantMessage - check for API errors
# Note: In streaming mode, the content was already yielded via StreamEvent,
# so we only check for errors here, not re-emit content.
if message.error:
# Map error types to descriptive messages
error_messages = {
"authentication_failed": "Authentication failed with Claude API",
"billing_error": "Billing error with Claude API",
"rate_limit": "Rate limit exceeded for Claude API",
"invalid_request": "Invalid request to Claude API",
"server_error": "Claude API server error",
"unknown": "Unknown error from Claude API",
}
error_msg = error_messages.get(message.error, f"Claude API error: {message.error}")
# Extract any error details from content blocks
if message.content:
for block in message.content:
if isinstance(block, TextBlock):
error_msg = f"{error_msg}: {block.text}"
break
raise ServiceException(error_msg)
elif isinstance(message, ResultMessage):
# Check for errors in result message
if message.is_error:
error_msg = message.result or "Unknown error from Claude API"
raise ServiceException(f"Claude API error: {error_msg}")
session_id = message.session_id
# Update thread with session ID
@@ -379,6 +379,61 @@ class TestClaudeAgentRunStream:
assert updates[0].text == "Streaming "
assert updates[1].text == "response"
async def test_run_stream_raises_on_assistant_message_error(self) -> None:
"""Test run_stream raises ServiceException when AssistantMessage has an error."""
from agent_framework.exceptions import ServiceException
from claude_agent_sdk import AssistantMessage, ResultMessage, TextBlock
messages = [
AssistantMessage(
content=[TextBlock(text="Error details from API")],
model="claude-sonnet",
error="invalid_request",
),
ResultMessage(
subtype="success",
duration_ms=100,
duration_api_ms=50,
is_error=False,
num_turns=1,
session_id="error-session",
),
]
mock_client = self._create_mock_client(messages)
with patch("agent_framework_claude._agent.ClaudeSDKClient", return_value=mock_client):
agent = ClaudeAgent()
with pytest.raises(ServiceException) as exc_info:
async for _ in agent.run_stream("Hello"):
pass
assert "Invalid request to Claude API" in str(exc_info.value)
assert "Error details from API" in str(exc_info.value)
async def test_run_stream_raises_on_result_message_error(self) -> None:
"""Test run_stream raises ServiceException when ResultMessage.is_error is True."""
from agent_framework.exceptions import ServiceException
from claude_agent_sdk import ResultMessage
messages = [
ResultMessage(
subtype="error",
duration_ms=100,
duration_api_ms=50,
is_error=True,
num_turns=0,
session_id="error-session",
result="Model 'claude-sonnet-4.5' not found",
),
]
mock_client = self._create_mock_client(messages)
with patch("agent_framework_claude._agent.ClaudeSDKClient", return_value=mock_client):
agent = ClaudeAgent()
with pytest.raises(ServiceException) as exc_info:
async for _ in agent.run_stream("Hello"):
pass
assert "Model 'claude-sonnet-4.5' not found" in str(exc_info.value)
# region Test ClaudeAgent Session Management