⚠️ 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
POST /v1/vector_stores to create a store
POST /v1/vector_stores/{id}/embeddings to add at least one embedding (this sets last_active_at)
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.
Description
GET /v1/vector_storesreturns a 500 Internal Server Error as soon as any vector store has a non-nulllast_active_at(i.e., after at least one embedding has been added to it).Error
Root cause
main.pycalls.timestamp()directly on theexpires_atandlast_active_atvalues returned bydb.query_raw(), assuming Prisma'squery_raw()returnsdatetimeobjects fortimestampcolumns. 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_atdoesn't hit this bug because it's computed as an epoch value directly in SQL viaEXTRACT(EPOCH FROM created_at)::bigint, whileexpires_at/last_active_atare passed through as raw column values.Steps to reproduce
POST /v1/vector_storesto create a storePOST /v1/vector_stores/{id}/embeddingsto add at least one embedding (this setslast_active_at)GET /v1/vector_stores→ 500 errorSuggested fix
Add a type-safe helper that accepts both
datetimeandstr, and use it in place of the direct.timestamp()calls:Then replace:
with:
in both
create_vector_storeandlist_vector_stores.Environment
mainbranch, Postgres 18 + pgvector,prisma-client-pygenerator.