Commit Graph
130 Commits
Author SHA1 Message Date
Liang WuandCopybara-Service 61c329f8ce ci: match the environment with internal ones for pyink/isort/unittest
Previously submitted change is causing [Pyink error](https://github.com/google/adk-python/actions/runs/21884351396/job/63176122118) in repo because Pyink recently updated to 25.12.0.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 868806575
2026-02-11 13:01:32 -08:00
Anmol JaiswalandCopybara-Service 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
Filipe CaixetaandCopybara-Service 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
George WealeandCopybara-Service 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
Liang WuandCopybara-Service e752bbb756 chore: Remove unused tzlocal dependency and logging
The `tzlocal` library and the logging of the local timezone were not used in the `DatabaseSessionService` logic.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 865677320
2026-02-04 18:49:55 -08:00
George WealeandCopybara-Service da73e718ef fix: Enable pool_pre_ping by default for non-SQLite database engines
This change sets `pool_pre_ping=True` in SQLAlchemy engine kwargs for database backends other than SQLite. This helps ensure that connections from the pool are still valid before being used, preventing issues with stale or disconnected connections. Tests are added to verify the default behavior and that explicit overrides are respected

Close #4211

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 864886767
2026-02-03 08:16:55 -08:00
George WealeandCopybara-Service 666cebe369 fix: Add update_timestamp_tz property to StorageSession
This property is a compatibility alias that returns the update timestamp as a POSIX timestamp. It infers whether the database is SQLite using sqlalchemy.inspect to call get_update_timestamp correctly

Close #4334

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 864595914
2026-02-02 17:39:42 -08:00
Liang WuandCopybara-Service 63a8eba53f fix: Ensure database sessions are always rolled back on errors
Fixes issue#3328

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 863314939
2026-01-30 11:10:02 -08:00
Shangjie ChenandCopybara-Service 1063fa532c fix: Reload the stale session in database_session_service when the update_time of storage is later than the current in memory session object
This changes the behavior of how we handle stale session, instead of reject the transaction entirely, we aggresively refresh the session.

Removed synchronous inspect(self).session calls within StorageSession.update_timestamp_tz and _dialect_name. This introspection was causing deadlocks/hangs when used with sqlalchemy.ext.asyncio in Python 3.13.

Closes issue: https://github.com/google/adk-python/issues/1733

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 861353058
2026-01-26 14:19:14 -08:00
Didier DurandandCopybara-Service 91ec80c606 docs: fixing multiple typos
Merge https://github.com/google/adk-python/pull/4221

### Link to Issue or Description of Change

N/A

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

Fixing various typos in .py files to improve quality: see commit diffs for details

**Problem:**

See above

**Solution:**
Fixing the typos

### Testing Plan

N/A

**Unit Tests:**

N/A

**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.
- [N/A] 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/4221 from didier-durand:fix-typos-e 1bcc8e05cdc116451222dd7fd7b0657745f769c1
PiperOrigin-RevId: 859332402
2026-01-21 17:33:51 -08:00
George WealeandCopybara-Service 2367901ec5 chore: Upgrade to headers to 2026
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 858763407
2026-01-20 14:50:09 -08:00
Liang WuandCopybara-Service 4b29d15b3e fix: Handle async driver URLs in migration tool
The migration tool uses synchronous SQLAlchemy engines but users often provide async driver URLs (e.g., postgresql+asyncpg://) since that's what ADK requires at runtime.

This fix:
- Makes `to_sync_url()` public in `_schema_check_utils.py` for reuse
- Updates `migrate_from_sqlalchemy_pickle.py` to convert async URLs
- Updates `migrate_from_sqlalchemy_sqlite.py` to convert async URLs
- Adds comprehensive unit tests for `to_sync_url()` function
- Adds integration test for migration with async driver URLs

Fixes #4176

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 858359061
2026-01-19 19:39:20 -08:00
Liang WuandCopybara-Service d0aef8a4fa docs: Update the migration guide for adding a new database session schema
Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 856882485
2026-01-15 17:13:08 -08:00
Liang WuandCopybara-Service 75231a30f1 fix: Handle NOT_FOUND error when fetching Vertex AI sessions
Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 855464349
2026-01-12 18:17:41 -08:00
Alexis MarasiganandCopybara-Service b725045e5a fix: fix httpx client closure during event pagination
Merge https://github.com/google/adk-python/pull/3756

move event iteration inside api_client context in get_session

Move event iteration inside the api_client context manager in VertexAiSessionService.get_session() to prevent client closure during multi-page event fetching.

**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: #3757

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

**Problem:**

When a session contains more than 100 events (requiring pagination), `VertexAiSessionService.get_session()` fails with:

```
RuntimeError: Cannot send a request, as the client has been closed.
```

The root cause is that the `events_iterator` is consumed **outside** the `async with self._get_api_client() as api_client:` context block. When the iterator needs to fetch page 2, 3, etc., the API client has already been closed because the `async with` block has exited.

```python
# Current buggy flow:
async with self._get_api_client() as api_client:
    get_session_response, events_iterator = await asyncio.gather(...)
# ← Client closed here

async for event in events_iterator:  # ← Fails on page 2+ (client closed)
    session.events.append(...)
```

**Solution:**

Move the session creation, user validation, and event iteration **inside** the `async with` block so the API client remains open during the entire pagination process:

```python
async with self._get_api_client() as api_client:
    get_session_response, events_iterator = await asyncio.gather(...)
    # Validation and session creation...
    async for event in events_iterator:  # ← Now works for all pages
        session.events.append(...)
# Client closed after all events are fetched
```

### Testing Plan

**Unit Tests:**

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

```bash
pytest tests/unittests/sessions/test_vertex_ai_session_service.py -v
```

**Added regression test:** `test_get_session_pagination_keeps_client_open`
- Creates a `MockAsyncClientWithPagination` that tracks whether it's inside the `async with` context
- Raises `RuntimeError` if iteration happens outside the context (matching real httpx behavior)
- Simulates 3 pages of events (100 + 100 + 50 = 250 events)
- Verifies all 250 events are successfully retrieved

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

1. Deploy an ADK agent to Vertex AI Agent Engine
2. Create a session and send 100+ messages to accumulate >100 events
3. Verify `get_session()` successfully retrieves all events without error

**Before fix:**
```
RuntimeError: Cannot send a request, as the client has been closed.
```

**After fix:**
- Session with 201 events (3 pages) loads successfully
- All events are retrieved and appended to the session

### 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 bug affects any production deployment where users have extended conversations. Sessions accumulating >100 events (which triggers pagination) become completely unusable as the agent cannot load the session to process new messages.

The fix is minimal and maintains backward compatibility - it only changes the scope of the `async with` block without altering any logic or return values.

**Affected versions:** Tested on google-adk 1.19.0, but the bug exists in earlier versions as well.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3756 from AlexisMarasigan:fix/vertex-ai-session-service-paginatio 01fbafa6524312f24f7c9feaffb07bff0ad49b77
PiperOrigin-RevId: 855451813
2026-01-12 17:27:29 -08:00
Dinesh ThummaandCopybara-Service 94d48fce32 fix: Database reserved keyword issue
The fix will use quotes to escape "key", which is column name in the metadata table. Should work for different database types.

Merge https://github.com/google/adk-python/pull/4106

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4106 from DineshThumma9:fix/mysql-reserved-keyword-issue e39d0d02f3695d6890bc3267417b5dad58f7e8ee
PiperOrigin-RevId: 854411915
2026-01-09 18:20:46 -08:00
George WealeandCopybara-Service b8917bc80e fix: Handle SQLite URLs in SqliteSessionService
The SqliteSessionService now accepts database paths in the form of SQLite URLs (e.g., "sqlite:///./sessions.db", "sqlite+aiosqlite:////absolute.db")

Close #4077

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 853922433
2026-01-08 16:14:07 -08:00
Liang WuandCopybara-Service ce64787c3e feat: Add database schema migration command and script
Final part of https://github.com/google/adk-python/discussions/3605.

This change introduces:
-   A new `adk migrate session` CLI command to run database schema upgrades.
-   A migration script to upgrade from the old Pickle-based session schema (v0) to the new JSON-based schema (v1).
-   A migration runner that orchestrates the upgrade process, handling sequential migrations and using temporary SQLite databases for intermediate steps if needed.
-   Unit tests for the v0 to v1 migration.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 852983323
2026-01-06 16:36:45 -08:00
George WealeandCopybara-Service 4ddb2cb2a8 chore: Close database engines to avoid aiosqlite pytest hangs
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 852428755
2026-01-05 13:18:08 -08:00
George WealeandCopybara-Service 838530ebe0 fix: rehydration of EventActions in StorageEvent.to_event
The change updates the `StorageEvent.to_event` method to use `EventActions.model_validate` when rehydrating the `actions` field. This ensures that nested models within `EventActions`, such as `EventCompaction`, are correctly reconstructed from the stored data

Close #4047

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 852408683
2026-01-05 12:26:10 -08:00
Liang WuandCopybara-Service ba91fea541 feat: Use new JSON-based schema for newly-created databases
Part 2 of https://github.com/google/adk-python/discussions/3605.

The DatabaseSessionService now checks for the usage of a V1 schema based on the "adk_internal_metadata" table. Table creation and subsequent operations use either the V0 or V1 SQLAlchemy models accordingly. New databases will default to V1.

Migration script and CLI command will be provided in the next change.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 845443406
2025-12-16 14:49:31 -08:00
Liang WuandCopybara-Service 7e6ef71eec feat: Introduce new database schema for DatabaseSessionService
Part 1 of https://github.com/google/adk-python/discussions/3605.

This change adds a new schema that uses JSON serialization to store Events data in the database. A new "adk_internal_metadata" table is also added to store information like schema version. Since we want to keep supporting existing DB, we fork from the original schema and call it "v0", while the new one is called "v1".

The change is no-op for existing users. In later change, the new schema will be used for new databases, and migration scripts will be provided for existing databases.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 844986248
2025-12-15 17:32:48 -08:00
Liang WuandCopybara-Service e8ab7dafa9 chore: Move SQLite migration script to migration/ folder
Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 844902789
2025-12-15 13:38:51 -08:00
Hiroaki SanoandCopybara-Service 8335f35015 fix: Change error_message column type to TEXT in DatabaseSessionService
Merge https://github.com/google/adk-python/pull/3917
To migrate from existing DB :

ALTER TABLE events ALTER COLUMN error_message TYPE TEXT;  -- PostgreSQL
ALTER TABLE events MODIFY error_message TEXT;             -- MySQL
SQLite: Doesn't enforce VARCHAR length limits anyway. No impact.
### 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:**

**Problem:**

When storing events with error messages longer than 1024 characters using `DatabaseSessionService`, PostgreSQL raises:

```
ERROR: value too long for type character varying(1024)
```

The `error_message` column in `StorageEvent` is defined as `String(1024)`, which maps to `VARCHAR(1024)`. Error messages can exceed 1024 characters.

**Solution:**

Change the column type from `String(1024)` to `Text` to allow unlimited length error messages.

### Testing Plan

**Unit Tests:**

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

$ pytest ./tests/unittests/sessions/ -v
======================= 75 passed, 3 warnings in 26.92s ========================

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

- Verified that events with long error messages (>1024 chars) can be stored in PostgreSQL
- Verified backward compatibility with existing databases

### 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 is a minimal change (1 line) that only affects the `error_message` column type definition.

Co-authored-by: Xiang (Sean) Zhou <seanzhougoogle@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3917 from hiroakis:main 1474fd552cdbd7206de383e5507fd8a733aecda1
PiperOrigin-RevId: 844845692
2025-12-15 11:09:25 -08:00
Shangjie ChenandCopybara-Service 9d918d45df feat!: Rollback the DB migration as it is breaking
Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 839818479
2025-12-03 10:39:43 -08:00