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.
- flashrag/__init__.py +0 -0
- flashrag/config/__init__.py +2 -0
- flashrag/config/config.py +219 -0
- flashrag/dataset/__init__.py +2 -0
- flashrag/dataset/dataset.py +160 -0
- flashrag/dataset/utils.py +60 -0
- flashrag/evaluator/__init__.py +2 -0
- flashrag/evaluator/_bleu.py +209 -0
- flashrag/evaluator/evaluator.py +82 -0
- flashrag/evaluator/metrics.py +508 -0
- flashrag/evaluator/utils.py +19 -0
- flashrag/generator/__init__.py +2 -0
- flashrag/generator/fid.py +247 -0
- flashrag/generator/generator.py +623 -0
- flashrag/generator/openai_generator.py +96 -0
- flashrag/generator/stop_word_criteria.py +105 -0
- flashrag/judger/__init__.py +1 -0
- flashrag/judger/judger.py +184 -0
- flashrag/pipeline/__init__.py +3 -0
- flashrag/pipeline/active_pipeline.py +980 -0
- flashrag/pipeline/branching_pipeline.py +250 -0
- flashrag/pipeline/pipeline.py +248 -0
- flashrag/pipeline/replug_utils.py +249 -0
- flashrag/prompt/__init__.py +1 -0
- flashrag/prompt/base_prompt.py +165 -0
- flashrag/prompt/selfask_examplars.py +108 -0
- flashrag/prompt/trace_examplars.py +4015 -0
- flashrag/refiner/__init__.py +2 -0
- flashrag/refiner/kg_refiner.py +612 -0
- flashrag/refiner/llmlingua_compressor.py +2360 -0
- flashrag/refiner/refiner.py +252 -0
- flashrag/refiner/selective_context_compressor.py +294 -0
- flashrag/retriever/__init__.py +3 -0
- flashrag/retriever/__main__.py +4 -0
- flashrag/retriever/encoder.py +120 -0
- flashrag/retriever/index_builder.py +334 -0
- flashrag/retriever/reranker.py +145 -0
- flashrag/retriever/retriever.py +346 -0
- flashrag/retriever/utils.py +49 -0
- flashrag/utils/__init__.py +2 -0
- flashrag/utils/constants.py +51 -0
- flashrag/utils/pred_parse.py +25 -0
- flashrag/utils/utils.py +135 -0
- flashrag_dev-0.1.1.dist-info/LICENSE +21 -0
- flashrag_dev-0.1.1.dist-info/METADATA +616 -0
- flashrag_dev-0.1.1.dist-info/RECORD +48 -0
- flashrag_dev-0.1.1.dist-info/WHEEL +5 -0
- 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
|