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,109 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List, Tuple
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import voyageai
|
|
5
|
+
|
|
6
|
+
from autorag.nodes.passagereranker.base import BasePassageReranker
|
|
7
|
+
from autorag.utils.util import result_to_dataframe, get_event_loop, process_batch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VoyageAIReranker(BasePassageReranker):
|
|
11
|
+
def __init__(self, project_dir: str, *args, **kwargs):
|
|
12
|
+
super().__init__(project_dir)
|
|
13
|
+
api_key = kwargs.pop("api_key", None)
|
|
14
|
+
api_key = os.getenv("VOYAGE_API_KEY", None) if api_key is None else api_key
|
|
15
|
+
if api_key is None:
|
|
16
|
+
raise KeyError(
|
|
17
|
+
"Please set the API key for VoyageAI rerank in the environment variable VOYAGE_API_KEY "
|
|
18
|
+
"or directly set it on the config YAML file."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
self.voyage_client = voyageai.AsyncClient(api_key=api_key)
|
|
22
|
+
|
|
23
|
+
def __del__(self):
|
|
24
|
+
del self.voyage_client
|
|
25
|
+
super().__del__()
|
|
26
|
+
|
|
27
|
+
@result_to_dataframe(["retrieved_contents", "retrieved_ids", "retrieve_scores"])
|
|
28
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
29
|
+
queries, contents, scores, ids = self.cast_to_run(previous_result)
|
|
30
|
+
top_k = kwargs.pop("top_k")
|
|
31
|
+
batch = kwargs.pop("batch", 8)
|
|
32
|
+
model = kwargs.pop("model", "rerank-2")
|
|
33
|
+
truncation = kwargs.pop("truncation", True)
|
|
34
|
+
return self._pure(queries, contents, ids, top_k, model, batch, truncation)
|
|
35
|
+
|
|
36
|
+
def _pure(
|
|
37
|
+
self,
|
|
38
|
+
queries: List[str],
|
|
39
|
+
contents_list: List[List[str]],
|
|
40
|
+
ids_list: List[List[str]],
|
|
41
|
+
top_k: int,
|
|
42
|
+
model: str = "rerank-2",
|
|
43
|
+
batch: int = 8,
|
|
44
|
+
truncation: bool = True,
|
|
45
|
+
) -> Tuple[List[List[str]], List[List[str]], List[List[float]]]:
|
|
46
|
+
"""
|
|
47
|
+
Rerank a list of contents with VoyageAI rerank models.
|
|
48
|
+
You can get the API key from https://docs.voyageai.com/docs/api-key-and-installation and set it in the environment variable VOYAGE_API_KEY.
|
|
49
|
+
|
|
50
|
+
:param queries: The list of queries to use for reranking
|
|
51
|
+
:param contents_list: The list of lists of contents to rerank
|
|
52
|
+
:param ids_list: The list of lists of ids retrieved from the initial ranking
|
|
53
|
+
:param top_k: The number of passages to be retrieved
|
|
54
|
+
:param model: The model name for VoyageAI rerank.
|
|
55
|
+
You can choose between "rerank-2" and "rerank-2-lite".
|
|
56
|
+
Default is "rerank-2".
|
|
57
|
+
:param batch: The number of queries to be processed in a batch
|
|
58
|
+
:param truncation: Whether to truncate the input to satisfy the 'context length limit' on the query and the documents.
|
|
59
|
+
:return: Tuple of lists containing the reranked contents, ids, and scores
|
|
60
|
+
"""
|
|
61
|
+
tasks = [
|
|
62
|
+
voyageai_rerank_pure(
|
|
63
|
+
self.voyage_client, model, query, contents, ids, top_k, truncation
|
|
64
|
+
)
|
|
65
|
+
for query, contents, ids in zip(queries, contents_list, ids_list)
|
|
66
|
+
]
|
|
67
|
+
loop = get_event_loop()
|
|
68
|
+
results = loop.run_until_complete(process_batch(tasks, batch))
|
|
69
|
+
|
|
70
|
+
content_result, id_result, score_result = zip(*results)
|
|
71
|
+
|
|
72
|
+
return list(content_result), list(id_result), list(score_result)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def voyageai_rerank_pure(
|
|
76
|
+
voyage_client: voyageai.AsyncClient,
|
|
77
|
+
model: str,
|
|
78
|
+
query: str,
|
|
79
|
+
documents: List[str],
|
|
80
|
+
ids: List[str],
|
|
81
|
+
top_k: int,
|
|
82
|
+
truncation: bool = True,
|
|
83
|
+
) -> Tuple[List[str], List[str], List[float]]:
|
|
84
|
+
"""
|
|
85
|
+
Rerank a list of contents with VoyageAI rerank models.
|
|
86
|
+
|
|
87
|
+
:param voyage_client: The Voyage Client to use for reranking
|
|
88
|
+
:param model: The model name for VoyageAI rerank
|
|
89
|
+
:param query: The query to use for reranking
|
|
90
|
+
:param documents: The list of contents to rerank
|
|
91
|
+
:param ids: The list of ids corresponding to the documents
|
|
92
|
+
:param top_k: The number of passages to be retrieved
|
|
93
|
+
:param truncation: Whether to truncate the input to satisfy the 'context length limit' on the query and the documents.
|
|
94
|
+
:return: Tuple of lists containing the reranked contents, ids, and scores
|
|
95
|
+
"""
|
|
96
|
+
rerank_results = await voyage_client.rerank(
|
|
97
|
+
model=model,
|
|
98
|
+
query=query,
|
|
99
|
+
documents=documents,
|
|
100
|
+
top_k=top_k,
|
|
101
|
+
truncation=truncation,
|
|
102
|
+
)
|
|
103
|
+
reranked_scores: List[float] = list(
|
|
104
|
+
map(lambda x: x.relevance_score, rerank_results.results)
|
|
105
|
+
)
|
|
106
|
+
indices = list(map(lambda x: x.index, rerank_results.results))
|
|
107
|
+
reranked_contents: List[str] = list(map(lambda i: documents[i], indices))
|
|
108
|
+
reranked_ids: List[str] = list(map(lambda i: ids[i], indices))
|
|
109
|
+
return reranked_contents, reranked_ids, reranked_scores
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .long_context_reorder import LongContextReorder
|
|
2
|
+
from .window_replacement import WindowReplacement
|
|
3
|
+
from .fstring import Fstring
|
|
4
|
+
from .chat_fstring import ChatFstring
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Fstring",
|
|
9
|
+
"ChatFstring",
|
|
10
|
+
"LongContextReorder",
|
|
11
|
+
"WindowReplacement",
|
|
12
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABCMeta
|
|
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.cast import cast_retrieved_contents
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("AutoRAG")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BasePromptMaker(BaseModule, metaclass=ABCMeta):
|
|
15
|
+
def __init__(self, project_dir: Union[str, Path], *args, **kwargs):
|
|
16
|
+
logger.info(
|
|
17
|
+
f"Initialize prompt maker node - {self.__class__.__name__} module..."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def __del__(self):
|
|
21
|
+
logger.info(f"Prompt maker node - {self.__class__.__name__} module is deleted.")
|
|
22
|
+
|
|
23
|
+
def cast_to_run(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
24
|
+
logger.info(f"Running prompt maker node - {self.__class__.__name__} module...")
|
|
25
|
+
# get query and retrieved contents from previous_result
|
|
26
|
+
assert "query" in previous_result.columns, (
|
|
27
|
+
"previous_result must have query column."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
query = previous_result["query"].tolist()
|
|
31
|
+
prompt = kwargs.pop("prompt")
|
|
32
|
+
return query, cast_retrieved_contents(previous_result), prompt
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from autorag.nodes.promptmaker.base import BasePromptMaker
|
|
7
|
+
from autorag.utils import result_to_dataframe
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ChatFstring(BasePromptMaker):
|
|
11
|
+
@result_to_dataframe(["prompts"])
|
|
12
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
13
|
+
query, retrieved_contents, prompt = self.cast_to_run(
|
|
14
|
+
previous_result, *args, **kwargs
|
|
15
|
+
)
|
|
16
|
+
return self._pure(prompt, query, retrieved_contents)
|
|
17
|
+
|
|
18
|
+
def _pure(
|
|
19
|
+
self,
|
|
20
|
+
prompt: List[dict[str, str]],
|
|
21
|
+
queries: List[str],
|
|
22
|
+
retrieved_contents: List[List[str]],
|
|
23
|
+
) -> List[List[dict[str, str]]]:
|
|
24
|
+
"""
|
|
25
|
+
Make a prompt using f-string from a query and retrieved_contents.
|
|
26
|
+
You must type a prompt or prompt list at a config YAML file like this:
|
|
27
|
+
|
|
28
|
+
.. Code:: yaml
|
|
29
|
+
nodes:
|
|
30
|
+
- node_type: prompt_maker
|
|
31
|
+
modules:
|
|
32
|
+
- module_type: chatfstring
|
|
33
|
+
prompt:
|
|
34
|
+
- - role: system
|
|
35
|
+
content: You are a helpful assistant that helps people find information.
|
|
36
|
+
- role: user
|
|
37
|
+
content: |
|
|
38
|
+
Answer this question: {query}
|
|
39
|
+
{retrieved_contents}
|
|
40
|
+
- - role: system
|
|
41
|
+
content: You are helpful.
|
|
42
|
+
- role: user
|
|
43
|
+
content: |
|
|
44
|
+
Read the passages carefully and answer this question: {query}
|
|
45
|
+
|
|
46
|
+
Passages: {retrieved_contents}
|
|
47
|
+
|
|
48
|
+
:param prompt: A prompt string.
|
|
49
|
+
:param queries: List of query strings.
|
|
50
|
+
:param retrieved_contents: List of retrieved contents.
|
|
51
|
+
:return: Prompts that are made by chat f-string.
|
|
52
|
+
It is the list of OpenAI chat format prompts.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def fstring_row(
|
|
56
|
+
_prompt: List[dict[str, str]], _query: str, _retrieved_contents: List[str]
|
|
57
|
+
) -> List[dict[str, str]]:
|
|
58
|
+
contents_str = "\n\n".join(_retrieved_contents)
|
|
59
|
+
result_prompt = copy.deepcopy(_prompt)
|
|
60
|
+
for lst in result_prompt:
|
|
61
|
+
if "content" in lst:
|
|
62
|
+
lst["content"] = lst["content"].format(
|
|
63
|
+
query=_query, retrieved_contents=contents_str
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return result_prompt
|
|
67
|
+
|
|
68
|
+
return list(
|
|
69
|
+
map(
|
|
70
|
+
lambda x: fstring_row(prompt, x[0], x[1]),
|
|
71
|
+
zip(queries, retrieved_contents),
|
|
72
|
+
)
|
|
73
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from autorag.nodes.promptmaker.base import BasePromptMaker
|
|
6
|
+
from autorag.utils import result_to_dataframe
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Fstring(BasePromptMaker):
|
|
10
|
+
@result_to_dataframe(["prompts"])
|
|
11
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
12
|
+
query, retrieved_contents, prompt = self.cast_to_run(
|
|
13
|
+
previous_result, *args, **kwargs
|
|
14
|
+
)
|
|
15
|
+
return self._pure(prompt, query, retrieved_contents)
|
|
16
|
+
|
|
17
|
+
def _pure(
|
|
18
|
+
self, prompt: str, queries: List[str], retrieved_contents: List[List[str]]
|
|
19
|
+
) -> List[str]:
|
|
20
|
+
"""
|
|
21
|
+
Make a prompt using f-string from a query and retrieved_contents.
|
|
22
|
+
You must type a prompt or prompt list at a config YAML file like this:
|
|
23
|
+
|
|
24
|
+
.. Code:: yaml
|
|
25
|
+
nodes:
|
|
26
|
+
- node_type: prompt_maker
|
|
27
|
+
modules:
|
|
28
|
+
- module_type: fstring
|
|
29
|
+
prompt: [Answer this question: {query} \n\n {retrieved_contents},
|
|
30
|
+
Read the passages carefully and answer this question: {query} \n\n Passages: {retrieved_contents}]
|
|
31
|
+
|
|
32
|
+
:param prompt: A prompt string.
|
|
33
|
+
:param queries: List of query strings.
|
|
34
|
+
:param retrieved_contents: List of retrieved contents.
|
|
35
|
+
:return: Prompts that are made by f-string.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def fstring_row(
|
|
39
|
+
_prompt: str, _query: str, _retrieved_contents: List[str]
|
|
40
|
+
) -> str:
|
|
41
|
+
contents_str = "\n\n".join(_retrieved_contents)
|
|
42
|
+
return _prompt.format(query=_query, retrieved_contents=contents_str)
|
|
43
|
+
|
|
44
|
+
return list(
|
|
45
|
+
map(
|
|
46
|
+
lambda x: fstring_row(prompt, x[0], x[1]),
|
|
47
|
+
zip(queries, retrieved_contents),
|
|
48
|
+
)
|
|
49
|
+
)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from autorag.nodes.promptmaker.base import BasePromptMaker
|
|
8
|
+
from autorag.utils import result_to_dataframe
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("AutoRAG")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LongContextReorder(BasePromptMaker):
|
|
14
|
+
@result_to_dataframe(["prompts"])
|
|
15
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
16
|
+
query, retrieved_contents, prompt = self.cast_to_run(
|
|
17
|
+
previous_result, *args, **kwargs
|
|
18
|
+
)
|
|
19
|
+
assert "retrieve_scores" in previous_result.columns, (
|
|
20
|
+
"previous_result must have retrieve_scores column."
|
|
21
|
+
)
|
|
22
|
+
retrieve_scores = previous_result["retrieve_scores"].tolist()
|
|
23
|
+
return self._pure(prompt, query, retrieved_contents, retrieve_scores)
|
|
24
|
+
|
|
25
|
+
def _pure(
|
|
26
|
+
self,
|
|
27
|
+
prompt: str,
|
|
28
|
+
queries: List[str],
|
|
29
|
+
retrieved_contents: List[List[str]],
|
|
30
|
+
retrieve_scores: List[List[float]],
|
|
31
|
+
) -> List[str]:
|
|
32
|
+
"""
|
|
33
|
+
Models struggle to access significant details found
|
|
34
|
+
in the center of extended contexts. A study
|
|
35
|
+
(https://arxiv.org/abs/2307.03172) observed that the best
|
|
36
|
+
performance typically arises when crucial data is positioned
|
|
37
|
+
at the start or conclusion of the input context. Additionally,
|
|
38
|
+
as the input context lengthens, performance drops notably, even
|
|
39
|
+
in models designed for long contexts."
|
|
40
|
+
|
|
41
|
+
.. Code:: yaml
|
|
42
|
+
nodes:
|
|
43
|
+
- node_type: prompt_maker
|
|
44
|
+
modules:
|
|
45
|
+
- module_type: long_context_reorder
|
|
46
|
+
prompt: [Answer this question: {query} \n\n {retrieved_contents},
|
|
47
|
+
Read the passages carefully and answer this question: {query} \n\n Passages: {retrieved_contents}]
|
|
48
|
+
|
|
49
|
+
:param prompt: A prompt string.
|
|
50
|
+
:param queries: List of query strings.
|
|
51
|
+
:param retrieved_contents: List of retrieved contents.
|
|
52
|
+
:param retrieve_scores: List of `retrieve scores`.
|
|
53
|
+
:return: Prompts that are made by long context reorder.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def long_context_reorder_row(
|
|
57
|
+
_prompt: str,
|
|
58
|
+
_query: str,
|
|
59
|
+
_retrieved_contents: List[str],
|
|
60
|
+
_retrieve_scores: List[float],
|
|
61
|
+
) -> str:
|
|
62
|
+
if isinstance(_retrieved_contents, np.ndarray):
|
|
63
|
+
_retrieved_contents = _retrieved_contents.tolist()
|
|
64
|
+
if not len(_retrieved_contents) == len(_retrieve_scores):
|
|
65
|
+
logger.info("If you use a summarizer, the reorder will not proceed.")
|
|
66
|
+
return _prompt.format(
|
|
67
|
+
query=_query, retrieved_contents="\n\n".join(_retrieved_contents)
|
|
68
|
+
)
|
|
69
|
+
content_scores = list(zip(_retrieved_contents, _retrieve_scores))
|
|
70
|
+
sorted_content_scores = sorted(
|
|
71
|
+
content_scores, key=lambda x: x[1], reverse=True
|
|
72
|
+
)
|
|
73
|
+
content_result, score_result = zip(*sorted_content_scores)
|
|
74
|
+
_retrieved_contents.append(content_result[0])
|
|
75
|
+
contents_str = "\n\n".join(_retrieved_contents)
|
|
76
|
+
return _prompt.format(query=_query, retrieved_contents=contents_str)
|
|
77
|
+
|
|
78
|
+
return list(
|
|
79
|
+
map(
|
|
80
|
+
lambda x: long_context_reorder_row(prompt, x[0], x[1], x[2]),
|
|
81
|
+
zip(queries, retrieved_contents, retrieve_scores),
|
|
82
|
+
)
|
|
83
|
+
)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import List, Dict, Optional, Union
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import tokenlog
|
|
8
|
+
|
|
9
|
+
from autorag.evaluation import evaluate_generation
|
|
10
|
+
from autorag.evaluation.util import cast_metrics
|
|
11
|
+
from autorag.schema.metricinput import MetricInput
|
|
12
|
+
from autorag.strategy import measure_speed, filter_by_threshold, select_best
|
|
13
|
+
from autorag.support import get_support_modules
|
|
14
|
+
from autorag.utils import validate_qa_dataset
|
|
15
|
+
from autorag.utils.util import make_combinations, explode, split_dataframe, to_list
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_prompt_maker_node(
|
|
19
|
+
modules: List,
|
|
20
|
+
module_params: List[Dict],
|
|
21
|
+
previous_result: pd.DataFrame,
|
|
22
|
+
node_line_dir: str,
|
|
23
|
+
strategies: Dict,
|
|
24
|
+
) -> pd.DataFrame:
|
|
25
|
+
"""
|
|
26
|
+
Run prompt maker node.
|
|
27
|
+
With this function, you can select the best prompt maker module.
|
|
28
|
+
As default, when you can use only one module, the evaluation will be skipped.
|
|
29
|
+
If you want to select the best prompt among modules, you can use strategies.
|
|
30
|
+
When you use them, you must pass 'generator_modules' and its parameters at strategies.
|
|
31
|
+
Because it uses generator modules and generator metrics for evaluation this module.
|
|
32
|
+
It is recommended to use one params and modules for evaluation,
|
|
33
|
+
but you can use multiple params and modules for evaluation.
|
|
34
|
+
When you don't set generator module at strategies, it will use the default generator module.
|
|
35
|
+
The default generator module is llama_index_llm with openai gpt-3.5-turbo model.
|
|
36
|
+
|
|
37
|
+
:param modules: Prompt maker module classes to run.
|
|
38
|
+
:param module_params: Prompt maker module parameters.
|
|
39
|
+
:param previous_result: Previous result dataframe.
|
|
40
|
+
Could be query expansion's best result or qa data.
|
|
41
|
+
:param node_line_dir: This node line's directory.
|
|
42
|
+
:param strategies: Strategies for prompt maker node.
|
|
43
|
+
:return: The best result dataframe.
|
|
44
|
+
It contains previous result columns and prompt maker's result columns which is 'prompts'.
|
|
45
|
+
"""
|
|
46
|
+
if not os.path.exists(node_line_dir):
|
|
47
|
+
os.makedirs(node_line_dir)
|
|
48
|
+
node_dir = os.path.join(node_line_dir, "prompt_maker")
|
|
49
|
+
if not os.path.exists(node_dir):
|
|
50
|
+
os.makedirs(node_dir)
|
|
51
|
+
project_dir = pathlib.PurePath(node_line_dir).parent.parent
|
|
52
|
+
|
|
53
|
+
# run modules
|
|
54
|
+
results, execution_times = zip(
|
|
55
|
+
*map(
|
|
56
|
+
lambda task: measure_speed(
|
|
57
|
+
task[0].run_evaluator,
|
|
58
|
+
project_dir=project_dir,
|
|
59
|
+
previous_result=previous_result,
|
|
60
|
+
**task[1],
|
|
61
|
+
),
|
|
62
|
+
zip(modules, module_params),
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
average_times = list(map(lambda x: x / len(results[0]), execution_times))
|
|
66
|
+
|
|
67
|
+
# get average token usage
|
|
68
|
+
token_usages = []
|
|
69
|
+
for i, result in enumerate(results):
|
|
70
|
+
token_logger = tokenlog.getLogger(
|
|
71
|
+
f"prompt_maker_{i}", strategies.get("tokenizer", "openai/gpt-oss-20b")
|
|
72
|
+
)
|
|
73
|
+
token_logger.query_batch(result["prompts"].tolist())
|
|
74
|
+
token_usages.append(token_logger.get_token_usage() / len(result))
|
|
75
|
+
|
|
76
|
+
# save results to folder
|
|
77
|
+
filepaths = list(
|
|
78
|
+
map(lambda x: os.path.join(node_dir, f"{x}.parquet"), range(len(modules)))
|
|
79
|
+
)
|
|
80
|
+
list(
|
|
81
|
+
map(lambda x: x[0].to_parquet(x[1], index=False), zip(results, filepaths))
|
|
82
|
+
) # execute save to parquet
|
|
83
|
+
filenames = list(map(lambda x: os.path.basename(x), filepaths))
|
|
84
|
+
|
|
85
|
+
# make summary file
|
|
86
|
+
summary_df = pd.DataFrame(
|
|
87
|
+
{
|
|
88
|
+
"filename": filenames,
|
|
89
|
+
"module_name": list(map(lambda module: module.__name__, modules)),
|
|
90
|
+
"module_params": module_params,
|
|
91
|
+
"execution_time": average_times,
|
|
92
|
+
"average_prompt_token": token_usages,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
metric_names, metric_params = cast_metrics(strategies.get("metrics"))
|
|
97
|
+
|
|
98
|
+
# Run evaluation when there are more than one module.
|
|
99
|
+
if len(modules) > 1:
|
|
100
|
+
# pop general keys from strategies (e.g. metrics, speed_threshold)
|
|
101
|
+
general_key = ["metrics", "speed_threshold", "token_threshold", "tokenizer"]
|
|
102
|
+
general_strategy = dict(
|
|
103
|
+
filter(lambda x: x[0] in general_key, strategies.items())
|
|
104
|
+
)
|
|
105
|
+
extra_strategy = dict(
|
|
106
|
+
filter(lambda x: x[0] not in general_key, strategies.items())
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# first, filter by threshold if it is enabled.
|
|
110
|
+
if general_strategy.get("speed_threshold") is not None:
|
|
111
|
+
results, filenames = filter_by_threshold(
|
|
112
|
+
results, average_times, general_strategy["speed_threshold"], filenames
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Calculate tokens and save to summary
|
|
116
|
+
if general_strategy.get("token_threshold") is not None:
|
|
117
|
+
results, filenames = filter_by_threshold(
|
|
118
|
+
results, token_usages, general_strategy["token_threshold"], filenames
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# run metrics before filtering
|
|
122
|
+
if metric_names is None or len(metric_names) <= 0:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
"You must at least one metrics for prompt maker evaluation."
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# get generator modules from strategy
|
|
128
|
+
generator_callables, generator_params = make_generator_callable_params(
|
|
129
|
+
extra_strategy
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# get generation_gt
|
|
133
|
+
qa_data = pd.read_parquet(
|
|
134
|
+
os.path.join(project_dir, "data", "qa.parquet"), engine="pyarrow"
|
|
135
|
+
)
|
|
136
|
+
validate_qa_dataset(qa_data)
|
|
137
|
+
generation_gt = qa_data["generation_gt"].tolist()
|
|
138
|
+
generation_gt = list(map(lambda x: x.tolist(), generation_gt))
|
|
139
|
+
queries = to_list(qa_data["query"].tolist())
|
|
140
|
+
metric_inputs = [
|
|
141
|
+
MetricInput(generation_gt=gen_gt, query=query)
|
|
142
|
+
for gen_gt, query in zip(generation_gt, queries)
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
all_prompts = []
|
|
146
|
+
for result in results:
|
|
147
|
+
all_prompts.extend(result["prompts"].tolist())
|
|
148
|
+
|
|
149
|
+
evaluation_result_all = evaluate_one_prompt_maker_node(
|
|
150
|
+
all_prompts,
|
|
151
|
+
generator_callables,
|
|
152
|
+
generator_params,
|
|
153
|
+
metric_inputs * len(results),
|
|
154
|
+
general_strategy["metrics"],
|
|
155
|
+
project_dir,
|
|
156
|
+
strategy_name=strategies.get("strategy", "mean"),
|
|
157
|
+
)
|
|
158
|
+
evaluation_results = split_dataframe(
|
|
159
|
+
evaluation_result_all, chunk_size=len(results[0])
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
evaluation_df = pd.DataFrame(
|
|
163
|
+
{
|
|
164
|
+
"filename": filenames,
|
|
165
|
+
**{
|
|
166
|
+
f"prompt_maker_{metric_name}": list(
|
|
167
|
+
map(lambda x: x[metric_name].mean(), evaluation_results)
|
|
168
|
+
)
|
|
169
|
+
for metric_name in metric_names
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
summary_df = pd.merge(
|
|
174
|
+
on="filename", left=summary_df, right=evaluation_df, how="left"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
best_result, best_filename = select_best(
|
|
178
|
+
evaluation_results,
|
|
179
|
+
metric_names,
|
|
180
|
+
filenames,
|
|
181
|
+
strategies.get("strategy", "mean"),
|
|
182
|
+
)
|
|
183
|
+
# change metric name columns to prompt_maker_metric_name
|
|
184
|
+
best_result = best_result.rename(
|
|
185
|
+
columns={
|
|
186
|
+
metric_name: f"prompt_maker_{metric_name}"
|
|
187
|
+
for metric_name in metric_names
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
best_result = best_result.drop(columns=["generated_texts"])
|
|
191
|
+
else:
|
|
192
|
+
best_result, best_filename = results[0], filenames[0]
|
|
193
|
+
|
|
194
|
+
# add 'is_best' column at summary file
|
|
195
|
+
summary_df["is_best"] = summary_df["filename"] == best_filename
|
|
196
|
+
|
|
197
|
+
best_result = pd.concat([previous_result, best_result], axis=1)
|
|
198
|
+
|
|
199
|
+
# save files
|
|
200
|
+
summary_df.to_csv(os.path.join(node_dir, "summary.csv"), index=False)
|
|
201
|
+
best_result.to_parquet(
|
|
202
|
+
os.path.join(node_dir, f"best_{os.path.splitext(best_filename)[0]}.parquet"),
|
|
203
|
+
index=False,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return best_result
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def make_generator_callable_params(strategy_dict: Dict):
|
|
210
|
+
node_dict = deepcopy(strategy_dict)
|
|
211
|
+
generator_module_list: Optional[List[Dict]] = node_dict.pop(
|
|
212
|
+
"generator_modules", None
|
|
213
|
+
)
|
|
214
|
+
if generator_module_list is None:
|
|
215
|
+
generator_module_list = [
|
|
216
|
+
{
|
|
217
|
+
"module_type": "llama_index_llm",
|
|
218
|
+
"llm": "openai",
|
|
219
|
+
"model": "gpt-3.5-turbo",
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
node_params = node_dict
|
|
223
|
+
modules = list(
|
|
224
|
+
map(
|
|
225
|
+
lambda module_dict: get_support_modules(module_dict.pop("module_type")),
|
|
226
|
+
generator_module_list,
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
param_combinations = list(
|
|
230
|
+
map(
|
|
231
|
+
lambda module_dict: make_combinations({**module_dict, **node_params}),
|
|
232
|
+
generator_module_list,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
return explode(modules, param_combinations)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def evaluate_one_prompt_maker_node(
|
|
239
|
+
prompts: List[str],
|
|
240
|
+
generator_classes: List,
|
|
241
|
+
generator_params: List[Dict],
|
|
242
|
+
metric_inputs: List[MetricInput],
|
|
243
|
+
metrics: Union[List[str], List[Dict]],
|
|
244
|
+
project_dir,
|
|
245
|
+
strategy_name: str,
|
|
246
|
+
) -> pd.DataFrame:
|
|
247
|
+
input_df = pd.DataFrame({"prompts": prompts})
|
|
248
|
+
generator_results = list(
|
|
249
|
+
map(
|
|
250
|
+
lambda x: x[0].run_evaluator(
|
|
251
|
+
project_dir=project_dir, previous_result=input_df, **x[1]
|
|
252
|
+
),
|
|
253
|
+
zip(generator_classes, generator_params),
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
evaluation_results = list(
|
|
257
|
+
map(
|
|
258
|
+
lambda x: evaluate_generator_result(x[0], metric_inputs, metrics),
|
|
259
|
+
zip(generator_results, generator_classes),
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
metric_names = (
|
|
263
|
+
list(map(lambda x: x["metric_name"], metrics))
|
|
264
|
+
if isinstance(metrics[0], dict)
|
|
265
|
+
else metrics
|
|
266
|
+
)
|
|
267
|
+
best_result, _ = select_best(
|
|
268
|
+
evaluation_results, metric_names, strategy_name=strategy_name
|
|
269
|
+
)
|
|
270
|
+
best_result = pd.concat([input_df, best_result], axis=1)
|
|
271
|
+
return best_result # it has 'generated_texts' column
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def evaluate_generator_result(
|
|
275
|
+
result_df: pd.DataFrame,
|
|
276
|
+
metric_inputs: List[MetricInput],
|
|
277
|
+
metrics: Union[List[str], List[Dict]],
|
|
278
|
+
) -> pd.DataFrame:
|
|
279
|
+
@evaluate_generation(metric_inputs=metric_inputs, metrics=metrics)
|
|
280
|
+
def evaluate(df):
|
|
281
|
+
return df["generated_texts"].tolist()
|
|
282
|
+
|
|
283
|
+
return evaluate(result_df)
|