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,252 @@
1
+ from typing import List
2
+ from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
3
+ from flashrag.retriever.encoder import Encoder
4
+ from tqdm import tqdm
5
+ import re
6
+ import torch
7
+ import numpy as np
8
+
9
+ class BaseRefiner:
10
+ r"""Base object of Refiner method"""
11
+
12
+ def __init__(self, config):
13
+ self.config = config
14
+ self.name = config["refiner_name"]
15
+ self.model_path = config["refiner_model_path"]
16
+ self.device = config["device"]
17
+ self.input_prompt_flag = config["refiner_input_prompt_flag"] if "refiner_input_prompt_flag" in config else False
18
+
19
+ def run(self, item) -> str:
20
+ r"""Get refining result.
21
+
22
+ Args:
23
+ item: dataset item, contains question, retrieval result...
24
+
25
+ Returns:
26
+ str: refining result of this item
27
+ """
28
+ pass
29
+
30
+ def batch_run(self, dataset, batch_size=None) -> List[str]:
31
+ return [self.run(item) for item in dataset]
32
+
33
+
34
+ class LLMLinguaRefiner(BaseRefiner):
35
+ """Implementation for (Long)LLMLingua."""
36
+
37
+ def __init__(self, config):
38
+ super().__init__(config)
39
+ default_config = {
40
+ "rate": 0.55,
41
+ "condition_in_question": "after_condition",
42
+ "reorder_context": "sort",
43
+ "dynamic_context_compression_ratio": 0.3,
44
+ "condition_compare": True,
45
+ "context_budget": "+100",
46
+ "rank_method": "longllmlingua",
47
+ }
48
+ if "llmlingua_config" in config and config["llmlingua_config"] is not None:
49
+ self.compress_config = config["llmlingua_config"]
50
+ else:
51
+ self.compress_config = default_config
52
+
53
+ from flashrag.refiner.llmlingua_compressor import PromptCompressor
54
+
55
+ self.refiner = PromptCompressor(model_name=self.model_path)
56
+
57
+ def format_reference(self, retrieval_result):
58
+ format_reference = ""
59
+ for idx, doc_item in enumerate(retrieval_result):
60
+ content = doc_item["contents"]
61
+ title = content.split("\n")[0]
62
+ text = "\n".join(content.split("\n")[1:])
63
+ format_reference += f"Doc {idx+1}(Title: {title}) {text}\n"
64
+
65
+ return format_reference
66
+
67
+ def batch_run(self, dataset):
68
+ output = []
69
+ for item in tqdm(dataset, desc="Refining process: "):
70
+ question = item.question
71
+ retrieval_result = item.retrieval_result
72
+ # TODO: suit more cases
73
+ if self.input_prompt_flag:
74
+ input_prompt = item.prompt
75
+ prompt_split = input_prompt.split("\n\n")
76
+ # need fixed format prompt: instr + demon(retrieval results) + question
77
+ instruction, question = prompt_split[0], prompt_split[-1]
78
+ demonstration = "\n".join(prompt_split[1:-1])
79
+ item_output = self.refiner.compress_prompt(
80
+ [i for i in demonstration.split("\n") if i != ""],
81
+ instruction=instruction,
82
+ question=question,
83
+ **self.compress_config,
84
+ )
85
+ else:
86
+ docs = self.format_reference(retrieval_result).split("\n")
87
+ docs = [i for i in docs if i != ""]
88
+ item_output = self.refiner.compress_prompt(
89
+ docs, instruction="", question=question, **self.compress_config
90
+ )
91
+ output.append(item_output["compressed_prompt"])
92
+ return output
93
+
94
+
95
+ class SelectiveContextRefiner(BaseRefiner):
96
+ """Implementation for Selective Context"""
97
+
98
+ def __init__(self, config):
99
+ super().__init__(config)
100
+ from flashrag.refiner.selective_context_compressor import SelectiveContext
101
+
102
+ default_config = {"reduce_ratio": 0.5}
103
+
104
+ self.refiner = SelectiveContext(model_type="gpt2", model_path=self.model_path, lang="en")
105
+ if "sc_config" in config and config["sc_config"] is not None:
106
+ self.compress_config = config["sc_config"]
107
+ else:
108
+ self.compress_config = default_config
109
+
110
+ def format_reference(self, retrieval_result):
111
+ format_reference = ""
112
+ for idx, doc_item in enumerate(retrieval_result):
113
+ content = doc_item["contents"]
114
+ title = content.split("\n")[0]
115
+ text = "\n".join(content.split("\n")[1:])
116
+ format_reference += f"Doc {idx+1}(Title: {title}) {text}\n"
117
+
118
+ return format_reference
119
+
120
+ def batch_run(self, dataset):
121
+ # only use text
122
+ all_inputs = []
123
+ for item in dataset:
124
+ retrieval_result = item.retrieval_result
125
+ all_inputs.append(self.format_reference(retrieval_result))
126
+
127
+ output = []
128
+ for text in tqdm(all_inputs, desc="Refining process: "):
129
+ compress_text, _ = self.refiner(text, **self.compress_config)
130
+ output.append(compress_text)
131
+ return output
132
+
133
+
134
+ class ExtractiveRefiner(BaseRefiner):
135
+ """Implementation for Extractive compressor.
136
+ Using retrieval method to select sentences or other granularity data.
137
+ """
138
+
139
+ def __init__(self, config):
140
+ super().__init__(config)
141
+ # number of keeping sentences
142
+ self.topk = config["refiner_topk"]
143
+ self.pooling_method = config["refiner_pooling_method"]
144
+ self.encode_max_length = config["refiner_encode_max_length"]
145
+ self.mini_batch_size = config['refiner_mini_batch_size'] if 'refiner_mini_batch_size' in config else 256
146
+ # load model
147
+ self.encoder = Encoder(
148
+ model_name=self.name,
149
+ model_path=self.model_path,
150
+ pooling_method=self.pooling_method,
151
+ max_length=self.encode_max_length,
152
+ use_fp16=True
153
+ )
154
+
155
+ def batch_run(self, dataset, batch_size=16):
156
+ questions = dataset.question
157
+ # only use text
158
+ retrieval_results = dataset.retrieval_result
159
+ retrieval_results = [
160
+ ["\n".join(doc_item["contents"].split("\n")[1:]) for doc_item in item_result]
161
+ for item_result in retrieval_results
162
+ ]
163
+
164
+ # split into sentences: [[sent1, sent2,...], [...]]
165
+ sent_lists = [
166
+ [i.strip() for i in re.split(r"(?<=[.!?])\s+", " ".join(res)) if len(i.strip()) > 5]
167
+ for res in retrieval_results
168
+ ]
169
+ score_lists = [] # matching scores, size == sent_lists
170
+ for idx in tqdm(range(0, len(questions), batch_size), desc="Refining process: "):
171
+ batch_questions = questions[idx : idx + batch_size]
172
+ batch_sents = sent_lists[idx : idx + batch_size]
173
+ question_embs = self.encoder.encode(batch_questions, is_query=True)
174
+
175
+ flatten_batch_sents = sum(batch_sents, [])
176
+ sent_embs = []
177
+ for s_index in tqdm(range(0, len(flatten_batch_sents), self.mini_batchsize), desc='Sentence encoding..,'):
178
+ mini_batch_sents = flatten_batch_sents[s_index:s_index+self.mini_batchsize]
179
+ mini_sent_embs = self.encoder.encode(mini_batch_sents, is_query=False)
180
+ sent_embs.append(mini_sent_embs)
181
+ sent_embs = np.concatenate(sent_embs, axis=0)
182
+
183
+ scores = question_embs @ sent_embs.T
184
+ start_idx = 0
185
+ for row_score, single_list in zip(scores, batch_sents):
186
+ row_score = row_score.tolist()
187
+ score_lists.append(row_score[start_idx : start_idx + len(single_list)])
188
+ start_idx += len(single_list)
189
+
190
+ # select topk sents
191
+ retain_lists = []
192
+ for sent_scores, sent_list in zip(score_lists, sent_lists):
193
+ assert len(sent_scores) == len(sent_list)
194
+ if len(sent_scores) < self.topk:
195
+ retain_lists.append(sent_list)
196
+ continue
197
+
198
+ topk_idxs = torch.topk(torch.Tensor(sent_scores), min(self.topk, len(sent_scores))).indices.tolist()
199
+ retain_lists.append([sent_list[idx] for idx in sorted(topk_idxs) if idx < len(sent_list)])
200
+
201
+ return [" ".join(sents) for sents in retain_lists]
202
+
203
+
204
+ class AbstractiveRecompRefiner(BaseRefiner):
205
+ """Implementation for Abstractive RECOMP compressor:
206
+ RECOMP: Improving Retrieval-Augmented LMs with Compression and Selective Augmentation.
207
+ """
208
+
209
+ def __init__(self, config):
210
+ super().__init__(config)
211
+
212
+ self.max_input_length = config["refiner_max_input_length"]
213
+ self.max_output_length = config["refiner_max_output_length"]
214
+
215
+ # load model
216
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_path)
217
+ self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model_path)
218
+ self.model.cuda()
219
+ self.model.eval()
220
+
221
+ def batch_run(self, dataset, batch_size=2):
222
+ # only use text
223
+ retrieval_results = dataset.retrieval_result
224
+ retrieval_results = [
225
+ ["\n".join(doc_item["contents"].split("\n")[1:]) for doc_item in item_result]
226
+ for item_result in retrieval_results
227
+ ]
228
+
229
+ # input processing in recomp training format
230
+ format_inputs = [
231
+ "Question: {question}\n Document: {document}\n Summary: ".format(
232
+ question=item.question, document="\n".join(docs)
233
+ )
234
+ for item, docs in zip(dataset, retrieval_results)
235
+ ]
236
+
237
+ results = []
238
+ for idx in tqdm(range(0, len(format_inputs), batch_size), desc="Refining process: "):
239
+ batch_inputs = format_inputs[idx : idx + batch_size]
240
+ batch_inputs = self.tokenizer(
241
+ batch_inputs, return_tensors="pt", padding=True, truncation=True, max_length=self.max_input_length
242
+ ).to(self.device)
243
+
244
+ batch_outputs = self.model.generate(**batch_inputs, max_length=self.max_output_length)
245
+
246
+ batch_outputs = self.tokenizer.batch_decode(
247
+ batch_outputs, skip_special_tokens=True, clean_up_tokenization_spaces=False
248
+ )
249
+
250
+ results.extend(batch_outputs)
251
+
252
+ return results
@@ -0,0 +1,294 @@
1
+ # Implementation of Selective-Context, modified from official repo: https://github.com/liyucheng09/Selective_Context
2
+ # Licensed under The MIT License
3
+
4
+
5
+ import re
6
+ from typing import List, Tuple
7
+ import spacy
8
+ import numpy as np
9
+ import os
10
+ import sys
11
+
12
+ sys.path.append("..")
13
+ from dataclasses import dataclass
14
+ from nltk.tokenize import word_tokenize
15
+ import time
16
+ import torch
17
+ from transformers import GPT2Tokenizer, GPT2LMHeadModel, BertTokenizer
18
+
19
+
20
+ @dataclass
21
+ class LexicalUnits:
22
+ unit_type: str
23
+ text: List[str]
24
+ self_info: List[float] = None
25
+
26
+ def __add__(self, other):
27
+ assert self.unit_type == other.unit_type, "Cannot add two different unit types"
28
+ return LexicalUnits(self.unit_type, self.text + other.text, self.self_info + other.self_info)
29
+
30
+ def __radd__(self, other):
31
+ if other == 0:
32
+ return self
33
+ return NotImplementedError()
34
+
35
+ def add_to_head(self, token, self_info):
36
+ return LexicalUnits(self.unit_type, [token] + self.text, [self_info] + self.self_info)
37
+
38
+ def add_to_tail(self, token, self_info):
39
+ return LexicalUnits(self.unit_type, self.text + [token], self.self_info + [self_info])
40
+
41
+
42
+ class SelectiveContext:
43
+
44
+ def __init__(self, model_type="gpt2", model_path="openai-community/gpt2", lang="en"):
45
+
46
+ self.model_type = model_type
47
+ self.model_path = model_path
48
+ self.lang = lang
49
+
50
+ # this means we calculate self-information sentence by sentence
51
+ self.sent_level_self_info = True
52
+
53
+ self._prepare_phrase_tokenizer()
54
+ self.sent_tokenize_pattern = r"(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s"
55
+ self.phrase_mask_token = ""
56
+ self.sent_mask_token = "<...some content omitted.>"
57
+
58
+ self._prepare_model()
59
+
60
+ def _prepare_phrase_tokenizer(self):
61
+ # we use space to tokenize sentence into phrases
62
+ # for English, we should use `spacy.load("en_core_web_sm").add_pipe('merge_noun_chunks')`
63
+ # for Chinese, use `nlp = spacy.load('zh_core_web_sm')`` directly
64
+ lang = self.lang
65
+ if lang == "en":
66
+ self.nlp = spacy.load("en_core_web_lg", disable=["ner"])
67
+ self.nlp.add_pipe("merge_noun_chunks")
68
+ elif lang == "zh":
69
+ self.nlp = spacy.load("zh_core_web_sm", disable=["ner"])
70
+
71
+ def _prepare_model(self):
72
+ # Load tokenizer
73
+ if self.lang == "zh":
74
+ self.tokenizer = BertTokenizer.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
75
+ elif self.lang == "en":
76
+ self.tokenizer = GPT2Tokenizer.from_pretrained(self.model_path)
77
+ else:
78
+ raise NotImplementedError()
79
+
80
+ if self.model_type == "gpt2":
81
+ if self.lang == "zh":
82
+ self.model = GPT2LMHeadModel.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
83
+ else:
84
+ self.model = GPT2LMHeadModel.from_pretrained(self.model_path)
85
+ self.model.to("cuda")
86
+ self.model.eval()
87
+
88
+ print("model loaded")
89
+
90
+ self.max_token_length = self.model.config.n_positions
91
+ self.get_self_information = self._get_self_info_via_gpt2
92
+
93
+ elif self.model_type == "curie":
94
+ global openai
95
+ import openai
96
+
97
+ self.max_token_length = 2048
98
+
99
+ self.get_self_information = self._get_self_info_via_curie
100
+
101
+ def get_self_information(self, text: str) -> Tuple[List[str], List[float]]:
102
+ # it takes text as input, and return a list of words and a list of self-information scores
103
+ raise NotImplementedError
104
+
105
+ def _get_self_info_via_gpt2(self, text: str) -> Tuple[List[str], List[float]]:
106
+ if self.lang == "en":
107
+ text = f"<|endoftext|>{text}"
108
+ elif self.lang == "zh":
109
+ text = f"[CLS]{text}"
110
+ with torch.inference_mode(mode=True):
111
+ encoding = self.tokenizer(
112
+ text, max_length=1024, truncation=True, add_special_tokens=False, return_tensors="pt"
113
+ )
114
+ encoding = encoding.to(self.model.device)
115
+ outputs = self.model(**encoding)
116
+ logits = outputs.logits
117
+ probs = torch.softmax(logits, dim=-1)
118
+ self_info = -torch.log(probs)
119
+
120
+ input_ids = encoding["input_ids"]
121
+ input_ids_expaned = input_ids[:, 1:].unsqueeze(-1)
122
+
123
+ tokens = [self.tokenizer.decode(token_) for token_ in input_ids.squeeze().tolist()[1:]]
124
+ return tokens, self_info[:, :-1].gather(-1, input_ids_expaned).squeeze(-1).squeeze(0).tolist()
125
+
126
+ def _get_self_info_via_curie(self, text: str) -> Tuple[List[str], List[float]]:
127
+ num_retry = 3
128
+ openai.api_key = os.environ["OPENAI_API_KEY"]
129
+
130
+ for _ in range(num_retry):
131
+ try:
132
+ r = openai.Completion.create(
133
+ model="curie",
134
+ prompt=f"<|endoftext|>{text}",
135
+ max_tokens=0,
136
+ temperature=0,
137
+ echo=True,
138
+ logprobs=0,
139
+ )
140
+ break
141
+ except Exception as e:
142
+ print(e)
143
+ time.sleep(1)
144
+
145
+ result = r["choices"][0]
146
+ tokens, logprobs = result["logprobs"]["tokens"][1:], result["logprobs"]["token_logprobs"][1:]
147
+
148
+ assert len(tokens) == len(logprobs), f"Expected {len(tokens)} logprobs, got {len(logprobs)}"
149
+
150
+ self_info = [-logprob for logprob in logprobs]
151
+ return tokens, self_info
152
+
153
+ def _lexical_unit(self, sents):
154
+
155
+ if self.sent_level_self_info:
156
+ sent_self_info = []
157
+ all_noun_phrases = []
158
+ all_noun_phrases_info = []
159
+ all_tokens = []
160
+ all_token_self_info = []
161
+
162
+ for sent in sents:
163
+ # print(sent)
164
+ tokens, self_info = self.get_self_information(sent)
165
+ sent_self_info.append(np.mean(self_info))
166
+
167
+ all_tokens.extend(tokens)
168
+ all_token_self_info.extend(self_info)
169
+
170
+ noun_phrases, noun_phrases_info = self._calculate_lexical_unit(tokens, self_info)
171
+
172
+ # We need to add a space before the first noun phrase for every sentence except the first one
173
+ if len(all_noun_phrases) != 0:
174
+ noun_phrases[0] = f" {noun_phrases[0]}"
175
+ all_noun_phrases.extend(noun_phrases)
176
+ all_noun_phrases_info.extend(noun_phrases_info)
177
+
178
+ return [
179
+ LexicalUnits("sent", text=sents, self_info=sent_self_info),
180
+ LexicalUnits("phrase", text=all_noun_phrases, self_info=all_noun_phrases_info),
181
+ LexicalUnits("token", text=all_tokens, self_info=all_token_self_info),
182
+ ]
183
+
184
+ def _calculate_lexical_unit(self, tokens, self_info):
185
+ def _unit_info(tokens, self_info, units):
186
+ current_unit_idx = 0
187
+ current_position = 0
188
+ unit_self_info = [[] for _ in range(len(units))]
189
+
190
+ for idx, (token, info) in enumerate(zip(tokens, self_info)):
191
+ current_position += len(token)
192
+ if current_position == len(units[current_unit_idx]):
193
+ unit_self_info[current_unit_idx].append(info)
194
+ current_position = current_position - len(units[current_unit_idx])
195
+ current_unit_idx += 1
196
+ elif current_position > len(units[current_unit_idx]):
197
+ counter_ = 1
198
+ current_position = current_position - len(units[current_unit_idx])
199
+ current_unit_idx += 1
200
+ while current_position >= len(units[current_unit_idx]):
201
+ counter_ += 1
202
+ current_position = current_position - len(units[current_unit_idx])
203
+ current_unit_idx += 1
204
+ if current_unit_idx >= len(units):
205
+ break
206
+ partial_info = info / counter_
207
+ for _ in range(counter_):
208
+ unit_self_info[(current_unit_idx - 1) - _].append(partial_info)
209
+ else:
210
+ if token == " ":
211
+ continue
212
+ unit_self_info[current_unit_idx].append(info)
213
+
214
+ unit_self_info_ = [np.mean(info) for info in unit_self_info]
215
+ return unit_self_info_
216
+
217
+ def _noun_phrases(sent):
218
+ noun_phrases = []
219
+ doc = self.nlp(sent)
220
+ for index, chunk in enumerate(doc):
221
+ if index == 0:
222
+ noun_phrases.append(chunk.text)
223
+ else:
224
+ noun_phrases.append(doc[index - 1].whitespace_ + chunk.text)
225
+ return noun_phrases
226
+
227
+ if self.sent_level_self_info:
228
+ # in this case, the self_info is for each sentence
229
+ # we only need to calculate the self_info for each phrase
230
+
231
+ sent = "".join(tokens)
232
+ # noun_phrases = [chunk.text for chunk in self.nlp(sent).noun_chunks]
233
+ noun_phrases = _noun_phrases(sent)
234
+ # noun_phrases[-1] = noun_phrases[-1] + ' '
235
+ noun_phrases_info = _unit_info(tokens, self_info, noun_phrases)
236
+
237
+ return noun_phrases, noun_phrases_info
238
+
239
+ def beautify_context(self, context: str) -> str:
240
+ context = re.sub(r"\s+", " ", context)
241
+ return context
242
+
243
+ def self_info_mask(self, sents: List[str], self_info: List[float], mask_level):
244
+ # mask_level: mask sentences, phrases, or tokens
245
+ sents_after_mask = []
246
+ masked_sents = []
247
+
248
+ self.ppl_threshold = np.nanpercentile(self_info, self.mask_ratio * 100)
249
+
250
+ for sent, info in zip(sents, self_info):
251
+ if info < self.ppl_threshold:
252
+ masked_sents.append(sent)
253
+ sents_after_mask.append(self.mask_a_sent(sent, mask_level))
254
+ else:
255
+ sents_after_mask.append(sent)
256
+ masked_context = " ".join(sents_after_mask) if mask_level == "sent" else "".join(sents_after_mask)
257
+
258
+ return masked_context, masked_sents
259
+
260
+ def mask_a_sent(self, sent, level):
261
+ if level == "phrase":
262
+ return self.phrase_mask_token
263
+ elif level == "sent":
264
+ if self.keep_leading_word:
265
+ leading_few_words = " ".join(word_tokenize(sent)[: self.num_lead_words]) + " "
266
+ else:
267
+ leading_few_words = ""
268
+ return leading_few_words + self.mask_token
269
+ elif level == "token":
270
+ return ""
271
+
272
+ def __call__(self, text: str, reduce_ratio: float = 0.5, reduce_level: str = "phrase") -> List[str]:
273
+ context = self.beautify_context(text)
274
+
275
+ self.mask_ratio = reduce_ratio
276
+
277
+ sents = re.split(self.sent_tokenize_pattern, context)
278
+ sents = [sent.strip() for sent in sents if sent.strip()]
279
+
280
+ # You want the reduce happen at sentence level, phrase level, or token level?
281
+ assert reduce_level in [
282
+ "sent",
283
+ "phrase",
284
+ "token",
285
+ ], f"reduce_level should be one of ['sent', 'phrase', 'token'], got {reduce_level}"
286
+ sent_lus, phrase_lus, token_lus = self._lexical_unit(sents)
287
+ # print(phrase_lus, '^^^^')
288
+ lexical_level = {"sent": sent_lus, "phrase": phrase_lus, "token": token_lus}
289
+
290
+ # context is the reduced context, masked_sents denotes what context has been filtered out
291
+ context, masked_sents = self.self_info_mask(
292
+ lexical_level[reduce_level].text, lexical_level[reduce_level].self_info, reduce_level
293
+ )
294
+ return context, masked_sents
@@ -0,0 +1,3 @@
1
+ from flashrag.retriever.retriever import *
2
+ from flashrag.retriever.reranker import *
3
+ from flashrag.retriever.utils import *
@@ -0,0 +1,4 @@
1
+ from . import index_builder
2
+
3
+ if __name__ == "__main__":
4
+ index_builder.main()
@@ -0,0 +1,120 @@
1
+ from typing import List
2
+ import torch
3
+ import numpy as np
4
+ from flashrag.retriever.utils import load_model, pooling
5
+
6
+
7
+ def parse_query(model_name, query_list, is_query=True):
8
+ """
9
+ processing query for different encoders
10
+ """
11
+
12
+ def is_zh(str):
13
+ import unicodedata
14
+
15
+ zh_char = 0
16
+ for c in str:
17
+ try:
18
+ if "CJK" in unicodedata.name(c):
19
+ zh_char += 1
20
+ except:
21
+ continue
22
+ if zh_char / len(str) > 0.2:
23
+ return True
24
+ else:
25
+ return False
26
+
27
+ if isinstance(query_list, str):
28
+ query_list = [query_list]
29
+
30
+ if "e5" in model_name.lower():
31
+ if is_query:
32
+ query_list = [f"query: {query}" for query in query_list]
33
+ else:
34
+ query_list = [f"passage: {query}" for query in query_list]
35
+
36
+ if "bge" in model_name.lower():
37
+ if is_query:
38
+ if is_zh(query_list[0]):
39
+ query_list = [f"为这个句子生成表示以用于检索相关文章:{query}" for query in query_list]
40
+ else:
41
+ query_list = [
42
+ f"Represent this sentence for searching relevant passages: {query}" for query in query_list
43
+ ]
44
+
45
+ return query_list
46
+
47
+
48
+ class Encoder:
49
+ def __init__(self, model_name, model_path, pooling_method, max_length, use_fp16):
50
+ self.model_name = model_name
51
+ self.model_path = model_path
52
+ self.pooling_method = pooling_method
53
+ self.max_length = max_length
54
+ self.use_fp16 = use_fp16
55
+
56
+ self.model, self.tokenizer = load_model(model_path=model_path, use_fp16=use_fp16)
57
+
58
+ @torch.inference_mode(mode=True)
59
+ def encode(self, query_list: List[str], is_query=True) -> np.ndarray:
60
+ query_list = parse_query(self.model_name, query_list, is_query)
61
+
62
+ inputs = self.tokenizer(
63
+ query_list, max_length=self.max_length, padding=True, truncation=True, return_tensors="pt"
64
+ )
65
+ inputs = {k: v.cuda() for k, v in inputs.items()}
66
+
67
+ if "T5" in type(self.model).__name__:
68
+ # T5-based retrieval model
69
+ decoder_input_ids = torch.zeros((inputs["input_ids"].shape[0], 1), dtype=torch.long).to(
70
+ inputs["input_ids"].device
71
+ )
72
+ output = self.model(**inputs, decoder_input_ids=decoder_input_ids, return_dict=True)
73
+ query_emb = output.last_hidden_state[:, 0, :]
74
+
75
+ else:
76
+ output = self.model(**inputs, return_dict=True)
77
+ query_emb = pooling(
78
+ output.pooler_output, output.last_hidden_state, inputs["attention_mask"], self.pooling_method
79
+ )
80
+
81
+ query_emb = query_emb.detach().cpu().numpy()
82
+ query_emb = query_emb.astype(np.float32, order="C")
83
+ return query_emb
84
+
85
+
86
+ class STEncoder:
87
+ def __init__(self, model_name, model_path, max_length, use_fp16):
88
+ import torch
89
+ from sentence_transformers import SentenceTransformer
90
+
91
+ self.model_name = model_name
92
+ self.model_path = model_path
93
+ self.max_length = max_length
94
+ self.use_fp16 = use_fp16
95
+
96
+ self.model = SentenceTransformer(
97
+ model_path, model_kwargs={"torch_dtype": torch.float16 if use_fp16 else torch.float}
98
+ )
99
+
100
+ @torch.inference_mode(mode=True)
101
+ def encode(self, query_list: List[str], batch_size=64, is_query=True) -> np.ndarray:
102
+ query_list = parse_query(self.model_name, query_list, is_query)
103
+ query_emb = self.model.encode(
104
+ query_list, batch_size=batch_size, convert_to_numpy=True, normalize_embeddings=True
105
+ )
106
+ query_emb = query_emb.astype(np.float32, order="C")
107
+
108
+ return query_emb
109
+
110
+ @torch.inference_mode(mode=True)
111
+ def multi_gpu_encode(self, query_list: List[str], is_query=True, batch_size=None) -> np.ndarray:
112
+ query_list = parse_query(self.model_name, query_list, is_query)
113
+ pool = self.model.start_multi_process_pool()
114
+ query_emb = self.model.encode_multi_process(
115
+ query_list, pool, convert_to_numpy=True, normalize_embeddings=True, batch_size=batch_size
116
+ )
117
+ self.model.stop_multi_process_pool(pool)
118
+ query_emb.astype(np.float32, order="C")
119
+
120
+ return query_emb