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,980 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from tqdm import tqdm
|
|
3
|
+
import numpy as np
|
|
4
|
+
from transformers import AutoTokenizer, PreTrainedTokenizer, PreTrainedTokenizerFast
|
|
5
|
+
from flashrag.utils import get_retriever, get_generator, selfask_pred_parse, ircot_pred_parse
|
|
6
|
+
from flashrag.pipeline import BasicPipeline
|
|
7
|
+
from flashrag.dataset import get_batch_dataset, merge_batch_dataset
|
|
8
|
+
from flashrag.prompt import PromptTemplate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class IterativePipeline(BasicPipeline):
|
|
12
|
+
def __init__(self, config, prompt_template=None, iter_num=3):
|
|
13
|
+
super().__init__(config, prompt_template)
|
|
14
|
+
self.iter_num = iter_num
|
|
15
|
+
self.generator = get_generator(config)
|
|
16
|
+
self.retriever = get_retriever(config)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run(self, dataset, do_eval=True, pred_process_fun=None):
|
|
20
|
+
questions = dataset.question
|
|
21
|
+
|
|
22
|
+
# run in batch
|
|
23
|
+
past_generation_result = [] # list of N items
|
|
24
|
+
for iter_idx in range(self.iter_num):
|
|
25
|
+
if iter_idx == 0:
|
|
26
|
+
input_query = questions
|
|
27
|
+
else:
|
|
28
|
+
assert len(questions) == len(past_generation_result)
|
|
29
|
+
input_query = [f"{q} {r}" for q, r in zip(questions, past_generation_result)]
|
|
30
|
+
|
|
31
|
+
# generation-augmented retrieval
|
|
32
|
+
retrieval_results = self.retriever.batch_search(input_query)
|
|
33
|
+
dataset.update_output(f"retrieval_result_iter_{iter_idx}", retrieval_results)
|
|
34
|
+
|
|
35
|
+
# retrieval-augmented generation
|
|
36
|
+
# input_prompts = self.build_prompt(questions, retrieval_results)
|
|
37
|
+
input_prompts = [
|
|
38
|
+
self.prompt_template.get_string(question=q, retrieval_result=r)
|
|
39
|
+
for q, r in zip(questions, retrieval_results)
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
dataset.update_output(f"prompt_iter_{iter_idx}", input_prompts)
|
|
43
|
+
past_generation_result = self.generator.generate(input_prompts)
|
|
44
|
+
dataset.update_output(f"pred_iter_{iter_idx}", past_generation_result)
|
|
45
|
+
|
|
46
|
+
# use last retrieval result for evaluation
|
|
47
|
+
dataset.update_output("retrieval_result", retrieval_results)
|
|
48
|
+
|
|
49
|
+
dataset.update_output("pred", past_generation_result)
|
|
50
|
+
dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
|
|
51
|
+
|
|
52
|
+
return dataset
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SelfRAGPipeline(BasicPipeline):
|
|
56
|
+
# Source: https://github.com/AkariAsai/self-rag
|
|
57
|
+
# The code is released under MIT license
|
|
58
|
+
|
|
59
|
+
rel_tokens_names = ["[Irrelevant]", "[Relevant]"]
|
|
60
|
+
retrieval_tokens_names = ["[No Retrieval]", "[Retrieval]", "[Continue to Use Evidence]"]
|
|
61
|
+
utility_tokens_names = ["[Utility:1]", "[Utility:2]", "[Utility:3]", "[Utility:4]", "[Utility:5]"]
|
|
62
|
+
ground_tokens_names = ["[Fully supported]", "[Partially supported]", "[No support / Contradictory]"]
|
|
63
|
+
other_special_tokens = ["<s>", "</s>", "[PAD]", "<unk>", "<paragraph>", "</paragraph>"]
|
|
64
|
+
control_tokens = [
|
|
65
|
+
"[Fully supported]",
|
|
66
|
+
"[Partially supported]",
|
|
67
|
+
"[No support / Contradictory]",
|
|
68
|
+
"[No Retrieval]",
|
|
69
|
+
"[Retrieval]",
|
|
70
|
+
"[Irrelevant]",
|
|
71
|
+
"[Relevant]",
|
|
72
|
+
"<paragraph>",
|
|
73
|
+
"</paragraph>",
|
|
74
|
+
"[Utility:1]",
|
|
75
|
+
"[Utility:2]",
|
|
76
|
+
"[Utility:3]",
|
|
77
|
+
"[Utility:4]",
|
|
78
|
+
"[Utility:5]",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
task_inst = {
|
|
82
|
+
"wow": "Given a chat history separated by new lines, generates an informative, knowledgeable and engaging response. ",
|
|
83
|
+
"fever": "Is the following statement correct or not? Say true if it's correct; otherwise say false.",
|
|
84
|
+
"eli5": "Provide a paragraph-length response using simple words to answer the following question.",
|
|
85
|
+
"obqa": "Given four answer candidates, A, B, C and D, choose the best answer choice.",
|
|
86
|
+
"arc_easy": "Given four answer candidates, A, B, C and D, choose the best answer choice.",
|
|
87
|
+
"arc_c": "Given four answer candidates, A, B, C and D, choose the best answer choice.",
|
|
88
|
+
"trex": "Given the input format 'Subject Entity [SEP] Relationship Type,' predict the target entity.",
|
|
89
|
+
"asqa": "Answer the following question. The question may be ambiguous and have multiple correct answers, and in that case, you have to provide a long-form answer including all correct answers.",
|
|
90
|
+
"normal_qa": "Answer the following question, give me a short answer.",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
config,
|
|
96
|
+
threhsold=0.2,
|
|
97
|
+
max_depth=2,
|
|
98
|
+
beam_width=2,
|
|
99
|
+
w_rel=1.0,
|
|
100
|
+
w_sup=1.0,
|
|
101
|
+
w_use=1.0,
|
|
102
|
+
use_grounding=True,
|
|
103
|
+
use_utility=True,
|
|
104
|
+
use_seqscore=True,
|
|
105
|
+
ignore_cont=True,
|
|
106
|
+
mode="adaptive_retrieval",
|
|
107
|
+
prompt_template=None,
|
|
108
|
+
):
|
|
109
|
+
|
|
110
|
+
super().__init__(config, prompt_template)
|
|
111
|
+
self.generator = get_generator(config)
|
|
112
|
+
self.retriever = get_retriever(config)
|
|
113
|
+
|
|
114
|
+
assert mode in ["adaptive_retrieval", "always_retrieve", "no_retrieval"]
|
|
115
|
+
|
|
116
|
+
self.task = config["dataset_name"]
|
|
117
|
+
self.task_instruction = self.task_inst.get(self.task, self.task_inst["normal_qa"])
|
|
118
|
+
if self.task_instruction is not None:
|
|
119
|
+
question_inst = self.task_instruction + "\n\n## Input:\n\n{question}"
|
|
120
|
+
else:
|
|
121
|
+
question_inst = "{question}"
|
|
122
|
+
if prompt_template is None:
|
|
123
|
+
self.prompt_template = PromptTemplate(
|
|
124
|
+
config, user_prompt="### Instruction:\n" + question_inst + "\n\n### Response:\n", enable_chat=False
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self.threshold = threhsold
|
|
128
|
+
self.max_depth = max_depth
|
|
129
|
+
self.beam_width = beam_width
|
|
130
|
+
self.w_rel, self.w_sup, self.w_use = w_rel, w_sup, w_use
|
|
131
|
+
self.use_grounding = use_grounding
|
|
132
|
+
self.use_utility = use_utility
|
|
133
|
+
self.use_seqscore = use_seqscore
|
|
134
|
+
self.ignore_cont = ignore_cont
|
|
135
|
+
self.mode = mode
|
|
136
|
+
self.closed = self.task in ["fever", "arc_c"]
|
|
137
|
+
tokenizer = AutoTokenizer.from_pretrained(config["generator_model_path"], padding_side="left")
|
|
138
|
+
self.ret_tokens, self.rel_tokens, self.grd_tokens, self.ut_tokens = self.load_special_tokens(
|
|
139
|
+
tokenizer, use_grounding=use_grounding, use_utility=use_utility
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def load_special_tokens(self, tokenizer, use_grounding, use_utility):
|
|
143
|
+
ret_tokens = {token: tokenizer.convert_tokens_to_ids(token) for token in self.retrieval_tokens_names}
|
|
144
|
+
rel_tokens = {}
|
|
145
|
+
for token in ["[Irrelevant]", "[Relevant]"]:
|
|
146
|
+
rel_tokens[token] = tokenizer.convert_tokens_to_ids(token)
|
|
147
|
+
|
|
148
|
+
grd_tokens = None
|
|
149
|
+
if use_grounding is True:
|
|
150
|
+
grd_tokens = {}
|
|
151
|
+
for token in self.ground_tokens_names:
|
|
152
|
+
grd_tokens[token] = tokenizer.convert_tokens_to_ids(token)
|
|
153
|
+
|
|
154
|
+
ut_tokens = None
|
|
155
|
+
if use_utility is True:
|
|
156
|
+
ut_tokens = {}
|
|
157
|
+
for token in self.utility_tokens_names:
|
|
158
|
+
ut_tokens[token] = tokenizer.convert_tokens_to_ids(token)
|
|
159
|
+
|
|
160
|
+
return ret_tokens, rel_tokens, grd_tokens, ut_tokens
|
|
161
|
+
|
|
162
|
+
def judge_retrieve(self, input_prompts):
|
|
163
|
+
"""Calculate whether a retrieve is required based on the output probability of
|
|
164
|
+
the special token in the model"""
|
|
165
|
+
|
|
166
|
+
if self.mode != "always_retrieve":
|
|
167
|
+
# result for total batch
|
|
168
|
+
all_pred_token_ids = []
|
|
169
|
+
all_pred_text = []
|
|
170
|
+
all_pred_log_probs = []
|
|
171
|
+
preds = self.generator.generate(input_prompts, return_raw_output=True, logprobs=32000)
|
|
172
|
+
for single_pred in preds:
|
|
173
|
+
pred_token_ids = single_pred.outputs[0].token_ids
|
|
174
|
+
pred_text = single_pred.outputs[0].text
|
|
175
|
+
pred_log_probs = single_pred.outputs[0].logprobs
|
|
176
|
+
all_pred_token_ids.append(pred_token_ids)
|
|
177
|
+
all_pred_text.append(pred_text)
|
|
178
|
+
all_pred_log_probs.append(pred_log_probs)
|
|
179
|
+
|
|
180
|
+
if self.mode == "always_retrieve":
|
|
181
|
+
retrieval_flags = [True] * len(input_prompts)
|
|
182
|
+
|
|
183
|
+
elif self.mode == "no_retrieval":
|
|
184
|
+
retrieval_flags = [False] * len(input_prompts)
|
|
185
|
+
|
|
186
|
+
else:
|
|
187
|
+
retrieval_flags = []
|
|
188
|
+
for idx, single_pred in enumerate(preds):
|
|
189
|
+
if self.threshold is not None:
|
|
190
|
+
score_dict = {}
|
|
191
|
+
for tok, tok_id in self.ret_tokens.items():
|
|
192
|
+
if tok_id not in all_pred_log_probs[idx][0]:
|
|
193
|
+
score_dict[tok] = -100
|
|
194
|
+
prob = all_pred_log_probs[idx][0][tok_id].logprob
|
|
195
|
+
score_dict[tok] = float(prob)
|
|
196
|
+
do_retrieve = (
|
|
197
|
+
score_dict["[Retrieval]"] / (score_dict["[Retrieval]"] + score_dict["[No Retrieval]"])
|
|
198
|
+
> self.threshold
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
do_retrieve = "[Retrieval]" in all_pred_text[idx]
|
|
202
|
+
|
|
203
|
+
retrieval_flags.append(do_retrieve)
|
|
204
|
+
|
|
205
|
+
return retrieval_flags
|
|
206
|
+
|
|
207
|
+
def critic_preds(self, preds):
|
|
208
|
+
"""Evaluate predictions using different retrieval docs"""
|
|
209
|
+
|
|
210
|
+
relevance_score_dict = {}
|
|
211
|
+
grd_score_dict = {}
|
|
212
|
+
ut_score_dict = {}
|
|
213
|
+
overall_scores = {}
|
|
214
|
+
results = {}
|
|
215
|
+
for p_idx, pred in enumerate(preds):
|
|
216
|
+
pred_token_ids = pred.outputs[0].token_ids
|
|
217
|
+
pred_text = pred.outputs[0].text
|
|
218
|
+
pred_log_probs = pred.outputs[0].logprobs
|
|
219
|
+
seq_score = pred.outputs[0].cumulative_logprob / max(len(pred.outputs[0].token_ids), 1)
|
|
220
|
+
relevance_score_dict.setdefault(p_idx, {})
|
|
221
|
+
grd_score_dict.setdefault(p_idx, {})
|
|
222
|
+
ut_score_dict.setdefault(p_idx, {})
|
|
223
|
+
# Compute reward scores
|
|
224
|
+
for tok, id in self.rel_tokens.items():
|
|
225
|
+
prob = pred_log_probs[0][id].logprob if id in pred_log_probs[0] else -100
|
|
226
|
+
relevance_score_dict[p_idx][tok] = np.exp(float(prob))
|
|
227
|
+
|
|
228
|
+
if self.grd_tokens is not None:
|
|
229
|
+
groundness_token_appear_indices = []
|
|
230
|
+
for tok_idx, tok in enumerate(pred_token_ids):
|
|
231
|
+
if tok in list(self.grd_tokens.values()):
|
|
232
|
+
groundness_token_appear_indices.append(tok_idx)
|
|
233
|
+
break
|
|
234
|
+
if len(groundness_token_appear_indices) > 0:
|
|
235
|
+
idx = groundness_token_appear_indices[0]
|
|
236
|
+
for token, token_id in self.grd_tokens.items():
|
|
237
|
+
prob = pred_log_probs[idx][token_id].logprob if token_id in pred_log_probs[idx] else -100
|
|
238
|
+
grd_score_dict[p_idx][token] = np.exp(float(prob))
|
|
239
|
+
utility_token_appear_indices = []
|
|
240
|
+
if self.ut_tokens is not None:
|
|
241
|
+
for tok_idx, tok in enumerate(pred_token_ids):
|
|
242
|
+
if tok in list(self.ut_tokens.values()):
|
|
243
|
+
utility_token_appear_indices.append(tok_idx)
|
|
244
|
+
if len(utility_token_appear_indices) > 0:
|
|
245
|
+
idx = utility_token_appear_indices[0]
|
|
246
|
+
for token, token_id in self.ut_tokens.items():
|
|
247
|
+
prob = pred_log_probs[idx][token_id].logprob if token_id in pred_log_probs[idx] else -100
|
|
248
|
+
ut_score_dict[p_idx][token] = np.exp(float(prob))
|
|
249
|
+
|
|
250
|
+
relevance_score = relevance_score_dict[p_idx]["[Relevant]"] / (
|
|
251
|
+
np.sum(list(relevance_score_dict[p_idx].values()))
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if len(grd_score_dict[p_idx]) == 3:
|
|
255
|
+
gt_sum = np.sum(list(grd_score_dict[p_idx].values()))
|
|
256
|
+
ground_score = (grd_score_dict[p_idx]["[Fully supported]"] / gt_sum) + 0.5 * (
|
|
257
|
+
grd_score_dict[p_idx]["[Partially supported]"] / gt_sum
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
ground_score = 0.0
|
|
261
|
+
|
|
262
|
+
if len(ut_score_dict[p_idx]) == 5:
|
|
263
|
+
ut_sum = np.sum(list(ut_score_dict[p_idx].values()))
|
|
264
|
+
ut_scores = [-1, -0.5, 0, 0.5, 1]
|
|
265
|
+
utility_score = np.sum(
|
|
266
|
+
[
|
|
267
|
+
ut_scores[i] * (ut_score_dict[p_idx]["[Utility:{}]".format(i + 1)] / ut_sum)
|
|
268
|
+
for i in range(len(ut_scores))
|
|
269
|
+
]
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
utility_score = 0.0
|
|
273
|
+
|
|
274
|
+
if self.use_seqscore is True:
|
|
275
|
+
final_score = (
|
|
276
|
+
np.exp(seq_score)
|
|
277
|
+
+ self.w_rel * relevance_score
|
|
278
|
+
+ self.w_sup * ground_score
|
|
279
|
+
+ self.w_use * utility_score
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
final_score = self.w_rel * relevance_score + self.w_sup * ground_score + self.w_use * utility_score
|
|
283
|
+
|
|
284
|
+
overall_scores[p_idx] = {
|
|
285
|
+
"final_score": final_score,
|
|
286
|
+
"relevance_score": relevance_score,
|
|
287
|
+
"ground_score": ground_score,
|
|
288
|
+
"utility_score": utility_score,
|
|
289
|
+
"relevance_score_dict": relevance_score_dict,
|
|
290
|
+
"grd_score_dict": grd_score_dict,
|
|
291
|
+
"ut_score_dict": utility_score,
|
|
292
|
+
}
|
|
293
|
+
results["retrieval_{}".format(p_idx)] = {"pred": pred_text, "score": final_score}
|
|
294
|
+
|
|
295
|
+
# modify and add do retrieve tokens (only used in long-form generation)
|
|
296
|
+
final_preds = []
|
|
297
|
+
if "[No Retrieval]" in pred_text:
|
|
298
|
+
ret_token_appear_indices = []
|
|
299
|
+
substrings = pred_text.split("[No Retrieval]")
|
|
300
|
+
|
|
301
|
+
for tok_idx, tok in enumerate(pred_token_ids):
|
|
302
|
+
if tok == self.ret_tokens["[No Retrieval]"]:
|
|
303
|
+
ret_token_appear_indices.append(tok_idx)
|
|
304
|
+
|
|
305
|
+
ret_token_score_dict = {}
|
|
306
|
+
retrieval_remap = {}
|
|
307
|
+
for order, idx in enumerate(ret_token_appear_indices):
|
|
308
|
+
ret_token_score_dict.setdefault(order, {})
|
|
309
|
+
for tok, tok_id in self.ret_tokens.items():
|
|
310
|
+
prob = pred_log_probs[idx][tok_id].logprob if tok_id in pred_log_probs[idx] else -100
|
|
311
|
+
ret_token_score_dict[order][tok] = np.exp(prob)
|
|
312
|
+
if ret_token_score_dict[order]["[Retrieval]"] + ret_token_score_dict[order]["[No Retrieval]"] != 0.0:
|
|
313
|
+
do_retrieve = (
|
|
314
|
+
ret_token_score_dict[order]["[Retrieval]"]
|
|
315
|
+
+ ret_token_score_dict[order]["[Continue to Use Evidence]"]
|
|
316
|
+
) / (
|
|
317
|
+
ret_token_score_dict[order]["[Retrieval]"] + ret_token_score_dict[order]["[No Retrieval]"]
|
|
318
|
+
) > self.threshold
|
|
319
|
+
else:
|
|
320
|
+
do_retrieve = 0.0
|
|
321
|
+
if do_retrieve > self.threshold:
|
|
322
|
+
retrieval_remap[order] = True
|
|
323
|
+
else:
|
|
324
|
+
retrieval_remap[order] = False
|
|
325
|
+
processed_pred = ""
|
|
326
|
+
for substr_i, substring in enumerate(substrings):
|
|
327
|
+
if substr_i in retrieval_remap and retrieval_remap[substr_i] is True:
|
|
328
|
+
processed_pred += substring + "[Retrieval]"
|
|
329
|
+
else:
|
|
330
|
+
processed_pred += substring + "[No Retrieval]"
|
|
331
|
+
pred_text = processed_pred
|
|
332
|
+
final_preds.append(pred_text)
|
|
333
|
+
else:
|
|
334
|
+
final_preds.append(pred_text)
|
|
335
|
+
|
|
336
|
+
scores = [overall_scores[p_idx]["final_score"] for p_idx in overall_scores]
|
|
337
|
+
|
|
338
|
+
return results, final_preds, scores, overall_scores
|
|
339
|
+
|
|
340
|
+
def postprocess_prediction(self, pred):
|
|
341
|
+
def fix_spacing(input_text):
|
|
342
|
+
# Add a space after periods that lack whitespace
|
|
343
|
+
output_text = re.sub(r"(?<=\w)([.!?])(?=\w)", r"\1 ", input_text)
|
|
344
|
+
return output_text
|
|
345
|
+
|
|
346
|
+
for token in self.control_tokens:
|
|
347
|
+
pred = pred.replace(token, "")
|
|
348
|
+
if "</s>" in pred:
|
|
349
|
+
pred = pred.replace("</s>", "")
|
|
350
|
+
if "\n" in pred:
|
|
351
|
+
pred = pred.replace("\n", "")
|
|
352
|
+
if "<|endoftext|>" in pred:
|
|
353
|
+
pred = pred.replace("<|endoftext|>", "")
|
|
354
|
+
|
|
355
|
+
pred = pred.strip()
|
|
356
|
+
if type(pred) is str and pred[0] == "#" or pred[0] == ":":
|
|
357
|
+
pred = pred[1:]
|
|
358
|
+
if len(pred) == 0:
|
|
359
|
+
|
|
360
|
+
return ""
|
|
361
|
+
|
|
362
|
+
return fix_spacing(pred)
|
|
363
|
+
|
|
364
|
+
def select_best_prediction(self, results):
|
|
365
|
+
answer2score = {}
|
|
366
|
+
if self.closed is True:
|
|
367
|
+
for key, result in results.items():
|
|
368
|
+
answer = self.postprocess_prediction(result["pred"])
|
|
369
|
+
score = result["score"]
|
|
370
|
+
answer2score.setdefault(answer, 0)
|
|
371
|
+
answer2score[answer] += score
|
|
372
|
+
sorted_answers = sorted(answer2score.items(), key=lambda x: x[1], reverse=True)
|
|
373
|
+
best_pred = sorted_answers[0][0]
|
|
374
|
+
else:
|
|
375
|
+
path2score = {key: item["score"] for key, item in results.items() if key != "no_retrieval"}
|
|
376
|
+
best_path = sorted(path2score.items(), key=lambda x: x[1], reverse=True)[0][0]
|
|
377
|
+
best_pred = results[best_path]["pred"]
|
|
378
|
+
|
|
379
|
+
return best_pred
|
|
380
|
+
|
|
381
|
+
def run_single_beam(self, prompt, item_retrieval_result=None):
|
|
382
|
+
curr_depth = 1
|
|
383
|
+
terminated = False
|
|
384
|
+
node_id = 0
|
|
385
|
+
prediction_tree = {}
|
|
386
|
+
levels = {}
|
|
387
|
+
prediction_tree[node_id] = {
|
|
388
|
+
"prompt": prompt,
|
|
389
|
+
"pred": "[Retrieval]",
|
|
390
|
+
"processed_pred": "",
|
|
391
|
+
"score": None,
|
|
392
|
+
"ctx": None,
|
|
393
|
+
"parent": None,
|
|
394
|
+
}
|
|
395
|
+
levels[0] = [0]
|
|
396
|
+
while curr_depth < self.max_depth:
|
|
397
|
+
levels[curr_depth] = []
|
|
398
|
+
if curr_depth - 1 in levels and terminated is False:
|
|
399
|
+
for node in levels[curr_depth - 1]:
|
|
400
|
+
pred = prediction_tree[node]["pred"]
|
|
401
|
+
if pred == "</s>":
|
|
402
|
+
terminated = True
|
|
403
|
+
continue
|
|
404
|
+
prompt = prediction_tree[node]["prompt"]
|
|
405
|
+
prev_generation = prediction_tree[node]["processed_pred"]
|
|
406
|
+
score = prediction_tree[node]["score"]
|
|
407
|
+
if "[Retrieval]" in pred:
|
|
408
|
+
retrieval_results = {}
|
|
409
|
+
|
|
410
|
+
if item_retrieval_result is not None:
|
|
411
|
+
aug_prompts = [
|
|
412
|
+
prompt
|
|
413
|
+
+ prev_generation
|
|
414
|
+
+ "[Retrieval]"
|
|
415
|
+
+ "<paragraph>{}</paragraph>".format(para["contents"])
|
|
416
|
+
for para in item_retrieval_result
|
|
417
|
+
]
|
|
418
|
+
else:
|
|
419
|
+
aug_prompts = [prompt + prev_generation]
|
|
420
|
+
|
|
421
|
+
item_pred = self.generator.generate(aug_prompts, return_raw_output=True)
|
|
422
|
+
_, preds, scores, overall_score_dict = self.critic_preds(item_pred)
|
|
423
|
+
|
|
424
|
+
for i, (pred, p_score) in enumerate(zip(preds, scores)):
|
|
425
|
+
retrieval_results[i] = {"pred": pred, "score": p_score}
|
|
426
|
+
|
|
427
|
+
for i, result in retrieval_results.items():
|
|
428
|
+
node_id += 1
|
|
429
|
+
node_score = result["score"] * score if score is not None else result["score"]
|
|
430
|
+
pred = result["pred"]
|
|
431
|
+
prediction_tree[node_id] = {
|
|
432
|
+
"prompt": prompt + prev_generation,
|
|
433
|
+
"pred": pred,
|
|
434
|
+
"score": node_score,
|
|
435
|
+
"ctx": item_retrieval_result[i],
|
|
436
|
+
"parent": node,
|
|
437
|
+
"overall_score_dict": overall_score_dict,
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if "[Retrieval]" in pred:
|
|
441
|
+
gen_result_index = pred.index("[Retrieval]")
|
|
442
|
+
prev_generation = pred[:gen_result_index]
|
|
443
|
+
else:
|
|
444
|
+
prev_generation = pred
|
|
445
|
+
prediction_tree[node_id]["processed_pred"] = prev_generation
|
|
446
|
+
levels[curr_depth].append(node_id)
|
|
447
|
+
|
|
448
|
+
current_rank = levels[curr_depth]
|
|
449
|
+
node2score = {node_id: prediction_tree[node_id]["score"] for node_id in current_rank}
|
|
450
|
+
top_nodes = sorted(node2score.items(), key=lambda x: x[1], reverse=True)[: self.beam_width]
|
|
451
|
+
levels[curr_depth] = [node[0] for node in top_nodes]
|
|
452
|
+
curr_depth += 1
|
|
453
|
+
else:
|
|
454
|
+
break
|
|
455
|
+
|
|
456
|
+
final_prediction = ""
|
|
457
|
+
parent = 0
|
|
458
|
+
best_selections = {}
|
|
459
|
+
|
|
460
|
+
# Traverse from the bottom
|
|
461
|
+
levels = {k: v for k, v in levels.items() if len(v) > 0 and k != 0}
|
|
462
|
+
for path_i, node in enumerate(levels[len(levels)]):
|
|
463
|
+
if node == 0:
|
|
464
|
+
break
|
|
465
|
+
best_selections[path_i] = [node]
|
|
466
|
+
current_node = node
|
|
467
|
+
current_level = curr_depth
|
|
468
|
+
if current_node is None:
|
|
469
|
+
continue
|
|
470
|
+
while current_level > 0 and current_node is not None:
|
|
471
|
+
parent = prediction_tree[current_node]["parent"]
|
|
472
|
+
best_selections[path_i] = [parent] + best_selections[path_i]
|
|
473
|
+
current_node = parent
|
|
474
|
+
current_level += 1
|
|
475
|
+
|
|
476
|
+
final_prediction = {}
|
|
477
|
+
splitted_sentences = {}
|
|
478
|
+
original_splitted_sentences = {}
|
|
479
|
+
ctxs = {}
|
|
480
|
+
for path_i, nodes in best_selections.items():
|
|
481
|
+
final_prediction[path_i] = " ".join(
|
|
482
|
+
[
|
|
483
|
+
prediction_tree[node]["processed_pred"]
|
|
484
|
+
for node in nodes
|
|
485
|
+
if node is not None
|
|
486
|
+
and (
|
|
487
|
+
self.ignore_cont is False
|
|
488
|
+
or (
|
|
489
|
+
self.ignore_cont is True
|
|
490
|
+
and "[No support / Contradictory]" not in prediction_tree[node]["processed_pred"]
|
|
491
|
+
)
|
|
492
|
+
)
|
|
493
|
+
]
|
|
494
|
+
)
|
|
495
|
+
splitted_sentences[path_i] = [
|
|
496
|
+
prediction_tree[node]["processed_pred"]
|
|
497
|
+
for node in nodes
|
|
498
|
+
if node is not None
|
|
499
|
+
and (
|
|
500
|
+
self.ignore_cont is False
|
|
501
|
+
or (
|
|
502
|
+
self.ignore_cont is True
|
|
503
|
+
and "[No support / Contradictory]" not in prediction_tree[node]["processed_pred"]
|
|
504
|
+
)
|
|
505
|
+
)
|
|
506
|
+
]
|
|
507
|
+
original_splitted_sentences[path_i] = [
|
|
508
|
+
prediction_tree[node]["pred"]
|
|
509
|
+
for node in nodes
|
|
510
|
+
if node is not None
|
|
511
|
+
and (
|
|
512
|
+
self.ignore_cont is False
|
|
513
|
+
or (
|
|
514
|
+
self.ignore_cont is True
|
|
515
|
+
and "[No support / Contradictory]" not in prediction_tree[node]["processed_pred"]
|
|
516
|
+
)
|
|
517
|
+
)
|
|
518
|
+
]
|
|
519
|
+
ctxs[path_i] = [
|
|
520
|
+
prediction_tree[node]["ctx"]
|
|
521
|
+
for node in nodes
|
|
522
|
+
if node is not None
|
|
523
|
+
and (
|
|
524
|
+
self.ignore_cont is False
|
|
525
|
+
or (
|
|
526
|
+
self.ignore_cont is True
|
|
527
|
+
and "[No support / Contradictory]" not in prediction_tree[node]["processed_pred"]
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
]
|
|
531
|
+
|
|
532
|
+
result = {
|
|
533
|
+
"final_prediction": final_prediction,
|
|
534
|
+
"splitted_sentences": splitted_sentences,
|
|
535
|
+
"original_splitted_sentences": original_splitted_sentences,
|
|
536
|
+
"best_selections": best_selections,
|
|
537
|
+
"ctxs": ctxs,
|
|
538
|
+
"prediction_tree": prediction_tree,
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return final_prediction[0], result
|
|
542
|
+
|
|
543
|
+
def postprocess_long_form(self, pred, intermediate):
|
|
544
|
+
final_output = ""
|
|
545
|
+
docs = []
|
|
546
|
+
prev_gen = []
|
|
547
|
+
if "splitted_sentences" not in intermediate:
|
|
548
|
+
final_output = self.postprocess_prediction(pred)
|
|
549
|
+
else:
|
|
550
|
+
if len(self.postprocess_prediction(pred)) == 0:
|
|
551
|
+
intermediate["splitted_sentences"][0], intermediate["ctxs"][0] = (
|
|
552
|
+
intermediate["splitted_sentences"][1],
|
|
553
|
+
intermediate["ctxs"][1],
|
|
554
|
+
)
|
|
555
|
+
for idx, (sent, doc) in enumerate(zip(intermediate["splitted_sentences"][0], intermediate["ctxs"][0])):
|
|
556
|
+
if len(sent) == 0:
|
|
557
|
+
continue
|
|
558
|
+
postprocessed_result = self.postprocess_prediction(sent)
|
|
559
|
+
if postprocessed_result in prev_gen:
|
|
560
|
+
continue
|
|
561
|
+
else:
|
|
562
|
+
prev_gen.append(postprocessed_result)
|
|
563
|
+
final_output += postprocessed_result[:-1] + " [{}]".format(idx) + ". "
|
|
564
|
+
docs.append(doc)
|
|
565
|
+
if len(final_output) == 0:
|
|
566
|
+
final_output = final_output
|
|
567
|
+
if len(final_output) > 0 and final_output[-1] == " ":
|
|
568
|
+
final_output = final_output[:-1]
|
|
569
|
+
final_output = final_output.strip()
|
|
570
|
+
final_output = final_output.replace(".[Continue to Use Evidence]", " [1]. ")
|
|
571
|
+
final_output = final_output.replace(". [1] ", " [1]. ")
|
|
572
|
+
|
|
573
|
+
return final_output
|
|
574
|
+
|
|
575
|
+
def run_batch_pred_long_form(self, dataset):
|
|
576
|
+
questions = dataset.question
|
|
577
|
+
retrieval_results = self.retriever.batch_search(questions)
|
|
578
|
+
dataset.update_output("retrieval_result", retrieval_results)
|
|
579
|
+
|
|
580
|
+
# input_prompts = self.build_prompt(questions)
|
|
581
|
+
input_prompts = [self.prompt_template.get_string(question=q) for q in questions]
|
|
582
|
+
|
|
583
|
+
# determine whether to retrieve
|
|
584
|
+
retrieval_flags = self.judge_retrieve(input_prompts)
|
|
585
|
+
dataset.update_output("retrieval_flag", retrieval_flags)
|
|
586
|
+
|
|
587
|
+
# for long form task, only support single item run
|
|
588
|
+
for item, prompt, retrieval_flag in zip(dataset, input_prompts, retrieval_flags):
|
|
589
|
+
if retrieval_flag:
|
|
590
|
+
pred, intermediate_result = self.run_single_beam(prompt, item_retrieval_result=item.retrieval_result)
|
|
591
|
+
item.update_output("intermediate_result", intermediate_result)
|
|
592
|
+
|
|
593
|
+
if self.task == "factscore":
|
|
594
|
+
pred = self.postprocess_prediction(pred)
|
|
595
|
+
else:
|
|
596
|
+
assert self.task in ["asqa", "eli5"]
|
|
597
|
+
pred = self.postprocess_long_form(pred, intermediate_result)
|
|
598
|
+
else:
|
|
599
|
+
prompt += "[No Retrieval]"
|
|
600
|
+
pred = self.generator.generate(prompt)[0]
|
|
601
|
+
|
|
602
|
+
item.update_output("pred", pred)
|
|
603
|
+
|
|
604
|
+
return dataset
|
|
605
|
+
|
|
606
|
+
def run(self, dataset, do_eval=True, pred_process_fun=None, batch_size=256, long_form=False):
|
|
607
|
+
all_dataset_list = []
|
|
608
|
+
run_func = self.run_batch_pred_long_form if long_form else self.run_batch_pred
|
|
609
|
+
# to avoid oom
|
|
610
|
+
for batch_dataset in tqdm(get_batch_dataset(dataset, batch_size=batch_size), desc="Batch dataset: "):
|
|
611
|
+
batch_dataset = run_func(batch_dataset)
|
|
612
|
+
all_dataset_list.append(batch_dataset)
|
|
613
|
+
dataset = merge_batch_dataset(all_dataset_list)
|
|
614
|
+
|
|
615
|
+
dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
|
|
616
|
+
return dataset
|
|
617
|
+
|
|
618
|
+
def run_batch_pred(self, dataset):
|
|
619
|
+
questions = dataset.question
|
|
620
|
+
retrieval_results = self.retriever.batch_search(questions)
|
|
621
|
+
dataset.update_output("retrieval_result", retrieval_results)
|
|
622
|
+
|
|
623
|
+
# input_prompts = self.build_prompt(questions)
|
|
624
|
+
input_prompts = [self.prompt_template.get_string(question=q) for q in questions]
|
|
625
|
+
|
|
626
|
+
# determine whether to retrieve
|
|
627
|
+
retrieval_flags = self.judge_retrieve(input_prompts)
|
|
628
|
+
dataset.update_output("retrieval_flag", retrieval_flags)
|
|
629
|
+
|
|
630
|
+
# process input item based on whether to retrieve
|
|
631
|
+
all_input_list = []
|
|
632
|
+
for idx, (prompt, item) in enumerate(zip(input_prompts, dataset)):
|
|
633
|
+
retrieval_flag = retrieval_flags[idx]
|
|
634
|
+
|
|
635
|
+
if retrieval_flag:
|
|
636
|
+
retrieval_result = retrieval_results[idx]
|
|
637
|
+
# for each doc in retrieval result, there is a prompt as input
|
|
638
|
+
prompt_list = [
|
|
639
|
+
prompt + "[Retrieval]<paragraph>{}</paragraph>".format(para["contents"])
|
|
640
|
+
for para in retrieval_result
|
|
641
|
+
]
|
|
642
|
+
else:
|
|
643
|
+
prompt += "[No Retrieval]"
|
|
644
|
+
prompt_list = [prompt]
|
|
645
|
+
|
|
646
|
+
item.update_output("prompt", prompt_list)
|
|
647
|
+
all_input_list += prompt_list
|
|
648
|
+
|
|
649
|
+
batch_pred = self.generator.generate(all_input_list, return_raw_output=True, logprobs=32016)
|
|
650
|
+
|
|
651
|
+
# parse output based on retrieval flag
|
|
652
|
+
pred_idx = 0
|
|
653
|
+
pred_answer_list = []
|
|
654
|
+
for idx, (retrieval_flag, item) in enumerate(zip(retrieval_flags, dataset)):
|
|
655
|
+
if retrieval_flag:
|
|
656
|
+
# for item that need retrieval, there may have more than one prediction
|
|
657
|
+
item_pred = batch_pred[pred_idx : pred_idx + len(retrieval_results[idx])]
|
|
658
|
+
pred_idx += len(retrieval_results[idx])
|
|
659
|
+
critic_result, _, _, _ = self.critic_preds(item_pred)
|
|
660
|
+
item.update_output("critic_result", critic_result)
|
|
661
|
+
|
|
662
|
+
# select best prediction
|
|
663
|
+
pred = self.select_best_prediction(critic_result)
|
|
664
|
+
|
|
665
|
+
else:
|
|
666
|
+
item_pred = batch_pred[pred_idx : pred_idx + 1][0]
|
|
667
|
+
pred_idx += 1
|
|
668
|
+
pred = item_pred.outputs[0].text
|
|
669
|
+
|
|
670
|
+
pred = self.postprocess_prediction(pred)
|
|
671
|
+
pred_answer_list.append(pred)
|
|
672
|
+
|
|
673
|
+
dataset.update_output("pred", pred_answer_list)
|
|
674
|
+
|
|
675
|
+
return dataset
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
class FLAREPipeline(BasicPipeline):
|
|
679
|
+
def __init__(
|
|
680
|
+
self,
|
|
681
|
+
config,
|
|
682
|
+
threshold=0.2,
|
|
683
|
+
look_ahead_steps=64,
|
|
684
|
+
max_generation_length=256,
|
|
685
|
+
max_iter_num=5,
|
|
686
|
+
prompt_template=None,
|
|
687
|
+
):
|
|
688
|
+
super().__init__(config, prompt_template)
|
|
689
|
+
|
|
690
|
+
self.generator = get_generator(config)
|
|
691
|
+
self.retriever = get_retriever(config)
|
|
692
|
+
|
|
693
|
+
self.threshold = threshold
|
|
694
|
+
self.max_generation_length = max_generation_length
|
|
695
|
+
self.max_iter_num = max_iter_num
|
|
696
|
+
self.look_ahead_steps = look_ahead_steps
|
|
697
|
+
self.stop_sym = list("!@#$%^&*()\n\n)(*&^%$#@!")
|
|
698
|
+
|
|
699
|
+
def get_next_sentence(self, output, scores):
|
|
700
|
+
tokenizer = self.generator.tokenizer
|
|
701
|
+
text_sentences = re.split(r"(?<=[^A-Z].[.?]) +", output)
|
|
702
|
+
if isinstance(tokenizer, (PreTrainedTokenizer, PreTrainedTokenizerFast)):
|
|
703
|
+
token_id_sentences = [tokenizer.encode(s, add_special_tokens=False) for s in text_sentences]
|
|
704
|
+
else:
|
|
705
|
+
token_id_sentences = [tokenizer.encode(s, allowed_special="all") for s in text_sentences]
|
|
706
|
+
|
|
707
|
+
output_ids = tokenizer.encode(output, add_special_tokens=False)
|
|
708
|
+
|
|
709
|
+
# assert sum([len(s) for s in token_id_sentences]) == len(
|
|
710
|
+
# output_ids), "token id sentences length not equal to output ids length"
|
|
711
|
+
|
|
712
|
+
first_sent_ids = token_id_sentences[0]
|
|
713
|
+
first_sent_score = scores[: len(first_sent_ids)]
|
|
714
|
+
|
|
715
|
+
return text_sentences[0], first_sent_score
|
|
716
|
+
|
|
717
|
+
def judge_sent_confidence(self, sent, sent_score):
|
|
718
|
+
judge_result = all([score > self.threshold for score in sent_score])
|
|
719
|
+
new_query = None
|
|
720
|
+
if not judge_result:
|
|
721
|
+
tokenizer = self.generator.tokenizer
|
|
722
|
+
if isinstance(tokenizer, (PreTrainedTokenizer, PreTrainedTokenizerFast)):
|
|
723
|
+
sent_ids = tokenizer.encode(sent, add_special_tokens=False)
|
|
724
|
+
else:
|
|
725
|
+
sent_ids = tokenizer.encode(sent, allowed_special="all")
|
|
726
|
+
# assert len(sent_ids) == len(sent_score)
|
|
727
|
+
new_query_ids = [i for i, score in zip(sent_ids, sent_score) if score > self.threshold]
|
|
728
|
+
new_query = tokenizer.decode(new_query_ids)
|
|
729
|
+
if len(new_query) == 0:
|
|
730
|
+
judge_result = True
|
|
731
|
+
return judge_result, new_query
|
|
732
|
+
|
|
733
|
+
def run_item(self, item):
|
|
734
|
+
question = item.question
|
|
735
|
+
gen_length = 0
|
|
736
|
+
iter_round = 0
|
|
737
|
+
final_gen_result = ""
|
|
738
|
+
while gen_length < self.max_generation_length and iter_round < self.max_iter_num:
|
|
739
|
+
input_prompt = self.prompt_template.get_string(question=question, previous_gen=final_gen_result)
|
|
740
|
+
|
|
741
|
+
# input_prompt = self.build_prompt(
|
|
742
|
+
# question_list=[question], use_reference=False, previous_gen=final_gen_result)[0]
|
|
743
|
+
# scores: token logits of the whole generation seq
|
|
744
|
+
round_gen_output, scores = self.generator.generate(
|
|
745
|
+
input_prompt, return_scores=True, stop=self.stop_sym, max_new_tokens=self.look_ahead_steps
|
|
746
|
+
)
|
|
747
|
+
round_gen_output, scores = round_gen_output[0], scores[0]
|
|
748
|
+
# next_sent_scores: token logits of the first sent in generation seq
|
|
749
|
+
next_sent, next_sent_score = self.get_next_sentence(round_gen_output, scores)
|
|
750
|
+
# judge next sentence
|
|
751
|
+
judge_result, query = self.judge_sent_confidence(next_sent, next_sent_score)
|
|
752
|
+
item.update_output(f"judge_result_iter{iter_round}", judge_result)
|
|
753
|
+
|
|
754
|
+
if not judge_result:
|
|
755
|
+
# do retrieval-augmented generation
|
|
756
|
+
retrieval_result = self.retriever.search(query)
|
|
757
|
+
item.update_output("retrieval_result", retrieval_result)
|
|
758
|
+
input_prompt = self.prompt_template.get_string(
|
|
759
|
+
question=question, retrieval_result=retrieval_result, previous_gen=final_gen_result
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# input_prompt = self.build_prompt(
|
|
763
|
+
# question_list = [question],
|
|
764
|
+
# retrieval_results = [retrieval_result],
|
|
765
|
+
# previous_gen = final_gen_result)[0]
|
|
766
|
+
output, scores = self.generator.generate(
|
|
767
|
+
input_prompt, return_scores=True, stop=self.stop_sym, max_new_tokens=self.look_ahead_steps
|
|
768
|
+
)
|
|
769
|
+
output, scores = output[0], scores[0]
|
|
770
|
+
next_sent, _ = self.get_next_sentence(output, scores)
|
|
771
|
+
item.update_output(f"gen_iter_{iter_round}", next_sent)
|
|
772
|
+
item.update_output("retrieval_result", retrieval_result)
|
|
773
|
+
|
|
774
|
+
final_gen_result += next_sent
|
|
775
|
+
gen_length += len(next_sent_score)
|
|
776
|
+
iter_round += 1
|
|
777
|
+
|
|
778
|
+
item.update_output("pred", final_gen_result)
|
|
779
|
+
|
|
780
|
+
def run(self, dataset, do_eval=True, pred_process_fun=None):
|
|
781
|
+
for item in tqdm(dataset, desc="Inference: "):
|
|
782
|
+
self.run_item(item)
|
|
783
|
+
|
|
784
|
+
dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
|
|
785
|
+
return dataset
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
class SelfAskPipeline(BasicPipeline):
|
|
789
|
+
FOLLOW_UP_PATTERN = r"Follow up:.*\n"
|
|
790
|
+
|
|
791
|
+
def __init__(self, config, prompt_template=None, max_iter=5, single_hop=True):
|
|
792
|
+
super().__init__(config, prompt_template)
|
|
793
|
+
from flashrag.prompt.selfask_examplars import SELF_ASK_PROMPT_SINGLE_HOP, SELF_ASK_PROMPT_MULTI_HOP
|
|
794
|
+
|
|
795
|
+
self.generator = get_generator(config)
|
|
796
|
+
self.retriever = get_retriever(config)
|
|
797
|
+
|
|
798
|
+
self.single_hop = single_hop
|
|
799
|
+
self.max_iter = max_iter
|
|
800
|
+
self.P_INS = SELF_ASK_PROMPT_SINGLE_HOP if self.single_hop else SELF_ASK_PROMPT_MULTI_HOP
|
|
801
|
+
|
|
802
|
+
def format_reference(self, retrieval_result):
|
|
803
|
+
format_reference = ""
|
|
804
|
+
for idx, doc_item in enumerate(retrieval_result):
|
|
805
|
+
content = doc_item["contents"]
|
|
806
|
+
title = content.split("\n")[0]
|
|
807
|
+
text = "\n".join(content.split("\n")[1:])
|
|
808
|
+
format_reference += f"Context{idx+1}: {text}\n"
|
|
809
|
+
|
|
810
|
+
return format_reference
|
|
811
|
+
|
|
812
|
+
def _remove_duplicate_doc(self, docs):
|
|
813
|
+
assert all(["id" in doc for doc in docs])
|
|
814
|
+
new_doc_list = []
|
|
815
|
+
exist_ids = []
|
|
816
|
+
for doc in docs:
|
|
817
|
+
doc_id = doc["id"]
|
|
818
|
+
if doc_id not in exist_ids:
|
|
819
|
+
exist_ids.append(doc_id)
|
|
820
|
+
new_doc_list.append(doc)
|
|
821
|
+
return new_doc_list
|
|
822
|
+
|
|
823
|
+
def run_item(self, item):
|
|
824
|
+
question = item.question
|
|
825
|
+
retrieval_result = self.retriever.search(question)
|
|
826
|
+
|
|
827
|
+
stop_condition = "Intermediate answer:"
|
|
828
|
+
follow_ups = "No." if self.single_hop else "Yes."
|
|
829
|
+
res = ""
|
|
830
|
+
early_exit = False
|
|
831
|
+
for idx in range(self.max_iter):
|
|
832
|
+
input_prompt = (
|
|
833
|
+
self.P_INS
|
|
834
|
+
+ "\n"
|
|
835
|
+
+ self.format_reference(retrieval_result)
|
|
836
|
+
+ f"\nQuesiton: {question}"
|
|
837
|
+
+ "\nAre follow up questions needed here: "
|
|
838
|
+
+ follow_ups
|
|
839
|
+
+ "\n"
|
|
840
|
+
+ res
|
|
841
|
+
)
|
|
842
|
+
gen_out = self.generator.generate(input_prompt, stop=["Context:", "#", stop_condition])[0]
|
|
843
|
+
item.update_output(f"intermediate_output_iter{idx}", gen_out)
|
|
844
|
+
|
|
845
|
+
if stop_condition == "Intermediate answer:":
|
|
846
|
+
res += gen_out.split("Intermediate answer:")[0]
|
|
847
|
+
stop_condition = "Follow up:"
|
|
848
|
+
|
|
849
|
+
elif stop_condition == "Follow up:":
|
|
850
|
+
followup_split = re.split(self.FOLLOW_UP_PATTERN, gen_out)
|
|
851
|
+
res += followup_split[0]
|
|
852
|
+
|
|
853
|
+
if len(followup_split) > 1:
|
|
854
|
+
res += re.findall(self.FOLLOW_UP_PATTERN, gen_out)[0]
|
|
855
|
+
stop_condition = "Intermediate answer:"
|
|
856
|
+
|
|
857
|
+
# make sure the result does not end in a new line
|
|
858
|
+
if len(res) == 0:
|
|
859
|
+
early_exit = True
|
|
860
|
+
break
|
|
861
|
+
if res[-1] == "\n":
|
|
862
|
+
res = res[:-1]
|
|
863
|
+
|
|
864
|
+
if "Follow up: " in gen_out:
|
|
865
|
+
# get the first follow up
|
|
866
|
+
new_query = [l for l in gen_out.split("\n") if "Follow up: " in l][0].split("Follow up: ")[-1]
|
|
867
|
+
retrieval_result = self.retriever.search(new_query)
|
|
868
|
+
|
|
869
|
+
if "So the final answer is: " in gen_out:
|
|
870
|
+
res = (
|
|
871
|
+
self.format_reference(retrieval_result)
|
|
872
|
+
+ f"\nQuesiton: {question}"
|
|
873
|
+
+ "\nAre follow up questions needed here: "
|
|
874
|
+
+ follow_ups
|
|
875
|
+
+ "\n"
|
|
876
|
+
+ res
|
|
877
|
+
)
|
|
878
|
+
early_exit = True
|
|
879
|
+
# print("Success: early exit!")
|
|
880
|
+
break
|
|
881
|
+
|
|
882
|
+
if not early_exit:
|
|
883
|
+
res = (
|
|
884
|
+
self.format_reference(retrieval_result)
|
|
885
|
+
+ f"\nQuesiton: {question}"
|
|
886
|
+
+ "\nAre follow up questions needed here: "
|
|
887
|
+
+ follow_ups
|
|
888
|
+
+ "\n"
|
|
889
|
+
+ res
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
item.update_output("retrieval_result", retrieval_result)
|
|
893
|
+
item.update_output("pred", res)
|
|
894
|
+
|
|
895
|
+
def run(self, dataset, do_eval=True, pred_process_fun=selfask_pred_parse):
|
|
896
|
+
for item in tqdm(dataset, desc="Inference: "):
|
|
897
|
+
self.run_item(item)
|
|
898
|
+
|
|
899
|
+
dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
|
|
900
|
+
return dataset
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
class IRCOTPipeline(BasicPipeline):
|
|
904
|
+
IRCOT_INSTRUCTION = 'You serve as an intelligent assistant, adept at facilitating users through complex, multi-hop reasoning across multiple documents. This task is illustrated through demonstrations, each consisting of a document set paired with a relevant question and its multi-hop reasoning thoughts. Your task is to generate one thought for current step, DON\'T generate the whole thoughts at once! If you reach what you believe to be the final step, start with "So the answer is:".'
|
|
905
|
+
IRCOT_EXAMPLE = "Wikipedia Title: Kurram Garhi\nKurram Garhi is a small village located near the city of Bannu, which is the part of Khyber Pakhtunkhwa province of Pakistan. Its population is approximately 35000. Barren hills are near this village. This village is on the border of Kurram Agency. Other nearby villages are Peppal, Surwangi and Amandi Kala.\n\nWikipedia Title: 2001–02 UEFA Champions League second group stage\nEight winners and eight runners- up from the first group stage were drawn into four groups of four teams, each containing two group winners and two runners- up. Teams from the same country or from the same first round group could not be drawn together. The top two teams in each group advanced to the quarter- finals.\n\nWikipedia Title: Satellite tournament\nA satellite tournament is either a minor tournament or event on a competitive sporting tour or one of a group of such tournaments that form a series played in the same country or region.\n\nWikipedia Title: Trojkrsti\nTrojkrsti is a village in Municipality of Prilep, Republic of Macedonia.\n\nWikipedia Title: Telephone numbers in Ascension Island\nCountry Code:+ 247< br> International Call Prefix: 00 Ascension Island does not share the same country code( +290) with the rest of St Helena.\n\nQuestion: Are both Kurram Garhi and Trojkrsti located in the same country?\nThought: Kurram Garhi is located in the country of Pakistan. Trojkrsti is located in the country of Republic of Macedonia. Thus, they are not in the same country. So the answer is: no.\n\n"
|
|
906
|
+
|
|
907
|
+
def __init__(self, config, prompt_template=None, retriever=None, generator=None, max_iter=2):
|
|
908
|
+
# if not provide prompt template, use default template provided by IRCOT
|
|
909
|
+
if prompt_template is None:
|
|
910
|
+
prompt_template = PromptTemplate(
|
|
911
|
+
config=config,
|
|
912
|
+
system_prompt=f"{self.IRCOT_INSTRUCTION}\n\n{self.IRCOT_EXAMPLE}",
|
|
913
|
+
user_prompt="{reference}Question: {question}\nThought:",
|
|
914
|
+
reference_template="Wikipedia Title: {title}\n{text}\n\n",
|
|
915
|
+
enable_chat=False,
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
super().__init__(config, prompt_template)
|
|
919
|
+
self.generator = get_generator(config) if generator is None else generator
|
|
920
|
+
self.retriever = get_retriever(config) if retriever is None else retriever
|
|
921
|
+
|
|
922
|
+
self.max_iter = max_iter
|
|
923
|
+
|
|
924
|
+
def run_item(self, item):
|
|
925
|
+
question = item.question
|
|
926
|
+
retrieval_result, scores = self.retriever.search(question, return_score=True)
|
|
927
|
+
doc2score = {doc_item["id"]: score for doc_item, score in zip(retrieval_result, scores)}
|
|
928
|
+
id2doc = {doc_item["id"]: doc_item for doc_item in retrieval_result}
|
|
929
|
+
|
|
930
|
+
thoughts = []
|
|
931
|
+
iter_num = 0
|
|
932
|
+
while iter_num < self.max_iter:
|
|
933
|
+
input_prompt = self.prompt_template.get_string(
|
|
934
|
+
question=question, retrieval_result=retrieval_result, previous_gen=" ".join(thoughts)
|
|
935
|
+
)
|
|
936
|
+
new_thought = self.generator.generate(input_prompt)[0]
|
|
937
|
+
thoughts.append(new_thought)
|
|
938
|
+
iter_num += 1
|
|
939
|
+
if "So the answer is:" in new_thought:
|
|
940
|
+
item.update_output(
|
|
941
|
+
f"intermediate_output_iter{iter_num}",
|
|
942
|
+
{
|
|
943
|
+
"input_prompt": input_prompt,
|
|
944
|
+
"new_thought": new_thought,
|
|
945
|
+
},
|
|
946
|
+
)
|
|
947
|
+
break
|
|
948
|
+
|
|
949
|
+
# retrieve new docs and merge
|
|
950
|
+
new_retrieval_result, new_scores = self.retriever.search(new_thought, return_score=True)
|
|
951
|
+
for doc_item, score in zip(new_retrieval_result, new_scores):
|
|
952
|
+
id2doc[doc_item["id"]] = doc_item
|
|
953
|
+
doc_id = doc_item["id"]
|
|
954
|
+
if doc_id in doc2score:
|
|
955
|
+
doc2score[doc_id] = max(doc2score[doc_id], score)
|
|
956
|
+
else:
|
|
957
|
+
doc2score[doc_id] = score
|
|
958
|
+
sorted_doc_score = sorted(doc2score.items(), key=lambda x: x[1], reverse=False)
|
|
959
|
+
sorted_doc_id = [t[0] for t in sorted_doc_score]
|
|
960
|
+
retrieval_result = [id2doc[id] for id in sorted_doc_id]
|
|
961
|
+
|
|
962
|
+
item.update_output(
|
|
963
|
+
f"intermediate_output_iter{iter_num}",
|
|
964
|
+
{
|
|
965
|
+
"input_prompt": input_prompt,
|
|
966
|
+
"new_thought": new_thought,
|
|
967
|
+
"new_retreival_result": new_retrieval_result,
|
|
968
|
+
},
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
item.update_output("retrieval_result", retrieval_result)
|
|
972
|
+
item.update_output("pred", " ".join(thoughts))
|
|
973
|
+
return item
|
|
974
|
+
|
|
975
|
+
def run(self, dataset, do_eval=True, pred_process_fun=ircot_pred_parse):
|
|
976
|
+
for item in tqdm(dataset, desc="Inference: "):
|
|
977
|
+
self.run_item(item)
|
|
978
|
+
|
|
979
|
+
dataset = self.evaluate(dataset, do_eval=do_eval, pred_process_fun=pred_process_fun)
|
|
980
|
+
return dataset
|