Compare commits

...

39 Commits

Author SHA1 Message Date
Claude a5e6f5853c fix(sessions): use UTC consistently in append_event for all DB backends
The append_event method had inconsistent timezone handling: SQLite
converted event.timestamp to UTC before stripping tzinfo, while all
other backends (including PostgreSQL) used datetime.fromtimestamp()
which produces naive datetimes in the server's local timezone.

On non-UTC servers, this caused PostgreSQL to store update_time values
offset by hours from SQLite, leading to incorrect staleness checks.

Now all backends first convert to UTC, then strip tzinfo only for
SQLite and PostgreSQL (which require naive datetimes).

https://claude.ai/code/session_01TNMxZwEdKgsQJvWtJhzQiQ
2026-02-11 07:19:50 +00:00
Anmol Jaiswal 758d337c76 fix(sessions): use async iteration for VertexAiSessionService.list_sessions pagination
Merge https://github.com/google/adk-python/pull/4435

### Link to Issue or Description of Change

- Closes: #4302

**Problem:**

`VertexAiSessionService.list_sessions()` only returns the first ~100 sessions. The `sessions_iterator` from `api_client.agent_engines.sessions.list()` is an `AsyncPager` — it implements `__aiter__`/`__anext__` for fetching subsequent pages, but the code uses a plain `for` loop which only calls `__iter__`/`__next__`, so it never fetches beyond the first page.

**Solution:**

Changed `for api_session in sessions_iterator` to `async for api_session in sessions_iterator` so the `AsyncPager` actually paginates. Updated the test mock to return an `AsyncIterableList` (supports both sync and async iteration) instead of a bare list, so the tests properly simulate real `AsyncPager` behaviour.

### Testing Plan

**Unit Tests:**

```
$ pytest tests/unittests/sessions/
115 passed, 1 warning in 2.25s
```

The existing `test_list_sessions`, `test_list_sessions_with_pagination`, and `test_list_sessions_all_users` all continue to pass with the updated mock.

Co-authored-by: Liang Wu <wuliang@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4435 from anmolg1997:fix/vertex-ai-session-service-pagination 14c71b607ecbf2215f4b9ba6eb4b0ff6b9eaf740
PiperOrigin-RevId: 868466166
2026-02-10 21:18:51 -08:00
Google Team Member 3cf43e3842 feat: Enhance google credentials config to support externally passed access token
PiperOrigin-RevId: 868390961
2026-02-10 17:21:38 -08:00
Kathy Wu 4aa475145f fix: Fix event loop closed bug in McpSessionManager
Sessions were being erroneously cached and reused across different asyncio event loops, causing "Event loop is closed" in environments with transient loops. This updates the session caching to be loop-aware: before reusing a cached session, check that the stored loop matches the current loop. Also, if session is disconnected and loops do not match, discard the cached entry without calling aclose().

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 868380746
2026-02-10 16:50:53 -08:00
Xiang (Sean) Zhou 7110336788 refactor: Replace check of instance for LlmAgent with hasAttribute check
Co-authored-by: Xiang (Sean) Zhou <seanzhougoogle@google.com>
PiperOrigin-RevId: 868370272
2026-02-10 16:27:20 -08:00
Google Team Member 0abf4cd2c7 feat: Add a demo simple prompt optimizer for the optimization interface
PiperOrigin-RevId: 868367793
2026-02-10 16:21:11 -08:00
Edwin Kim 40c15d0595 feat(cli): Add --auto_create_session flag to adk api_server CLI
Merge https://github.com/google/adk-python/pull/4288

**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.**

### Link to Issue or Description of Change

**1. Link to an existing issue (if applicable):**

- Closes: #4274

**2. Or, if no issue exists, describe the change:**

N/A - Issue exists

### Testing Plan

_Please describe the tests that you ran to verify your changes. This is required
for all PRs that are not small documentation or typo fixes._

**Unit Tests:**

- [X] I have added or updated unit tests for my change.
- [X] All unit tests pass locally.

$ pytest tests/unittests/cli/ -v

============================= test session starts ==============================
platform darwin -- Python 3.11.14, pytest-9.0.2, pluggy-1.6.0
collected 246 items
...
====================== 246 passed, 147 warnings in 21.38s ======================

**Manual End-to-End (E2E) Tests:**

1. Verify CLI flag is recognized:
$ adk api_server --help | grep auto_create_session --auto_create_session
Automatically create a session if it doesn't exist when calling /run.

2. Start server with flag enabled:
$ adk api_server --auto_create_session

3. Test /run endpoint without pre-creating session:
$ curl -X POST http://localhost:8000/run \
  -H "Content-Type: application/json" \
  -d '{"app_name": "my_agent", "user_id": "user1", "session_id": "new_session", "new_message": {"role": "user", "parts": [{"text": "Hello"}]}}'

Expected: Session auto-created, request succeeds (no 404 error).

### Checklist

- [X] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [X] I have performed a self-review of my own code.
- [X] I have commented my code, particularly in hard-to-understand areas.
- [X] I have added tests that prove my fix is effective or that my feature works.
- [X] New and existing unit tests pass locally with my changes.
- [X] I have manually tested my changes end-to-end.
- [X] Any dependent changes have been merged and published in downstream modules.

### Additional context
This PR exposes the existing Runner.auto_create_session functionality (added in commit 8e69a58 / ADK v1.23.0) through the adk api_server CLI command.

Files changed (3 files, ~15 lines):

src/google/adk/cli/cli_tools_click.py - Add --auto_create_session CLI option
src/google/adk/cli/fast_api.py - Pass parameter through get_fast_api_app()
src/google/adk/cli/adk_web_server.py - Store and use in _create_runner()

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4288 from ekimcodes:main 3c8d299a88b21789431dbda488befba7ee3ace81
PiperOrigin-RevId: 868361303
2026-02-10 16:06:01 -08:00
Xuan Yang 2010569010 fix: Preserve thought_signature in function call conversions for interactions API integration
Related: https://github.com/google/adk-python/issues/4311

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 868340444
2026-02-10 15:14:18 -08:00
Google Team Member 7af1858f46 feat: Update agent simulator by improving prompts and add environment data
PiperOrigin-RevId: 868324749
2026-02-10 14:37:03 -08:00
Sasha Sobran e6da417292 fix: propagate grounding and citation metadata in streaming responses
Co-authored-by: Sasha Sobran <asobran@google.com>
PiperOrigin-RevId: 868324488
2026-02-10 14:36:21 -08:00
Didier Durand 6ee5126d1c docs: fixing a typo
Merge https://github.com/google/adk-python/pull/3975

