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,186 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
import pathlib
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from autorag.evaluation.metric import (
|
|
8
|
+
retrieval_token_recall,
|
|
9
|
+
retrieval_token_precision,
|
|
10
|
+
retrieval_token_f1,
|
|
11
|
+
)
|
|
12
|
+
from autorag.schema.metricinput import MetricInput
|
|
13
|
+
from autorag.strategy import measure_speed, filter_by_threshold, select_best
|
|
14
|
+
from autorag.utils.util import fetch_contents
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_passage_compressor_node(
|
|
18
|
+
modules: List,
|
|
19
|
+
module_params: List[Dict],
|
|
20
|
+
previous_result: pd.DataFrame,
|
|
21
|
+
node_line_dir: str,
|
|
22
|
+
strategies: Dict,
|
|
23
|
+
) -> pd.DataFrame:
|
|
24
|
+
"""
|
|
25
|
+
Run evaluation and select the best module among passage compressor modules.
|
|
26
|
+
|
|
27
|
+
:param modules: Passage compressor modules to run.
|
|
28
|
+
:param module_params: Passage compressor module parameters.
|
|
29
|
+
:param previous_result: Previous result dataframe.
|
|
30
|
+
Could be retrieval, reranker modules result.
|
|
31
|
+
It means it must contain 'query', 'retrieved_contents', 'retrieved_ids', 'retrieve_scores' columns.
|
|
32
|
+
:param node_line_dir: This node line's directory.
|
|
33
|
+
:param strategies: Strategies for passage compressor node.
|
|
34
|
+
In this node, we use
|
|
35
|
+
You can skip evaluation when you use only one module and a module parameter.
|
|
36
|
+
:return: The best result dataframe with previous result columns.
|
|
37
|
+
This node will replace 'retrieved_contents' to compressed passages, so its length will be one.
|
|
38
|
+
"""
|
|
39
|
+
if not os.path.exists(node_line_dir):
|
|
40
|
+
os.makedirs(node_line_dir)
|
|
41
|
+
project_dir = pathlib.PurePath(node_line_dir).parent.parent
|
|
42
|
+
data_dir = os.path.join(project_dir, "data")
|
|
43
|
+
save_dir = os.path.join(node_line_dir, "passage_compressor")
|
|
44
|
+
if not os.path.exists(save_dir):
|
|
45
|
+
os.makedirs(save_dir)
|
|
46
|
+
|
|
47
|
+
# make retrieval contents gt
|
|
48
|
+
qa_data = pd.read_parquet(os.path.join(data_dir, "qa.parquet"), engine="pyarrow")
|
|
49
|
+
corpus_data = pd.read_parquet(
|
|
50
|
+
os.path.join(data_dir, "corpus.parquet"), engine="pyarrow"
|
|
51
|
+
)
|
|
52
|
+
# check qa_data have retrieval_gt
|
|
53
|
+
assert all(len(x[0]) > 0 for x in qa_data["retrieval_gt"].tolist()), (
|
|
54
|
+
"Can't use passage compressor if you don't have retrieval gt values in QA dataset."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# run modules
|
|
58
|
+
results, execution_times = zip(
|
|
59
|
+
*map(
|
|
60
|
+
lambda task: measure_speed(
|
|
61
|
+
task[0].run_evaluator,
|
|
62
|
+
project_dir=project_dir,
|
|
63
|
+
previous_result=previous_result,
|
|
64
|
+
**task[1],
|
|
65
|
+
),
|
|
66
|
+
zip(modules, module_params),
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
results = list(results)
|
|
70
|
+
average_times = list(map(lambda x: x / len(results[0]), execution_times))
|
|
71
|
+
|
|
72
|
+
retrieval_gt_contents = list(
|
|
73
|
+
map(lambda x: fetch_contents(corpus_data, x), qa_data["retrieval_gt"].tolist())
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
metric_inputs = [
|
|
77
|
+
MetricInput(retrieval_gt_contents=ret_cont_gt)
|
|
78
|
+
for ret_cont_gt in retrieval_gt_contents
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
# run metrics before filtering
|
|
82
|
+
if strategies.get("metrics") is None:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"You must at least one metrics for retrieval contents evaluation."
|
|
85
|
+
"It can be 'retrieval_token_f1', 'retrieval_token_precision', 'retrieval_token_recall'."
|
|
86
|
+
)
|
|
87
|
+
results = list(
|
|
88
|
+
map(
|
|
89
|
+
lambda x: evaluate_passage_compressor_node(
|
|
90
|
+
x, metric_inputs, strategies.get("metrics")
|
|
91
|
+
),
|
|
92
|
+
results,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# save results to folder
|
|
97
|
+
filepaths = list(
|
|
98
|
+
map(lambda x: os.path.join(save_dir, f"{x}.parquet"), range(len(modules)))
|
|
99
|
+
)
|
|
100
|
+
list(
|
|
101
|
+
map(lambda x: x[0].to_parquet(x[1], index=False), zip(results, filepaths))
|
|
102
|
+
) # execute save to parquet
|
|
103
|
+
filenames = list(map(lambda x: os.path.basename(x), filepaths))
|
|
104
|
+
|
|
105
|
+
# make summary file
|
|
106
|
+
summary_df = pd.DataFrame(
|
|
107
|
+
{
|
|
108
|
+
"filename": filenames,
|
|
109
|
+
"module_name": list(map(lambda module: module.__name__, modules)),
|
|
110
|
+
"module_params": module_params,
|
|
111
|
+
"execution_time": average_times,
|
|
112
|
+
**{
|
|
113
|
+
f"passage_compressor_{metric}": list(
|
|
114
|
+
map(lambda result: result[metric].mean(), results)
|
|
115
|
+
)
|
|
116
|
+
for metric in strategies.get("metrics")
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# filter by strategies
|
|
122
|
+
if strategies.get("speed_threshold") is not None:
|
|
123
|
+
results, filenames = filter_by_threshold(
|
|
124
|
+
results, average_times, strategies["speed_threshold"], filenames
|
|
125
|
+
)
|
|
126
|
+
selected_result, selected_filename = select_best(
|
|
127
|
+
results,
|
|
128
|
+
strategies.get("metrics"),
|
|
129
|
+
filenames,
|
|
130
|
+
strategies.get("strategy", "mean"),
|
|
131
|
+
)
|
|
132
|
+
new_retrieved_contents = selected_result["retrieved_contents"]
|
|
133
|
+
previous_result["retrieved_contents"] = new_retrieved_contents
|
|
134
|
+
selected_result = selected_result.drop(columns=["retrieved_contents"])
|
|
135
|
+
best_result = pd.concat([previous_result, selected_result], axis=1)
|
|
136
|
+
|
|
137
|
+
# add 'is_best' column to summary file
|
|
138
|
+
summary_df["is_best"] = summary_df["filename"] == selected_filename
|
|
139
|
+
|
|
140
|
+
# add prefix 'passage_compressor' to best_result columns
|
|
141
|
+
best_result = best_result.rename(
|
|
142
|
+
columns={
|
|
143
|
+
metric_name: f"passage_compressor_{metric_name}"
|
|
144
|
+
for metric_name in strategies.get("metrics")
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# save the result files
|
|
149
|
+
best_result.to_parquet(
|
|
150
|
+
os.path.join(
|
|
151
|
+
save_dir, f"best_{os.path.splitext(selected_filename)[0]}.parquet"
|
|
152
|
+
),
|
|
153
|
+
index=False,
|
|
154
|
+
)
|
|
155
|
+
summary_df.to_csv(os.path.join(save_dir, "summary.csv"), index=False)
|
|
156
|
+
return best_result
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def evaluate_passage_compressor_node(
|
|
160
|
+
result_df: pd.DataFrame, metric_inputs: List[MetricInput], metrics: List[str]
|
|
161
|
+
):
|
|
162
|
+
metric_funcs = {
|
|
163
|
+
retrieval_token_recall.__name__: retrieval_token_recall,
|
|
164
|
+
retrieval_token_precision.__name__: retrieval_token_precision,
|
|
165
|
+
retrieval_token_f1.__name__: retrieval_token_f1,
|
|
166
|
+
}
|
|
167
|
+
for metric_input, generated_text in zip(
|
|
168
|
+
metric_inputs, result_df["retrieved_contents"].tolist()
|
|
169
|
+
):
|
|
170
|
+
metric_input.retrieved_contents = generated_text
|
|
171
|
+
metrics = list(filter(lambda x: x in metric_funcs.keys(), metrics))
|
|
172
|
+
if len(metrics) <= 0:
|
|
173
|
+
raise ValueError(f"metrics must be one of {metric_funcs.keys()}")
|
|
174
|
+
metrics_scores = dict(
|
|
175
|
+
map(
|
|
176
|
+
lambda metric: (
|
|
177
|
+
metric,
|
|
178
|
+
metric_funcs[metric](
|
|
179
|
+
metric_inputs=metric_inputs,
|
|
180
|
+
),
|
|
181
|
+
),
|
|
182
|
+
metrics,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
result_df = pd.concat([result_df, pd.DataFrame(metrics_scores)], axis=1)
|
|
186
|
+
return result_df
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from llama_index.core import PromptTemplate
|
|
4
|
+
from llama_index.core.prompts import PromptType
|
|
5
|
+
from llama_index.core.prompts.utils import is_chat_model
|
|
6
|
+
from llama_index.core.response_synthesizers import TreeSummarize as ts
|
|
7
|
+
|
|
8
|
+
from autorag.nodes.passagecompressor.base import LlamaIndexCompressor
|
|
9
|
+
from autorag.utils.util import get_event_loop, process_batch
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TreeSummarize(LlamaIndexCompressor):
|
|
13
|
+
def _pure(
|
|
14
|
+
self,
|
|
15
|
+
queries: List[str],
|
|
16
|
+
contents: List[List[str]],
|
|
17
|
+
prompt: Optional[str] = None,
|
|
18
|
+
chat_prompt: Optional[str] = None,
|
|
19
|
+
batch: int = 16,
|
|
20
|
+
) -> List[str]:
|
|
21
|
+
"""
|
|
22
|
+
Recursively merge retrieved texts and summarizes them in a bottom-up fashion.
|
|
23
|
+
This function is a wrapper for llama_index.response_synthesizers.TreeSummarize.
|
|
24
|
+
For more information, visit https://docs.llamaindex.ai/en/latest/examples/response_synthesizers/tree_summarize.html.
|
|
25
|
+
|
|
26
|
+
:param queries: The queries for retrieved passages.
|
|
27
|
+
:param contents: The contents of retrieved passages.
|
|
28
|
+
:param prompt: The prompt template for summarization.
|
|
29
|
+
If you want to use chat prompt, you should pass chat_prompt instead.
|
|
30
|
+
At prompt, you must specify where to put 'context_str' and 'query_str'.
|
|
31
|
+
Default is None. When it is None, it will use llama index default prompt.
|
|
32
|
+
:param chat_prompt: The chat prompt template for summarization.
|
|
33
|
+
If you want to use normal prompt, you should pass prompt instead.
|
|
34
|
+
At prompt, you must specify where to put 'context_str' and 'query_str'.
|
|
35
|
+
Default is None. When it is None, it will use llama index default chat prompt.
|
|
36
|
+
:param batch: The batch size for llm.
|
|
37
|
+
Set low if you face some errors.
|
|
38
|
+
Default is 16.
|
|
39
|
+
:return: The list of compressed texts.
|
|
40
|
+
"""
|
|
41
|
+
if prompt is not None and not is_chat_model(self.llm):
|
|
42
|
+
summary_template = PromptTemplate(prompt, prompt_type=PromptType.SUMMARY)
|
|
43
|
+
elif chat_prompt is not None and is_chat_model(self.llm):
|
|
44
|
+
summary_template = PromptTemplate(
|
|
45
|
+
chat_prompt, prompt_type=PromptType.SUMMARY
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
summary_template = None
|
|
49
|
+
summarizer = ts(llm=self.llm, summary_template=summary_template, use_async=True)
|
|
50
|
+
tasks = [
|
|
51
|
+
summarizer.aget_response(query, content)
|
|
52
|
+
for query, content in zip(queries, contents)
|
|
53
|
+
]
|
|
54
|
+
loop = get_event_loop()
|
|
55
|
+
results = loop.run_until_complete(process_batch(tasks, batch_size=batch))
|
|
56
|
+
return results
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .pass_passage_filter import PassPassageFilter
|
|
2
|
+
from .percentile_cutoff import PercentileCutoff
|
|
3
|
+
from .recency import RecencyFilter
|
|
4
|
+
from .similarity_percentile_cutoff import SimilarityPercentileCutoff
|
|
5
|
+
from .similarity_threshold_cutoff import SimilarityThresholdCutoff
|
|
6
|
+
from .threshold_cutoff import ThresholdCutoff
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from autorag.schema.base import BaseModule
|
|
9
|
+
from autorag.utils import validate_qa_dataset
|
|
10
|
+
from autorag.utils.cast import cast_retrieve_infos
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("AutoRAG")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BasePassageFilter(BaseModule, metaclass=abc.ABCMeta):
|
|
16
|
+
def __init__(self, project_dir: Union[str, Path], *args, **kwargs):
|
|
17
|
+
logger.info(f"Initialize passage filter node - {self.__class__.__name__}")
|
|
18
|
+
|
|
19
|
+
def __del__(self):
|
|
20
|
+
logger.info(f"Prompt maker node - {self.__class__.__name__} module is deleted.")
|
|
21
|
+
|
|
22
|
+
def cast_to_run(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
23
|
+
logger.info(
|
|
24
|
+
f"Running passage filter node - {self.__class__.__name__} module..."
|
|
25
|
+
)
|
|
26
|
+
validate_qa_dataset(previous_result)
|
|
27
|
+
|
|
28
|
+
# find queries columns
|
|
29
|
+
assert "query" in previous_result.columns, (
|
|
30
|
+
"previous_result must have query column."
|
|
31
|
+
)
|
|
32
|
+
queries = previous_result["query"].tolist()
|
|
33
|
+
|
|
34
|
+
retrieve_infos = cast_retrieve_infos(previous_result)
|
|
35
|
+
return (
|
|
36
|
+
queries,
|
|
37
|
+
retrieve_infos["retrieved_contents"],
|
|
38
|
+
retrieve_infos["retrieve_scores"],
|
|
39
|
+
retrieve_infos["retrieved_ids"],
|
|
40
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from autorag.nodes.passagefilter.base import BasePassageFilter
|
|
4
|
+
from autorag.utils import result_to_dataframe
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PassPassageFilter(BasePassageFilter):
|
|
8
|
+
@result_to_dataframe(["retrieved_contents", "retrieved_ids", "retrieve_scores"])
|
|
9
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
10
|
+
_, contents, scores, ids = self.cast_to_run(previous_result)
|
|
11
|
+
return contents, ids, scores
|
|
12
|
+
|
|
13
|
+
def _pure(self, *args, **kwargs):
|
|
14
|
+
pass
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from autorag.nodes.passagefilter.base import BasePassageFilter
|
|
6
|
+
from autorag.utils.util import sort_by_scores, select_top_k, result_to_dataframe
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PercentileCutoff(BasePassageFilter):
|
|
10
|
+
@result_to_dataframe(["retrieved_contents", "retrieved_ids", "retrieve_scores"])
|
|
11
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
12
|
+
queries, contents, scores, ids = self.cast_to_run(previous_result)
|
|
13
|
+
return self._pure(queries, contents, scores, ids, *args, **kwargs)
|
|
14
|
+
|
|
15
|
+
def _pure(
|
|
16
|
+
self,
|
|
17
|
+
queries: List[str],
|
|
18
|
+
contents_list: List[List[str]],
|
|
19
|
+
scores_list: List[List[float]],
|
|
20
|
+
ids_list: List[List[str]],
|
|
21
|
+
percentile: float,
|
|
22
|
+
reverse: bool = False,
|
|
23
|
+
) -> Tuple[List[List[str]], List[List[str]], List[List[float]]]:
|
|
24
|
+
"""
|
|
25
|
+
Filter out the contents that are below the content's length times percentile.
|
|
26
|
+
If This is a filter and does not override scores.
|
|
27
|
+
If the value of content's length times percentile is less than 1, keep the only one highest similarity content.
|
|
28
|
+
|
|
29
|
+
:param queries: The list of queries to use for filtering
|
|
30
|
+
:param contents_list: The list of lists of contents to filter
|
|
31
|
+
:param scores_list: The list of lists of scores retrieved
|
|
32
|
+
:param ids_list: The list of lists of ids retrieved
|
|
33
|
+
:param percentile: The percentile to cut off
|
|
34
|
+
:param reverse: If True, the lower the score, the better
|
|
35
|
+
Default is False.
|
|
36
|
+
:return: Tuple of lists containing the filtered contents, ids, and scores
|
|
37
|
+
"""
|
|
38
|
+
num_top_k = max(1, int(len(scores_list[0]) * percentile))
|
|
39
|
+
|
|
40
|
+
df = pd.DataFrame(
|
|
41
|
+
{
|
|
42
|
+
"contents": contents_list,
|
|
43
|
+
"ids": ids_list,
|
|
44
|
+
"scores": scores_list,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
reverse = not reverse
|
|
49
|
+
df[["contents", "ids", "scores"]] = df.apply(
|
|
50
|
+
sort_by_scores, axis=1, result_type="expand", reverse=reverse
|
|
51
|
+
)
|
|
52
|
+
results = select_top_k(df, ["contents", "ids", "scores"], num_top_k)
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
results["contents"].tolist(),
|
|
56
|
+
results["ids"].tolist(),
|
|
57
|
+
results["scores"].tolist(),
|
|
58
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime, date
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Tuple, Union
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from autorag.nodes.passagefilter.base import BasePassageFilter
|
|
10
|
+
from autorag.utils import fetch_contents, result_to_dataframe
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("AutoRAG")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RecencyFilter(BasePassageFilter):
|
|
16
|
+
def __init__(self, project_dir: Union[str, Path], *args, **kwargs):
|
|
17
|
+
super().__init__(project_dir, *args, **kwargs)
|
|
18
|
+
self.corpus_df = pd.read_parquet(
|
|
19
|
+
os.path.join(project_dir, "data", "corpus.parquet"), engine="pyarrow"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
@result_to_dataframe(["retrieved_contents", "retrieved_ids", "retrieve_scores"])
|
|
23
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
24
|
+
_, contents, scores, ids = self.cast_to_run(previous_result, *args, **kwargs)
|
|
25
|
+
metadatas = fetch_contents(self.corpus_df, ids, column_name="metadata")
|
|
26
|
+
times = [
|
|
27
|
+
[time["last_modified_datetime"] for time in time_list]
|
|
28
|
+
for time_list in metadatas
|
|
29
|
+
]
|
|
30
|
+
return self._pure(contents, scores, ids, times, *args, **kwargs)
|
|
31
|
+
|
|
32
|
+
def _pure(
|
|
33
|
+
self,
|
|
34
|
+
contents_list: List[List[str]],
|
|
35
|
+
scores_list: List[List[float]],
|
|
36
|
+
ids_list: List[List[str]],
|
|
37
|
+
time_list: List[List[datetime]],
|
|
38
|
+
threshold_datetime: Union[datetime, date],
|
|
39
|
+
) -> Tuple[List[List[str]], List[List[str]], List[List[float]]]:
|
|
40
|
+
"""
|
|
41
|
+
Filter out the contents that are below the threshold datetime.
|
|
42
|
+
If all contents are filtered, keep the only one recency content.
|
|
43
|
+
If the threshold date format is incorrect, return the original contents.
|
|
44
|
+
|
|
45
|
+
:param contents_list: The list of lists of contents to filter
|
|
46
|
+
:param scores_list: The list of lists of scores retrieved
|
|
47
|
+
:param ids_list: The list of lists of ids retrieved
|
|
48
|
+
:param time_list: The list of lists of datetime retrieved
|
|
49
|
+
:param threshold_datetime: The threshold to cut off.
|
|
50
|
+
In recency filter, you have to use the datetime.datetime object or datetime.date object.
|
|
51
|
+
All you need to do is to set the date at your YAML file.
|
|
52
|
+
For example, you can write "2010-09-09 3:45:06" or "2010-09-09" in the YAML file.
|
|
53
|
+
:return: Tuple of lists containing the filtered contents, ids, and scores
|
|
54
|
+
"""
|
|
55
|
+
if not (
|
|
56
|
+
isinstance(threshold_datetime, datetime)
|
|
57
|
+
or isinstance(threshold_datetime, date)
|
|
58
|
+
):
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Threshold should be a datetime object, but got {type(threshold_datetime)}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if not isinstance(threshold_datetime, datetime):
|
|
64
|
+
threshold_datetime = datetime.combine(
|
|
65
|
+
threshold_datetime, datetime.min.time()
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
time_list = [
|
|
69
|
+
list(
|
|
70
|
+
map(
|
|
71
|
+
lambda t: datetime.combine(t, datetime.min.time())
|
|
72
|
+
if not isinstance(t, datetime)
|
|
73
|
+
else t,
|
|
74
|
+
time,
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
for time in time_list
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
def sort_row(contents, scores, ids, time, _datetime_threshold):
|
|
81
|
+
combined = list(zip(contents, scores, ids, time))
|
|
82
|
+
combined_filtered = [
|
|
83
|
+
item for item in combined if item[3] >= _datetime_threshold
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
if combined_filtered:
|
|
87
|
+
remain_contents, remain_scores, remain_ids, _ = zip(*combined_filtered)
|
|
88
|
+
else:
|
|
89
|
+
combined.sort(key=lambda x: x[3], reverse=True)
|
|
90
|
+
remain_contents, remain_scores, remain_ids, _ = zip(*combined[:1])
|
|
91
|
+
|
|
92
|
+
return list(remain_contents), list(remain_ids), list(remain_scores)
|
|
93
|
+
|
|
94
|
+
remain_contents_list, remain_ids_list, remain_scores_list = zip(
|
|
95
|
+
*map(
|
|
96
|
+
sort_row,
|
|
97
|
+
contents_list,
|
|
98
|
+
scores_list,
|
|
99
|
+
ids_list,
|
|
100
|
+
time_list,
|
|
101
|
+
[threshold_datetime] * len(contents_list),
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return remain_contents_list, remain_ids_list, remain_scores_list
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from autorag.nodes.retrieval.run_util import evaluate_retrieval_node
|
|
8
|
+
from autorag.schema.metricinput import MetricInput
|
|
9
|
+
from autorag.strategy import measure_speed, filter_by_threshold, select_best
|
|
10
|
+
from autorag.utils.util import to_list, apply_recursive
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_passage_filter_node(
|
|
14
|
+
modules: List,
|
|
15
|
+
module_params: List[Dict],
|
|
16
|
+
previous_result: pd.DataFrame,
|
|
17
|
+
node_line_dir: str,
|
|
18
|
+
strategies: Dict,
|
|
19
|
+
) -> pd.DataFrame:
|
|
20
|
+
"""
|
|
21
|
+
Run evaluation and select the best module among passage filter node results.
|
|
22
|
+
|
|
23
|
+
:param modules: Passage filter modules to run.
|
|
24
|
+
:param module_params: Passage filter module parameters.
|
|
25
|
+
:param previous_result: Previous result dataframe.
|
|
26
|
+
Could be retrieval, reranker, passage filter modules result.
|
|
27
|
+
It means it must contain 'query', 'retrieved_contents', 'retrieved_ids', 'retrieve_scores' columns.
|
|
28
|
+
:param node_line_dir: This node line's directory.
|
|
29
|
+
:param strategies: Strategies for passage filter node.
|
|
30
|
+
In this node, we use 'retrieval_f1', 'retrieval_recall' and 'retrieval_precision'.
|
|
31
|
+
You can skip evaluation when you use only one module and a module parameter.
|
|
32
|
+
:return: The best result dataframe with previous 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
|
+
|
|
43
|
+
# make rows to metric_inputs
|
|
44
|
+
metric_inputs = [
|
|
45
|
+
MetricInput(retrieval_gt=ret_gt, query=query, generation_gt=gen_gt)
|
|
46
|
+
for ret_gt, query, gen_gt in zip(
|
|
47
|
+
retrieval_gt, qa_df["query"].tolist(), qa_df["generation_gt"].tolist()
|
|
48
|
+
)
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
results, execution_times = zip(
|
|
52
|
+
*map(
|
|
53
|
+
lambda task: measure_speed(
|
|
54
|
+
task[0].run_evaluator,
|
|
55
|
+
project_dir=project_dir,
|
|
56
|
+
previous_result=previous_result,
|
|
57
|
+
**task[1],
|
|
58
|
+
),
|
|
59
|
+
zip(modules, module_params),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
average_times = list(map(lambda x: x / len(results[0]), execution_times))
|
|
63
|
+
|
|
64
|
+
# run metrics before filtering
|
|
65
|
+
if strategies.get("metrics") is None:
|
|
66
|
+
raise ValueError("You must at least one metrics for passage_filter evaluation.")
|
|
67
|
+
results = list(
|
|
68
|
+
map(
|
|
69
|
+
lambda x: evaluate_retrieval_node(
|
|
70
|
+
x,
|
|
71
|
+
metric_inputs,
|
|
72
|
+
strategies.get("metrics"),
|
|
73
|
+
),
|
|
74
|
+
results,
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# save results to folder
|
|
79
|
+
save_dir = os.path.join(node_line_dir, "passage_filter") # node name
|
|
80
|
+
if not os.path.exists(save_dir):
|
|
81
|
+
os.makedirs(save_dir)
|
|
82
|
+
filepaths = list(
|
|
83
|
+
map(lambda x: os.path.join(save_dir, f"{x}.parquet"), range(len(modules)))
|
|
84
|
+
)
|
|
85
|
+
list(
|
|
86
|
+
map(lambda x: x[0].to_parquet(x[1], index=False), zip(results, filepaths))
|
|
87
|
+
) # execute save to parquet
|
|
88
|
+
filenames = list(map(lambda x: os.path.basename(x), filepaths))
|
|
89
|
+
|
|
90
|
+
summary_df = pd.DataFrame(
|
|
91
|
+
{
|
|
92
|
+
"filename": filenames,
|
|
93
|
+
"module_name": list(map(lambda module: module.__name__, modules)),
|
|
94
|
+
"module_params": module_params,
|
|
95
|
+
"execution_time": average_times,
|
|
96
|
+
**{
|
|
97
|
+
f"passage_filter_{metric}": list(
|
|
98
|
+
map(lambda result: result[metric].mean(), results)
|
|
99
|
+
)
|
|
100
|
+
for metric in strategies.get("metrics")
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# filter by strategies
|
|
106
|
+
if strategies.get("speed_threshold") is not None:
|
|
107
|
+
results, filenames = filter_by_threshold(
|
|
108
|
+
results, average_times, strategies["speed_threshold"], filenames
|
|
109
|
+
)
|
|
110
|
+
selected_result, selected_filename = select_best(
|
|
111
|
+
results,
|
|
112
|
+
strategies.get("metrics"),
|
|
113
|
+
filenames,
|
|
114
|
+
strategies.get("strategy", "mean"),
|
|
115
|
+
)
|
|
116
|
+
selected_result = selected_result.rename(
|
|
117
|
+
columns={
|
|
118
|
+
metric_name: f"passage_filter_{metric_name}"
|
|
119
|
+
for metric_name in strategies["metrics"]
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
previous_result = previous_result.drop(
|
|
123
|
+
columns=["retrieved_contents", "retrieved_ids", "retrieve_scores"]
|
|
124
|
+
)
|
|
125
|
+
best_result = pd.concat([previous_result, selected_result], axis=1)
|
|
126
|
+
|
|
127
|
+
# add 'is_best' column to summary file
|
|
128
|
+
summary_df["is_best"] = summary_df["filename"] == selected_filename
|
|
129
|
+
|
|
130
|
+
# save files
|
|
131
|
+
summary_df.to_csv(os.path.join(save_dir, "summary.csv"), index=False)
|
|
132
|
+
best_result.to_parquet(
|
|
133
|
+
os.path.join(
|
|
134
|
+
save_dir, f"best_{os.path.splitext(selected_filename)[0]}.parquet"
|
|
135
|
+
),
|
|
136
|
+
index=False,
|
|
137
|
+
)
|
|
138
|
+
return best_result
|