pgvector-haystack 0.2.0__tar.gz → 0.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.
Files changed (21) hide show
  1. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/PKG-INFO +1 -1
  2. pgvector_haystack-0.3.0/examples/hybrid_retrieval.py +69 -0
  3. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/pydoc/config.yml +1 -0
  4. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/src/haystack_integrations/document_stores/pgvector/document_store.py +50 -23
  5. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/tests/conftest.py +0 -1
  6. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/tests/test_document_store.py +6 -9
  7. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/.gitignore +0 -0
  8. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/LICENSE.txt +0 -0
  9. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/README.md +0 -0
  10. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/examples/embedding_retrieval.py +0 -0
  11. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/pyproject.toml +0 -0
  12. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
  13. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +0 -0
  14. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +0 -0
  15. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
  16. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/src/haystack_integrations/document_stores/pgvector/filters.py +0 -0
  17. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/tests/__init__.py +0 -0
  18. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/tests/test_embedding_retrieval.py +0 -0
  19. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/tests/test_filters.py +0 -0
  20. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/tests/test_keyword_retrieval.py +0 -0
  21. {pgvector_haystack-0.2.0 → pgvector_haystack-0.3.0}/tests/test_retrievers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pgvector-haystack
3
- Version: 0.2.0
3
+ Version: 0.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
@@ -0,0 +1,69 @@
1
+ # Before running this example, ensure you have PostgreSQL installed with the pgvector extension.
2
+ # For a quick setup using Docker:
3
+ # docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres
4
+ # -e POSTGRES_DB=postgres ankane/pgvector
5
+
6
+ # Install required packages for this example, including pgvector-haystack and other libraries needed
7
+ # for Markdown conversion and embeddings generation. Use the following command:
8
+ # pip install pgvector-haystack markdown-it-py mdit_plain "sentence-transformers>=2.2.0"
9
+
10
+ # Download some Markdown files to index.
11
+ # git clone https://github.com/anakin87/neural-search-pills
12
+
13
+ import glob
14
+
15
+ from haystack import Pipeline
16
+ from haystack.components.converters import MarkdownToDocument
17
+ from haystack.components.embedders import SentenceTransformersDocumentEmbedder, SentenceTransformersTextEmbedder
18
+ from haystack.components.joiners import DocumentJoiner
19
+ from haystack.components.preprocessors import DocumentSplitter
20
+ from haystack.components.writers import DocumentWriter
21
+ from haystack_integrations.components.retrievers.pgvector import PgvectorEmbeddingRetriever, PgvectorKeywordRetriever
22
+ from haystack_integrations.document_stores.pgvector import PgvectorDocumentStore
23
+
24
+ # Set an environment variable `PG_CONN_STR` with the connection string to your PostgreSQL database.
25
+ # e.g., "postgresql://USER:PASSWORD@HOST:PORT/DB_NAME"
26
+
27
+ # Initialize PgvectorDocumentStore
28
+ document_store = PgvectorDocumentStore(
29
+ table_name="haystack_test",
30
+ embedding_dimension=768,
31
+ vector_function="cosine_similarity",
32
+ recreate_table=True,
33
+ search_strategy="hnsw",
34
+ )
35
+
36
+ # Create the indexing Pipeline and index some documents
37
+ file_paths = glob.glob("neural-search-pills/pills/*.md")
38
+
39
+
40
+ indexing = Pipeline()
41
+ indexing.add_component("converter", MarkdownToDocument())
42
+ indexing.add_component("splitter", DocumentSplitter(split_by="sentence", split_length=2))
43
+ indexing.add_component("document_embedder", SentenceTransformersDocumentEmbedder())
44
+ indexing.add_component("writer", DocumentWriter(document_store))
45
+ indexing.connect("converter", "splitter")
46
+ indexing.connect("splitter", "document_embedder")
47
+ indexing.connect("document_embedder", "writer")
48
+
49
+ indexing.run({"converter": {"sources": file_paths}})
50
+
51
+ # Create the querying Pipeline and try a query
52
+ querying = Pipeline()
53
+ querying.add_component("text_embedder", SentenceTransformersTextEmbedder())
54
+ querying.add_component("retriever", PgvectorEmbeddingRetriever(document_store=document_store, top_k=3))
55
+ querying.add_component("keyword_retriever", PgvectorKeywordRetriever(document_store=document_store, top_k=3))
56
+ querying.add_component(
57
+ "joiner",
58
+ DocumentJoiner(join_mode="reciprocal_rank_fusion", top_k=3),
59
+ )
60
+ querying.connect("text_embedder", "retriever")
61
+ querying.connect("keyword_retriever", "joiner")
62
+ querying.connect("retriever", "joiner")
63
+
64
+ query = "cross-encoder"
65
+ results = querying.run({"text_embedder": {"text": query}, "keyword_retriever": {"query": query}})
66
+
67
+ for doc in results["joiner"]["documents"]:
68
+ print(doc)
69
+ print("-" * 10)
@@ -3,6 +3,7 @@ loaders:
3
3
  search_path: [../src]
