Skip to content

Commit 4460f4f

Browse files
wyf7107copybara-github
authored andcommitted
chore: add /dev/build_graph/{app_name} to build the graph serialization for apps, and
make it dev only with `with_ui` flag Co-authored-by: Yifan Wang <wanyif@google.com> PiperOrigin-RevId: 875319398
1 parent d7cfd8f commit 4460f4f

File tree

1 file changed

+94
-7
lines changed

1 file changed

+94
-7
lines changed

src/google/adk/cli/adk_web_server.py

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,8 @@ def _import_plugin_object(self, qualified_name: str) -> Any:
588588
"""Import a plugin object (class or instance) from a fully qualified name.
589589
590590
Args:
591-
qualified_name: Fully qualified name (e.g., 'my_package.my_plugin.MyPlugin')
591+
qualified_name: Fully qualified name (e.g.,
592+
'my_package.my_plugin.MyPlugin')
592593
593594
Returns:
594595
The imported object, which can be either a class or an instance.
@@ -688,6 +689,7 @@ def get_fast_api_app(
688689
] = lambda o, s: None,
689690
register_processors: Callable[[TracerProvider], None] = lambda o: None,
690691
otel_to_cloud: bool = False,
692+
with_ui: bool = False,
691693
):
692694
"""Creates a FastAPI app for the ADK web server.
693695
@@ -700,7 +702,8 @@ def get_fast_api_app(
700702
lifespan: The lifespan of the FastAPI app.
701703
allow_origins: The origins that are allowed to make cross-origin requests.
702704
Entries can be literal origins (e.g., 'https://example.com') or regex
703-
patterns prefixed with 'regex:' (e.g., 'regex:https://.*\\.example\\.com').
705+
patterns prefixed with 'regex:' (e.g.,
706+
'regex:https://.*\\.example\\.com').
704707
web_assets_dir: The directory containing the web assets to serve.
705708
setup_observer: Callback for setting up the file system observer.
706709
tear_down_observer: Callback for cleaning up the file system observer.
@@ -795,10 +798,93 @@ async def get_trace_dict(event_id: str) -> Any:
795798
raise HTTPException(status_code=404, detail="Trace not found")
796799
return event_dict
797800

798-
@app.get("/apps/{app_name}")
799-
async def get_app_info(app_name: str) -> Any:
800-
runner = await self.get_runner_async(app_name)
801-
return runner.app
801+
if web_assets_dir:
802+
803+
@app.get("/dev/build_graph/{app_name}")
804+
async def get_app_info(app_name: str) -> Any:
805+
runner = await self.get_runner_async(app_name)
806+
807+
if not runner.app:
808+
raise HTTPException(
809+
status_code=404, detail=f"App not found: {app_name}"
810+
)
811+
812+
def serialize_agent(agent: BaseAgent) -> dict[str, Any]:
813+
"""Recursively serialize an agent, excluding non-serializable fields."""
814+
agent_dict = {}
815+
816+
for field_name, field_info in agent.__class__.model_fields.items():
817+
# Skip non-serializable fields
818+
if field_name in [
819+
"parent_agent",
820+
"before_agent_callback",
821+
"after_agent_callback",
822+
"before_model_callback",
823+
"after_model_callback",
824+
"on_model_error_callback",
825+
"before_tool_callback",
826+
"after_tool_callback",
827+
"on_tool_error_callback",
828+
]:
829+
continue
830+
831+
value = getattr(agent, field_name, None)
832+
833+
# Handle sub_agents recursively
834+
if field_name == "sub_agents" and value:
835+
agent_dict[field_name] = [
836+
serialize_agent(sub_agent) for sub_agent in value
837+
]
838+
elif value is None or field_name == "tools":
839+
continue
840+
else:
841+
try:
842+
if isinstance(value, (str, int, float, bool, list, dict)):
843+
agent_dict[field_name] = value
844+
elif hasattr(value, "model_dump"):
845+
agent_dict[field_name] = value.model_dump(
846+
mode="python", exclude_none=True
847+
)
848+
else:
849+
agent_dict[field_name] = str(value)
850+
except Exception:
851+
pass
852+
853+
return agent_dict
854+
855+
app_info = {
856+
"name": runner.app.name,
857+
"root_agent": serialize_agent(runner.app.root_agent),
858+
}
859+
860+
# Add optional fields if present
861+
if runner.app.plugins:
862+
app_info["plugins"] = [
863+
{"name": getattr(plugin, "name", type(plugin).__name__)}
864+
for plugin in runner.app.plugins
865+
]
866+
867+
if runner.app.context_cache_config:
868+
try:
869+
app_info["context_cache_config"] = (
870+
runner.app.context_cache_config.model_dump(
871+
mode="python", exclude_none=True
872+
)
873+
)
874+
except Exception:
875+
pass
876+
877+
if runner.app.resumability_config:
878+
try:
879+
app_info["resumability_config"] = (
880+
runner.app.resumability_config.model_dump(
881+
mode="python", exclude_none=True
882+
)
883+
)
884+
except Exception:
885+
pass
886+
887+
return app_info
802888

803889
@app.get("/debug/trace/session/{session_id}", tags=[TAG_DEBUG])
804890
async def get_session_trace(session_id: str) -> Any:
@@ -1534,7 +1620,8 @@ async def patch_memory(
15341620
update_memory_request: The memory request for the update
15351621
15361622
Raises:
1537-
HTTPException: If the memory service is not configured or the request is invalid.
1623+
HTTPException: If the memory service is not configured or the request
1624+
is invalid.
15381625
"""
15391626
if not self.memory_service:
15401627
raise HTTPException(

0 commit comments

Comments
 (0)