mrkeyoor.com_
Sat 19 Sept 23:45 UTC
PyPIAI / MLupdated 19 Sept 2026

pgvector review

pgvector is the official Python adapter package for the pgvector PostgreSQL extension. It registers the extension's `vector`, `halfvec`, `bit`, and `sparsevec` types with database drivers and adds model fields, distance expressions, and index helpers for Django, SQLAlchemy, SQLModel, and Peewee. It does not install the server extension, connect to PostgreSQL, create embeddings, or provide a collection service. Version 0.5.0 removes the NumPy dependency, requires Python 3.10 and SQLAlchemy 2, adds experimental type hints, removes old utility re-exports, and changes results: ORM integrations return lists for vector and halfvec columns, while raw drivers return `Vector` objects for vector columns.

Verdict

pgvector 0.5.0 is small, typed adapter code for teams that have already chosen PostgreSQL plus the pgvector extension. Check the new Python, SQLAlchemy, import, and return-type boundaries before upgrading; the hard performance decisions remain in database indexing and query plans.

We installed it

Lab card: what happened when we installed pgvectorScreenshot of pgvector documentation
Install✓ · 0.4s1 package on disk · 1 MB
Importimport pgvector in 0.08s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does pgvector install cleanly?

Yes. In a fresh container with an empty cache, pip install pgvector finished in 0.4s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does pgvector need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import pgvector succeeded in 0.08s, and the package ships py.typed for type checkers.

pgvector or chromadb: which should you use?

chromadb: Use it for a local collection-oriented vector store with embedding integrations and less PostgreSQL setup. pgvector 0.5.0 is small, typed adapter code for teams that have already chosen PostgreSQL plus the pgvector extension.

When should you not use pgvector?

Your PostgreSQL host cannot install or enable the pgvector server extension. This Python wheel contains adapters only and cannot supply missing database types.

API stability3/5Driver registration, ORM fields, distance expressions, and index helpers remain easy to recognize, but 0.5.0 makes several application-visible breaks. It drops Python below 3.10 and SQLAlchemy below 2, removes `utils` and re-exported classes, removes NumPy as a dependency, and changes vector return values between ORM and raw-driver integrations. The package is still pre-1.0, so pin it and test deserialization types.
Docs4/5The repository README has concrete sections for Django, SQLAlchemy, SQLModel, Psycopg 3, Psycopg 2, asyncpg, pg8000, and Peewee. It covers distance operators, HNSW, IVFFlat, half precision, binary quantization, sparse vectors, arrays, pools, and COPY, with a large examples directory for retrieval and hybrid search. There is no separate generated API reference, and the version 0.5 return-type changes are easier to find in the change log than in each integration section.
Maintenance5/5Version 0.5.0 and the latest repository push both landed on July 6, 2026. GitHub reports 1,516 stars, four open issues and pull requests, and an unarchived repository. The release modernized Python and SQLAlchemy support, reduced the required dependency graph to zero, and started typed-package support. The small tracker and aligned extension ownership make maintenance signals unusually clear for a driver adapter.
Ecosystem5/5The supplied weekly count is 8,610,823 downloads. One package covers Django, SQLAlchemy, SQLModel, Psycopg 2 and 3, asyncpg, pg8000, and Peewee, while the server extension works inside PostgreSQL's joins, filters, transactions, backups, and access controls. The README also includes examples for RAG, hybrid retrieval, sparse search, image search, recommendations, bulk loading, and Citus, showing how the adapter fits common Python data stacks.

Use it if

  • Embeddings should live beside relational rows so similarity ordering can combine with SQL filters, joins, permissions, and transactions.
  • Django or SQLAlchemy models need typed pgvector columns, distance expressions, and HNSW or IVFFlat index declarations.
  • Psycopg, asyncpg, pg8000, or Peewee should adapt PostgreSQL vector values without hand-building text literals and casts.
  • Half-precision, binary, or sparse vectors are needed to reduce storage or support their matching distance operators.
Skip it if

Setup reality

We installed pgvector 0.5.0 in a fresh Python 3.12 Bookworm container. The install finished in 0.4 seconds, left one package, and used 1 MB on disk. It declares no direct dependencies, requires Python 3.10 or newer, and is pure Python. The wheel ships a py.typed marker, although the change log still labels type-hint support experimental. pip-audit found zero known vulnerabilities, and import pgvector worked in 0.08 seconds.

The database work comes first. PostgreSQL must already have compatible pgvector extension binaries, and a privileged migration must run CREATE EXTENSION vector. Managed services control which extension version is available, so Python code cannot assume every column type or index feature exists. Keep extension enablement in database migrations rather than application startup when the runtime role lacks extension privileges.

Raw drivers require type registration on every connection. Psycopg pools need a configure callback, asyncpg pools need an awaited init callback, and SQLAlchemy arrays of vectors need a connection event for the underlying driver. Missing registration commonly returns strings or produces adaptation errors. Version 0.5.0 also changes result shapes: ORMs return lists for vector and halfvec, while Psycopg, asyncpg, and pg8000 return Vector for vector values.

Index definitions must match query operators. L2 uses vector_l2_ops, cosine uses vector_cosine_ops, and inner product uses vector_ip_ops; a mismatch can leave PostgreSQL sorting without the intended ANN index. Build IVFFlat after representative data exists and tune its lists and probes. HNSW has different build-memory and search controls. Bulk loads should use COPY, then create the approximate index and measure recall against exact search on real filters.

