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,227 @@
1
+ from pathlib import Path
2
+ from typing import Tuple, List, Union
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from autorag.nodes.hybridretrieval.base import HybridRetrieval
8
+ from autorag.nodes.hybridretrieval.run import evaluate_retrieval_node
9
+ from autorag.strategy import select_best
10
+
11
+
12
+ def normalize_mm(scores: List[str], fixed_min_value: float = 0):
13
+ arr = np.array(scores)
14
+ max_value = np.max(arr)
15
+ min_value = np.min(arr)
16
+ norm_score = (arr - min_value) / (max_value - min_value)
17
+ return norm_score
18
+
19
+
20
+ def normalize_tmm(scores: List[str], fixed_min_value: float):
21
+ arr = np.array(scores)
22
+ max_value = np.max(arr)
23
+ norm_score = (arr - fixed_min_value) / (max_value - fixed_min_value)
24
+ return norm_score
25
+
26
+
27
+ def normalize_z(scores: List[str], fixed_min_value: float = 0):
28
+ arr = np.array(scores)
29
+ mean_value = np.mean(arr)
30
+ std_value = np.std(arr)
31
+ norm_score = (arr - mean_value) / std_value
32
+ return norm_score
33
+
34
+
35
+ def normalize_dbsf(scores: List[str], fixed_min_value: float = 0):
36
+ arr = np.array(scores)
37
+ mean_value = np.mean(arr)
38
+ std_value = np.std(arr)
39
+ min_value = mean_value - 3 * std_value
40
+ max_value = mean_value + 3 * std_value
41
+ norm_score = (arr - min_value) / (max_value - min_value)
42
+ return norm_score
43
+
44
+
45
+ normalize_method_dict = {
46
+ "mm": normalize_mm,
47
+ "tmm": normalize_tmm,
48
+ "z": normalize_z,
49
+ "dbsf": normalize_dbsf,
50
+ }
51
+
52
+
53
+ class HybridCC(HybridRetrieval):
54
+ def _pure(
55
+ self,
56
+ info: dict,
57
+ top_k: int,
58
+ weight: float,
59
+ normalize_method: str = "mm",
60
+ semantic_theoretical_min_value: float = -1.0,
61
+ lexical_theoretical_min_value: float = 0.0,
62
+ ):
63
+ return hybrid_cc(
64
+ (info["retrieved_ids_semantic"], info["retrieved_ids_lexical"]),
65
+ (info["retrieve_scores_semantic"], info["retrieve_scores_lexical"]),
66
+ top_k,
67
+ weight,
68
+ normalize_method,
69
+ semantic_theoretical_min_value,
70
+ lexical_theoretical_min_value,
71
+ )
72
+
73
+ @classmethod
74
+ def run_evaluator(
75
+ cls,
76
+ project_dir: Union[str, Path],
77
+ previous_result: pd.DataFrame,
78
+ *args,
79
+ **kwargs,
80
+ ):
81
+ assert "strategy" in kwargs, "You must specify the strategy to use."
82
+ assert "input_metrics" in kwargs, (
83
+ "You must specify the input metrics to use, which is list of MetricInput."
84
+ )
85
+ strategies = kwargs.pop("strategy")
86
+ input_metrics = kwargs.pop("input_metrics")
87
+ weight_range = kwargs.pop("weight_range", (0.0, 1.0))
88
+ test_weight_size = kwargs.pop("test_weight_size", 101)
89
+ weight_candidates = np.linspace(
90
+ weight_range[0], weight_range[1], test_weight_size
91
+ ).tolist()
92
+
93
+ result_list = []
94
+ instance = cls(project_dir, *args, **kwargs)
95
+ for weight_value in weight_candidates:
96
+ result_df = instance.pure(previous_result, weight=weight_value, **kwargs)
97
+ result_list.append(result_df)
98
+
99
+ if strategies.get("metrics") is None:
100
+ raise ValueError("You must at least one metrics for retrieval evaluation.")
101
+ result_list = list(
102
+ map(
103
+ lambda x: evaluate_retrieval_node(
104
+ x,
105
+ input_metrics,
106
+ strategies.get("metrics"),
107
+ ),
108
+ result_list,
109
+ )
110
+ )
111
+
112
+ # select best result
113
+ best_result_df, best_weight = select_best(
114
+ result_list,
115
+ strategies.get("metrics"),
116
+ metadatas=weight_candidates,
117
+ strategy_name=strategies.get("strategy", "normalize_mean"),
118
+ )
119
+ return {
120
+ "best_result": best_result_df,
121
+ "best_weight": best_weight,
122
+ }
123
+
124
+
125
+ def hybrid_cc(
126
+ ids: Tuple,
127
+ scores: Tuple,
128
+ top_k: int,
129
+ weight: float,
130
+ normalize_method: str = "mm",
131
+ semantic_theoretical_min_value: float = -1.0,
132
+ lexical_theoretical_min_value: float = 0.0,
133
+ ) -> Tuple[List[List[str]], List[List[float]]]:
134
+ """
135
+ Hybrid CC function.
136
+ CC (convex combination) is a method to fuse lexical and semantic retrieval results.
137
+ It is a method that first normalizes the scores of each retrieval result,
138
+ and then combines them with the given weights.
139
+ It is uniquer than other retrieval modules, because it does not really execute retrieval,
140
+ but just fuse the results of other retrieval functions.
141
+ So you have to run more than two retrieval modules before running this function.
142
+ And collect ids and scores result from each retrieval module.
143
+ Make it as tuple and input it to this function.
144
+
145
+ :param ids: The tuple of ids that you want to fuse.
146
+ The length of this must be the same as the length of scores.
147
+ The semantic retrieval ids must be the first index.
148
+ :param scores: The retrieve scores that you want to fuse.
149
+ The length of this must be the same as the length of ids.
150
+ The semantic retrieval scores must be the first index.
151
+ :param top_k: The number of passages to be retrieved.
152
+ :param normalize_method: The normalization method to use.
153
+ There are some normalization method that you can use at the hybrid cc method.
154
+ AutoRAG support following.
155
+ - `mm`: Min-max scaling
156
+ - `tmm`: Theoretical min-max scaling
157
+ - `z`: z-score normalization
158
+ - `dbsf`: 3-sigma normalization
159
+ :param weight: The weight value. If the weight is 1.0, it means the
160
+ weight to the semantic module will be 1.0 and weight to the lexical module will be 0.0.
161
+ :param semantic_theoretical_min_value: This value used by `tmm` normalization method. You can set the
162
+ theoretical minimum value by yourself. Default is -1.
163
+ :param lexical_theoretical_min_value: This value used by `tmm` normalization method. You can set the
164
+ theoretical minimum value by yourself. Default is 0.
165
+ :return: The tuple of ids and fused scores that fused by CC. Plus, the third element is selected weight value.
166
+ """
167
+ assert len(ids) == len(scores), "The length of ids and scores must be the same."
168
+ assert len(ids) > 1, "You must input more than one retrieval results."
169
+ assert top_k > 0, "top_k must be greater than 0."
170
+ assert weight >= 0, "The weight must be greater than 0."
171
+ assert weight <= 1, "The weight must be less than 1."
172
+
173
+ df = pd.DataFrame(
174
+ {
175
+ "semantic_ids": ids[0],
176
+ "lexical_ids": ids[1],
177
+ "semantic_score": scores[0],
178
+ "lexical_score": scores[1],
179
+ }
180
+ )
181
+
182
+ def cc_pure_apply(row):
183
+ return fuse_per_query(
184
+ row["semantic_ids"],
185
+ row["lexical_ids"],
186
+ row["semantic_score"],
187
+ row["lexical_score"],
188
+ normalize_method=normalize_method,
189
+ weight=weight,
190
+ top_k=top_k,
191
+ semantic_theoretical_min_value=semantic_theoretical_min_value,
192
+ lexical_theoretical_min_value=lexical_theoretical_min_value,
193
+ )
194
+
195
+ # fixed weight
196
+ df[["cc_id", "cc_score"]] = df.apply(
197
+ lambda row: cc_pure_apply(row), axis=1, result_type="expand"
198
+ )
199
+ return df["cc_id"].tolist(), df["cc_score"].tolist()
200
+
201
+
202
+ def fuse_per_query(
203
+ semantic_ids: List[str],
204
+ lexical_ids: List[str],
205
+ semantic_scores: List[float],
206
+ lexical_scores: List[float],
207
+ normalize_method: str,
208
+ weight: float,
209
+ top_k: int,
210
+ semantic_theoretical_min_value: float,
211
+ lexical_theoretical_min_value: float,
212
+ ):
213
+ normalize_func = normalize_method_dict[normalize_method]
214
+ norm_semantic_scores = normalize_func(
215
+ semantic_scores, semantic_theoretical_min_value
216
+ )
217
+ norm_lexical_scores = normalize_func(lexical_scores, lexical_theoretical_min_value)
218
+ ids = [semantic_ids, lexical_ids]
219
+ scores = [norm_semantic_scores, norm_lexical_scores]
220
+ df = pd.concat(
221
+ [pd.Series(dict(zip(_id, score))) for _id, score in zip(ids, scores)], axis=1
222
+ )
223
+ df.columns = ["semantic", "lexical"]
224
+ df = df.fillna(0)
225
+ df["weighted_sum"] = df.mul((weight, 1.0 - weight)).sum(axis=1)
226
+ df = df.sort_values(by="weighted_sum", ascending=False)
227
+ return df.index.tolist()[:top_k], df["weighted_sum"][:top_k].tolist()
@@ -0,0 +1,149 @@
1
+ from pathlib import Path
2
+ from typing import List, Tuple, Union
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from autorag.nodes.hybridretrieval.base import HybridRetrieval
8
+ from autorag.nodes.hybridretrieval.run import evaluate_retrieval_node
9
+ from autorag.strategy import select_best
10
+
11
+
12
+ class HybridRRF(HybridRetrieval):
13
+ def _pure(self, info: dict, top_k: int, weight: int = 60, rrf_k: int = -1):
14
+ return hybrid_rrf(
15
+ (info["retrieved_ids_semantic"], info["retrieved_ids_lexical"]),
16
+ (info["retrieve_scores_semantic"], info["retrieve_scores_lexical"]),
17
+ top_k,
18
+ weight,
19
+ rrf_k,
20
+ )
21
+
22
+ @classmethod
23
+ def run_evaluator(
24
+ cls,
25
+ project_dir: Union[str, Path],
26
+ previous_result: pd.DataFrame,
27
+ *args,
28
+ **kwargs,
29
+ ):
30
+ assert "strategy" in kwargs, "You must specify the strategy to use."
31
+ assert "input_metrics" in kwargs, (
32
+ "You must specify the input metrics to use, which is list of MetricInput."
33
+ )
34
+ strategies = kwargs.pop("strategy")
35
+ input_metrics = kwargs.pop("input_metrics")
36
+ weight_range = kwargs.pop("weight_range", (4, 80))
37
+ test_weight_size = weight_range[1] - weight_range[0] + 1
38
+ weight_candidates = np.linspace(
39
+ weight_range[0], weight_range[1], test_weight_size
40
+ ).tolist()
41
+
42
+ result_list = []
43
+ instance = cls(project_dir, *args, **kwargs)
44
+ for weight_value in weight_candidates:
45
+ result_df = instance.pure(previous_result, weight=weight_value, **kwargs)
46
+ result_list.append(result_df)
47
+
48
+ if strategies.get("metrics") is None:
49
+ raise ValueError("You must at least one metrics for retrieval evaluation.")
50
+ result_list = list(
51
+ map(
52
+ lambda x: evaluate_retrieval_node(
53
+ x,
54
+ input_metrics,
55
+ strategies.get("metrics"),
56
+ ),
57
+ result_list,
58
+ )
59
+ )
60
+
61
+ # select best result
62
+ best_result_df, best_weight = select_best(
63
+ result_list,
64
+ strategies.get("metrics"),
65
+ metadatas=weight_candidates,
66
+ strategy_name=strategies.get("strategy", "normalize_mean"),
67
+ )
68
+ return {
69
+ "best_result": best_result_df,
70
+ "best_weight": best_weight,
71
+ }
72
+
73
+
74
+ def hybrid_rrf(
75
+ ids: Tuple,
76
+ scores: Tuple,
77
+ top_k: int,
78
+ weight: int = 60,
79
+ rrf_k: int = -1,
80
+ ) -> Tuple[List[List[str]], List[List[float]]]:
81
+ """
82
+ Hybrid RRF function.
83
+ RRF (Rank Reciprocal Fusion) is a method to fuse multiple retrieval results.
84
+ It is common to fuse dense retrieval and sparse retrieval results using RRF.
85
+ To use this function, you must input ids and scores as tuple.
86
+ It is more unique than other retrieval modules because it does not really execute retrieval but just fuses
87
+ the results of other retrieval functions.
88
+ So you have to run more than two retrieval modules before running this function.
89
+ And collect ids and scores result from each retrieval module.
90
+ Make it as a tuple and input it to this function.
91
+
92
+ :param ids: The tuple of ids that you want to fuse.
93
+ The length of this must be the same as the length of scores.
94
+ :param scores: The retrieve scores that you want to fuse.
95
+ The length of this must be the same as the length of ids.
96
+ :param top_k: The number of passages to be retrieved.
97
+ :param weight: Hyperparameter for RRF.
98
+ It was originally rrf_k value.
99
+ Default is 60.
100
+ For more information, please visit our documentation.
101
+ :param rrf_k: (Deprecated) Hyperparameter for RRF.
102
+ It was originally rrf_k value. Will remove at a further version.
103
+ :return: The tuple of ids and fused scores that are fused by RRF.
104
+ """
105
+ assert len(ids) == len(scores), "The length of ids and scores must be the same."
106
+ assert len(ids) > 1, "You must input more than one retrieval results."
107
+ assert top_k > 0, "top_k must be greater than 0."
108
+ assert weight > 0, "rrf_k must be greater than 0."
109
+
110
+ if rrf_k != -1:
111
+ weight = int(rrf_k)
112
+ else:
113
+ weight = int(weight)
114
+
115
+ id_df = pd.DataFrame({f"id_{i}": id_list for i, id_list in enumerate(ids)})
116
+ score_df = pd.DataFrame(
117
+ {f"score_{i}": score_list for i, score_list in enumerate(scores)}
118
+ )
119
+ df = pd.concat([id_df, score_df], axis=1)
120
+
121
+ def rrf_pure_apply(row):
122
+ ids_tuple = tuple(row[[f"id_{i}" for i in range(len(ids))]].values)
123
+ scores_tuple = tuple(row[[f"score_{i}" for i in range(len(scores))]].values)
124
+ return pd.Series(rrf_pure(ids_tuple, scores_tuple, weight, top_k))
125
+
126
+ df[["rrf_id", "rrf_score"]] = df.apply(rrf_pure_apply, axis=1)
127
+ return df["rrf_id"].tolist(), df["rrf_score"].tolist()
128
+
129
+
130
+ def rrf_pure(
131
+ ids: Tuple, scores: Tuple, rrf_k: int, top_k: int
132
+ ) -> Tuple[List[str], List[float]]:
133
+ df = pd.concat(
134
+ [pd.Series(dict(zip(_id, score))) for _id, score in zip(ids, scores)], axis=1
135
+ )
136
+ rank_df = df.rank(ascending=False, method="min")
137
+ rank_df = rank_df.fillna(0)
138
+ rank_df["rrf"] = rank_df.apply(lambda row: rrf_calculate(row, rrf_k), axis=1)
139
+ rank_df = rank_df.sort_values(by="rrf", ascending=False)
140
+ return rank_df.index.tolist()[:top_k], rank_df["rrf"].tolist()[:top_k]
141
+
142
+
143
+ def rrf_calculate(row, rrf_k):
144
+ result = 0
145
+ for r in row:
146
+ if r == 0:
147
+ continue
148
+ result += 1 / (r + rrf_k)
149
+ return result
@@ -0,0 +1,137 @@
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_hybrid_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 hybrid retrieval node with the given modules and parameters.
24
+
25
+ :param modules: List of hybrid retrieval modules to run.
26
+ :param module_params: List of parameters for each module.
27
+ :param previous_result: DataFrame containing the previous results.
28
+ :param node_line_dir: Directory where the results will be saved.
29
+ :param strategies: Dictionary containing strategies for filtering and evaluation.
30
+ :return: DataFrame containing the results of the hybrid retrieval.
31
+ """
32
+ if not os.path.exists(node_line_dir):
33
+ os.makedirs(node_line_dir)
34
+ project_dir = pathlib.PurePath(node_line_dir).parent.parent
35
+ qa_df = pd.read_parquet(
36
+ os.path.join(project_dir, "data", "qa.parquet"), engine="pyarrow"
37
+ )
38
+ retrieval_gt = qa_df["retrieval_gt"].tolist()
39
+ retrieval_gt = apply_recursive(lambda x: str(x), to_list(retrieval_gt))
40
+
41
+ # make rows to metric_inputs
42
+ metric_inputs = [
43
+ MetricInput(retrieval_gt=ret_gt, query=query, generation_gt=gen_gt)
44
+ for ret_gt, query, gen_gt in zip(
45
+ retrieval_gt, qa_df["query"].tolist(), qa_df["generation_gt"].tolist()
46
+ )
47
+ ]
48
+
49
+ save_dir = os.path.join(node_line_dir, "hybrid_retrieval") # node name
50
+ if not os.path.exists(save_dir):
51
+ os.makedirs(save_dir)
52
+
53
+ results, execution_times = [], []
54
+ for module, module_param in zip(modules, module_params):
55
+ result, speed = measure_speed(
56
+ module.run_evaluator,
57
+ project_dir=project_dir,
58
+ previous_result=previous_result,
59
+ strategy=strategies,
60
+ input_metrics=metric_inputs,
61
+ **module_param,
62
+ )
63
+ if "best_weight" in result.keys():
64
+ module_param["weight"] = result["best_weight"]
65
+ results.append(result["best_result"])
66
+ execution_times.append(speed)
67
+
68
+ hybrid_times = list(map(lambda x: x / len(results[0]), execution_times))
69
+
70
+ # run metrics before filtering
71
+ if strategies.get("metrics") is None:
72
+ raise ValueError("You must at least one metrics for retrieval evaluation.")
73
+ results = list(
74
+ map(
75
+ lambda x: evaluate_retrieval_node(
76
+ x,
77
+ metric_inputs,
78
+ strategies.get("metrics"),
79
+ ),
80
+ results,
81
+ )
82
+ )
83
+
84
+ summary_df = save_and_summary(
85
+ modules,
86
+ module_params,
87
+ results,
88
+ hybrid_times,
89
+ 0,
90
+ save_dir,
91
+ strategies,
92
+ )
93
+ selected_result, selected_filename = find_best(
94
+ results, hybrid_times, summary_df["filename"].tolist(), strategies
95
+ )
96
+ summary_df["is_best"] = summary_df["filename"] == selected_filename
97
+ previous_result.drop(
98
+ columns=list(RETRIEVAL_METRIC_FUNC_DICT.keys()), inplace=True, errors="ignore"
99
+ )
100
+ best_result = pd.concat([previous_result, selected_result], axis=1)
101
+ best_result.to_parquet(
102
+ os.path.join(
103
+ save_dir, f"best_{os.path.splitext(selected_filename)[0]}.parquet"
104
+ ),
105
+ index=False,
106
+ )
107
+ summary_df.to_csv(os.path.join(save_dir, "summary.csv"), index=False)
108
+ return best_result
109
+
110
+
111
+ def evaluate_retrieval_node(
112
+ result_df: pd.DataFrame,
113
+ metric_inputs: List[MetricInput],
114
+ metrics: Union[List[str], List[Dict]],
115
+ ) -> pd.DataFrame:
116
+ """
117
+ Evaluate retrieval node from retrieval node result dataframe.
118
+
119
+ :param result_df: The result dataframe from a retrieval node.
120
+ :param metric_inputs: List of metric input schema for AutoRAG.
121
+ :param metrics: Metric list from input strategies.
122
+ :return: Return result_df with metrics columns.
123
+ The columns will be 'retrieved_contents', 'retrieved_ids', 'retrieve_scores', and metric names.
124
+ """
125
+
126
+ @evaluate_retrieval(
127
+ metric_inputs=metric_inputs,
128
+ metrics=metrics,
129
+ )
130
+ def evaluate_this_module(df: pd.DataFrame):
131
+ return (
132
+ df["retrieved_contents"].tolist(),
133
+ df["retrieved_ids"].tolist(),
134
+ df["retrieve_scores"].tolist(),
135
+ )
136
+
137
+ return evaluate_this_module(result_df)
@@ -0,0 +1 @@
1
+ from .bm25 import BM25