Skip to content

Commit 3652176

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
fix: update agent engine utils to support python-a2a sdk 1.0
PiperOrigin-RevId: 913139425
1 parent 4ba222b commit 3652176

1 file changed

Lines changed: 130 additions & 10 deletions

File tree

vertexai/_genai/_agent_engines_utils.py

Lines changed: 130 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@
110110

111111

112112
try:
113+
from a2a.utils.constants import TransportProtocol as _TpTest
114+
_A2A_SDK_VERSION = "v1"
115+
except ImportError:
116+
try:
117+
from a2a.types import TransportProtocol as _TpTest
118+
_A2A_SDK_VERSION = "v0"
119+
except ImportError:
120+
_A2A_SDK_VERSION = None
121+
122+
if _A2A_SDK_VERSION == "v1":
123+
from a2a.types import AgentCard, Message
124+
from a2a.client import ClientConfig, ClientFactory
125+
from a2a.utils.constants import TransportProtocol
126+
from a2a.compat.v0_3.types import TaskIdParams, TaskQueryParams
127+
elif _A2A_SDK_VERSION == "v0":
113128
from a2a.types import (
114129
AgentCard,
115130
TransportProtocol,
@@ -118,15 +133,7 @@
118133
TaskQueryParams,
119134
)
120135
from a2a.client import ClientConfig, ClientFactory
121-
122-
AgentCard = AgentCard
123-
TransportProtocol = TransportProtocol
124-
Message = Message
125-
ClientConfig = ClientConfig
126-
ClientFactory = ClientFactory
127-
TaskIdParams = TaskIdParams
128-
TaskQueryParams = TaskQueryParams
129-
except (ImportError, AttributeError):
136+
else:
130137
AgentCard = None
131138
TransportProtocol = None
132139
Message = None
@@ -1737,7 +1744,7 @@ async def _method(self: genai_types.AgentEngine, **kwargs) -> AsyncIterator[Any]
17371744
return _method
17381745

17391746

1740-
def _wrap_a2a_operation(method_name: str, agent_card: str) -> Callable[..., list[Any]]:
1747+
def _wrap_a2a_operation_v03(method_name: str, agent_card: str) -> Callable[..., list[Any]]:
17411748
"""Wraps an Agent Engine method, creating a callable for A2A API.
17421749
17431750
Args:
@@ -1854,6 +1861,119 @@ async def _method(self, **kwargs) -> Any: # type: ignore[no-untyped-def]
18541861
return _method # type: ignore[return-value]
18551862

18561863

1864+
def _wrap_a2a_operation(method_name: str, agent_card: str) -> Callable[..., list[Any]]:
1865+
"""Wraps an Agent Engine method, creating a callable for A2A API (v1.0.0+).
1866+
1867+
Args:
1868+
method_name: The name of the Agent Engine method to call.
1869+
agent_card: The agent card to use for the A2A API call.
1870+
Example:
1871+
{'additionalInterfaces': None,
1872+
'capabilities': {'extensions': None,
1873+
'pushNotifications': None,
1874+
'stateTransitionHistory': None,
1875+
'streaming': False},
1876+
'defaultInputModes': ['text'],
1877+
'defaultOutputModes': ['text'],
1878+
'description': (
1879+
'A helpful assistant agent that can answer questions.'
1880+
),
1881+
'documentationUrl': None,
1882+
'iconUrl': None,
1883+
'name': 'Q&A Agent',
1884+
'preferredTransport': 'JSONRPC',
1885+
'protocolVersion': '0.3.0',
1886+
'provider': None,
1887+
'security': None,
1888+
'securitySchemes': None,
1889+
'signatures': None,
1890+
'skills': [{
1891+
'description': (
1892+
'A helpful assistant agent that can answer questions.'
1893+
),
1894+
'examples': ['Who is leading 2025 F1 Standings?',
1895+
'Where can i find an active volcano?'],
1896+
'id': 'question_answer',
1897+
'inputModes': None,
1898+
'name': 'Q&A Agent',
1899+
'outputModes': None,
1900+
'security': None,
1901+
'tags': ['Question-Answer']}],
1902+
'supportsAuthenticatedExtendedCard': True,
1903+
'url': 'http://localhost:8080/',
1904+
'version': '1.0.0'}
1905+
Returns:
1906+
A callable object that executes the method on the Agent Engine via
1907+
the A2A API.
1908+
"""
1909+
1910+
async def _method(self, **kwargs) -> Any: # type: ignore[no-untyped-def]
1911+
if not self.api_client:
1912+
raise ValueError("api_client is not initialized.")
1913+
if not self.api_resource:
1914+
raise ValueError("api_resource is not initialized.")
1915+
1916+
a2a_agent_card = AgentCard()
1917+
json_format.ParseDict(json.loads(agent_card), a2a_agent_card, ignore_unknown_fields=True)
1918+
1919+
if a2a_agent_card.supported_interfaces:
1920+
interface = a2a_agent_card.supported_interfaces[0]
1921+
if interface.protocol_binding != TransportProtocol.HTTP_JSON:
1922+
raise ValueError("Only HTTP+JSON is supported for preferred transport on agent card")
1923+
else:
1924+
raise ValueError("Agent card does not define any supported interfaces.")
1925+
1926+
base_url = self.api_client._api_client._http_options.base_url.rstrip("/")
1927+
api_version = self.api_client._api_client._http_options.api_version
1928+
a2a_agent_card.supported_interfaces[0].url = f"{base_url}/{api_version}/{self.api_resource.name}/a2a"
1929+
1930+
config = ClientConfig(
1931+
supported_protocol_bindings=[
1932+
TransportProtocol.HTTP_JSON,
1933+
],
1934+
use_client_preference=True,
1935+
httpx_client=httpx.AsyncClient(
1936+
headers={
1937+
"Authorization": (
1938+
f"Bearer {self.api_client._api_client._credentials.token}"
1939+
)
1940+
},
1941+
timeout=(
1942+
self.api_client._api_client._http_options.timeout / 1000.0
1943+
if self.api_client._api_client._http_options.timeout
1944+
else None
1945+
),
1946+
),
1947+
)
1948+
factory = ClientFactory(config)
1949+
client = factory.create(a2a_agent_card)
1950+
1951+
if method_name == "on_message_send":
1952+
response = client.send_message(Message(**kwargs))
1953+
chunks = []
1954+
async for chunk in response:
1955+
chunks.append(chunk)
1956+
return chunks
1957+
elif method_name == "on_get_task":
1958+
return await client.get_task(TaskQueryParams(**kwargs))
1959+
elif method_name == "on_cancel_task":
1960+
return await client.cancel_task(TaskIdParams(**kwargs))
1961+
elif method_name == "handle_authenticated_agent_card":
1962+
return await client.get_card()
1963+
elif method_name == "on_get_extended_agent_card":
1964+
from a2a.types import GetExtendedAgentCardRequest
1965+
req = GetExtendedAgentCardRequest()
1966+
return await client.get_extended_agent_card(req)
1967+
else:
1968+
raise ValueError(f"Unknown method name: {method_name}")
1969+
1970+
return _method # type: ignore[return-value]
1971+
1972+
1973+
if _A2A_SDK_VERSION != "v1":
1974+
_wrap_a2a_operation = _wrap_a2a_operation_v03
1975+
1976+
18571977
def _yield_parsed_json(http_response: google_genai_types.HttpResponse) -> Iterator[Any]:
18581978
"""Converts the body of the HTTP Response message to JSON format.
18591979

0 commit comments

Comments
 (0)