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.
Files changed (184) hide show
  1. autorag/__init__.py +82 -0
  2. autorag/chunker.py +51 -0
  3. autorag/cli.py +209 -0
  4. autorag/dashboard.py +199 -0
  5. autorag/data/__init__.py +109 -0
  6. autorag/data/chunk/__init__.py +2 -0
  7. autorag/data/chunk/base.py +128 -0
  8. autorag/data/chunk/langchain_chunk.py +76 -0
  9. autorag/data/chunk/llama_index_chunk.py +96 -0
  10. autorag/data/chunk/run.py +38 -0
  11. autorag/data/legacy/__init__.py +0 -0
  12. autorag/data/legacy/corpus/__init__.py +2 -0
  13. autorag/data/legacy/corpus/langchain.py +47 -0
  14. autorag/data/legacy/corpus/llama_index.py +93 -0
  15. autorag/data/legacy/qacreation/__init__.py +6 -0
  16. autorag/data/legacy/qacreation/base.py +239 -0
  17. autorag/data/legacy/qacreation/llama_index.py +253 -0
  18. autorag/data/legacy/qacreation/llama_index_default_prompt.txt +54 -0
  19. autorag/data/legacy/qacreation/ragas.py +75 -0
  20. autorag/data/legacy/qacreation/simple.py +99 -0
  21. autorag/data/parse/__init__.py +1 -0
  22. autorag/data/parse/base.py +79 -0
  23. autorag/data/parse/clova.py +194 -0
  24. autorag/data/parse/langchain_parse.py +87 -0
  25. autorag/data/parse/llamaparse.py +126 -0
  26. autorag/data/parse/run.py +141 -0
  27. autorag/data/parse/table_hybrid_parse.py +134 -0
  28. autorag/data/qa/__init__.py +3 -0
  29. autorag/data/qa/evolve/__init__.py +0 -0
  30. autorag/data/qa/evolve/llama_index_query_evolve.py +64 -0
  31. autorag/data/qa/evolve/openai_query_evolve.py +81 -0
  32. autorag/data/qa/evolve/prompt.py +288 -0
  33. autorag/data/qa/extract_evidence.py +1 -0
  34. autorag/data/qa/filter/__init__.py +0 -0
  35. autorag/data/qa/filter/dontknow.py +117 -0
  36. autorag/data/qa/filter/passage_dependency.py +88 -0
  37. autorag/data/qa/filter/prompt.py +73 -0
  38. autorag/data/qa/generation_gt/__init__.py +0 -0
  39. autorag/data/qa/generation_gt/base.py +16 -0
  40. autorag/data/qa/generation_gt/llama_index_gen_gt.py +41 -0
  41. autorag/data/qa/generation_gt/openai_gen_gt.py +84 -0
  42. autorag/data/qa/generation_gt/prompt.py +27 -0
  43. autorag/data/qa/query/__init__.py +0 -0
  44. autorag/data/qa/query/llama_gen_query.py +82 -0
  45. autorag/data/qa/query/openai_gen_query.py +95 -0
  46. autorag/data/qa/query/prompt.py +201 -0
  47. autorag/data/qa/sample.py +26 -0
  48. autorag/data/qa/schema.py +322 -0
  49. autorag/data/utils/__init__.py +0 -0
  50. autorag/data/utils/util.py +103 -0
  51. autorag/deploy/__init__.py +9 -0
  52. autorag/deploy/api.py +303 -0
  53. autorag/deploy/base.py +235 -0
  54. autorag/deploy/gradio.py +74 -0
  55. autorag/deploy/swagger.yml +202 -0
  56. autorag/embedding/__init__.py +0 -0
  57. autorag/embedding/base.py +144 -0
  58. autorag/embedding/vllm.py +256 -0
  59. autorag/evaluation/__init__.py +3 -0
  60. autorag/evaluation/generation.py +88 -0
  61. autorag/evaluation/metric/__init__.py +22 -0
  62. autorag/evaluation/metric/deepeval_prompt.py +322 -0
  63. autorag/evaluation/metric/g_eval_prompts/coh_detailed.txt +32 -0
  64. autorag/evaluation/metric/g_eval_prompts/con_detailed.txt +33 -0
  65. autorag/evaluation/metric/g_eval_prompts/flu_detailed.txt +26 -0
  66. autorag/evaluation/metric/g_eval_prompts/rel_detailed.txt +33 -0
  67. autorag/evaluation/metric/generation.py +504 -0
  68. autorag/evaluation/metric/retrieval.py +115 -0
  69. autorag/evaluation/metric/retrieval_contents.py +65 -0
  70. autorag/evaluation/metric/util.py +88 -0
  71. autorag/evaluation/retrieval.py +83 -0
  72. autorag/evaluation/retrieval_contents.py +65 -0
  73. autorag/evaluation/util.py +43 -0
  74. autorag/evaluator.py +559 -0
  75. autorag/node_line.py +65 -0
  76. autorag/nodes/__init__.py +0 -0
  77. autorag/nodes/generator/__init__.py +4 -0
  78. autorag/nodes/generator/base.py +103 -0
  79. autorag/nodes/generator/llama_index_llm.py +169 -0
  80. autorag/nodes/generator/openai_llm.py +329 -0
  81. autorag/nodes/generator/run.py +148 -0
  82. autorag/nodes/generator/vllm.py +147 -0
  83. autorag/nodes/generator/vllm_api.py +191 -0
  84. autorag/nodes/hybridretrieval/__init__.py +2 -0
  85. autorag/nodes/hybridretrieval/base.py +58 -0
  86. autorag/nodes/hybridretrieval/hybrid_cc.py +227 -0
  87. autorag/nodes/hybridretrieval/hybrid_rrf.py +149 -0
  88. autorag/nodes/hybridretrieval/run.py +137 -0
  89. autorag/nodes/lexicalretrieval/__init__.py +1 -0
  90. autorag/nodes/lexicalretrieval/bm25.py +381 -0
  91. autorag/nodes/lexicalretrieval/run.py +148 -0
  92. autorag/nodes/passageaugmenter/__init__.py +2 -0
  93. autorag/nodes/passageaugmenter/base.py +76 -0
  94. autorag/nodes/passageaugmenter/pass_passage_augmenter.py +43 -0
  95. autorag/nodes/passageaugmenter/prev_next_augmenter.py +155 -0
  96. autorag/nodes/passageaugmenter/run.py +131 -0
  97. autorag/nodes/passagecompressor/__init__.py +4 -0
  98. autorag/nodes/passagecompressor/base.py +78 -0
  99. autorag/nodes/passagecompressor/longllmlingua.py +115 -0
  100. autorag/nodes/passagecompressor/pass_compressor.py +16 -0
  101. autorag/nodes/passagecompressor/refine.py +54 -0
  102. autorag/nodes/passagecompressor/run.py +186 -0
  103. autorag/nodes/passagecompressor/tree_summarize.py +56 -0
  104. autorag/nodes/passagefilter/__init__.py +6 -0
  105. autorag/nodes/passagefilter/base.py +40 -0
  106. autorag/nodes/passagefilter/pass_passage_filter.py +14 -0
  107. autorag/nodes/passagefilter/percentile_cutoff.py +58 -0
  108. autorag/nodes/passagefilter/recency.py +105 -0
  109. autorag/nodes/passagefilter/run.py +138 -0
  110. autorag/nodes/passagefilter/similarity_percentile_cutoff.py +134 -0
  111. autorag/nodes/passagefilter/similarity_threshold_cutoff.py +112 -0
  112. autorag/nodes/passagefilter/threshold_cutoff.py +78 -0
  113. autorag/nodes/passagereranker/__init__.py +16 -0
  114. autorag/nodes/passagereranker/base.py +44 -0
  115. autorag/nodes/passagereranker/cohere.py +118 -0
  116. autorag/nodes/passagereranker/colbert.py +213 -0
  117. autorag/nodes/passagereranker/flag_embedding.py +112 -0
  118. autorag/nodes/passagereranker/flag_embedding_llm.py +101 -0
  119. autorag/nodes/passagereranker/flashrank.py +245 -0
  120. autorag/nodes/passagereranker/jina.py +115 -0
  121. autorag/nodes/passagereranker/koreranker.py +136 -0
  122. autorag/nodes/passagereranker/mixedbreadai.py +126 -0
  123. autorag/nodes/passagereranker/monot5.py +190 -0
  124. autorag/nodes/passagereranker/openvino.py +191 -0
  125. autorag/nodes/passagereranker/pass_reranker.py +31 -0
  126. autorag/nodes/passagereranker/rankgpt.py +170 -0
  127. autorag/nodes/passagereranker/run.py +145 -0
  128. autorag/nodes/passagereranker/sentence_transformer.py +129 -0
  129. autorag/nodes/passagereranker/tart/__init__.py +1 -0
  130. autorag/nodes/passagereranker/tart/modeling_enc_t5.py +152 -0
  131. autorag/nodes/passagereranker/tart/tart.py +139 -0
  132. autorag/nodes/passagereranker/tart/tokenization_enc_t5.py +112 -0
  133. autorag/nodes/passagereranker/time_reranker.py +72 -0
  134. autorag/nodes/passagereranker/upr.py +160 -0
  135. autorag/nodes/passagereranker/voyageai.py +109 -0
  136. autorag/nodes/promptmaker/__init__.py +12 -0
  137. autorag/nodes/promptmaker/base.py +32 -0
  138. autorag/nodes/promptmaker/chat_fstring.py +73 -0
  139. autorag/nodes/promptmaker/fstring.py +49 -0
  140. autorag/nodes/promptmaker/long_context_reorder.py +83 -0
  141. autorag/nodes/promptmaker/run.py +283 -0
  142. autorag/nodes/promptmaker/window_replacement.py +85 -0
  143. autorag/nodes/queryexpansion/__init__.py +4 -0
  144. autorag/nodes/queryexpansion/base.py +62 -0
  145. autorag/nodes/queryexpansion/hyde.py +43 -0
  146. autorag/nodes/queryexpansion/multi_query_expansion.py +57 -0
  147. autorag/nodes/queryexpansion/pass_query_expansion.py +22 -0
  148. autorag/nodes/queryexpansion/query_decompose.py +111 -0
  149. autorag/nodes/queryexpansion/run.py +308 -0
  150. autorag/nodes/retrieval/__init__.py +0 -0
  151. autorag/nodes/retrieval/base.py +127 -0
  152. autorag/nodes/retrieval/run_util.py +152 -0
  153. autorag/nodes/semanticretrieval/__init__.py +1 -0
  154. autorag/nodes/semanticretrieval/run.py +148 -0
  155. autorag/nodes/semanticretrieval/vectordb.py +339 -0
  156. autorag/nodes/util.py +16 -0
  157. autorag/parser.py +37 -0
  158. autorag/schema/__init__.py +3 -0
  159. autorag/schema/base.py +35 -0
  160. autorag/schema/metricinput.py +99 -0
  161. autorag/schema/module.py +24 -0
  162. autorag/schema/node.py +144 -0
  163. autorag/strategy.py +165 -0
  164. autorag/support.py +235 -0
  165. autorag/utils/__init__.py +8 -0
  166. autorag/utils/cast.py +45 -0
  167. autorag/utils/preprocess.py +149 -0
  168. autorag/utils/util.py +759 -0
  169. autorag/validator.py +98 -0
  170. autorag/vectordb/__init__.py +75 -0
  171. autorag/vectordb/base.py +73 -0
  172. autorag/vectordb/chroma.py +118 -0
  173. autorag/vectordb/couchbase.py +239 -0
  174. autorag/vectordb/milvus.py +169 -0
  175. autorag/vectordb/pinecone.py +121 -0
  176. autorag/vectordb/qdrant.py +155 -0
  177. autorag/vectordb/weaviate.py +184 -0
  178. autorag/web.py +81 -0
  179. autorag-0.0.0.dist-info/METADATA +780 -0
  180. autorag-0.0.0.dist-info/RECORD +184 -0
  181. autorag-0.0.0.dist-info/WHEEL +5 -0
  182. autorag-0.0.0.dist-info/entry_points.txt +2 -0
  183. autorag-0.0.0.dist-info/licenses/LICENSE +201 -0
  184. autorag-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,504 @@
