From 787e933796e94a5c96afb7896f21a97514c715ba Mon Sep 17 00:00:00 2001 From: Thumma Dinesh Date: Fri, 23 Jan 2026 20:57:04 +0530 Subject: [PATCH 1/3] fix(cli):detailed error message on sse stream specifying stacktrack ,error type on client side as well --- src/google/adk/cli/adk_web_server.py | 22 ++++++-- tests/unittests/cli/test_fast_api.py | 81 ++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 4404d62e4e..feda4dffe6 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -28,6 +28,7 @@ from typing import List from typing import Literal from typing import Optional +import uuid from fastapi import FastAPI from fastapi import HTTPException @@ -761,7 +762,7 @@ async def internal_lifespan(app: FastAPI): async def list_apps( detailed: bool = Query( default=False, description="Return detailed app information" - ) + ), ) -> list[str] | ListAppsResponse: if detailed: apps_info = self.agent_loader.list_agents_detailed() @@ -1554,16 +1555,27 @@ async def event_generator(): by_alias=True, ) logger.debug( - "Generated event in agent run streaming: %s", sse_event + "Generated event in agent run streaming: %s", + sse_event, ) yield f"data: {sse_event}\n\n" except Exception as e: - logger.exception("Error in event_generator: %s", e) - # Yield a proper Event object for the error + logger.debug("Exception in agent run streaming: %s", e) + + error_details = { + "error_type": type(e).__name__, + "error_message": str(e), + "timestamp": time.time(), + } + + if logger.isEnabledFor(logging.DEBUG): + error_details["stacktrace"] = traceback.format_exc() + error_event = Event( author="system", content=types.Content( - role="model", parts=[types.Part(text=f"Error: {e}")] + role="model", + parts=[types.Part(text=json.dumps(error_details))], ), ) yield ( diff --git a/tests/unittests/cli/test_fast_api.py b/tests/unittests/cli/test_fast_api.py index 0c69605349..13f2389da6 100755 --- a/tests/unittests/cli/test_fast_api.py +++ b/tests/unittests/cli/test_fast_api.py @@ -90,7 +90,7 @@ def _event_2(): types.Part( text=None, inline_data=types.Blob( - mime_type="audio/pcm;rate=24000", data=b"\x00\xFF" + mime_type="audio/pcm;rate=24000", data=b"\x00\xff" ), ) ], @@ -183,7 +183,6 @@ def test_session_info(): @pytest.fixture def mock_agent_loader(): - class MockAgentLoader: def __init__(self, agents_dir: str): @@ -1052,8 +1051,7 @@ def test_save_artifact(test_app, create_test_session, mock_artifact_service): assert data["customMetadata"] == {} assert data["mimeType"] in (None, "text/plain") assert data["canonicalUri"].endswith( - f"/sessions/{info['session_id']}/artifacts/" - f"{payload['filename']}/versions/0" + f"/sessions/{info['session_id']}/artifacts/{payload['filename']}/versions/0" ) assert isinstance(data["createTime"], float) @@ -1411,5 +1409,80 @@ def test_builder_save_rejects_traversal(builder_test_client, tmp_path): assert not (tmp_path / "app" / "tmp" / "escape.yaml").exists() +def test_agent_run_sse_error_details( + test_app, create_test_session, monkeypatch +): + """Test /run_sse returns structured JSON on error.""" + info = create_test_session + + async def run_async_with_error(self, **kwargs): + # Yield one normal event then fail + yield Event( + author="dummy agent", + invocation_id="test_invocation", + content=types.Content( + role="model", parts=[types.Part(text="Initial part")] + ), + ) + raise ValueError("Simulated runner error") + + monkeypatch.setattr(Runner, "run_async", run_async_with_error) + + payload = { + "app_name": info["app_name"], + "user_id": info["user_id"], + "session_id": info["session_id"], + "new_message": {"role": "user", "parts": [{"text": "Trigger error"}]}, + "streaming": True, + } + + # 1. Test without DEBUG enabled (Secure mode) + with patch( + "google.adk.cli.adk_web_server.logger.isEnabledFor", return_value=False + ): + response = test_app.post("/run_sse", json=payload) + assert response.status_code == 200 + + events = [ + json.loads(line.removeprefix("data: ")) + for line in response.text.splitlines() + if line.startswith("data: ") + ] + + assert len(events) == 2 + # Verify first (normal) event + assert events[0]["content"]["parts"][0]["text"] == "Initial part" + + # Verify second (error) event + error_event = events[1] + assert error_event["author"] == "system" + + # Verify the JSON structure in the content part + error_data = json.loads(error_event["content"]["parts"][0]["text"]) + assert error_data["error_type"] == "ValueError" + assert error_data["error_message"] == "Simulated runner error" + assert "stacktrace" not in error_data + assert "timestamp" in error_data + + # 2. Test with DEBUG enabled (Trace enabled) + with patch( + "google.adk.cli.adk_web_server.logger.isEnabledFor", return_value=True + ): + response = test_app.post("/run_sse", json=payload) + assert response.status_code == 200 + + events = [ + json.loads(line.removeprefix("data: ")) + for line in response.text.splitlines() + if line.startswith("data: ") + ] + + assert len(events) == 2 + error_data = json.loads(events[1]["content"]["parts"][0]["text"]) + assert error_data["error_type"] == "ValueError" + assert "stacktrace" in error_data + assert "ValueError: Simulated runner error" in error_data["stacktrace"] + + if __name__ == "__main__": pytest.main(["-xvs", __file__]) From 2d296d6b67ac261d83cc71deb007345bc9a3c4a3 Mon Sep 17 00:00:00 2001 From: Thumma Dinesh Date: Fri, 23 Jan 2026 21:14:56 +0530 Subject: [PATCH 2/3] minor changes --- src/google/adk/cli/adk_web_server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index feda4dffe6..36b98b2b15 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -28,7 +28,6 @@ from typing import List from typing import Literal from typing import Optional -import uuid from fastapi import FastAPI from fastapi import HTTPException From 046302ff17f9d84a4135a5b6d351b79408197cf3 Mon Sep 17 00:00:00 2001 From: Thumma Dinesh Date: Fri, 23 Jan 2026 23:09:37 +0530 Subject: [PATCH 3/3] formatted and linted --- src/google/adk/cli/adk_web_server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 36b98b2b15..46a44de06c 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -1556,10 +1556,14 @@ async def event_generator(): logger.debug( "Generated event in agent run streaming: %s", sse_event, + extra={ + "session_id": req.session_id, + "user_id": req.user_id, + }, ) yield f"data: {sse_event}\n\n" except Exception as e: - logger.debug("Exception in agent run streaming: %s", e) + logger.debug(f"Exception in agent run streaming: {e}", exc_info=True) error_details = { "error_type": type(e).__name__,