AutoRAG 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. autorag/__init__.py +82 -0
  2. autorag/chunker.py +51 -0
  3. autorag/cli.py +209 -0
  4. autorag/dashboard.py +199 -0
  5. autorag/data/__init__.py +109 -0
  6. autorag/data/chunk/__init__.py +2 -0
  7. autorag/data/chunk/base.py +128 -0
  8. autorag/data/chunk/langchain_chunk.py +76 -0
  9. autorag/data/chunk/llama_index_chunk.py +96 -0
  10. autorag/data/chunk/run.py +38 -0
  11. autorag/data/legacy/__init__.py +0 -0
  12. autorag/data/legacy/corpus/__init__.py +2 -0
  13. autorag/data/legacy/corpus/langchain.py +47 -0
  14. autorag/data/legacy/corpus/llama_index.py +93 -0
  15. autorag/data/legacy/qacreation/__init__.py +6 -0
  16. autorag/data/legacy/qacreation/base.py +239 -0
  17. autorag/data/legacy/qacreation/llama_index.py +253 -0
  18. autorag/data/legacy/qacreation/llama_index_default_prompt.txt +54 -0
  19. autorag/data/legacy/qacreation/ragas.py +75 -0
  20. autorag/data/legacy/qacreation/simple.py +99 -0
  21. autorag/data/parse/__init__.py +1 -0
  22. autorag/data/parse/base.py +79 -0
  23. autorag/data/parse/clova.py +194 -0
  24. autorag/data/parse/langchain_parse.py +87 -0
  25. autorag/data/parse/llamaparse.py +126 -0
  26. autorag/data/parse/run.py +141 -0
  27. autorag/data/parse/table_hybrid_parse.py +134 -0
  28. autorag/data/qa/__init__.py +3 -0
  29. autorag/data/qa/evolve/__init__.py +0 -0
  30. autorag/data/qa/evolve/llama_index_query_evolve.py +64 -0
  31. autorag/data/qa/evolve/openai_query_evolve.py +81 -0
  32. autorag/data/qa/evolve/prompt.py +288 -0
  33. autorag/data/qa/extract_evidence.py +1 -0
  34. autorag/data/qa/filter/__init__.py +0 -0
  35. autorag/data/qa/filter/dontknow.py +117 -0
  36. autorag/data/qa/filter/passage_dependency.py +88 -0
  37. autorag/data/qa/filter/prompt.py +73 -0
  38. autorag/data/qa/generation_gt/__init__.py +0 -0
  39. autorag/data/qa/generation_gt/base.py +16 -0
  40. autorag/data/qa/generation_gt/llama_index_gen_gt.py +41 -0
  41. autorag/data/qa/generation_gt/openai_gen_gt.py +84 -0
  42. autorag/data/qa/generation_gt/prompt.py +27 -0
  43. autorag/data/qa/query/__init__.py +0 -0
  44. autorag/data/qa/query/llama_gen_query.py +82 -0
  45. autorag/data/qa/query/openai_gen_query.py +95 -0
  46. autorag/data/qa/query/prompt.py +201 -0
  47. autorag/data/qa/sample.py +26 -0
  48. autorag/data/qa/schema.py +322 -0
  49. autorag/data/utils/__init__.py +0 -0
  50. autorag/data/utils/util.py +103 -0
  51. autorag/deploy/__init__.py +9 -0
  52. autorag/deploy/api.py +303 -0
  53. autorag/deploy/base.py +235 -0
  54. autorag/deploy/gradio.py +74 -0
  55. autorag/deploy/swagger.yml +202 -0
  56. autorag/embedding/__init__.py +0 -0
  57. autorag/embedding/base.py +144 -0
  58. autorag/embedding/vllm.py +256 -0
  59. autorag/evaluation/__init__.py +3 -0
  60. autorag/evaluation/generation.py +88 -0
  61. autorag/evaluation/metric/__init__.py +22 -0
  62. autorag/evaluation/metric/deepeval_prompt.py +322 -0
  63. autorag/evaluation/metric/g_eval_prompts/coh_detailed.txt +32 -0
  64. autorag/evaluation/metric/g_eval_prompts/con_detailed.txt +33 -0
  65. autorag/evaluation/metric/g_eval_prompts/flu_detailed.txt +26 -0
  66. autorag/evaluation/metric/g_eval_prompts/rel_detailed.txt +33 -0
  67. autorag/evaluation/metric/generation.py +504 -0
  68. autorag/evaluation/metric/retrieval.py +115 -0
  69. autorag/evaluation/metric/retrieval_contents.py +65 -0
  70. autorag/evaluation/metric/util.py +88 -0
  71. autorag/evaluation/retrieval.py +83 -0
  72. autorag/evaluation/retrieval_contents.py +65 -0
  73. autorag/evaluation/util.py +43 -0
  74. autorag/evaluator.py +559 -0
  75. autorag/node_line.py +65 -0
  76. autorag/nodes/__init__.py +0 -0
  77. autorag/nodes/generator/__init__.py +4 -0
  78. autorag/nodes/generator/base.py +103 -0
  79. autorag/nodes/generator/llama_index_llm.py +169 -0
  80. autorag/nodes/generator/openai_llm.py +329 -0
  81. autorag/nodes/generator/run.py +148 -0
  82. autorag/nodes/generator/vllm.py +147 -0
  83. autorag/nodes/generator/vllm_api.py +191 -0
  84. autorag/nodes/hybridretrieval/__init__.py +2 -0
  85. autorag/nodes/hybridretrieval/base.py +58 -0
  86. autorag/nodes/hybridretrieval/hybrid_cc.py +227 -0
  87. autorag/nodes/hybridretrieval/hybrid_rrf.py +149 -0
  88. autorag/nodes/hybridretrieval/run.py +137 -0
  89. autorag/nodes/lexicalretrieval/__init__.py +1 -0
  90. autorag/nodes/lexicalretrieval/bm25.py +381 -0
  91. autorag/nodes/lexicalretrieval/run.py +148 -0
  92. autorag/nodes/passageaugmenter/__init__.py +2 -0
  93. autorag/nodes/passageaugmenter/base.py +76 -0
  94. autorag/nodes/passageaugmenter/pass_passage_augmenter.py +43 -0
  95. autorag/nodes/passageaugmenter/prev_next_augmenter.py +155 -0
  96. autorag/nodes/passageaugmenter/run.py +131 -0
  97. autorag/nodes/passagecompressor/__init__.py +4 -0
  98. autorag/nodes/passagecompressor/base.py +78 -0
  99. autorag/nodes/passagecompressor/longllmlingua.py +115 -0
  100. autorag/nodes/passagecompressor/pass_compressor.py +16 -0
  101. autorag/nodes/passagecompressor/refine.py +54 -0
  102. autorag/nodes/passagecompressor/run.py +186 -0
  103. autorag/nodes/passagecompressor/tree_summarize.py +56 -0
  104. autorag/nodes/passagefilter/__init__.py +6 -0
  105. autorag/nodes/passagefilter/base.py +40 -0
  106. autorag/nodes/passagefilter/pass_passage_filter.py +14 -0
  107. autorag/nodes/passagefilter/percentile_cutoff.py +58 -0
  108. autorag/nodes/passagefilter/recency.py +105 -0
  109. autorag/nodes/passagefilter/run.py +138 -0
  110. autorag/nodes/passagefilter/similarity_percentile_cutoff.py +134 -0
  111. autorag/nodes/passagefilter/similarity_threshold_cutoff.py +112 -0
  112. autorag/nodes/passagefilter/threshold_cutoff.py +78 -0
  113. autorag/nodes/passagereranker/__init__.py +16 -0
  114. autorag/nodes/passagereranker/base.py +44 -0
  115. autorag/nodes/passagereranker/cohere.py +118 -0
  116. autorag/nodes/passagereranker/colbert.py +213 -0
  117. autorag/nodes/passagereranker/flag_embedding.py +112 -0
  118. autorag/nodes/passagereranker/flag_embedding_llm.py +101 -0
  119. autorag/nodes/passagereranker/flashrank.py +245 -0
  120. autorag/nodes/passagereranker/jina.py +115 -0
  121. autorag/nodes/passagereranker/koreranker.py +136 -0
  122. autorag/nodes/passagereranker/mixedbreadai.py +126 -0
  123. autorag/nodes/passagereranker/monot5.py +190 -0
  124. autorag/nodes/passagereranker/openvino.py +191 -0
  125. autorag/nodes/passagereranker/pass_reranker.py +31 -0
  126. autorag/nodes/passagereranker/rankgpt.py +170 -0
  127. autorag/nodes/passagereranker/run.py +145 -0
  128. autorag/nodes/passagereranker/sentence_transformer.py +129 -0
  129. autorag/nodes/passagereranker/tart/__init__.py +1 -0
  130. autorag/nodes/passagereranker/tart/modeling_enc_t5.py +152 -0
  131. autorag/nodes/passagereranker/tart/tart.py +139 -0
  132. autorag/nodes/passagereranker/tart/tokenization_enc_t5.py +112 -0
  133. autorag/nodes/passagereranker/time_reranker.py +72 -0
  134. autorag/nodes/passagereranker/upr.py +160 -0
  135. autorag/nodes/passagereranker/voyageai.py +109 -0
  136. autorag/nodes/promptmaker/__init__.py +12 -0
  137. autorag/nodes/promptmaker/base.py +32 -0
  138. autorag/nodes/promptmaker/chat_fstring.py +73 -0
  139. autorag/nodes/promptmaker/fstring.py +49 -0
  140. autorag/nodes/promptmaker/long_context_reorder.py +83 -0
  141. autorag/nodes/promptmaker/run.py +283 -0
  142. autorag/nodes/promptmaker/window_replacement.py +85 -0
  143. autorag/nodes/queryexpansion/__init__.py +4 -0
  144. autorag/nodes/queryexpansion/base.py +62 -0
  145. autorag/nodes/queryexpansion/hyde.py +43 -0
  146. autorag/nodes/queryexpansion/multi_query_expansion.py +57 -0
  147. autorag/nodes/queryexpansion/pass_query_expansion.py +22 -0
  148. autorag/nodes/queryexpansion/query_decompose.py +111 -0
  149. autorag/nodes/queryexpansion/run.py +308 -0
  150. autorag/nodes/retrieval/__init__.py +0 -0
  151. autorag/nodes/retrieval/base.py +127 -0
  152. autorag/nodes/retrieval/run_util.py +152 -0
  153. autorag/nodes/semanticretrieval/__init__.py +1 -0
  154. autorag/nodes/semanticretrieval/run.py +148 -0
  155. autorag/nodes/semanticretrieval/vectordb.py +339 -0
  156. autorag/nodes/util.py +16 -0
  157. autorag/parser.py +37 -0
  158. autorag/schema/__init__.py +3 -0
  159. autorag/schema/base.py +35 -0
  160. autorag/schema/metricinput.py +99 -0
  161. autorag/schema/module.py +24 -0
  162. autorag/schema/node.py +144 -0
  163. autorag/strategy.py +165 -0
  164. autorag/support.py +235 -0
  165. autorag/utils/__init__.py +8 -0
  166. autorag/utils/cast.py +45 -0
  167. autorag/utils/preprocess.py +149 -0
  168. autorag/utils/util.py +759 -0
  169. autorag/validator.py +98 -0
  170. autorag/vectordb/__init__.py +75 -0
  171. autorag/vectordb/base.py +73 -0
  172. autorag/vectordb/chroma.py +118 -0
  173. autorag/vectordb/couchbase.py +239 -0
  174. autorag/vectordb/milvus.py +169 -0
  175. autorag/vectordb/pinecone.py +121 -0
  176. autorag/vectordb/qdrant.py +155 -0
  177. autorag/vectordb/weaviate.py +184 -0
  178. autorag/web.py +81 -0
  179. autorag-0.0.0.dist-info/METADATA +780 -0
  180. autorag-0.0.0.dist-info/RECORD +184 -0
  181. autorag-0.0.0.dist-info/WHEEL +5 -0
  182. autorag-0.0.0.dist-info/entry_points.txt +2 -0
  183. autorag-0.0.0.dist-info/licenses/LICENSE +201 -0
  184. autorag-0.0.0.dist-info/top_level.txt +1 -0
