AutoRAG 0.0.0__py3-none-any.whl

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 (184) hide show
  1. autorag/__init__.py +82 -0
  2. autorag/chunker.py +51 -0
  3. autorag/cli.py +209 -0
  4. autorag/dashboard.py +199 -0
  5. autorag/data/__init__.py +109 -0
  6. autorag/data/chunk/__init__.py +2 -0
  7. autorag/data/chunk/base.py +128 -0
  8. autorag/data/chunk/langchain_chunk.py +76 -0
  9. autorag/data/chunk/llama_index_chunk.py +96 -0
  10. autorag/data/chunk/run.py +38 -0
  11. autorag/data/legacy/__init__.py +0 -0
  12. autorag/data/legacy/corpus/__init__.py +2 -0
  13. autorag/data/legacy/corpus/langchain.py +47 -0
  14. autorag/data/legacy/corpus/llama_index.py +93 -0
  15. autorag/data/legacy/qacreation/__init__.py +6 -0
  16. autorag/data/legacy/qacreation/base.py +239 -0
  17. autorag/data/legacy/qacreation/llama_index.py +253 -0
  18. autorag/data/legacy/qacreation/llama_index_default_prompt.txt +54 -0
  19. autorag/data/legacy/qacreation/ragas.py +75 -0
  20. autorag/data/legacy/qacreation/simple.py +99 -0
  21. autorag/data/parse/__init__.py +1 -0
  22. autorag/data/parse/base.py +79 -0
  23. autorag/data/parse/clova.py +194 -0
  24. autorag/data/parse/langchain_parse.py +87 -0
  25. autorag/data/parse/llamaparse.py +126 -0
  26. autorag/data/parse/run.py +141 -0
  27. autorag/data/parse/table_hybrid_parse.py +134 -0
  28. autorag/data/qa/__init__.py +3 -0
  29. autorag/data/qa/evolve/__init__.py +0 -0
  30. autorag/data/qa/evolve/llama_index_query_evolve.py +64 -0
  31. autorag/data/qa/evolve/openai_query_evolve.py +81 -0
  32. autorag/data/qa/evolve/prompt.py +288 -0
  33. autorag/data/qa/extract_evidence.py +1 -0
  34. autorag/data/qa/filter/__init__.py +0 -0
  35. autorag/data/qa/filter/dontknow.py +117 -0
  36. autorag/data/qa/filter/passage_dependency.py +88 -0
  37. autorag/data/qa/filter/prompt.py +73 -0
  38. autorag/data/qa/generation_gt/__init__.py +0 -0
  39. autorag/data/qa/generation_gt/base.py +16 -0
  40. autorag/data/qa/generation_gt/llama_index_gen_gt.py +41 -0
  41. autorag/data/qa/generation_gt/openai_gen_gt.py +84 -0
  42. autorag/data/qa/generation_gt/prompt.py +27 -0
  43. autorag/data/qa/query/__init__.py +0 -0
  44. autorag/data/qa/query/llama_gen_query.py +82 -0
  45. autorag/data/qa/query/openai_gen_query.py +95 -0
  46. autorag/data/qa/query/prompt.py +201 -0
  47. autorag/data/qa/sample.py +26 -0
  48. autorag/data/qa/schema.py +322 -0
  49. autorag/data/utils/__init__.py +0 -0
  50. autorag/data/utils/util.py +103 -0
  51. autorag/deploy/__init__.py +9 -0
  52. autorag/deploy/api.py +303 -0
  53. autorag/deploy/base.py +235 -0
  54. autorag/deploy/gradio.py +74 -0
  55. autorag/deploy/swagger.yml +202 -0
  56. autorag/embedding/__init__.py +0 -0
  57. autorag/embedding/base.py +144 -0
  58. autorag/embedding/vllm.py +256 -0
  59. autorag/evaluation/__init__.py +3 -0
  60. autorag/evaluation/generation.py +88 -0
  61. autorag/evaluation/metric/__init__.py +22 -0
  62. autorag/evaluation/metric/deepeval_prompt.py +322 -0
  63. autorag/evaluation/metric/g_eval_prompts/coh_detailed.txt +32 -0
  64. autorag/evaluation/metric/g_eval_prompts/con_detailed.txt +33 -0
  65. autorag/evaluation/metric/g_eval_prompts/flu_detailed.txt +26 -0
  66. autorag/evaluation/metric/g_eval_prompts/rel_detailed.txt +33 -0
  67. autorag/evaluation/metric/generation.py +504 -0
  68. autorag/evaluation/metric/retrieval.py +115 -0
  69. autorag/evaluation/metric/retrieval_contents.py +65 -0
  70. autorag/evaluation/metric/util.py +88 -0
  71. autorag/evaluation/retrieval.py +83 -0
  72. autorag/evaluation/retrieval_contents.py +65 -0
  73. autorag/evaluation/util.py +43 -0
  74. autorag/evaluator.py +559 -0
  75. autorag/node_line.py +65 -0
  76. autorag/nodes/__init__.py +0 -0
  77. autorag/nodes/generator/__init__.py +4 -0
  78. autorag/nodes/generator/base.py +103 -0
  79. autorag/nodes/generator/llama_index_llm.py +169 -0
  80. autorag/nodes/generator/openai_llm.py +329 -0
  81. autorag/nodes/generator/run.py +148 -0
  82. autorag/nodes/generator/vllm.py +147 -0
  83. autorag/nodes/generator/vllm_api.py +191 -0
  84. autorag/nodes/hybridretrieval/__init__.py +2 -0
  85. autorag/nodes/hybridretrieval/base.py +58 -0
  86. autorag/nodes/hybridretrieval/hybrid_cc.py +227 -0
  87. autorag/nodes/hybridretrieval/hybrid_rrf.py +149 -0
  88. autorag/nodes/hybridretrieval/run.py +137 -0
  89. autorag/nodes/lexicalretrieval/__init__.py +1 -0
  90. autorag/nodes/lexicalretrieval/bm25.py +381 -0
  91. autorag/nodes/lexicalretrieval/run.py +148 -0
  92. autorag/nodes/passageaugmenter/__init__.py +2 -0
  93. autorag/nodes/passageaugmenter/base.py +76 -0
  94. autorag/nodes/passageaugmenter/pass_passage_augmenter.py +43 -0
  95. autorag/nodes/passageaugmenter/prev_next_augmenter.py +155 -0
  96. autorag/nodes/passageaugmenter/run.py +131 -0
  97. autorag/nodes/passagecompressor/__init__.py +4 -0
  98. autorag/nodes/passagecompressor/base.py +78 -0
  99. autorag/nodes/passagecompressor/longllmlingua.py +115 -0
  100. autorag/nodes/passagecompressor/pass_compressor.py +16 -0
  101. autorag/nodes/passagecompressor/refine.py +54 -0
  102. autorag/nodes/passagecompressor/run.py +186 -0
  103. autorag/nodes/passagecompressor/tree_summarize.py +56 -0
  104. autorag/nodes/passagefilter/__init__.py +6 -0
  105. autorag/nodes/passagefilter/base.py +40 -0
  106. autorag/nodes/passagefilter/pass_passage_filter.py +14 -0
  107. autorag/nodes/passagefilter/percentile_cutoff.py +58 -0
  108. autorag/nodes/passagefilter/recency.py +105 -0
  109. autorag/nodes/passagefilter/run.py +138 -0
  110. autorag/nodes/passagefilter/similarity_percentile_cutoff.py +134 -0
  111. autorag/nodes/passagefilter/similarity_threshold_cutoff.py +112 -0
  112. autorag/nodes/passagefilter/threshold_cutoff.py +78 -0
  113. autorag/nodes/passagereranker/__init__.py +16 -0
  114. autorag/nodes/passagereranker/base.py +44 -0
  115. autorag/nodes/passagereranker/cohere.py +118 -0
  116. autorag/nodes/passagereranker/colbert.py +213 -0
  117. autorag/nodes/passagereranker/flag_embedding.py +112 -0
  118. autorag/nodes/passagereranker/flag_embedding_llm.py +101 -0
  119. autorag/nodes/passagereranker/flashrank.py +245 -0
  120. autorag/nodes/passagereranker/jina.py +115 -0
  121. autorag/nodes/passagereranker/koreranker.py +136 -0
  122. autorag/nodes/passagereranker/mixedbreadai.py +126 -0
  123. autorag/nodes/passagereranker/monot5.py +190 -0
  124. autorag/nodes/passagereranker/openvino.py +191 -0
  125. autorag/nodes/passagereranker/pass_reranker.py +31 -0
  126. autorag/nodes/passagereranker/rankgpt.py +170 -0
  127. autorag/nodes/passagereranker/run.py +145 -0
  128. autorag/nodes/passagereranker/sentence_transformer.py +129 -0
  129. autorag/nodes/passagereranker/tart/__init__.py +1 -0
  130. autorag/nodes/passagereranker/tart/modeling_enc_t5.py +152 -0
  131. autorag/nodes/passagereranker/tart/tart.py +139 -0
  132. autorag/nodes/passagereranker/tart/tokenization_enc_t5.py +112 -0
  133. autorag/nodes/passagereranker/time_reranker.py +72 -0
  134. autorag/nodes/passagereranker/upr.py +160 -0
  135. autorag/nodes/passagereranker/voyageai.py +109 -0
  136. autorag/nodes/promptmaker/__init__.py +12 -0
  137. autorag/nodes/promptmaker/base.py +32 -0
  138. autorag/nodes/promptmaker/chat_fstring.py +73 -0
  139. autorag/nodes/promptmaker/fstring.py +49 -0
  140. autorag/nodes/promptmaker/long_context_reorder.py +83 -0
  141. autorag/nodes/promptmaker/run.py +283 -0
  142. autorag/nodes/promptmaker/window_replacement.py +85 -0
  143. autorag/nodes/queryexpansion/__init__.py +4 -0
  144. autorag/nodes/queryexpansion/base.py +62 -0
  145. autorag/nodes/queryexpansion/hyde.py +43 -0
  146. autorag/nodes/queryexpansion/multi_query_expansion.py +57 -0
  147. autorag/nodes/queryexpansion/pass_query_expansion.py +22 -0
  148. autorag/nodes/queryexpansion/query_decompose.py +111 -0
  149. autorag/nodes/queryexpansion/run.py +308 -0
  150. autorag/nodes/retrieval/__init__.py +0 -0
  151. autorag/nodes/retrieval/base.py +127 -0
  152. autorag/nodes/retrieval/run_util.py +152 -0
  153. autorag/nodes/semanticretrieval/__init__.py +1 -0
  154. autorag/nodes/semanticretrieval/run.py +148 -0
  155. autorag/nodes/semanticretrieval/vectordb.py +339 -0
  156. autorag/nodes/util.py +16 -0
  157. autorag/parser.py +37 -0
  158. autorag/schema/__init__.py +3 -0
  159. autorag/schema/base.py +35 -0
  160. autorag/schema/metricinput.py +99 -0
  161. autorag/schema/module.py +24 -0
  162. autorag/schema/node.py +144 -0
  163. autorag/strategy.py +165 -0
  164. autorag/support.py +235 -0
  165. autorag/utils/__init__.py +8 -0
  166. autorag/utils/cast.py +45 -0
  167. autorag/utils/preprocess.py +149 -0
  168. autorag/utils/util.py +759 -0
  169. autorag/validator.py +98 -0
  170. autorag/vectordb/__init__.py +75 -0
  171. autorag/vectordb/base.py +73 -0
  172. autorag/vectordb/chroma.py +118 -0
  173. autorag/vectordb/couchbase.py +239 -0
  174. autorag/vectordb/milvus.py +169 -0
  175. autorag/vectordb/pinecone.py +121 -0
  176. autorag/vectordb/qdrant.py +155 -0
  177. autorag/vectordb/weaviate.py +184 -0
  178. autorag/web.py +81 -0
  179. autorag-0.0.0.dist-info/METADATA +780 -0
  180. autorag-0.0.0.dist-info/RECORD +184 -0
  181. autorag-0.0.0.dist-info/WHEEL +5 -0
  182. autorag-0.0.0.dist-info/entry_points.txt +2 -0
  183. autorag-0.0.0.dist-info/licenses/LICENSE +201 -0
  184. autorag-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,381 @@
