-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
[Fix] A2a Agent Gateway Fixes - A2A agents deployed with localhost/internal URLs in their agent cards (e.g., http://0.0.0.0:8001/) #20604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
8b9ba7b
v1 card resolver fix
ishaan-jaff 5c1c8df
fix: is_localhost_or_internal_url
ishaan-jaff d90b27b
fix code
ishaan-jaff b9afa06
test_fix_agent_card_url_replaces_localhost
ishaan-jaff 4890402
test restruct
ishaan-jaff 5c7f463
test_a2a_non_streaming
ishaan-jaff 3621e51
test agnts
ishaan-jaff cea9baa
add exception handling
ishaan-jaff 43e574c
init errors
ishaan-jaff 1437a5f
add localhost retry
ishaan-jaff 20726d8
add agent_testing
ishaan-jaff da62119
test_a2a_non_streaming
ishaan-jaff 0505812
_build_streaming_logging_obj
ishaan-jaff 01a6adc
code qa fixes
ishaan-jaff c1a2379
test_card_resolver_fallback_from_new_to_old_path
ishaan-jaff b197b04
fix linting
ishaan-jaff File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| """ | ||
| A2A Protocol Exception Mapping Utils. | ||
|
|
||
| Maps A2A SDK exceptions to LiteLLM A2A exception types. | ||
| """ | ||
|
|
||
| from typing import TYPE_CHECKING, Any, Optional | ||
|
|
||
| from litellm._logging import verbose_logger | ||
| from litellm.a2a_protocol.card_resolver import ( | ||
| fix_agent_card_url, | ||
| is_localhost_or_internal_url, | ||
| ) | ||
| from litellm.a2a_protocol.exceptions import ( | ||
| A2AAgentCardError, | ||
| A2AConnectionError, | ||
| A2AError, | ||
| A2ALocalhostURLError, | ||
| ) | ||
| from litellm.constants import CONNECTION_ERROR_PATTERNS | ||
|
|
||
| if TYPE_CHECKING: | ||
| from a2a.client import A2AClient as A2AClientType | ||
|
|
||
|
|
||
| # Runtime import | ||
| _A2AClient: Any = None | ||
| try: | ||
| from a2a.client import A2AClient as _A2AClient | ||
| except ImportError: | ||
| pass | ||
|
|
||
|
|
||
| class A2AExceptionCheckers: | ||
| """ | ||
| Helper class for checking various A2A error conditions. | ||
| """ | ||
|
|
||
| @staticmethod | ||
| def is_connection_error(error_str: str) -> bool: | ||
| """ | ||
| Check if an error string indicates a connection error. | ||
|
|
||
| Args: | ||
| error_str: The error string to check | ||
|
|
||
| Returns: | ||
| True if the error indicates a connection issue | ||
| """ | ||
| if not isinstance(error_str, str): | ||
| return False | ||
|
|
||
| error_str_lower = error_str.lower() | ||
| return any(pattern in error_str_lower for pattern in CONNECTION_ERROR_PATTERNS) | ||
|
|
||
| @staticmethod | ||
| def is_localhost_url(url: Optional[str]) -> bool: | ||
| """ | ||
| Check if a URL is a localhost/internal URL. | ||
|
|
||
| Args: | ||
| url: The URL to check | ||
|
|
||
| Returns: | ||
| True if the URL is localhost/internal | ||
| """ | ||
| return is_localhost_or_internal_url(url) | ||
|
|
||
| @staticmethod | ||
| def is_agent_card_error(error_str: str) -> bool: | ||
| """ | ||
| Check if an error string indicates an agent card error. | ||
|
|
||
| Args: | ||
| error_str: The error string to check | ||
|
|
||
| Returns: | ||
| True if the error is related to agent card fetching/parsing | ||
| """ | ||
| if not isinstance(error_str, str): | ||
| return False | ||
|
|
||
| error_str_lower = error_str.lower() | ||
| agent_card_patterns = [ | ||
| "agent card", | ||
| "agent-card", | ||
| ".well-known", | ||
| "card not found", | ||
| "invalid agent", | ||
| ] | ||
| return any(pattern in error_str_lower for pattern in agent_card_patterns) | ||
|
|
||
|
|
||
| def map_a2a_exception( | ||
| original_exception: Exception, | ||
| card_url: Optional[str] = None, | ||
| api_base: Optional[str] = None, | ||
| model: Optional[str] = None, | ||
| ) -> Exception: | ||
| """ | ||
| Map an A2A SDK exception to a LiteLLM A2A exception type. | ||
|
|
||
| Args: | ||
| original_exception: The original exception from the A2A SDK | ||
| card_url: The URL from the agent card (if available) | ||
| api_base: The original API base URL | ||
| model: The model/agent name | ||
|
|
||
| Returns: | ||
| A mapped LiteLLM A2A exception | ||
|
|
||
| Raises: | ||
| A2ALocalhostURLError: If the error is a connection error to a localhost URL | ||
| A2AConnectionError: If the error is a general connection error | ||
| A2AAgentCardError: If the error is related to agent card issues | ||
| A2AError: For other A2A-related errors | ||
| """ | ||
| error_str = str(original_exception) | ||
|
|
||
| # Check for localhost URL connection error (special case - retryable) | ||
| if ( | ||
| card_url | ||
| and api_base | ||
| and A2AExceptionCheckers.is_localhost_url(card_url) | ||
| and A2AExceptionCheckers.is_connection_error(error_str) | ||
| ): | ||
| raise A2ALocalhostURLError( | ||
| localhost_url=card_url, | ||
| base_url=api_base, | ||
| original_error=original_exception, | ||
| model=model, | ||
| ) | ||
|
|
||
| # Check for agent card errors | ||
| if A2AExceptionCheckers.is_agent_card_error(error_str): | ||
| raise A2AAgentCardError( | ||
| message=error_str, | ||
| url=api_base, | ||
| model=model, | ||
| ) | ||
|
|
||
| # Check for general connection errors | ||
| if A2AExceptionCheckers.is_connection_error(error_str): | ||
| raise A2AConnectionError( | ||
| message=error_str, | ||
| url=card_url or api_base, | ||
| model=model, | ||
| ) | ||
|
|
||
| # Default: wrap in generic A2AError | ||
| raise A2AError( | ||
| message=error_str, | ||
| model=model, | ||
| ) | ||
|
|
||
|
|
||
| def handle_a2a_localhost_retry( | ||
| error: A2ALocalhostURLError, | ||
| agent_card: Any, | ||
| a2a_client: "A2AClientType", | ||
| is_streaming: bool = False, | ||
| ) -> "A2AClientType": | ||
| """ | ||
| Handle A2ALocalhostURLError by fixing the URL and creating a new client. | ||
|
|
||
| This is called when we catch an A2ALocalhostURLError and want to retry | ||
| with the corrected URL. | ||
|
|
||
| Args: | ||
| error: The localhost URL error | ||
| agent_card: The agent card object to fix | ||
| a2a_client: The current A2A client | ||
| is_streaming: Whether this is a streaming request (for logging) | ||
|
|
||
| Returns: | ||
| A new A2A client with the fixed URL | ||
| """ | ||
| request_type = "streaming " if is_streaming else "" | ||
| verbose_logger.warning( | ||
| f"A2A {request_type}request to '{error.localhost_url}' failed: {error.original_error}. " | ||
| f"Agent card contains localhost/internal URL. " | ||
| f"Retrying with base_url '{error.base_url}'." | ||
| ) | ||
|
|
||
| # Fix the agent card URL | ||
| fix_agent_card_url(agent_card, error.base_url) | ||
|
|
||
| # Create a new client with the fixed agent card (transport caches URL) | ||
| return _A2AClient( | ||
| httpx_client=a2a_client._transport.httpx_client, # type: ignore[union-attr] | ||
| agent_card=agent_card, | ||
| ) | ||
|
ishaan-jaff marked this conversation as resolved.
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.