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,2 @@
1
+ from flashrag.refiner.refiner import *
2
+ from flashrag.refiner.kg_refiner import *
@@ -0,0 +1,612 @@
1
+ import os
2
+ import json
3
+ from tqdm import tqdm
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from flashrag.refiner import BaseRefiner
8
+ from flashrag.prompt import PromptTemplate
9
+ from flashrag.retriever.encoder import Encoder, STEncoder
10
+ from flashrag.utils import hash_object
11
+
12
+
13
+ class KGTraceRefiner(BaseRefiner):
14
+ def __init__(self, config, retriever=None, generator=None):
15
+ super().__init__(config)
16
+ self.config = config
17
+ self.input_prompt_flag = False
18
+
19
+ default_setting = {
20
+ "num_examplars": 3,
21
+ "max_chain_length": 4,
22
+ "topk_triple_select": 5, # num of candidate triples
23
+ "num_choices": 20,
24
+ "min_triple_prob": 1e-4,
25
+ "num_beams": 5, # number of selected prob at each step of constructing chain
26
+ "num_chains": 20, # number of generated chains
27
+ "n_context": 5, # number of used chains in generation
28
+ "context_type": "triples", # triples/triple-doc
29
+ "triple_save_path": os.path.join(config["save_dir"], "save_triples.json"),
30
+ "triple_load_path": None,
31
+ }
32
+ if "trace_config" in config and config["trace_config"] is not None:
33
+ default_setting.update(config["trace_config"])
34
+ self.kg_setting = default_setting
35
+
36
+ self.num_examplars = self.kg_setting["num_examplars"]
37
+ self.max_chain_length = self.kg_setting["max_chain_length"]
38
+ self.topk_triple_select = self.kg_setting["topk_triple_select"]
39
+ self.num_beams = self.kg_setting["num_beams"]
40
+ self.num_chains = self.kg_setting["num_chains"]
41
+ self.num_choices = self.kg_setting["num_choices"]
42
+ self.min_triple_prob = self.kg_setting["min_triple_prob"]
43
+ self.n_context = self.kg_setting["n_context"]
44
+ self.context_type = self.kg_setting["context_type"]
45
+ self.triple_save_path = self.kg_setting["triple_save_path"]
46
+ self.triple_load_path = self.kg_setting["triple_load_path"]
47
+
48
+ # set necessary component
49
+ if retriever is None:
50
+ print("Load new retriever")
51
+ from flashrag.utils import get_retriever
52
+
53
+ self.retriever = get_retriever(config)
54
+ else:
55
+ self.retriever = retriever
56
+ if generator is None:
57
+ print("Load new generator")
58
+ from flashrag.utils import get_generator
59
+
60
+ self.generator = get_generator(config)
61
+ else:
62
+ self.generator = generator
63
+
64
+ # load demonstrations
65
+ if config["retrieval_method"] != "e5":
66
+ self.encoder = Encoder(
67
+ model_name="e5",
68
+ model_path=config["model2path"]["e5"],
69
+ pooling_method="mean",
70
+ max_length=256,
71
+ use_fp16=True,
72
+ )
73
+ else:
74
+ self.encoder = self.retriever.encoder
75
+
76
+ # load demonstrations for generating triples and reasoning chain
77
+ if config["dataset_name"].lower() == "hotpotqa":
78
+ from flashrag.prompt.trace_examplars import (
79
+ TRIPLE_EXAMPLARS_HOTPOTQA,
80
+ GENERATING_CHAIN_EXAMPLARS_HOTPOTQA,
81
+ FINAL_CHAIN_EXAMPLARS_HOTPOTQA,
82
+ )
83
+
84
+ self.triple_examplars = TRIPLE_EXAMPLARS_HOTPOTQA
85
+ self.final_chain_examplars = FINAL_CHAIN_EXAMPLARS_HOTPOTQA
86
+ self.generating_chain_examplars = GENERATING_CHAIN_EXAMPLARS_HOTPOTQA
87
+ elif config["dataset_name"].lower() == "musique":
88
+ from flashrag.prompt.trace_examplars import (
89
+ TRIPLE_EXAMPLARS_MUSIQUE,
90
+ GENERATING_CHAIN_EXAMPLARS_MUSIQUE,
91
+ FINAL_CHAIN_EXAMPLARS_MUSIQUE,
92
+ )
93
+
94
+ self.triple_examplars = TRIPLE_EXAMPLARS_MUSIQUE
95
+ self.final_chain_examplars = FINAL_CHAIN_EXAMPLARS_MUSIQUE
96
+ self.generating_chain_examplars = GENERATING_CHAIN_EXAMPLARS_MUSIQUE
97
+ elif "2wiki" in config["dataset_name"].lower():
98
+ from flashrag.prompt.trace_examplars import (
99
+ TRIPLE_EXAMPLARS_2WIKIMULTIHOPQA,
100
+ GENERATING_CHAIN_EXAMPLARS_2WIKIMULTIHOPQA,
101
+ FINAL_CHAIN_EXAMPLARS_2WIKIMULTIHOPQA,
102
+ )
103
+
104
+ self.triple_examplars = TRIPLE_EXAMPLARS_2WIKIMULTIHOPQA
105
+ self.final_chain_examplars = FINAL_CHAIN_EXAMPLARS_2WIKIMULTIHOPQA
106
+ self.generating_chain_examplars = GENERATING_CHAIN_EXAMPLARS_2WIKIMULTIHOPQA
107
+ else:
108
+ # use hotpotqa examplars
109
+ from flashrag.prompt.trace_examplars import (
110
+ TRIPLE_EXAMPLARS_HOTPOTQA,
111
+ GENERATING_CHAIN_EXAMPLARS_HOTPOTQA,
112
+ FINAL_CHAIN_EXAMPLARS_HOTPOTQA,
113
+ )
114
+
115
+ self.triple_examplars = TRIPLE_EXAMPLARS_HOTPOTQA
116
+ self.final_chain_examplars = FINAL_CHAIN_EXAMPLARS_HOTPOTQA
117
+ self.generating_chain_examplars = GENERATING_CHAIN_EXAMPLARS_HOTPOTQA
118
+
119
+ triple_examplars_text_list = [f"title: {item['title']} text: {item['text']}" for item in self.triple_examplars]
120
+ self.triple_examplars_embeddings = self.encoder.encode(triple_examplars_text_list, is_query=False)
121
+ self.triple_examplars_embeddings = torch.tensor(self.triple_examplars_embeddings)
122
+
123
+ chain_examplars_text_list = [item["question"] for item in self.final_chain_examplars]
124
+ self.chain_examplars_embeddings = self.encoder.encode(chain_examplars_text_list, is_query=True)
125
+ self.chain_examplars_embeddings = torch.tensor(self.chain_examplars_embeddings)
126
+
127
+ if self.triple_load_path is not None:
128
+ with open(self.triple_load_path, "r") as f:
129
+ self.extracted_doc_triples = json.load(f)
130
+ else:
131
+ self.extracted_doc_triples = {} # id: triples
132
+ self.token_id_to_choice_map = None
133
+
134
+ def get_examplars_for_triple(self, doc_list, batch_size=64):
135
+ # load demonstrations for each doc
136
+ doc_examplars = []
137
+
138
+ doc_text_list = []
139
+ for doc_content in doc_list:
140
+ title = doc_content.split("\n")[0]
141
+ text = "\n".join(doc_content.split("\n")[1:])
142
+ doc_text_list.append(f"title: {title} text: {text}")
143
+
144
+ doc_embeddings = []
145
+ for idx in range(0, len(doc_text_list), batch_size):
146
+ batch_data = doc_text_list[idx : idx + batch_size]
147
+ batch_embedding = self.encoder.encode(batch_data, is_query=True)
148
+ doc_embeddings.append(batch_embedding)
149
+ doc_embeddings = np.concatenate(doc_embeddings, axis=0)
150
+ doc_embeddings = torch.tensor(doc_embeddings)
151
+
152
+ similarities = torch.matmul(doc_embeddings, self.triple_examplars_embeddings.T)
153
+ examplars_rank = torch.argsort(similarities, dim=1, descending=True)
154
+ for i, _ in enumerate(doc_list):
155
+ rank = examplars_rank[i].tolist()
156
+ examplars = [self.triple_examplars[idx] for idx in rank[: self.num_examplars]]
157
+ examplars = [
158
+ "Title: {}\nText: {}\nKnowledge Triples: {}".format(
159
+ example["title"], example["text"], example["triples"]
160
+ )
161
+ for example in examplars
162
+ ]
163
+ doc_examplars.append(examplars)
164
+
165
+ return doc_examplars
166
+
167
+ def get_examplars_for_reasoning_chain(self, all_query):
168
+ generating_chain_examplars = []
169
+ final_chain_examplars = []
170
+ query_embeddings = self.encoder.encode(all_query, is_query=True)
171
+ query_embeddings = torch.tensor(query_embeddings)
172
+
173
+ similarities = torch.matmul(query_embeddings, self.chain_examplars_embeddings.T)
174
+ examplars_rank = torch.argsort(similarities, dim=1, descending=True)
175
+ for i, _ in enumerate(all_query):
176
+ rank = examplars_rank[i].tolist()
177
+ examplars = [self.final_chain_examplars[idx] for idx in rank[: self.num_examplars]]
178
+ final_chain_examplars.append(examplars)
179
+ examplars = [self.generating_chain_examplars[idx] for idx in rank[: self.num_examplars]]
180
+ generating_chain_examplars.append(examplars)
181
+ return generating_chain_examplars, final_chain_examplars
182
+
183
+ def parse_triple_output(self, doc_list, output_list):
184
+ def parse_model_output(triples_text: str):
185
+ import re
186
+
187
+ results = []
188
+ for one_triple_text in re.findall(r"<([^>]*)>", triples_text):
189
+ pieces = one_triple_text.rsplit(";", maxsplit=2)
190
+ if len(pieces) != 3:
191
+ print(f'Something wrong with this triple: "{one_triple_text}", Skip this triple!')
192
+ continue
193
+ head, relation, tail = pieces
194
+ results.append((head.strip(), relation.strip(), tail.strip()))
195
+ return results
196
+
197
+ # parse model_outputs
198
+ results = []
199
+ for j, (doc_content, generated_content) in enumerate(zip(doc_list, output_list)):
200
+ triples = parse_model_output(generated_content) # [(head, relation, tail)]
201
+ triples_in_one_document = []
202
+ title = doc_content.split("\n")[0]
203
+ text = "\n".join(doc_content.split("\n")[1:])
204
+ for head, relation, tail in triples:
205
+ # if head.lower() != title.lower():
206
+ # if head.lower() not in text.lower():
207
+ # head = title
208
+
209
+ triples_in_one_document.append(
210
+ {
211
+ "head": head,
212
+ "relation": relation,
213
+ "tail": tail,
214
+ }
215
+ )
216
+ results.append(triples_in_one_document)
217
+ return results
218
+
219
+ def extract_document_triples(self, queries, retrieval_results):
220
+ """
221
+ Extract triples from documents associated with each query, handling duplicates and generating prompts for LLM processing.
222
+ """
223
+
224
+ # deduplicate documents and map document IDs to their contents and associated queries
225
+ unique_docs = {}
226
+ doc_queries = {}
227
+ for query, docs in zip(queries, retrieval_results):
228
+ for doc in docs:
229
+ # if 'id' not in doc:
230
+ # doc['id'] = hash_object(doc['contents'])
231
+ doc["id"] = hash_object(doc["contents"])
232
+ doc_id = doc["id"]
233
+ if doc_id not in self.extracted_doc_triples:
234
+ unique_docs[doc_id] = doc["contents"]
235
+ doc_queries.setdefault(doc_id, []).append(query)
236
+
237
+ # prepare data structures for document processing
238
+ doc_ids = list(unique_docs.keys())
239
+ doc_id_mapping = {doc_id: index for index, doc_id in enumerate(doc_ids)}
240
+ docs_content = [unique_docs[doc_id] for doc_id in doc_ids]
241
+
242
+ if len(docs_content) > 0:
243
+ # obtain exemplars for each document
244
+ doc_examplars = self.get_examplars_for_triple(docs_content)
245
+
246
+ # construct prompts for triple extraction using an LLM
247
+ # prompts for extracting triples from documents
248
+ system_prompt = (
249
+ "Given a title and a text, extract all the knowledge triples in the form of <title; relation; tail entity>, "
250
+ "where title is the provided title, tail entity is a phrase in the text and relation denotes a description of the relation "
251
+ "between the title and the tail entity. \n\nThe followings are some examples: \n\n{examplars}"
252
+ )
253
+ user_prompt = "Title: {title}\nText: {text}\nKnowledge Triples: "
254
+ prompt_template = PromptTemplate(config=self.config, system_prompt=system_prompt, user_prompt=user_prompt)
255
+ prompts = [
256
+ prompt_template.get_string_with_varying_examplars(
257
+ question="",
258
+ examplars=examplars,
259
+ title=doc.split("\n")[0],
260
+ text="\n".join(doc.split("\n")[1:]),
261
+ tokenizer=self.generator.tokenizer,
262
+ max_length=2048,
263
+ )
264
+ for doc, examplars in zip(docs_content, doc_examplars)
265
+ ]
266
+
267
+ # generate triples via the language model
268
+ outputs = self.generator.generate(prompts, max_tokens=512)
269
+ triples = self.parse_triple_output(docs_content, outputs)
270
+
271
+ # reconstruct triples for the original query-documents structure
272
+ all_doc_triples = []
273
+ for query_retrieval_result in retrieval_results:
274
+ query_triples = []
275
+ for doc in query_retrieval_result:
276
+ doc_id = doc["id"]
277
+ if doc_id in doc_id_mapping:
278
+ triple_set = triples[doc_id_mapping[doc_id]]
279
+ self.extracted_doc_triples[doc_id] = triple_set
280
+ elif doc_id in self.extracted_doc_triples:
281
+ triple_set = self.extracted_doc_triples[doc_id]
282
+ else:
283
+ raise AssertionError("Document ID not found during triple extraction.")
284
+ query_triples.append(triple_set)
285
+ all_doc_triples.append(query_triples)
286
+
287
+ return all_doc_triples
288
+
289
+ def convert_candidate_triples_to_choices(self, candidates):
290
+ return "\n".join(
291
+ ["A. no need for additional knowledge triples"]
292
+ + ["{}. {}".format(chr(ord("B") + k), triple) for k, triple in enumerate(candidates)]
293
+ )
294
+
295
+ def build_prompt_for_reasoning_chain(
296
+ self,
297
+ hop,
298
+ question,
299
+ existing_triples,
300
+ candidate_triples,
301
+ generating_chain_examplars=[],
302
+ final_chain_examplars=[],
303
+ use_demonstration=True,
304
+ ):
305
+
306
+ base_instruction = (
307
+ "Select the next knowledge triple that extends an existing set of knowledge triples to form a coherent reasoning path capable of answering a specified question. "
308
+ "If the current reasoning path is sufficient to answer the question, simply output A. Please only output the choice for the next knowledge triple."
309
+ )
310
+
311
+ if use_demonstration and len(generating_chain_examplars) > 0:
312
+ demonstration_instruction = (
313
+ "\n\nThe followings are some examples of coherent reasoning paths capable of answering the specified question "
314
+ f"and how the {hop}-th knowledge triples in these paths are selected:\n\n"
315
+ )
316
+
317
+ # deal with examplars
318
+ examplars = []
319
+ for i, (rp_examplar, grp_examplar) in enumerate(zip(final_chain_examplars, generating_chain_examplars)):
320
+ if len(grp_examplar) < hop + 1:
321
+ continue
322
+ examplar = "coherent reasoning path: {}\nquestion: {}\n".format(
323
+ rp_examplar["chains"], rp_examplar["question"]
324
+ )
325
+ examplar += "The {}-th triple in the reasoning path is selected as:\n".format(hop + 1)
326
+ one_step_item = grp_examplar[hop]
327
+ examplar += "existing knowledge triples: {}\nquestion: {}\ncandidate knowledge triples:\n{}\nthe next possible triple is:{}\n".format(
328
+ ", ".join(one_step_item["triples"]),
329
+ one_step_item["question"],
330
+ "\n".join(one_step_item["candidate_triples"]),
331
+ one_step_item["answer"],
332
+ )
333
+ examplars.append(examplar)
334
+ if len(examplars) >= self.num_examplars:
335
+ break
336
+
337
+ system_prompt = base_instruction + " " + demonstration_instruction + "{examplars}"
338
+ else:
339
+ system_prompt = base_instruction + "\n\n"
340
+
341
+ user_prompt = (
342
+ "The {hop}-th triple in the reasoning path is selected as:\nexisting knowledge triples: {existing_triples}\n"
343
+ "question: {question}\ncandidate knowledge triples:\n{candidate_triples}\nthe next possible triple is:"
344
+ )
345
+
346
+ prompt_template = PromptTemplate(config=self.config, system_prompt=system_prompt, user_prompt=user_prompt)
347
+ prompt = prompt_template.get_string_with_varying_examplars(
348
+ hop=hop + 1,
349
+ question=question,
350
+ examplars=examplars,
351
+ existing_triples=", ".join(existing_triples),
352
+ candidate_triples=self.convert_candidate_triples_to_choices(candidate_triples),
353
+ tokenizer=self.generator.tokenizer,
354
+ max_length=2048,
355
+ )
356
+
357
+ return prompt
358
+
359
+ def get_answer_token_indices(self, tokenizer, num_choices, token_ids):
360
+ """Obtain the index of token corresponsding to the option"""
361
+ if self.token_id_to_choice_map is None:
362
+ self.token_id_to_choice_map = {}
363
+ choices = [chr(ord("A") + i) for i in range(num_choices + 1)]
364
+ for choice in choices:
365
+ self.token_id_to_choice_map[tokenizer.encode(choice, add_special_tokens=False)[0]] = choice
366
+ self.token_id_to_choice_map[tokenizer.encode(" {}".format(choice), add_special_tokens=False)[-1]] = (
367
+ choice
368
+ )
369
+
370
+ answer_token_indices = torch.zeros((token_ids.shape[0],), dtype=token_ids.dtype).fill_(token_ids.shape[1] - 1)
371
+ for i in range(token_ids.shape[0]):
372
+ for j in range(token_ids.shape[1]):
373
+ if token_ids[i, j].item() in self.token_id_to_choice_map:
374
+ answer_token_indices[i] = j
375
+ break
376
+
377
+ return answer_token_indices
378
+
379
+ def get_reasoning_chain(self, all_query, all_doc_triples, triple_to_doc_ids):
380
+ all_generating_chain_examplars, all_final_chain_examplars = self.get_examplars_for_reasoning_chain(all_query)
381
+ all_chain_results = []
382
+
383
+ for query, doc_triples, generating_chain_examplars, final_chain_examplars, triple_doc_id_list in tqdm(
384
+ zip(
385
+ all_query, all_doc_triples, all_generating_chain_examplars, all_final_chain_examplars, triple_to_doc_ids
386
+ ),
387
+ total=len(all_query),
388
+ desc="Generating reasoning chain for query",
389
+ ):
390
+ # run single item
391
+ flatten_triples = sum(doc_triples, []) # all triples
392
+ flatten_doc_ids = sum(
393
+ [
394
+ [doc_id for _ in single_doc_triple]
395
+ for doc_id, single_doc_triple in zip(triple_doc_id_list, doc_triples)
396
+ ],
397
+ [],
398
+ )
399
+ num_total_triples = len(flatten_triples)
400
+ triple_text = [
401
+ "<{}; {}; {}>".format(triple_item["head"], triple_item["relation"], triple_item["tail"])
402
+ for triple_item in flatten_triples
403
+ ]
404
+ triple_embeddings = torch.tensor(self.encoder.encode(triple_text, is_query=False))
405
+
406
+ paths = [[]] # each list contains index of triples to format a reasoning path
407
+ paths_scores = [1.0]
408
+ paths_finished = [False]
409
+
410
+ for j in range(self.max_chain_length):
411
+ if np.sum(paths_finished) == self.num_chains:
412
+ break
413
+
414
+ # triple rank: concatenation of question and selected triples as query
415
+ path_queries = [
416
+ "knowledge triples: {}\nquestion: {}".format(" ".join([triple_text[idx] for idx in path]), query)
417
+ for path in paths
418
+ ]
419
+ path_query_embeddings = torch.tensor(self.encoder.encode(path_queries, is_query=True))
420
+
421
+ path_triples_similarities = torch.matmul(path_query_embeddings, triple_embeddings.T)
422
+ candidate_triples_mask = torch.ones_like(path_triples_similarities)
423
+ for k, path in enumerate(paths):
424
+ # mask the chosen triples
425
+ candidate_triples_mask[k, path] = 0.0
426
+ path_triples_similarities = path_triples_similarities - 10000 * (1.0 - candidate_triples_mask)
427
+ topk_most_relevant_triples_indices = torch.topk(
428
+ path_triples_similarities, k=min(self.topk_triple_select, num_total_triples), dim=1
429
+ )[1].tolist()
430
+
431
+ # construct prompts for selecting triple in reasoning chain
432
+ # for each path, create an input to LLM
433
+ input_prompts = []
434
+ exisiting_triples = [
435
+ [triple_text[idx] for idx in path] for path in paths
436
+ ] # each item represents existing triples path
437
+ candidate_triples = [
438
+ [triple_text[idx] for idx in candidate_triples_indices]
439
+ for candidate_triples_indices in topk_most_relevant_triples_indices
440
+ ] # each item represents candidate triples in a path
441
+ for triples, candidates in zip(exisiting_triples, candidate_triples):
442
+ prompt = self.build_prompt_for_reasoning_chain(
443
+ hop=j,
444
+ question=query,
445
+ existing_triples=triples,
446
+ candidate_triples=candidates,
447
+ generating_chain_examplars=generating_chain_examplars,
448
+ final_chain_examplars=final_chain_examplars,
449
+ use_demonstration=True,
450
+ )
451
+ input_prompts.append(prompt)
452
+
453
+ # get generated result
454
+ torch.cuda.empty_cache()
455
+ generate_output = self.generator.generate(input_prompts, max_tokens=32, return_dict=True)
456
+ generated_token_ids, generated_token_logits = (
457
+ generate_output["generated_token_ids"],
458
+ generate_output["generated_token_logits"],
459
+ )
460
+
461
+ answer_token_indices = self.get_answer_token_indices(
462
+ self.generator.tokenizer, self.num_choices, generated_token_ids
463
+ )
464
+ answer_token_logits = generated_token_logits.gather(
465
+ 1, answer_token_indices[:, None, None].expand(-1, -1, generated_token_logits.shape[-1])
466
+ )
467
+ answer_token_logits = answer_token_logits.squeeze(1)
468
+
469
+ choices_token_ids_list = list(self.token_id_to_choice_map.keys())
470
+ choices_list = [
471
+ self.token_id_to_choice_map[token_id] for token_id in choices_token_ids_list
472
+ ] # ['A','B','C',..], may be duplication
473
+ answer_token_probs = F.softmax(answer_token_logits[:, choices_token_ids_list], dim=1)
474
+
475
+ new_paths, new_paths_scores, new_paths_finished = [], [], []
476
+ topk_choices_probs, topk_choices_indices = torch.topk(
477
+ answer_token_probs, k=self.num_beams, dim=1
478
+ ) # for each path, select choice token in topk probs
479
+ for i in range(len(paths)):
480
+ if paths_finished[i]:
481
+ new_paths.append(paths[i])
482
+ new_paths_scores.append(paths_scores[i])
483
+ new_paths_finished.append(True)
484
+ continue
485
+ if torch.all(torch.isnan(topk_choices_probs[i])):
486
+ print(
487
+ "No choice in generated results! generated text: {}".format(
488
+ self.generator.tokenizer.decode(generated_token_ids[i])
489
+ )
490
+ )
491
+ new_paths.append(paths[i])
492
+ new_paths_scores.append(paths_scores[i])
493
+ new_paths_finished.append(False)
494
+ continue
495
+ for b in range(self.num_beams):
496
+ if (
497
+ torch.isnan(topk_choices_probs[i, b])
498
+ or topk_choices_probs[i, b].item() < self.min_triple_prob
499
+ ):
500
+ continue
501
+ current_choice = choices_list[topk_choices_indices[i, b].item()]
502
+ if current_choice != "A" and (
503
+ ord(current_choice) - ord("B") >= len(topk_most_relevant_triples_indices[i])
504
+ ):
505
+ # generated invalid option
506
+ continue
507
+ new_paths_scores.append(paths_scores[i] * topk_choices_probs[i, b].item())
508
+ if current_choice == "A":
509
+ new_paths.append(paths[i] + [-1])
510
+ new_paths_finished.append(True)
511
+ else:
512
+ new_paths.append(
513
+ paths[i] + [topk_most_relevant_triples_indices[i][ord(current_choice) - ord("B")]]
514
+ )
515
+ new_paths_finished.append(False)
516
+
517
+ assert len(new_paths) == len(new_paths_scores)
518
+ assert len(new_paths) == len(new_paths_finished)
519
+ new_paths_sorted_indices = sorted(
520
+ range(len(new_paths_scores)), key=lambda x: new_paths_scores[x], reverse=True
521
+ )
522
+ topk_new_paths_sorted_indices = new_paths_sorted_indices[: self.num_chains]
523
+ paths = [new_paths[idx] for idx in topk_new_paths_sorted_indices]
524
+ paths_scores = [new_paths_scores[idx] for idx in topk_new_paths_sorted_indices]
525
+ paths_finished = [new_paths_finished[idx] for idx in topk_new_paths_sorted_indices]
526
+
527
+ query_chain_result = [
528
+ {
529
+ "triples": [triple_text[idx] for idx in path if idx >= 0],
530
+ "triple_doc_ids": [flatten_doc_ids[idx] for idx in path if idx >= 0],
531
+ "score": path_score,
532
+ }
533
+ for path, path_score in zip(paths, paths_scores)
534
+ ] # Contains paths with their scores
535
+
536
+ # sort with scores
537
+ query_chain_result.sort(key=lambda x: float(x["score"]), reverse=True)
538
+
539
+ all_chain_results.append(query_chain_result)
540
+
541
+ return all_chain_results
542
+
543
+ def format_reference(self, retrieval_result):
544
+ format_reference = ""
545
+ for idx, doc_item in enumerate(retrieval_result):
546
+ content = doc_item["contents"]
547
+ title = content.split("\n")[0]
548
+ text = "\n".join(content.split("\n")[1:])
549
+ if self.reference_template is not None:
550
+ format_reference += self.reference_template.format(idx=idx, title=title, text=text)
551
+ else:
552
+ format_reference += f"Doc {idx+1}(Title: {title}) {text}\n"
553
+
554
+ return format_reference
555
+
556
+ def batch_run(self, dataset):
557
+ all_query = dataset.question
558
+ retrieval_results = dataset.retrieval_result
559
+ print("Begin extracting triples")
560
+ all_doc_triples = self.extract_document_triples(all_query, retrieval_results)
561
+ triple_to_doc_ids = [
562
+ [doc_item["id"] for doc_item in query_retrieval_result] for query_retrieval_result in retrieval_results
563
+ ]
564
+ dataset.update_output("doc_triples", all_doc_triples)
565
+ # save triples for re-use
566
+ with open(self.triple_save_path, "w") as f:
567
+ json.dump(self.extracted_doc_triples, f, indent=4)
568
+ print("Finish extracting tiples")
569
+
570
+ print("Begin generating reasoning chain")
571
+ reasoning_chain_result = self.get_reasoning_chain(all_query, all_doc_triples, triple_to_doc_ids)
572
+ dataset.update_output("reasoning_chain", reasoning_chain_result)
573
+ print("Finish generating reasoning chain")
574
+
575
+ # get refine result based on context type
576
+ refine_result = []
577
+
578
+ if self.context_type == "triples":
579
+ for query_chain_result in reasoning_chain_result:
580
+ # query_chain_list: reasoning chain for a query
581
+ query_chain_result = query_chain_result[: self.n_context]
582
+ all_triple_text = []
583
+ for chain in query_chain_result:
584
+ triples = chain["triples"]
585
+ for triple in triples:
586
+ triple = triple.replace("<", "").replace(">", "").replace(";", "", 2)
587
+ if triple not in all_triple_text:
588
+ all_triple_text.append(triple)
589
+ refine_text = "\n".join(["{}. {}".format(i + 1, text) for i, text in enumerate(all_triple_text)])
590
+ refine_result.append(refine_text)
591
+
592
+ elif self.context_type == "triple-doc":
593
+ for query_chain_result, query_retrieval_results in zip(reasoning_chain_result, retrieval_results):
594
+
595
+ query_chain_result = query_chain_result[: self.n_context]
596
+ chains_doc_id_count_dict = {}
597
+
598
+ for chain in query_chain_result:
599
+ for triple, doc_id in zip(chain["triples"], chain["triple_doc_ids"]):
600
+ chains_doc_id_count_dict[doc_id] = chains_doc_id_count_dict.get(doc_id, 0) + 1
601
+
602
+ ranked_chains_doc_id = sorted(chains_doc_id_count_dict.items(), key=lambda x: x[1], reverse=True)
603
+ query_doc_to_idx = {doc_item["id"]: idx for idx, doc_item in enumerate(query_retrieval_results)}
604
+ final_doc_idx = [query_doc_to_idx[doc_id] for doc_id in ranked_chains_doc_id]
605
+ final_doc_list = [query_retrieval_results[idx] for idx in final_doc_idx]
606
+ refine_text = self.format_reference(final_doc_list)
607
+ refine_result.append(refine_text)
608
+
609
+ else:
610
+ assert False
611
+
612
+ return refine_result