pgvector-haystack 3.1.0__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/CHANGELOG.md +20 -1
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/PKG-INFO +1 -1
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/src/haystack_integrations/document_stores/pgvector/converters.py +5 -1
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/src/haystack_integrations/document_stores/pgvector/document_store.py +37 -6
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/conftest.py +24 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/test_document_store.py +22 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/test_retrievers.py +2 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/.gitignore +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/LICENSE.txt +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/README.md +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/examples/embedding_retrieval.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/examples/hybrid_retrieval.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/pydoc/config.yml +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/pyproject.toml +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/src/haystack_integrations/document_stores/pgvector/filters.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/__init__.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/test_converters.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/test_document_store_async.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/test_filters.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/test_retrieval.py +0 -0
- {pgvector_haystack-3.1.0 → pgvector_haystack-3.3.0}/tests/test_retrieval_async.py +0 -0
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/pgvector-v3.2.0] - 2025-04-11
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Add halfvec support for storing high-dimensional embeddings in half-precision (#1607)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [integrations/pgvector-v3.1.0] - 2025-03-20
|
|
11
|
+
|
|
12
|
+
### 🚀 Features
|
|
13
|
+
|
|
14
|
+
- Pgvector - async support (+ refactoring) (#1547)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### ⚙️ CI
|
|
18
|
+
|
|
19
|
+
- Review testing workflows (#1541)
|
|
20
|
+
|
|
3
21
|
## [integrations/pgvector-v3.0.1] - 2025-03-12
|
|
4
22
|
|
|
23
|
+
|
|
5
24
|
### 🌀 Miscellaneous
|
|
6
25
|
|
|
7
26
|
- Fix: pgvector - improve `_create_table_if_not_exists` to be used without admin rights (#1490)
|
|
8
27
|
|
|
9
28
|
## [integrations/pgvector-v3.0.0] - 2025-03-11
|
|
10
29
|
|
|
30
|
+
|
|
11
31
|
### 🧹 Chores
|
|
12
32
|
|
|
13
33
|
- Remove Python 3.8 support (#1421)
|
|
@@ -20,7 +40,6 @@
|
|
|
20
40
|
|
|
21
41
|
- Pgvector - remove support for dataframe (#1370)
|
|
22
42
|
|
|
23
|
-
### 🌀 Miscellaneous
|
|
24
43
|
|
|
25
44
|
## [integrations/pgvector-v1.3.0] - 2025-02-03
|
|
26
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgvector-haystack
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: An integration of pgvector (vector search extension for Postgres) with Haystack
|
|
5
5
|
Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations
|
|
6
6
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/pgvector/README.md
|
|
@@ -51,8 +51,12 @@ def _from_pg_to_haystack_documents(documents: List[Dict[str, Any]]) -> List[Docu
|
|
|
51
51
|
blob_mime_type = haystack_dict.pop("blob_mime_type")
|
|
52
52
|
|
|
53
53
|
# convert the embedding to a list of floats
|
|
54
|
+
# for strange reasons, halfvec and vector have different methods to convert the embedding to a list
|
|
54
55
|
if document.get("embedding") is not None:
|
|
55
|
-
|
|
56
|
+
if hasattr(document["embedding"], "tolist"): # vector
|
|
57
|
+
haystack_dict["embedding"] = document["embedding"].tolist()
|
|
58
|
+
else: # halfvec
|
|
59
|
+
haystack_dict["embedding"] = document["embedding"].to_list()
|
|
56
60
|
|
|
57
61
|
haystack_document = Document.from_dict(haystack_dict)
|
|
58
62
|
|
|
@@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
|
|
|
26
26
|
CREATE_TABLE_STATEMENT = """
|
|
27
27
|
CREATE TABLE {schema_name}.{table_name} (
|
|
28
28
|
id VARCHAR(128) PRIMARY KEY,
|
|
29
|
-
embedding
|
|
29
|
+
embedding {embedding_col_type}({embedding_dimension}),
|
|
30
30
|
content TEXT,
|
|
31
31
|
blob_data BYTEA,
|
|
32
32
|
blob_meta JSONB,
|
|
@@ -64,6 +64,12 @@ VECTOR_FUNCTION_TO_POSTGRESQL_OPS = {
|
|
|
64
64
|
"l2_distance": "vector_l2_ops",
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
HALF_VECTOR_FUNCTION_TO_POSTGRESQL_OPS = {
|
|
68
|
+
"cosine_similarity": "halfvec_cosine_ops",
|
|
69
|
+
"inner_product": "halfvec_ip_ops",
|
|
70
|
+
"l2_distance": "halfvec_l2_ops",
|
|
71
|
+
}
|
|
72
|
+
|
|
67
73
|
HNSW_INDEX_CREATION_VALID_KWARGS = ["m", "ef_construction"]
|
|
68
74
|
|
|
69
75
|
|
|
@@ -81,6 +87,7 @@ class PgvectorDocumentStore:
|
|
|
81
87
|
table_name: str = "haystack_documents",
|
|
82
88
|
language: str = "english",
|
|
83
89
|
embedding_dimension: int = 768,
|
|
90
|
+
vector_type: Literal["vector", "halfvec"] = "vector",
|
|
84
91
|
vector_function: Literal["cosine_similarity", "inner_product", "l2_distance"] = "cosine_similarity",
|
|
85
92
|
recreate_table: bool = False,
|
|
86
93
|
search_strategy: Literal["exact_nearest_neighbor", "hnsw"] = "exact_nearest_neighbor",
|
|
@@ -112,6 +119,11 @@ class PgvectorDocumentStore:
|
|
|
112
119
|
`SELECT cfgname FROM pg_ts_config;`.
|
|
113
120
|
More information can be found in this [StackOverflow answer](https://stackoverflow.com/a/39752553).
|
|
114
121
|
:param embedding_dimension: The dimension of the embedding.
|
|
122
|
+
:param vector_type: The type of vector used for embedding storage.
|
|
123
|
+
"vector" is the default.
|
|
124
|
+
"halfvec" stores embeddings in half-precision, which is particularly useful for high-dimensional embeddings
|
|
125
|
+
(dimension greater than 2,000 and up to 4,000). Requires pgvector versions 0.7.0 or later. For more
|
|
126
|
+
information, see the [pgvector documentation](https://github.com/pgvector/pgvector?tab=readme-ov-file).
|
|
115
127
|
:param vector_function: The similarity function to use when searching for similar embeddings.
|
|
116
128
|
`"cosine_similarity"` and `"inner_product"` are similarity functions and
|
|
117
129
|
higher scores indicate greater similarity between the documents.
|
|
@@ -145,6 +157,10 @@ class PgvectorDocumentStore:
|
|
|
145
157
|
self.table_name = table_name
|
|
146
158
|
self.schema_name = schema_name
|
|
147
159
|
self.embedding_dimension = embedding_dimension
|
|
160
|
+
if vector_type not in ["vector", "halfvec"]:
|
|
161
|
+
msg = "vector_type must be one of ['vector', 'halfvec']"
|
|
162
|
+
raise ValueError(msg)
|
|
163
|
+
self.vector_type = vector_type
|
|
148
164
|
if vector_function not in VALID_VECTOR_FUNCTIONS:
|
|
149
165
|
msg = f"vector_function must be one of {VALID_VECTOR_FUNCTIONS}, but got {vector_function}"
|
|
150
166
|
raise ValueError(msg)
|
|
@@ -180,6 +196,7 @@ class PgvectorDocumentStore:
|
|
|
180
196
|
schema_name=self.schema_name,
|
|
181
197
|
table_name=self.table_name,
|
|
182
198
|
embedding_dimension=self.embedding_dimension,
|
|
199
|
+
vector_type=self.vector_type,
|
|
183
200
|
vector_function=self.vector_function,
|
|
184
201
|
recreate_table=self.recreate_table,
|
|
185
202
|
search_strategy=self.search_strategy,
|
|
@@ -259,7 +276,9 @@ class PgvectorDocumentStore:
|
|
|
259
276
|
result = cursor.execute(sql_query, params)
|
|
260
277
|
except Error as e:
|
|
261
278
|
self._connection.rollback()
|
|
262
|
-
detailed_error_msg =
|
|
279
|
+
detailed_error_msg = (
|
|
280
|
+
f"{error_msg}. Error: {e!r}. \nYou can find the SQL query and the parameters in the debug logs."
|
|
281
|
+
)
|
|
263
282
|
raise DocumentStoreError(detailed_error_msg) from e
|
|
264
283
|
|
|
265
284
|
return result
|
|
@@ -297,7 +316,9 @@ class PgvectorDocumentStore:
|
|
|
297
316
|
result = await cursor.execute(sql_query, params)
|
|
298
317
|
except Error as e:
|
|
299
318
|
await self._async_connection.rollback()
|
|
300
|
-
detailed_error_msg =
|
|
319
|
+
detailed_error_msg = (
|
|
320
|
+
f"{error_msg}. Error: {e!r}. \nYou can find the SQL query and the parameters in the debug logs."
|
|
321
|
+
)
|
|
301
322
|
raise DocumentStoreError(detailed_error_msg) from e
|
|
302
323
|
|
|
303
324
|
return result
|
|
@@ -376,6 +397,7 @@ class PgvectorDocumentStore:
|
|
|
376
397
|
schema_name=Identifier(self.schema_name),
|
|
377
398
|
table_name=Identifier(self.table_name),
|
|
378
399
|
embedding_dimension=SQLLiteral(self.embedding_dimension),
|
|
400
|
+
embedding_col_type=SQL(self.vector_type),
|
|
379
401
|
)
|
|
380
402
|
|
|
381
403
|
sql_keyword_index_exists = SQL(
|
|
@@ -516,7 +538,16 @@ class PgvectorDocumentStore:
|
|
|
516
538
|
index_name=Identifier(self.hnsw_index_name),
|
|
517
539
|
)
|
|
518
540
|
|
|
519
|
-
|
|
541
|
+
if self.vector_type == "halfvec":
|
|
542
|
+
if self.vector_function not in HALF_VECTOR_FUNCTION_TO_POSTGRESQL_OPS:
|
|
543
|
+
msg = f"Unsupported vector_function '{self.vector_function}' for halfvec type."
|
|
544
|
+
raise ValueError(msg)
|
|
545
|
+
pg_ops = HALF_VECTOR_FUNCTION_TO_POSTGRESQL_OPS[self.vector_function]
|
|
546
|
+
else:
|
|
547
|
+
if self.vector_function not in VECTOR_FUNCTION_TO_POSTGRESQL_OPS:
|
|
548
|
+
msg = f"Unsupported vector_function '{self.vector_function}' for vector type."
|
|
549
|
+
raise ValueError(msg)
|
|
550
|
+
pg_ops = VECTOR_FUNCTION_TO_POSTGRESQL_OPS[self.vector_function]
|
|
520
551
|
|
|
521
552
|
sql_create_hnsw_index = SQL(
|
|
522
553
|
"CREATE INDEX {index_name} ON {schema_name}.{table_name} USING hnsw (embedding {ops})"
|
|
@@ -768,7 +799,7 @@ class PgvectorDocumentStore:
|
|
|
768
799
|
except Error as e:
|
|
769
800
|
self._connection.rollback()
|
|
770
801
|
error_msg = (
|
|
771
|
-
"Could not write documents to PgvectorDocumentStore. \n"
|
|
802
|
+
f"Could not write documents to PgvectorDocumentStore. Error: {e!r}. \n"
|
|
772
803
|
"You can find the SQL query and the parameters in the debug logs."
|
|
773
804
|
)
|
|
774
805
|
raise DocumentStoreError(error_msg) from e
|
|
@@ -825,7 +856,7 @@ class PgvectorDocumentStore:
|
|
|
825
856
|
except Error as e:
|
|
826
857
|
await self._async_connection.rollback()
|
|
827
858
|
error_msg = (
|
|
828
|
-
"Could not write documents to PgvectorDocumentStore. \n"
|
|
859
|
+
f"Could not write documents to PgvectorDocumentStore. Error: {e!r}. \n"
|
|
829
860
|
"You can find the SQL query and the parameters in the debug logs."
|
|
830
861
|
)
|
|
831
862
|
raise DocumentStoreError(error_msg) from e
|
|
@@ -54,6 +54,30 @@ def document_store_w_hnsw_index(request, monkeypatch):
|
|
|
54
54
|
store.delete_table()
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def document_store_w_halfvec_hnsw_index(request, monkeypatch):
|
|
59
|
+
monkeypatch.setenv("PG_CONN_STR", "postgresql://postgres:postgres@localhost:5432/postgres")
|
|
60
|
+
table_name = f"haystack_halfvec_hnsw_{request.node.name}"
|
|
61
|
+
embedding_dimension = 2500
|
|
62
|
+
vector_function = "cosine_similarity"
|
|
63
|
+
recreate_table = True
|
|
64
|
+
|
|
65
|
+
store = PgvectorDocumentStore(
|
|
66
|
+
table_name=table_name,
|
|
67
|
+
embedding_dimension=embedding_dimension,
|
|
68
|
+
vector_function=vector_function,
|
|
69
|
+
recreate_table=recreate_table,
|
|
70
|
+
search_strategy="hnsw",
|
|
71
|
+
vector_type="halfvec",
|
|
72
|
+
)
|
|
73
|
+
yield store
|
|
74
|
+
|
|
75
|
+
# Ensure connection just for table deletion.
|
|
76
|
+
# During test execution, the tested methods are expected to call _ensure_db_setup() themselves.
|
|
77
|
+
store._ensure_db_setup()
|
|
78
|
+
store.delete_table()
|
|
79
|
+
|
|
80
|
+
|
|
57
81
|
@pytest.fixture
|
|
58
82
|
def patches_for_unit_tests():
|
|
59
83
|
with patch("haystack_integrations.document_stores.pgvector.document_store.register_vector") as mock_register, patch(
|
|
@@ -95,6 +95,7 @@ def test_to_dict(monkeypatch):
|
|
|
95
95
|
create_extension=False,
|
|
96
96
|
table_name="my_table",
|
|
97
97
|
embedding_dimension=512,
|
|
98
|
+
vector_type="halfvec",
|
|
98
99
|
vector_function="l2_distance",
|
|
99
100
|
recreate_table=True,
|
|
100
101
|
search_strategy="hnsw",
|
|
@@ -113,6 +114,7 @@ def test_to_dict(monkeypatch):
|
|
|
113
114
|
"table_name": "my_table",
|
|
114
115
|
"schema_name": "public",
|
|
115
116
|
"embedding_dimension": 512,
|
|
117
|
+
"vector_type": "halfvec",
|
|
116
118
|
"vector_function": "l2_distance",
|
|
117
119
|
"recreate_table": True,
|
|
118
120
|
"search_strategy": "hnsw",
|
|
@@ -126,6 +128,26 @@ def test_to_dict(monkeypatch):
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
|
|
131
|
+
@pytest.mark.integration
|
|
132
|
+
def test_halfvec_hnsw_write_documents(document_store_w_halfvec_hnsw_index: PgvectorDocumentStore):
|
|
133
|
+
documents = [
|
|
134
|
+
Document(id="1", content="Hello, world!", embedding=[0.1] * 2500),
|
|
135
|
+
Document(id="2", content="Hello, mum!", embedding=[0.3] * 2500),
|
|
136
|
+
Document(id="3", content="Hello, dad!", embedding=[0.2] * 2500),
|
|
137
|
+
]
|
|
138
|
+
document_store_w_halfvec_hnsw_index.write_documents(documents)
|
|
139
|
+
|
|
140
|
+
retrieved_docs = document_store_w_halfvec_hnsw_index.filter_documents()
|
|
141
|
+
retrieved_docs.sort(key=lambda x: x.id)
|
|
142
|
+
|
|
143
|
+
for original_doc, retrieved_doc in zip(documents, retrieved_docs):
|
|
144
|
+
assert original_doc.id == retrieved_doc.id
|
|
145
|
+
assert original_doc.content == retrieved_doc.content
|
|
146
|
+
assert len(original_doc.embedding) == len(retrieved_doc.embedding)
|
|
147
|
+
# these embeddings are in half precision, so we increase the tolerance
|
|
148
|
+
assert original_doc.embedding == pytest.approx(retrieved_doc.embedding, abs=5e-5)
|
|
149
|
+
|
|
150
|
+
|
|
129
151
|
@pytest.mark.integration
|
|
130
152
|
def test_hnsw_index_recreation():
|
|
131
153
|
def get_index_oid(document_store, schema_name, index_name):
|
|
@@ -54,6 +54,7 @@ class TestEmbeddingRetriever:
|
|
|
54
54
|
"schema_name": "public",
|
|
55
55
|
"table_name": "haystack",
|
|
56
56
|
"embedding_dimension": 768,
|
|
57
|
+
"vector_type": "vector",
|
|
57
58
|
"vector_function": "cosine_similarity",
|
|
58
59
|
"recreate_table": True,
|
|
59
60
|
"search_strategy": "exact_nearest_neighbor",
|
|
@@ -198,6 +199,7 @@ class TestKeywordRetriever:
|
|
|
198
199
|
"schema_name": "public",
|
|
199
200
|
"table_name": "haystack",
|
|
200
201
|
"embedding_dimension": 768,
|
|
202
|
+
"vector_type": "vector",
|
|
201
203
|
"vector_function": "cosine_similarity",
|
|
202
204
|
"recreate_table": True,
|
|
203
205
|
"search_strategy": "exact_nearest_neighbor",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|