Skip to content

Commit c99b324

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
This CL introduces the convenience method to_a2a enabling easier deployment of agents as a2a agents.
feat: to_a2a on Agent Engine PiperOrigin-RevId: 832480210
1 parent 2195411 commit c99b324

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

vertexai/preview/reasoning_engines/templates/a2a.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,188 @@ async def cancel(
151151
)
152152

153153

154+
def to_a2a(
155+
agent_engine_app: object,
156+
agent_card: "AgentCard",
157+
user_id: str = None,
158+
task_store_builder: Callable[..., "TaskStore"] = None,
159+
task_store_kwargs: Optional[Mapping[str, Any]] = None,
160+
agent_executor_kwargs: Optional[Mapping[str, Any]] = None,
161+
agent_executor_builder: Optional[Callable[..., "AgentExecutor"]] = None,
162+
request_handler_kwargs: Optional[Mapping[str, Any]] = None,
163+
request_handler_builder: Optional[Callable[..., "RequestHandler"]] = None,
164+
extended_agent_card: "AgentCard" = None,
165+
):
166+
"""Converts an existing Agent Engine application to be compatible with A2A.
167+
168+
This function wraps an `agent_engine_app` with A2A functionalities, allowing it
169+
to handle A2A protocol requests. It augments the app's setup and operation
170+
registration to include A2A-specific handlers.
171+
172+
Args:
173+
agent_engine_app (object): The Agent Engine application instance.
174+
agent_card (AgentCard): The AgentCard describing the agent.
175+
user_id (str): The user ID.
176+
task_store_builder (Callable[..., TaskStore], optional): A callable to build the
177+
TaskStore.
178+
task_store_kwargs (Optional[Mapping[str, Any]], optional):
179+
Keyword arguments for the TaskStore builder.
180+
agent_executor_kwargs (Optional[Mapping[str, Any]], optional):
181+
Keyword arguments for the AgentExecutor builder.
182+
agent_executor_builder (Optional[Callable[..., AgentExecutor]], optional):
183+
A callable to build the AgentExecutor.
184+
If not provided, a default `AdkAgentExecutor` will be used.
185+
request_handler_kwargs (Optional[Mapping[str, Any]], optional):
186+
Keyword arguments for the RequestHandler builder.
187+
request_handler_builder (Optional[Callable[..., RequestHandler]], optional):
188+
A callable to build the RequestHandler.
189+
extended_agent_card (AgentCard, optional): An extended AgentCard.
190+
191+
Returns:
192+
object: The augmented Agent Engine application instance.
193+
"""
194+
from a2a.server.agent_execution import AgentExecutor, RequestContext
195+
from a2a.server.events import EventQueue
196+
from a2a.server.tasks import TaskUpdater
197+
from google.genai import types
198+
from a2a.types import (
199+
TextPart,
200+
FilePart,
201+
FileWithBytes,
202+
FileWithUri,
203+
Part,
204+
)
205+
206+
class DefaultAgentExecutor(AgentExecutor):
207+
"""Agent Executor for adapting an AE application to the A2A protocol."""
208+
209+
def __init__(self, adk_app_instance):
210+
self.adk_app = adk_app_instance
211+
212+
def convert_genai_parts_to_a2a(
213+
self, parts: list[types.Part]
214+
) -> list[Part]:
215+
"""Convert a list of Google Gen AI Part types into a list of A2A Part types."""
216+
return [
217+
self.convert_genai_part_to_a2a(part)
218+
for part in parts
219+
if (part.text or part.file_data or part.inline_data)
220+
]
221+
222+
def convert_genai_part_to_a2a(self, part: types.Part) -> Part:
223+
"""Convert a single Google Gen AI Part type into an A2A Part type."""
224+
if part.text:
225+
return TextPart(text=part.text)
226+
if part.file_data:
227+
return FilePart(
228+
file=FileWithUri(
229+
uri=part.file_data.file_uri,
230+
mime_type=part.file_data.mime_type,
231+
)
232+
)
233+
if part.inline_data:
234+
return Part(
235+
root=FilePart(
236+
file=FileWithBytes(
237+
bytes=part.inline_data.data,
238+
mime_type=part.inline_data.mime_type,
239+
)
240+
)
241+
)
242+
raise ValueError(f"Unsupported part type: {part}")
243+
244+
async def execute(
245+
self, context: RequestContext, event_queue: EventQueue
246+
) -> None:
247+
from google.adk.events.event import Event
248+
249+
query = context.get_user_input()
250+
251+
updater = TaskUpdater(
252+
event_queue, context.task_id, context.context_id
253+
)
254+
255+
if not context.current_task:
256+
await updater.submit()
257+
258+
await updater.start_work()
259+
260+
parts = []
261+
for stream_event in self.adk_app.stream_query(
262+
user_id=user_id, message=query
263+
):
264+
event = Event(**stream_event)
265+
parts.extend(
266+
self.convert_genai_parts_to_a2a(event.content.parts)
267+
)
268+
269+
await updater.add_artifact(
270+
parts,
271+
name="result",
272+
)
273+
await updater.complete()
274+
275+
async def cancel(
276+
self, context: RequestContext, event_queue: EventQueue
277+
) -> None:
278+
raise Exception("Cancel not supported for this ADK agent")
279+
280+
if agent_executor_builder:
281+
agent_executor_kwargs["adk_app_instance"] = agent_engine_app
282+
a2a_mixin = A2aAgent(
283+
agent_card=agent_card,
284+
task_store_builder=task_store_builder,
285+
task_store_kwargs=task_store_kwargs,
286+
agent_executor_builder=agent_executor_builder,
287+
agent_executor_kwargs=agent_executor_kwargs,
288+
request_handler_builder=request_handler_builder,
289+
request_handler_kwargs=request_handler_kwargs,
290+
extended_agent_card=extended_agent_card,
291+
)
292+
else:
293+
a2a_mixin = A2aAgent(
294+
agent_card=agent_card,
295+
agent_executor_builder=DefaultAgentExecutor,
296+
agent_executor_kwargs={"adk_app_instance": agent_engine_app},
297+
)
298+
299+
def augmented_register_operations():
300+
routes = agent_engine_app.register_operations()
301+
a2a_routes = a2a_mixin.register_operations()
302+
for group, ops in a2a_routes.items():
303+
if group not in routes:
304+
routes[group] = []
305+
for op in ops:
306+
if op not in routes[group]:
307+
routes[group].append(op)
308+
return routes
309+
310+
original_setup = agent_engine_app.set_up
311+
312+
def augmented_setup():
313+
original_setup()
314+
a2a_mixin.set_up()
315+
316+
agent_engine_app.set_up = augmented_setup
317+
318+
agent_engine_app.register_operations = augmented_register_operations
319+
agent_engine_app.a2a_mixin = a2a_mixin
320+
321+
_original_getattr = agent_engine_app.__class__.__getattribute__
322+
323+
def __getattr__(instance, name):
324+
try:
325+
return _original_getattr(instance, name)
326+
except AttributeError:
327+
if hasattr(instance.a2a_mixin, name):
328+
return getattr(instance.a2a_mixin, name)
329+
raise
330+
331+
agent_engine_app.__class__.__getattr__ = __getattr__
332+
333+
return agent_engine_app
334+
335+
154336
class A2aAgent:
155337
"""A class to initialize and set up an Agent-to-Agent application."""
156338

