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.
- autorag/__init__.py +82 -0
- autorag/chunker.py +51 -0
- autorag/cli.py +209 -0
- autorag/dashboard.py +199 -0
- autorag/data/__init__.py +109 -0
- autorag/data/chunk/__init__.py +2 -0
- autorag/data/chunk/base.py +128 -0
- autorag/data/chunk/langchain_chunk.py +76 -0
- autorag/data/chunk/llama_index_chunk.py +96 -0
- autorag/data/chunk/run.py +38 -0
- autorag/data/legacy/__init__.py +0 -0
- autorag/data/legacy/corpus/__init__.py +2 -0
- autorag/data/legacy/corpus/langchain.py +47 -0
- autorag/data/legacy/corpus/llama_index.py +93 -0
- autorag/data/legacy/qacreation/__init__.py +6 -0
- autorag/data/legacy/qacreation/base.py +239 -0
- autorag/data/legacy/qacreation/llama_index.py +253 -0
- autorag/data/legacy/qacreation/llama_index_default_prompt.txt +54 -0
- autorag/data/legacy/qacreation/ragas.py +75 -0
- autorag/data/legacy/qacreation/simple.py +99 -0
- autorag/data/parse/__init__.py +1 -0
- autorag/data/parse/base.py +79 -0
- autorag/data/parse/clova.py +194 -0
- autorag/data/parse/langchain_parse.py +87 -0
- autorag/data/parse/llamaparse.py +126 -0
- autorag/data/parse/run.py +141 -0
- autorag/data/parse/table_hybrid_parse.py +134 -0
- autorag/data/qa/__init__.py +3 -0
- autorag/data/qa/evolve/__init__.py +0 -0
- autorag/data/qa/evolve/llama_index_query_evolve.py +64 -0
- autorag/data/qa/evolve/openai_query_evolve.py +81 -0
- autorag/data/qa/evolve/prompt.py +288 -0
- autorag/data/qa/extract_evidence.py +1 -0
- autorag/data/qa/filter/__init__.py +0 -0
- autorag/data/qa/filter/dontknow.py +117 -0
- autorag/data/qa/filter/passage_dependency.py +88 -0
- autorag/data/qa/filter/prompt.py +73 -0
- autorag/data/qa/generation_gt/__init__.py +0 -0
- autorag/data/qa/generation_gt/base.py +16 -0
- autorag/data/qa/generation_gt/llama_index_gen_gt.py +41 -0
- autorag/data/qa/generation_gt/openai_gen_gt.py +84 -0
- autorag/data/qa/generation_gt/prompt.py +27 -0
- autorag/data/qa/query/__init__.py +0 -0
- autorag/data/qa/query/llama_gen_query.py +82 -0
- autorag/data/qa/query/openai_gen_query.py +95 -0
- autorag/data/qa/query/prompt.py +201 -0
- autorag/data/qa/sample.py +26 -0
- autorag/data/qa/schema.py +322 -0
- autorag/data/utils/__init__.py +0 -0
- autorag/data/utils/util.py +103 -0
- autorag/deploy/__init__.py +9 -0
- autorag/deploy/api.py +303 -0
- autorag/deploy/base.py +235 -0
- autorag/deploy/gradio.py +74 -0
- autorag/deploy/swagger.yml +202 -0
- autorag/embedding/__init__.py +0 -0
- autorag/embedding/base.py +144 -0
- autorag/embedding/vllm.py +256 -0
- autorag/evaluation/__init__.py +3 -0
- autorag/evaluation/generation.py +88 -0
- autorag/evaluation/metric/__init__.py +22 -0
- autorag/evaluation/metric/deepeval_prompt.py +322 -0
- autorag/evaluation/metric/g_eval_prompts/coh_detailed.txt +32 -0
- autorag/evaluation/metric/g_eval_prompts/con_detailed.txt +33 -0
- autorag/evaluation/metric/g_eval_prompts/flu_detailed.txt +26 -0
- autorag/evaluation/metric/g_eval_prompts/rel_detailed.txt +33 -0
- autorag/evaluation/metric/generation.py +504 -0
- autorag/evaluation/metric/retrieval.py +115 -0
- autorag/evaluation/metric/retrieval_contents.py +65 -0
- autorag/evaluation/metric/util.py +88 -0
- autorag/evaluation/retrieval.py +83 -0
- autorag/evaluation/retrieval_contents.py +65 -0
- autorag/evaluation/util.py +43 -0
- autorag/evaluator.py +559 -0
- autorag/node_line.py +65 -0
- autorag/nodes/__init__.py +0 -0
- autorag/nodes/generator/__init__.py +4 -0
- autorag/nodes/generator/base.py +103 -0
- autorag/nodes/generator/llama_index_llm.py +169 -0
- autorag/nodes/generator/openai_llm.py +329 -0
- autorag/nodes/generator/run.py +148 -0
- autorag/nodes/generator/vllm.py +147 -0
- autorag/nodes/generator/vllm_api.py +191 -0
- autorag/nodes/hybridretrieval/__init__.py +2 -0
- autorag/nodes/hybridretrieval/base.py +58 -0
- autorag/nodes/hybridretrieval/hybrid_cc.py +227 -0
- autorag/nodes/hybridretrieval/hybrid_rrf.py +149 -0
- autorag/nodes/hybridretrieval/run.py +137 -0
- autorag/nodes/lexicalretrieval/__init__.py +1 -0
- autorag/nodes/lexicalretrieval/bm25.py +381 -0
- autorag/nodes/lexicalretrieval/run.py +148 -0
- autorag/nodes/passageaugmenter/__init__.py +2 -0
- autorag/nodes/passageaugmenter/base.py +76 -0
- autorag/nodes/passageaugmenter/pass_passage_augmenter.py +43 -0
- autorag/nodes/passageaugmenter/prev_next_augmenter.py +155 -0
- autorag/nodes/passageaugmenter/run.py +131 -0
- autorag/nodes/passagecompressor/__init__.py +4 -0
- autorag/nodes/passagecompressor/base.py +78 -0
- autorag/nodes/passagecompressor/longllmlingua.py +115 -0
- autorag/nodes/passagecompressor/pass_compressor.py +16 -0
- autorag/nodes/passagecompressor/refine.py +54 -0
- autorag/nodes/passagecompressor/run.py +186 -0
- autorag/nodes/passagecompressor/tree_summarize.py +56 -0
- autorag/nodes/passagefilter/__init__.py +6 -0
- autorag/nodes/passagefilter/base.py +40 -0
- autorag/nodes/passagefilter/pass_passage_filter.py +14 -0
- autorag/nodes/passagefilter/percentile_cutoff.py +58 -0
- autorag/nodes/passagefilter/recency.py +105 -0
- autorag/nodes/passagefilter/run.py +138 -0
- autorag/nodes/passagefilter/similarity_percentile_cutoff.py +134 -0
- autorag/nodes/passagefilter/similarity_threshold_cutoff.py +112 -0
- autorag/nodes/passagefilter/threshold_cutoff.py +78 -0
- autorag/nodes/passagereranker/__init__.py +16 -0
- autorag/nodes/passagereranker/base.py +44 -0
- autorag/nodes/passagereranker/cohere.py +118 -0
- autorag/nodes/passagereranker/colbert.py +213 -0
- autorag/nodes/passagereranker/flag_embedding.py +112 -0
- autorag/nodes/passagereranker/flag_embedding_llm.py +101 -0
- autorag/nodes/passagereranker/flashrank.py +245 -0
- autorag/nodes/passagereranker/jina.py +115 -0
- autorag/nodes/passagereranker/koreranker.py +136 -0
- autorag/nodes/passagereranker/mixedbreadai.py +126 -0
- autorag/nodes/passagereranker/monot5.py +190 -0
- autorag/nodes/passagereranker/openvino.py +191 -0
- autorag/nodes/passagereranker/pass_reranker.py +31 -0
- autorag/nodes/passagereranker/rankgpt.py +170 -0
- autorag/nodes/passagereranker/run.py +145 -0
- autorag/nodes/passagereranker/sentence_transformer.py +129 -0
- autorag/nodes/passagereranker/tart/__init__.py +1 -0
- autorag/nodes/passagereranker/tart/modeling_enc_t5.py +152 -0
- autorag/nodes/passagereranker/tart/tart.py +139 -0
- autorag/nodes/passagereranker/tart/tokenization_enc_t5.py +112 -0
- autorag/nodes/passagereranker/time_reranker.py +72 -0
- autorag/nodes/passagereranker/upr.py +160 -0
- autorag/nodes/passagereranker/voyageai.py +109 -0
- autorag/nodes/promptmaker/__init__.py +12 -0
- autorag/nodes/promptmaker/base.py +32 -0
- autorag/nodes/promptmaker/chat_fstring.py +73 -0
- autorag/nodes/promptmaker/fstring.py +49 -0
- autorag/nodes/promptmaker/long_context_reorder.py +83 -0
- autorag/nodes/promptmaker/run.py +283 -0
- autorag/nodes/promptmaker/window_replacement.py +85 -0
- autorag/nodes/queryexpansion/__init__.py +4 -0
- autorag/nodes/queryexpansion/base.py +62 -0
- autorag/nodes/queryexpansion/hyde.py +43 -0
- autorag/nodes/queryexpansion/multi_query_expansion.py +57 -0
- autorag/nodes/queryexpansion/pass_query_expansion.py +22 -0
- autorag/nodes/queryexpansion/query_decompose.py +111 -0
- autorag/nodes/queryexpansion/run.py +308 -0
- autorag/nodes/retrieval/__init__.py +0 -0
- autorag/nodes/retrieval/base.py +127 -0
- autorag/nodes/retrieval/run_util.py +152 -0
- autorag/nodes/semanticretrieval/__init__.py +1 -0
- autorag/nodes/semanticretrieval/run.py +148 -0
- autorag/nodes/semanticretrieval/vectordb.py +339 -0
- autorag/nodes/util.py +16 -0
- autorag/parser.py +37 -0
- autorag/schema/__init__.py +3 -0
- autorag/schema/base.py +35 -0
- autorag/schema/metricinput.py +99 -0
- autorag/schema/module.py +24 -0
- autorag/schema/node.py +144 -0
- autorag/strategy.py +165 -0
- autorag/support.py +235 -0
- autorag/utils/__init__.py +8 -0
- autorag/utils/cast.py +45 -0
- autorag/utils/preprocess.py +149 -0
- autorag/utils/util.py +759 -0
- autorag/validator.py +98 -0
- autorag/vectordb/__init__.py +75 -0
- autorag/vectordb/base.py +73 -0
- autorag/vectordb/chroma.py +118 -0
- autorag/vectordb/couchbase.py +239 -0
- autorag/vectordb/milvus.py +169 -0
- autorag/vectordb/pinecone.py +121 -0
- autorag/vectordb/qdrant.py +155 -0
- autorag/vectordb/weaviate.py +184 -0
- autorag/web.py +81 -0
- autorag-0.0.0.dist-info/METADATA +780 -0
- autorag-0.0.0.dist-info/RECORD +184 -0
- autorag-0.0.0.dist-info/WHEEL +5 -0
- autorag-0.0.0.dist-info/entry_points.txt +2 -0
- autorag-0.0.0.dist-info/licenses/LICENSE +201 -0
- 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
|