pberck commited on
Commit
dd32acc
·
1 Parent(s): 45fd7ba

Added hybrid.py and store.

Browse files
.gitattributes CHANGED
@@ -35,3 +35,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  *.sqlite3 filter=lfs diff=lfs merge=lfs -text
37
  *.jpg filter=lfs diff=lfs merge=lfs -text
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  *.sqlite3 filter=lfs diff=lfs merge=lfs -text
37
  *.jpg filter=lfs diff=lfs merge=lfs -text
38
+ *.store filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
@@ -11,7 +11,13 @@ import json
11
  from sentence_transformers import CrossEncoder
12
  import numpy as np
13
  from datetime import datetime
14
-
 
 
 
 
 
 
15
  # openAI API credits:
16
  # https://platform.openai.com/settings/organization/billing/overview
17
 
@@ -414,4 +420,6 @@ with gr.Blocks(theme=theme) as demo_blocks:
414
  # demo.launch(share=True)
415
  if __name__ == "__main__":
416
  print("Starting")
 
 
417
  demo_blocks.launch()
 
11
  from sentence_transformers import CrossEncoder
12
  import numpy as np
13
  from datetime import datetime
14
+ from hybrid import (
15
+ embedding_model,
16
+ reranker_model,
17
+ create_hybrid_retriever,
18
+ retrieve,
19
+ InMemoryDocumentStore,
20
+ )
21
  # openAI API credits:
22
  # https://platform.openai.com/settings/organization/billing/overview
23
 
 
420
  # demo.launch(share=True)
421
  if __name__ == "__main__":
422
  print("Starting")
423
+ doc_store = InMemoryDocumentStore().load_from_disk("pufendorfdocs.store")
424
+ print(f"Number of documents: {doc_store.count_documents()}.")
425
  demo_blocks.launch()