1
+ import asyncio
2
+ import itertools
3
+ import os
4
+ from typing import List, Optional
5
+
6
+ import evaluate
7
+ import nltk
8
+ import pandas as pd
9
+ from llama_index.core.embeddings import BaseEmbedding
10
+ from llama_index.embeddings.openai import OpenAIEmbedding
11
+ from openai import AsyncOpenAI
12
+ from pydantic import BaseModel
13
+ from rouge_score import tokenizers
14
+ from rouge_score.rouge_scorer import RougeScorer
15
+ from sacrebleu.metrics.bleu import BLEU
16
+
17
+ from autorag.embedding.base import embedding_models
18
+ from autorag.evaluation.metric.deepeval_prompt import FaithfulnessTemplate
19
+ from autorag.evaluation.metric.util import (
20
+ autorag_metric_loop,
21
+ calculate_cosine_similarity,
22
+ )
23
+ from autorag.nodes.generator import OpenAILLM
24
+ from autorag.nodes.generator.base import BaseGenerator
25
+ from autorag.schema.metricinput import MetricInput
26
+ from autorag.support import get_support_modules
27
+ from autorag.utils.util import (
28
+ get_event_loop,
29
+ process_batch,
30
+ openai_truncate_by_token,
31
+ convert_inputs_to_list,
32
+ pop_params,
33
+ empty_cuda_cache,
34
+ )
35
+
36
+
37
+ @convert_inputs_to_list
38
+ def huggingface_evaluate(
39
+ instance, key: str, metric_inputs: List[MetricInput], **kwargs
40
+ ) -> List[float]:
41
+ """
42
+ Compute huggingface evaluate metric.
43
+
44
+ :param instance: The instance of huggingface evaluates metric.
45
+ :param key: The key to retrieve result score from huggingface evaluate result.
46
+ :param metric_inputs: A list of MetricInput schema
47
+ :param kwargs: The additional arguments for metric function.
48
+ :return: The list of scores.
49
+ """
50
+
51
+ def compute_score(gt: List[str], pred: str) -> float:
52
+ return max(
53
+ list(
54
+ map(
55
+ lambda x: instance.compute(
56
+ predictions=[pred], references=[x], **kwargs
57
+ )[key],
58
+ gt,
59
+ )
60
+ )
61
+ )
62
+
63
+ result = list(
64
+ map(lambda x: compute_score(x.generation_gt, x.generated_texts), metric_inputs)
65
+ )
66
+ return result
67
+
68
+
69
+ def make_generator_instance(generator_module_type: str, llm: str, **kwargs):
70
+ llm_class = get_support_modules(generator_module_type)
71
+ init_params = pop_params(llm_class.__init__, kwargs)
72
+ return llm_class(project_dir="", llm=llm, **init_params)
73
+
74
+
75
+ @autorag_metric_loop(fields_to_check=["retrieval_gt_contents", "generated_texts"])
76
+ def deepeval_faithfulness(
77
+ metric_inputs: List[MetricInput],
78
+ generator_module_type: str = "openai_llm",
79
+ lang: str = "en",
80
+ llm: str = "gpt-4o-2024-08-06",
81
+ batch: int = 16,
82
+ **kwargs,
83
+ ) -> List[float]:
84
+ """
85
+ Compute deepeval faithfulness metric.
86
+ Its default model is gpt-4o-2024-08-06.
87
+ Since it uses OpenAI model, please be aware of the expensive cost.
88
+
89
+ :param metric_inputs: The list of MetricInput schema (Required Field -> "generation_gt", "generated_texts")
90
+ :param generator_module_type: Generator module type.
91
+ The default is "openai_llm".
92
+ You can use like "llama_index_llm" or "vllm".
93
+ :param lang: The prompt language that you want to use.
94
+ "en", "ko" and "ja" are supported.
95
+ Korean prompt is not officially supported by DeepEval, but it can be translated by AutoRAG developers.
96
+ Default is "en".
97
+ :param llm: The model name to use for generation.
98
+ Or llm if using llama_index_llm.
99
+ The default is "gpt-4o-2024-08-06".
100
+ :param batch: The batch size for processing.
101
+ Default is 16.
102
+ :param kwargs: The extra parameters for initializing the llm instance.
103
+ :return: The metric scores.
104
+ """
105
+
106
+ class Truth(BaseModel):
107
+ truths: List[str]
108
+
109
+ class Claim(BaseModel):
110
+ claims: List[str]
111
+
112
+ class Verdict(BaseModel):
113
+ verdict: str
114
+ reason: Optional[str]
115
+
116
+ class FaithfulnessVerdicts(BaseModel):
117
+ verdicts: List[Verdict]
118
+
119
+ def calculate_score(verdicts: List[Verdict]) -> float:
120
+ number_of_verdicts = len(verdicts)
121
+ if number_of_verdicts == 0:
122
+ return 1
123
+
124
+ faithfulness_count = 0
125
+ for verdict in verdicts:
126
+ if verdict.verdict.strip().lower() != "no":
127
+ faithfulness_count += 1
128
+
129
+ score = faithfulness_count / number_of_verdicts
130
+ return score
131
+
132
+ retrieval_contexts = list(map(lambda x: x.retrieval_gt_contents, metric_inputs))
133
+ truth_prompts = list(
134
+ map(lambda x: FaithfulnessTemplate.generate_truths(x, lang), retrieval_contexts)
135
+ )
136
+
137
+ generated_texts = list(map(lambda x: x.generated_texts, metric_inputs))
138
+ claim_prompts = list(
139
+ map(lambda x: FaithfulnessTemplate.generate_claims(x, lang), generated_texts)
140
+ )
141
+
142
+ generator: BaseGenerator = make_generator_instance(
143
+ generator_module_type, llm=llm, batch=batch, **kwargs
144
+ )
145
+ if isinstance(generator, OpenAILLM): # Because of the event loop error at the httpx
146
+ # TODO: Fix the httpx APIConnectionError at the many repetitive request to the OpenAILLM on the same instance
147
+ truth_responses: List[Truth] = generator.structured_output(truth_prompts, Truth)
148
+ claim_responses: List[Claim] = make_generator_instance(
149
+ generator_module_type, llm=llm, batch=batch, **kwargs
150
+ ).structured_output(claim_prompts, Claim)
151
+ verdict_prompts = list(
152
+ map(
153
+ lambda claim, truth: FaithfulnessTemplate.generate_verdicts(
154
+ "\n\n".join(claim.claims), "\n\n".join(truth.truths), lang
155
+ ),
156
+ claim_responses,
157
+ truth_responses,
158
+ )
159
+ )
160
+ verdict_responses: List[FaithfulnessVerdicts] = make_generator_instance(
161
+ generator_module_type, llm=llm, batch=batch, **kwargs
162
+ ).structured_output(verdict_prompts, FaithfulnessVerdicts)
163
+ else:
164
+ truth_responses: List[Truth] = generator.structured_output(truth_prompts, Truth)
165
+ claim_responses: List[Claim] = generator.structured_output(claim_prompts, Claim)
166
+ verdict_prompts = list(
167
+ map(
168
+ lambda claim, truth: FaithfulnessTemplate.generate_verdicts(
169
+ "\n\n".join(claim.claims), "\n\n".join(truth.truths), lang
170
+ ),
171
+ claim_responses,
172
+ truth_responses,
173
+ )
174
+ )
175
+ verdict_responses: List[FaithfulnessVerdicts] = generator.structured_output(
176
+ verdict_prompts, FaithfulnessVerdicts
177
+ )
178
+
179
+ result = list(map(lambda x: calculate_score(x.verdicts), verdict_responses))
180
+ return result
181
+
182
+
183
+ @autorag_metric_loop(fields_to_check=["generation_gt", "generated_texts"])
184
+ def bleu(
185
+ metric_inputs: List[MetricInput],
186
+ tokenize: Optional[str] = None,
187
+ smooth_method: str = "exp",
188
+ smooth_value: Optional[float] = None,
189
+ max_ngram_order: int = 4,
190
+ trg_lang: str = "",
191
+ effective_order: bool = True,
192
+ **kwargs,
193
+ ) -> List[float]:
194
+ """
195
+ Computes the BLEU metric given pred and ground-truth.
196
+
197
+ :param metric_inputs: A list of MetricInput schema (Required Field -> "generation_gt", "generated_texts")
198
+ :param tokenize: The tokenizer to use. If None, defaults to language-specific tokenizers with '13a' as the fallback default. check #https://github.com/mjpost/sacrebleu/blob/master/sacrebleu/metrics/bleu.py
199
+ :param smooth_method: The smoothing method to use ('floor', 'add-k', 'exp' or 'none').
200
+ :param smooth_value: The smoothing value for `floor` and `add-k` methods. `None` falls back to default value.
201
+ :param max_ngram_order: If given, it overrides the maximum n-gram order (default: 4) when computing precisions.
202
+ :param trg_lang: An optional language code to raise potential tokenizer warnings.
203
+ :param effective_order: If `True`, stop including n-gram orders for which precision is 0. This should be
204
+ `True`, if sentence-level BLEU will be computed.
205
+ """
206
+ bleu_instance = BLEU(
207
+ tokenize=tokenize,
208
+ smooth_method=smooth_method,
209
+ smooth_value=smooth_value,
210
+ max_ngram_order=max_ngram_order,
211
+ trg_lang=trg_lang,
212
+ effective_order=effective_order,
213
+ **kwargs,
214
+ )
215
+
216
+ result = list(
217
+ map(
218
+ lambda x: bleu_instance.sentence_score(
219
+ x.generated_texts, x.generation_gt
220
+ ).score,
221
+ metric_inputs,
222
+ )
223
+ )
224
+ return result
225
+
226
+
227
+ @autorag_metric_loop(fields_to_check=["generation_gt", "generated_texts"])
228
+ def meteor(
229
+ metric_inputs: List[MetricInput],
230
+ alpha: float = 0.9,
231
+ beta: float = 3.0,
232
+ gamma: float = 0.5,
233
+ ) -> List[float]:
234
+ """
235
+ Compute meteor score for generation.
236
+
237
+ :param metric_inputs: A list of MetricInput schema (Required Field -> "generation_gt", "generated_texts")
238
+ :param alpha: Parameter for controlling relative weights of precision and recall.
239
+ Default is 0.9.
240
+ :param beta: Parameter for controlling shape of penalty as a
241
+ function of as a function of fragmentation.
242
+ Default is 3.0.
243
+ :param gamma: Relative weight assigned to fragmentation penalty.
244
+ Default is 0.5.
245
+ :return: A list of computed metric scores.
246
+ """
247
+ nltk.download("punkt", quiet=True)
248
+ meteor_instance = evaluate.load("meteor")
249
+ result = huggingface_evaluate(
250
+ meteor_instance,
251
+ "meteor",
252
+ metric_inputs,
253
+ alpha=alpha,
254
+ beta=beta,
255
+ gamma=gamma,
256
+ )
257
+ del meteor_instance
258
+ return result
259
+
260
+
261
+ @autorag_metric_loop(fields_to_check=["generation_gt", "generated_texts"])
262
+ def rouge(
263
+ metric_inputs: List[MetricInput],
264
+ rouge_type: Optional[str] = "rougeL",
265
+ use_stemmer: bool = False,
266
+ split_summaries: bool = False,
267
+ batch: int = os.cpu_count(),
268
+ ) -> List[float]:
269
+ """
270
+ Compute rouge score for generation.
271
+
272
+ :param metric_inputs: A list of MetricInput schema (Required Field -> "generation_gt", "generated_texts")
273
+ :param rouge_type: A rouge type to use for evaluation.
274
+ Default is 'RougeL'.
275
+ Choose between rouge1, rouge2, rougeL, and rougeLSum.
276
+ - rouge1: unigram (1-gram) based scoring.
277
+ - rouge2: bigram (2-gram) based scoring.
278
+ - rougeL: Longest Common Subsequence based scoring.
279
+ - rougeLSum: splits text using "\n"
280
+ :param use_stemmer: Bool indicating whether Porter stemmer should be used to
281
+ strip word suffixes to improve matching. This arg is used in the
282
+ DefaultTokenizer, but other tokenizers might or might not choose to
283
+ use this. Default is False.
284
+ :param split_summaries: Whether to add newlines between sentences for rougeLsum.
285
+ Default is False.
286
+ :param batch: The batch size for processing.
287
+ Default is your cpu count.
288
+ :return: A list of computed metric scores.
289
+ """
290
+ rouge_instance = RougeScorer(
291
+ rouge_types=[rouge_type],
292
+ use_stemmer=use_stemmer,
293
+ split_summaries=split_summaries,
294
+ tokenizer=tokenizers.DefaultTokenizer(use_stemmer),
295
+ )
296
+
297
+ async def compute(gt: List[str], pred: str) -> float:
298
+ return rouge_instance.score_multi(targets=gt, prediction=pred)[
299
+ rouge_type
300
+ ].fmeasure
301
+
302
+ tasks = [
303
+ compute(metric_input.generation_gt, metric_input.generated_texts)
304
+ for metric_input in metric_inputs
305
+ ]
306
+ loop = get_event_loop()
307
+ result = loop.run_until_complete(process_batch(tasks, batch_size=batch))
308
+
309
+ del rouge_instance
310
+ return result
311
+
312
+
313
+ @autorag_metric_loop(fields_to_check=["generation_gt", "generated_texts"])
314
+ def sem_score(
315
+ metric_inputs: List[MetricInput],
316
+ embedding_model: Optional[BaseEmbedding] = None,
317
+ batch: int = 128,
318
+ ) -> List[float]:
319
+ """
320
+ Compute sem score between generation gt and pred with cosine similarity.
321
+
322
+ :param metric_inputs: A list of MetricInput schema (Required Field -> "generation_gt", "generated_texts")
323
+ :param embedding_model: Embedding model to use for compute cosine similarity.
324
+ Default is all-mpnet-base-v2 embedding model.
325
+ The paper used this embedding model.
326
+ :param batch: The batch size for processing.
327
+ Default is 128
328
+ :return: A list of computed metric scores.
329
+ """
330
+ generations = [metric_input.generated_texts for metric_input in metric_inputs]
331
+ generation_gt = [metric_input.generation_gt for metric_input in metric_inputs]
332
+ if embedding_model is None:
333
+ embedding_model = embedding_models.get("huggingface_all_mpnet_base_v2")()
334
+
335
+ embedding_model.embed_batch_size = batch
336
+
337
+ openai_embedding_max_length = 8000
338
+ if isinstance(embedding_model, OpenAIEmbedding):
339
+ generations = openai_truncate_by_token(
340
+ generations, openai_embedding_max_length, embedding_model.model_name
341
+ )
342
+
343
+ embedded_pred: List[List[float]] = embedding_model.get_text_embedding_batch(
344
+ generations, show_progress=True
345
+ )
346
+ gt_lengths = list(map(len, generation_gt))
347
+ flatten_gt = list(itertools.chain.from_iterable(generation_gt))
348
+ if isinstance(embedding_model, OpenAIEmbedding):
349
+ flatten_gt = openai_truncate_by_token(
350
+ flatten_gt, openai_embedding_max_length, embedding_model.model_name
351
+ )
352
+ embedded_gt_flatten = embedding_model.get_text_embedding_batch(
353
+ flatten_gt, show_progress=True
354
+ )
355
+ # re-group embedded_gt_flatten with gt_lengths
356
+ iterator = iter(embedded_gt_flatten)
357
+ embedded_gt: List[List[List[float]]] = [
358
+ list(itertools.islice(iterator, length)) for length in gt_lengths
359
+ ]
360
+
361
+ result = []
362
+ for gt, pred in zip(embedded_gt, embedded_pred):
363
+ similarity_scores: List[float] = list(
364
+ map(lambda x: calculate_cosine_similarity(x, pred), gt)
365
+ )
366
+ result.append(max(similarity_scores))
367
+
368
+ del embedding_model
369
+ empty_cuda_cache()
370
+
371
+ return result
372
+
373
+
374
+ @autorag_metric_loop(fields_to_check=["generation_gt", "generated_texts"])
375
+ def g_eval(
376
+ metric_inputs: List[MetricInput],
377
+ metrics: Optional[List[str]] = None,
378
+ model: str = "gpt-4-0125-preview",
379
+ batch_size: int = 8,
380
+ ) -> List[float]:
381
+ """
382
+ Calculate G-Eval score.
383
+ G-eval is a metric that uses high-performance LLM model to evaluate generation performance.
384
+ It evaluates the generation result by coherence, consistency, fluency, and relevance.
385
+ It uses only 'openai' model, and we recommend to use gpt-4 for evaluation accuracy.
386
+
387
+ :param metric_inputs: A list of MetricInput schema (Required Field -> "generation_gt", "generated_texts")
388
+ :param metrics: A list of metrics to use for evaluation.
389
+ Default is all metrics, which is ['coherence', 'consistency', 'fluency', 'relevance'].
390
+ :param model: OpenAI model name.
391
+ Default is 'gpt-4-0125-preview'.
392
+ :param batch_size: The batch size for processing.
393
+ Default is 8.
394
+ :return: G-Eval score.
395
+ """
396
+ generations = [metric_input.generated_texts for metric_input in metric_inputs]
397
+ generation_gt = [metric_input.generation_gt for metric_input in metric_inputs]
398
+ loop = get_event_loop()
399
+ tasks = [
400
+ async_g_eval(gt, pred, metrics, model)
401
+ for gt, pred in zip(generation_gt, generations)
402
+ ]
403
+ result = loop.run_until_complete(process_batch(tasks, batch_size=batch_size))
404
+ return result
405
+
406
+
407
+ async def async_g_eval(
408
+ generation_gt: List[str],
409
+ pred: str,
410
+ metrics: Optional[List[str]] = None,
411
+ model: str = "gpt-4-0125-preview",
412
+ ) -> float:
413
+ available_metrics = ["coherence", "consistency", "fluency", "relevance"]
414
+ if metrics is None:
415
+ metrics = available_metrics
416
+ else:
417
+ assert len(metrics) > 0, "metrics must be a list of string"
418
+ metrics = [metric for metric in metrics if metric in available_metrics]
419
+
420
+ current_path = os.path.dirname(os.path.realpath(__file__))
421
+ prompt_path = os.path.join(current_path, "g_eval_prompts")
422
+ g_eval_prompts = {
423
+ "coherence": open(os.path.join(prompt_path, "coh_detailed.txt")).read(),
424
+ "consistency": open(os.path.join(prompt_path, "con_detailed.txt")).read(),
425
+ "fluency": open(os.path.join(prompt_path, "flu_detailed.txt")).read(),
426
+ "relevance": open(os.path.join(prompt_path, "rel_detailed.txt")).read(),
427
+ }
428
+
429
+ client = AsyncOpenAI()
430
+
431
+ async def g_eval_score(prompt: str, gen_gt: List[str], pred: str):
432
+ scores = []
433
+ for gt in gen_gt:
434
+ input_prompt = prompt.replace("{{Document}}", gt).replace(
435
+ "{{Summary}}", pred
436
+ )
437
+ response = await client.chat.completions.create(
438
+ model=model,
439
+ messages=[{"role": "system", "content": input_prompt}],
440
+ logprobs=True,
441
+ top_logprobs=5,
442
+ temperature=0,
443
+ max_tokens=2,
444
+ frequency_penalty=0,
445
+ presence_penalty=0,
446
+ stop=None,
447
+ n=20,
448
+ )
449
+ if "(1-3):" in prompt:
450
+ scores.append(get_g_eval_score(response, max_score=3))
451
+ else:
452
+ scores.append(get_g_eval_score(response))
453
+ return max(scores)
454
+
455
+ def get_g_eval_score(responses, max_score: int = 5) -> int:
456
+ target_tokens = {str(i): 0 for i in range(1, max_score + 1)}
457
+ for choice in responses.choices:
458
+ first_top_log_probs = choice.logprobs.content[0].top_logprobs
459
+ for i, top_log_prob in enumerate(
460
+ list(map(lambda x: x.token, first_top_log_probs))
461
+ ):
462
+ if top_log_prob in target_tokens:
463
+ target_tokens[top_log_prob] += 5 - i
464
+
465
+ return int(max(target_tokens, key=target_tokens.get))
466
+
467
+ g_eval_scores = await asyncio.gather(
468
+ *(g_eval_score(g_eval_prompts[x], generation_gt, pred) for x in metrics)
469
+ )
470
+ return sum(g_eval_scores) / len(g_eval_scores)
471
+
472
+
473
+ @autorag_metric_loop(fields_to_check=["generation_gt", "generated_texts"])
474
+ def bert_score(
475
+ metric_inputs: List[MetricInput],
476
+ lang: str = "en",
477
+ batch: int = 128,
478
+ n_threads: int = os.cpu_count(),
479
+ ) -> List[float]:
480
+ generations = [metric_input.generated_texts for metric_input in metric_inputs]
481
+ generation_gt = [metric_input.generation_gt for metric_input in metric_inputs]
482
+ evaluator = evaluate.load("bertscore")
483
+
484
+ df = pd.DataFrame(
485
+ {
486
+ "reference": generation_gt,
487
+ "prediction": generations,
488
+ "lang": lang,
489
+ }
490
+ )
491
+
492
+ df = df.explode("reference", ignore_index=False)
493
+ df["bert_score"] = evaluator.compute(
494
+ predictions=df["prediction"].tolist(),
495
+ references=df["reference"].tolist(),
496
+ lang=lang,
497
+ nthreads=n_threads,
498
+ batch_size=batch,
499
+ )["f1"]
500
+
501
+ del evaluator
502
+ empty_cuda_cache()
503
+
504
+ return df.groupby(level=0)["bert_score"].max().tolist()
@@ -0,0 +1,115 @@
1
+ import itertools
2
+ import math
3
+
4
+ from autorag.evaluation.metric.util import autorag_metric
5
+ from autorag.schema.metricinput import MetricInput
6
+
7
+
8
+ @autorag_metric(fields_to_check=["retrieval_gt", "retrieved_ids"])
9
+ def retrieval_f1(metric_input: MetricInput):
10
+ """
11
+ Compute f1 score for retrieval.
12
+
13
+ :param metric_input: The MetricInput schema for AutoRAG metric.
14
+ :return: The f1 score.
15
+ """
16
+ recall_score = retrieval_recall.__wrapped__(metric_input)
17
+ precision_score = retrieval_precision.__wrapped__(metric_input)
18
+ if recall_score + precision_score == 0:
19
+ return 0
20
+ else:
21
+ return 2 * (recall_score * precision_score) / (recall_score + precision_score)
22
+
23
+
24
+ @autorag_metric(fields_to_check=["retrieval_gt", "retrieved_ids"])
25
+ def retrieval_recall(metric_input: MetricInput) -> float:
26
+ gt, pred = metric_input.retrieval_gt, metric_input.retrieved_ids
27
+
28
+ gt_sets = [frozenset(g) for g in gt]
29
+ pred_set = set(pred)
30
+ hits = sum(any(pred_id in gt_set for pred_id in pred_set) for gt_set in gt_sets)
31
+ recall = hits / len(gt) if len(gt) > 0 else 0.0
32
+ return recall
33
+
34
+
35
+ @autorag_metric(fields_to_check=["retrieval_gt", "retrieved_ids"])
36
+ def retrieval_precision(metric_input: MetricInput) -> float:
37
+ gt, pred = metric_input.retrieval_gt, metric_input.retrieved_ids
38
+
39
+ gt_sets = [frozenset(g) for g in gt]
40
+ pred_set = set(pred)
41
+ hits = sum(any(pred_id in gt_set for gt_set in gt_sets) for pred_id in pred_set)
42
+ precision = hits / len(pred) if len(pred) > 0 else 0.0
43
+ return precision
44
+
45
+
46
+ @autorag_metric(fields_to_check=["retrieval_gt", "retrieved_ids"])
47
+ def retrieval_ndcg(metric_input: MetricInput) -> float:
48
+ gt, pred = metric_input.retrieval_gt, metric_input.retrieved_ids
49
+
50
+ gt_sets = [frozenset(g) for g in gt]
51
+ pred_set = set(pred)
52
+ relevance_scores = {
53
+ pred_id: 1 if any(pred_id in gt_set for gt_set in gt_sets) else 0
54
+ for pred_id in pred_set
55
+ }
56
+
57
+ dcg = sum(
58
+ (2 ** relevance_scores[doc_id] - 1) / math.log2(i + 2)
59
+ for i, doc_id in enumerate(pred)
60
+ )
61
+
62
+ len_flatten_gt = len(list(itertools.chain.from_iterable(gt)))
63
+ len_pred = len(pred)
64
+ ideal_pred = [1] * min(len_flatten_gt, len_pred) + [0] * max(
65
+ 0, len_pred - len_flatten_gt
66
+ )
67
+ idcg = sum(relevance / math.log2(i + 2) for i, relevance in enumerate(ideal_pred))
68
+
69
+ ndcg = dcg / idcg if idcg > 0 else 0
70
+ return ndcg
71
+
72
+
73
+ @autorag_metric(fields_to_check=["retrieval_gt", "retrieved_ids"])
74
+ def retrieval_mrr(metric_input: MetricInput) -> float:
75
+ """
76
+ Reciprocal Rank (RR) is the reciprocal of the rank of the first relevant item.
77
+ Mean of RR in whole queries is MRR.
78
+ """
79
+ gt, pred = metric_input.retrieval_gt, metric_input.retrieved_ids
80
+
81
+ # Flatten the ground truth list of lists into a single set of relevant documents
82
+ gt_sets = [frozenset(g) for g in gt]
83
+
84
+ rr_list = []
85
+ for gt_set in gt_sets:
86
+ for i, pred_id in enumerate(pred):
87
+ if pred_id in gt_set:
88
+ rr_list.append(1.0 / (i + 1))
89
+ break
90
+ return sum(rr_list) / len(gt_sets) if rr_list else 0.0
91
+
92
+
93
+ @autorag_metric(fields_to_check=["retrieval_gt", "retrieved_ids"])
94
+ def retrieval_map(metric_input: MetricInput) -> float:
95
+ """
96
+ Mean Average Precision (MAP) is the mean of Average Precision (AP) for all queries.
97
+ """
98
+ gt, pred = metric_input.retrieval_gt, metric_input.retrieved_ids
99
+
100
+ gt_sets = [frozenset(g) for g in gt]
101
+
102
+ ap_list = []
103
+
104
+ for gt_set in gt_sets:
105
+ pred_hits = [1 if pred_id in gt_set else 0 for pred_id in pred]
106
+ precision_list = [
107
+ sum(pred_hits[: i + 1]) / (i + 1)
108
+ for i, hit in enumerate(pred_hits)
109
+ if hit == 1
110
+ ]
111
+ ap_list.append(
112
+ sum(precision_list) / len(precision_list) if precision_list else 0.0
113
+ )
114
+
115
+ return sum(ap_list) / len(gt_sets) if ap_list else 0.0
@@ -0,0 +1,65 @@
1
+ """
2
+ This file contains the retrieval contents metric,
3
+ which means calculate the metric based on the contents of the retrieved items.
4
+ """
5
+
6
+ import itertools
7
+ from collections import Counter
8
+
9
+ import numpy as np
10
+
11
+ from autorag.evaluation.metric.util import autorag_metric
12
+ from autorag.schema.metricinput import MetricInput
13
+ from autorag.utils.util import normalize_string
14
+
15
+
16
+ def single_token_f1(ground_truth: str, prediction: str):
17
+ prediction_tokens = normalize_string(prediction).split()
18
+ ground_truth_tokens = normalize_string(ground_truth).split()
19
+ common = Counter(prediction_tokens) & Counter(ground_truth_tokens)
20
+ num_same = sum(common.values())
21
+ if num_same == 0:
22
+ return 0, 0, 0
23
+ precision = 1.0 * num_same / len(prediction_tokens)
24
+ recall = 1.0 * num_same / len(ground_truth_tokens)
25
+ f1 = (2 * precision * recall) / (precision + recall)
26
+ return precision, recall, f1
27
+
28
+
29
+ @autorag_metric(fields_to_check=["retrieved_contents", "retrieval_gt_contents"])
30
+ def retrieval_token_f1(metric_input: MetricInput):
31
+ pred = metric_input.retrieved_contents
32
+ gt = itertools.chain.from_iterable(metric_input.retrieval_gt_contents)
33
+
34
+ calculated_results = list(
35
+ map(lambda x: single_token_f1(x[1], x[0]), list(itertools.product(pred, gt)))
36
+ )
37
+ _, _, result = zip(*calculated_results)
38
+ result_np = np.array(list(result)).reshape(len(pred), -1)
39
+ return result_np.max(axis=1).mean()
40
+
41
+
42
+ @autorag_metric(fields_to_check=["retrieved_contents", "retrieval_gt_contents"])
43
+ def retrieval_token_precision(metric_input: MetricInput):
44
+ pred = metric_input.retrieved_contents
45
+ gt = itertools.chain.from_iterable(metric_input.retrieval_gt_contents)
46
+
47
+ calculated_results = list(
48
+ map(lambda x: single_token_f1(x[1], x[0]), list(itertools.product(pred, gt)))
49
+ )
50
+ result, _, _ = zip(*calculated_results)
51
+ result_np = np.array(list(result)).reshape(len(pred), -1)
52
+ return result_np.max(axis=1).mean()
53
+
54
+
55
+ @autorag_metric(fields_to_check=["retrieved_contents", "retrieval_gt_contents"])
56
+ def retrieval_token_recall(metric_input: MetricInput):
57
+ pred = metric_input.retrieved_contents
58
+ gt = itertools.chain.from_iterable(metric_input.retrieval_gt_contents)
59
+
60
+ calculated_results = list(
61
+ map(lambda x: single_token_f1(x[1], x[0]), list(itertools.product(pred, gt)))
62
+ )
63
+ _, result, _ = zip(*calculated_results)
64
+ result_np = np.array(list(result)).reshape(len(pred), -1)
65
+ return result_np.max(axis=1).mean()