Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/google/adk/a2a/utils/agent_to_a2a.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,32 @@
from ...runners import Runner
from ...sessions.in_memory_session_service import InMemorySessionService
from ..executor.a2a_agent_executor import A2aAgentExecutor
from ..experimental import a2a_experimental
from .agent_card_builder import AgentCardBuilder


@a2a_experimental
def to_a2a(
agent: BaseAgent, *, host: str = "localhost", port: int = 8000
agent: BaseAgent,
*,
host: str = "localhost",
port: int = 8000,
protocol: str = "http",
) -> Starlette:
"""Convert an ADK agent to a A2A Starlette application.

Args:
agent: The ADK agent to convert
host: The host for the A2A RPC URL (default: "localhost")
port: The port for the A2A RPC URL (default: 8000)
protocol: The protocol for the A2A RPC URL (default: "http")

Returns:
A Starlette application that can be run with uvicorn

Example:
agent = MyAgent()
app = to_a2a(agent, host="localhost", port=8000)
app = to_a2a(agent, host="localhost", port=8000, protocol="http")
# Then run with: uvicorn module:app --host localhost --port 8000
"""
# Set up ADK logging to ensure logs are visible when using uvicorn directly
Expand Down Expand Up @@ -87,7 +94,7 @@ async def create_runner() -> Runner:
)

# Build agent card
rpc_url = f"http://{host}:{port}/"
rpc_url = f"{protocol}://{host}:{port}/"
card_builder = AgentCardBuilder(
agent=agent,
rpc_url=rpc_url,
Expand Down
117 changes: 113 additions & 4 deletions tests/unittests/a2a/utils/test_agent_to_a2a.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ def test_to_a2a_default_parameters(
@patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore")
@patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder")
@patch("google.adk.a2a.utils.agent_to_a2a.Starlette")
def test_to_a2a_custom_host_port(
def test_to_a2a_custom_host_port_protocol(
self,
mock_starlette_class,
mock_card_builder_class,
mock_task_store_class,
mock_request_handler_class,
mock_agent_executor_class,
):
"""Test to_a2a with custom host and port."""
"""Test to_a2a with custom host, port, and protocol."""
# Arrange
mock_app = Mock(spec=Starlette)
mock_starlette_class.return_value = mock_app
Expand All @@ -145,12 +145,14 @@ def test_to_a2a_custom_host_port(
mock_card_builder_class.return_value = mock_card_builder

# Act
result = to_a2a(self.mock_agent, host="example.com", port=9000)
result = to_a2a(
self.mock_agent, host="example.com", port=9000, protocol="https"
)

# Assert
assert result == mock_app
mock_card_builder_class.assert_called_once_with(
agent=self.mock_agent, rpc_url="http://example.com:9000/"
agent=self.mock_agent, rpc_url="https://example.com:9000/"
)

@patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor")
Expand Down Expand Up @@ -704,3 +706,110 @@ def test_to_a2a_with_ip_address_host(
mock_card_builder_class.assert_called_once_with(
agent=self.mock_agent, rpc_url="http://192.168.1.1:8000/"
)

@patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor")
@patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler")
@patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore")
@patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder")
@patch("google.adk.a2a.utils.agent_to_a2a.Starlette")
def test_to_a2a_with_https_protocol(
self,
mock_starlette_class,
mock_card_builder_class,
mock_task_store_class,
mock_request_handler_class,
mock_agent_executor_class,
):
"""Test to_a2a with HTTPS protocol."""
# Arrange
mock_app = Mock(spec=Starlette)
mock_starlette_class.return_value = mock_app
mock_task_store = Mock(spec=InMemoryTaskStore)
mock_task_store_class.return_value = mock_task_store
mock_agent_executor = Mock(spec=A2aAgentExecutor)
mock_agent_executor_class.return_value = mock_agent_executor
mock_request_handler = Mock(spec=DefaultRequestHandler)
mock_request_handler_class.return_value = mock_request_handler
mock_card_builder = Mock(spec=AgentCardBuilder)
mock_card_builder_class.return_value = mock_card_builder

# Act
result = to_a2a(self.mock_agent, protocol="https")

# Assert
assert result == mock_app
mock_card_builder_class.assert_called_once_with(
agent=self.mock_agent, rpc_url="https://localhost:8000/"
)

@patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor")
@patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler")
@patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore")
@patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder")
@patch("google.adk.a2a.utils.agent_to_a2a.Starlette")
def test_to_a2a_with_custom_protocol(
self,
mock_starlette_class,
mock_card_builder_class,
mock_task_store_class,
mock_request_handler_class,
mock_agent_executor_class,
):
"""Test to_a2a with custom protocol."""
# Arrange
mock_app = Mock(spec=Starlette)
mock_starlette_class.return_value = mock_app
mock_task_store = Mock(spec=InMemoryTaskStore)
mock_task_store_class.return_value = mock_task_store
mock_agent_executor = Mock(spec=A2aAgentExecutor)
mock_agent_executor_class.return_value = mock_agent_executor
mock_request_handler = Mock(spec=DefaultRequestHandler)
mock_request_handler_class.return_value = mock_request_handler
mock_card_builder = Mock(spec=AgentCardBuilder)
mock_card_builder_class.return_value = mock_card_builder

# Act
result = to_a2a(self.mock_agent, protocol="ws")

# Assert
assert result == mock_app
mock_card_builder_class.assert_called_once_with(
agent=self.mock_agent, rpc_url="ws://localhost:8000/"
)

@patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor")
@patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler")
@patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore")
@patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder")
@patch("google.adk.a2a.utils.agent_to_a2a.Starlette")
def test_to_a2a_with_all_custom_parameters(
self,
mock_starlette_class,
mock_card_builder_class,
mock_task_store_class,
mock_request_handler_class,
mock_agent_executor_class,
):
"""Test to_a2a with all custom parameters."""
# Arrange
mock_app = Mock(spec=Starlette)
mock_starlette_class.return_value = mock_app
mock_task_store = Mock(spec=InMemoryTaskStore)
mock_task_store_class.return_value = mock_task_store
mock_agent_executor = Mock(spec=A2aAgentExecutor)
mock_agent_executor_class.return_value = mock_agent_executor
mock_request_handler = Mock(spec=DefaultRequestHandler)
mock_request_handler_class.return_value = mock_request_handler
mock_card_builder = Mock(spec=AgentCardBuilder)
mock_card_builder_class.return_value = mock_card_builder

# Act
result = to_a2a(
self.mock_agent, host="api.example.com", port=443, protocol="https"
)

# Assert
assert result == mock_app
mock_card_builder_class.assert_called_once_with(
agent=self.mock_agent, rpc_url="https://api.example.com:443/"
)