### Link to Issue or Description of Change

Fixing various typos: see commit diffs for details

**1. Link to an existing issue (if applicable):**

- Closes: N/A
- Related: N/A

**2. Or, if no issue exists, describe the change:**

Fixing various typos: see commit diffs for details

**Problem:**

Improve quality of repo

**Solution:**
Pull this P/R

### Testing Plan

N/A
**Unit Tests:**

- [N/A] I have added or updated unit tests for my change.
- [X] All unit tests pass locally.

**Manual End-to-End (E2E) Tests:**

N/A

### Checklist

- [X] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [X] I have performed a self-review of my own code.
- [N/A] I have commented my code, particularly in hard-to-understand areas.
- [N/A] I have added tests that prove my fix is effective or that my feature works.
- [X] New and existing unit tests pass locally with my changes.
- [N/A] I have manually tested my changes end-to-end.
- [N/A] Any dependent changes have been merged and published in downstream modules.

### Additional context

N/A

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3975 from didier-durand:fix-typos-b ca1cba4e7afe99bb60efffb4de79a2bfe8522ea8
PiperOrigin-RevId: 868308215
2026-02-10 14:01:58 -08:00
George Weale a88e864755 fix: Add post-invocation token-threshold compaction with event retention
Adds optional token_limit and event_retention_size fields to EventsCompactionConfig. When the latest prompt token count meets/exceeds the threshold, ADK compacts older raw events after the invocation and keeps the last N events un-compacted. Updates prompt history building to apply compaction ranges correctly so retained events remain visible.

Close #4146

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 868297968
2026-02-10 13:38:28 -08:00
Liang Wu 25ec2c6b61 feat(web): Add /health and /version endpoints to ADK web server
These endpoints provide basic health checks and version information for the running ADK server, including the ADK version and Python runtime details. The version information will be used to generate ADK conformance test report.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 868283421
2026-02-10 13:07:21 -08:00
Salman Chishti 2f0fe97729 ci: Upgrade GitHub Actions for Node 24 compatibility
Merge https://github.com/google/adk-python/pull/4426

## Summary

Upgrade GitHub Actions to their latest versions to ensure compatibility with Node 24, as Node 20 will reach end-of-life in April 2026.

## Changes