1
+ import asyncio
2
+ import os
3
+ import pickle
4
+ import re
5
+ from typing import List, Dict, Tuple, Callable, Union, Iterable, Optional
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+ from llama_index.core.indices.keyword_table.utils import simple_extract_keywords
10
+ from nltk import PorterStemmer
11
+ from rank_bm25 import BM25Okapi
12
+ from transformers import AutoTokenizer, PreTrainedTokenizerBase
13
+
14
+ from autorag.nodes.retrieval.base import (
15
+ evenly_distribute_passages,
16
+ BaseRetrieval,
17
+ get_bm25_pkl_name,
18
+ )
19
+ from autorag.utils import validate_corpus_dataset, fetch_contents
20
+ from autorag.utils.util import (
21
+ get_event_loop,
22
+ normalize_string,
23
+ result_to_dataframe,
24
+ pop_params,
25
+ )
26
+
27
+
28
+ def tokenize_ko_kiwi(texts: List[str]) -> List[List[str]]:
29
+ try:
30
+ from kiwipiepy import Kiwi, Token
31
+ except ImportError:
32
+ raise ImportError(
33
+ "You need to install kiwipiepy to use 'ko_kiwi' tokenizer. "
34
+ "Please install kiwipiepy by running 'pip install kiwipiepy'. "
35
+ "Or install Korean version of AutoRAG by running 'pip install AutoRAG[ko]'."
36
+ )
37
+ texts = list(map(lambda x: x.strip().lower(), texts))
38
+ kiwi = Kiwi()
39
+ tokenized_list: Iterable[List[Token]] = kiwi.tokenize(texts)
40
+
41
+ def extract_form_safe(x):
42
+ try:
43
+ return x.form
44
+ except UnicodeDecodeError:
45
+ return " "
46
+
47
+ return [
48
+ list(map(lambda x: extract_form_safe(x), token_list))
49
+ for token_list in tokenized_list
50
+ ]
51
+
52
+
53
+ def tokenize_ko_kkma(texts: List[str]) -> List[List[str]]:
54
+ try:
55
+ from konlpy.tag import Kkma
56
+ except ImportError:
57
+ raise ImportError(
58
+ "You need to install konlpy to use 'ko_kkma' tokenizer. "
59
+ "Please install konlpy by running 'pip install konlpy'. "
60
+ "Or install Korean version of AutoRAG by running 'pip install AutoRAG[ko]'."
61
+ )
62
+ tokenizer = Kkma()
63
+ tokenized_list: List[List[str]] = list(map(lambda x: tokenizer.morphs(x), texts))
64
+ return tokenized_list
65
+
66
+
67
+ def tokenize_ko_okt(texts: List[str]) -> List[List[str]]:
68
+ try:
69
+ from konlpy.tag import Okt
70
+ except ImportError:
71
+ raise ImportError(
72
+ "You need to install konlpy to use 'ko_kkma' tokenizer. "
73
+ "Please install konlpy by running 'pip install konlpy'. "
74
+ "Or install Korean version of AutoRAG by running 'pip install AutoRAG[ko]'."
75
+ )
76
+ tokenizer = Okt()
77
+ tokenized_list: List[List[str]] = list(map(lambda x: tokenizer.morphs(x), texts))
78
+ return tokenized_list
79
+
80
+
81
+ def tokenize_porter_stemmer(texts: List[str]) -> List[List[str]]:
82
+ def tokenize_remove_stopword(text: str, stemmer) -> List[str]:
83
+ text = text.lower()
84
+ words = list(simple_extract_keywords(text))
85
+ return [stemmer.stem(word) for word in words]
86
+
87
+ stemmer = PorterStemmer()
88
+ tokenized_list: List[List[str]] = list(
89
+ map(lambda x: tokenize_remove_stopword(x, stemmer), texts)
90
+ )
91
+ return tokenized_list
92
+
93
+
94
+ def tokenize_space(texts: List[str]) -> List[List[str]]:
95
+ def tokenize_space_text(text: str) -> List[str]:
96
+ text = normalize_string(text)
97
+ return re.split(r"\s+", text.strip())
98
+
99
+ return list(map(tokenize_space_text, texts))
100
+
101
+
102
+ def load_bm25_corpus(bm25_path: str) -> Dict:
103
+ if bm25_path is None:
104
+ return {}
105
+ with open(bm25_path, "rb") as f:
106
+ bm25_corpus = pickle.load(f)
107
+ return bm25_corpus
108
+
109
+
110
+ def tokenize_ja_sudachipy(texts: List[str]) -> List[List[str]]:
111
+ try:
112
+ from sudachipy import dictionary, tokenizer
113
+ except ImportError:
114
+ raise ImportError(
115
+ "You need to install SudachiPy to use 'sudachipy' tokenizer. "
116
+ "Please install SudachiPy by running 'pip install sudachipy'."
117
+ )
118
+
119
+ # Initialize SudachiPy with the default tokenizer
120
+ tokenizer_obj = dictionary.Dictionary(dict="core").create()
121
+
122
+ # Choose the tokenizer mode: NORMAL, SEARCH, A
123
+ mode = tokenizer.Tokenizer.SplitMode.A
124
+
125
+ # Tokenize the input texts
126
+ tokenized_list = []
127
+ for text in texts:
128
+ tokens = tokenizer_obj.tokenize(text, mode)
129
+ tokenized_list.append([token.surface() for token in tokens])
130
+
131
+ return tokenized_list
132
+
133
+
134
+ BM25_TOKENIZER = {
135
+ "porter_stemmer": tokenize_porter_stemmer,
136
+ "ko_kiwi": tokenize_ko_kiwi,
137
+ "space": tokenize_space,
138
+ "ko_kkma": tokenize_ko_kkma,
139
+ "ko_okt": tokenize_ko_okt,
140
+ "sudachipy": tokenize_ja_sudachipy,
141
+ }
142
+
143
+
144
+ class BM25(BaseRetrieval):
145
+ def __init__(self, project_dir: str, *args, **kwargs):
146
+ """
147
+ Initialize BM25 module.
148
+ (Retrieval)
149
+
150
+ :param project_dir: The project directory path.
151
+ :param bm25_tokenizer: The tokenizer name that is used to the BM25.
152
+ It supports 'porter_stemmer', 'ko_kiwi', and huggingface `AutoTokenizer`.
153
+ You can pass huggingface tokenizer name.
154
+ Default is porter_stemmer.
155
+ :param kwargs: The optional arguments.
156
+ """
157
+
158
+ super().__init__(project_dir)
159
+ # check if bm25_path and file exist
160
+ bm25_tokenizer = kwargs.get("bm25_tokenizer", None)
161
+ if bm25_tokenizer is None:
162
+ bm25_tokenizer = "porter_stemmer"
163
+ bm25_path = os.path.join(self.resources_dir, get_bm25_pkl_name(bm25_tokenizer))
164
+
165
+ assert bm25_path is not None, (
166
+ "bm25_path must be specified for using bm25 retrieval."
167
+ )
168
+ assert os.path.exists(bm25_path), (
169
+ f"bm25_path {bm25_path} does not exist. Please ingest first."
170
+ )
171
+
172
+ self.bm25_corpus = load_bm25_corpus(bm25_path)
173
+ assert "tokens" and "passage_id" in list(self.bm25_corpus.keys()), (
174
+ "bm25_corpus must contain tokens and passage_id. Please check you ingested bm25 corpus correctly."
175
+ )
176
+ self.tokenizer = select_bm25_tokenizer(bm25_tokenizer)
177
+ assert self.bm25_corpus["tokenizer_name"] == bm25_tokenizer, (
178
+ f"The bm25 corpus tokenizer is {self.bm25_corpus['tokenizer_name']}, but your input is {bm25_tokenizer}. "
179
+ f"You need to ingest again. Delete bm25 pkl file and re-ingest it."
180
+ )
181
+ self.bm25_instance = BM25Okapi(self.bm25_corpus["tokens"])
182
+
183
+ @result_to_dataframe(
184
+ [
185
+ "retrieved_contents_lexical",
186
+ "retrieved_ids_lexical",
187
+ "retrieve_scores_lexical",
188
+ ]
189
+ )
190
+ def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
191
+ queries = self.cast_to_run(previous_result)
192
+ pure_params = pop_params(self._pure, kwargs)
193
+ ids, scores = self._pure(queries, *args, **pure_params)
194
+ contents = fetch_contents(self.corpus_df, ids)
195
+ return contents, ids, scores
196
+
197
+ def _pure(
198
+ self,
199
+ queries: List[List[str]],
200
+ top_k: int,
201
+ ids: Optional[List[List[str]]] = None,
202
+ ) -> Tuple[List[List[str]], List[List[float]]]:
203
+ """
204
+ BM25 retrieval function.
205
+ You have to load a pickle file that is already ingested.
206
+
207
+ :param queries: 2-d list of query strings.
208
+ Each element of the list is a query strings of each row.
209
+ :param top_k: The number of passages to be retrieved.
210
+ :param ids: The optional list of ids that you want to retrieve.
211
+ You don't need to specify this in the general use cases.
212
+ Default is None.
213
+ :return: The 2-d list contains a list of passage ids that retrieved from bm25 and 2-d list of its scores.
214
+ It will be a length of queries. And each element has a length of top_k.
215
+ """
216
+ if ids is not None:
217
+ score_result = list(
218
+ map(
219
+ lambda query_list, id_list: get_bm25_scores(
220
+ query_list,
221
+ id_list,
222
+ self.tokenizer,
223
+ self.bm25_instance,
224
+ self.bm25_corpus,
225
+ ),
226
+ queries,
227
+ ids,
228
+ )
229
+ )
230
+ return ids, score_result
231
+
232
+ # run async bm25_pure function
233
+ tasks = [
234
+ bm25_pure(
235
+ input_queries,
236
+ top_k,
237
+ self.tokenizer,
238
+ self.bm25_instance,
239
+ self.bm25_corpus,
240
+ )
241
+ for input_queries in queries
242
+ ]
243
+ loop = get_event_loop()
244
+ results = loop.run_until_complete(asyncio.gather(*tasks))
245
+ id_result = list(map(lambda x: x[0], results))
246
+ score_result = list(map(lambda x: x[1], results))
247
+ return id_result, score_result
248
+
249
+
250
+ async def bm25_pure(
251
+ queries: List[str], top_k: int, tokenizer, bm25_api: BM25Okapi, bm25_corpus: Dict
252
+ ) -> Tuple[List[str], List[float]]:
253
+ """
254
+ Async BM25 retrieval function.
255
+ Its usage is for async retrieval of bm25 row by row.
256
+
257
+ :param queries: A list of query strings.
258
+ :param top_k: The number of passages to be retrieved.
259
+ :param tokenizer: A tokenizer that will be used to tokenize queries.
260
+ :param bm25_api: A bm25 api instance that will be used to retrieve passages.
261
+ :param bm25_corpus: A dictionary containing the bm25 corpus, which is doc_id from corpus and tokenized corpus.
262
+ Its data structure looks like this:
263
+
264
+ .. Code:: python
265
+
266
+ {
267
+ "tokens": [], # 2d list of tokens
268
+ "passage_id": [], # 2d list of passage_id. Type must be str.
269
+ }
270
+ :return: The tuple contains a list of passage ids that retrieved from bm25 and its scores.
271
+ """
272
+ # I don't make queries operation to async, because queries length might be small, so it will occur overhead.
273
+ tokenized_queries = tokenize(queries, tokenizer)
274
+ id_result = []
275
+ score_result = []
276
+ for query in tokenized_queries:
277
+ scores = bm25_api.get_scores(query)
278
+ sorted_scores = sorted(scores, reverse=True)
279
+ top_n_index = np.argsort(scores)[::-1][:top_k]
280
+ ids = [bm25_corpus["passage_id"][i] for i in top_n_index]
281
+ id_result.append(ids)
282
+ score_result.append(sorted_scores[:top_k])
283
+
284
+ # make a total result to top_k
285
+ id_result, score_result = evenly_distribute_passages(id_result, score_result, top_k)
286
+ # sort id_result and score_result by score
287
+ result = [
288
+ (_id, score)
289
+ for score, _id in sorted(
290
+ zip(score_result, id_result), key=lambda pair: pair[0], reverse=True
291
+ )
292
+ ]
293
+ id_result, score_result = zip(*result)
294
+ return list(id_result), list(score_result)
295
+
296
+
297
+ def get_bm25_scores(
298
+ queries: List[str],
299
+ ids: List[str],
300
+ tokenizer,
301
+ bm25_api: BM25Okapi,
302
+ bm25_corpus: Dict,
303
+ ) -> List[float]:
304
+ if len(ids) == 0 or not bool(ids):
305
+ return []
306
+ tokenized_queries = tokenize(queries, tokenizer)
307
+ result_dict = {id_: [] for id_ in ids}
308
+ for query in tokenized_queries:
309
+ scores = bm25_api.get_scores(query)
310
+ for i, id_ in enumerate(ids):
311
+ result_dict[id_].append(scores[bm25_corpus["passage_id"].index(id_)])
312
+ result_df = pd.DataFrame(result_dict)
313
+ return result_df.max(axis=0).tolist()
314
+
315
+
316
+ def tokenize(queries: List[str], tokenizer) -> List[List[int]]:
317
+ if isinstance(tokenizer, PreTrainedTokenizerBase):
318
+ tokenized_queries = tokenizer(queries).input_ids
319
+ else:
320
+ tokenized_queries = tokenizer(queries)
321
+ return tokenized_queries
322
+
323
+
324
+ def bm25_ingest(
325
+ corpus_path: str, corpus_data: pd.DataFrame, bm25_tokenizer: str = "porter_stemmer"
326
+ ):
327
+ if not corpus_path.endswith(".pkl"):
328
+ raise ValueError(f"Corpus path {corpus_path} is not a pickle file.")
329
+ validate_corpus_dataset(corpus_data)
330
+ ids = corpus_data["doc_id"].tolist()
331
+
332
+ # Initialize bm25_corpus
333
+ bm25_corpus = pd.DataFrame()
334
+
335
+ # Load the BM25 corpus if it exists and get the passage ids
336
+ if os.path.exists(corpus_path) and os.path.getsize(corpus_path) > 0:
337
+ with open(corpus_path, "rb") as r:
338
+ corpus = pickle.load(r)
339
+ bm25_corpus = pd.DataFrame.from_dict(corpus)
340
+ duplicated_passage_rows = bm25_corpus[bm25_corpus["passage_id"].isin(ids)]
341
+ new_passage = corpus_data[
342
+ ~corpus_data["doc_id"].isin(duplicated_passage_rows["passage_id"])
343
+ ]
344
+ else:
345
+ new_passage = corpus_data
346
+
347
+ if not new_passage.empty:
348
+ tokenizer = select_bm25_tokenizer(bm25_tokenizer)
349
+ if isinstance(tokenizer, PreTrainedTokenizerBase):
350
+ tokenized_corpus = tokenizer(new_passage["contents"].tolist()).input_ids
351
+ else:
352
+ tokenized_corpus = tokenizer(new_passage["contents"].tolist())
353
+ new_bm25_corpus = pd.DataFrame(
354
+ {
355
+ "tokens": tokenized_corpus,
356
+ "passage_id": new_passage["doc_id"].tolist(),
357
+ }
358
+ )
359
+
360
+ if not bm25_corpus.empty:
361
+ bm25_corpus_updated = pd.concat(
362
+ [bm25_corpus, new_bm25_corpus], ignore_index=True
363
+ )
364
+ bm25_dict = bm25_corpus_updated.to_dict("list")
365
+ else:
366
+ bm25_dict = new_bm25_corpus.to_dict("list")
367
+
368
+ # add tokenizer name to bm25_dict
369
+ bm25_dict["tokenizer_name"] = bm25_tokenizer
370
+
371
+ with open(corpus_path, "wb") as w:
372
+ pickle.dump(bm25_dict, w)
373
+
374
+
375
+ def select_bm25_tokenizer(
376
+ bm25_tokenizer: str,
377
+ ) -> Callable[[str], List[Union[int, str]]]:
378
+ if bm25_tokenizer in list(BM25_TOKENIZER.keys()):
379
+ return BM25_TOKENIZER[bm25_tokenizer]
380
+
381
+ return AutoTokenizer.from_pretrained(bm25_tokenizer, use_fast=False)
@@ -0,0 +1,148 @@
1
+ import os
2
+ import pathlib
3
+ from typing import List, Dict, Union
4
+
5
+ import pandas as pd
6
+
7
+ from autorag.evaluation import evaluate_retrieval
8
+ from autorag.evaluation.retrieval import RETRIEVAL_METRIC_FUNC_DICT
9
+ from autorag.nodes.retrieval.run_util import save_and_summary, find_best
10
+ from autorag.schema.metricinput import MetricInput
11
+ from autorag.strategy import measure_speed
12
+ from autorag.utils.util import apply_recursive, to_list
13
+
14
+
15
+ def run_lexical_retrieval_node(
16
+ modules: List,
17
+ module_params: List[Dict],
18
+ previous_result: pd.DataFrame,
19
+ node_line_dir: str,
20
+ strategies: Dict,
21
+ ) -> pd.DataFrame:
22
+ """
23
+ Run the semantic retrieval node.
24
+
25
+ :param modules: Retrieval modules to run.
26
+ :param module_params: Retrieval module parameters.
27
+ :param previous_result: Previous result dataframe.
28
+ Could be query expansion's best result or qa data.
29
+ :param node_line_dir: This node line's directory.
30
+ :param strategies: Strategies for retrieval node.
31
+ :return: The best result dataframe.
32
+ It contains previous result columns and retrieval node's result columns.
33
+ """
34
+ if not os.path.exists(node_line_dir):
35
+ os.makedirs(node_line_dir)
36
+ project_dir = pathlib.PurePath(node_line_dir).parent.parent
37
+ qa_df = pd.read_parquet(
38
+ os.path.join(project_dir, "data", "qa.parquet"), engine="pyarrow"
39
+ )
40
+ retrieval_gt = qa_df["retrieval_gt"].tolist()
41
+ retrieval_gt = apply_recursive(lambda x: str(x), to_list(retrieval_gt))
42
+ # make rows to metric_inputs
43
+ metric_inputs = [
44
+ MetricInput(retrieval_gt=ret_gt, query=query, generation_gt=gen_gt)
45
+ for ret_gt, query, gen_gt in zip(
46
+ retrieval_gt, qa_df["query"].tolist(), qa_df["generation_gt"].tolist()
47
+ )
48
+ ]
49
+
50
+ save_dir = os.path.join(node_line_dir, "lexical_retrieval")
51
+ if not os.path.exists(save_dir):
52
+ os.makedirs(save_dir)
53
+
54
+ # Run the modules
55
+ lexical_results, execution_times = zip(
56
+ *map(
57
+ lambda task: measure_speed(
58
+ task[0].run_evaluator,
59
+ project_dir=project_dir,
60
+ previous_result=previous_result,
61
+ **task[1],
62
+ ),
63
+ zip(modules, module_params),
64
+ )
65
+ )
66
+ lexical_times = list(map(lambda x: x / len(lexical_results[0]), execution_times))
67
+
68
+ # run metrics
69
+ if strategies.get("metrics") is None:
70
+ raise ValueError("You must at least one metrics for retrieval evaluation.")
71
+ lexical_results = list(
72
+ map(
73
+ lambda x: evaluate_lexical_retrieval_node(
74
+ x,
75
+ metric_inputs,
76
+ strategies.get("metrics"),
77
+ ),
78
+ lexical_results,
79
+ )
80
+ )
81
+
82
+ lexical_summary_df = save_and_summary(
83
+ modules,
84
+ module_params,
85
+ lexical_results,
86
+ lexical_times,
87
+ 0,
88
+ save_dir,
89
+ strategies,
90
+ )
91
+ lexical_selected_result, lexical_selected_filename = find_best(
92
+ lexical_results,
93
+ lexical_times,
94
+ lexical_summary_df["filename"].tolist(),
95
+ strategies,
96
+ )
97
+ lexical_summary_df["is_best"] = (
98
+ lexical_summary_df["filename"] == lexical_selected_filename
99
+ )
100
+ previous_result.drop(
101
+ columns=list(RETRIEVAL_METRIC_FUNC_DICT.keys()), inplace=True, errors="ignore"
102
+ )
103
+ lexical_selected_result.rename(
104
+ columns={
105
+ "retrieved_contents": "retrieved_contents_lexical",
106
+ "retrieved_ids": "retrieved_ids_lexical",
107
+ "retrieve_scores": "retrieve_scores_lexical",
108
+ },
109
+ inplace=True,
110
+ )
111
+ best_result = pd.concat([previous_result, lexical_selected_result], axis=1)
112
+ best_result.to_parquet(
113
+ os.path.join(
114
+ save_dir, f"best_{os.path.splitext(lexical_selected_filename)[0]}.parquet"
115
+ ),
116
+ index=False,
117
+ )
118
+ lexical_summary_df.to_csv(os.path.join(save_dir, "summary.csv"), index=False)
119
+ return best_result
120
+
121
+
122
+ def evaluate_lexical_retrieval_node(
123
+ result_df: pd.DataFrame,
124
+ metric_inputs: List[MetricInput],
125
+ metrics: Union[List[str], List[Dict]],
126
+ ) -> pd.DataFrame:
127
+ """
128
+ Evaluate retrieval node from retrieval node result dataframe.
129
+
130
+ :param result_df: The result dataframe from a retrieval node.
131
+ :param metric_inputs: List of metric input schema for AutoRAG.
132
+ :param metrics: Metric list from input strategies.
133
+ :return: Return result_df with metrics columns.
134
+ The columns will be 'retrieved_contents_lexical', 'retrieved_ids_lexical', 'retrieve_scores_lexical', and metric names.
135
+ """
136
+
137
+ @evaluate_retrieval(
138
+ metric_inputs=metric_inputs,
139
+ metrics=metrics,
140
+ )
141
+ def evaluate_this_module(df: pd.DataFrame):
142
+ return (
143
+ df["retrieved_contents_lexical"].tolist(),
144
+ df["retrieved_ids_lexical"].tolist(),
145
+ df["retrieve_scores_lexical"].tolist(),
146
+ )
147
+
148
+ return evaluate_this_module(result_df)
@@ -0,0 +1,2 @@
1
+ from .pass_passage_augmenter import PassPassageAugmenter
2
+ from .prev_next_augmenter import PrevNextPassageAugmenter
@@ -0,0 +1,76 @@
1
+ import abc
2
+ import logging
3
+ import os
4
+
5
+ import pandas as pd
6
+
7
+ from autorag.schema import BaseModule
8
+ from autorag.utils import (
9
+ validate_qa_dataset,
10
+ sort_by_scores,
11
+ validate_corpus_dataset,
12
+ cast_corpus_dataset,
13
+ )
14
+ from autorag.utils.cast import cast_retrieved_ids
15
+ from autorag.utils.util import select_top_k
16
+
17
+ logger = logging.getLogger("AutoRAG")
18
+
19
+
20
+ class BasePassageAugmenter(BaseModule, metaclass=abc.ABCMeta):
21
+ def __init__(self, project_dir: str, *args, **kwargs):
22
+ logger.info(
23
+ f"Initialize passage augmenter node - {self.__class__.__name__} module..."
24
+ )
25
+ data_dir = os.path.join(project_dir, "data")
26
+ corpus_df = pd.read_parquet(
27
+ os.path.join(data_dir, "corpus.parquet"), engine="pyarrow"
28
+ )
29
+ validate_corpus_dataset(corpus_df)
30
+ corpus_df = cast_corpus_dataset(corpus_df)
31
+ self.corpus_df = corpus_df
32
+
33
+ def __del__(self):
34
+ logger.info(
35
+ f"Initialize passage augmenter node - {self.__class__.__name__} module..."
36
+ )
37
+
38
+ def cast_to_run(self, previous_result: pd.DataFrame, *args, **kwargs):
39
+ logger.info(
40
+ f"Running passage augmenter node - {self.__class__.__name__} module..."
41
+ )
42
+ validate_qa_dataset(previous_result)
43
+
44
+ # find ids columns
45
+ return cast_retrieved_ids(previous_result)
46
+
47
+ @staticmethod
48
+ def sort_by_scores(
49
+ augmented_contents,
50
+ augmented_ids,
51
+ augmented_scores,
52
+ top_k: int,
53
+ reverse: bool = True,
54
+ ):
55
+ # sort by scores
56
+ df = pd.DataFrame(
57
+ {
58
+ "contents": augmented_contents,
59
+ "ids": augmented_ids,
60
+ "scores": augmented_scores,
61
+ }
62
+ )
63
+ df[["contents", "ids", "scores"]] = df.apply(
64
+ lambda row: sort_by_scores(row, reverse=reverse),
65
+ axis=1,
66
+ result_type="expand",
67
+ )
68
+
69
+ # select by top_k
70
+ results = select_top_k(df, ["contents", "ids", "scores"], top_k)
71
+
72
+ return (
73
+ results["contents"].tolist(),
74
+ results["ids"].tolist(),
75
+ results["scores"].tolist(),
76
+ )
@@ -0,0 +1,43 @@
1
+ from typing import List
2
+
3
+ import pandas as pd
4
+
5
+ from autorag.nodes.passageaugmenter.base import BasePassageAugmenter
6
+ from autorag.utils import result_to_dataframe
7
+
8
+
9
+ class PassPassageAugmenter(BasePassageAugmenter):
10
+ @result_to_dataframe(["retrieved_contents", "retrieved_ids", "retrieve_scores"])
11
+ def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
12
+ """
13
+ Run the passage augmenter node - PassPassageAugmenter module.
14
+
15
+ :param previous_result: The previous result Dataframe.
16
+ :param top_k: You must input the top_k value to get the top k results.
17
+ :param kwargs: Not affected.
18
+ :return: DataFrame with retrieved_contents, retrieved_ids, and retrieve_scores columns
19
+ """
20
+ top_k = kwargs.pop("top_k")
21
+
22
+ ids = self.cast_to_run(previous_result)
23
+ contents = previous_result["retrieved_contents"].tolist()
24
+ scores = previous_result["retrieve_scores"].tolist()
25
+
26
+ augmented_ids, augmented_contents, augmented_scores = self._pure(
27
+ ids, contents, scores
28
+ )
29
+ return self.sort_by_scores(
30
+ augmented_contents, augmented_ids, augmented_scores, top_k
31
+ )
32
+
33
+ def _pure(
34
+ self,
35
+ ids_list: List[List[str]],
36
+ contents_list: List[List[str]],
37
+ scores_list: List[List[float]],
38
+ ):
39
+ """
40
+ Do not perform augmentation.
41
+ Return given passages, scores, and ids as is.
42
+ """
43
+ return ids_list, contents_list, scores_list