@@ -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