Skip to content

Commit 13b912b

Browse files
committed
feat: more generic url
1 parent 9291daa commit 13b912b

File tree

6 files changed

+71
-25
lines changed

6 files changed

+71
-25
lines changed

contributing/samples/a2a_auth/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,16 @@ When deploying the remote BigQuery A2A agent to different environments (e.g., Cl
185185
}
186186
```
187187

188-
**Important:** The `url` field in `remote_a2a/bigquery_agent/agent.json` must point to the actual RPC endpoint where your remote BigQuery A2A agent is deployed and accessible.
188+
**Important:** The `url` field in `remote_a2a/bigquery_agent/agent.json` must point to the actual RPC endpoint where your remote BigQuery A2A agent is deployed and accessible.
189+
If the `url` field is an empty string, it will be automatically filled by the `--host` and `--port`, or `--base_url` provided to `adk api_server`.
189190

190191
## Troubleshooting
191192

192193
**Connection Issues:**
193194
- Ensure the local ADK web server is running on port 8000
194195
- Ensure the remote A2A server is running on port 8001
195196
- Check that no firewall is blocking localhost connections
196-
- **Verify the `url` field in `remote_a2a/bigquery_agent/agent.json` matches the actual deployed location of your remote A2A server**
197+
- **Verify the `url` field in `remote_a2a/bigquery_agent/agent.json` matches the actual deployed location of your remote A2A server, or if it's empty, make sure the `--host` and `--port`, or `--base_url` provided to `adk api_server` match the actual deployed location of your remote A2A server**
197198
- Verify the agent card URL passed to RemoteA2AAgent constructor matches the running A2A server
198199

199200

contributing/samples/a2a_basic/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,15 @@ When deploying the remote A2A agent to different environments (e.g., Cloud Run,
136136
```
137137

138138
**Important:** The `url` field in `remote_a2a/check_prime_agent/agent.json` must point to the actual RPC endpoint where your remote A2A agent is deployed and accessible.
139+
If the `url` field is an empty string, it will be automatically filled by the `--host` and `--port`, or `--base_url` provided to `adk api_server`.
139140

140141
## Troubleshooting
141142

142143
**Connection Issues:**
143144
- Ensure the local ADK web server is running on port 8000
144145
- Ensure the remote A2A server is running on port 8001
145146
- Check that no firewall is blocking localhost connections
146-
- **Verify the `url` field in `remote_a2a/check_prime_agent/agent.json` matches the actual deployed location of your remote A2A server**
147+
- **Verify the `url` field in `remote_a2a/check_prime_agent/agent.json` matches the actual deployed location of your remote A2A server, or if it's empty, make sure the `--host` and `--port`, or `--base_url` provided to `adk api_server` match the actual deployed location of your remote A2A server**
147148
- Verify the agent card URL passed to RemoteA2AAgent constructor matches the running A2A server
148149

149150

