flashrag-dev 0.1.1__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 (48) hide show
  1. flashrag/__init__.py +0 -0
  2. flashrag/config/__init__.py +2 -0
  3. flashrag/config/config.py +219 -0
  4. flashrag/dataset/__init__.py +2 -0
  5. flashrag/dataset/dataset.py +160 -0
  6. flashrag/dataset/utils.py +60 -0
  7. flashrag/evaluator/__init__.py +2 -0
  8. flashrag/evaluator/_bleu.py +209 -0
  9. flashrag/evaluator/evaluator.py +82 -0
  10. flashrag/evaluator/metrics.py +508 -0
  11. flashrag/evaluator/utils.py +19 -0
  12. flashrag/generator/__init__.py +2 -0
  13. flashrag/generator/fid.py +247 -0
  14. flashrag/generator/generator.py +623 -0
  15. flashrag/generator/openai_generator.py +96 -0
  16. flashrag/generator/stop_word_criteria.py +105 -0
  17. flashrag/judger/__init__.py +1 -0
  18. flashrag/judger/judger.py +184 -0
  19. flashrag/pipeline/__init__.py +3 -0
  20. flashrag/pipeline/active_pipeline.py +980 -0
  21. flashrag/pipeline/branching_pipeline.py +250 -0
  22. flashrag/pipeline/pipeline.py +248 -0
  23. flashrag/pipeline/replug_utils.py +249 -0
  24. flashrag/prompt/__init__.py +1 -0
  25. flashrag/prompt/base_prompt.py +165 -0
  26. flashrag/prompt/selfask_examplars.py +108 -0
  27. flashrag/prompt/trace_examplars.py +4015 -0
  28. flashrag/refiner/__init__.py +2 -0
  29. flashrag/refiner/kg_refiner.py +612 -0
  30. flashrag/refiner/llmlingua_compressor.py +2360 -0
  31. flashrag/refiner/refiner.py +252 -0
  32. flashrag/refiner/selective_context_compressor.py +294 -0
  33. flashrag/retriever/__init__.py +3 -0
  34. flashrag/retriever/__main__.py +4 -0
  35. flashrag/retriever/encoder.py +120 -0
  36. flashrag/retriever/index_builder.py +334 -0
  37. flashrag/retriever/reranker.py +145 -0
  38. flashrag/retriever/retriever.py +346 -0
  39. flashrag/retriever/utils.py +49 -0
  40. flashrag/utils/__init__.py +2 -0
  41. flashrag/utils/constants.py +51 -0
  42. flashrag/utils/pred_parse.py +25 -0
  43. flashrag/utils/utils.py +135 -0
  44. flashrag_dev-0.1.1.dist-info/LICENSE +21 -0
  45. flashrag_dev-0.1.1.dist-info/METADATA +616 -0
  46. flashrag_dev-0.1.1.dist-info/RECORD +48 -0
  47. flashrag_dev-0.1.1.dist-info/WHEEL +5 -0
  48. flashrag_dev-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,250 @@
