Skip to content

Commit f660ec8

Browse files
authored
Merge branch 'main' into release/candidate
2 parents ef471b8 + 34da2d5 commit f660ec8

6 files changed

Lines changed: 142 additions & 13 deletions

File tree

src/google/adk/cli/conformance/_generate_markdown_utils.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,27 @@
1717
from __future__ import annotations
1818

1919
from pathlib import Path
20+
from typing import Any
2021
from typing import Optional
2122
from typing import TYPE_CHECKING
2223

2324
import click
2425

25-
from ... import version
26-
2726
if TYPE_CHECKING:
2827
from .cli_test import _ConformanceTestSummary
2928

3029

3130
def generate_markdown_report(
32-
summary: _ConformanceTestSummary, report_dir: Optional[str]
31+
version_data: dict[str, Any],
32+
summary: _ConformanceTestSummary,
33+
report_dir: Optional[str],
3334
) -> None:
3435
"""Generates a Markdown report of the test results."""
35-
report_name = f"python_{'_'.join(version.__version__.split('.'))}_report.md"
36+
server_version = version_data.get("version", "Unknown")
37+
language = version_data.get("language", "Unknown")
38+
language_version = version_data.get("language_version", "Unknown")
39+
40+
report_name = f"python_{'_'.join(server_version.split('.'))}_report.md"
3641
if not report_dir:
3742
report_path = Path(report_name)
3843
else:
@@ -44,7 +49,8 @@ def generate_markdown_report(
4449

4550
# Summary
4651
f.write("## Summary\n\n")
47-
f.write(f"- **ADK Version**: {version.__version__}\n")
52+
f.write(f"- **ADK Version**: {server_version}\n")
53+
f.write(f"- **Language**: {language} {language_version}\n")
4854
f.write(f"- **Total Tests**: {summary.total_tests}\n")
4955
f.write(f"- **Passed**: {summary.passed_tests}\n")
5056
f.write(f"- **Failed**: {summary.failed_tests}\n")

src/google/adk/cli/conformance/adk_web_server_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,17 @@ async def update_session(
207207
response.raise_for_status()
208208
return Session.model_validate(response.json())
209209

210+
async def get_version_data(self) -> Dict[str, str]:
211+
"""Retrieve version data from the ADK web server.
212+
213+
Returns:
214+
Dictionary containing version information
215+
"""
216+
async with self._get_client() as client:
217+
response = await client.get("/version")
218+
response.raise_for_status()
219+
return response.json()
220+
210221
async def run_agent(
211222
self,
212223
request: RunAgentRequest,

src/google/adk/cli/conformance/cli_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,9 @@ async def run_conformance_test(
328328
runner = ConformanceTestRunner(test_paths, client, mode)
329329
summary = await runner.run_all_tests()
330330

331-
if generate_report:
332-
generate_markdown_report(summary, report_dir)
331+
if generate_report:
332+
version_data = await client.get_version_data()
333+
generate_markdown_report(version_data, summary, report_dir)
333334

334335
_print_test_summary(summary)
335336

src/google/adk/cli/fast_api.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from .utils import evals
4646
from .utils.agent_change_handler import AgentChangeEventHandler
4747
from .utils.agent_loader import AgentLoader
48+
from .utils.base_agent_loader import BaseAgentLoader
4849
from .utils.service_factory import create_artifact_service_from_options
4950
from .utils.service_factory import create_memory_service_from_options
5051
from .utils.service_factory import create_session_service_from_options
@@ -72,6 +73,7 @@ def __getattr__(name: str):
7273
def get_fast_api_app(
7374
*,
7475
agents_dir: str,
76+
agent_loader: Optional[BaseAgentLoader] = None,
7577
session_service_uri: Optional[str] = None,
7678
session_db_kwargs: Optional[Mapping[str, Any]] = None,
7779
artifact_service_uri: Optional[str] = None,
@@ -93,6 +95,52 @@ def get_fast_api_app(
9395
logo_image_url: Optional[str] = None,
9496
auto_create_session: bool = False,
9597
) -> FastAPI:
98+
"""Constructs and returns a FastAPI application for serving ADK agents.
99+
100+
This function orchestrates the initialization of core ADK services (Session,
101+
Artifact, Memory, and Credential) based on the provided configuration,
102+
configures the ADK Web Server, and optionally enables advanced features
103+
like Agent-to-Agent (A2A) protocol support and cloud telemetry.
104+
105+
Args:
106+
agents_dir: The root directory containing agent definitions. This path is
107+
used to discover agents, load custom service registrations (via
108+
services.py/yaml), and as a base for local storage.
109+
agent_loader: An optional custom loader for retrieving agent instances. If
110+
not provided, a default AgentLoader targeting agents_dir is used.
111+
session_service_uri: A URI defining the backend for session persistence.
112+
Supports schemes like 'memory://', 'sqlite://', 'postgresql://',
113+
'mysql://', or 'agentengine://'. Defaults to per-agent local SQLite
114+
storage if None.
115+
session_db_kwargs: Optional keyword arguments for custom session service
116+
initialization. These are passed to the service factory along with the
117+
URI.
118+
artifact_service_uri: URI for the artifact service. Uses local artifact
119+
service if None.
120+
memory_service_uri: URI for the memory service. Uses local memory service if
121+
None.
122+
use_local_storage: Whether to use local storage for session and artifacts.
123+
eval_storage_uri: URI for evaluation storage. If provided, uses GCS
124+
managers.
125+
allow_origins: List of allowed origins for CORS.
126+
web: Whether to enable the web UI and serve its assets.
127+
a2a: Whether to enable Agent-to-Agent (A2A) protocol support.
128+
host: Host address for the server (defaults to 127.0.0.1).
129+
port: Port number for the server (defaults to 8000).
130+
url_prefix: Optional prefix for all URL routes.
131+
trace_to_cloud: Whether to export traces to Google Cloud Trace.
132+
otel_to_cloud: Whether to export OpenTelemetry data to Google Cloud.
133+
reload_agents: Whether to watch for file changes and reload agents.
134+
lifespan: Optional FastAPI lifespan context manager.
135+
extra_plugins: List of extra plugin names to load.
136+
logo_text: Text to display in the web UI logo area.
137+
logo_image_url: URL for an image to display in the web UI logo area.
138+
auto_create_session: Whether to automatically create a session when
139+
not found.
140+
141+
Returns:
142+
The configured FastAPI application instance.
143+
"""
96144

97145
# Set up eval managers.
98146
if eval_storage_uri:
@@ -105,8 +153,10 @@ def get_fast_api_app(
105153
eval_sets_manager = LocalEvalSetsManager(agents_dir=agents_dir)
106154
eval_set_results_manager = LocalEvalSetResultsManager(agents_dir=agents_dir)
107155

108-
# initialize Agent Loader
109-
agent_loader = AgentLoader(agents_dir)
156+
# initialize Agent Loader if not passed as argument
157+
if agent_loader is None:
158+
agent_loader = AgentLoader(agents_dir)
159+
110160
# Load services.py from agents_dir for custom service registration.
111161
load_services_module(agents_dir)
112162

src/google/adk/tools/mcp_tool/mcp_tool.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from google.genai.types import FunctionDeclaration
3232
from mcp.shared.session import ProgressFnT
3333
from mcp.types import Tool as McpBaseTool
34+
from opentelemetry import propagate
3435
from typing_extensions import override
3536

3637
from ...agents.callback_context import CallbackContext
@@ -313,6 +314,12 @@ async def _run_async_impl(
313314
headers.update(dynamic_headers)
314315
final_headers = headers if headers else None
315316

317+
# Propagate trace context in the _meta field as sprcified by MCP protocol.
318+
# See https://agentclientprotocol.com/protocol/extensibility#the-meta-field
319+
trace_carrier: Dict[str, str] = {}
320+
propagate.get_global_textmap().inject(carrier=trace_carrier)
321+
meta_trace_context = trace_carrier if trace_carrier else None
322+
316323
# Get the session from the session manager
317324
session = await self._mcp_session_manager.create_session(
318325
headers=final_headers
@@ -325,6 +332,7 @@ async def _run_async_impl(
325332
self._mcp_tool.name,
326333
arguments=args,
327334
progress_callback=resolved_callback,
335+
meta=meta_trace_context,
328336
)
329337
return response.model_dump(exclude_none=True, mode="json")
330338

tests/unittests/tools/mcp_tool/test_mcp_tool.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from google.adk.auth.auth_credential import ServiceAccount
2626
from google.adk.features import FeatureName
2727
from google.adk.features._feature_registry import temporary_feature_override
28+
from google.adk.tools.mcp_tool import mcp_tool
2829
from google.adk.tools.mcp_tool.mcp_session_manager import MCPSessionManager
2930
from google.adk.tools.mcp_tool.mcp_tool import MCPTool
3031
from google.adk.tools.tool_context import ToolContext
@@ -225,7 +226,7 @@ async def test_run_async_impl_no_auth(self):
225226
)
226227
# Fix: call_tool uses 'arguments' parameter, not positional args
227228
self.mock_session.call_tool.assert_called_once_with(
228-
"test_tool", arguments=args, progress_callback=None
229+
"test_tool", arguments=args, progress_callback=None, meta=None
229230
)
230231

231232
@pytest.mark.asyncio
@@ -262,6 +263,55 @@ async def test_run_async_impl_with_oauth2(self):
262263
headers = call_args[1]["headers"]
263264
assert headers == {"Authorization": "Bearer test_access_token"}
264265

266+
@patch.object(mcp_tool, "propagate", autospec=True)
267+
@pytest.mark.asyncio
268+
async def test_run_async_impl_with_trace_context(self, mock_propagate):
269+
"""Test running tool with trace context injection."""
270+
mock_propagator = Mock()
271+
272+
def inject_context(carrier, context=None) -> None:
273+
carrier["traceparent"] = (
274+
"00-1234567890abcdef1234567890abcdef-1234567890abcdef-01"
275+
)
276+
carrier["tracestate"] = "foo=bar"
277+
carrier["baggage"] = "baz=qux"
278+
279+
mock_propagator.inject.side_effect = inject_context
280+
mock_propagate.get_global_textmap.return_value = mock_propagator
281+
282+
tool = MCPTool(
283+
mcp_tool=self.mock_mcp_tool,
284+
mcp_session_manager=self.mock_session_manager,
285+
)
286+
287+
mcp_response = CallToolResult(
288+
content=[TextContent(type="text", text="success")]
289+
)
290+
self.mock_session.call_tool = AsyncMock(return_value=mcp_response)
291+
292+
tool_context = Mock(spec=ToolContext)
293+
args = {"param1": "test_value"}
294+
295+
await tool._run_async_impl(
296+
args=args, tool_context=tool_context, credential=None
297+
)
298+
299+
self.mock_session_manager.create_session.assert_called_once_with(
300+
headers=None
301+
)
302+
self.mock_session.call_tool.assert_called_once_with(
303+
"test_tool",
304+
arguments=args,
305+
progress_callback=None,
306+
meta={
307+
"traceparent": (
308+
"00-1234567890abcdef1234567890abcdef-1234567890abcdef-01"
309+
),
310+
"tracestate": "foo=bar",
311+
"baggage": "baz=qux",
312+
},
313+
)
314+
265315
@pytest.mark.asyncio
266316
async def test_get_headers_oauth2(self):
267317
"""Test header generation for OAuth2 credentials."""
@@ -778,7 +828,7 @@ async def test_run_async_impl_with_header_provider_no_auth(self):
778828
headers=expected_headers
779829
)
780830
self.mock_session.call_tool.assert_called_once_with(
781-
"test_tool", arguments=args, progress_callback=None
831+
"test_tool", arguments=args, progress_callback=None, meta=None
782832
)
783833

784834
@pytest.mark.asyncio
@@ -821,7 +871,7 @@ async def test_run_async_impl_with_header_provider_and_oauth2(self):
821871
"X-Tenant-ID": "test-tenant",
822872
}
823873
self.mock_session.call_tool.assert_called_once_with(
824-
"test_tool", arguments=args, progress_callback=None
874+
"test_tool", arguments=args, progress_callback=None, meta=None
825875
)
826876

827877
def test_init_with_progress_callback(self):
@@ -875,7 +925,10 @@ async def my_progress_callback(
875925
)
876926
# Verify progress_callback was passed to call_tool
877927
self.mock_session.call_tool.assert_called_once_with(
878-
"test_tool", arguments=args, progress_callback=my_progress_callback
928+
"test_tool",
929+
arguments=args,
930+
progress_callback=my_progress_callback,
931+
meta=None,
879932
)
880933

881934
@pytest.mark.asyncio

0 commit comments

Comments
 (0)