mrkeyoor.com_
Wed 05 Aug 19:51 UTC
PyPIAI / MLupdated 05 Aug 2026

pgvector

pgvector is the official Python support package for the pgvector Postgres extension. It does not talk to the database itself; it teaches your existing driver or ORM (Django, SQLAlchemy, SQLModel, Psycopg 2 and 3, asyncpg, pg8000, Peewee) how to send and receive the extension's vector, halfvec, bit and sparsevec column types, and adds query helpers like L2Distance or embedding.cosine_distance() so nearest-neighbor queries read like normal ORM code instead of hand-built SQL strings.

Verdict

If your app already runs on Postgres, this is the least-drama way to add similarity search: a thin, well-tested adapter covering every Python driver that matters. Treat it as glue; the decisions that make or break you (extension version, index type, memory settings) happen on the Postgres side.

API stability4/5The registration and ORM helper APIs have been steady for years despite the 0.x version number, but 0.4.0 reorganized the type classes (Vector, HalfVector, SparseVector) into the top-level module, so older snippets importing from submodules need updating.
Docs4/5The README is the documentation: exhaustive per-driver recipes plus a large examples directory covering RAG, hybrid search, ColBERT and bulk loading. There is no hosted API reference, so details like return types per driver take a little experimentation.
Maintenance5/5Maintained by Andrew Kane alongside the pgvector extension itself, pushed within the last month, and the tracker held only a handful of issues and PRs at review time; the small surface area makes that sustainable.
Ecosystem5/5Wraps eight Python database libraries, and pgvector-the-extension is the default Postgres vector store in most RAG frameworks, so LangChain, LlamaIndex and Django integrations sit on top of this package.

Use it if

  • Your embeddings live next to relational data in Postgres and you want similarity search with joins, filters and transactions instead of running a separate vector database
  • You use Django, SQLAlchemy or SQLModel and want vector columns, distance ordering and HNSW/IVFFlat index definitions expressed in the ORM rather than raw migration SQL
  • Your corpus is small to mid-size (up to a few million vectors), where a properly indexed Postgres beats the operational cost of a dedicated vector engine
  • You need the newer pgvector types: halfvec for half precision, sparsevec for sparse embeddings, bit for binary quantization, all wrapped for every supported driver
Skip it if

Setup reality

pip install pgvector is the easy half. The extension must exist server side first (a postgresql-XX-pgvector OS package, the pgvector/pgvector Docker image, or your cloud provider's extension list) and CREATE EXTENSION vector needs adequate privileges. After that, every driver has its own registration dance: register_vector(conn) per connection for Psycopg, a configure hook for pools, an async variant for asyncpg, and a SQLAlchemy connect event if you use vector arrays. Skipping registration gives you strings back instead of vectors. Index tuning also lives in Postgres, not Python: IVFFlat lists should be set after data is loaded and HNSW builds go much faster with a bigger maintenance_work_mem.

Patterns

Enable the pgvector extensionenable-extension

# Psycopg 3
conn.execute('CREATE EXTENSION IF NOT EXISTS vector')

# SQLAlchemy
from sqlalchemy import text
session.execute(text('CREATE EXTENSION IF NOT EXISTS vector'))

This only works if the extension binaries are installed on the server and your role has permission; on managed Postgres, enable it from the provider's extension settings first.

Register vector types with Psycopg 3register-types-psycopg

from pgvector.psycopg import register_vector

register_vector(conn)

# for connection pools
from psycopg_pool import ConnectionPool

def configure(conn):
    register_vector(conn)

pool = ConnectionPool(conninfo, configure=configure)

Registration is per connection. Without it, vector columns come back as plain strings and inserts of Vector objects fail to adapt.

Insert vectors and query nearest neighbors with raw SQLinsert-and-query-nearest

from pgvector import Vector

conn.execute('CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))')

embedding = Vector([1, 2, 3])
conn.execute('INSERT INTO items (embedding) VALUES (%s)', (embedding,))

rows = conn.execute(
    'SELECT * FROM items ORDER BY embedding <-> %s LIMIT 5', (embedding,)
).fetchall()

<-> is L2 distance, <=> is cosine distance, <#> is negative inner product. Pick the operator that matches the opclass of your index or the index will not be used.

Define a vector column in SQLAlchemysqlalchemy-vector-column

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))