hybrid.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from haystack.document_stores.in_memory import InMemoryDocumentStore
4
+ from datasets import load_from_disk
5
+ from haystack import Document
6
+ from haystack.components.writers import DocumentWriter
7
+ from haystack.components.embedders import SentenceTransformersDocumentEmbedder
8
+ from haystack.components.preprocessors.document_splitter import DocumentSplitter
9
+ from haystack import Pipeline
10
+ from haystack.components.retrievers.in_memory import (
11
+ InMemoryBM25Retriever,
12
+ InMemoryEmbeddingRetriever,
13
+ )
14
+ from haystack.components.embedders import SentenceTransformersTextEmbedder
15
+ from haystack.components.joiners import DocumentJoiner
16
+
17
+ # from haystack.components.rankers import TransformersSimilarityRanker
18
+ from haystack.components.rankers import SentenceTransformersSimilarityRanker
19
+ from haystack.document_stores.types import DuplicatePolicy
20
+ from haystack.components.converters import PyPDFToDocument
21
+ from haystack.components.preprocessors import DocumentCleaner
22
+ from haystack.components.builders import PromptBuilder
23
+ from haystack_integrations.components.generators.ollama import OllamaGenerator
24
+ from pathlib import Path
25
+ from haystack.components.converters import DOCXToDocument
26
+ import re
27
+ import argparse
28
+
29
+
30
+ """
31
+ python hybrid.py -c newstore.store │
32
+ python hybrid.py -r newstore.store -q "who is pufendorf"
33
+ """
34
+
35
+ # embedding_model = "sentence-transformers/all-MiniLM-L6-v2"
36
+ embedding_model = "sentence-transformers/all-MiniLM-L12-v2"
37
+
38
+ # see https://huggingface.co/BAAI/bge-m3
39
+ reranker_model = "BAAI/bge-reranker-base"
40
+
41
+
42
+ def build_store_from_dir(dir_path: str) -> InMemoryDocumentStore:
43
+ root = Path(dir_path)
44
+ pdfs = sorted(str(p) for p in root.rglob("*.pdf"))
45
+ docxs = sorted(str(p) for p in root.rglob("*.docx"))
46
+
47
+ print(pdfs)
48
+ print(docxs)
49
+
50
+ pdf_conv = PyPDFToDocument()
51
+ docx_conv = DOCXToDocument()
52
+
53
+ docs = []
54
+ if pdfs:
55
+ out = pdf_conv.run(sources=pdfs, meta=[{"source": p} for p in pdfs])
56
+ docs.extend(out["documents"])
57
+ if docxs:
58
+ out = docx_conv.run(sources=docxs, meta=[{"source": p} for p in docxs])
59
+ docs.extend(out["documents"])
60
+
61
+ return docs
62
+
63
+
64
+ # Example usage:
65
+ # store = build_store_from_dir("/path/to/folder")
66
+ # print(len(store.filter_documents({})))
67
+
68
+
69
+ # As above, but splits the contents into sentences.
70
+ def create_index_split(docs, doc_store, split_length=5, split_overlap=1):
71
+ document_splitter = DocumentSplitter(
72
+ split_by="sentence", split_length=split_length, split_overlap=split_overlap
73
+ )
74
+ document_embedder = SentenceTransformersDocumentEmbedder(
75
+ model=embedding_model,
76
+ )
77
+ document_writer = DocumentWriter(doc_store, policy=DuplicatePolicy.SKIP)
78
+
79
+ indexing_pipeline = Pipeline()
80
+ indexing_pipeline.add_component("document_splitter", document_splitter)
81
+ indexing_pipeline.add_component("document_embedder", document_embedder)
82
+ indexing_pipeline.add_component("document_writer", document_writer)
83
+
84
+ indexing_pipeline.connect("document_splitter", "document_embedder")
85
+ indexing_pipeline.connect("document_embedder", "document_writer")
86
+
87
+ indexing_pipeline.run({"document_splitter": {"documents": docs}})
88
+
89
+ hybrid_retrieval = create_hybrid_retriever(doc_store)
90
+ return hybrid_retrieval
91
+
92
+
93
+ # Just the retriever pipeline on a document store.
94
+ # Creates an embedding and BM25 retriever on the doc_store.
95
+ def create_hybrid_retriever(doc_store):
96
+ text_embedder = SentenceTransformersTextEmbedder(
97
+ model=embedding_model,
98
+ )
99
+ embedding_retriever = InMemoryEmbeddingRetriever(doc_store)
100
+ bm25_retriever = InMemoryBM25Retriever(doc_store)
101
+
102
+ document_joiner = DocumentJoiner()
103
+ # ranker = TransformersSimilarityRanker(model=reranker_model)
104
+ # Needs haystack-ai >= 2.14
105
+ ranker = SentenceTransformersSimilarityRanker(model=reranker_model)
106
+
107
+ hybrid_retrieval = Pipeline()
108
+ hybrid_retrieval.add_component("text_embedder", text_embedder)
109
+ hybrid_retrieval.add_component("embedding_retriever", embedding_retriever)
110
+ hybrid_retrieval.add_component("bm25_retriever", bm25_retriever)
111
+ hybrid_retrieval.add_component("document_joiner", document_joiner)
112
+ hybrid_retrieval.add_component("ranker", ranker)
113
+
114
+ hybrid_retrieval.connect("text_embedder", "embedding_retriever")
115
+ hybrid_retrieval.connect("bm25_retriever", "document_joiner")
116
+ hybrid_retrieval.connect("embedding_retriever", "document_joiner")
117
+ hybrid_retrieval.connect("document_joiner", "ranker")
118
+
119
+ return hybrid_retrieval
120
+
121
+
122
+ def create_embedding_retriever(doc_store):
123
+ text_embedder = SentenceTransformersTextEmbedder(
124
+ model=embedding_model, # "BAAI/bge-small-en-v1.5" #, device=ComponentDevice.from_str("cuda:0")
125
+ )
126
+ embedding_retriever = InMemoryEmbeddingRetriever(doc_store)
127
+
128
+ ranker = SentenceTransformersSimilarityRanker(model=reranker_model)
129
+
130
+ embedding_retrieval = Pipeline()
131
+ embedding_retrieval.add_component("text_embedder", text_embedder)
132
+ embedding_retrieval.add_component("embedding_retriever", embedding_retriever)
133
+ embedding_retrieval.add_component("ranker", ranker)
134
+
135
+ embedding_retrieval.connect("text_embedder", "embedding_retriever")
136
+ embedding_retrieval.connect("embedding_retriever", "ranker")
137
+
138
+ return embedding_retrieval
139
+
140
+
141
+ def create_bm25_retriever(doc_store):
142
+ bm25_retriever = InMemoryBM25Retriever(doc_store)
143
+
144
+ document_joiner = DocumentJoiner()
145
+ ranker = SentenceTransformersSimilarityRanker(model=reranker_model)
146
+
147
+ bm25_retrieval = Pipeline()
148
+ bm25_retrieval.add_component("bm25_retriever", bm25_retriever)
149
+ bm25_retrieval.add_component("ranker", ranker)
150
+ bm25_retrieval.connect("bm25_retriever", "ranker")
151
+
152
+ return bm25_retrieval
153
+
154
+
155
+ # Run the pre-defined retrievers, returns the top_k best documents.
156
+ # We can filter the doc store if we find a name in the query.
157
+ # filters = {
158
+ # "operator": "AND",
159
+ # "conditions": [
160
+ # {"field": "meta.type", "operator": "==", "value": "article"},
161
+ # {"field": "meta.genre", "operator": "in", "value": ["economy", "politics"]},
162
+ # ],
163
+ # }
164
+ # results = DocumentStore.filter_documents(filters=filters)
165
+ def retrieve(retriever, query, top_k=8, scale=True):
166
+ result = retriever.run(
167
+ {
168
+ "text_embedder": {"text": query},
169
+ "bm25_retriever": {
170
+ "query": query,
171
+ "top_k": top_k,
172
+ "scale_score": scale,
173
+ # "filters": {"field": "meta.researcher_name",
174
+ # "operator": "==",
175
+ # "value": "P. Berck"}
176
+ },
177
+ "embedding_retriever": {"top_k": top_k, "scale_score": True},
178
+ "ranker": {"query": query, "top_k": top_k, "scale_score": True},
179
+ }
180
+ )
181
+ # print(result)
182
+ # pretty_print_results(result["ranker"])
183
+ return result["ranker"]["documents"]
184
+
185
+
186
+ def retrieve_embedded(retriever, query, top_k=8, scale=True):
187
+ result = retriever.run(
188
+ {
189
+ "text_embedder": {"text": query},
190
+ "embedding_retriever": {"top_k": top_k, "scale_score": scale},
191
+ "ranker": {"query": query, "top_k": top_k, "scale_score": scale},
192
+ }
193
+ )
194
+ return result["ranker"]["documents"]
195
+
196
+
197
+ def retrieve_bm25(retriever, query, top_k=8, scale=True):
198
+ result = retriever.run(
199
+ {
200
+ "bm25_retriever": {
201
+ "query": query,
202
+ "top_k": top_k,
203
+ "scale_score": scale,
204
+ # "filters": {"field": "meta.researcher_name",
205
+ # "operator": "==",
206
+ # "value": "P. Berck"}
207
+ },
208
+ "ranker": {"query": query, "top_k": top_k, "scale_score": True},
209
+ }
210
+ )
211
+ # print(result)
212
+ # pretty_print_results(result["ranker"])
213
+ return result["ranker"]["documents"]
214
+
215
+
216
+ def print_res(doc, width=0):
217
+ try:
218
+ txt = doc.meta["researcher_name"] + ":" + " ".join(doc.content.split())
219
+ except KeyError:
220
+ txt = " ".join(doc.content.split())
221
+ if width > 0:
222
+ txt_width = width - 8 - 3 - 1 # float and ... and LF
223
+ txt = txt[0:txt_width] + "..."
224
+ print("{:.5f}".format(doc.score), txt)
225
+
226
+
227
+ if __name__ == "__main__":
228
+ terminal_width = os.get_terminal_size().columns
229
+ parser = argparse.ArgumentParser()
230
+ parser.add_argument(
231
+ "-c", "--create_store", help="Create a new data store.", default=None
232
+ )
233
+ parser.add_argument("-d", "--dataset", help="Dataset filename.", default=None)
234
+ parser.add_argument("-r", "--read_store", help="Read a data store.", default=None)
235
+ parser.add_argument(
236
+ "-s",
237
+ "--scale",
238
+ action="store_false",
239
+ help="Do not scale retrieved scores.",
240
+ default=True,
241
+ )
242
+ parser.add_argument("--top_k", type=int, help="Retriever top_k.", default=8)
243
+ parser.add_argument("-q", "--query", help="Query DBs.", default=None)
244
+ args = parser.parse_args()
245
+ query = args.query
246
+
247
+ if args.create_store:
248
+ docs = build_store_from_dir("../Gradio/docs")
249
+ rs_doc_store = InMemoryDocumentStore()
250
+ print("Starting create_index_nosplit()")
251
+ create_index_split(docs, rs_doc_store)
252
+ rs_doc_store.save_to_disk(args.create_store)
253
+ print("Ready create_index_nosplit()")
254
+
255
+ if not args.query:
256
+ sys.exit(0)
257
+
258
+ if not args.read_store and not args.create_store:
259
+ args.read_store = "research_docs_ns.store"
260
+ elif not args.read_store and args.create_store:
261
+ args.read_store = args.create_store
262
+ print(f"Loading document store {args.read_store}...")
263
+ doc_store = InMemoryDocumentStore().load_from_disk(args.read_store)
264
+ print(f"Number of documents: {doc_store.count_documents()}.")
265
+
266
+ # Docs are already indexed/embedded in the store.
267
+ hybrid_retrieval = create_hybrid_retriever(doc_store)
268
+
269
+ documents = retrieve(hybrid_retrieval, query, top_k=args.top_k, scale=args.scale)
270
+ print("=" * 80)
271
+ print("== Hybrid")
272
+ print("=" * 80)
273
+ for doc in documents:
274
+ # print(doc.id, doc.meta["names"], ":", doc.meta["title"])
275
+ print_res(doc, terminal_width)
276
+
277
+ embedding_retrieval = create_embedding_retriever(doc_store)
278
+ documents = retrieve_embedded(
279
+ embedding_retrieval, query, top_k=args.top_k, scale=args.scale
280
+ )
281
+ print("=" * 80)
282
+ print("== Embedding")
283
+ print("=" * 80)
284
+ for doc in documents:
285
+ print_res(doc, terminal_width)
286
+
287
+ bm25_retrieval = create_bm25_retriever(doc_store)
288
+ documents = retrieve_bm25(bm25_retrieval, query, top_k=args.top_k, scale=args.scale)
289
+ print("=" * 80)
290
+ print("== bm25")
291
+ print("=" * 80)
292
+ for doc in documents:
293
+ print_res(doc, terminal_width)
pufendorfdocs.store ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2e1919adcb2c09b60c3e2e731e05bf2fb97246987855c30f6ddd419e009fc64e
3
+ size 9391350
vector3_db/a1b2bf9f-4f30-46a6-a6c2-b6ca99effce9/length.bin CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ff80c6ec655fc2ca4f622b1c630607bb775837f7bfa8ff648d72e980f7040e22
3
  size 40000
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc238a7a80a8cb9db9df824df6a3252ba0dd6f473223db345f2c4727a127151f
3
  size 40000
vector3_db/chroma.sqlite3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:466cd1438ed0a1ccd64dfd2bd430844cc62159b875dc1762d5728e531b396670
3
  size 11452416
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2cc72bc69672f1f5b68354ede0d475127b802f0ccc23c123c1cdb4186df4e549
3
  size 11452416