contributing/samples/a2a_human_in_loop/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,15 @@ When deploying the remote approval A2A agent to different environments (e.g., Cl
145145
```
146146

147147
**Important:** The `url` field in `remote_a2a/human_in_loop/agent.json` must point to the actual RPC endpoint where your remote approval A2A agent is deployed and accessible.
148+
If the `url` field is an empty string, it will be automatically filled by the `--host` and `--port`, or `--base_url` provided to `adk api_server`.
148149

149150
## Troubleshooting
150151

151152
**Connection Issues:**
152153
- Ensure the local ADK web server is running on port 8000
153154
- Ensure the remote A2A server is running on port 8001
154155
- Check that no firewall is blocking localhost connections
155-
- **Verify the `url` field in `remote_a2a/human_in_loop/agent.json` matches the actual deployed location of your remote A2A server**
156+
- **Verify the `url` field in `remote_a2a/human_in_loop/agent.json` matches the actual deployed location of your remote A2A server, or if it's empty, make sure the `--host` and `--port`, or `--base_url` provided to `adk api_server` match the actual deployed location of your remote A2A server**
156157
- Verify the agent card URL passed to RemoteA2AAgent constructor matches the running A2A server
157158

158159
**Agent Not Responding:**

src/google/adk/cli/cli_tools_click.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import os
2424
import tempfile
2525
from typing import Optional
26+
from urllib.parse import urlparse
2627

2728
import click
2829
from click.core import ParameterSource
@@ -628,6 +629,16 @@ def decorator(func):
628629
help="Optional. The port of the server",
629630
default=8000,
630631
)
632+
@click.option(
633+
"--base_url",
634+
type=str,
635+
help=(
636+
"Optional. The base URL of the server. "
637+
"Mutually exclusive with --host and --port. "
638+
"Overrides the values of --host and --port if specified."
639+
),
640+
show_default=True,
641+
)
631642
@click.option(
632643
"--allow_origins",
633644
help="Optional. Any additional origins to allow for CORS.",
@@ -721,6 +732,7 @@ def cli_web(
721732
allow_origins: Optional[list[str]] = None,
722733
host: str = "127.0.0.1",
723734
port: int = 8000,
735+
base_url: Optional[str] = None,
724736
trace_to_cloud: bool = False,
725737
reload: bool = True,
726738
session_service_uri: Optional[str] = None,
@@ -741,6 +753,16 @@ def cli_web(
741753
adk web --session_service_uri=[uri] --port=[port] path/to/agents_dir
742754
"""
743755
logs.setup_adk_logger(getattr(logging, log_level.upper()))
756+
if base_url is None:
757+
base_url = f"http://{host}:{port}"
758+
else:
759+
parsed_url = urlparse(base_url)
760+
host = parsed_url.hostname
761+
port = parsed_url.port
762+
logging.debug(
763+
f"Ignoring --host and --port parameters, because --base-url is"
764+
f" specified."
765+
)
744766

745767
@asynccontextmanager
746768
async def _lifespan(app: FastAPI):
@@ -749,7 +771,7 @@ async def _lifespan(app: FastAPI):
749771
+-----------------------------------------------------------------------------+
750772
| ADK Web Server started |
751773
| |
752-
| For local testing, access at http://{host}:{port}.{" "*(29 - len(str(port)))}|
774+
| For local testing, access at {base_url}.{" "*(29 - len(str(port)))}|
753775
+-----------------------------------------------------------------------------+
754776
""",
755777
fg="green",
@@ -777,8 +799,7 @@ async def _lifespan(app: FastAPI):
777799
trace_to_cloud=trace_to_cloud,
778800
lifespan=_lifespan,
779801
a2a=a2a,
780-
host=host,
781-
port=port,
802+
base_url=base_url,
782803
reload_agents=reload_agents,
783804
)
784805
config = uvicorn.Config(
@@ -812,6 +833,7 @@ def cli_api_server(
812833
allow_origins: Optional[list[str]] = None,
813834
host: str = "127.0.0.1",
814835
port: int = 8000,
836+
base_url: Optional[str] = None,
815837
trace_to_cloud: bool = False,
816838
reload: bool = True,
817839
session_service_uri: Optional[str] = None,
@@ -833,6 +855,16 @@ def cli_api_server(
833855
"""
834856
logs.setup_adk_logger(getattr(logging, log_level.upper()))
835857

858+
if base_url is None:
859+
base_url = f"http://{host}:{port}"
860+
else:
861+
parsed_url = urlparse(base_url)
862+
host = parsed_url.hostname
863+
port = parsed_url.port
864+
logging.debug(
865+
f"Ignoring --host and --port parameters, because --base-url is"
866+
f" specified."
867+
)
836868
session_service_uri = session_service_uri or session_db_url
837869
artifact_service_uri = artifact_service_uri or artifact_storage_uri
838870
config = uvicorn.Config(
@@ -846,8 +878,7 @@ def cli_api_server(
846878
web=False,
847879
trace_to_cloud=trace_to_cloud,
848880
a2a=a2a,
849-
host=host,
850-
port=port,
881+
base_url=base_url,
851882
reload_agents=reload_agents,
852883
),
853884
host=host,

src/google/adk/cli/fast_api.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ def get_fast_api_app(
6464
allow_origins: Optional[list[str]] = None,
6565
web: bool,
6666
a2a: bool = False,
67-
host: str = "127.0.0.1",
68-
port: int = 8000,
67+
base_url: str = "http://127.0.0.1:8000",
6968
trace_to_cloud: bool = False,
7069
reload_agents: bool = False,
7170
lifespan: Optional[Lifespan[FastAPI]] = None,
@@ -352,6 +351,8 @@ async def _get_a2a_runner_async() -> Runner:
352351
logger.info("Setting up A2A agent: %s", app_name)
353352

354353
try:
354+
a2a_rpc_path = f"{base_url}/a2a/{app_name}"
355+
355356
agent_executor = A2aAgentExecutor(
356357
runner=create_a2a_runner_loader(app_name),
357358
)
@@ -363,6 +364,10 @@ async def _get_a2a_runner_async() -> Runner:
363364
with (p / "agent.json").open("r", encoding="utf-8") as f:
364365
data = json.load(f)
365366
agent_card = AgentCard(**data)
367+
if (
368+
agent_card.url == ""
369+
): # empty url is a placeholder to be filled with the provided url
370+
agent_card.url = a2a_rpc_path
366371

367372
a2a_app = A2AStarletteApplication(
368373
agent_card=agent_card,

tests/unittests/cli/test_fast_api.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from unittest.mock import MagicMock
2525
from unittest.mock import patch
2626

27+
from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH
2728
from fastapi.testclient import TestClient
2829
from google.adk.agents.base_agent import BaseAgent
2930
from google.adk.agents.run_config import RunConfig
@@ -432,8 +433,7 @@ def test_app(
432433
memory_service_uri="",
433434
allow_origins=["*"],
434435
a2a=False, # Disable A2A for most tests
435-
host="127.0.0.1",
436-
port=8000,
436+
base_url="http://127.0.0.1:8000",
437437
)
438438

439439
# Create a TestClient that doesn't start a real server
@@ -502,11 +502,22 @@ def temp_agents_dir_with_a2a():
502502

503503
# Create agent.json file
504504
agent_card = {
505+
"capabilities": {"pushNotifications": True, "streaming": True},
506+
"defaultInputModes": ["text", "text/plain"],
507+
"defaultOutputModes": ["text", "text/plain"],
505508
"name": "test_a2a_agent",
506509
"description": "Test A2A agent",
507510
"version": "1.0.0",
508511
"author": "test",
509-
"capabilities": ["text"],
512+
"protocolVersion": "0.2.6",
513+
"skills": [{
514+
"description": "Makes the tests pass",
515+
"examples": ["Fix the tests."],
516+
"id": "test_a2a_agent",
517+
"name": "Test A2A agent",
518+
"tags": ["testing"],
519+
}],
520+
"url": "",
510521
}
511522

512523
with open(agent_dir / "agent.json", "w") as f:
@@ -580,20 +591,12 @@ def test_app_with_a2a(
580591
patch(
581592
"a2a.server.request_handlers.DefaultRequestHandler"
582593
) as mock_handler,
583-
patch("a2a.server.apps.A2AStarletteApplication") as mock_a2a_app,
584594
):
585595
# Configure mocks
586596
mock_task_store.return_value = MagicMock()
587597
mock_executor.return_value = MagicMock()
588598
mock_handler.return_value = MagicMock()
589599

590-
# Mock A2AStarletteApplication
591-
mock_app_instance = MagicMock()
592-
mock_app_instance.routes.return_value = (
593-
[]
594-
) # Return empty routes for testing
595-
mock_a2a_app.return_value = mock_app_instance
596-
597600
# Change to temp directory
598601
original_cwd = os.getcwd()
599602
os.chdir(temp_agents_dir_with_a2a)
@@ -607,8 +610,7 @@ def test_app_with_a2a(
607610
memory_service_uri="",
608611
allow_origins=["*"],
609612
a2a=True,
610-
host="127.0.0.1",
611-
port=8000,
613+
base_url="http://127.0.0.1:8000",
612614
)
613615

614616
client = TestClient(app)
@@ -881,9 +883,14 @@ def test_debug_trace(test_app):
881883
)
882884
def test_a2a_agent_discovery(test_app_with_a2a):
883885
"""Test that A2A agents are properly discovered and configured."""
884-
# This test mainly verifies that the A2A setup doesn't break the app
886+
# This test verifies that the A2A setup doesn't break the app
887+
# and that the well known card works
885888
response = test_app_with_a2a.get("/list-apps")
886889
assert response.status_code == 200
890+
response2 = test_app_with_a2a.get(
891+
f"/a2a/test_a2a_agent{AGENT_CARD_WELL_KNOWN_PATH}"
892+
)
893+
assert response2.status_code == 200
887894
logger.info("A2A agent discovery test passed")
888895

889896

0 commit comments

Comments
 (0)