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