4
4
  modules: [
5
5
  "haystack_integrations.components.retrievers.pgvector.embedding_retriever",
6
+ "haystack_integrations.components.retrievers.pgvector.keyword_retriever",
6
7
  "haystack_integrations.document_stores.pgvector.document_store",
7
8
  ]
8
9
  ignore_when_discovered: ["__init__"]
@@ -145,26 +145,53 @@ class PgvectorDocumentStore:
145
145
  self.hnsw_index_creation_kwargs = hnsw_index_creation_kwargs or {}
146
146
  self.hnsw_ef_search = hnsw_ef_search
147
147
  self.language = language
148
+ self._connection = None
149
+ self._cursor = None
150
+ self._dict_cursor = None
148
151
 
149
- connection = connect(self.connection_string.resolve_value())
150
- connection.autocommit = True
151
- self._connection = connection
152
+ @property
153
+ def cursor(self):
154
+ if self._cursor is None:
155
+ self._create_connection()
156
+
157
+ return self._cursor
158
+
159
+ @property
160
+ def dict_cursor(self):
161
+ if self._dict_cursor is None:
162
+ self._create_connection()
163
+
164
+ return self._dict_cursor
152
165
 
153
- # we create a generic cursor and another one that returns dictionaries
154
- self._cursor = connection.cursor()
155
- self._dict_cursor = connection.cursor(row_factory=dict_row)
166
+ @property
167
+ def connection(self):
168
+ if self._connection is None:
169
+ self._create_connection()
156
170
 
171
+ return self._connection
172
+
173
+ def _create_connection(self):
174
+ conn_str = self.connection_string.resolve_value() or ""
175
+ connection = connect(conn_str)
176
+ connection.autocommit = True
157
177
  connection.execute("CREATE EXTENSION IF NOT EXISTS vector")
158
- register_vector(connection)
178
+ register_vector(connection) # Note: this must be called before creating the cursors.
159
179
 
160
- if recreate_table:
180
+ self._connection = connection
181
+ self._cursor = self._connection.cursor()
182
+ self._dict_cursor = self._connection.cursor(row_factory=dict_row)
183
+
184
+ # Init schema
185
+ if self.recreate_table:
161
186
  self.delete_table()
162
187
  self._create_table_if_not_exists()
163
188
  self._create_keyword_index_if_not_exists()
164
189
 
165
- if search_strategy == "hnsw":
190
+ if self.search_strategy == "hnsw":
166
191
  self._handle_hnsw()
167
192
 
193
+ return self._connection
194
+
168
195
  def to_dict(self) -> Dict[str, Any]:
169
196
  """
170
197
  Serializes the component to a dictionary.
@@ -208,11 +235,11 @@ class PgvectorDocumentStore:
208
235
  :param sql_query: The SQL query to execute.
209
236
  :param params: The parameters to pass to the SQL query.
210
237
  :param error_msg: The error message to use if an exception is raised.
211
- :param cursor: The cursor to use to execute the SQL query. Defaults to self._cursor.
238
+ :param cursor: The cursor to use to execute the SQL query. Defaults to self.cursor.
212
239
  """
213
240
 
214
241
  params = params or ()
215
- cursor = cursor or self._cursor
242
+ cursor = cursor or self.cursor
216
243
 
217
244
  sql_query_str = sql_query.as_string(cursor) if not isinstance(sql_query, str) else sql_query
218
245
  logger.debug("SQL query: %s\nParameters: %s", sql_query_str, params)
@@ -220,7 +247,7 @@ class PgvectorDocumentStore:
220
247
  try:
221
248
  result = cursor.execute(sql_query, params)
222
249
  except Error as e:
223
- self._connection.rollback()
250
+ self.connection.rollback()
224
251
  detailed_error_msg = f"{error_msg}.\nYou can find the SQL query and the parameters in the debug logs."
225
252
  raise DocumentStoreError(detailed_error_msg) from e
226
253
 
@@ -371,7 +398,7 @@ class PgvectorDocumentStore:
371
398
  sql_filter,
372
399
  params,
373
400
  error_msg="Could not filter documents from PgvectorDocumentStore.",
374
- cursor=self._dict_cursor,
401
+ cursor=self.dict_cursor,
375
402
  )
376
403
 
377
404
  records = result.fetchall()
@@ -408,16 +435,16 @@ class PgvectorDocumentStore:
408
435
 
409
436
  sql_insert += SQL(" RETURNING id")
410
437
 
411
- sql_query_str = sql_insert.as_string(self._cursor) if not isinstance(sql_insert, str) else sql_insert
438
+ sql_query_str = sql_insert.as_string(self.cursor) if not isinstance(sql_insert, str) else sql_insert
412
439
  logger.debug("SQL query: %s\nParameters: %s", sql_query_str, db_documents)
413
440
 
414
441
  try:
415
- self._cursor.executemany(sql_insert, db_documents, returning=True)
442
+ self.cursor.executemany(sql_insert, db_documents, returning=True)
416
443
  except IntegrityError as ie:
417
- self._connection.rollback()
444
+ self.connection.rollback()
418
445
  raise DuplicateDocumentError from ie