autorag/evaluator.py ADDED
@@ -0,0 +1,559 @@
1
+ import glob
2
+ import json
3
+ import logging
4
+ import os
5
+ import shutil
6
+ from datetime import datetime
7
+ from itertools import chain
8
+ from typing import List, Dict, Optional
9
+
10
+ import pandas as pd
11
+ import yaml
12
+
13
+ from autorag.node_line import run_node_line
14
+ from autorag.nodes.retrieval.base import get_bm25_pkl_name
15
+ from autorag.nodes.lexicalretrieval.bm25 import bm25_ingest
16
+ from autorag.nodes.semanticretrieval.vectordb import (
17
+ vectordb_ingest_api,
18
+ filter_exist_ids,
19
+ filter_exist_ids_from_retrieval_gt,
20
+ vectordb_ingest_huggingface,
21
+ )
22
+ from autorag.schema import Node
23
+ from autorag.schema.node import (
24
+ module_type_exists,
25
+ extract_values_from_nodes,
26
+ extract_values_from_nodes_strategy,
27
+ )
28
+ from autorag.utils import (
29
+ cast_qa_dataset,
30
+ cast_corpus_dataset,
31
+ validate_qa_from_corpus_dataset,
32
+ )
33
+ from autorag.utils.util import (
34
+ load_summary_file,
35
+ explode,
36
+ load_yaml_config,
37
+ get_event_loop,
38
+ )
39
+ from autorag.vectordb import load_all_vectordb_from_yaml
40
+
41
+ logger = logging.getLogger("AutoRAG")
42
+
43
+ ascii_art = """
44
+ _ _____ _____
45
+ /\ | | | __ \ /\ / ____|
46
+ / \ _ _| |_ ___ | |__) | / \ | | __
47
+ / /\ \| | | | __/ _ \| _ / / /\ \| | |_ |
48
+ / ____ \ |_| | || (_) | | \ \ / ____ \ |__| |
49
+ /_/ \_\__,_|\__\___/|_| \_\/_/ \_\_____|
50
+
51
+ """
52
+
53
+
54
+ class Evaluator:
55
+ def __init__(
56
+ self,
57
+ qa_data_path: str,
58
+ corpus_data_path: str,
59
+ project_dir: Optional[str] = None,
60
+ ):
61
+ """
62
+ Initialize an Evaluator object.
63
+
64
+ :param qa_data_path: The path to the QA dataset.
65
+ Must be parquet file.
66
+ :param corpus_data_path: The path to the corpus dataset.
67
+ Must be parquet file.
68
+ :param project_dir: The path to the project directory.
69
+ Default is the current directory.
70
+ """
71
+ # validate data paths
72
+ if not os.path.exists(qa_data_path):
73
+ raise ValueError(f"QA data path {qa_data_path} does not exist.")
74
+ if not os.path.exists(corpus_data_path):
75
+ raise ValueError(f"Corpus data path {corpus_data_path} does not exist.")
76
+ if not qa_data_path.endswith(".parquet"):
77
+ raise ValueError(f"QA data path {qa_data_path} is not a parquet file.")
78
+ if not corpus_data_path.endswith(".parquet"):
79
+ raise ValueError(
80
+ f"Corpus data path {corpus_data_path} is not a parquet file."
81
+ )
82
+ self.qa_data_path = qa_data_path
83
+ self.corpus_data_path = corpus_data_path
84
+ self.qa_data = pd.read_parquet(qa_data_path, engine="pyarrow")
85
+ self.corpus_data = pd.read_parquet(corpus_data_path, engine="pyarrow")
86
+ self.qa_data = cast_qa_dataset(self.qa_data)
87
+ self.corpus_data = cast_corpus_dataset(self.corpus_data)
88
+ self.project_dir = project_dir if project_dir is not None else os.getcwd()
89
+ if not os.path.exists(self.project_dir):
90
+ os.makedirs(self.project_dir)
91
+
92
+ validate_qa_from_corpus_dataset(self.qa_data, self.corpus_data)
93
+
94
+ # copy dataset to the project directory
95
+ if not os.path.exists(os.path.join(self.project_dir, "data")):
96
+ os.makedirs(os.path.join(self.project_dir, "data"))
97
+ qa_path_in_project = os.path.join(self.project_dir, "data", "qa.parquet")
98
+ if not os.path.exists(qa_path_in_project):
99
+ self.qa_data.to_parquet(qa_path_in_project, index=False)
100
+ corpus_path_in_project = os.path.join(
101
+ self.project_dir, "data", "corpus.parquet"
102
+ )
103
+ if not os.path.exists(corpus_path_in_project):
104
+ self.corpus_data.to_parquet(corpus_path_in_project, index=False)
105
+
106
+ def start_trial(
107
+ self,
108
+ yaml_path: str,
109
+ skip_validation: bool = False,
110
+ full_ingest: bool = True,
111
+ ):
112
+ """
113
+ Start AutoRAG trial.
114
+ The trial means one experiment to optimize the RAG pipeline.
115
+ It consists of ingesting corpus data, running all nodes and modules, evaluating and finding the optimal modules.
116
+
117
+ :param yaml_path: The config YAML path
118
+ :param skip_validation: If True, it skips the validation step.
119
+ The validation step checks the input config YAML file is well formatted,
120
+ and there is any problem with the system settings.
121
+ Default is False.
122
+ :param full_ingest: If True, it checks the whole corpus data from corpus.parquet that exists in the Vector DB.
123
+ If your corpus is huge and don't want to check the whole vector DB, please set it to False.
124
+ :return: None
125
+ """
126
+ # Make Resources directory
127
+ os.makedirs(os.path.join(self.project_dir, "resources"), exist_ok=True)
128
+
129
+ if not skip_validation:
130
+ logger.info(ascii_art)
131
+ logger.info(
132
+ "Start Validation input data and config YAML file first. "
133
+ "If you want to skip this, put the --skip_validation flag or "
134
+ "`skip_validation` at the start_trial function."
135
+ )
136
+ from autorag.validator import Validator # resolve circular import
137
+
138
+ validator = Validator(
139
+ qa_data_path=self.qa_data_path, corpus_data_path=self.corpus_data_path
140
+ )
141
+ validator.validate(yaml_path)
142
+
143
+ os.environ["PROJECT_DIR"] = self.project_dir
144
+
145
+ trial_name = self.__get_new_trial_name()
146
+ self.__make_trial_dir(trial_name)
147
+
148
+ # copy YAML file to the trial directory
149
+ shutil.copy(
150
+ yaml_path, os.path.join(self.project_dir, trial_name, "config.yaml")
151
+ )
152
+ yaml_dict = load_yaml_config(yaml_path)
153
+ vectordb = yaml_dict.get("vectordb", [])
154
+
155
+ vectordb_config_path = os.path.join(
156
+ self.project_dir, "resources", "vectordb.yaml"
157
+ )
158
+ with open(vectordb_config_path, "w") as f:
159
+ yaml.safe_dump({"vectordb": vectordb}, f)
160
+
161
+ node_lines = self._load_node_lines(yaml_path)
162
+ self.__ingest_bm25_full(node_lines)
163
+
164
+ # Ingest VectorDB corpus
165
+ if any(
166
+ list(
167
+ map(
168
+ lambda nodes: module_type_exists(nodes, "vectordb"),
169
+ node_lines.values(),
170
+ )
171
+ )
172
+ ):
173
+ vectordb_list = load_all_vectordb_from_yaml(yaml_path, self.project_dir)
174
+ for vectordb in vectordb_list:
175
+ loop = get_event_loop()
176
+ target_corpus = loop.run_until_complete(
177
+ self.__get_ingest_target_corpus(vectordb, full_ingest)
178
+ )
179
+ if vectordb.embedding.__class__.class_name() == "HuggingFaceEmbedding":
180
+ vectordb_ingest_huggingface(vectordb, target_corpus)
181
+ else:
182
+ # API Ingest Method
183
+ loop = get_event_loop()
184
+ loop.run_until_complete(
185
+ vectordb_ingest_api(vectordb, target_corpus)
186
+ )
187
+
188
+ trial_summary_df = pd.DataFrame(
189
+ columns=[
190
+ "node_line_name",
191
+ "node_type",
192
+ "best_module_filename",
193
+ "best_module_name",
194
+ "best_module_params",
195
+ "best_execution_time",
196
+ ]
197
+ )
198
+
199
+ for i, (node_line_name, node_line) in enumerate(node_lines.items()):
200
+ node_line_dir = os.path.join(self.project_dir, trial_name, node_line_name)
201
+ os.makedirs(node_line_dir, exist_ok=False)
202
+ if i == 0:
203
+ previous_result = self.qa_data
204
+ logger.info(f"Running node line {node_line_name}...")
205
+ previous_result = run_node_line(
206
+ node_line,
207
+ node_line_dir,
208
+ previous_result,
209
+ )
210
+
211
+ trial_summary_df = self._append_node_line_summary(
212
+ node_line_name, node_line_dir, trial_summary_df
213
+ )
214
+
215
+ trial_summary_df.to_csv(
216
+ os.path.join(self.project_dir, trial_name, "summary.csv"), index=False
217
+ )
218
+
219
+ logger.info("Evaluation complete.")
220
+
221
+ def __ingest_bm25_full(self, node_lines: Dict[str, List[Node]]):
222
+ if any(
223
+ list(
224
+ map(
225
+ lambda nodes: module_type_exists(nodes, "bm25"), node_lines.values()
226
+ )
227
+ )
228
+ ):
229
+ logger.info("Embedding BM25 corpus...")
230
+ bm25_tokenizer_list = list(
231
+ chain.from_iterable(
232
+ map(
233
+ lambda nodes: self._find_bm25_tokenizer(nodes),
234
+ node_lines.values(),
235
+ )
236
+ )
237
+ )
238
+
239
+ if len(bm25_tokenizer_list) == 0:
240
+ bm25_tokenizer_list = ["porter_stemmer"]
241
+ for bm25_tokenizer in bm25_tokenizer_list:
242
+ bm25_dir = os.path.join(
243
+ self.project_dir, "resources", get_bm25_pkl_name(bm25_tokenizer)
244
+ )
245
+ if not os.path.exists(os.path.dirname(bm25_dir)):
246
+ os.makedirs(os.path.dirname(bm25_dir))
247
+ # ingest because bm25 supports update new corpus data
248
+ bm25_ingest(bm25_dir, self.corpus_data, bm25_tokenizer=bm25_tokenizer)
249
+ logger.info("BM25 corpus embedding complete.")
250
+
251
+ def __get_new_trial_name(self) -> str:
252
+ trial_json_path = os.path.join(self.project_dir, "trial.json")
253
+ if not os.path.exists(trial_json_path):
254
+ return "0"
255
+ with open(trial_json_path, "r") as f:
256
+ trial_json = json.load(f)
257
+ return str(int(trial_json[-1]["trial_name"]) + 1)
258
+
259
+ def __make_trial_dir(self, trial_name: str):
260
+ trial_json_path = os.path.join(self.project_dir, "trial.json")
261
+ if os.path.exists(trial_json_path):
262
+ with open(trial_json_path, "r") as f:
263
+ trial_json = json.load(f)
264
+ else:
265
+ trial_json = []
266
+
267
+ trial_json.append(
268
+ {
269
+ "trial_name": trial_name,
270
+ "start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
271
+ }
272
+ )
273
+ os.makedirs(os.path.join(self.project_dir, trial_name))
274
+ with open(trial_json_path, "w") as f:
275
+ json.dump(trial_json, f, indent=4)
276
+
277
+ @staticmethod
278
+ def _load_node_lines(yaml_path: str) -> Dict[str, List[Node]]:
279
+ yaml_dict = load_yaml_config(yaml_path)
280
+ node_lines = yaml_dict["node_lines"]
281
+ node_line_dict = {}
282
+ for node_line in node_lines:
283
+ node_line_dict[node_line["node_line_name"]] = list(
284
+ map(lambda node: Node.from_dict(node), node_line["nodes"])
285
+ )
286
+ return node_line_dict
287
+
288
+ def restart_trial(self, trial_path: str):
289
+ logger.info(ascii_art)
290
+ os.environ["PROJECT_DIR"] = self.project_dir
291
+ # Check if trial_path exists
292
+ if not os.path.exists(trial_path):
293
+ raise ValueError(f"Trial path {trial_path} does not exist.")
294
+ # Check if trial is completed
295
+ if os.path.exists(os.path.join(trial_path, "summary.csv")):
296
+ raise ValueError(f"Trial path {trial_path} is already completed.")
297
+
298
+ # Extract node lines from config.yaml
299
+ yaml_path = os.path.join(trial_path, "config.yaml")
300
+ node_lines = self._load_node_lines(yaml_path)
301
+
302
+ node_line_names = list(node_lines.keys())
303
+ nodes = list(node_lines.values())
304
+ node_names = list(
305
+ map(lambda node: list(map(lambda n: n.node_type, node)), nodes)
306
+ )
307
+
308
+ # If the First Node Line folder hasn't even been created, proceed to start_trial
309
+ if not os.path.exists(os.path.join(trial_path, node_line_names[0])):
310
+ self.start_trial(yaml_path)
311
+ return None
312
+
313
+ # Find conflict node line and node
314
+ conflict_line_name, conflict_node_name = self.__find_conflict_point(
315
+ trial_path, node_line_names, node_lines
316
+ )
317
+ node_dir = os.path.join(trial_path, conflict_line_name, conflict_node_name)
318
+ if os.path.exists(node_dir):
319
+ shutil.rmtree(node_dir)
320
+
321
+ # Set remain_nodes and remain_lines
322
+ remain_nodes, completed_node_names, remain_lines, remain_line_names = (
323
+ self._set_remain_nodes_and_lines(
324
+ node_line_names,
325
+ nodes,
326
+ node_names,
327
+ conflict_node_name,
328
+ conflict_line_name,
329
+ )
330
+ )
331
+ # Set previous_result
332
+ previous_result = self.__set_previous_result(
333
+ node_line_names, node_names, trial_path, conflict_node_name
334
+ )
335
+
336
+ # Run Node
337
+ if remain_nodes:
338
+ conflict_line_dir = os.path.join(trial_path, conflict_line_name)
339
+ summary_lst = []
340
+ # Get already run node summary and append to summary_lst
341
+ for completed_node_name in completed_node_names:
342
+ summary_lst = self._append_node_summary(
343
+ conflict_line_dir, completed_node_name, summary_lst
344
+ )
345
+ for node in remain_nodes:
346
+ previous_result = node.run(previous_result, conflict_line_dir)
347
+ summary_lst = self._append_node_summary(
348
+ conflict_line_dir, node.node_type, summary_lst
349
+ )
350
+ pd.DataFrame(summary_lst).to_csv(
351
+ os.path.join(conflict_line_dir, "summary.csv"), index=False
352
+ )
353
+
354
+ # Run node line
355
+ trial_summary_df = pd.DataFrame(
356
+ columns=[
357
+ "node_line_name",
358
+ "node_type",
359
+ "best_module_filename",
360
+ "best_module_name",
361
+ "best_module_params",
362
+ "best_execution_time",
363
+ ]
364
+ )
365
+ completed_line_names = node_line_names[
366
+ : node_line_names.index(conflict_line_name)
367
+ ]
368
+ # Get already run node line's summary and append to trial_summary_df
369
+ if completed_line_names:
370
+ for line_name in completed_line_names:
371
+ node_line_dir = os.path.join(trial_path, line_name)
372
+ trial_summary_df = self._append_node_line_summary(
373
+ line_name, node_line_dir, trial_summary_df
374
+ )
375
+ if remain_lines:
376
+ for node_line_name, node_line in zip(remain_line_names, remain_lines):
377
+ node_line_dir = os.path.join(trial_path, node_line_name)
378
+ if not os.path.exists(node_line_dir):
379
+ os.makedirs(node_line_dir)
380
+ logger.info(f"Running node line {node_line_name}...")
381
+ previous_result = run_node_line(
382
+ node_line, node_line_dir, previous_result
383
+ )
384
+ trial_summary_df = self._append_node_line_summary(
385
+ node_line_name, node_line_dir, trial_summary_df
386
+ )
387
+ trial_summary_df.to_csv(os.path.join(trial_path, "summary.csv"), index=False)
388
+
389
+ logger.info("Evaluation complete.")
390
+
391
+ def __find_conflict_point(
392
+ self,
393
+ trial_path: str,
394
+ node_line_names: List[str],
395
+ node_lines: Dict[str, List[Node]],
396
+ ) -> tuple[str, str]:
397
+ for node_line_name in node_line_names:
398
+ node_line_dir = os.path.join(trial_path, node_line_name)
399
+ if not os.path.exists(node_line_dir):
400
+ return node_line_name, node_lines[node_line_name][0].node_type
401
+
402
+ if not os.path.exists(os.path.join(node_line_dir, "summary.csv")):
403
+ conflict_node_name = self._find_conflict_node_name(
404
+ node_line_dir, node_lines[node_line_name]
405
+ )
406
+ return node_line_name, conflict_node_name
407
+
408
+ raise ValueError(f"No error node line found in {trial_path}.")
409
+
410
+ @staticmethod
411
+ def _find_conflict_node_name(node_line_dir: str, node_line: List[Node]) -> str:
412
+ for node in node_line:
413
+ node_dir = os.path.join(node_line_dir, node.node_type)
414
+ if not os.path.exists(node_dir) or not os.path.exists(
415
+ os.path.join(node_dir, "summary.csv")
416
+ ):
417
+ return node.node_type
418
+ raise TypeError("No conflict node name found.")
419
+
420
+ def __set_previous_result(
421
+ self,
422
+ node_line_names: List[str],
423
+ node_names: List[List[str]],
424
+ trial_path: str,
425
+ conflict_node_name: str,
426
+ ):
427
+ exploded_node_line, exploded_node = explode(node_line_names, node_names)
428
+ conflict_node_index = exploded_node.index(conflict_node_name)
429
+ # Set previous_result
430
+ if conflict_node_index == 0:
431
+ previous_result = self.qa_data
432
+ else:
433
+ previous_node_line = exploded_node_line[conflict_node_index - 1]
434
+ previous_node = exploded_node[conflict_node_index - 1]
435
+
436
+ previous_node_dir = os.path.join(
437
+ trial_path, previous_node_line, previous_node
438
+ )
439
+ best_file_pattern = f"{previous_node_dir}/best_*.parquet"
440
+ previous_result = pd.read_parquet(
441
+ glob.glob(best_file_pattern)[0], engine="pyarrow"
442
+ )
443
+ return previous_result
444
+
445
+ @staticmethod
446
+ def _set_remain_nodes_and_lines(
447
+ node_line_names: List[str],
448
+ nodes: List[List[Node]],
449
+ node_names: List[List[str]],
450
+ conflict_node_name: str,
451
+ conflict_node_line_name: str,
452
+ ):
453
+ conflict_node_line_index = node_line_names.index(conflict_node_line_name)
454
+ full_conflict_node_line_nodes = nodes[conflict_node_line_index]
455
+ full_conflict_node_line_node_names = node_names[conflict_node_line_index]
456
+
457
+ if conflict_node_name == full_conflict_node_line_node_names[0]:
458
+ remain_nodes = None
459
+ completed_node_names = None
460
+ remain_node_lines = nodes[conflict_node_line_index:]
461
+ remain_node_line_names = node_line_names[conflict_node_line_index:]
462
+ else:
463
+ conflict_node_index = full_conflict_node_line_node_names.index(
464
+ conflict_node_name
465
+ )
466
+ remain_nodes = full_conflict_node_line_nodes[conflict_node_index:]
467
+ completed_node_names = full_conflict_node_line_node_names[
468
+ :conflict_node_index
469
+ ]
470
+ if conflict_node_line_index + 1 >= len(node_line_names):
471
+ remain_node_lines = None
472
+ remain_node_line_names = None
473
+ else:
474
+ remain_node_lines = nodes[conflict_node_line_index + 1 :]
475
+ remain_node_line_names = node_line_names[conflict_node_line_index + 1 :]
476
+ return (
477
+ remain_nodes,
478
+ completed_node_names,
479
+ remain_node_lines,
480
+ remain_node_line_names,
481
+ )
482
+
483
+ @staticmethod
484
+ def _append_node_line_summary(
485
+ node_line_name: str, node_line_dir: str, trial_summary_df: pd.DataFrame
486
+ ):
487
+ summary_df = load_summary_file(
488
+ os.path.join(node_line_dir, "summary.csv"),
489
+ dict_columns=["best_module_params"],
490
+ )
491
+ summary_df = summary_df.assign(node_line_name=node_line_name)
492
+ summary_df = summary_df[list(trial_summary_df.columns)]
493
+ if len(trial_summary_df) <= 0:
494
+ trial_summary_df = summary_df
495
+ else:
496
+ trial_summary_df = pd.concat(
497
+ [trial_summary_df, summary_df], ignore_index=True
498
+ )
499
+ return trial_summary_df
500
+
501
+ @staticmethod
502
+ def _append_node_summary(
503
+ node_line_dir: str, node_name: str, summary_lst: List[Dict]
504
+ ):
505
+ node_summary_df = load_summary_file(
506
+ os.path.join(node_line_dir, node_name, "summary.csv")
507
+ )
508
+ best_node_row = node_summary_df.loc[node_summary_df["is_best"]]
509
+ summary_lst.append(
510
+ {
511
+ "node_type": node_name,
512
+ "best_module_filename": best_node_row["filename"].values[0],
513
+ "best_module_name": best_node_row["module_name"].values[0],
514
+ "best_module_params": best_node_row["module_params"].values[0],
515
+ "best_execution_time": best_node_row["execution_time"].values[0],
516
+ }
517
+ )
518
+ return summary_lst
519
+
520
+ @staticmethod
521
+ def _find_bm25_tokenizer(nodes: List[Node]):
522
+ bm25_tokenizer_list = extract_values_from_nodes(nodes, "bm25_tokenizer")
523
+ strategy_tokenizer_list = list(
524
+ chain.from_iterable(
525
+ extract_values_from_nodes_strategy(nodes, "bm25_tokenizer")
526
+ )
527
+ )
528
+ return list(set(bm25_tokenizer_list + strategy_tokenizer_list))
529
+
530
+ @staticmethod
531
+ def _find_embedding_model(nodes: List[Node]):
532
+ embedding_models_list = extract_values_from_nodes(nodes, "embedding_model")
533
+ retrieval_module_dicts = extract_values_from_nodes_strategy(
534
+ nodes, "retrieval_modules"
535
+ )
536
+ for retrieval_modules in retrieval_module_dicts:
537
+ vectordb_modules = list(
538
+ filter(lambda x: x["module_type"] == "vectordb", retrieval_modules)
539
+ )
540
+ embedding_models_list.extend(
541
+ list(map(lambda x: x.get("embedding_model", None), vectordb_modules))
542
+ )
543
+ embedding_models_list = list(
544
+ filter(lambda x: x is not None, embedding_models_list)
545
+ )
546
+ return list(set(embedding_models_list))
547
+
548
+ async def __get_ingest_target_corpus(
549
+ self, vectordb, full_ingest: bool
550
+ ) -> pd.DataFrame:
551
+ if full_ingest is True:
552
+ # get the target ingest corpus from the whole corpus
553
+ target_corpus = await filter_exist_ids(vectordb, self.corpus_data)
554
+ else:
555
+ # get the target ingest corpus from the retrieval gt only
556
+ target_corpus = await filter_exist_ids_from_retrieval_gt(
557
+ vectordb, self.qa_data, self.corpus_data
558
+ )
559
+ return target_corpus
autorag/node_line.py ADDED
@@ -0,0 +1,65 @@
1
+ import os
2
+ import pathlib
3
+ from typing import Dict, List, Optional
4
+
5
+ import pandas as pd
6
+
7
+ from autorag.schema import Node
8
+ from autorag.utils.util import load_summary_file
9
+
10
+
11
+ def make_node_lines(node_line_dict: Dict) -> List[Node]:
12
+ """
13
+ This method makes a list of nodes from node line dictionary.
14
+ :param node_line_dict: Node_line_dict loaded from yaml file, or get from user input.
15
+ :return: List of Nodes inside this node line.
16
+ """
17
+ nodes = node_line_dict.get("nodes")
18
+ if nodes is None:
19
+ raise ValueError("Node line must have 'nodes' key.")
20
+ node_objects = list(map(lambda x: Node.from_dict(x), nodes))
21
+ return node_objects
22
+
23
+
24
+ def run_node_line(
25
+ nodes: List[Node],
26
+ node_line_dir: str,
27
+ previous_result: Optional[pd.DataFrame] = None,
28
+ ):
29
+ """
30
+ Run the whole node line by running each node.
31
+
32
+ :param nodes: A list of nodes.
33
+ :param node_line_dir: This node line's directory.
34
+ :param previous_result: A result of the previous node line.
35
+ If None, it loads qa data from data/qa.parquet.
36
+ :return: The final result of the node line.
37
+ """
38
+ if previous_result is None:
39
+ project_dir = pathlib.PurePath(node_line_dir).parent.parent
40
+ qa_path = os.path.join(project_dir, "data", "qa.parquet")
41
+ if not os.path.exists(qa_path):
42
+ raise ValueError(f"qa.parquet does not exist in {qa_path}.")
43
+ previous_result = pd.read_parquet(qa_path, engine="pyarrow")
44
+
45
+ summary_lst = []
46
+ for node in nodes:
47
+ previous_result = node.run(previous_result, node_line_dir)
48
+ node_summary_df = load_summary_file(
49
+ os.path.join(node_line_dir, node.node_type, "summary.csv")
50
+ )
51
+ best_node_row = node_summary_df.loc[node_summary_df["is_best"]]
52
+ summary_lst.append(
53
+ {
54
+ "node_type": node.node_type,
55
+ "best_module_filename": best_node_row["filename"].values[0],
56
+ "best_module_name": best_node_row["module_name"].values[0],
57
+ "best_module_params": best_node_row["module_params"].values[0],
58
+ "best_execution_time": best_node_row["execution_time"].values[0],
59
+ }
60
+ )
61
+
62
+ pd.DataFrame(summary_lst).to_csv(
63
+ os.path.join(node_line_dir, "summary.csv"), index=False
64
+ )
65
+ return previous_result
File without changes
@@ -0,0 +1,4 @@
1
+ from .llama_index_llm import LlamaIndexLLM
2
+ from .openai_llm import OpenAILLM
3
+ from .vllm import Vllm
4
+ from .vllm_api import VllmAPI