Skip to content
1 change: 0 additions & 1 deletion contributing/samples/gepa/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
from tau_bench.types import EnvRunResult
from tau_bench.types import RunConfig
import tau_bench_agent as tau_bench_agent_lib

import utils


Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from pydantic import BaseModel

from ..agents.llm_agent import LlmAgent
from ..agents.run_config import RunConfig
from ..apps.app import App
from ..artifacts.base_artifact_service import BaseArtifactService
from ..auth.credential_service.base_credential_service import BaseCredentialService
Expand Down Expand Up @@ -56,6 +57,8 @@ async def run_input_file(
credential_service: BaseCredentialService,
input_path: str,
memory_service: Optional[BaseMemoryService] = None,
max_llm_calls: int = 500,
avatar_config: Optional[types.AvatarConfig] = None,
) -> Session:
app = (
agent_or_app
Expand All @@ -81,7 +84,12 @@ async def run_input_file(
content = types.Content(role='user', parts=[types.Part(text=query)])
async with Aclosing(
runner.run_async(
user_id=session.user_id, session_id=session.id, new_message=content
user_id=session.user_id,
session_id=session.id,
new_message=content,
run_config=RunConfig(
max_llm_calls=max_llm_calls, avatar_config=avatar_config
),
)
) as agen:
async for event in agen:
Expand All @@ -98,6 +106,8 @@ async def run_interactively(
session_service: BaseSessionService,
credential_service: BaseCredentialService,
memory_service: Optional[BaseMemoryService] = None,
max_llm_calls: int = 500,
avatar_config: Optional[types.AvatarConfig] = None,
) -> None:
app = (
root_agent_or_app
Expand All @@ -124,6 +134,9 @@ async def run_interactively(
new_message=types.Content(
role='user', parts=[types.Part(text=query)]
),
run_config=RunConfig(
max_llm_calls=max_llm_calls, avatar_config=avatar_config
),
)
) as agen:
async for event in agen:
Expand All @@ -145,6 +158,8 @@ async def run_cli(
artifact_service_uri: Optional[str] = None,
memory_service_uri: Optional[str] = None,
use_local_storage: bool = True,
max_llm_calls: int = 500,
avatar_config: Optional[str] = None,
) -> None:
"""Runs an interactive CLI for a certain agent.

Expand All @@ -170,6 +185,19 @@ async def run_cli(
user_id = 'test_user'

agents_dir = str(agent_parent_path)

avatar_config_obj = None
if avatar_config:
try:
if Path(avatar_config).is_file():
with open(avatar_config, 'r', encoding='utf-8') as f:
config_dict = json.load(f)
else:
config_dict = json.loads(avatar_config)
avatar_config_obj = types.AvatarConfig.model_validate(config_dict)
except Exception as e:
click.secho(f'Warning: Failed to parse avatar_config: {e}', fg='yellow')

agent_loader = AgentLoader(agents_dir=agents_dir)
agent_or_app = agent_loader.load_agent(agent_folder_name)
session_app_name = (
Expand Down Expand Up @@ -224,6 +252,8 @@ def _print_event(event) -> None:
memory_service=memory_service,
credential_service=credential_service,
input_path=input_file,
max_llm_calls=max_llm_calls,
avatar_config=avatar_config_obj,
)
elif saved_session_file:
# Load the saved session from file
Expand All @@ -250,6 +280,8 @@ def _print_event(event) -> None:
session_service,
credential_service,
memory_service=memory_service,
max_llm_calls=max_llm_calls,
avatar_config=avatar_config_obj,
)
else:
session = await session_service.create_session(
Expand All @@ -263,6 +295,8 @@ def _print_event(event) -> None:
session_service,
credential_service,
memory_service=memory_service,
max_llm_calls=max_llm_calls,
avatar_config=avatar_config_obj,
)

if save_session:
Expand Down
Loading
Loading