Skip to content

GET /v1/vector_stores crashes with AttributeError once last_active_at is set #12

Description

@SecMyth

⚠️ Note: This issue was drafted with AI assistance. Please verify the analysis and reproduction steps before acting on it.

Description

GET /v1/vector_stores returns a 500 Internal Server Error as soon as any vector store has a non-null last_active_at (i.e., after at least one embedding has been added to it).

Error

Traceback (most recent call last):
  File "/app/main.py", line 187, in list_vector_stores
    last_active_at = int(row["last_active_at"].timestamp()) if row.get("last_active_at") else None
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'timestamp'

Root cause

main.py calls .timestamp() directly on the expires_at and last_active_at values returned by db.query_raw(), assuming Prisma's query_raw() returns datetime objects for timestamp columns. In practice it returns ISO-8601 strings for these fields, so the .timestamp() call fails. This affects both:

  • create_vector_store (lines ~111–112)
  • list_vector_stores (lines ~186–187)

Notably, created_at doesn't hit this bug because it's computed as an epoch value directly in SQL via EXTRACT(EPOCH FROM created_at)::bigint, while expires_at/last_active_at are passed through as raw column values.

Steps to reproduce

  1. POST /v1/vector_stores to create a store
  2. POST /v1/vector_stores/{id}/embeddings to add at least one embedding (this sets last_active_at)
  3. GET /v1/vector_stores → 500 error

Suggested fix

Add a type-safe helper that accepts both datetime and str, and use it in place of the direct .timestamp() calls:

from datetime import datetime

def to_epoch_seconds(value):
    if value is None:
        return None
    if isinstance(value, datetime):
        return int(value.timestamp())
    if isinstance(value, str):
        return int(datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp())
    raise TypeError(f"Unsupported timestamp type: {type(value)!r}")

Then replace:

expires_at = int(row["expires_at"].timestamp()) if row.get("expires_at") else None
last_active_at = int(row["last_active_at"].timestamp()) if row.get("last_active_at") else None

with:

expires_at = to_epoch_seconds(row.get("expires_at"))
last_active_at = to_epoch_seconds(row.get("last_active_at"))

in both create_vector_store and list_vector_stores.

Environment

  • Reproduced against a local Docker build of main branch, Postgres 18 + pgvector, prisma-client-py generator.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions