Skip to content

Commit bae2917

Browse files
committed
docs: add missing type hints and update vector query parameters
1 parent d2ace87 commit bae2917

2 files changed

Lines changed: 270 additions & 14 deletions

File tree

‎pgvector/django/functions.py‎

Lines changed: 116 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
from django.db.models import FloatField, Func, Value
22
from .. import Vector, HalfVector, SparseVector
3-
from typing import Any
3+
from typing import Any, ClassVar
44

55

66
class DistanceBase(Func):
7+
"""Base class for pgvector distance functions that operate on vector columns.
8+
9+
Accepts either a Django ORM expression (any object with a
10+
``resolve_expression`` method) or a raw vector value as ``list[float]``,
11+
:class:`~pgvector.Vector`, :class:`~pgvector.HalfVector`, or
12+
:class:`~pgvector.SparseVector`. Raw values are converted to their
13+
PostgreSQL text representation before being passed to the query.
14+
"""
15+
716
output_field = FloatField() # type: ignore
817

918
def __init__(self, expression: Any, vector: Any, /, **extra: Any) -> None:
19+
"""Initialize a distance expression between *expression* and *vector*.
20+
21+
Args:
22+
expression: A Django ORM expression referencing a vector column.
23+
vector: The comparison vector. May be a ``list[float]``,
24+
:class:`~pgvector.Vector`, :class:`~pgvector.HalfVector`,
25+
:class:`~pgvector.SparseVector`, or any Django expression
26+
that resolves to a compatible vector type.
27+
**extra: Additional keyword arguments forwarded to
28+
:class:`django.db.models.Func`.
29+
"""
1030
if not hasattr(vector, 'resolve_expression'):
1131
if isinstance(vector, (Vector, HalfVector, SparseVector)):
1232
vector = Value(vector.to_text())
@@ -20,39 +40,122 @@ def __init__(self, expression: Any, vector: Any, /, **extra: Any) -> None:
2040

2141

2242
class BitDistanceBase(Func):
43+
"""Base class for pgvector distance functions that operate on bit columns.
44+
45+
Unlike :class:`DistanceBase`, the comparison value is used as-is (a plain
46+
Python value that Django wraps in a :class:`~django.db.models.Value`).
47+
"""
48+
2349
output_field = FloatField() # type: ignore
2450

2551
def __init__(self, expression: Any, vector: Any, /, **extra: Any) -> None:
52+
"""Initialize a bit-distance expression between *expression* and *vector*.
53+
54+
Args:
55+
expression: A Django ORM expression referencing a bit column.
56+
vector: The comparison bit string or any Django expression that
57+
resolves to a compatible bit type.
58+
**extra: Additional keyword arguments forwarded to
59+
:class:`django.db.models.Func`.
60+
"""
2661
if not hasattr(vector, 'resolve_expression'):
2762
vector = Value(vector)
2863
super().__init__(expression, vector, **extra)
2964

3065

3166
class L2Distance(DistanceBase):
32-
function = ''
33-
arg_joiner = ' <-> '
67+
"""L2 (Euclidean) distance via the pgvector ``<->`` operator.
68+
69+
Returns the straight-line distance between two vectors in Euclidean space.
70+
Equivalent to ``sqrt(sum((a_i - b_i)^2))``. Lower values indicate
71+
greater similarity.
72+
73+
Example::
74+
75+
Item.objects.annotate(dist=L2Distance('embedding', [1, 2, 3])).order_by('dist')
76+
"""
77+
78+
function: ClassVar[str] = ''
79+
arg_joiner: ClassVar[str] = ' <-> '
3480

3581

3682
class MaxInnerProduct(DistanceBase):
37-
function = ''
38-
arg_joiner = ' <#> '
83+
"""Negative inner product via the pgvector ``<#>`` operator.
84+
85+
Returns the *negated* dot product of two vectors so that ORDER BY gives
86+
the highest-similarity results first (pgvector stores it negated to
87+
support ``ORDER BY ASC``). Equivalent to ``-sum(a_i * b_i)``.
88+
89+
Use this instead of :class:`CosineDistance` when vectors are already
90+
unit-normalised (cosine similarity and inner product are equivalent in
91+
that case, but inner product is faster).
92+
93+
Example::
94+
95+
Item.objects.annotate(score=MaxInnerProduct('embedding', [1, 2, 3])).order_by('score')
96+
"""
97+
98+
function: ClassVar[str] = ''
99+
arg_joiner: ClassVar[str] = ' <#> '
39100

40101

41102
class CosineDistance(DistanceBase):
42-
function = ''
43-
arg_joiner = ' <=> '
103+
"""Cosine distance via the pgvector ``<=>`` operator.
104+
105+
Returns ``1 - cosine_similarity(a, b)``, ranging from 0 (identical
106+
direction) to 2 (opposite direction). Magnitude-invariant, making it
107+
the typical choice for semantic-embedding similarity.
108+
109+
Example::
110+
111+
Item.objects.annotate(dist=CosineDistance('embedding', [1, 2, 3])).order_by('dist')
112+
"""
113+
114+
function: ClassVar[str] = ''
115+
arg_joiner: ClassVar[str] = ' <=> '
44116

45117

46118
class L1Distance(DistanceBase):
47-
function = ''
48-
arg_joiner = ' <+> '
119+
"""L1 (Manhattan / taxicab) distance via the pgvector ``<+>`` operator.
120+
121+
Returns ``sum(|a_i - b_i|)``. More robust than L2 distance in
122+
high-dimensional spaces because it penalises large component differences
123+
less aggressively.
124+
125+
Example::
126+
127+
Item.objects.annotate(dist=L1Distance('embedding', [1, 2, 3])).order_by('dist')
128+
"""
129+
130+
function: ClassVar[str] = ''
131+
arg_joiner: ClassVar[str] = ' <+> '
49132

50133

51134
class HammingDistance(BitDistanceBase):
52-
function = ''
53-
arg_joiner = ' <~> '
135+
"""Hamming distance between two bit strings via the pgvector ``<~>`` operator.
136+
137+
Counts the number of bit positions at which the two values differ.
138+
Operates on ``BIT(n)`` columns (use :class:`~pgvector.django.bit.BitField`).
139+
140+
Example::
141+
142+
Item.objects.annotate(dist=HammingDistance('signature', b'\\x01')).order_by('dist')
143+
"""
144+
145+
function: ClassVar[str] = ''
146+
arg_joiner: ClassVar[str] = ' <~> '
54147

55148

56149
class JaccardDistance(BitDistanceBase):
57-
function = ''
58-
arg_joiner = ' <%%> '
150+
"""Jaccard distance between two bit strings via the pgvector ``<%%>`` operator.
151+
152+
Returns ``1 - |A ∩ B| / |A ∪ B|``, i.e. 0 for identical bit strings and
153+
1 for disjoint ones. Operates on ``BIT(n)`` columns.
154+
155+
Example::
156+
157+
Item.objects.annotate(dist=JaccardDistance('signature', b'\\x01')).order_by('dist')
158+
"""
159+
160+
function: ClassVar[str] = ''
161+
arg_joiner: ClassVar[str] = ' <%%> '

‎pgvector/django/indexes.py‎

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,181 @@
33

44

55
class IvfflatIndex(PostgresIndex):
6+
"""Django index class for the pgvector IVFFlat (Inverted File Flat) index type.
7+
8+
IVFFlat partitions vectors into *lists* (Voronoi cells) at build time and
9+
restricts each query to the *probes* nearest lists. It offers fast approximate
10+
nearest-neighbour search with a controllable recall/speed trade-off.
11+
12+
Build-time cost is proportional to ``lists``; query-time cost is proportional to
13+
``probes`` (a session-level GUC, ``ivfflat.probes``, not an index parameter).
14+
The index **requires** the table to contain data before it is created — an empty
15+
table produces degenerate clusters.
16+
17+
Supported distance operators (set via ``opclasses``):
18+
19+
- ``vector_l2_ops`` — L2 / Euclidean distance (``<->``)
20+
- ``vector_ip_ops`` — inner product (``<#>``)
21+
- ``vector_cosine_ops`` — cosine distance (``<=>``)
22+
- ``halfvec_l2_ops`` / ``halfvec_ip_ops`` / ``halfvec_cosine_ops`` — half-precision variants
23+
- ``sparsevec_l2_ops`` / ``sparsevec_ip_ops`` / ``sparsevec_cosine_ops`` — sparse-vector variants
24+
25+
Example::
26+
27+
from pgvector.django import IvfflatIndex
28+
29+
class Meta:
30+
indexes = [
31+
IvfflatIndex(
32+
name='item_embedding_ivfflat',
33+
fields=['embedding'],
34+
lists=100,
35+
opclasses=['vector_cosine_ops'],
36+
)
37+
]
38+
39+
See also: https://github.com/pgvector/pgvector#ivfflat
40+
"""
41+
642
suffix = 'ivfflat'
743

844
def __init__(self, *expressions: Any, lists: int | None = None, **kwargs: Any) -> None:
45+
"""Create an IVFFlat index.
46+
47+
Args:
48+
*expressions: Column expressions to index (forwarded to
49+
:class:`~django.contrib.postgres.indexes.PostgresIndex`).
50+
lists: Number of lists (Voronoi cells) to partition the vector
51+
space into during index construction. Controls the
52+
accuracy/speed trade-off at *build* time.
53+
54+
- Higher values improve recall but increase build time and
55+
memory consumption.
56+
- As a rule of thumb pgvector recommends ``rows / 1000`` for
57+
up to 1 million rows, and ``sqrt(rows)`` beyond that.
58+
- Valid range: 1 – 32768. pgvector default: ``100``.
59+
- ``None`` omits the parameter and lets the server use its
60+
compiled-in default.
61+
**kwargs: Additional keyword arguments forwarded to
62+
:class:`~django.contrib.postgres.indexes.PostgresIndex`
63+
(e.g. ``name``, ``fields``, ``opclasses``, ``condition``).
64+
"""
965
self.lists = lists
1066
super().__init__(*expressions, **kwargs)
1167

1268
def deconstruct(self) -> tuple[Any, Any, Any]:
69+
"""Return enough information to recreate the index via migrations.
70+
71+
Returns:
72+
A ``(path, args, kwargs)`` tuple as required by Django's
73+
deconstruction protocol. ``lists`` is included in *kwargs* only
74+
when it was explicitly set.
75+
"""
1376
path, args, kwargs = super().deconstruct()
1477
if self.lists is not None:
1578
kwargs['lists'] = self.lists
1679
return path, args, kwargs
1780

1881
def get_with_params(self) -> list[str]:
82+
"""Return the ``WITH (...)`` storage parameters for the ``CREATE INDEX`` DDL.
83+
84+
Returns:
85+
A list of ``'key = value'`` strings passed to the PostgreSQL
86+
``WITH`` clause. Empty list when no parameters were specified.
87+
"""
1988
with_params = []
2089
if self.lists is not None:
2190
with_params.append('lists = %d' % self.lists)
2291
return with_params
2392

2493

2594
class HnswIndex(PostgresIndex):
95+
"""Django index class for the pgvector HNSW (Hierarchical Navigable Small World) index type.
96+
97+
HNSW builds a multi-layer proximity graph over the vectors. It generally
98+
provides higher recall and faster queries than :class:`IvfflatIndex` at the
99+
cost of more memory and a longer build time. Unlike IVFFlat it can be built
100+
on an empty table and updated incrementally.
101+
102+
Query-time recall is tuned with ``hnsw.ef_search`` (a session-level GUC),
103+
not an index parameter.
104+
105+
Supported distance operators (set via ``opclasses``):
106+
107+
- ``vector_l2_ops`` — L2 / Euclidean distance (``<->``)
108+
- ``vector_ip_ops`` — inner product (``<#>``)
109+
- ``vector_cosine_ops`` — cosine distance (``<=>``)
110+
- ``vector_l1_ops`` — L1 / Manhattan distance (``<+>``)
111+
- ``halfvec_*`` / ``sparsevec_*`` variants are also available
112+
113+
Example::
114+
115+
from pgvector.django import HnswIndex
116+
117+
class Meta:
118+
indexes = [
119+
HnswIndex(
120+
name='item_embedding_hnsw',
121+
fields=['embedding'],
122+
m=16,
123+
ef_construction=64,
124+
opclasses=['vector_cosine_ops'],
125+
)
126+
]
127+
128+
See also: https://github.com/pgvector/pgvector#hnsw
129+
"""
130+
26131
suffix = 'hnsw'
27132

28-
def __init__(self, *expressions: Any, m: int | None = None, ef_construction: int | None = None, **kwargs: Any) -> None:
133+
def __init__(
134+
self,
135+
*expressions: Any,
136+
m: int | None = None,
137+
ef_construction: int | None = None,
138+
**kwargs: Any,
139+
) -> None:
140+
"""Create an HNSW index.
141+
142+
Args:
143+
*expressions: Column expressions to index (forwarded to
144+
:class:`~django.contrib.postgres.indexes.PostgresIndex`).
145+
m: Maximum number of bidirectional links (edges) created for each
146+
new node during graph construction. Controls the graph
147+
connectivity at *build* time.
148+
149+
- Higher values yield better recall and faster queries but
150+
consume more memory (roughly ``m * 8`` bytes per vector per
151+
layer beyond the base layer).
152+
- Valid range: 2 – 100. pgvector default: ``16``.
153+
- ``None`` omits the parameter and lets the server use its
154+
compiled-in default.
155+
ef_construction: Size of the dynamic candidate list maintained
156+
while inserting a new vector into the graph. Controls the
157+
quality of the *build*-time graph structure.
158+
159+
- Higher values produce a denser, better-connected graph with
160+
improved recall, at the cost of a slower build.
161+
- Must satisfy ``ef_construction >= 2 * m``.
162+
- Valid range: 4 – 1000. pgvector default: ``64``.
163+
- ``None`` omits the parameter and lets the server use its
164+
compiled-in default.
165+
**kwargs: Additional keyword arguments forwarded to
166+
:class:`~django.contrib.postgres.indexes.PostgresIndex`
167+
(e.g. ``name``, ``fields``, ``opclasses``, ``condition``).
168+
"""
29169
self.m = m
30170
self.ef_construction = ef_construction
31171
super().__init__(*expressions, **kwargs)
32172

33173
def deconstruct(self) -> tuple[Any, Any, Any]:
174+
"""Return enough information to recreate the index via migrations.
175+
176+
Returns:
177+
A ``(path, args, kwargs)`` tuple as required by Django's
178+
deconstruction protocol. ``m`` and ``ef_construction`` are
179+
included in *kwargs* only when they were explicitly set.
180+
"""
34181
path, args, kwargs = super().deconstruct()
35182
if self.m is not None:
36183
kwargs['m'] = self.m
@@ -39,6 +186,12 @@ def deconstruct(self) -> tuple[Any, Any, Any]:
39186
return path, args, kwargs
40187

41188
def get_with_params(self) -> list[str]:
189+
"""Return the ``WITH (...)`` storage parameters for the ``CREATE INDEX`` DDL.
190+
191+
Returns:
192+
A list of ``'key = value'`` strings passed to the PostgreSQL
193+
``WITH`` clause. Empty list when no parameters were specified.
194+
"""
42195
with_params = []
43196
if self.m is not None:
44197
with_params.append('m = %d' % self.m)

0 commit comments

Comments
 (0)