AutoRAG 0.0.0__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.
- autorag/__init__.py +82 -0
- autorag/chunker.py +51 -0
- autorag/cli.py +209 -0
- autorag/dashboard.py +199 -0
- autorag/data/__init__.py +109 -0
- autorag/data/chunk/__init__.py +2 -0
- autorag/data/chunk/base.py +128 -0
- autorag/data/chunk/langchain_chunk.py +76 -0
- autorag/data/chunk/llama_index_chunk.py +96 -0
- autorag/data/chunk/run.py +38 -0
- autorag/data/legacy/__init__.py +0 -0
- autorag/data/legacy/corpus/__init__.py +2 -0
- autorag/data/legacy/corpus/langchain.py +47 -0
- autorag/data/legacy/corpus/llama_index.py +93 -0
- autorag/data/legacy/qacreation/__init__.py +6 -0
- autorag/data/legacy/qacreation/base.py +239 -0
- autorag/data/legacy/qacreation/llama_index.py +253 -0
- autorag/data/legacy/qacreation/llama_index_default_prompt.txt +54 -0
- autorag/data/legacy/qacreation/ragas.py +75 -0
- autorag/data/legacy/qacreation/simple.py +99 -0
- autorag/data/parse/__init__.py +1 -0
- autorag/data/parse/base.py +79 -0
- autorag/data/parse/clova.py +194 -0
- autorag/data/parse/langchain_parse.py +87 -0
- autorag/data/parse/llamaparse.py +126 -0
- autorag/data/parse/run.py +141 -0
- autorag/data/parse/table_hybrid_parse.py +134 -0
- autorag/data/qa/__init__.py +3 -0
- autorag/data/qa/evolve/__init__.py +0 -0
- autorag/data/qa/evolve/llama_index_query_evolve.py +64 -0
- autorag/data/qa/evolve/openai_query_evolve.py +81 -0
- autorag/data/qa/evolve/prompt.py +288 -0
- autorag/data/qa/extract_evidence.py +1 -0
- autorag/data/qa/filter/__init__.py +0 -0
- autorag/data/qa/filter/dontknow.py +117 -0
- autorag/data/qa/filter/passage_dependency.py +88 -0
- autorag/data/qa/filter/prompt.py +73 -0
- autorag/data/qa/generation_gt/__init__.py +0 -0
- autorag/data/qa/generation_gt/base.py +16 -0
- autorag/data/qa/generation_gt/llama_index_gen_gt.py +41 -0
- autorag/data/qa/generation_gt/openai_gen_gt.py +84 -0
- autorag/data/qa/generation_gt/prompt.py +27 -0
- autorag/data/qa/query/__init__.py +0 -0
- autorag/data/qa/query/llama_gen_query.py +82 -0
- autorag/data/qa/query/openai_gen_query.py +95 -0
- autorag/data/qa/query/prompt.py +201 -0
- autorag/data/qa/sample.py +26 -0
- autorag/data/qa/schema.py +322 -0
- autorag/data/utils/__init__.py +0 -0
- autorag/data/utils/util.py +103 -0
- autorag/deploy/__init__.py +9 -0
- autorag/deploy/api.py +303 -0
- autorag/deploy/base.py +235 -0
- autorag/deploy/gradio.py +74 -0
- autorag/deploy/swagger.yml +202 -0
- autorag/embedding/__init__.py +0 -0
- autorag/embedding/base.py +144 -0
- autorag/embedding/vllm.py +256 -0
- autorag/evaluation/__init__.py +3 -0
- autorag/evaluation/generation.py +88 -0
- autorag/evaluation/metric/__init__.py +22 -0
- autorag/evaluation/metric/deepeval_prompt.py +322 -0
- autorag/evaluation/metric/g_eval_prompts/coh_detailed.txt +32 -0
- autorag/evaluation/metric/g_eval_prompts/con_detailed.txt +33 -0
- autorag/evaluation/metric/g_eval_prompts/flu_detailed.txt +26 -0
- autorag/evaluation/metric/g_eval_prompts/rel_detailed.txt +33 -0
- autorag/evaluation/metric/generation.py +504 -0
- autorag/evaluation/metric/retrieval.py +115 -0
- autorag/evaluation/metric/retrieval_contents.py +65 -0
- autorag/evaluation/metric/util.py +88 -0
- autorag/evaluation/retrieval.py +83 -0
- autorag/evaluation/retrieval_contents.py +65 -0
- autorag/evaluation/util.py +43 -0
- autorag/evaluator.py +559 -0
- autorag/node_line.py +65 -0
- autorag/nodes/__init__.py +0 -0
- autorag/nodes/generator/__init__.py +4 -0
- autorag/nodes/generator/base.py +103 -0
- autorag/nodes/generator/llama_index_llm.py +169 -0
- autorag/nodes/generator/openai_llm.py +329 -0
- autorag/nodes/generator/run.py +148 -0
- autorag/nodes/generator/vllm.py +147 -0
- autorag/nodes/generator/vllm_api.py +191 -0
- autorag/nodes/hybridretrieval/__init__.py +2 -0
- autorag/nodes/hybridretrieval/base.py +58 -0
- autorag/nodes/hybridretrieval/hybrid_cc.py +227 -0
- autorag/nodes/hybridretrieval/hybrid_rrf.py +149 -0
- autorag/nodes/hybridretrieval/run.py +137 -0
- autorag/nodes/lexicalretrieval/__init__.py +1 -0
- autorag/nodes/lexicalretrieval/bm25.py +381 -0
- autorag/nodes/lexicalretrieval/run.py +148 -0
- autorag/nodes/passageaugmenter/__init__.py +2 -0
- autorag/nodes/passageaugmenter/base.py +76 -0
- autorag/nodes/passageaugmenter/pass_passage_augmenter.py +43 -0
- autorag/nodes/passageaugmenter/prev_next_augmenter.py +155 -0
- autorag/nodes/passageaugmenter/run.py +131 -0
- autorag/nodes/passagecompressor/__init__.py +4 -0
- autorag/nodes/passagecompressor/base.py +78 -0
- autorag/nodes/passagecompressor/longllmlingua.py +115 -0
- autorag/nodes/passagecompressor/pass_compressor.py +16 -0
- autorag/nodes/passagecompressor/refine.py +54 -0
- autorag/nodes/passagecompressor/run.py +186 -0
- autorag/nodes/passagecompressor/tree_summarize.py +56 -0
- autorag/nodes/passagefilter/__init__.py +6 -0
- autorag/nodes/passagefilter/base.py +40 -0
- autorag/nodes/passagefilter/pass_passage_filter.py +14 -0
- autorag/nodes/passagefilter/percentile_cutoff.py +58 -0
- autorag/nodes/passagefilter/recency.py +105 -0
- autorag/nodes/passagefilter/run.py +138 -0
- autorag/nodes/passagefilter/similarity_percentile_cutoff.py +134 -0
- autorag/nodes/passagefilter/similarity_threshold_cutoff.py +112 -0
- autorag/nodes/passagefilter/threshold_cutoff.py +78 -0
- autorag/nodes/passagereranker/__init__.py +16 -0
- autorag/nodes/passagereranker/base.py +44 -0
- autorag/nodes/passagereranker/cohere.py +118 -0
- autorag/nodes/passagereranker/colbert.py +213 -0
- autorag/nodes/passagereranker/flag_embedding.py +112 -0
- autorag/nodes/passagereranker/flag_embedding_llm.py +101 -0
- autorag/nodes/passagereranker/flashrank.py +245 -0
- autorag/nodes/passagereranker/jina.py +115 -0
- autorag/nodes/passagereranker/koreranker.py +136 -0
- autorag/nodes/passagereranker/mixedbreadai.py +126 -0
- autorag/nodes/passagereranker/monot5.py +190 -0
- autorag/nodes/passagereranker/openvino.py +191 -0
- autorag/nodes/passagereranker/pass_reranker.py +31 -0
- autorag/nodes/passagereranker/rankgpt.py +170 -0
- autorag/nodes/passagereranker/run.py +145 -0
- autorag/nodes/passagereranker/sentence_transformer.py +129 -0
- autorag/nodes/passagereranker/tart/__init__.py +1 -0
- autorag/nodes/passagereranker/tart/modeling_enc_t5.py +152 -0
- autorag/nodes/passagereranker/tart/tart.py +139 -0
- autorag/nodes/passagereranker/tart/tokenization_enc_t5.py +112 -0
- autorag/nodes/passagereranker/time_reranker.py +72 -0
- autorag/nodes/passagereranker/upr.py +160 -0
- autorag/nodes/passagereranker/voyageai.py +109 -0
- autorag/nodes/promptmaker/__init__.py +12 -0
- autorag/nodes/promptmaker/base.py +32 -0
- autorag/nodes/promptmaker/chat_fstring.py +73 -0
- autorag/nodes/promptmaker/fstring.py +49 -0
- autorag/nodes/promptmaker/long_context_reorder.py +83 -0
- autorag/nodes/promptmaker/run.py +283 -0
- autorag/nodes/promptmaker/window_replacement.py +85 -0
- autorag/nodes/queryexpansion/__init__.py +4 -0
- autorag/nodes/queryexpansion/base.py +62 -0
- autorag/nodes/queryexpansion/hyde.py +43 -0
- autorag/nodes/queryexpansion/multi_query_expansion.py +57 -0
- autorag/nodes/queryexpansion/pass_query_expansion.py +22 -0
- autorag/nodes/queryexpansion/query_decompose.py +111 -0
- autorag/nodes/queryexpansion/run.py +308 -0
- autorag/nodes/retrieval/__init__.py +0 -0
- autorag/nodes/retrieval/base.py +127 -0
- autorag/nodes/retrieval/run_util.py +152 -0
- autorag/nodes/semanticretrieval/__init__.py +1 -0
- autorag/nodes/semanticretrieval/run.py +148 -0
- autorag/nodes/semanticretrieval/vectordb.py +339 -0
- autorag/nodes/util.py +16 -0
- autorag/parser.py +37 -0
- autorag/schema/__init__.py +3 -0
- autorag/schema/base.py +35 -0
- autorag/schema/metricinput.py +99 -0
- autorag/schema/module.py +24 -0
- autorag/schema/node.py +144 -0
- autorag/strategy.py +165 -0
- autorag/support.py +235 -0
- autorag/utils/__init__.py +8 -0
- autorag/utils/cast.py +45 -0
- autorag/utils/preprocess.py +149 -0
- autorag/utils/util.py +759 -0
- autorag/validator.py +98 -0
- autorag/vectordb/__init__.py +75 -0
- autorag/vectordb/base.py +73 -0
- autorag/vectordb/chroma.py +118 -0
- autorag/vectordb/couchbase.py +239 -0
- autorag/vectordb/milvus.py +169 -0
- autorag/vectordb/pinecone.py +121 -0
- autorag/vectordb/qdrant.py +155 -0
- autorag/vectordb/weaviate.py +184 -0
- autorag/web.py +81 -0
- autorag-0.0.0.dist-info/METADATA +780 -0
- autorag-0.0.0.dist-info/RECORD +184 -0
- autorag-0.0.0.dist-info/WHEEL +5 -0
- autorag-0.0.0.dist-info/entry_points.txt +2 -0
- autorag-0.0.0.dist-info/licenses/LICENSE +201 -0
- autorag-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
from typing import List, Dict, Union
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from autorag.evaluation import evaluate_generation
|
|
8
|
+
from autorag.evaluation.util import cast_metrics
|
|
9
|
+
from autorag.schema.metricinput import MetricInput
|
|
10
|
+
from autorag.strategy import measure_speed, filter_by_threshold, select_best
|
|
11
|
+
from autorag.utils.util import to_list
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_generator_node(
|
|
15
|
+
modules: List,
|
|
16
|
+
module_params: List[Dict],
|
|
17
|
+
previous_result: pd.DataFrame,
|
|
18
|
+
node_line_dir: str,
|
|
19
|
+
strategies: Dict,
|
|
20
|
+
) -> pd.DataFrame:
|
|
21
|
+
"""
|
|
22
|
+
Run evaluation and select the best module among generator node results.
|
|
23
|
+
And save the results and summary to generator node directory.
|
|
24
|
+
|
|
25
|
+
:param modules: Generator modules to run.
|
|
26
|
+
:param module_params: Generator module parameters.
|
|
27
|
+
Including node parameters, which is used for every module in this node.
|
|
28
|
+
:param previous_result: Previous result dataframe.
|
|
29
|
+
Could be prompt maker node's result.
|
|
30
|
+
:param node_line_dir: This node line's directory.
|
|
31
|
+
:param strategies: Strategies for generator node.
|
|
32
|
+
:return: The best result dataframe.
|
|
33
|
+
It contains previous result columns and generator node's result columns.
|
|
34
|
+
"""
|
|
35
|
+
if not os.path.exists(node_line_dir):
|
|
36
|
+
os.makedirs(node_line_dir)
|
|
37
|
+
project_dir = pathlib.PurePath(node_line_dir).parent.parent
|
|
38
|
+
node_dir = os.path.join(node_line_dir, "generator") # node name
|
|
39
|
+
if not os.path.exists(node_dir):
|
|
40
|
+
os.makedirs(node_dir)
|
|
41
|
+
qa_data = pd.read_parquet(
|
|
42
|
+
os.path.join(project_dir, "data", "qa.parquet"), engine="pyarrow"
|
|
43
|
+
)
|
|
44
|
+
if "generation_gt" not in qa_data.columns:
|
|
45
|
+
raise ValueError("You must have 'generation_gt' column in qa.parquet.")
|
|
46
|
+
|
|
47
|
+
results, execution_times = zip(
|
|
48
|
+
*map(
|
|
49
|
+
lambda x: measure_speed(
|
|
50
|
+
x[0].run_evaluator,
|
|
51
|
+
project_dir=project_dir,
|
|
52
|
+
previous_result=previous_result,
|
|
53
|
+
**x[1],
|
|
54
|
+
),
|
|
55
|
+
zip(modules, module_params),
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
average_times = list(map(lambda x: x / len(results[0]), execution_times))
|
|
59
|
+
|
|
60
|
+
# get average token usage
|
|
61
|
+
token_usages = list(map(lambda x: x["generated_tokens"].apply(len).mean(), results))
|
|
62
|
+
|
|
63
|
+
# make rows to metric_inputs
|
|
64
|
+
generation_gt = to_list(qa_data["generation_gt"].tolist())
|
|
65
|
+
queries = to_list(qa_data["query"].tolist())
|
|
66
|
+
|
|
67
|
+
metric_inputs = [
|
|
68
|
+
MetricInput(generation_gt=gen_gt, query=query)
|
|
69
|
+
for gen_gt, query in zip(generation_gt, queries)
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
metric_names, metric_params = cast_metrics(strategies.get("metrics"))
|
|
73
|
+
if metric_names is None or len(metric_names) <= 0:
|
|
74
|
+
raise ValueError("You must at least one metrics for generator evaluation.")
|
|
75
|
+
results = list(
|
|
76
|
+
map(
|
|
77
|
+
lambda result: evaluate_generator_node(
|
|
78
|
+
result, metric_inputs, strategies.get("metrics")
|
|
79
|
+
),
|
|
80
|
+
results,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# save results to folder
|
|
85
|
+
filepaths = list(
|
|
86
|
+
map(lambda x: os.path.join(node_dir, f"{x}.parquet"), range(len(modules)))
|
|
87
|
+
)
|
|
88
|
+
list(
|
|
89
|
+
map(lambda x: x[0].to_parquet(x[1], index=False), zip(results, filepaths))
|
|
90
|
+
) # execute save to parquet
|
|
91
|
+
filenames = list(map(lambda x: os.path.basename(x), filepaths))
|
|
92
|
+
|
|
93
|
+
summary_df = pd.DataFrame(
|
|
94
|
+
{
|
|
95
|
+
"filename": filenames,
|
|
96
|
+
"module_name": list(map(lambda module: module.__name__, modules)),
|
|
97
|
+
"module_params": module_params,
|
|
98
|
+
"execution_time": average_times,
|
|
99
|
+
"average_output_token": token_usages,
|
|
100
|
+
**{
|
|
101
|
+
metric: list(map(lambda x: x[metric].mean(), results))
|
|
102
|
+
for metric in metric_names
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# filter by strategies
|
|
108
|
+
if strategies.get("speed_threshold") is not None:
|
|
109
|
+
results, filenames = filter_by_threshold(
|
|
110
|
+
results, average_times, strategies["speed_threshold"], filenames
|
|
111
|
+
)
|
|
112
|
+
if strategies.get("token_threshold") is not None:
|
|
113
|
+
results, filenames = filter_by_threshold(
|
|
114
|
+
results, token_usages, strategies["token_threshold"], filenames
|
|
115
|
+
)
|
|
116
|
+
selected_result, selected_filename = select_best(
|
|
117
|
+
results, metric_names, filenames, strategies.get("strategy", "mean")
|
|
118
|
+
)
|
|
119
|
+
best_result = pd.concat([previous_result, selected_result], axis=1)
|
|
120
|
+
|
|
121
|
+
# add 'is_best' column at summary file
|
|
122
|
+
summary_df["is_best"] = summary_df["filename"] == selected_filename
|
|
123
|
+
|
|
124
|
+
# save files
|
|
125
|
+
summary_df.to_csv(os.path.join(node_dir, "summary.csv"), index=False)
|
|
126
|
+
best_result.to_parquet(
|
|
127
|
+
os.path.join(
|
|
128
|
+
node_dir, f"best_{os.path.splitext(selected_filename)[0]}.parquet"
|
|
129
|
+
),
|
|
130
|
+
index=False,
|
|
131
|
+
)
|
|
132
|
+
return best_result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def evaluate_generator_node(
|
|
136
|
+
result_df: pd.DataFrame,
|
|
137
|
+
metric_inputs: List[MetricInput],
|
|
138
|
+
metrics: Union[List[str], List[Dict]],
|
|
139
|
+
):
|
|
140
|
+
@evaluate_generation(metric_inputs=metric_inputs, metrics=metrics)
|
|
141
|
+
def evaluate_generation_module(df: pd.DataFrame):
|
|
142
|
+
return (
|
|
143
|
+
df["generated_texts"].tolist(),
|
|
144
|
+
df["generated_tokens"].tolist(),
|
|
145
|
+
df["generated_log_probs"].tolist(),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return evaluate_generation_module(result_df)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import gc
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from typing import List, Tuple, Union
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from autorag.nodes.generator.base import BaseGenerator
|
|
8
|
+
from autorag.utils import result_to_dataframe
|
|
9
|
+
from autorag.utils.util import pop_params, to_list, is_chat_prompt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Vllm(BaseGenerator):
|
|
13
|
+
def __init__(self, project_dir: str, llm: str, **kwargs):
|
|
14
|
+
super().__init__(project_dir, llm, **kwargs)
|
|
15
|
+
try:
|
|
16
|
+
from vllm import SamplingParams, LLM
|
|
17
|
+
except ImportError:
|
|
18
|
+
raise ImportError(
|
|
19
|
+
"Please install vllm library. You can install it by running `pip install vllm`."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
model_from_kwargs = kwargs.pop("model", None)
|
|
23
|
+
model = llm if model_from_kwargs is None else model_from_kwargs
|
|
24
|
+
|
|
25
|
+
input_kwargs = deepcopy(kwargs)
|
|
26
|
+
sampling_params_init_params = pop_params(
|
|
27
|
+
SamplingParams.from_optional, input_kwargs
|
|
28
|
+
)
|
|
29
|
+
input_kwargs.pop("thinking", None)
|
|
30
|
+
self.vllm_model = LLM(model, **input_kwargs)
|
|
31
|
+
|
|
32
|
+
# delete not sampling param keys in the kwargs
|
|
33
|
+
kwargs_keys = list(kwargs.keys())
|
|
34
|
+
for key in kwargs_keys:
|
|
35
|
+
if key not in sampling_params_init_params:
|
|
36
|
+
kwargs.pop(key)
|
|
37
|
+
|
|
38
|
+
def __del__(self):
|
|
39
|
+
try:
|
|
40
|
+
import torch
|
|
41
|
+
import contextlib
|
|
42
|
+
|
|
43
|
+
if torch.cuda.is_available():
|
|
44
|
+
from vllm.distributed.parallel_state import (
|
|
45
|
+
destroy_model_parallel,
|
|
46
|
+
destroy_distributed_environment,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
destroy_model_parallel()
|
|
50
|
+
destroy_distributed_environment()
|
|
51
|
+
if hasattr(self.vllm_model.llm_engine, "model_executor"):
|
|
52
|
+
del self.vllm_model.llm_engine.model_executor
|
|
53
|
+
del self.vllm_model
|
|
54
|
+
with contextlib.suppress(AssertionError):
|
|
55
|
+
torch.distributed.destroy_process_group()
|
|
56
|
+
gc.collect()
|
|
57
|
+
torch.cuda.empty_cache()
|
|
58
|
+
torch.cuda.synchronize()
|
|
59
|
+
except ImportError:
|
|
60
|
+
del self.vllm_model
|
|
61
|
+
|
|
62
|
+
super().__del__()
|
|
63
|
+
|
|
64
|
+
@result_to_dataframe(["generated_texts", "generated_tokens", "generated_log_probs"])
|
|
65
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
66
|
+
prompts = self.cast_to_run(previous_result)
|
|
67
|
+
thinking = kwargs.pop("thinking", False)
|
|
68
|
+
return self._pure(prompts, thinking=thinking, **kwargs)
|
|
69
|
+
|
|
70
|
+
def _pure(
|
|
71
|
+
self,
|
|
72
|
+
prompts: Union[List[str], List[List[dict]]],
|
|
73
|
+
thinking: bool = False,
|
|
74
|
+
**kwargs,
|
|
75
|
+
) -> Tuple[List[str], List[List[int]], List[List[float]]]:
|
|
76
|
+
"""
|
|
77
|
+
Vllm module.
|
|
78
|
+
It gets the VLLM instance and returns generated texts by the input prompt.
|
|
79
|
+
You can set logprobs to get the log probs of the generated text.
|
|
80
|
+
Default logprobs is 1.
|
|
81
|
+
|
|
82
|
+
:param prompts: A list of prompts or a list of chat prompts.
|
|
83
|
+
:param thinking: A boolean that indicates whether to think when generating text.
|
|
84
|
+
Default is False.
|
|
85
|
+
Effective when set True and using chat prompts.
|
|
86
|
+
You can learn how to use chat prompt at `chat_fstring` module documentation.
|
|
87
|
+
:param kwargs: The extra parameters for generating the text.
|
|
88
|
+
:return: A tuple of three elements.
|
|
89
|
+
The first element is a list of generated text.
|
|
90
|
+
The second element is a list of generated text's token ids.
|
|
91
|
+
The third element is a list of generated text's log probs.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
from vllm.outputs import RequestOutput
|
|
95
|
+
from vllm import SamplingParams, LLM
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
from vllm.logprobs import SampleLogprobs # vLLM >= 0.11
|
|
99
|
+
except ImportError: # pragma: no cover - older vLLM fallback
|
|
100
|
+
from vllm.sequence import SampleLogprobs
|
|
101
|
+
except ImportError:
|
|
102
|
+
raise ImportError(
|
|
103
|
+
"Please install vllm library. You can install it by running `pip install vllm`."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if "logprobs" not in kwargs:
|
|
107
|
+
kwargs["logprobs"] = 1
|
|
108
|
+
|
|
109
|
+
sampling_params = pop_params(SamplingParams.from_optional, kwargs)
|
|
110
|
+
generate_params = SamplingParams(**sampling_params)
|
|
111
|
+
if is_chat_prompt(prompts):
|
|
112
|
+
chat_template_kwargs = kwargs.pop("chat_template_kwargs", {})
|
|
113
|
+
chat_template_kwargs["enable_thinking"] = thinking
|
|
114
|
+
chat_kwargs = pop_params(LLM.chat, kwargs)
|
|
115
|
+
results: List[RequestOutput] = self.vllm_model.chat(
|
|
116
|
+
prompts,
|
|
117
|
+
generate_params,
|
|
118
|
+
chat_template_kwargs=chat_template_kwargs,
|
|
119
|
+
**chat_kwargs,
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
generate_kwargs = pop_params(LLM.generate, kwargs)
|
|
123
|
+
results: List[RequestOutput] = self.vllm_model.generate(
|
|
124
|
+
prompts, generate_params, **generate_kwargs
|
|
125
|
+
)
|
|
126
|
+
generated_texts = list(map(lambda x: x.outputs[0].text, results))
|
|
127
|
+
generated_token_ids = list(map(lambda x: x.outputs[0].token_ids, results))
|
|
128
|
+
log_probs: List[SampleLogprobs] = list(
|
|
129
|
+
map(lambda x: x.outputs[0].logprobs, results)
|
|
130
|
+
)
|
|
131
|
+
generated_log_probs = list(
|
|
132
|
+
map(
|
|
133
|
+
lambda x: list(map(lambda y: y[0][y[1]].logprob, zip(x[0], x[1]))),
|
|
134
|
+
zip(log_probs, generated_token_ids),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
return (
|
|
138
|
+
to_list(generated_texts),
|
|
139
|
+
to_list(generated_token_ids),
|
|
140
|
+
to_list(generated_log_probs),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
async def astream(self, prompt: str, **kwargs):
|
|
144
|
+
raise NotImplementedError
|
|
145
|
+
|
|
146
|
+
def stream(self, prompt: str, **kwargs):
|
|
147
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List, Tuple, Dict, Union
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import requests
|
|
7
|
+
from asyncio import to_thread
|
|
8
|
+
|
|
9
|
+
from autorag.nodes.generator.base import BaseGenerator
|
|
10
|
+
from autorag.utils.util import get_event_loop, process_batch, result_to_dataframe
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("AutoRAG")
|
|
13
|
+
|
|
14
|
+
DEFAULT_MAX_TOKENS = 4096 # Default token limit
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VllmAPI(BaseGenerator):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
project_dir,
|
|
21
|
+
llm: str,
|
|
22
|
+
uri: str,
|
|
23
|
+
max_tokens: int = None,
|
|
24
|
+
batch: int = 16,
|
|
25
|
+
*args,
|
|
26
|
+
**kwargs,
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
VLLM API Wrapper for OpenAI-compatible chat/completions format.
|
|
30
|
+
|
|
31
|
+
:param project_dir: Project directory.
|
|
32
|
+
:param llm: Model name (e.g., LLaMA model).
|
|
33
|
+
:param uri: VLLM API server URI.
|
|
34
|
+
:param max_tokens: Maximum token limit.
|
|
35
|
+
Default is 4096.
|
|
36
|
+
:param batch: Request batch size.
|
|
37
|
+
Default is 16.
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(project_dir, llm, *args, **kwargs)
|
|
40
|
+
assert batch > 0, "Batch size must be greater than 0."
|
|
41
|
+
self.uri = uri.rstrip("/") # Set API URI
|
|
42
|
+
self.batch = batch
|
|
43
|
+
# Use the provided max_tokens if available, otherwise use the default
|
|
44
|
+
self.max_token_size = max_tokens if max_tokens else DEFAULT_MAX_TOKENS
|
|
45
|
+
self.max_model_len = self.get_max_model_length()
|
|
46
|
+
logger.info(f"{llm} max model length: {self.max_model_len}")
|
|
47
|
+
|
|
48
|
+
@result_to_dataframe(["generated_texts", "generated_tokens", "generated_log_probs"])
|
|
49
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
50
|
+
prompts = self.cast_to_run(previous_result)
|
|
51
|
+
return self._pure(prompts, **kwargs)
|
|
52
|
+
|
|
53
|
+
def _pure(
|
|
54
|
+
self,
|
|
55
|
+
prompts: Union[List[str], List[List[Dict]]],
|
|
56
|
+
truncate: bool = True,
|
|
57
|
+
**kwargs,
|
|
58
|
+
) -> Tuple[List[str], List[List[int]], List[List[float]]]:
|
|
59
|
+
"""
|
|
60
|
+
Method to call the VLLM API to generate text.
|
|
61
|
+
|
|
62
|
+
:param prompts: List of input prompts.
|
|
63
|
+
:param truncate: Whether to truncate input prompts to fit within the token limit.
|
|
64
|
+
:param kwargs: Additional options (e.g., temperature, top_p).
|
|
65
|
+
:return: Generated text, token lists, and log probability lists.
|
|
66
|
+
"""
|
|
67
|
+
if kwargs.get("logprobs") is not None:
|
|
68
|
+
kwargs.pop("logprobs")
|
|
69
|
+
logger.warning(
|
|
70
|
+
"parameter logprob does not effective. It always set to True."
|
|
71
|
+
)
|
|
72
|
+
if kwargs.get("n") is not None:
|
|
73
|
+
kwargs.pop("n")
|
|
74
|
+
logger.warning("parameter n does not effective. It always set to 1.")
|
|
75
|
+
|
|
76
|
+
if truncate:
|
|
77
|
+
prompts = list(map(lambda p: self.truncate_by_token(p), prompts))
|
|
78
|
+
loop = get_event_loop()
|
|
79
|
+
tasks = [to_thread(self.get_result, prompt, **kwargs) for prompt in prompts]
|
|
80
|
+
results = loop.run_until_complete(process_batch(tasks, self.batch))
|
|
81
|
+
|
|
82
|
+
answer_result = list(map(lambda x: x[0], results))
|
|
83
|
+
token_result = list(map(lambda x: x[1], results))
|
|
84
|
+
logprob_result = list(map(lambda x: x[2], results))
|
|
85
|
+
return answer_result, token_result, logprob_result
|
|
86
|
+
|
|
87
|
+
def truncate_by_token(self, prompt: Union[str, List[Dict]]) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Function to truncate prompts to fit within the maximum token limit.
|
|
90
|
+
"""
|
|
91
|
+
if not isinstance(prompt, str):
|
|
92
|
+
content_list = [msg["content"] for msg in prompt]
|
|
93
|
+
prompt = "\n".join(content_list)
|
|
94
|
+
tokens = self.encoding_for_model(prompt)["tokens"] # Simple tokenization
|
|
95
|
+
return self.decoding_for_model(tokens[: self.max_model_len])["prompt"]
|
|
96
|
+
|
|
97
|
+
def call_vllm_api(self, prompt: Union[str, List[Dict]], **kwargs) -> dict:
|
|
98
|
+
"""
|
|
99
|
+
Calls the VLLM API to get chat/completions responses.
|
|
100
|
+
|
|
101
|
+
:param prompt: Input prompt.
|
|
102
|
+
:param kwargs: Additional API options (e.g., temperature, max_tokens).
|
|
103
|
+
:return: API response.
|
|
104
|
+
"""
|
|
105
|
+
payload = {
|
|
106
|
+
"model": self.llm,
|
|
107
|
+
"messages": parse_prompt(prompt),
|
|
108
|
+
"temperature": kwargs.get("temperature", 0.4),
|
|
109
|
+
"max_tokens": min(
|
|
110
|
+
kwargs.get("max_tokens", self.max_token_size), self.max_token_size
|
|
111
|
+
),
|
|
112
|
+
"logprobs": True,
|
|
113
|
+
"n": 1,
|
|
114
|
+
}
|
|
115
|
+
start_time = time.time() # Record request start time
|
|
116
|
+
response = requests.post(f"{self.uri}/v1/chat/completions", json=payload)
|
|
117
|
+
end_time = time.time() # Record request end time
|
|
118
|
+
|
|
119
|
+
response.raise_for_status()
|
|
120
|
+
elapsed_time = end_time - start_time # Calculate elapsed time
|
|
121
|
+
logger.info(
|
|
122
|
+
f"Request chat completions to vllm server completed in {elapsed_time:.2f} seconds"
|
|
123
|
+
)
|
|
124
|
+
return response.json()
|
|
125
|
+
|
|
126
|
+
# Additional method: abstract method implementation
|
|
127
|
+
async def astream(self, prompt: Union[str, List[Dict]], **kwargs):
|
|
128
|
+
"""
|
|
129
|
+
Asynchronous streaming method not implemented.
|
|
130
|
+
"""
|
|
131
|
+
raise NotImplementedError("astream method is not implemented for VLLM API yet.")
|
|
132
|
+
|
|
133
|
+
def stream(self, prompt: Union[str, List[Dict]], **kwargs):
|
|
134
|
+
"""
|
|
135
|
+
Synchronous streaming method not implemented.
|
|
136
|
+
"""
|
|
137
|
+
raise NotImplementedError("stream method is not implemented for VLLM API yet.")
|
|
138
|
+
|
|
139
|
+
def get_result(self, prompt: Union[str, List[Dict]], **kwargs):
|
|
140
|
+
response = self.call_vllm_api(prompt, **kwargs)
|
|
141
|
+
choice = response["choices"][0]
|
|
142
|
+
answer = choice["message"]["content"]
|
|
143
|
+
|
|
144
|
+
# Handle cases where logprobs is None
|
|
145
|
+
if choice.get("logprobs") and "content" in choice["logprobs"]:
|
|
146
|
+
logprobs = list(map(lambda x: x["logprob"], choice["logprobs"]["content"]))
|
|
147
|
+
tokens = list(
|
|
148
|
+
map(
|
|
149
|
+
lambda x: self.encoding_for_model(x["token"])["tokens"],
|
|
150
|
+
choice["logprobs"]["content"],
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
logprobs = []
|
|
155
|
+
tokens = []
|
|
156
|
+
|
|
157
|
+
return answer, tokens, logprobs
|
|
158
|
+
|
|
159
|
+
def encoding_for_model(self, answer_piece: str):
|
|
160
|
+
payload = {
|
|
161
|
+
"model": self.llm,
|
|
162
|
+
"prompt": answer_piece,
|
|
163
|
+
"add_special_tokens": True,
|
|
164
|
+
}
|
|
165
|
+
response = requests.post(f"{self.uri}/tokenize", json=payload)
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
return response.json()
|
|
168
|
+
|
|
169
|
+
def decoding_for_model(self, tokens: list[int]):
|
|
170
|
+
payload = {
|
|
171
|
+
"model": self.llm,
|
|
172
|
+
"tokens": tokens,
|
|
173
|
+
}
|
|
174
|
+
response = requests.post(f"{self.uri}/detokenize", json=payload)
|
|
175
|
+
response.raise_for_status()
|
|
176
|
+
return response.json()
|
|
177
|
+
|
|
178
|
+
def get_max_model_length(self):
|
|
179
|
+
response = requests.get(f"{self.uri}/v1/models")
|
|
180
|
+
response.raise_for_status()
|
|
181
|
+
json_data = response.json()
|
|
182
|
+
return json_data["data"][0]["max_model_len"]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def parse_prompt(prompt: Union[str, List[Dict]]) -> List[Dict]:
|
|
186
|
+
if isinstance(prompt, str):
|
|
187
|
+
return [{"role": "user", "content": prompt}]
|
|
188
|
+
elif isinstance(prompt, list):
|
|
189
|
+
return prompt
|
|
190
|
+
else:
|
|
191
|
+
raise ValueError("prompt must be a string or a list of dicts.")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from autorag.nodes.retrieval.base import BaseRetrieval
|
|
6
|
+
from autorag.utils import result_to_dataframe
|
|
7
|
+
from autorag.utils.util import pop_params, fetch_contents
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HybridRetrieval(BaseRetrieval, metaclass=abc.ABCMeta):
|
|
11
|
+
def __init__(self, project_dir: str, *args, **kwargs):
|
|
12
|
+
super().__init__(project_dir)
|
|
13
|
+
|
|
14
|
+
@result_to_dataframe(["retrieved_contents", "retrieved_ids", "retrieve_scores"])
|
|
15
|
+
def pure(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
16
|
+
previous_info = self.cast_to_run(previous_result, *args, **kwargs)
|
|
17
|
+
_pure_params = pop_params(self._pure, kwargs)
|
|
18
|
+
ids, scores = self._pure(previous_info, **_pure_params)
|
|
19
|
+
contents = fetch_contents(self.corpus_df, ids)
|
|
20
|
+
return contents, ids, scores
|
|
21
|
+
|
|
22
|
+
def cast_to_run(self, previous_result: pd.DataFrame, *args, **kwargs):
|
|
23
|
+
return hybrid_cast(previous_result)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def cast_to_run_class(cls, previous_result: pd.DataFrame):
|
|
27
|
+
return hybrid_cast(previous_result)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def hybrid_cast(
|
|
31
|
+
previous_result: pd.DataFrame,
|
|
32
|
+
):
|
|
33
|
+
assert "query" in previous_result.columns, "previous_result must have query column."
|
|
34
|
+
queries = previous_result["query"].tolist()
|
|
35
|
+
|
|
36
|
+
assert "retrieved_contents_semantic" in previous_result.columns
|
|
37
|
+
assert "retrieved_contents_lexical" in previous_result.columns
|
|
38
|
+
assert "retrieve_scores_semantic" in previous_result.columns
|
|
39
|
+
assert "retrieve_scores_lexical" in previous_result.columns
|
|
40
|
+
assert "retrieved_ids_semantic" in previous_result.columns
|
|
41
|
+
assert "retrieved_ids_lexical" in previous_result.columns
|
|
42
|
+
|
|
43
|
+
contents_semantic = previous_result["retrieved_contents_semantic"].tolist()
|
|
44
|
+
contents_lexical = previous_result["retrieved_contents_lexical"].tolist()
|
|
45
|
+
scores_semantic = previous_result["retrieve_scores_semantic"].tolist()
|
|
46
|
+
scores_lexical = previous_result["retrieve_scores_lexical"].tolist()
|
|
47
|
+
ids_semantic = previous_result["retrieved_ids_semantic"].tolist()
|
|
48
|
+
ids_lexical = previous_result["retrieved_ids_lexical"].tolist()
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
"queries": queries,
|
|
52
|
+
"retrieved_contents_semantic": contents_semantic,
|
|
53
|
+
"retrieved_contents_lexical": contents_lexical,
|
|
54
|
+
"retrieve_scores_semantic": scores_semantic,
|
|
55
|
+
"retrieve_scores_lexical": scores_lexical,
|
|
56
|
+
"retrieved_ids_semantic": ids_semantic,
|
|
57
|
+
"retrieved_ids_lexical": ids_lexical,
|
|
58
|
+
}
|