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,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