Skip to content

feat(memory): add self-hosted PostgreSQL/pgvector memory service - #7274

Open
Nanduu24 wants to merge 1 commit into
google:mainfrom
Nanduu24:feat/pgvector-memory-service
Open

Nanduu24 wants to merge 1 commit into
google:mainfrom
Nanduu24:feat/pgvector-memory-service

Conversation

@Nanduu24

@Nanduu24 Nanduu24 commented Sep 25, 2026 •

Copy link
Copy Markdown

Link to Issue or Description of Change

Problem:

ADK's memory services do not cover persistent, semantic memory on
infrastructure the user controls. InMemoryMemoryService is keyword-only and
"for prototyping purpose only" (loses data on restart), while
VertexAiMemoryBankService and VertexAiRagMemoryService require Google Cloud.
Teams running on-premise, air-gapped, or under data-residency constraints have
no built-in option, and the pending SQLite memory service (#4116) provides
persistence but exact-match retrieval rather than semantic search.

Solution:

Add PgVectorMemoryService under google.adk.integrations.pgvector, a
BaseMemoryService backed by PostgreSQL + pgvector:

  • add_session_to_memory / add_events_to_memory embed each event's text and
    upsert it (idempotent per event) into a table scoped by (app_name, user_id).
  • add_memory supports direct writes of explicit MemoryEntry items.
  • search_memory embeds the query and returns the nearest memories by cosine
    distance using a pgvector HNSW index, with an optional distance_threshold.
  • Embeddings use the google-genai client by default; an injectable embedder
    makes the service provider-agnostic.
  • Ships as an optional extra: pip install "google-adk[pgvector]".

It follows the existing integrations/redis pattern (pydantic config plus an
injectable connection pool and embedder), so the store runs entirely on a
database the user owns and stays unit-testable without a live database. The
table, vector extension, and indexes are created on first use. Happy to align
the table layout with #4116 if a shared schema is preferred.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

New tests in tests/unittests/integrations/pgvector/ run against an in-process
fake connection pool and a deterministic bag-of-words embedder, so they exercise
ingestion, idempotent upserts, semantic ranking, per-(app_name, user_id)
scoping, the distance threshold, empty-query handling, skipping text-less
events, the direct-write path, serialization round-trips, and the
missing-driver / missing-dsn errors — all without psycopg, pgvector, or any
network access.

$ python -m pytest tests/unittests/integrations/pgvector -q
13 passed

The suite passes both with the optional driver installed and with it absent
(matching CI). Lint/format on the changed files: pyink and isort clean,
pylint 10.00/10.

Manual End-to-End (E2E) Tests:

Verified against a live PostgreSQL 17 + pgvector 0.8.6 database using the
real psycopg async driver (a deterministic local embedder stood in for the
embedding API). Confirmed: session ingestion, semantic ranking (a "billing"
query returns the billing turn; a "basketball" query returns the basketball
turn), cross-instance persistence (a fresh service reads the stored rows back),
(app_name, user_id) scoping, idempotent re-ingestion (row count stays at 3
after a double ingest), and the created schema (a vector(64) column and an
HNSW index). To reproduce:

docker run -d --name adk-pgvector -e POSTGRES_PASSWORD=pw -p 5432:5432 pgvector/pgvector:pg16
pip install "google-adk[pgvector]"
export GOOGLE_API_KEY=...   # for gemini-embedding-001
import asyncio
from google.adk.integrations.pgvector import (
    PgVectorMemoryService, PgVectorMemoryServiceConfig,
)
from google.adk.events.event import Event
from google.adk.sessions.session import Session
from google.genai import types

async def main():
    svc = PgVectorMemoryService(PgVectorMemoryServiceConfig(
        dsn="postgresql://postgres:pw@localhost:5432/postgres"))
    session = Session(app_name="demo", user_id="u1", id="s1", events=[
        Event(author="user", timestamp=1.0,
              content=types.Content(parts=[types.Part(
                  text="We decided to move billing to net-30 terms.")])),
    ])
    await svc.add_session_to_memory(session)
    res = await svc.search_memory(app_name="demo", user_id="u1",
                                  query="what did we decide about billing?")
    print([m.content.parts[0].text for m in res.memories])

asyncio.run(main())

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

Design mirrors the integrations/redis session service and the google-genai
embedding usage in tools/spanner and integrations/mongodb. The new optional
extra pgvector pins psycopg[binary], psycopg-pool, and pgvector, and is
included in the all extra.

Adds PgVectorMemoryService under google.adk.integrations.pgvector, a
BaseMemoryService backed by PostgreSQL with the pgvector extension. Events are
embedded (via the google-genai client by default, or an injectable embedder)
and stored with their vectors in a table the user owns; search_memory returns
the nearest memories by cosine distance using a pgvector HNSW index.

Unlike VertexAiMemoryBankService and VertexAiRagMemoryService it needs no Google
Cloud, and unlike InMemoryMemoryService it persists across restarts and ranks by
semantic similarity rather than keyword overlap. This fills the self-hosted,
non-GCP semantic-memory gap raised in google#6254 and complements the exact-match
SQLite memory service in google#4116.

The service follows the existing integrations/redis pattern: a pydantic config
plus an injectable connection pool and embedder, which keeps it unit-testable
without a live database. Ships as an optional extra, google-adk[pgvector].

Includes unit tests that exercise ingestion, idempotent upserts, semantic
ranking, per-(app_name, user_id) scoping, the distance threshold, and
serialization round-trips against an in-process fake pool and a deterministic
embedder, so they run without psycopg, pgvector, or any network access.

Resolves google#7273
@Nanduu24
Nanduu24 force-pushed the feat/pgvector-memory-service branch from 96f7abb to a04727e Compare September 25, 2026 03:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT: Add a self-hosted PostgreSQL/pgvector semantic MemoryService

2 participants