Python: wrap Anthropic and Gemini SDK exceptions in ChatClientException - #7855
Conversation
_inner_get_response() in the OpenAI, Mistral, Ollama, and Bedrock chat clients all translate raw provider SDK exceptions into the framework's ChatClientException hierarchy (ChatClientInvalidAuthException for auth failures, ChatClientInvalidRequestException for bad requests, ChatClientException otherwise). Mistral's test suite explicitly asserts this behavior (test_get_response_http_error_wrapped, test_get_response_network_error_wrapped). The Anthropic and Gemini clients call their SDKs directly with no try/except at all, so a raw anthropic.APIError or google.genai.errors.APIError (and subclasses) propagates unwrapped. Code that catches ChatClientException to handle chat-client failures in a provider-agnostic way - the documented purpose of that base class - silently fails to catch failures from these two providers. Wraps both the streaming and non-streaming call sites in both clients, using each SDK's real exception hierarchy (verified against the installed anthropic and google-genai packages, not assumed) to classify auth vs. bad-request vs. other errors the same way Mistral already does. Adds regression tests mirroring Mistral's existing coverage: one parametrized non-streaming test per provider covering all three exception classes, plus a streaming-path test per provider. Full anthropic and gemini unit test suites pass locally (162 and 154 tests respectively, no regressions).
|
karthikchundi-commits please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
There was a problem hiding this comment.
Pull request overview
Normalizes Anthropic and Gemini SDK failures into the framework’s chat-client exception hierarchy.
Changes:
- Wraps streaming and non-streaming provider errors.
- Adds regression tests for error classification.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
python/packages/gemini/agent_framework_gemini/_chat_client.py |
Adds Gemini exception translation. |
python/packages/gemini/tests/test_gemini_client.py |
Tests Gemini error wrapping. |
python/packages/anthropic/agent_framework_anthropic/_chat_client.py |
Adds Anthropic exception translation. |
python/packages/anthropic/tests/test_anthropic_client.py |
Tests Anthropic error wrapping. |
Suppressed comments (2)
python/packages/gemini/agent_framework_gemini/_chat_client.py:597
- A 403 from Gemini/Vertex represents an authentication/authorization failure, not an invalid request. Include it with 401 here so the non-streaming path emits
ChatClientInvalidAuthExceptionconsistently with the framework's existing provider mappings.
if ex.code == 401:
python/packages/anthropic/agent_framework_anthropic/_chat_client.py:594
- The non-streaming path maps Anthropic's 403
PermissionDeniedErrorto the generic base exception. Classify 403 asChatClientInvalidAuthExceptionso callers can handle credential and permission failures consistently across providers and response modes.
except AnthropicAPIError as ex:
raise ChatClientException(f"Anthropic chat request failed: {ex}", inner_exception=ex) from ex
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| except GenAIAPIError as ex: | ||
| raise ChatClientException(f"Gemini chat request failed: {ex}", inner_exception=ex) from ex |
| except GenAIAPIError as ex: | ||
| raise ChatClientException(f"Gemini chat request failed: {ex}", inner_exception=ex) from ex |
| ): | ||
| yield self._process_chunk(chunk) | ||
| except GenAIClientError as ex: | ||
| if ex.code == 401: |
| except AnthropicAPIError as ex: | ||
| raise ChatClientException(f"Anthropic chat request failed: {ex}", inner_exception=ex) from ex |
| mock.aio.models.generate_content_stream = AsyncMock( | ||
| side_effect=genai_errors.ClientError(401, {"error": {"message": "invalid api key"}}) | ||
| ) |
| mock_anthropic_client.beta.messages.create.side_effect = _anthropic_status_error( | ||
| anthropic_sdk.AuthenticationError, 401, "invalid api key" | ||
| ) |
|
@microsoft-github-policy-service agree |
Summary
_inner_get_response()in the OpenAI, Mistral, Ollama, and Bedrock chat clients all translate raw provider SDK exceptions into the framework'sChatClientExceptionhierarchy (ChatClientInvalidAuthExceptionfor auth failures,ChatClientInvalidRequestExceptionfor bad requests,ChatClientExceptionotherwise). Mistral's test suite explicitly asserts this behavior (test_get_response_http_error_wrapped,test_get_response_network_error_wrapped).The Anthropic and Gemini clients call their SDKs directly with no
try/exceptat all, so a rawanthropic.APIErrororgoogle.genai.errors.APIError(and subclasses) propagates unwrapped instead. Code written against the framework's provider-agnostic abstraction - catchingChatClientExceptionto handle "any chat client failure," which is the documented purpose of that base class - silently fails to catch failures from these two providers specifically.Fix
Wraps both the streaming and non-streaming call sites in both
RawAnthropicClient._inner_get_responseandRawGeminiChatClient._inner_get_response, classifying auth vs. bad-request vs. other errors the same way Mistral's existing implementation already does:anthropic.AuthenticationError→ChatClientInvalidAuthException,anthropic.BadRequestError→ChatClientInvalidRequestException, any otheranthropic.APIError→ChatClientException.google.genai.errors.ClientErrorwithcode == 401→ChatClientInvalidAuthException, otherClientError→ChatClientInvalidRequestException, any othergoogle.genai.errors.APIError(includingServerError) →ChatClientException.I verified both SDKs' actual exception hierarchies against the installed
anthropic(0.103.1) andgoogle-genaipackages rather than assuming from memory, sinceanthropic.InternalServerErrorin particular doesn't carry a class-levelstatus_codethe wayAuthenticationError/BadRequestErrordo.Testing
Added regression tests mirroring Mistral's existing coverage pattern:
test_anthropic_client.py:test_inner_get_response_wraps_sdk_errors(parametrized overAuthenticationError/BadRequestError/InternalServerError) +test_inner_get_response_streaming_wraps_sdk_errors.test_gemini_client.py:test_get_response_wraps_sdk_errors(parametrized over 401/400ClientError+ServerError) +test_get_response_streaming_wraps_sdk_errors.Ran locally (editable install of
agent-framework-core,-anthropic, and-gemini,pytest, Python 3.13 via conda):packages/anthropic: full suite passes, 162 tests, no regressions.packages/gemini: full suite passes, 154 tests, no regressions.ruff format+ruff check(pinnedruff==0.16.3) clean on all four changed files.I don't have a way to hit the live Anthropic or Gemini APIs in this environment, so verification is at the unit level (real SDK exception classes, mocked HTTP layer) rather than an end-to-end run against production endpoints - happy to have this validated further in CI/review.
Scope note
Per
CONTRIBUTING.md's guidance to discuss non-trivial changes first: this felt like a "small, obvious fix" (matching an existing, already-tested pattern used by 4 of 6 sibling providers, no new API surface, no behavioral change to successful responses) rather than something needing a design discussion, so I went straight to a PR. Happy to file an issue first if that's preferred for something touching this many provider packages.