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,96 @@
1
+ import os
2
+ from typing import List
3
+ from copy import deepcopy
4
+ import warnings
5
+ from tqdm import tqdm
6
+ import numpy as np
7
+
8
+ import asyncio
9
+ from openai import AsyncOpenAI, AsyncAzureOpenAI
10
+ import tiktoken
11
+
12
+
13
+ class OpenaiGenerator:
14
+ """Class for api-based openai models"""
15
+
16
+ def __init__(self, config):
17
+ self.model_name = config["generator_model"]
18
+ self.batch_size = config["generator_batch_size"]
19
+ self.generation_params = config["generation_params"]
20
+
21
+ self.openai_setting = config["openai_setting"]
22
+ if self.openai_setting["api_key"] is None:
23
+ self.openai_setting["api_key"] = os.getenv("OPENAI_API_KEY")
24
+
25
+ if "api_type" in self.openai_setting and self.openai_setting["api_type"] == "azure":
26
+ del self.openai_setting["api_type"]
27
+ self.client = AsyncAzureOpenAI(**self.openai_setting)
28
+ else:
29
+ self.client = AsyncOpenAI(**self.openai_setting)
30
+
31
+ self.tokenizer = tiktoken.encoding_for_model(self.model_name)
32
+
33
+ async def get_response(self, input: List, **params):
34
+ response = await self.client.chat.completions.create(model=self.model_name, messages=input, **params)
35
+ return response.choices[0]
36
+
37
+ async def get_batch_response(self, input_list: List[List], batch_size, **params):
38
+ total_input = [self.get_response(input, **params) for input in input_list]
39
+ all_result = []
40
+ for idx in tqdm(range(0, len(input_list), batch_size), desc="Generation process: "):
41
+ batch_input = total_input[idx : idx + batch_size]
42
+ batch_result = await asyncio.gather(*batch_input)
43
+ all_result.extend(batch_result)
44
+
45
+ return all_result
46
+
47
+ def generate(self, input_list: List[List], batch_size=None, return_scores=False, **params) -> List[str]:
48
+ # deal with single input
49
+ if len(input_list) == 1:
50
+ input_list = [input_list]
51
+ if batch_size is None:
52
+ batch_size = self.batch_size
53
+
54
+ # deal with generation params
55
+ generation_params = deepcopy(self.generation_params)
56
+ generation_params.update(params)
57
+ if "do_sample" in generation_params:
58
+ generation_params.pop("do_sample")
59
+
60
+ max_tokens = params.pop("max_tokens", None) or params.pop("max_new_tokens", None)
61
+ if max_tokens is not None:
62
+ generation_params["max_tokens"] = max_tokens
63
+ else:
64
+ generation_params["max_tokens"] = generation_params.get(
65
+ "max_tokens", generation_params.pop("max_new_tokens", None)
66
+ )
67
+ generation_params.pop("max_new_tokens", None)
68
+
69
+ if return_scores:
70
+ if generation_params.get("logprobs") is not None:
71
+ generation_params["logprobs"] = True
72
+ warnings.warn("Set logprobs to True to get generation scores.")
73
+ else:
74
+ generation_params["logprobs"] = True
75
+
76
+ if generation_params.get("n") is not None:
77
+ generation_params["n"] = 1
78
+ warnings.warn("Set n to 1. It can minimize costs.")
79
+ else:
80
+ generation_params["n"] = 1
81
+
82
+ loop = asyncio.get_event_loop()
83
+ result = loop.run_until_complete(self.get_batch_response(input_list, batch_size, **generation_params))
84
+
85
+ # parse result into response text and logprob
86
+ scores = []
87
+ response_text = []
88
+ for res in result:
89
+ response_text.append(res.message.content)
90
+ if return_scores:
91
+ score = np.exp(list(map(lambda x: x.logprob, res.logprobs.content)))
92
+ scores.append(score)
93
+ if return_scores:
94
+ return response_text, scores
95
+ else:
96
+ return response_text
@@ -0,0 +1,105 @@
1
+ """
2
+ Created by Nestor Demeure.
3
+ This software is released under the Apache License 2.0.
4
+ """
5
+
6
+ from typing import List
7
+ import torch
8
+ from transformers import StoppingCriteria, AutoTokenizer
9
+
10
+
11
+ class StopWordCriteria(StoppingCriteria):
12
+ """
13
+ A stopping criteria that halts the text generation process if any specified stop word is encountered.
14
+
15
+ Inspired by https://discuss.huggingface.co/t/implimentation-of-stopping-criteria-list/20040/9
16
+ And: https://github.com/outlines-dev/outlines/blob/main/outlines/generate/api.py
17
+ """
18
+
19
+ def __init__(self, tokenizer: AutoTokenizer, prompts: List[str], stop_words: List[str] = [], check_every: int = 1):
20
+ """
21
+ Initializes the StopWordCriteria with the necessary parameters for checking stop words during text generation.
22
+
23
+ Parameters:
24
+ tokenizer (AutoTokenizer): The tokenizer for encoding prompts and stop words.
25
+ prompts (List[str]): Initial prompts used for generation, needed to determine where generated text begins.
26
+ stop_words (List[str]): Words that trigger the stopping of generation when detected.
27
+ check_every (int): Frequency of checking for stop words in the token stream (a performance optimization, use 1 to cut it out).
28
+ """
29
+ super().__init__()
30
+ self.tokenizer = tokenizer
31
+ self.input_sizes = [self.tokenizer.encode(prompt, return_tensors="pt").size(-1) for prompt in prompts]
32
+ self.stop_words = stop_words
33
+ self.max_stop_word_size = max(
34
+ (self.tokenizer.encode(word, return_tensors="pt").size(-1) for word in stop_words), default=0
35
+ )
36
+ self.check_every = check_every
37
+
38
+ def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
39
+ """
40
+ Determines whether to stop generation based on the presence of stop words.
41
+
42
+ Stops if a stop word is found in *all* batch elements *and* the sequence length is a multiple of `check_every`.
43
+ Note: Delay in stopping may occur if `check_every > 1`.
44
+
45
+ Parameters:
46
+ input_ids (torch.LongTensor): Generated token IDs.
47
+ scores (torch.FloatTensor): Generation scores for each token. Not used here.
48
+
49
+ Returns:
50
+ bool: True to stop generation, False to continue.
51
+ """
52
+ batch_size, seq_len = input_ids.shape
53
+
54
+ # Skip check if no stop words are defined or it is not yet time to check
55
+ if (len(self.stop_words) == 0) or (seq_len % self.check_every != 0):
56
+ return False
57
+
58
+ for i in range(batch_size):
59
+ # Calculate starting index for new tokens
60
+ prompt_size = self.input_sizes[i]
61
+ max_new_tokens = (2 * self.max_stop_word_size) + self.check_every
62
+ latest_tokens = input_ids[i, prompt_size:][-max_new_tokens:]
63
+
64
+ # Check for stop words in the decoded text
65
+ if not any(
66
+ word in self.tokenizer.decode(latest_tokens, skip_special_tokens=True) for word in self.stop_words
67
+ ):
68
+ return False # Continue generation if any batch item lacks stop words
69
+
70
+ return True # Stop generation if all conditions are met
71
+
72
+ def extract_answers(self, input_ids: torch.LongTensor, strip_stopword: bool = True) -> List[str]:
73
+ """
74
+ Extracts generated answers by removing prompts and optionally stopping at the first stop word.
75
+
76
+ Parameters:
77
+ input_ids (torch.LongTensor): Generated token IDs.
78
+ strip_stopword (bool): Determines whether the stop word is removed from the output.
79
+
80
+ Returns:
81
+ List[str]: Extracted answers, with or without stop words.
82
+ """
83
+ batch_size, _ = input_ids.shape
84
+ result = []
85
+
86
+ for i in range(batch_size):
87
+ # Decode generated tokens to text, excluding the prompt
88
+ prompt_size = self.input_sizes[i]
89
+ answer_tokens = input_ids[i, prompt_size:]
90
+ answer_text = self.tokenizer.decode(answer_tokens, skip_special_tokens=True)
91
+
92
+ # Find the first occurrence of any stop word
93
+ lower_stop_index = len(answer_text) # Default to end of text
94
+ for word in self.stop_words:
95
+ stop_index = answer_text.find(word)
96
+ if stop_index != -1:
97
+ # Adjust stop index based on whether we're stripping the stop word
98
+ stop_index += 0 if strip_stopword else len(word)
99
+ lower_stop_index = min(stop_index, lower_stop_index)
100
+
101
+ # Cut the text at the first stop word found (if any)
102
+ answer_text = answer_text[:lower_stop_index]
103
+ result.append(answer_text)
104
+
105
+ return result
@@ -0,0 +1 @@
1
+ from flashrag.judger.judger import *
@@ -0,0 +1,184 @@
1
+ from typing import cast, List
2
+ import json
3
+ from tqdm.auto import trange
4
+ from collections import Counter
5
+ import numpy as np
6
+ import torch
7
+ import faiss
8
+ from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
9
+ from flashrag.retriever.utils import load_model, pooling
10
+
11
+
12
+ class BaseJudger:
13
+ """Base object of Judger, used for judging whether to retrieve"""
14
+
15
+ def __init__(self, config):
16
+ self.config = config
17
+ self.name = config["judger_name"] if "judger_name" in config else None
18
+ self.judger_config = config["judger_config"] if "judger_config" in config else {}
19
+ self.device = config["device"]
20
+
21
+ def run(self, item) -> str:
22
+ """Get judgement result.
23
+
24
+ Args:
25
+ item: dataset item, contains question, retrieval result...
26
+
27
+ Returns:
28
+ judgement: bool, whether to retreive
29
+ """
30
+ pass
31
+
32
+ def batch_run(self, dataset, batch_size=None) -> List[str]:
33
+ return [self.run(item) for item in dataset]
34
+
35
+
36
+ class SKRJudger(BaseJudger):
37
+ """Implementation for SKR-knn
38
+ Paper link: https://aclanthology.org/2023.findings-emnlp.691.pdf
39
+ """
40
+
41
+ def __init__(self, config):
42
+ super().__init__(config)
43
+ self.model_path = self.judger_config["model_path"]
44
+ self.training_data_path = self.judger_config["training_data_path"]
45
+ self.encoder, self.tokenizer = load_model(model_path=self.model_path, use_fp16=False)
46
+ self.topk = self.judger_config["topk"] if "topk" in self.judger_config else 5
47
+ self.batch_size = self.judger_config["batch_size"] if "batch_size" in config else 64
48
+ self.max_length = self.judger_config["max_length"] if "max_length" in config else 128
49
+
50
+ with open(self.training_data_path, "r") as f:
51
+ self.training_data = json.load(f)
52
+ # count number of pos & neg samples in training data
53
+ self.training_data_counter = Counter([item["judgement"].strip() for item in self.training_data])
54
+ self.training_pos_num = self.training_data_counter["ir_better"]
55
+ self.training_neg_num = self.training_data_counter["ir_worse"]
56
+ self.training_data_num = sum(self.training_data_counter.values())
57
+
58
+ # encode training question into faiss
59
+ training_questions = [item["question"] for item in self.training_data]
60
+ all_embeddings = self.encode(training_questions)
61
+ faiss_index = faiss.index_factory(all_embeddings.shape[-1], "Flat", faiss.METRIC_L2)
62
+ faiss_index.add(all_embeddings)
63
+ self.faiss = faiss_index
64
+
65
+ @torch.inference_mode(mode=True)
66
+ def encode(self, contents: list):
67
+ inputs = self.tokenizer(
68
+ contents,
69
+ padding=True,
70
+ truncation=True,
71
+ return_tensors="pt",
72
+ max_length=self.max_length,
73
+ ).to("cuda")
74
+ output = self.encoder(**inputs, return_dict=True)
75
+ embeddings = pooling(output.pooler_output, output.last_hidden_state, inputs["attention_mask"], "pooler")
76
+
77
+ embeddings = cast(torch.Tensor, embeddings)
78
+ embeddings = torch.nn.functional.normalize(embeddings, dim=-1).detach()
79
+
80
+ all_embeddings = embeddings.cpu().numpy()
81
+ # all_embeddings = np.concatenate(all_embeddings, axis=0)
82
+ all_embeddings = all_embeddings.astype(np.float32)
83
+
84
+ return all_embeddings
85
+
86
+ def judge(self, dataset):
87
+ questions = dataset.question
88
+
89
+ all_judgements = []
90
+ for start_idx in range(0, len(questions), self.batch_size):
91
+ batch_question = questions[start_idx : start_idx + self.batch_size]
92
+ batch_emb = self.encode(batch_question)
93
+ scores, batch_idxs = self.faiss.search(batch_emb, k=self.topk)
94
+
95
+ for idxs in batch_idxs:
96
+ topk_samples = [self.training_data[idx]["judgement"].strip() for idx in idxs]
97
+ topk_counter = Counter(topk_samples)
98
+
99
+ # count number of pos & neg samples in topk
100
+ ir_better_num = topk_counter["ir_better"]
101
+ ir_worse_num = topk_counter["ir_worse"]
102
+ topk_delta = ir_better_num - ir_worse_num
103
+
104
+ training_data_delta = self.training_pos_num - self.training_neg_num
105
+
106
+ # provide judgments based on the formula in the paper
107
+ if training_data_delta < 0:
108
+ if topk_delta < 0 and topk_delta <= int(training_data_delta * self.topk / self.training_data_num):
109
+ judgement = False
110
+ else:
111
+ judgement = True
112
+ else:
113
+ if topk_delta > 0 and topk_delta >= int(training_data_delta * self.topk / self.training_data_num):
114
+ judgement = True
115
+ else:
116
+ judgement = False
117
+
118
+ all_judgements.append(judgement)
119
+
120
+ return all_judgements
121
+
122
+
123
+ class AdaptiveJudger(BaseJudger):
124
+ """Implementation for Adaptive-RAG
125
+ Paper link: https://aclanthology.org/2024.naacl-long.389.pdf
126
+ """
127
+
128
+ def __init__(self, config):
129
+ super().__init__(config)
130
+ self.model_path = self.judger_config["model_path"]
131
+ self.batch_size = self.judger_config.get("batch_size", 16)
132
+ self.max_length = self.judger_config.get("max_length", 512)
133
+
134
+ self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model_path)
135
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_path)
136
+ self.model.eval()
137
+ self.model.cuda()
138
+
139
+ @torch.inference_mode(mode=True)
140
+ def judge(self, dataset):
141
+ questions = dataset.question
142
+ questions = [q.strip() for q in questions]
143
+
144
+ all_preds = []
145
+ for idx in trange(0, len(questions), self.batch_size, desc="Judger process: "):
146
+ batch_input = questions[idx : idx + self.batch_size]
147
+ batch_input = self.tokenizer(
148
+ batch_input,
149
+ truncation=True,
150
+ padding=True,
151
+ max_length=512,
152
+ return_tensors="pt",
153
+ ).to(self.model.device)
154
+
155
+ scores = self.model.generate(
156
+ **batch_input, return_dict_in_generate=True, output_scores=True, max_length=self.max_length
157
+ ).scores[0]
158
+
159
+ probs = (
160
+ torch.nn.functional.softmax(
161
+ torch.stack(
162
+ [
163
+ scores[:, self.tokenizer("A").input_ids[0]],
164
+ scores[:, self.tokenizer("B").input_ids[0]],
165
+ scores[:, self.tokenizer("C").input_ids[0]],
166
+ ]
167
+ ),
168
+ dim=0,
169
+ )
170
+ .detach()
171
+ .cpu()
172
+ .numpy()
173
+ )
174
+
175
+ preds_labels = np.argmax(probs, 0)
176
+ label_to_option = {
177
+ 0: "A",
178
+ 1: "B",
179
+ 2: "C",
180
+ }
181
+ preds = [label_to_option[pred] for pred in preds_labels]
182
+ all_preds.extend(preds)
183
+
184
+ return all_preds
@@ -0,0 +1,3 @@
1
+ from flashrag.pipeline.pipeline import *
2
+ from flashrag.pipeline.branching_pipeline import REPLUGPipeline, SuRePipeline
3
+ from flashrag.pipeline.active_pipeline import IterativePipeline, SelfRAGPipeline, FLAREPipeline, SelfAskPipeline, IRCOTPipeline