mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
feat: expose service URI flags
Adds the shared adk_services_options decorator to adk run and other commands so developers can pass session/artifact URIs from the CLI Has new warning for the unsupported memory service on adk run, and removes the legacy --session_db_url/--artifact_storage_uri flags with tests Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 836743358
This commit is contained in:
committed by
Copybara-Service
parent
06e6fc9132
commit
f283027e92
@@ -24,6 +24,7 @@ import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import textwrap
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
@@ -354,7 +355,62 @@ def validate_exclusive(ctx, param, value):
|
||||
return value
|
||||
|
||||
|
||||
def adk_services_options():
|
||||
"""Decorator to add ADK services options to click commands."""
|
||||
|
||||
def decorator(func):
|
||||
@click.option(
|
||||
"--session_service_uri",
|
||||
help=textwrap.dedent(
|
||||
"""\
|
||||
Optional. The URI of the session service.
|
||||
- Leave unset to use the in-memory session service (default).
|
||||
- Use 'agentengine://<agent_engine>' to connect to Agent Engine
|
||||
sessions. <agent_engine> can either be the full qualified resource
|
||||
name 'projects/abc/locations/us-central1/reasoningEngines/123' or
|
||||
the resource id '123'.
|
||||
- Use 'memory://' to run with the in-memory session service.
|
||||
- Use 'sqlite://<path_to_sqlite_file>' to connect to a SQLite DB.
|
||||
- See https://docs.sqlalchemy.org/en/20/core/engines.html#backend-specific-urls for more details on supported database URIs."""
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--artifact_service_uri",
|
||||
type=str,
|
||||
help=textwrap.dedent(
|
||||
"""\
|
||||
Optional. The URI of the artifact service.
|
||||
- Leave unset to store artifacts under '.adk/artifacts' locally.
|
||||
- Use 'gs://<bucket_name>' to connect to the GCS artifact service.
|
||||
- Use 'memory://' to force the in-memory artifact service.
|
||||
- Use 'file://<path>' to store artifacts in a custom local directory."""
|
||||
),
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"--memory_service_uri",
|
||||
type=str,
|
||||
help=textwrap.dedent("""\
|
||||
Optional. The URI of the memory service.
|
||||
- Use 'rag://<rag_corpus_id>' to connect to Vertex AI Rag Memory Service.
|
||||
- Use 'agentengine://<agent_engine>' to connect to Agent Engine
|
||||
sessions. <agent_engine> can either be the full qualified resource
|
||||
name 'projects/abc/locations/us-central1/reasoningEngines/123' or
|
||||
the resource id '123'.
|
||||
- Use 'memory://' to force the in-memory memory service."""),
|
||||
default=None,
|
||||
)
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@main.command("run", cls=HelpfulCommand)
|
||||
@adk_services_options()
|
||||
@click.option(
|
||||
"--save_session",
|
||||
type=bool,
|
||||
@@ -409,6 +465,9 @@ def cli_run(
|
||||
session_id: Optional[str],
|
||||
replay: Optional[str],
|
||||
resume: Optional[str],
|
||||
session_service_uri: Optional[str] = None,
|
||||
artifact_service_uri: Optional[str] = None,
|
||||
memory_service_uri: Optional[str] = None,
|
||||
):
|
||||
"""Runs an interactive CLI for a certain agent.
|
||||
|
||||
@@ -420,6 +479,14 @@ def cli_run(
|
||||
"""
|
||||
logs.log_to_tmp_folder()
|
||||
|
||||
# Validation warning for memory_service_uri (not supported for adk run)
|
||||
if memory_service_uri:
|
||||
click.secho(
|
||||
"WARNING: --memory_service_uri is not supported for adk run.",
|
||||
fg="yellow",
|
||||
err=True,
|
||||
)
|
||||
|
||||
agent_parent_folder = os.path.dirname(agent)
|
||||
agent_folder_name = os.path.basename(agent)
|
||||
|
||||
@@ -431,6 +498,8 @@ def cli_run(
|
||||
saved_session_file=resume,
|
||||
save_session=save_session,
|
||||
session_id=session_id,
|
||||
session_service_uri=session_service_uri,
|
||||
artifact_service_uri=artifact_service_uri,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -865,55 +934,6 @@ def web_options():
|
||||
return decorator
|
||||
|
||||
|
||||
def adk_services_options():
|
||||
"""Decorator to add ADK services options to click commands."""
|
||||
|
||||
def decorator(func):
|
||||
@click.option(
|
||||
"--session_service_uri",
|
||||
help=(
|
||||
"""Optional. The URI of the session service.
|
||||
- Use 'agentengine://<agent_engine>' to connect to Agent Engine
|
||||
sessions. <agent_engine> can either be the full qualified resource
|
||||
name 'projects/abc/locations/us-central1/reasoningEngines/123' or
|
||||
the resource id '123'.
|
||||
- Use 'sqlite://<path_to_sqlite_file>' to connect to an aio-sqlite
|
||||
based session service, which is good for local development.
|
||||
- Use 'postgresql://<user>:<password>@<host>:<port>/<database_name>'
|
||||
to connect to a PostgreSQL DB.
|
||||
- See https://docs.sqlalchemy.org/en/20/core/engines.html#backend-specific-urls
|
||||
for more details on other database URIs supported by SQLAlchemy."""
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
"--artifact_service_uri",
|
||||
type=str,
|
||||
help=(
|
||||
"Optional. The URI of the artifact service,"
|
||||
" supported URIs: gs://<bucket name> for GCS artifact service."
|
||||
),
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"--memory_service_uri",
|
||||
type=str,
|
||||
help=("""Optional. The URI of the memory service.
|
||||
- Use 'rag://<rag_corpus_id>' to connect to Vertex AI Rag Memory Service.
|
||||
- Use 'agentengine://<agent_engine>' to connect to Agent Engine
|
||||
sessions. <agent_engine> can either be the full qualified resource
|
||||
name 'projects/abc/locations/us-central1/reasoningEngines/123' or
|
||||
the resource id '123'."""),
|
||||
default=None,
|
||||
)
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def deprecated_adk_services_options():
|
||||
"""Deprecated ADK services options."""
|
||||
|
||||
@@ -921,7 +941,7 @@ def deprecated_adk_services_options():
|
||||
if value:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"WARNING: Deprecated option {param.name} is used. Please use"
|
||||
f"WARNING: Deprecated option --{param.name} is used. Please use"
|
||||
f" {alternative_param} instead.",
|
||||
fg="yellow",
|
||||
),
|
||||
@@ -1116,6 +1136,8 @@ def cli_web(
|
||||
|
||||
adk web --session_service_uri=[uri] --port=[port] path/to/agents_dir
|
||||
"""
|
||||
session_service_uri = session_service_uri or session_db_url
|
||||
artifact_service_uri = artifact_service_uri or artifact_storage_uri
|
||||
logs.setup_adk_logger(getattr(logging, log_level.upper()))
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -1140,8 +1162,6 @@ def cli_web(
|
||||
fg="green",
|
||||
)
|
||||
|
||||
session_service_uri = session_service_uri or session_db_url
|
||||
artifact_service_uri = artifact_service_uri or artifact_storage_uri
|
||||
app = get_fast_api_app(
|
||||
agents_dir=agents_dir,
|
||||
session_service_uri=session_service_uri,
|
||||
@@ -1215,10 +1235,10 @@ def cli_api_server(
|
||||
|
||||
adk api_server --session_service_uri=[uri] --port=[port] path/to/agents_dir
|
||||
"""
|
||||
logs.setup_adk_logger(getattr(logging, log_level.upper()))
|
||||
|
||||
session_service_uri = session_service_uri or session_db_url
|
||||
artifact_service_uri = artifact_service_uri or artifact_storage_uri
|
||||
logs.setup_adk_logger(getattr(logging, log_level.upper()))
|
||||
|
||||
config = uvicorn.Config(
|
||||
get_fast_api_app(
|
||||
agents_dir=agents_dir,
|
||||
|
||||
@@ -18,8 +18,10 @@ from typing import Optional
|
||||
|
||||
from ...agents.base_agent import BaseAgent
|
||||
from ...agents.llm_agent import LlmAgent
|
||||
from .dot_adk_folder import DotAdkFolder
|
||||
from .state import create_empty_state
|
||||
|
||||
__all__ = [
|
||||
'create_empty_state',
|
||||
'DotAdkFolder',
|
||||
]
|
||||
|
||||
@@ -76,8 +76,11 @@ class _Recorder(BaseModel):
|
||||
|
||||
# Fixtures
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mute_click(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def _mute_click(request, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Suppress click output during tests."""
|
||||
# Allow tests to opt-out of muting by using the 'unmute_click' marker
|
||||
if "unmute_click" in request.keywords:
|
||||
return
|
||||
monkeypatch.setattr(click, "echo", lambda *a, **k: None)
|
||||
# Keep secho for error messages
|
||||
# monkeypatch.setattr(click, "secho", lambda *a, **k: None)
|
||||
@@ -121,32 +124,70 @@ def test_cli_create_cmd_invokes_run_cmd(
|
||||
cli_tools_click.main,
|
||||
["create", "--model", "gemini", "--api_key", "key123", str(app_dir)],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert result.exit_code == 0, (result.output, repr(result.exception))
|
||||
assert rec.calls, "cli_create.run_cmd must be called"
|
||||
|
||||
|
||||
# cli run
|
||||
@pytest.mark.asyncio
|
||||
async def test_cli_run_invokes_run_cli(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
@pytest.mark.parametrize(
|
||||
"cli_args,expected_session_uri,expected_artifact_uri",
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
"--session_service_uri",
|
||||
"memory://",
|
||||
"--artifact_service_uri",
|
||||
"memory://",
|
||||
],
|
||||
"memory://",
|
||||
"memory://",
|
||||
id="memory_scheme_uris",
|
||||
),
|
||||
pytest.param(
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
id="default_uris_none",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cli_run_service_uris(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
cli_args: list,
|
||||
expected_session_uri: str,
|
||||
expected_artifact_uri: str,
|
||||
) -> None:
|
||||
"""`adk run` should call run_cli via asyncio.run with correct parameters."""
|
||||
rec = _Recorder()
|
||||
monkeypatch.setattr(cli_tools_click, "run_cli", lambda **kwargs: rec(kwargs))
|
||||
monkeypatch.setattr(
|
||||
cli_tools_click.asyncio, "run", lambda coro: coro
|
||||
) # pass-through
|
||||
|
||||
# create dummy agent directory
|
||||
"""`adk run` should forward service URIs correctly to run_cli."""
|
||||
agent_dir = tmp_path / "agent"
|
||||
agent_dir.mkdir()
|
||||
(agent_dir / "__init__.py").touch()
|
||||
(agent_dir / "agent.py").touch()
|
||||
|
||||
# Capture the coroutine's locals before closing it
|
||||
captured_locals = []
|
||||
|
||||
def capture_asyncio_run(coro):
|
||||
# Extract the locals before closing the coroutine
|
||||
if coro.cr_frame is not None:
|
||||
captured_locals.append(dict(coro.cr_frame.f_locals))
|
||||
coro.close() # Properly close the coroutine to avoid warnings
|
||||
|
||||
monkeypatch.setattr(cli_tools_click.asyncio, "run", capture_asyncio_run)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli_tools_click.main, ["run", str(agent_dir)])
|
||||
assert result.exit_code == 0
|
||||
assert rec.calls and rec.calls[0][0][0]["agent_folder_name"] == "agent"
|
||||
result = runner.invoke(
|
||||
cli_tools_click.main,
|
||||
["run", *cli_args, str(agent_dir)],
|
||||
)
|
||||
assert result.exit_code == 0, (result.output, repr(result.exception))
|
||||
assert len(captured_locals) == 1, "Expected asyncio.run to be called once"
|
||||
|
||||
# Verify the kwargs passed to run_cli
|
||||
coro_locals = captured_locals[0]
|
||||
assert coro_locals.get("session_service_uri") == expected_session_uri
|
||||
assert coro_locals.get("artifact_service_uri") == expected_artifact_uri
|
||||
assert coro_locals["agent_folder_name"] == "agent"
|
||||
|
||||
|
||||
# cli deploy cloud_run
|
||||
@@ -520,10 +561,13 @@ def test_cli_web_passes_service_uris(
|
||||
assert called_kwargs.get("memory_service_uri") == "rag://mycorpus"
|
||||
|
||||
|
||||
def test_cli_web_passes_deprecated_uris(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, _patch_uvicorn: _Recorder
|
||||
@pytest.mark.unmute_click
|
||||
def test_cli_web_warns_and_maps_deprecated_uris(
|
||||
tmp_path: Path,
|
||||
_patch_uvicorn: _Recorder,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""`adk web` should use deprecated URIs if new ones are not provided."""
|
||||
"""`adk web` should accept deprecated URI flags with warnings."""
|
||||
agents_dir = tmp_path / "agents"
|
||||
agents_dir.mkdir()
|
||||
|
||||
@@ -542,11 +586,14 @@ def test_cli_web_passes_deprecated_uris(
|
||||
"gs://deprecated",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert mock_get_app.calls
|
||||
called_kwargs = mock_get_app.calls[0][1]
|
||||
assert called_kwargs.get("session_service_uri") == "sqlite:///deprecated.db"
|
||||
assert called_kwargs.get("artifact_service_uri") == "gs://deprecated"
|
||||
# Check output for deprecation warnings (CliRunner captures both stdout and stderr)
|
||||
assert "--session_db_url" in result.output
|
||||
assert "--artifact_storage_uri" in result.output
|
||||
|
||||
|
||||
def test_cli_eval_with_eval_set_file_path(
|
||||
|
||||
Reference in New Issue
Block a user