419
446
  except Error as e:
420
- self._connection.rollback()
447
+ self.connection.rollback()
421
448
  error_msg = (
422
449
  "Could not write documents to PgvectorDocumentStore. \n"
423
450
  "You can find the SQL query and the parameters in the debug logs."
@@ -428,9 +455,9 @@ class PgvectorDocumentStore:
428
455
  # https://www.psycopg.org/psycopg3/docs/api/cursors.html#psycopg.Cursor.executemany
429
456
  written_docs = 0
430
457
  while True:
431
- if self._cursor.fetchone():
458
+ if self.cursor.fetchone():
432
459
  written_docs += 1
433
- if not self._cursor.nextset():
460
+ if not self.cursor.nextset():
434
461
  break
435
462
 
436
463
  return written_docs
@@ -483,8 +510,8 @@ class PgvectorDocumentStore:
483
510
 
484
511
  # postgresql returns the embedding as a string
485
512
  # so we need to convert it to a list of floats
486
- if document.get("embedding"):
487
- haystack_dict["embedding"] = [float(el) for el in document["embedding"].strip("[]").split(",")]
513
+ if document.get("embedding") is not None:
514
+ haystack_dict["embedding"] = document["embedding"].tolist()
488
515
 
489
516
  haystack_document = Document.from_dict(haystack_dict)
490
517
 
@@ -555,7 +582,7 @@ class PgvectorDocumentStore:
555
582
  sql_query,
556
583
  (query, *where_params),
557
584
  error_msg="Could not retrieve documents from PgvectorDocumentStore.",
558
- cursor=self._dict_cursor,
585
+ cursor=self.dict_cursor,
559
586
  )
560
587
 
561
588
  records = result.fetchall()
@@ -633,7 +660,7 @@ class PgvectorDocumentStore:
633
660
  sql_query,
634
661
  params,
635
662
  error_msg="Could not retrieve documents from PgvectorDocumentStore.",
636
- cursor=self._dict_cursor,
663
+ cursor=self.dict_cursor,
637
664
  )
638
665
 
639
666
  records = result.fetchall()
@@ -40,7 +40,6 @@ def patches_for_unit_tests():
40
40
  ) as mock_create_kw_index, patch(
41
41
  "haystack_integrations.document_stores.pgvector.document_store.PgvectorDocumentStore._handle_hnsw"
42
42
  ) as mock_hnsw:
43
-
44
43
  yield mock_connect, mock_register, mock_delete, mock_create, mock_create_kw_index, mock_hnsw
45
44
 
46
45
 
@@ -4,11 +4,13 @@
4
4
 
5
5
  from unittest.mock import patch
6
6
 
7
+ import numpy as np
7
8
  import pytest
8
9
  from haystack.dataclasses.document import ByteStream, Document
9
10
  from haystack.document_stores.errors import DuplicateDocumentError
10
11
  from haystack.document_stores.types import DuplicatePolicy
11
12
  from haystack.testing.document_store import CountDocumentsTest, DeleteDocumentsTest, WriteDocumentsTest
13
+ from haystack.utils import Secret
12
14
  from haystack_integrations.document_stores.pgvector import PgvectorDocumentStore
13
15
  from pandas import DataFrame
14
16
 
@@ -170,7 +172,7 @@ def test_from_pg_to_haystack_documents():
170
172
  "blob_meta": None,
171
173
  "blob_mime_type": None,
172
174
  "meta": {"meta_key": "meta_value"},
173
- "embedding": "[0.1, 0.2, 0.3]",
175
+ "embedding": np.array([0.1, 0.2, 0.3]),
174
176
  },
175
177
  {
176
178
  "id": "2",
@@ -180,7 +182,7 @@ def test_from_pg_to_haystack_documents():
180
182
  "blob_meta": None,
181
183
  "blob_mime_type": None,
182
184
  "meta": {"meta_key": "meta_value"},
183
- "embedding": "[0.4, 0.5, 0.6]",
185
+ "embedding": np.array([0.4, 0.5, 0.6]),
184
186
  },
185
187
  {
186
188
  "id": "3",
@@ -190,16 +192,11 @@ def test_from_pg_to_haystack_documents():
190
192
  "blob_meta": {"blob_meta_key": "blob_meta_value"},
191
193
  "blob_mime_type": "mime_type",
192
194
  "meta": {"meta_key": "meta_value"},
193
- "embedding": "[0.7, 0.8, 0.9]",
195
+ "embedding": np.array([0.7, 0.8, 0.9]),
194
196
  },
195
197
  ]
196
198
 
197
- with patch(
198
- "haystack_integrations.document_stores.pgvector.document_store.PgvectorDocumentStore.__init__"
199
- ) as mock_init:
200
- mock_init.return_value = None
201
- ds = PgvectorDocumentStore(connection_string="test")
202
-
199
+ ds = PgvectorDocumentStore(connection_string=Secret.from_token("test"))
203
200
  haystack_docs = ds._from_pg_to_haystack_documents(pg_docs)
204
201
 
205
202
  assert haystack_docs[0].id == "1"