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