item = Item(embedding=[1, 2, 3])
session.add(item)
session.commit()

The dimension argument is enforced by Postgres on insert; also available: HALFVEC, BIT and SPARSEVEC for the other pgvector types.

Nearest neighbors and distance filters in SQLAlchemysqlalchemy-nearest-neighbors

from sqlalchemy import select

# top 5 by L2 distance
session.scalars(
    select(Item).order_by(Item.embedding.l2_distance([3, 1, 2])).limit(5)
)

# everything within a distance threshold
session.scalars(
    select(Item).filter(Item.embedding.l2_distance([3, 1, 2]) < 5)
)

cosine_distance, max_inner_product, l1_distance, hamming_distance and jaccard_distance are also available on the column.

Create an HNSW or IVFFlat index from SQLAlchemysqlalchemy-hnsw-index

from sqlalchemy import Index

index = Index(
    'items_embedding_idx',
    Item.embedding,
    postgresql_using='hnsw',
    postgresql_with={'m': 16, 'ef_construction': 64},
    postgresql_ops={'embedding': 'vector_l2_ops'}
)
index.create(engine)

Use vector_cosine_ops for cosine and vector_ip_ops for inner product; the opclass must match the distance operator you query with. IVFFlat (postgresql_using='ivfflat', lists=N) should be built after the table has data.

Vector field and nearest-neighbor query in Djangodjango-model-and-query

from pgvector.django import VectorExtension, VectorField, L2Distance

class Migration(migrations.Migration):
    operations = [VectorExtension()]

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

Item.objects.order_by(L2Distance('embedding', [3, 1, 2]))[:5]
Item.objects.alias(d=L2Distance('embedding', [3, 1, 2])).filter(d__lt=5)

HnswIndex and IvfflatIndex go in the model Meta.indexes with an opclasses list; CosineDistance and MaxInnerProduct mirror the other operators.

Register types on an asyncpg poolasyncpg-pool-setup

import asyncpg
from pgvector.asyncpg import register_vector

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

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

async with pool.acquire() as conn:
    rows = await conn.fetch(
        'SELECT * FROM items ORDER BY embedding <-> $1 LIMIT 5', embedding
    )

asyncpg uses $1 placeholders, not %s, and register_vector is awaited; forgetting the init hook is the usual cause of 'unknown type vector' errors.

Bulk load embeddings with COPYbulk-load-copy

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

Binary COPY through Psycopg 3 is dramatically faster than row-by-row inserts for large loads; build the ANN index after loading, not before.

Work with sparse and half-precision vectorssparse-and-half-vectors

from pgvector import HalfVector, SparseVector

hv = HalfVector([1, 2, 3])            # halfvec column, 2 bytes per dim
sv = SparseVector({0: 1.0, 2: 2.0, 4: 3.0}, 6)  # non-zero index: value, dims

sv.indices()   # [0, 2, 4]
sv.values()    # [1.0, 2.0, 3.0]
sv.to_numpy()  # dense numpy array

SparseVector also accepts SciPy sparse arrays and its indices start at 0 in Python but are stored 1-based in Postgres text format; the classes convert for you.

Alternatives

PackageRegistryPick it when
chromadbPyPIYou want a batteries-included local vector store with collections and built-in embedding functions instead of managing Postgres
qdrant-clientPyPIA dedicated vector database with strong filtered search and quantization once you outgrow a single Postgres instance
pymilvusPyPIBillion-scale ANN workloads that need horizontal scaling and GPU index options