| Action | Old Version(s) | New Version | Release | Files |
|--------|---------------|-------------|---------|-------|
| `actions/checkout` | [`v4`](https://github.com/actions/checkout/releases/tag/v4) | [`v6`](https://github.com/actions/checkout/releases/tag/v6) | [Release](https://github.com/actions/checkout/releases/tag/v6) | mypy-new-errors.yml, mypy.yml |
| `actions/setup-python` | [`v5`](https://github.com/actions/setup-python/releases/tag/v5) | [`v6`](https://github.com/actions/setup-python/releases/tag/v6) | [Release](https://github.com/actions/setup-python/releases/tag/v6) | mypy-new-errors.yml, mypy.yml |

## Context

Per [GitHub's announcement](https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/), Node 20 is being deprecated and runners will begin using Node 24 by default starting March 4th, 2026.

### Why this matters

- **Node 20 EOL**: April 2026
- **Node 24 default**: March 4th, 2026
- **Action**: Update to latest action versions that support Node 24

### Security Note

Actions that were previously pinned to commit SHAs remain pinned to SHAs (updated to the latest release SHA) to maintain the security benefits of immutable references.

### Testing

These changes only affect CI/CD workflow configurations and should not impact application functionality. The workflows should be tested by running them on a branch before merging.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4426 from salmanmkc:upgrade-github-actions-node24 a574f92c72502ae60531163da60f7820e01380f3
PiperOrigin-RevId: 868281419
2026-02-10 13:02:24 -08:00
Salman Chishti f5702d70bf ci: Upgrade GitHub Actions to latest versions
Merge https://github.com/google/adk-python/pull/4427

## Summary

Upgrade GitHub Actions to their latest versions for improved features, bug fixes, and security updates.

## Changes

| Action | Old Version(s) | New Version | Release | Files |
|--------|---------------|-------------|---------|-------|
| `astral-sh/setup-uv` | [`v1`](https://github.com/astral-sh/setup-uv/releases/tag/v1), [`v5`](https://github.com/astral-sh/setup-uv/releases/tag/v5), [`v6`](https://github.com/astral-sh/setup-uv/releases/tag/v6) | [`v7`](https://github.com/astral-sh/setup-uv/releases/tag/v7) | [Release](https://github.com/astral-sh/setup-uv/releases/tag/v7) | mypy-new-errors.yml, mypy.yml, python-unit-tests.yml |
| `webfactory/ssh-agent` | [`v0.9.0`](https://github.com/webfactory/ssh-agent/releases/tag/v0.9.0) | [`v0.9.1`](https://github.com/webfactory/ssh-agent/releases/tag/v0.9.1) | [Release](https://github.com/webfactory/ssh-agent/releases/tag/v0) | analyze-releases-for-adk-docs-updates.yml |

## Why upgrade?

Keeping GitHub Actions up to date ensures:
- **Security**: Latest security patches and fixes
- **Features**: Access to new functionality and improvements
- **Compatibility**: Better support for current GitHub features
- **Performance**: Optimizations and efficiency improvements

### Security Note

Actions that were previously pinned to commit SHAs remain pinned to SHAs (updated to the latest release SHA) to maintain the security benefits of immutable references.

### Testing

These changes only affect CI/CD workflow configurations and should not impact application functionality. The workflows should be tested by running them on a branch before merging.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4427 from salmanmkc:upgrade-github-actions-node24-general 4aacbae5378233559880ff155e4f478e484c694b
PiperOrigin-RevId: 868263909
2026-02-10 12:20:03 -08:00
George Weale 59e88972ae feat: add add_events_to_memory facade for event-delta
Adds BaseMemoryService.add_events_to_memory(session, events=..., custom_metadata=...) and CallbackContext.add_events_to_memory(events=..., custom_metadata=...) so callers can add memories from an explicit subset of ADK events.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 868261578
2026-02-10 12:14:52 -08:00
Google Team Member de79bf12b5 feat: log exception details before re-raising in MCP session execution
Adds a logger.exception call to capture traceback and error details when an exception occurs during the execution of a coroutine within an MCP session, before re-raising it as a ConnectionError.

PiperOrigin-RevId: 868253547
2026-02-10 11:56:38 -08:00
Didier Durand 80ff067c6b docs: fixing typo in multiple files
Merge https://github.com/google/adk-python/pull/3944

### Link to Issue or Description of Change

**1. Link to an existing issue (if applicable):** N/A

**2. Or, if no issue exists, describe the change:** fixing various typos in multiple files: see commit diffs for details

**Problem:**

Discovered typos while reading ADK repo

**Solution:**

Submitted this PR to fix them

### Testing Plan

N/A: changes only in comments, .md and docstrings.

**Unit Tests:**

- [N/A ] I have added or updated unit tests for my change.
- [X] All unit tests pass locally.

_Please include a summary of passed `pytest` results._

**Manual End-to-End (E2E) Tests:**

N/A

### Checklist

- [X] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [X] I have performed a self-review of my own code.
- [N/A] I have commented my code, particularly in hard-to-understand areas.
- [N/A] I have added tests that prove my fix is effective or that my feature works.
- [X] New and existing unit tests pass locally with my changes.
- [N/A] I have manually tested my changes end-to-end.
- [N/A] Any dependent changes have been merged and published in downstream modules.

### Additional context

N/A

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3944 from didier-durand:fix-typos-a 02378a488d9a87ac9b6b7397fe9ad7c393faf16a
PiperOrigin-RevId: 868245940
2026-02-10 11:38:22 -08:00
sarojrout e0b9712a49 fix: Add endpoints to get/list artifact version metadata
This change introduces new FastAPI endpoints in adk_web_server.py and corresponding client methods in adk_web_server_client.py to allow fetching metadata for artifact versions without downloading the artifact content

Close #3710

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 868217569
2026-02-10 10:40:54 -08:00
George Weale 7c7d25a4a6 fix: Support escaped curly braces in instruction templates
Close #3527

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 868199186
2026-02-10 10:04:39 -08:00
Kathy Wu ec660ed4f0 chore: Add experimental tag to SkillToolset
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 867816959
2026-02-09 16:07:35 -08:00
Filipe Caixeta 19b607684f fix: Strip timezone for PostgreSQL timestamps in DatabaseSessionService
Merge https://github.com/google/adk-python/pull/4365

## Summary
- Fixes `DataError` when using PostgreSQL with `asyncpg` for session storage
- PostgreSQL's default `TIMESTAMP` type is `WITHOUT TIME ZONE`, which cannot accept timezone-aware datetime objects
- The existing code handled this for SQLite but not PostgreSQL - this fix applies the same timezone stripping

## Error
When creating a session with PostgreSQL + asyncpg, the following error occurs:

```
sqlalchemy.dialects.postgresql.asyncpg.Error: <class 'asyncpg.exceptions.DataError'>:
invalid input for query argument $5: datetime.datetime(2026, 2, 3, 21, 32, 50, 353909,
tzinfo=datetime.timezone.utc) (can't subtract offset-naive and offset-aware datetimes)
```

During the INSERT:
```sql
INSERT INTO sessions (app_name, user_id, id, state, create_time, update_time)
VALUES ($1, $2, $3, $4, $5, $6)
```

Where `$5` and `$6` are timezone-aware datetimes being inserted into `TIMESTAMP WITHOUT TIME ZONE` columns.

## Root Cause
Commit 1063fa53 changed from database-generated timestamps (`func.now()`) to explicit Python datetimes (`datetime.now(timezone.utc)`). The SQLite case was handled by stripping the timezone, but PostgreSQL was overlooked.

## Test plan
- [x] Verified fix resolves the error when creating sessions with PostgreSQL + asyncpg
- [ ] Existing unit tests pass

Fixes regression from #1733

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4365 from filipecaixeta:fix-postgresql-timestamp-timezone 9d788ba99e7167a53962d93e59a80f78af091ca9
PiperOrigin-RevId: 867800330
2026-02-09 15:27:42 -08:00
Kathy Wu 8d0279251c feat: Add SkillToolset to adk
Currently supports load skill and load skill resource, scripts support coming later.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 867756231
2026-02-09 13:46:49 -08:00
George Weale f50847460f fix: Per-session locking and row-level locking in DatabaseSessionService.append_event
This change introduces an in-process `asyncio.Lock` per session to serialize `append_event` calls for the same session ID within a single process. For supported database dialects (MySQL, PostgreSQL, MariaDB), it also uses `SELECT ... FOR UPDATE` to acquire row-level locks on the session, app state, and user state records, preventing race conditions across different processes or database connections. A new test case verifies that concurrent updates to stale session objects correctly merge all state changes.

Close #1049

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 867752676
2026-02-09 13:38:38 -08:00
Benson Wang 32ee07df01 fix: prompt token may be None in streaming mode
Merge https://github.com/google/adk-python/pull/3462

**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.**

### Link to Issue or Description of Change

**1. Link to an existing issue (if applicable):**

- Closes: #_issue_number_
- Related: #_issue_number_

**2. Or, if no issue exists, describe the change:**

**Problem:**
When using adk in streaming mode, `usage_metadata.prompt_token_count` may be `None` which will emit log
```Invalid type NoneType for attribute 'gen_ai.usage.input_tokens' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types```

**Solution:**
Skip setting span attribute if prompt token count is None

**Unit Tests:**

- [x] All unit tests pass locally.

_Please include a summary of passed `pytest` results._

### Checklist

- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] I have manually tested my changes end-to-end.
- [x] Any dependent changes have been merged and published in downstream modules.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3462 from wsa-2002:prompt-token-count-may-be-none-in-streaming-mode 94666862f70ed2577d5c55485e67f6da36a57bc6
PiperOrigin-RevId: 867693355
2026-02-09 11:24:22 -08:00
Liang Wu 43c437e38b feat(conformance): add report generation to adk conformance test command
Added `generate_report` and `report_dir` CLI args to the command.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 867689055
2026-02-09 11:15:11 -08:00
George Weale d2dba27134 fix: Pass invocation_id from /run endpoint to Runner.run_async
Close #3290

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 867684592
2026-02-09 11:15:00 -08:00
George Weale 663cb75b32 fix: Conditionally preserve function call IDs in LLM requests
Function call and response IDs generated by ADK are now preserved in the LLM request contents when the agent is using a Gemini model with `use_interactions_api` enabled

Close #4381

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 867675945
2026-02-09 10:46:55 -08:00
George Weale 64a44c2897 fix: Migrate VertexAiMemoryBankService to use the async Vertex AI client
This change updates the VertexAiMemoryBankService to utilize the asynchronous interface provided by `vertexai.Client().aio`. This involves:
-   Retrieving the async client via `_get_api_client().aio`.
-   Awaiting calls to `generate` and `retrieve`.
-   Using `async for` to iterate over the results of the `retrieve` method, as it now returns an async iterator

Close #4386

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 867675311
2026-02-09 10:45:15 -08:00
George Weale 0758f877b1 chore: remove bare excepts
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 867666149
2026-02-09 10:25:03 -08:00
George Weale fd8a9e3962 fix: Handle list values in Gemini schema sanitization
The schema sanitization utility now recursively processes list items, ensuring that properties with list values (e.g., "required") are correctly handled and not altered.

Close #4363

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 867663267
2026-02-09 10:20:08 -08:00
Dinesh Thumma 6bc70a6bab fix(mcp): used logger to log instead of print
Merge https://github.com/google/adk-python/pull/4324

**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.**

### Link to Issue or Description of Change

**1. Link to an existing issue (if applicable):**

- Closes: #4320

**2. Or, if no issue exists, describe the change:**

**Problem:**
 Was using print instead of logger for warning message

**Solution:*
Refactored

### Testing Plan

**Unit Tests:**

- [x] I have added or updated unit tests for my change.
- [x] All unit tests pass locally.

_Please include a summary of passed `pytest` results._

**Manual End-to-End (E2E) Tests:**

### Checklist

- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] I have manually tested my changes end-to-end.
- [ ] Any dependent changes have been merged and published in downstream modules.

### Additional context

_Add any other context or screenshots about the feature request here._

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4324 from DineshThumma9:logger_in_mcp_session_clean 5eaa9bdc8c3c6697e75b0e876f043d496dd8ee95
PiperOrigin-RevId: 867601662
2026-02-09 07:48:36 -08:00
Wei Sun (Jack) a2e43aaf19 chore(version): Limits sphinx under 9 for docs
Co-authored-by: Wei Sun (Jack) <weisun@google.com>
PiperOrigin-RevId: 866569793
2026-02-06 12:34:07 -08:00
Kathy Wu e25227da5e feat: Add a load MCP resource tool
If the user specifies use_mcp_resources=True in their MCPToolset, the agent will be able to load resources with the load_mcp_resource_tool.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 866539602
2026-02-06 11:18:32 -08:00
Kathy Wu c7362100eb feat: Add models.py and prompt.py to adk/skills to use in skill toolset
Also redefined schemas in models.py to be pydantic.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 866270203
2026-02-05 22:06:58 -08:00
Google Team Member 483c5bab94 ADK changes
PiperOrigin-RevId: 866173091
2026-02-05 16:55:06 -08:00
Google Team Member 0b9cbd2d42 feat: Add experimental agent tool simulator
PiperOrigin-RevId: 866160711
2026-02-05 16:21:48 -08:00
Matthew Chan 781f605a1e feat: add base_url option to Gemini LLM class
PiperOrigin-RevId: 866146209
2026-02-05 15:44:00 -08:00
Kacper Jawoszek d9e8e9cf32 fix: otel_to_cloud flag help text points at Cloud Run instead of Agent Engine
Co-authored-by: Kacper Jawoszek <jawoszek@google.com>
PiperOrigin-RevId: 866122815
2026-02-05 14:49:02 -08:00
107 changed files with 5867 additions and 409 deletions
@@ -24,7 +24,7 @@ jobs:
python-version: '3.11'
- name: Load adk-bot SSH Private Key
uses: webfactory/ssh-agent@v0.9.0
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.ADK_BOT_SSH_PRIVATE_KEY }}
+3 -3
View File
@@ -15,17 +15,17 @@ jobs:
python-version: ['3.10', '3.11', '3.12', '3.13',]
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
- name: Generate Baseline (Main)
run: |
+3 -3
View File
@@ -14,13 +14,13 @@ jobs:
python-version: ['3.10', '3.11', '3.12', '3.13',]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v1
uses: astral-sh/setup-uv@v7
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v7
- name: Install dependencies
run: |
+33
View File
@@ -119,6 +119,39 @@ type.
1. Set `CREDENTIALS_TYPE=AuthCredentialTypes.OAUTH2` in `agent.py` and run the agent
### With Agent Engine and Gemini Enterprise
This mode is useful when you deploy the agent to Vertex AI Agent Engine and
want to make it available in Gemini Enterprise, allowing the agent to access
BigQuery on behalf of the end-user. This setup uses OAuth 2.0 managed by
Gemini Enterprise.
1. Create an Authorization resource in Gemini Enterprise by following the guide at
[Register and manage ADK agents hosted on Vertex AI Agent Engine](https://docs.cloud.google.com/gemini/enterprise/docs/register-and-manage-an-adk-agent) to:
* Create OAuth 2.0 credentials in your Google Cloud project.
* Create an Authorization resource in Gemini Enterprise, linking it to your
OAuth 2.0 credentials. When creating this resource, you will define a
unique identifier (`AUTH_ID`).
2. Prepare the sample agent for consuming the access token provided by Gemini
Enterprise and deploy to Vertex AI Agent Engine.
* Set `CREDENTIALS_TYPE=AuthCredentialTypes.HTTP` in `agent.py`. This
configures the agent to use access tokens provided by Gemini Enterprise and
provided by Agent Engine via the tool context.
* Replace `AUTH_ID` in `agent.py` with your authorization resource identifier
from step 1.
* [Deploy your agent to Vertex AI Agent Engine](https://google.github.io/adk-docs/deploy/agent-engine/).
3. [Register your deployed agent with Gemini Enterprise](https://docs.cloud.google.com/gemini/enterprise/docs/register-and-manage-an-adk-agent#register-an-adk-agent), attaching the
Authorization resource `AUTH_ID`. When this agent is invoked through Gemini
Enterprise, an access token obtained using these OAuth credentials will be
passed to the agent and made available in the ADK `tool_context` under the key
`AUTH_ID`, which `agent.py` is configured to use.
Once registered, users interacting with your agent via Gemini Enterprise will
go through an OAuth consent flow, and Agent Engine will provide the agent with
the necessary access tokens to call BigQuery APIs on their behalf.
## Sample prompts
* which weather datasets exist in bigquery public data?
+11
View File
@@ -56,6 +56,17 @@ elif CREDENTIALS_TYPE == AuthCredentialTypes.SERVICE_ACCOUNT:
# https://cloud.google.com/iam/docs/service-account-creds#user-managed-keys
creds, _ = google.auth.load_credentials_from_file("service_account_key.json")
credentials_config = BigQueryCredentialsConfig(credentials=creds)
elif CREDENTIALS_TYPE == AuthCredentialTypes.HTTP:
# Initialize the tools to use the externally provided access token. One such
# use case is creating an authorization resource `AUTH_ID` in Gemini
# Enterprise and using it to register an ADK agent deployed to Vertex AI
# Agent Engine with Gemini Enterprise. See for more details:
# https://docs.cloud.google.com/gemini/enterprise/docs/register-and-manage-an-adk-agent.
# This access token will be passed to the agent via the tool context, with
# the key `AUTH_ID`.
credentials_config = BigQueryCredentialsConfig(
external_access_token_key="AUTH_ID"
)
else:
# Initialize the tools to use the application default credentials.
# https://cloud.google.com/docs/authentication/provide-credentials-adc
+1 -1
View File
@@ -159,7 +159,7 @@ def _adk_agent(
class _UserAgent(base_agent.BaseAgent):
"""An agent that wraps the provided environment and simulates an user."""
"""An agent that wraps the provided environment and simulates a user."""
env: Env
+1 -1
View File
@@ -103,7 +103,7 @@ class _ADKAgent(tool_calling_agent.ToolCallingAgent):
max_num_steps: The maximum number of steps to run the agent.
Returns:
The result of the solve.
The result of the solve function.
Raises:
- ValueError: If the LLM inference failed.
@@ -52,6 +52,7 @@ Allowed directory: {_allowed_path}
'get_file_info',
'list_allowed_directories',
],
use_mcp_resources=True,
)
],
)
@@ -13,6 +13,7 @@
# limitations under the License.
import asyncio
import json
import os
from pathlib import Path
import sys
@@ -45,6 +46,24 @@ def get_cwd() -> str:
return str(Path.cwd())
# Add a resource for testing with JSON data
@mcp.resource(
name="sample_data",
uri="file:///sample_data.json",
mime_type="application/json",
)
def sample_data() -> str:
data = {
"users": [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"},
{"id": 3, "name": "Charlie", "role": "user"},
],
"settings": {"theme": "dark", "notifications": True},
}
return json.dumps(data, indent=2)
# Graceful shutdown handler
async def shutdown(signal, loop):
"""Cleanup tasks tied to the service's shutdown."""
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,54 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Example agent demonstrating the use of SkillToolset."""
import inspect
from google.adk import Agent
from google.adk.skills import models
from google.adk.tools import skill_toolset
greeting_skill = models.Skill(
frontmatter=models.Frontmatter(
name="greeting-skill",
description=(
"A friendly greeting skill that can say hello to a specific person."
),
),
instructions=(
"Step 1: Read the 'references/hello_world.txt' file to understand how"
" to greet the user. Step 2: Return a greeting based on the reference."
),
resources=models.Resources(
references={
"hello_world.txt": "Hello! 👋👋👋 So glad to have you here! ✨✨✨",
"example.md": "This is an example reference.",
},
),
)
my_skill_toolset = skill_toolset.SkillToolset(skills=[greeting_skill])
root_agent = Agent(
model="gemini-2.5-flash",
name="skill_user_agent",
description="An agent that can use specialized skills.",
instruction=(
"You are a helpful assistant that can leverage skills to perform tasks."
),
tools=[
my_skill_toolset,
],
)
+1 -1
View File
@@ -143,7 +143,7 @@ docs = [
"autodoc_pydantic",
"furo",
"myst-parser",
"sphinx",
"sphinx<9.0.0",
"sphinx-autodoc-typehints",
"sphinx-rtd-theme",
]
+32
View File
@@ -14,6 +14,8 @@
from __future__ import annotations
from collections.abc import Mapping
from collections.abc import Sequence
from typing import Any
from typing import Optional
from typing import TYPE_CHECKING
@@ -28,6 +30,7 @@ if TYPE_CHECKING:
from ..artifacts.base_artifact_service import ArtifactVersion
from ..auth.auth_credential import AuthCredential
from ..auth.auth_tool import AuthConfig
from ..events.event import Event
from ..events.event_actions import EventActions
from ..sessions.state import State
from .invocation_context import InvocationContext
@@ -219,3 +222,32 @@ class CallbackContext(ReadonlyContext):
await self._invocation_context.memory_service.add_session_to_memory(
self._invocation_context.session
)
async def add_events_to_memory(
self,
*,
events: Sequence[Event],
custom_metadata: Mapping[str, object] | None = None,
) -> None:
"""Adds an explicit list of events to the memory service.
Uses this callback's current session identifiers as memory scope.
Args:
events: Explicit events to add to memory.
custom_metadata: Optional standard metadata for memory generation.
Raises:
ValueError: If memory service is not available.
"""
if self._invocation_context.memory_service is None:
raise ValueError(
"Cannot add events to memory: memory service is not available."
)
await self._invocation_context.memory_service.add_events_to_memory(
app_name=self._invocation_context.session.app_name,
user_id=self._invocation_context.session.user_id,
session_id=self._invocation_context.session.id,
events=events,
custom_metadata=custom_metadata,
)
+2 -2
View File
@@ -469,7 +469,7 @@ class LlmAgent(BaseAgent):
self.__maybe_save_output_to_state(event)
yield event
if ctx.should_pause_invocation(event):
# Do not pause immediately, wait until the long running tool call is
# Do not pause immediately, wait until the long-running tool call is
# executed.
should_pause = True
if should_pause:
@@ -479,7 +479,7 @@ class LlmAgent(BaseAgent):
events = ctx._get_events(current_invocation=True, current_branch=True)
if events and any(ctx.should_pause_invocation(e) for e in events[-2:]):
return
# Only yield an end state if the last event is no longer a long running
# Only yield an end state if the last event is no longer a long-running
# tool call.
ctx.set_agent_state(self.name, end_of_agent=True)
yield self._create_agent_state_event(ctx)
+27
View File
@@ -80,6 +80,33 @@ class EventsCompactionConfig(BaseModel):
end of the last compacted range. This creates an overlap between consecutive
compacted summaries, maintaining context."""
token_threshold: Optional[int] = Field(
default=None,
gt=0,
)
"""Post-invocation token threshold trigger.
If set, ADK will attempt a post-invocation compaction when the most recently
observed prompt token count meets or exceeds this threshold.
"""
event_retention_size: Optional[int] = Field(default=None, ge=0)
"""Post-invocation raw event retention size.
If token-based post-invocation compaction is triggered, this keeps the last N
raw events un-compacted.
"""
@model_validator(mode="after")
def _validate_token_params(self) -> EventsCompactionConfig:
token_threshold_set = self.token_threshold is not None
retention_size_set = self.event_retention_size is not None
if token_threshold_set != retention_size_set:
raise ValueError(
"token_threshold and event_retention_size must be set together."
)
return self
class App(BaseModel):
"""Represents an LLM-backed agentic application.
+238 -4
View File
@@ -16,14 +16,236 @@ from __future__ import annotations
import logging
from google.adk.apps.app import App
from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
from google.adk.sessions.base_session_service import BaseSessionService
from google.adk.sessions.session import Session
from ..events.event import Event
from ..sessions.base_session_service import BaseSessionService
from ..sessions.session import Session
from .app import App
from .llm_event_summarizer import LlmEventSummarizer
logger = logging.getLogger('google_adk.' + __name__)
def _count_text_chars_in_event(event: Event) -> int:
"""Returns the number of text characters in an event's content."""
total_chars = 0
if event.content and event.content.parts:
for part in event.content.parts:
if part.text:
total_chars += len(part.text)
return total_chars
def _is_compaction_subsumed(
*,
start_timestamp: float,
end_timestamp: float,
event_index: int,
compactions: list[tuple[int, float, float, Event]],
) -> bool:
"""Returns True if a compaction range is fully contained by another.
If two compactions have identical ranges, the earlier event is treated as
subsumed by the later event.
"""
for other_index, other_start, other_end, _ in compactions:
if other_index == event_index:
continue
if other_start <= start_timestamp and other_end >= end_timestamp:
if (
other_start < start_timestamp
or other_end > end_timestamp
or other_index > event_index
):
return True
return False
def _estimate_prompt_token_count(events: list[Event]) -> int | None:
"""Returns an approximate prompt token count from session events.
This estimate is compaction-aware: it counts compaction summaries and only
counts raw events that would remain visible after applying compaction ranges.
"""
compactions: list[tuple[int, float, float, Event]] = []
for i, event in enumerate(events):
if not (event.actions and event.actions.compaction):
continue
compaction = event.actions.compaction
if (
compaction.start_timestamp is None
or compaction.end_timestamp is None
or compaction.compacted_content is None
):
continue
compactions.append((
i,
compaction.start_timestamp,
compaction.end_timestamp,
Event(
timestamp=compaction.end_timestamp,
author='model',
content=compaction.compacted_content,
branch=event.branch,
invocation_id=event.invocation_id,
actions=event.actions,
),
))
effective_compactions = [
(i, start, end, summary_event)
for i, start, end, summary_event in compactions
if not _is_compaction_subsumed(
start_timestamp=start,
end_timestamp=end,
event_index=i,
compactions=compactions,
)
]
compaction_ranges = [
(start, end) for _, start, end, _ in effective_compactions
]
def _is_timestamp_compacted(ts: float) -> bool:
for start_ts, end_ts in compaction_ranges:
if start_ts <= ts <= end_ts:
return True
return False
total_chars = 0
for _, _, _, summary_event in effective_compactions:
total_chars += _count_text_chars_in_event(summary_event)
for event in events:
if event.actions and event.actions.compaction:
continue
if _is_timestamp_compacted(event.timestamp):
continue
total_chars += _count_text_chars_in_event(event)
if total_chars <= 0:
return None
# Rough estimate: 4 characters per token.
return total_chars // 4
def _latest_prompt_token_count(events: list[Event]) -> int | None:
"""Returns the most recently observed prompt token count, if available."""
for event in reversed(events):
if (
event.usage_metadata
and event.usage_metadata.prompt_token_count is not None
):
return event.usage_metadata.prompt_token_count
return _estimate_prompt_token_count(events)
def _latest_compaction_event(events: list[Event]) -> Event | None:
"""Returns the compaction event with the greatest covered end timestamp."""
latest_event = None
latest_end = 0.0
for event in events:
if (
event.actions
and event.actions.compaction
and event.actions.compaction.end_timestamp is not None
):
end_ts = event.actions.compaction.end_timestamp
if end_ts is not None and end_ts >= latest_end:
latest_end = end_ts
latest_event = event
return latest_event
def _latest_compaction_end_timestamp(events: list[Event]) -> float:
"""Returns the end timestamp of the most recent compaction event."""
latest_event = _latest_compaction_event(events)
if not latest_event or not latest_event.actions.compaction:
return 0.0
if latest_event.actions.compaction.end_timestamp is None:
return 0.0
return latest_event.actions.compaction.end_timestamp
async def _run_compaction_for_token_threshold(
app: App, session: Session, session_service: BaseSessionService
):
"""Runs post-invocation compaction based on a token threshold.
If triggered, this compacts older raw events and keeps the last
`event_retention_size` raw events un-compacted.
"""
config = app.events_compaction_config
if not config:
return False
if config.token_threshold is None or config.event_retention_size is None:
return False
prompt_token_count = _latest_prompt_token_count(session.events)
if prompt_token_count is None or prompt_token_count < config.token_threshold:
return False
latest_compaction_event = _latest_compaction_event(session.events)
last_compacted_end_timestamp = 0.0
if (
latest_compaction_event
and latest_compaction_event.actions
and latest_compaction_event.actions.compaction
and latest_compaction_event.actions.compaction.end_timestamp is not None
):
last_compacted_end_timestamp = (
latest_compaction_event.actions.compaction.end_timestamp
)
candidate_events = [
e
for e in session.events
if not (e.actions and e.actions.compaction)
and e.timestamp > last_compacted_end_timestamp
]
if len(candidate_events) <= config.event_retention_size:
return False
if config.event_retention_size == 0:
events_to_compact = candidate_events
else:
events_to_compact = candidate_events[: -config.event_retention_size]
if not events_to_compact:
return False
# Rolling summary: if a previous compaction exists, seed the next summary with
# the previous compaction summary content so new compactions can subsume older
# ones while still keeping `event_retention_size` raw events visible.
if (
latest_compaction_event
and latest_compaction_event.actions
and latest_compaction_event.actions.compaction
and latest_compaction_event.actions.compaction.start_timestamp is not None
and latest_compaction_event.actions.compaction.compacted_content
is not None
):
seed_event = Event(
timestamp=latest_compaction_event.actions.compaction.start_timestamp,
author='model',
content=latest_compaction_event.actions.compaction.compacted_content,
branch=latest_compaction_event.branch,
invocation_id=Event.new_id(),
)
events_to_compact = [seed_event] + events_to_compact
if not config.summarizer:
config.summarizer = LlmEventSummarizer(llm=app.root_agent.canonical_model)
compaction_event = await config.summarizer.maybe_summarize_events(
events=events_to_compact
)
if compaction_event:
await session_service.append_event(session=session, event=compaction_event)
logger.debug('Token-threshold event compactor finished.')
return True
return False
async def _run_compaction_for_sliding_window(
app: App, session: Session, session_service: BaseSessionService
):
@@ -109,6 +331,18 @@ async def _run_compaction_for_sliding_window(
events = session.events
if not events:
return None
# Prefer token-threshold compaction if configured and triggered.
if (
app.events_compaction_config
and app.events_compaction_config.token_threshold is not None
):
token_compacted = await _run_compaction_for_token_threshold(
app, session, session_service
)
if token_compacted:
return None
# Find the last compaction event and its range.
last_compacted_end_timestamp = 0.0
for event in reversed(events):
+1 -7
View File
@@ -15,7 +15,6 @@
from __future__ import annotations
from typing import AsyncGenerator
from typing import TYPE_CHECKING
from typing_extensions import override
@@ -30,9 +29,6 @@ from .auth_handler import AuthHandler
from .auth_tool import AuthConfig
from .auth_tool import AuthToolArguments
if TYPE_CHECKING:
from ..agents.llm_agent import LlmAgent
# Prefix used by toolset auth credential IDs.
# Auth requests with this prefix are for toolset authentication (before tool
# listing) and don't require resuming a function call.
@@ -46,10 +42,8 @@ class _AuthLlmRequestProcessor(BaseLlmRequestProcessor):
async def run_async(
self, invocation_context: InvocationContext, llm_request: LlmRequest
) -> AsyncGenerator[Event, None]:
from ..agents.llm_agent import LlmAgent
agent = invocation_context.agent
if not isinstance(agent, LlmAgent):
if not hasattr(agent, 'canonical_tools'):
return
events = invocation_context.session.events
if not events:
+61
View File
@@ -20,6 +20,7 @@ import importlib
import json
import logging
import os
import sys
import time
import traceback
import typing
@@ -88,6 +89,7 @@ from ..runners import Runner
from ..sessions.base_session_service import BaseSessionService
from ..sessions.session import Session
from ..utils.context_utils import Aclosing
from ..version import __version__
from .cli_eval import EVAL_SESSION_ID_PREFIX
from .utils import cleanup
from .utils import common
@@ -492,6 +494,7 @@ class AdkWebServer:
logo_text: Optional[str] = None,
logo_image_url: Optional[str] = None,
url_prefix: Optional[str] = None,
auto_create_session: bool = False,
):
self.agent_loader = agent_loader
self.session_service = session_service
@@ -509,6 +512,7 @@ class AdkWebServer:
self.current_app_name_ref: SharedValue[str] = SharedValue(value="")
self.runner_dict = {}
self.url_prefix = url_prefix
self.auto_create_session = auto_create_session
async def get_runner_async(self, app_name: str) -> Runner:
"""Returns the cached runner for the given app."""
@@ -558,6 +562,7 @@ class AdkWebServer:
session_service=self.session_service,
memory_service=self.memory_service,
credential_service=self.credential_service,
auto_create_session=self.auto_create_session,
)
def _instantiate_extra_plugins(self) -> list[BasePlugin]:
@@ -757,6 +762,18 @@ class AdkWebServer:
allow_headers=["*"],
)
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok"}
@app.get("/version")
async def version() -> dict[str, str]:
return {
"version": __version__,
"language": "python",
"language_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
}
@app.get("/list-apps")
async def list_apps(
detailed: bool = Query(
@@ -1347,6 +1364,24 @@ class AdkWebServer:
raise HTTPException(status_code=404, detail="Artifact not found")
return artifact
@app.get(
"/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name}/versions/metadata",
response_model=list[ArtifactVersion],
response_model_exclude_none=True,
)
async def list_artifact_versions_metadata(
app_name: str,
user_id: str,
session_id: str,
artifact_name: str,
) -> list[ArtifactVersion]:
return await self.artifact_service.list_artifact_versions(
app_name=app_name,
user_id=user_id,
session_id=session_id,
filename=artifact_name,
)
@app.get(
"/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name}/versions/{version_id}",
response_model_exclude_none=True,
@@ -1416,6 +1451,31 @@ class AdkWebServer:
)
return artifact_version
@app.get(
"/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name}/versions/{version_id}/metadata",
response_model=ArtifactVersion,
response_model_exclude_none=True,
)
async def get_artifact_version_metadata(
app_name: str,
user_id: str,
session_id: str,
artifact_name: str,
version_id: int,
) -> ArtifactVersion:
artifact_version = await self.artifact_service.get_artifact_version(
app_name=app_name,
user_id=user_id,
session_id=session_id,
filename=artifact_name,
version=version_id,
)
if not artifact_version:
raise HTTPException(
status_code=404, detail="Artifact version not found"
)
return artifact_version
@app.get(
"/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts",
response_model_exclude_none=True,
@@ -1503,6 +1563,7 @@ class AdkWebServer:
session_id=req.session_id,
new_message=req.new_message,
state_delta=req.state_delta,
invocation_id=req.invocation_id,
)
) as agen:
events = [event async for event in agen]
+59 -5
View File
@@ -294,11 +294,28 @@ def cli_conformance_record(
" runs evaluation-based verification."
),
)
@click.option(
"--generate_report",
is_flag=True,
show_default=True,
default=False,
help="Optional. Whether to generate a Markdown report of the test results.",
)
@click.option(
"--report_dir",
type=click.Path(file_okay=False, dir_okay=True, resolve_path=True),
help=(
"Optional. Directory to store the generated report. Defaults to current"
" directory."
),
)
@click.pass_context
def cli_conformance_test(
ctx,
paths: tuple[str, ...],
mode: str,
generate_report: bool,
report_dir: Optional[str] = None,
):
"""Run conformance tests to verify agent behavior consistency.
@@ -309,7 +326,7 @@ def cli_conformance_test(
- Contain a spec.yaml file directly (single test case)
- Contain subdirectories with spec.yaml files (multiple test cases)
If no paths are provided, defaults to searching the 'tests' folder.
If no paths are provided, defaults to searching for the 'tests' folder.
TEST MODES:
@@ -329,6 +346,11 @@ def cli_conformance_test(
generated-recordings.yaml # Recorded interactions (replay mode)
generated-session.yaml # Session data (replay mode)
REPORT GENERATION:
Use --generate_report to create a Markdown report of test results.
Use --report_dir to specify where the report should be saved.
EXAMPLES:
\b
@@ -346,6 +368,14 @@ def cli_conformance_test(
\b
# Run in live mode (when available)
adk conformance test --mode=live tests/core
\b
# Generate a test report
adk conformance test --generate_report
\b
# Generate a test report in a specific directory
adk conformance test --generate_report --report_dir=reports
"""
try:
@@ -363,10 +393,18 @@ def cli_conformance_test(
)
ctx.exit(1)
# Convert to Path objects, use default if empty (paths are already resolved by Click)
# Convert to Path objects, use default if empty (paths are already resolved
# by Click)
test_paths = [Path(p) for p in paths] if paths else [Path("tests").resolve()]
asyncio.run(run_conformance_test(test_paths=test_paths, mode=mode.lower()))
asyncio.run(
run_conformance_test(
test_paths=test_paths,
mode=mode.lower(),
generate_report=generate_report,
report_dir=report_dir,
)
)
@main.command("create", cls=HelpfulCommand)
@@ -1381,6 +1419,14 @@ def cli_web(
@fast_api_common_options()
@adk_services_options(default_use_local_storage=True)
@deprecated_adk_services_options()
@click.option(
"--auto_create_session",
is_flag=True,
default=False,
help=(
"Automatically create a session if it doesn't exist when calling /run."
),
)
def cli_api_server(
agents_dir: str,
eval_storage_uri: Optional[str] = None,
@@ -1401,6 +1447,7 @@ def cli_api_server(
a2a: bool = False,
reload_agents: bool = False,
extra_plugins: Optional[list[str]] = None,
auto_create_session: bool = False,
):
"""Starts a FastAPI server for agents.
@@ -1433,6 +1480,7 @@ def cli_api_server(
url_prefix=url_prefix,
reload_agents=reload_agents,
extra_plugins=extra_plugins,
auto_create_session=auto_create_session,
),
host=host,
port=port,
@@ -1494,14 +1542,20 @@ def cli_api_server(
is_flag=True,
show_default=True,
default=False,
help="Optional. Whether to enable Cloud Trace for cloud run.",
help=(
"Optional. Whether to enable Cloud Trace export for Cloud Run"
" deployments."
),
)
@click.option(
"--otel_to_cloud",
is_flag=True,
show_default=True,
default=False,
help="Optional. Whether to enable OpenTelemetry for Agent Engine.",
help=(
"Optional. Whether to enable OpenTelemetry export to GCP for Cloud Run"
" deployments."
),
)
@click.option(
"--with_ui",

Some files were not shown because too many files have changed in this diff Show More