Skip to content

Commit 7955df9

Browse files
committed
feat(memory): add DatabaseMemoryService with SQLAlchemy async backend
Adds a durable, SQL-backed memory service that works with any SQLAlchemy-compatible async database (SQLite, PostgreSQL, MySQL/MariaDB). This fills the gap between the volatile InMemoryMemoryService and the cloud-only Firestore/Vertex AI options, giving self-hosted deployments a persistent memory backend with zero cloud dependencies. The implementation mirrors the keyword-extraction approach used by FirestoreMemoryService and reuses the existing SQLAlchemy patterns established by DatabaseSessionService. Closes #2524 Closes #2976
1 parent 454188d commit 7955df9

4 files changed

Lines changed: 907 additions & 0 deletions

File tree

src/google/adk/memory/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,26 @@
2121

2222
__all__ = [
2323
'BaseMemoryService',
24+
'DatabaseMemoryService',
2425
'InMemoryMemoryService',
2526
'VertexAiMemoryBankService',
2627
]
2728

29+
30+
def __getattr__(name: str):
31+
if name == 'DatabaseMemoryService':
32+
try:
33+
from .database_memory_service import DatabaseMemoryService
34+
35+
return DatabaseMemoryService
36+
except ImportError as e:
37+
raise ImportError(
38+
'DatabaseMemoryService requires sqlalchemy>=2.0, please ensure it is'
39+
' installed correctly.'
40+
) from e
41+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
42+
43+
2844
try:
2945
from .vertex_ai_rag_memory_service import VertexAiRagMemoryService
3046

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""SQLAlchemy ORM models for DatabaseMemoryService."""
16+
17+
from __future__ import annotations
18+
19+
from typing import Any
20+
from typing import Optional
21+
22+
from sqlalchemy import Float
23+
from sqlalchemy import Index
24+
from sqlalchemy import Text
25+
from sqlalchemy.orm import DeclarativeBase
26+
from sqlalchemy.orm import Mapped
27+
from sqlalchemy.orm import mapped_column
28+
from sqlalchemy.types import String
29+
30+
from ..sessions.schemas.shared import DynamicJSON
31+
32+
_MAX_KEY_LENGTH = 128
33+
34+
35+
class Base(DeclarativeBase):
36+
"""Base class for memory database tables."""
37+
38+
pass
39+
40+
41+
class StorageMemoryEntry(Base):
42+
"""Represents a single memory entry stored in the database."""
43+
44+
__tablename__ = "adk_memory_entries"
45+
46+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
47+
48+
app_name: Mapped[str] = mapped_column(String(_MAX_KEY_LENGTH), index=True)
49+
user_id: Mapped[str] = mapped_column(String(_MAX_KEY_LENGTH), index=True)
50+
51+
keywords: Mapped[str] = mapped_column(Text)
52+
53+
author: Mapped[Optional[str]] = mapped_column(
54+
String(_MAX_KEY_LENGTH), nullable=True
55+
)
56+
content: Mapped[dict[str, Any]] = mapped_column(DynamicJSON)
57+
timestamp: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
58+
59+
__table_args__ = (Index("idx_memory_app_user", "app_name", "user_id"),)
60+
61+
def __repr__(self) -> str:
62+
return (
63+
f"<StorageMemoryEntry(id={self.id}, app_name={self.app_name!r}, "
64+
f"user_id={self.user_id!r})>"
65+
)

0 commit comments

Comments
 (0)