vertexai/preview/reasoning_engines/templates/adk.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,3 +1534,12 @@ def _warn_if_telemetry_api_disabled(self):
15341534
r = session.post("https://telemetry.googleapis.com/v1/traces", data=None)
15351535
if "Telemetry API has not been used in project" in r.text:
15361536
_warn(_TELEMETRY_API_DISABLED_WARNING % (project, project))
1537+
1538+
def to_a2a(self, agent_card: "AgentCard"):
1539+
"""Converts an existing ADK application to be compatible with A2A."""
1540+
1541+
from vertexai.preview.reasoning_engines.templates import a2a
1542+
1543+
return a2a.to_a2a(
1544+
agent_engine_app=self, agent_card=agent_card, user_id="123"
1545+
)

vertexai/preview/reasoning_engines/templates/langgraph.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,3 +658,12 @@ def register_operations(self) -> Mapping[str, Sequence[str]]:
658658
"": ["query", "get_state", "update_state"],
659659
"stream": ["stream_query", "get_state_history"],
660660
}
661+
662+
def to_a2a(self, agent_card: "AgentCard"):
663+
"""Converts an existing Langraph application to be compatible with A2A."""
664+
665+
from vertexai.preview.reasoning_engines.templates import a2a
666+
667+
return a2a.to_a2a(
668+
agent_engine_app=self, agent_card=agent_card
669+
)

0 commit comments

Comments
 (0)