Patterns

Create the database extension enable-vector-extension

from sqlalchemy import text

with engine.begin() as connection:
    connection.execute(text(
        'CREATE EXTENSION IF NOT EXISTS vector'
    ))

The server must already have pgvector installed, and the database role needs extension privileges. Put this in a controlled migration.

Register Psycopg 3 adapters register-psycopg-types

from pgvector.psycopg import register_vector

with psycopg.connect(dsn) as connection:
    register_vector(connection)
    row = connection.execute(
        'SELECT %s::vector',
        ([1.0, 2.0, 3.0],),
    ).fetchone()

Registration belongs to each physical connection. Pool users should call it from the pool's configure callback.

Register vector types on pool connections configure-asyncpg-pool

import asyncpg
from pgvector.asyncpg import register_vector

async def init(connection):
    await register_vector(connection)

pool = await asyncpg.create_pool(dsn, init=init)

The asyncpg registration function is awaited. A one-time call on a temporary connection does not configure later pool members.

Order rows by L2 distance query-raw-neighbors

from pgvector import Vector

query = Vector([1.0, 2.0, 3.0])
rows = connection.execute(
    '''SELECT id, embedding <-> %s AS distance
       FROM items
       ORDER BY embedding <-> %s
       LIMIT 5''',
    (query, query),
).fetchall()

`<->` is L2 distance. Use `<=>` for cosine or `<#>` for negative inner product, with a matching index operator class.

Map a fixed-dimension vector define-sqlalchemy-vector

from pgvector.sqlalchemy import VECTOR
from sqlalchemy.orm import Mapped, mapped_column

class Item(Base):
    __tablename__ = 'items'

    id: Mapped[int] = mapped_column(primary_key=True)
    embedding: Mapped[list[float]] = mapped_column(VECTOR(3))

PostgreSQL enforces the declared dimension. In version 0.5.0, SQLAlchemy vector results are Python lists rather than NumPy arrays.

Use a cosine-distance expression query-sqlalchemy-neighbors

from sqlalchemy import select

statement = (
    select(Item)
    .order_by(Item.embedding.cosine_distance([3, 1, 2]))
    .limit(10)
)
items = session.scalars(statement).all()

A cosine ANN index must use `vector_cosine_ops`. Confirm index use with EXPLAIN on the production filter shape.

Declare an HNSW index create-hnsw-index

from sqlalchemy import Index

Index(
    'items_embedding_hnsw',
    Item.embedding,
    postgresql_using='hnsw',
    postgresql_with={'m': 16, 'ef_construction': 64},
    postgresql_ops={'embedding': 'vector_cosine_ops'},
)

Index parameters trade build time, memory, recall, and query speed. Test them against exact results from your own embeddings.

Declare an IVFFlat index create-ivfflat-index

Index(
    'items_embedding_ivfflat',
    Item.embedding,
    postgresql_using='ivfflat',
    postgresql_with={'lists': 100},
    postgresql_ops={'embedding': 'vector_l2_ops'},
)

Create IVFFlat after loading representative data. Its list count and query-time probes need retuning as the table grows.

Add a vector field in Django define-django-vector

from pgvector.django import VectorField

class Item(models.Model):
    embedding = VectorField(dimensions=3)

nearest = Item.objects.order_by(
    L2Distance('embedding', [3, 1, 2])
)[:5]

Use `VectorExtension()` in a migration before creating this table. Django returns the column as a list in version 0.5.0.

Load vectors with binary COPY bulk-copy-vectors

from pgvector import Vector

with connection.cursor() as cursor:
    with cursor.copy(
        'COPY items (embedding) FROM STDIN WITH (FORMAT BINARY)'
    ) as copy:
        copy.set_types(['vector'])
        for values in embeddings:
            copy.write_row([Vector(values)])

Load the table before building IVFFlat or HNSW when possible. Commit the COPY before creating indexes in a separate migration step.

Use half-precision coordinates store-half-vectors

from pgvector import HalfVector
from pgvector.sqlalchemy import HALFVEC

class Item(Base):
    __tablename__ = 'half_items'
    id: Mapped[int] = mapped_column(primary_key=True)
    embedding: Mapped[list[float]] = mapped_column(HALFVEC(768))

value = HalfVector([0.1, -0.2, 0.3])

Half precision reduces coordinate storage and can alter neighbor ordering. Measure recall before changing an existing vector column or index.

Create a sparse vector value build-sparse-vector

from pgvector import SparseVector

value = SparseVector(
    {0: 1.0, 20: 0.5, 99: -0.25},
    100,
)
print(value.indices())
print(value.values())

Python indices are zero-based. The class handles conversion to pgvector's wire representation and validates the declared dimension.

Alternatives

PackageRegistryPick it when
chromadbPyPIUse it for a local collection-oriented vector store with embedding integrations and less PostgreSQL setup.
qdrant-clientPyPIUse it with Qdrant when filtered ANN search and vector-service operations deserve a separate system.
pymilvusPyPIUse it with Milvus for distributed vector workloads that need its indexing and scaling model.
weaviate-clientPyPIUse it when Weaviate's object schema and hosted or clustered service fit better than SQL tables.

More ai / ml guides

openai · mcp · huggingface-hub · @modelcontextprotocol/sdk · scikit-learn · tiktoken · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.