fix(cli): restore Dev UI graph and fix FunctionTool complex type crash by EaCognitive · Pull Request #5447 · google/adk-python · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion contributing/samples/gepa/experiment.py
1 change: 0 additions & 1 deletion contributing/samples/gepa/run_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from absl import flags
import experiment
from google.genai import types

import utils

_OUTPUT_DIR = flags.DEFINE_string(
Expand Down
12 changes: 12 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import nox

@nox.session(python=["3.10", "3.11", "3.12", "3.13"])
def lint(session):
session.install("-e", ".")
session.install("pylint")
session.run("pylint", "src/google", "--rcfile=pylintrc")

@nox.session(python=["3.10", "3.11", "3.12", "3.13"])
def unit(session):
session.install("-e", ".[test]")
session.run("pytest", "tests/unittests")
20 changes: 20 additions & 0 deletions repro_5428.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import asyncio
from google.adk.tools.function_tool import FunctionTool

async def generate_image(
prompt: str,
input_bytes: list[tuple[bytes, str]] | None = None,
) -> dict:
"""Generate an image from a prompt."""
return {"status": "success"}

async def main():
try:
generate_image_tool = FunctionTool(func=generate_image)
generate_image_tool._get_declaration()
print("SUCCESS! No validation error.")
except Exception as e:
print(f"FAILED! Error: {type(e).__name__}: {e}")

if __name__ == "__main__":
asyncio.run(main())
55 changes: 54 additions & 1 deletion src/google/adk/cli/adk_web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,8 @@ def __init__(
extra_plugins: Optional[list[str]] = None,
logo_text: Optional[str] = None,
logo_image_url: Optional[str] = None,
max_llm_calls: int = 500,
avatar_config: Optional[str] = None,
url_prefix: Optional[str] = None,
auto_create_session: bool = False,
trigger_sources: Optional[list[str]] = None,
Expand All @@ -675,10 +677,31 @@ def __init__(
self.runners_to_clean: set[str] = set()
self.current_app_name_ref: SharedValue[str] = SharedValue(value="")
self.runner_dict = {}
self.max_llm_calls = max_llm_calls
self.avatar_config = avatar_config
self.url_prefix = url_prefix
self.auto_create_session = auto_create_session
self.trigger_sources = trigger_sources

def _get_avatar_config(self) -> Optional[types.AvatarConfig]:
"""Parses avatar_config string or file into AvatarConfig object."""
if not self.avatar_config:
return None

try:
# Check if it's a file path
if os.path.isfile(self.avatar_config):
with open(self.avatar_config, "r", encoding="utf-8") as f:
config_dict = json.load(f)
else:
# Assume it's a JSON string
config_dict = json.loads(self.avatar_config)

return types.AvatarConfig.model_validate(config_dict)
except Exception as e:
logger.error("Failed to parse avatar_config: %s", e)
return None

async def get_runner_async(self, app_name: str) -> Runner:
"""Returns the cached runner for the given app."""
# Handle cleanup
Expand Down Expand Up @@ -1898,6 +1921,10 @@ async def run_agent(req: RunAgentRequest) -> list[Event]:
session_id=req.session_id,
new_message=req.new_message,
state_delta=req.state_delta,
run_config=RunConfig(
max_llm_calls=self.max_llm_calls,
avatar_config=self._get_avatar_config(),
),
invocation_id=req.invocation_id,
)
) as agen:
Expand Down Expand Up @@ -1940,7 +1967,11 @@ async def event_generator():
session_id=req.session_id,
new_message=req.new_message,
state_delta=req.state_delta,
run_config=RunConfig(streaming_mode=stream_mode),
run_config=RunConfig(
streaming_mode=stream_mode,
max_llm_calls=self.max_llm_calls,
avatar_config=self._get_avatar_config(),
),
invocation_id=req.invocation_id,
)
) as agen:
Expand Down Expand Up @@ -1982,6 +2013,26 @@ async def event_generator():
media_type="text/event-stream",
)

@app.get("/dev/build_graph_image/{app_name}", tags=[TAG_DEBUG])
async def get_app_graph_image(
app_name: str, dark_mode: bool = False
) -> Response:
"""Returns the agent graph as an SVG image for the dev UI."""
agent_or_app = self.agent_loader.load_agent(app_name)
root_agent = self._get_root_agent(agent_or_app)

# Get graph with NO highlights (empty list) and specified theme
dot_graph = await agent_graph.get_agent_graph(
root_agent, [], dark_mode=dark_mode
)

if dot_graph and isinstance(dot_graph, graphviz.Digraph):
# Render the graph as SVG
svg_image = dot_graph.pipe(format="svg")
return Response(content=svg_image, media_type="image/svg+xml")
else:
raise HTTPException(status_code=404, detail="Graph not found")

@app.get(
"/dev/{app_name}/graph",
response_model_exclude_none=True,
Expand Down Expand Up @@ -2119,6 +2170,8 @@ async def forward_events():
else None
),
save_live_blob=save_live_blob,
max_llm_calls=self.max_llm_calls,
avatar_config=self._get_avatar_config(),
)
async with Aclosing(
runner.run_live(
Expand Down
36 changes: 35 additions & 1 deletion src/google/adk/cli/cli.py
Loading