1
+ import itertools
2
+ from typing import List
3
+ import re
4
+ from tqdm import tqdm
5
+ import numpy as np
6
+ from transformers import LogitsProcessorList
7
+ from flashrag.utils import get_retriever, get_generator
8
+ from flashrag.pipeline import BasicPipeline
9
+ from flashrag.prompt import PromptTemplate
10
+
11
+
12
+ class REPLUGPipeline(BasicPipeline):
13
+ def __init__(self, config, prompt_template=None):
14
+ from flashrag.pipeline.replug_utils import load_replug_model
15
+
16
+ super().__init__(config, prompt_template)
17
+ # load specify model for REPLUG
18
+ model = load_replug_model(config["generator_model_path"])
19
+ self.generator = get_generator(config, model=model)
20
+
21
+ self.retriever = get_retriever(config)
22
+
23
+ def build_single_doc_prompt(self, question: str, doc_list: List[str]):
24
+ return [self.prompt_template.get_string(question=question, formatted_reference=doc) for doc in doc_list]
25
+
26
+ def format_reference(self, doc_item):
27
+ content = doc_item["contents"]
28
+ title = content.split("\n")[0]
29
+ text = "\n".join(content.split("\n")[1:])
30
+ return f"Document(Title: {title}): {text}"
31
+
32
+ def run(self, dataset, do_eval=True, pred_process_fun=None):
33
+ import torch
34
+ from flashrag.pipeline.replug_utils import REPLUGLogitsProcessor
35
+
36
+ input_query = dataset.question
37
+
38
+ retrieval_results, doc_scores = self.retriever.batch_search(input_query, return_score=True)
39
+ dataset.update_output("retrieval_result", retrieval_results)
40
+ dataset.update_output("doc_scores", doc_scores)
41
+
42
+ pred_answer_list = []
43
+ # each doc has a prompt
44
+ for item in tqdm(dataset, desc="Inference: "):
45
+ docs = [self.format_reference(doc_item) for doc_item in item.retrieval_result]
46
+ prompts = self.build_single_doc_prompt(question=item.question, doc_list=docs)
47
+
48
+ scores = torch.tensor(item.doc_scores, dtype=torch.float32).to(self.device)
49
+ output = self.generator.generate(
50
+ prompts, batch_size=len(docs), logits_processor=LogitsProcessorList([REPLUGLogitsProcessor(scores)])
51
+ )
52
+ # the output of the batch is same
53
+ output = output[0]
54
+ pred_answer_list.append(output)
55
+
56
+ dataset.update_output("pred", pred_answer_list)
57
+
58
+ dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
59
+
60
+ return dataset
61
+
62
+
63
+ class SuRePipeline(BasicPipeline):
64
+ def __init__(self, config, prompt_template=None):
65
+ super().__init__(config, prompt_template)
66
+ self.config = config
67
+ self.generator = get_generator(config)
68
+ self.retriever = get_retriever(config)
69
+
70
+ self.load_prompts()
71
+
72
+ def load_prompts(self):
73
+ # prompt for candidates generation
74
+ P_CAN_INSTRUCT = (
75
+ "Below are {N} passages related to the question at the end. After reading"
76
+ "the passages, provide two correct candidates for the answer to the"
77
+ "question at the end. Each answer should be in the form: (a) xx, (b)"
78
+ "yy, and should not exceed 3 words for each candidate.\n\n"
79
+ "{reference}"
80
+ "Question: {question}\n"
81
+ "Answer:"
82
+ )
83
+
84
+ # prompt for candidate-conditioned summarization
85
+ P_SUM_INSTRUCT = (
86
+ "Reference:\n{reference}\n"
87
+ "Your job is to act as a professional writer. You need to write a"
88
+ "good-quality passage that can support the given prediction about the"
89
+ "question only based on the information in the provided supporting passages.\n"
90
+ "Now, let's start. After you write, please write [DONE] to indicate you"
91
+ "are done. Do not write a prefix (e.g., 'Response:') while writing a passage.\n"
92
+ "Question: {question}\n"
93
+ "Prediction: {pred}\n"
94
+ "Passage:"
95
+ )
96
+
97
+ # prompt for instance-wise validation
98
+ P_VAL_INSTRUCT = (
99
+ "Question: {question}\n"
100
+ "Prediction: {pred}\n"
101
+ "Passage: {summary}\n"
102
+ "Does the passage correctly support the prediction? Choices: [True,False].\n"
103
+ "Answer:"
104
+ )
105
+
106
+ # prompt for pair-wise ranking
107
+ P_RANK_INSTRUCT = (
108
+ "Question: Given the following passages, determine which one provides a"
109
+ "more informative answer to the subsequent question.\n"
110
+ "Passage 1: {summary1}\n"
111
+ "Passage 2: {summary2}\n"
112
+ "Target Question: {question}\n"
113
+ "Your Task:\n"
114
+ "Identify which passage (Passage 1 or Passage 2) is more relevant and"
115
+ "informative to answer the question at hand. Choices: [Passage 1,Passage 2].\n"
116
+ "Answer:"
117
+ )
118
+
119
+ self.P_CAN_TEMPLATE = PromptTemplate(self.config, "", P_CAN_INSTRUCT)
120
+ self.P_SUM_TEMPLATE = PromptTemplate(self.config, "", P_SUM_INSTRUCT)
121
+ self.P_VAL_TEMPLATE = PromptTemplate(self.config, "", P_VAL_INSTRUCT)
122
+ self.P_RANK_TEMPLATE = PromptTemplate(self.config, "", P_RANK_INSTRUCT)
123
+
124
+ @staticmethod
125
+ def format_ref(titles, texts):
126
+ formatted_ref = ""
127
+ idx = 1
128
+ for title, text in zip(titles, texts):
129
+ formatted_ref += f"Passage #{idx} Title: {title}\n"
130
+ formatted_ref += f"Passage #{idx} Text: {text}\n"
131
+ formatted_ref += "\n"
132
+ idx += 1
133
+ return formatted_ref
134
+
135
+ @staticmethod
136
+ def parse_candidates(model_response):
137
+ """Parse candidates from model response"""
138
+ model_response = model_response.strip("\n").strip()
139
+ # r'\([a-z]\) ([^,]+)'
140
+ candidates = re.findall("\((\w+)\)\s*([^()]+)", model_response)
141
+ candidates = [cand[1].split("\n")[0].strip() for cand in candidates]
142
+ # post-process
143
+ candidates = [cand.replace(",", "").strip() for cand in candidates]
144
+ return candidates
145
+
146
+ @staticmethod
147
+ def parse_validation(model_response):
148
+ """Parse model's validation result into score based on the paper formula"""
149
+ model_response = model_response.strip().lower()
150
+ if "true" in model_response:
151
+ return 1
152
+ else:
153
+ return 0
154
+
155
+ @staticmethod
156
+ def parse_ranking(model_response):
157
+ """Parse model's pair ranking result into score"""
158
+ model_response = model_response.strip().lower()
159
+ if "passage 1" in model_response:
160
+ score = 1
161
+ elif "passage 2" in model_response:
162
+ score = 0
163
+ else:
164
+ score = 0.5
165
+ return score
166
+
167
+ def run(self, dataset, do_eval=True, pred_process_fun=None):
168
+ input_query = dataset.question
169
+
170
+ retrieval_results, doc_scores = self.retriever.batch_search(input_query, return_score=True)
171
+ dataset.update_output("retrieval_result", retrieval_results)
172
+
173
+ pred_answer_list = []
174
+ for item in tqdm(dataset, desc="Pipeline runing: "):
175
+ retrieval_result = item.retrieval_result
176
+ doc_num = len(retrieval_result)
177
+ # format all docs
178
+ for doc_item in retrieval_result:
179
+ if "title" not in doc_item or "text" not in doc_item:
180
+ doc_item["title"] = doc_item["contents"].split("\n")[0]
181
+ doc_item["text"] = "\n".join(doc_item["contents"].split("\n")[1:])
182
+ formatted_ref = self.format_ref(
183
+ titles=[i["title"] for i in retrieval_result], texts=[i["text"] for i in retrieval_result]
184
+ )
185
+ # get candidates
186
+
187
+ input_prompt = self.P_CAN_TEMPLATE.get_string(
188
+ N=doc_num, formatted_reference=formatted_ref, question=item.question
189
+ )
190
+ output = self.generator.generate([input_prompt])[0]
191
+ candidates = self.parse_candidates(output)
192
+ item.update_output("candidates", candidates)
193
+
194
+ if len(candidates) == 0:
195
+ print("No valid predictions!")
196
+ pred = ""
197
+ pred_answer_list.append(pred)
198
+ continue
199
+
200
+ # get summarization for each candidate
201
+ input_prompts = [
202
+ self.P_SUM_TEMPLATE.get_string(question=item.question, pred=cand, formatted_reference=formatted_ref)
203
+ for cand in candidates
204
+ ]
205
+
206
+ all_summary = self.generator.generate(input_prompts)
207
+ item.update_output("all_summary", all_summary)
208
+
209
+ # instance-wise validation
210
+ input_prompts = [
211
+ self.P_VAL_TEMPLATE.get_string(question=item.question, pred=cand, summary=summary)
212
+ for cand, summary in zip(candidates, all_summary)
213
+ ]
214
+ val_results = self.generator.generate(input_prompts)
215
+ val_scores = [self.parse_validation(res) for res in val_results]
216
+ item.update_output("val_scores", val_scores)
217
+
218
+ # pair-wise ranking
219
+ summary_num = len(all_summary)
220
+ score_matrix = np.zeros((summary_num, summary_num))
221
+ iter_idxs = list(itertools.permutations(range(summary_num), 2))
222
+ input_prompts = [
223
+ self.P_RANK_TEMPLATE.get_string(
224
+ question=item.question, summary1=all_summary[idx_tuple[0]], summary2=all_summary[idx_tuple[1]]
225
+ )
226
+ for idx_tuple in iter_idxs
227
+ ]
228
+ ranking_output = self.generator.generate(input_prompts)
229
+ ranking_scores = [self.parse_ranking(res) for res in ranking_output]
230
+ for idx_tuple, score in zip(iter_idxs, ranking_scores):
231
+ score_matrix[idx_tuple[0], idx_tuple[1]] = score
232
+ ranking_scores = score_matrix.sum(axis=1).squeeze().tolist() # ranking score for each summary
233
+ item.update_output("ranking_scores", ranking_scores)
234
+
235
+ # combine two scores as the final score for each summary
236
+ if not isinstance(ranking_scores, list):
237
+ ranking_scores = [ranking_scores]
238
+ if not isinstance(val_scores, list):
239
+ val_scores = [val_scores]
240
+ total_scores = [x + y for x, y in zip(val_scores, ranking_scores)]
241
+
242
+ best_idx = np.argmax(total_scores)
243
+ pred = candidates[best_idx]
244
+ pred_answer_list.append(pred)
245
+
246
+ dataset.update_output("pred", pred_answer_list)
247
+
248
+ dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
249
+
250
+ return dataset
@@ -0,0 +1,248 @@
1
+ from flashrag.evaluator import Evaluator
2
+ from flashrag.dataset.utils import split_dataset, merge_dataset
3
+ from flashrag.utils import get_retriever, get_generator, get_refiner, get_judger
4
+ from flashrag.prompt import PromptTemplate
5
+
6
+
7
+ class BasicPipeline:
8
+ """Base object of all pipelines. A pipeline includes the overall process of RAG.
9
+ If you want to implement a pipeline, you should inherit this class.
10
+ """
11
+
12
+ def __init__(self, config, prompt_template=None):
13
+ self.config = config
14
+ self.device = config["device"]
15
+ self.retriever = None
16
+ self.evaluator = Evaluator(config)
17
+ self.save_retrieval_cache = config["save_retrieval_cache"]
18
+ if prompt_template is None:
19
+ prompt_template = PromptTemplate(config)
20
+ self.prompt_template = prompt_template
21
+
22
+ def run(self, dataset):
23
+ """The overall inference process of a RAG framework."""
24
+ pass
25
+
26
+ def evaluate(self, dataset, do_eval=True, pred_process_fun=None):
27
+ """The evaluation process after finishing overall generation"""
28
+
29
+ if pred_process_fun is not None:
30
+ raw_pred = dataset.pred
31
+ processed_pred = [pred_process_fun(pred) for pred in raw_pred]
32
+ dataset.update_output("raw_pred", raw_pred)
33
+ dataset.update_output("pred", processed_pred)
34
+
35
+ if do_eval:
36
+ # evaluate & save result
37
+ eval_result = self.evaluator.evaluate(dataset)
38
+ print(eval_result)
39
+
40
+ # save retrieval cache
41
+ if self.save_retrieval_cache:
42
+ self.retriever._save_cache()
43
+
44
+ return dataset
45
+
46
+
47
+ class SequentialPipeline(BasicPipeline):
48
+ def __init__(self, config, prompt_template=None, retriever=None, generator=None):
49
+ """
50
+ inference stage:
51
+ query -> pre-retrieval -> retriever -> post-retrieval -> generator
52
+ """
53
+
54
+ super().__init__(config, prompt_template)
55
+ if generator is None:
56
+ self.generator = get_generator(config)
57
+ else:
58
+ self.generator = generator
59
+
60
+ if retriever is None:
61
+ self.retriever = get_retriever(config)
62
+ else:
63
+ self.retriever = retriever
64
+
65
+ # TODO: add rewriter module
66
+
67
+ self.use_fid = config["use_fid"]
68
+
69
+ if config["refiner_name"] is not None:
70
+ self.refiner = get_refiner(config, self.retriever, self.generator)
71
+ else:
72
+ self.refiner = None
73
+
74
+ def naive_run(self, dataset, do_eval=True, pred_process_fun=None):
75
+ # direct generation without RAG
76
+ input_prompts = [self.prompt_template.get_string(question=q) for q in dataset.question]
77
+ dataset.update_output("prompt", input_prompts)
78
+
79
+ pred_answer_list = self.generator.generate(input_prompts)
80
+ dataset.update_output("pred", pred_answer_list)
81
+
82
+ dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
83
+ return dataset
84
+
85
+ def run(self, dataset, do_eval=True, pred_process_fun=None):
86
+ input_query = dataset.question
87
+
88
+ retrieval_results = self.retriever.batch_search(input_query)
89
+ dataset.update_output("retrieval_result", retrieval_results)
90
+
91
+ if self.refiner:
92
+ input_prompt_flag = self.refiner.input_prompt_flag
93
+ if "llmlingua" in self.refiner.name and input_prompt_flag:
94
+ # input prompt
95
+ input_prompts = [
96
+ self.prompt_template.get_string(question=q, retrieval_result=r)
97
+ for q, r in zip(dataset.question, dataset.retrieval_result)
98
+ ]
99
+ dataset.update_output("prompt", input_prompts)
100
+ input_prompts = self.refiner.batch_run(dataset)
101
+ else:
102
+ # input retrieval docs
103
+ refine_results = self.refiner.batch_run(dataset)
104
+ dataset.update_output("refine_result", refine_results)
105
+ input_prompts = [
106
+ self.prompt_template.get_string(question=q, formatted_reference=r)
107
+ for q, r in zip(dataset.question, refine_results)
108
+ ]
109
+
110
+ else:
111
+ input_prompts = [
112
+ self.prompt_template.get_string(question=q, retrieval_result=r)
113
+ for q, r in zip(dataset.question, dataset.retrieval_result)
114
+ ]
115
+ dataset.update_output("prompt", input_prompts)
116
+
117
+ if self.use_fid:
118
+ print("Use FiD generation")
119
+ input_prompts = []
120
+ for item in dataset:
121
+ q = item.question
122
+ docs = item.retrieval_result
123
+ input_prompts.append([q + " " + doc for doc in docs])
124
+ # delete used refiner to release memory
125
+ if self.refiner:
126
+ del self.refiner
127
+ pred_answer_list = self.generator.generate(input_prompts)
128
+ dataset.update_output("pred", pred_answer_list)
129
+
130
+ dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
131
+
132
+ return dataset
133
+
134
+
135
+ class ConditionalPipeline(BasicPipeline):
136
+ def __init__(self, config, prompt_template=None):
137
+ """
138
+ inference stage:
139
+ query -> judger -> sequential pipeline or naive generate
140
+ """
141
+
142
+ super().__init__(config, prompt_template)
143
+ self.judger = get_judger(config)
144
+ self.generator = get_generator(config)
145
+ self.retriever = get_retriever(config)
146
+
147
+ self.sequential_pipeline = SequentialPipeline(
148
+ config, prompt_template, retriever=self.retriever, generator=self.generator
149
+ )
150
+
151
+ self.zero_shot_templete = PromptTemplate(
152
+ config=config,
153
+ system_prompt="Answer the question based on your own knowledge. \
154
+ Only give me the answer and do not output any other words.",
155
+ user_prompt="Question: {question}",
156
+ )
157
+
158
+ def run(self, dataset, do_eval=True, pred_process_fun=None):
159
+ # judge_result: list of bool element, representing whether to use retrieval
160
+ judge_result = self.judger.judge(dataset)
161
+ dataset.update_output("judge_result", judge_result)
162
+
163
+ # split dataset based on judge_result
164
+ dataset_split = split_dataset(dataset, judge_result)
165
+ pos_dataset, neg_dataset = dataset_split[True], dataset_split[False]
166
+
167
+ pos_dataset = self.sequential_pipeline.run(pos_dataset, do_eval=False)
168
+ self.sequential_pipeline.prompt_template = self.zero_shot_templete
169
+ neg_dataset = self.sequential_pipeline.naive_run(neg_dataset, do_eval=False)
170
+
171
+ # merge datasets into original format
172
+ dataset = merge_dataset(dataset_split, judge_result)
173
+
174
+ dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
175
+
176
+ return dataset
177
+
178
+
179
+ class AdaptivePipeline(BasicPipeline):
180
+ def __init__(
181
+ self,
182
+ config,
183
+ norag_template=None,
184
+ single_hop_prompt_template=None,
185
+ multi_hop_prompt_template=None,
186
+ ):
187
+ super().__init__(config)
188
+ # load adaptive classifier as judger
189
+ self.judger = get_judger(config)
190
+
191
+ generator = get_generator(config)
192
+ retriever = get_retriever(config)
193
+ self.generator = generator
194
+ self.retriever = retriever
195
+
196
+ # Load three pipeline for three types of query: naive/single-hop/multi-hop
197
+ from flashrag.pipeline import IRCOTPipeline
198
+
199
+ if norag_template is None:
200
+ norag_templete = PromptTemplate(
201
+ config=config,
202
+ system_prompt="Answer the question based on your own knowledge. Only give me the answer and do not output any other words.",
203
+ user_prompt="Question: {question}",
204
+ )
205
+ self.norag_pipeline = SequentialPipeline(
206
+ config,
207
+ prompt_template=norag_templete,
208
+ retriever=retriever,
209
+ generator=generator,
210
+ )
211
+
212
+ self.single_hop_pipeline = SequentialPipeline(
213
+ config,
214
+ prompt_template=single_hop_prompt_template,
215
+ retriever=retriever,
216
+ generator=generator,
217
+ )
218
+
219
+ self.multi_hop_pipeline = IRCOTPipeline(
220
+ config,
221
+ prompt_template=multi_hop_prompt_template,
222
+ retriever=retriever,
223
+ generator=generator,
224
+ )
225
+
226
+ def run(self, dataset, do_eval=True, pred_process_fun=None):
227
+ # judge_result: choice result representing which pipeline to use(e.g. A, B, C)
228
+ judge_result = self.judger.judge(dataset)
229
+ dataset.update_output("judge_result", judge_result)
230
+
231
+ # split dataset based on judge_result
232
+ dataset_split = split_dataset(dataset, judge_result)
233
+ for symbol, symbol_dataset in dataset_split.items():
234
+ if symbol == "A":
235
+ symbol_dataset = self.norag_pipeline.naive_run(symbol_dataset, do_eval=False)
236
+ elif symbol == "B":
237
+ symbol_dataset = self.single_hop_pipeline.run(symbol_dataset, do_eval=False)
238
+ elif symbol == "C":
239
+ symbol_dataset = self.multi_hop_pipeline.run(symbol_dataset, do_eval=False)
240
+ else:
241
+ assert False, "Unknown symbol!"
242
+
243
+ # merge datasets into original format
244
+ dataset = merge_dataset(dataset_split, judge_result)
245
+
246
+ dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
247
+
248
+ return dataset