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,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from autorag.nodes.promptmaker.base import BasePromptMaker
|
|
8
|
+
from autorag.utils import result_to_dataframe, fetch_contents
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("AutoRAG")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WindowReplacement(BasePromptMaker):
|
|
14
|
+
def __init__(self, project_dir: str, *args, **kwargs):
|
|
15
|
+
super().__init__(project_dir, *args, **kwargs)
|
|
16
|
+
# load corpus
|
|
17
|
+
data_dir = os.path.join(project_dir, "data")
|
|
18
|
+
self.corpus_data = pd.read_parquet(
|
|
19
|
+
os.path.join(data_dir, "corpus.parquet"), engine="pyarrow"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
@result_to_dataframe(["prompts"])
|
|
23
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
24
|
+
query, retrieved_contents, prompt = self.cast_to_run(
|
|
25
|
+
previous_result, *args, **kwargs
|
|
26
|
+
)
|
|
27
|
+
retrieved_ids = previous_result["retrieved_ids"].tolist()
|
|
28
|
+
# get metadata from corpus
|
|
29
|
+
retrieved_metadata = fetch_contents(
|
|
30
|
+
self.corpus_data, retrieved_ids, column_name="metadata"
|
|
31
|
+
)
|
|
32
|
+
return self._pure(prompt, query, retrieved_contents, retrieved_metadata)
|
|
33
|
+
|
|
34
|
+
def _pure(
|
|
35
|
+
self,
|
|
36
|
+
prompt: str,
|
|
37
|
+
queries: List[str],
|
|
38
|
+
retrieved_contents: List[List[str]],
|
|
39
|
+
retrieved_metadata: List[List[Dict]],
|
|
40
|
+
) -> List[str]:
|
|
41
|
+
"""
|
|
42
|
+
Replace retrieved_contents with a window to create a Prompt
|
|
43
|
+
(only available for corpus chunked with Sentence window method)
|
|
44
|
+
You must type a prompt or prompt list at a config YAML file like this:
|
|
45
|
+
|
|
46
|
+
.. Code:: yaml
|
|
47
|
+
nodes:
|
|
48
|
+
- node_type: prompt_maker
|
|
49
|
+
modules:
|
|
50
|
+
- module_type: window_replacement
|
|
51
|
+
prompt: [Answer this question: {query} \n\n {retrieved_contents},
|
|
52
|
+
Read the passages carefully and answer this question: {query} \n\n Passages: {retrieved_contents}]
|
|
53
|
+
|
|
54
|
+
:param prompt: A prompt string.
|
|
55
|
+
:param queries: List of query strings.
|
|
56
|
+
:param retrieved_contents: List of retrieved contents.
|
|
57
|
+
:param retrieved_metadata: List of retrieved metadata.
|
|
58
|
+
:return: Prompts that are made by window_replacement.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def window_replacement_row(
|
|
62
|
+
_prompt: str,
|
|
63
|
+
_query: str,
|
|
64
|
+
_retrieved_contents,
|
|
65
|
+
_retrieved_metadata: List[Dict],
|
|
66
|
+
) -> str:
|
|
67
|
+
window_list = []
|
|
68
|
+
for content, metadata in zip(_retrieved_contents, _retrieved_metadata):
|
|
69
|
+
if "window" in metadata:
|
|
70
|
+
window_list.append(metadata["window"])
|
|
71
|
+
else:
|
|
72
|
+
window_list.append(content)
|
|
73
|
+
logger.info(
|
|
74
|
+
"Only available for corpus chunked with Sentence window method."
|
|
75
|
+
"window_replacement will not proceed."
|
|
76
|
+
)
|
|
77
|
+
contents_str = "\n\n".join(window_list)
|
|
78
|
+
return _prompt.format(query=_query, retrieved_contents=contents_str)
|
|
79
|
+
|
|
80
|
+
return list(
|
|
81
|
+
map(
|
|
82
|
+
lambda x: window_replacement_row(prompt, x[0], x[1], x[2]),
|
|
83
|
+
zip(queries, retrieved_contents, retrieved_metadata),
|
|
84
|
+
)
|
|
85
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Union
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from autorag.nodes.util import make_generator_callable_param
|
|
9
|
+
from autorag.schema import BaseModule
|
|
10
|
+
from autorag.utils import validate_qa_dataset
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("AutoRAG")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseQueryExpansion(BaseModule, metaclass=abc.ABCMeta):
|
|
16
|
+
def __init__(self, project_dir: Union[str, Path], *args, **kwargs):
|
|
17
|
+
logger.info(
|
|
18
|
+
f"Initialize query expansion node - {self.__class__.__name__} module..."
|
|
19
|
+
)
|
|
20
|
+
# set generator module for query expansion
|
|
21
|
+
generator_class, generator_param = make_generator_callable_param(kwargs)
|
|
22
|
+
self.generator = generator_class(project_dir, **generator_param)
|
|
23
|
+
|
|
24
|
+
def __del__(self):
|
|
25
|
+
del self.generator
|
|
26
|
+
logger.info(
|
|
27
|
+
f"Delete query expansion node - {self.__class__.__name__} module..."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def cast_to_run(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
31
|
+
logger.info(
|
|
32
|
+
f"Running query expansion node - {self.__class__.__name__} module..."
|
|
33
|
+
)
|
|
34
|
+
validate_qa_dataset(previous_result)
|
|
35
|
+
|
|
36
|
+
# find queries columns
|
|
37
|
+
assert "query" in previous_result.columns, (
|
|
38
|
+
"previous_result must have query column."
|
|
39
|
+
)
|
|
40
|
+
queries = previous_result["query"].tolist()
|
|
41
|
+
return queries
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _check_expanded_query(queries: List[str], expanded_queries: List[List[str]]):
|
|
45
|
+
return list(
|
|
46
|
+
map(
|
|
47
|
+
lambda query, expanded_query_list: check_expanded_query(
|
|
48
|
+
query, expanded_query_list
|
|
49
|
+
),
|
|
50
|
+
queries,
|
|
51
|
+
expanded_queries,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def check_expanded_query(query: str, expanded_query_list: List[str]):
|
|
57
|
+
# check if the expanded query is the same as the original query
|
|
58
|
+
expanded_query_list = list(map(lambda x: x.strip(), expanded_query_list))
|
|
59
|
+
return [
|
|
60
|
+
expanded_query if expanded_query else query
|
|
61
|
+
for expanded_query in expanded_query_list
|
|
62
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from autorag.nodes.queryexpansion.base import BaseQueryExpansion
|
|
6
|
+
from autorag.utils import result_to_dataframe
|
|
7
|
+
|
|
8
|
+
hyde_prompt = "Please write a passage to answer the question"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HyDE(BaseQueryExpansion):
|
|
12
|
+
@result_to_dataframe(["queries"])
|
|
13
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
14
|
+
queries = self.cast_to_run(previous_result, *args, **kwargs)
|
|
15
|
+
|
|
16
|
+
# pop prompt from kwargs
|
|
17
|
+
prompt = kwargs.pop("prompt", hyde_prompt)
|
|
18
|
+
kwargs.pop("generator_module_type", None)
|
|
19
|
+
|
|
20
|
+
expanded_queries = self._pure(queries, prompt, **kwargs)
|
|
21
|
+
return self._check_expanded_query(queries, expanded_queries)
|
|
22
|
+
|
|
23
|
+
def _pure(self, queries: List[str], prompt: str = hyde_prompt, **generator_params):
|
|
24
|
+
"""
|
|
25
|
+
HyDE, which inspired by "Precise Zero-shot Dense Retrieval without Relevance Labels" (https://arxiv.org/pdf/2212.10496.pdf)
|
|
26
|
+
LLM model creates a hypothetical passage.
|
|
27
|
+
And then, retrieve passages using hypothetical passage as a query.
|
|
28
|
+
:param queries: List[str], queries to retrieve.
|
|
29
|
+
:param prompt: Prompt to use when generating hypothetical passage
|
|
30
|
+
:return: List[List[str]], List of hyde results.
|
|
31
|
+
"""
|
|
32
|
+
full_prompts = list(
|
|
33
|
+
map(
|
|
34
|
+
lambda x: (prompt if not bool(prompt) else hyde_prompt)
|
|
35
|
+
+ f"\nQuestion: {x}\nPassage:",
|
|
36
|
+
queries,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
input_df = pd.DataFrame({"prompts": full_prompts})
|
|
40
|
+
result_df = self.generator.pure(previous_result=input_df, **generator_params)
|
|
41
|
+
answers = result_df["generated_texts"].tolist()
|
|
42
|
+
results = list(map(lambda x: [x], answers))
|
|
43
|
+
return results
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from autorag.nodes.queryexpansion.base import BaseQueryExpansion
|
|
6
|
+
from autorag.utils import result_to_dataframe
|
|
7
|
+
|
|
8
|
+
multi_query_expansion_prompt = """You are an AI language model assistant.
|
|
9
|
+
Your task is to generate 3 different versions of the given user
|
|
10
|
+
question to retrieve relevant documents from a vector database.
|
|
11
|
+
By generating multiple perspectives on the user question,
|
|
12
|
+
your goal is to help the user overcome some of the limitations
|
|
13
|
+
of distance-based similarity search. Provide these alternative
|
|
14
|
+
questions separated by newlines. Original question: {query}"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MultiQueryExpansion(BaseQueryExpansion):
|
|
18
|
+
@result_to_dataframe(["queries"])
|
|
19
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
20
|
+
queries = self.cast_to_run(previous_result, *args, **kwargs)
|
|
21
|
+
|
|
22
|
+
# pop prompt from kwargs
|
|
23
|
+
prompt = kwargs.pop("prompt", multi_query_expansion_prompt)
|
|
24
|
+
kwargs.pop("generator_module_type", None)
|
|
25
|
+
|
|
26
|
+
expanded_queries = self._pure(queries, prompt, **kwargs)
|
|
27
|
+
return self._check_expanded_query(queries, expanded_queries)
|
|
28
|
+
|
|
29
|
+
def _pure(
|
|
30
|
+
self, queries, prompt: str = multi_query_expansion_prompt, **kwargs
|
|
31
|
+
) -> List[List[str]]:
|
|
32
|
+
"""
|
|
33
|
+
Expand a list of queries using a multi-query expansion approach.
|
|
34
|
+
LLM model generate 3 different versions queries for each input query.
|
|
35
|
+
|
|
36
|
+
:param queries: List[str], queries to decompose.
|
|
37
|
+
:param prompt: str, prompt to use for multi-query expansion.
|
|
38
|
+
default prompt comes from langchain MultiQueryRetriever default query prompt.
|
|
39
|
+
:return: List[List[str]], list of expansion query.
|
|
40
|
+
"""
|
|
41
|
+
full_prompts = list(map(lambda x: prompt.format(query=x), queries))
|
|
42
|
+
input_df = pd.DataFrame({"prompts": full_prompts})
|
|
43
|
+
result_df = self.generator.pure(previous_result=input_df, **kwargs)
|
|
44
|
+
answers = result_df["generated_texts"].tolist()
|
|
45
|
+
results = list(
|
|
46
|
+
map(lambda x: get_multi_query_expansion(x[0], x[1]), zip(queries, answers))
|
|
47
|
+
)
|
|
48
|
+
return results
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_multi_query_expansion(query: str, answer: str) -> List[str]:
|
|
52
|
+
try:
|
|
53
|
+
queries = answer.split("\n")
|
|
54
|
+
queries.insert(0, query)
|
|
55
|
+
return queries
|
|
56
|
+
except:
|
|
57
|
+
return [query]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from autorag.nodes.queryexpansion.base import BaseQueryExpansion
|
|
4
|
+
from autorag.utils import result_to_dataframe
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PassQueryExpansion(BaseQueryExpansion):
|
|
8
|
+
@result_to_dataframe(["queries"])
|
|
9
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
10
|
+
"""
|
|
11
|
+
Do not perform query expansion.
|
|
12
|
+
Return with the same queries.
|
|
13
|
+
The dimension will be 2-d list, and the column name will be 'queries'.
|
|
14
|
+
"""
|
|
15
|
+
assert "query" in previous_result.columns, (
|
|
16
|
+
"previous_result must have query column."
|
|
17
|
+
)
|
|
18
|
+
queries = previous_result["query"].tolist()
|
|
19
|
+
return list(map(lambda x: [x], queries))
|
|
20
|
+
|
|
21
|
+
def _pure(self, *args, **kwargs):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from autorag.nodes.queryexpansion.base import BaseQueryExpansion
|
|
6
|
+
from autorag.utils import result_to_dataframe
|
|
7
|
+
|
|
8
|
+
decompose_prompt = """Decompose a question in self-contained sub-questions. Use \"The question needs no decomposition\" when no decomposition is needed.
|
|
9
|
+
|
|
10
|
+
Example 1:
|
|
11
|
+
|
|
12
|
+
Question: Is Hamlet more common on IMDB than Comedy of Errors?
|
|
13
|
+
Decompositions:
|
|
14
|
+
1: How many listings of Hamlet are there on IMDB?
|
|
15
|
+
2: How many listing of Comedy of Errors is there on IMDB?
|
|
16
|
+
|
|
17
|
+
Example 2:
|
|
18
|
+
|
|
19
|
+
Question: Are birds important to badminton?
|
|
20
|
+
|
|
21
|
+
Decompositions:
|
|
22
|
+
The question needs no decomposition
|
|
23
|
+
|
|
24
|
+
Example 3:
|
|
25
|
+
|
|
26
|
+
Question: Is it legal for a licensed child driving Mercedes-Benz to be employed in US?
|
|
27
|
+
|
|
28
|
+
Decompositions:
|
|
29
|
+
1: What is the minimum driving age in the US?
|
|
30
|
+
2: What is the minimum age for someone to be employed in the US?
|
|
31
|
+
|
|
32
|
+
Example 4:
|
|
33
|
+
|
|
34
|
+
Question: Are all cucumbers the same texture?
|
|
35
|
+
|
|
36
|
+
Decompositions:
|
|
37
|
+
The question needs no decomposition
|
|
38
|
+
|
|
39
|
+
Example 5:
|
|
40
|
+
|
|
41
|
+
Question: Hydrogen's atomic number squared exceeds number of Spice Girls?
|
|
42
|
+
|
|
43
|
+
Decompositions:
|
|
44
|
+
1: What is the atomic number of hydrogen?
|
|
45
|
+
2: How many Spice Girls are there?
|
|
46
|
+
|
|
47
|
+
Example 6:
|
|
48
|
+
|
|
49
|
+
Question: {question}
|
|
50
|
+
|
|
51
|
+
Decompositions:
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class QueryDecompose(BaseQueryExpansion):
|
|
56
|
+
@result_to_dataframe(["queries"])
|
|
57
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
58
|
+
queries = self.cast_to_run(previous_result, *args, **kwargs)
|
|
59
|
+
|
|
60
|
+
# pop prompt from kwargs
|
|
61
|
+
prompt = kwargs.pop("prompt", decompose_prompt)
|
|
62
|
+
kwargs.pop("generator_module_type", None)
|
|
63
|
+
|
|
64
|
+
expanded_queries = self._pure(queries, prompt, **kwargs)
|
|
65
|
+
return self._check_expanded_query(queries, expanded_queries)
|
|
66
|
+
|
|
67
|
+
def _pure(
|
|
68
|
+
self, queries: List[str], prompt: str = decompose_prompt, *args, **kwargs
|
|
69
|
+
) -> List[List[str]]:
|
|
70
|
+
"""
|
|
71
|
+
decompose query to little piece of questions.
|
|
72
|
+
:param queries: List[str], queries to decompose.
|
|
73
|
+
:param prompt: str, prompt to use for query decomposition.
|
|
74
|
+
default prompt comes from Visconde's StrategyQA few-shot prompt.
|
|
75
|
+
:return: List[List[str]], list of decomposed query. Return input query if query is not decomposable.
|
|
76
|
+
"""
|
|
77
|
+
full_prompts = []
|
|
78
|
+
for query in queries:
|
|
79
|
+
if bool(prompt):
|
|
80
|
+
full_prompt = f"prompt: {prompt}\n\n question: {query}"
|
|
81
|
+
else:
|
|
82
|
+
full_prompt = decompose_prompt.format(question=query)
|
|
83
|
+
full_prompts.append(full_prompt)
|
|
84
|
+
input_df = pd.DataFrame({"prompts": full_prompts})
|
|
85
|
+
result_df = self.generator.pure(previous_result=input_df, *args, **kwargs)
|
|
86
|
+
answers = result_df["generated_texts"].tolist()
|
|
87
|
+
results = list(
|
|
88
|
+
map(lambda x: get_query_decompose(x[0], x[1]), zip(queries, answers))
|
|
89
|
+
)
|
|
90
|
+
return results
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_query_decompose(query: str, answer: str) -> List[str]:
|
|
94
|
+
"""
|
|
95
|
+
decompose query to little piece of questions.
|
|
96
|
+
:param query: str, query to decompose.
|
|
97
|
+
:param answer: str, answer from query_decompose function.
|
|
98
|
+
:return: List[str], list of a decomposed query. Return input query if query is not decomposable.
|
|
99
|
+
"""
|
|
100
|
+
if answer.lower() == "the question needs no decomposition":
|
|
101
|
+
return [query]
|
|
102
|
+
try:
|
|
103
|
+
lines = [line.strip() for line in answer.splitlines() if line.strip()]
|
|
104
|
+
if lines[0].startswith("Decompositions:"):
|
|
105
|
+
lines.pop(0)
|
|
106
|
+
questions = [line.split(":", 1)[1].strip() for line in lines if ":" in line]
|
|
107
|
+
if not questions:
|
|
108
|
+
return [query]
|
|
109
|
+
return questions
|
|
110
|
+
except:
|
|
111
|
+
return [query]
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from typing import List, Dict, Optional, Union
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from autorag.evaluation import evaluate_retrieval
|
|
10
|
+
from autorag.schema.metricinput import MetricInput
|
|
11
|
+
from autorag.strategy import measure_speed, filter_by_threshold, select_best
|
|
12
|
+
from autorag.support import get_support_modules
|
|
13
|
+
from autorag.utils.cast import cast_retrieve_infos
|
|
14
|
+
from autorag.utils.util import make_combinations, explode
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("AutoRAG")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run_query_expansion_node(
|
|
20
|
+
modules: List,
|
|
21
|
+
module_params: List[Dict],
|
|
22
|
+
previous_result: pd.DataFrame,
|
|
23
|
+
node_line_dir: str,
|
|
24
|
+
strategies: Dict,
|
|
25
|
+
) -> pd.DataFrame:
|
|
26
|
+
"""
|
|
27
|
+
Run evaluation and select the best module among query expansion node results.
|
|
28
|
+
Initially, retrieval is run using expanded_queries, the result of the query_expansion module.
|
|
29
|
+
The retrieval module is run as a combination of the retrieval_modules in strategies.
|
|
30
|
+
If there are multiple retrieval_modules, run them all and choose the best result.
|
|
31
|
+
If there are no retrieval_modules, run them with the default of bm25.
|
|
32
|
+
In this way, the best result is selected for each module, and then the best result is selected.
|
|
33
|
+
|
|
34
|
+
:param modules: Query expansion modules to run.
|
|
35
|
+
:param module_params: Query expansion module parameters.
|
|
36
|
+
:param previous_result: Previous result dataframe.
|
|
37
|
+
In this case, it would be qa data.
|
|
38
|
+
:param node_line_dir: This node line's directory.
|
|
39
|
+
:param strategies: Strategies for query expansion node.
|
|
40
|
+
:return: The best result dataframe.
|
|
41
|
+
"""
|
|
42
|
+
if not os.path.exists(node_line_dir):
|
|
43
|
+
os.makedirs(node_line_dir)
|
|
44
|
+
node_dir = os.path.join(node_line_dir, "query_expansion")
|
|
45
|
+
if not os.path.exists(node_dir):
|
|
46
|
+
os.makedirs(node_dir)
|
|
47
|
+
project_dir = pathlib.PurePath(node_line_dir).parent.parent
|
|
48
|
+
|
|
49
|
+
# run query expansion
|
|
50
|
+
results, execution_times = zip(
|
|
51
|
+
*map(
|
|
52
|
+
lambda task: measure_speed(
|
|
53
|
+
task[0].run_evaluator,
|
|
54
|
+
project_dir=project_dir,
|
|
55
|
+
previous_result=previous_result,
|
|
56
|
+
**task[1],
|
|
57
|
+
),
|
|
58
|
+
zip(modules, module_params),
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
average_times = list(map(lambda x: x / len(results[0]), execution_times))
|
|
62
|
+
|
|
63
|
+
# save results to folder
|
|
64
|
+
pseudo_module_params = deepcopy(module_params)
|
|
65
|
+
for i, module_param in enumerate(pseudo_module_params):
|
|
66
|
+
if "prompt" in module_params:
|
|
67
|
+
module_param["prompt"] = str(i)
|
|
68
|
+
filepaths = list(
|
|
69
|
+
map(lambda x: os.path.join(node_dir, f"{x}.parquet"), range(len(modules)))
|
|
70
|
+
)
|
|
71
|
+
list(
|
|
72
|
+
map(lambda x: x[0].to_parquet(x[1], index=False), zip(results, filepaths))
|
|
73
|
+
) # execute save to parquet
|
|
74
|
+
filenames = list(map(lambda x: os.path.basename(x), filepaths))
|
|
75
|
+
|
|
76
|
+
# make summary file
|
|
77
|
+
summary_df = pd.DataFrame(
|
|
78
|
+
{
|
|
79
|
+
"filename": filenames,
|
|
80
|
+
"module_name": list(map(lambda module: module.__name__, modules)),
|
|
81
|
+
"module_params": module_params,
|
|
82
|
+
"execution_time": average_times,
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Run evaluation when there are more than one module.
|
|
87
|
+
if len(modules) > 1:
|
|
88
|
+
# pop general keys from strategies (e.g. metrics, speed_threshold)
|
|
89
|
+
general_key = ["metrics", "speed_threshold", "strategy"]
|
|
90
|
+
general_strategy = dict(
|
|
91
|
+
filter(lambda x: x[0] in general_key, strategies.items())
|
|
92
|
+
)
|
|
93
|
+
extra_strategy = dict(
|
|
94
|
+
filter(lambda x: x[0] not in general_key, strategies.items())
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# first, filter by threshold if it is enabled.
|
|
98
|
+
if general_strategy.get("speed_threshold") is not None:
|
|
99
|
+
results, filenames = filter_by_threshold(
|
|
100
|
+
results, average_times, general_strategy["speed_threshold"], filenames
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# check metrics in strategy
|
|
104
|
+
if general_strategy.get("metrics") is None:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
"You must at least one metrics for query expansion evaluation."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if extra_strategy.get("top_k") is None:
|
|
110
|
+
extra_strategy["top_k"] = 10 # default value
|
|
111
|
+
|
|
112
|
+
# get retrieval modules from strategy
|
|
113
|
+
retrieval_callables, retrieval_params = make_retrieval_callable_params(
|
|
114
|
+
extra_strategy
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# get retrieval_gt
|
|
118
|
+
retrieval_gt = pd.read_parquet(
|
|
119
|
+
os.path.join(project_dir, "data", "qa.parquet"), engine="pyarrow"
|
|
120
|
+
)["retrieval_gt"].tolist()
|
|
121
|
+
|
|
122
|
+
# make rows to metric_inputs
|
|
123
|
+
metric_inputs = [
|
|
124
|
+
MetricInput(retrieval_gt=ret_gt, query=query, generation_gt=gen_gt)
|
|
125
|
+
for ret_gt, query, gen_gt in zip(
|
|
126
|
+
retrieval_gt,
|
|
127
|
+
previous_result["query"].tolist(),
|
|
128
|
+
previous_result["generation_gt"].tolist(),
|
|
129
|
+
)
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# run evaluation
|
|
133
|
+
evaluation_results = list(
|
|
134
|
+
map(
|
|
135
|
+
lambda result: evaluate_one_query_expansion_node(
|
|
136
|
+
retrieval_callables,
|
|
137
|
+
retrieval_params,
|
|
138
|
+
[
|
|
139
|
+
setattr(metric_input, "queries", queries) or metric_input
|
|
140
|
+
for metric_input, queries in zip(
|
|
141
|
+
metric_inputs, result["queries"].to_list()
|
|
142
|
+
)
|
|
143
|
+
],
|
|
144
|
+
general_strategy["metrics"],
|
|
145
|
+
project_dir,
|
|
146
|
+
previous_result,
|
|
147
|
+
general_strategy.get("strategy", "mean"),
|
|
148
|
+
),
|
|
149
|
+
results,
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
evaluation_df = pd.DataFrame(
|
|
154
|
+
{
|
|
155
|
+
"filename": filenames,
|
|
156
|
+
**{
|
|
157
|
+
f"query_expansion_{metric_name}": list(
|
|
158
|
+
map(lambda x: x[metric_name].mean(), evaluation_results)
|
|
159
|
+
)
|
|
160
|
+
for metric_name in general_strategy["metrics"]
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
summary_df = pd.merge(
|
|
165
|
+
on="filename", left=summary_df, right=evaluation_df, how="left"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
best_result, best_filename = select_best(
|
|
169
|
+
evaluation_results,
|
|
170
|
+
general_strategy["metrics"],
|
|
171
|
+
filenames,
|
|
172
|
+
strategies.get("strategy", "mean"),
|
|
173
|
+
)
|
|
174
|
+
# change metric name columns to query_expansion_metric_name
|
|
175
|
+
best_result = best_result.rename(
|
|
176
|
+
columns={
|
|
177
|
+
metric_name: f"query_expansion_{metric_name}"
|
|
178
|
+
for metric_name in strategies["metrics"]
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
best_result = best_result.drop(
|
|
182
|
+
columns=["retrieved_contents", "retrieved_ids", "retrieve_scores"]
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
best_result, best_filename = results[0], filenames[0]
|
|
186
|
+
best_result = pd.concat([previous_result, best_result], axis=1)
|
|
187
|
+
|
|
188
|
+
# add 'is_best' column at summary file
|
|
189
|
+
summary_df["is_best"] = summary_df["filename"] == best_filename
|
|
190
|
+
|
|
191
|
+
# save files
|
|
192
|
+
summary_df.to_csv(os.path.join(node_dir, "summary.csv"), index=False)
|
|
193
|
+
best_result.to_parquet(
|
|
194
|
+
os.path.join(node_dir, f"best_{os.path.splitext(best_filename)[0]}.parquet"),
|
|
195
|
+
index=False,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return best_result
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def evaluate_one_query_expansion_node(
|
|
202
|
+
retrieval_funcs: List,
|
|
203
|
+
retrieval_params: List[Dict],
|
|
204
|
+
metric_inputs: List[MetricInput],
|
|
205
|
+
metrics: List[str],
|
|
206
|
+
project_dir,
|
|
207
|
+
previous_result: pd.DataFrame,
|
|
208
|
+
strategy_name: str,
|
|
209
|
+
) -> pd.DataFrame:
|
|
210
|
+
previous_result["queries"] = [
|
|
211
|
+
metric_input.queries for metric_input in metric_inputs
|
|
212
|
+
]
|
|
213
|
+
retrieval_results = list(
|
|
214
|
+
map(
|
|
215
|
+
lambda x: x[0].run_evaluator(
|
|
216
|
+
project_dir=project_dir, previous_result=previous_result, **x[1]
|
|
217
|
+
),
|
|
218
|
+
zip(retrieval_funcs, retrieval_params),
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
# Cast each retrieval results
|
|
222
|
+
retrieval_result_dicts = list(map(cast_retrieve_infos, retrieval_results))
|
|
223
|
+
evaluation_results = list(
|
|
224
|
+
map(
|
|
225
|
+
lambda x: evaluate_retrieval_node(
|
|
226
|
+
x,
|
|
227
|
+
metric_inputs,
|
|
228
|
+
metrics,
|
|
229
|
+
),
|
|
230
|
+
retrieval_result_dicts,
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
best_result, _ = select_best(
|
|
234
|
+
evaluation_results, metrics, strategy_name=strategy_name
|
|
235
|
+
)
|
|
236
|
+
best_result = pd.concat([previous_result, best_result], axis=1)
|
|
237
|
+
return best_result
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def make_retrieval_callable_params(strategy_dict: Dict):
|
|
241
|
+
"""
|
|
242
|
+
strategy_dict looks like this:
|
|
243
|
+
|
|
244
|
+
.. Code:: json
|
|
245
|
+
|
|
246
|
+
{
|
|
247
|
+
"metrics": ["retrieval_f1", "retrieval_recall"],
|
|
248
|
+
"top_k": 50,
|
|
249
|
+
"retrieval_modules": [
|
|
250
|
+
{"module_type": "bm25"},
|
|
251
|
+
{"module_type": "vectordb", "embedding_model": ["openai", "huggingface"]}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
node_dict = deepcopy(strategy_dict)
|
|
257
|
+
retrieval_module_list: Optional[List[Dict]] = node_dict.pop(
|
|
258
|
+
"retrieval_modules", None
|
|
259
|
+
)
|
|
260
|
+
if retrieval_module_list is None:
|
|
261
|
+
retrieval_module_list = [
|
|
262
|
+
{
|
|
263
|
+
"module_type": "bm25",
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
node_params = node_dict
|
|
267
|
+
modules = list(
|
|
268
|
+
map(
|
|
269
|
+
lambda module_dict: get_support_modules(module_dict.pop("module_type")),
|
|
270
|
+
retrieval_module_list,
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
param_combinations = list(
|
|
274
|
+
map(
|
|
275
|
+
lambda module_dict: make_combinations({**module_dict, **node_params}),
|
|
276
|
+
retrieval_module_list,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
return explode(modules, param_combinations)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def evaluate_retrieval_node(
|
|
283
|
+
result_dict: Dict,
|
|
284
|
+
metric_inputs: List[MetricInput],
|
|
285
|
+
metrics: Union[List[str], List[Dict]],
|
|
286
|
+
) -> pd.DataFrame:
|
|
287
|
+
"""
|
|
288
|
+
Evaluate retrieval node from retrieval node result dataframe.
|
|
289
|
+
|
|
290
|
+
:param result_df: The result dataframe from a retrieval node.
|
|
291
|
+
:param metric_inputs: List of metric input schema for AutoRAG.
|
|
292
|
+
:param metrics: Metric list from input strategies.
|
|
293
|
+
:return: Return result_df with metrics columns.
|
|
294
|
+
The columns will be 'retrieved_contents', 'retrieved_ids', 'retrieve_scores', and metric names.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
@evaluate_retrieval(
|
|
298
|
+
metric_inputs=metric_inputs,
|
|
299
|
+
metrics=metrics,
|
|
300
|
+
)
|
|
301
|
+
def evaluate_this_module(_dict: Dict):
|
|
302
|
+
return (
|
|
303
|
+
_dict["retrieved_contents"],
|
|
304
|
+
_dict["retrieved_ids"],
|
|
305
|
+
_dict["retrieve_scores"],
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return evaluate_this_module(result_dict)
|
|
File without changes
|