aidial-rag-eval 0.1.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 (44) hide show
  1. aidial_rag_eval/__init__.py +7 -0
  2. aidial_rag_eval/dataframe/__init__.py +11 -0
  3. aidial_rag_eval/dataframe/match_facts.py +40 -0
  4. aidial_rag_eval/dataframe/merge.py +64 -0
  5. aidial_rag_eval/dataframe/metrics.py +301 -0
  6. aidial_rag_eval/dataset.py +99 -0
  7. aidial_rag_eval/evaluate.py +107 -0
  8. aidial_rag_eval/facts/__init__.py +0 -0
  9. aidial_rag_eval/facts/citation.py +48 -0
  10. aidial_rag_eval/facts/exact.py +19 -0
  11. aidial_rag_eval/facts/match_str_facts.py +71 -0
  12. aidial_rag_eval/generation/__init__.py +0 -0
  13. aidial_rag_eval/generation/inference.py +344 -0
  14. aidial_rag_eval/generation/metric_binds.py +173 -0
  15. aidial_rag_eval/generation/models/__init__.py +0 -0
  16. aidial_rag_eval/generation/models/converters/__init__.py +0 -0
  17. aidial_rag_eval/generation/models/converters/base_converter.py +18 -0
  18. aidial_rag_eval/generation/models/converters/decontextualization_template.py +47 -0
  19. aidial_rag_eval/generation/models/converters/llm_decontextualization_converter.py +207 -0
  20. aidial_rag_eval/generation/models/inference_scorers/__init__.py +0 -0
  21. aidial_rag_eval/generation/models/inference_scorers/base_inference_scorer.py +19 -0
  22. aidial_rag_eval/generation/models/inference_scorers/inference_template.py +77 -0
  23. aidial_rag_eval/generation/models/inference_scorers/llm_inference_scorer.py +115 -0
  24. aidial_rag_eval/generation/models/lambdas.py +31 -0
  25. aidial_rag_eval/generation/models/refusal_detectors/__init__.py +0 -0
  26. aidial_rag_eval/generation/models/refusal_detectors/base_refusal_detector.py +17 -0
  27. aidial_rag_eval/generation/models/refusal_detectors/llm_refusal_detector.py +112 -0
  28. aidial_rag_eval/generation/models/refusal_detectors/refusal_template.py +47 -0
  29. aidial_rag_eval/generation/refusal.py +102 -0
  30. aidial_rag_eval/generation/types.py +52 -0
  31. aidial_rag_eval/generation/utils/__init__.py +0 -0
  32. aidial_rag_eval/generation/utils/progress_bar.py +24 -0
  33. aidial_rag_eval/generation/utils/segmented_text.py +169 -0
  34. aidial_rag_eval/metric_binds.py +15 -0
  35. aidial_rag_eval/metrics.py +28 -0
  36. aidial_rag_eval/retrieval/__init__.py +0 -0
  37. aidial_rag_eval/retrieval/metrics.py +66 -0
  38. aidial_rag_eval/retrieval/types.py +24 -0
  39. aidial_rag_eval/types.py +62 -0
  40. aidial_rag_eval/utils.py +9 -0
  41. aidial_rag_eval-0.1.0.dist-info/LICENSE +201 -0
  42. aidial_rag_eval-0.1.0.dist-info/METADATA +184 -0
  43. aidial_rag_eval-0.1.0.dist-info/RECORD +44 -0
  44. aidial_rag_eval-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,7 @@
1
+ from aidial_rag_eval.dataframe import create_rag_eval_metrics_report
2
+ from aidial_rag_eval.evaluate import evaluate
3
+
4
+ __all__ = [
5
+ "create_rag_eval_metrics_report",
6
+ "evaluate",
7
+ ]
@@ -0,0 +1,11 @@
1
+ from aidial_rag_eval.dataframe.metrics import (
2
+ calculate_generation_metrics,
3
+ create_rag_eval_metrics_report,
4
+ create_retrieval_metrics_report,
5
+ )
6
+
7
+ __all__ = [
8
+ "create_rag_eval_metrics_report",
9
+ "create_retrieval_metrics_report",
10
+ "calculate_generation_metrics",
11
+ ]
@@ -0,0 +1,40 @@
1
+ import pandas as pd
2
+
3
+ from aidial_rag_eval.dataframe.merge import merge_ground_truth_and_answers
4
+ from aidial_rag_eval.facts.citation import CitationMatcher
5
+ from aidial_rag_eval.retrieval.types import Matcher
6
+
7
+ DEFAULT_MATCHER = CitationMatcher
8
+
9
+
10
+ def match_facts(row: pd.Series, matcher: Matcher) -> pd.Series:
11
+ result_row = matcher.match_facts(row.facts, row.context)
12
+ return pd.Series(result_row._asdict())
13
+
14
+
15
+ def apply_matcher_to_merged_dataframe(
16
+ df_merged: pd.DataFrame,
17
+ matcher: Matcher = DEFAULT_MATCHER,
18
+ ) -> pd.DataFrame:
19
+ matched_results = df_merged.apply(
20
+ match_facts,
21
+ matcher=matcher,
22
+ axis=1,
23
+ result_type="expand",
24
+ )
25
+ assert isinstance(matched_results, pd.DataFrame)
26
+ return matched_results
27
+
28
+
29
+ def match_facts_dataframe(
30
+ ground_truth: pd.DataFrame,
31
+ answers: pd.DataFrame,
32
+ matcher: Matcher = DEFAULT_MATCHER,
33
+ ) -> pd.DataFrame:
34
+ df_merged = merge_ground_truth_and_answers(ground_truth, answers)
35
+ return pd.merge(
36
+ df_merged,
37
+ apply_matcher_to_merged_dataframe(df_merged, matcher),
38
+ left_index=True,
39
+ right_index=True,
40
+ )
@@ -0,0 +1,64 @@
1
+ import pandas as pd
2
+
3
+ from aidial_rag_eval.types import (
4
+ ANSWERS_COLUMNS,
5
+ GROUND_TRUTH_COLUMNS,
6
+ MERGED_KEY_COLUMNS,
7
+ AnswerColumns,
8
+ GroundTruthColumns,
9
+ MergedColumns,
10
+ )
11
+
12
+
13
+ def merge_ground_truth_and_answers(
14
+ ground_truth: pd.DataFrame, answers: pd.DataFrame
15
+ ) -> pd.DataFrame:
16
+ """
17
+ Merges ground_truth and collected answers dataframes.
18
+
19
+ Parameters
20
+ -----------
21
+ ground_truth : pd.DataFrame
22
+ contains the ground truth answers
23
+ The structure of the ground truth
24
+ is described in `aidial_rag_eval.types.GroundTruthAnswers`.
25
+
26
+ answers : pd.DataFrame
27
+ contains the collected answers
28
+ The structure of the collected answers
29
+ is described in `aidial_rag_eval.types.CollectedAnswers`.
30
+
31
+ Returns
32
+ ------------
33
+ pd.DataFrame
34
+ Returns merged ground_truth dataframe and answers dataframe.
35
+ The structure of the df_merged
36
+ is described in `aidial_rag_eval.types.MergedColumns`.
37
+ """
38
+ ground_truth_copy = ground_truth[
39
+ ground_truth.columns.intersection(GROUND_TRUTH_COLUMNS)
40
+ ].copy()
41
+ assert isinstance(ground_truth_copy, pd.DataFrame)
42
+ answers_copy = answers[answers.columns.intersection(ANSWERS_COLUMNS)].copy()
43
+ assert isinstance(answers_copy, pd.DataFrame)
44
+ ground_truth_copy = ground_truth_copy.rename(
45
+ columns={
46
+ GroundTruthColumns.ANSWER.value: MergedColumns.GROUND_TRUTH_ANSWER.value
47
+ }
48
+ )
49
+ ground_truth_copy[GroundTruthColumns.DOCUMENTS] = ground_truth_copy[
50
+ GroundTruthColumns.DOCUMENTS
51
+ ].apply(frozenset)
52
+ answers_copy[AnswerColumns.DOCUMENTS] = answers_copy[AnswerColumns.DOCUMENTS].apply(
53
+ frozenset
54
+ )
55
+ data = pd.merge(
56
+ ground_truth_copy,
57
+ answers_copy,
58
+ on=MERGED_KEY_COLUMNS,
59
+ )
60
+ data[MergedColumns.DOCUMENTS] = answers_copy.loc[
61
+ data.index, MergedColumns.DOCUMENTS
62
+ ]
63
+ data[MergedColumns.DOCUMENTS] = data[MergedColumns.DOCUMENTS].apply(list)
64
+ return data
@@ -0,0 +1,301 @@
1
+ from typing import List, Optional
2
+
3
+ import pandas as pd
4
+ from langchain_core.language_models import BaseChatModel
5
+
6
+ from aidial_rag_eval.dataframe.match_facts import (
7
+ DEFAULT_MATCHER,
8
+ apply_matcher_to_merged_dataframe,
9
+ )
10
+ from aidial_rag_eval.dataframe.merge import merge_ground_truth_and_answers
11
+ from aidial_rag_eval.generation.metric_binds import metric_binds_dict
12
+ from aidial_rag_eval.generation.types import MetricBind, inference_column
13
+ from aidial_rag_eval.retrieval.metrics import (
14
+ calculate_metrics as calculate_metrics_by_row,
15
+ )
16
+ from aidial_rag_eval.retrieval.types import Matcher
17
+ from aidial_rag_eval.types import MergedColumns
18
+
19
+
20
+ def apply_metrics_to_matched_results(match_result_data: pd.DataFrame) -> pd.DataFrame:
21
+ retrieval_metrics = match_result_data.apply(
22
+ calculate_metrics_by_row,
23
+ axis=1,
24
+ result_type="expand",
25
+ )
26
+ assert isinstance(retrieval_metrics, pd.DataFrame)
27
+ return retrieval_metrics
28
+
29
+
30
+ def calculate_metrics(match_result_data: pd.DataFrame) -> pd.DataFrame:
31
+ return pd.merge(
32
+ match_result_data,
33
+ apply_metrics_to_matched_results(match_result_data),
34
+ left_index=True,
35
+ right_index=True,
36
+ )
37
+
38
+
39
+ def calculate_retrieval_metrics(
40
+ df_merged: pd.DataFrame,
41
+ matcher: Matcher = DEFAULT_MATCHER,
42
+ ) -> pd.DataFrame:
43
+ """
44
+ Calculates RAG evaluation retrieval metrics from df_merged dataframe.
45
+
46
+ Parameters
47
+ -----------
48
+ df_merged : pd.DataFrame
49
+ merged ground truth answers and collected answers dataframes
50
+ The structure of the df_merged
51
+ is described in `aidial_rag_eval.types.MergedColumns`.
52
+
53
+ matcher : Matcher, default=DEFAULT_MATCHER
54
+ An object responsible for matching facts
55
+ from the ground truth to the context in the answers.
56
+
57
+ Returns
58
+ ------------
59
+ pd.DataFrame
60
+ Returns retrieval metrics dataframe.
61
+ """
62
+ matched_result = apply_matcher_to_merged_dataframe(df_merged, matcher)
63
+ matched_result_with_df = pd.merge(
64
+ df_merged, matched_result, left_index=True, right_index=True
65
+ )
66
+ retrieval_metrics = apply_metrics_to_matched_results(matched_result_with_df)
67
+ return pd.merge(
68
+ matched_result, retrieval_metrics, left_index=True, right_index=True
69
+ )
70
+
71
+
72
+ def create_retrieval_metrics_report(
73
+ ground_truth: pd.DataFrame,
74
+ answers: pd.DataFrame,
75
+ matcher: Matcher = DEFAULT_MATCHER,
76
+ ) -> pd.DataFrame:
77
+ """
78
+ Calculates RAG evaluation retrieval metrics from input dataframes.
79
+
80
+ Parameters
81
+ -----------
82
+ ground_truth : pd.DataFrame
83
+ contains the ground truth answers
84
+ The structure of the ground truth
85
+ is described in `aidial_rag_eval.types.GroundTruthAnswers`.
86
+
87
+ answers : pd.DataFrame
88
+ contains the collected answers
89
+ The structure of the collected answers
90
+ is described in `aidial_rag_eval.types.CollectedAnswers`.
91
+
92
+ matcher : Matcher, default=DEFAULT_MATCHER
93
+ An object responsible for matching facts
94
+ from the ground truth to the context in the answers.
95
+
96
+ Returns
97
+ ------------
98
+ pd.DataFrame
99
+ Returns merged ground_truth dataframe,
100
+ answers dataframe with retrieval metrics dataframe.
101
+ """
102
+ df_merged = merge_ground_truth_and_answers(ground_truth, answers)
103
+ return pd.merge(
104
+ df_merged,
105
+ calculate_retrieval_metrics(df_merged, matcher),
106
+ left_index=True,
107
+ right_index=True,
108
+ )
109
+
110
+
111
+ def calculate_generation_metrics(
112
+ df_merged: pd.DataFrame,
113
+ llm: BaseChatModel,
114
+ metric_binds: List[MetricBind],
115
+ max_concurrency: int = 8,
116
+ batch_size: int = 6,
117
+ show_progress_bar: bool = True,
118
+ ) -> pd.DataFrame:
119
+ """
120
+ Calculates RAG evaluation generation metrics from df_merged dataframe.
121
+
122
+ Parameters
123
+ -----------
124
+ df_merged : pd.DataFrame
125
+ merged ground truth answers and collected answers dataframes
126
+ The structure of the df_merged
127
+ is described in `aidial_rag_eval.types.MergedColumns`.
128
+
129
+ llm : BaseChatModel, optional, default=None
130
+ A Langchain chat model used for calculating generation metrics.
131
+
132
+ metric_binds : List[MetricBind], optional, default=None
133
+ A list of string constants from `aidial_rag_eval.metric_binds`
134
+ specifying the generation metrics.
135
+
136
+ max_concurrency : int, default=8
137
+ The maximum number of concurrent requests to the LLM.
138
+
139
+ batch_size : int, default=6
140
+ The maximum number objects are processed in a single prompt for simple tasks.
141
+
142
+ show_progress_bar : bool, default=True
143
+ To display a progress bar during LLM requests.
144
+
145
+ Returns
146
+ ------------
147
+ pd.DataFrame
148
+ Returns generation metrics dataframe.
149
+ """
150
+ df_merged_copy = df_merged.copy()
151
+ df_merged_copy[MergedColumns.JOINED_CONTEXT] = df_merged_copy[
152
+ MergedColumns.CONTEXT
153
+ ].apply(lambda x: "\n".join(x))
154
+ metric_results = dict()
155
+ for metric_bind in metric_binds:
156
+ metric_results.update(
157
+ metric_binds_dict[metric_bind](
158
+ df_merged=df_merged_copy,
159
+ llm=llm,
160
+ max_concurrency=max_concurrency,
161
+ batch_size=batch_size,
162
+ show_progress_bar=show_progress_bar,
163
+ ).to_dict(orient="series")
164
+ )
165
+ df_metrics = pd.DataFrame(data=metric_results)
166
+ nli_columns = [
167
+ column for column in metric_results.keys() if column.endswith(inference_column)
168
+ ]
169
+ if nli_columns:
170
+ sub_df_nli = df_metrics[nli_columns]
171
+ df_metrics["mean_" + inference_column] = sub_df_nli.mean(1)
172
+ df_metrics["median_" + inference_column] = sub_df_nli.median(
173
+ 1
174
+ ) # pyright: ignore # noqa
175
+
176
+ return df_metrics
177
+
178
+
179
+ def create_generation_metrics_report(
180
+ ground_truth: pd.DataFrame,
181
+ answers: pd.DataFrame,
182
+ llm: BaseChatModel,
183
+ metric_binds: List[MetricBind],
184
+ max_concurrency: int = 8,
185
+ batch_size: int = 6,
186
+ show_progress_bar: bool = True,
187
+ ) -> pd.DataFrame:
188
+ """
189
+ Calculates RAG evaluation generation metrics from input dataframes.
190
+
191
+ Parameters
192
+ -----------
193
+ ground_truth : pd.DataFrame
194
+ contains the ground truth answers
195
+ The structure of the ground truth
196
+ is described in `aidial_rag_eval.types.GroundTruthAnswers`.
197
+
198
+ answers : pd.DataFrame
199
+ contains the collected answers
200
+ The structure of the collected answers
201
+ is described in `aidial_rag_eval.types.CollectedAnswers`.
202
+
203
+ llm : BaseChatModel, optional, default=None
204
+ A Langchain chat model used for calculating generation metrics.
205
+
206
+ metric_binds : List[MetricBind], optional, default=None
207
+ A list of string constants from `aidial_rag_eval.metric_binds`
208
+ specifying the generation metrics.
209
+
210
+ max_concurrency : int, default=8
211
+ The maximum number of concurrent requests to the LLM.
212
+
213
+ batch_size : int, default=6
214
+ The maximum number objects are processed in a single prompt for simple tasks.
215
+
216
+ show_progress_bar : bool, default=True
217
+ To display a progress bar during LLM requests.
218
+
219
+ Returns
220
+ ------------
221
+ pd.DataFrame
222
+ Returns merged ground_truth dataframe,
223
+ answers dataframe with generation metrics dataframe.
224
+ """
225
+ df_merged = merge_ground_truth_and_answers(ground_truth, answers)
226
+ df_metrics = calculate_generation_metrics(
227
+ df_merged,
228
+ llm,
229
+ metric_binds,
230
+ max_concurrency,
231
+ batch_size,
232
+ show_progress_bar,
233
+ )
234
+ return pd.merge(df_merged, df_metrics, left_index=True, right_index=True)
235
+
236
+
237
+ def create_rag_eval_metrics_report(
238
+ ground_truth: pd.DataFrame,
239
+ answers: pd.DataFrame,
240
+ matcher: Matcher = DEFAULT_MATCHER,
241
+ llm: Optional[BaseChatModel] = None,
242
+ metric_binds: Optional[List[MetricBind]] = None,
243
+ max_concurrency: int = 8,
244
+ batch_size: int = 6,
245
+ show_progress_bar: bool = True,
246
+ ) -> pd.DataFrame:
247
+ """
248
+ Calculates RAG evaluation metrics from input dataframes.
249
+
250
+ Parameters
251
+ -----------
252
+ ground_truth : pd.DataFrame
253
+ contains the ground truth answers
254
+ The structure of the ground truth
255
+ is described in `aidial_rag_eval.types.GroundTruthAnswers`.
256
+
257
+ answers : pd.DataFrame
258
+ contains the collected answers
259
+ The structure of the collected answers
260
+ is described in `aidial_rag_eval.types.CollectedAnswers`.
261
+
262
+ matcher : Matcher, default=DEFAULT_MATCHER
263
+ An object responsible for matching facts
264
+ from the ground truth to the context in the answers.
265
+
266
+ llm : BaseChatModel, optional, default=None
267
+ A Langchain chat model used for calculating generation metrics.
268
+
269
+ metric_binds : List[MetricBind], optional, default=None
270
+ A list of string constants from `aidial_rag_eval.metric_binds`
271
+ specifying the generation metrics.
272
+
273
+ max_concurrency : int, default=8
274
+ The maximum number of concurrent requests to the LLM.
275
+
276
+ batch_size : int, default=6
277
+ The maximum number objects are processed in a single prompt for simple tasks.
278
+
279
+ show_progress_bar : bool, default=True
280
+ To display a progress bar during LLM requests.
281
+
282
+ Returns
283
+ ------------
284
+ pd.DataFrame
285
+ Returns merged ground_truth dataframe, answers dataframe with metrics dataframe.
286
+ """
287
+ df_merged = merge_ground_truth_and_answers(ground_truth, answers)
288
+ retrieval_metrics = calculate_retrieval_metrics(df_merged, matcher)
289
+ if not metric_binds:
290
+ return pd.merge(df_merged, retrieval_metrics, left_index=True, right_index=True)
291
+ if llm is None:
292
+ raise ValueError("Argument 'llm' is required.")
293
+ generation_metrics = calculate_generation_metrics(
294
+ df_merged,
295
+ llm,
296
+ metric_binds,
297
+ max_concurrency,
298
+ batch_size,
299
+ show_progress_bar,
300
+ )
301
+ return pd.concat([df_merged, retrieval_metrics, generation_metrics], axis=1)
@@ -0,0 +1,99 @@
1
+ from datetime import datetime
2
+ from typing import Optional, Union
3
+
4
+ import fsspec
5
+ import pandas as pd
6
+ from pydantic import Field, TypeAdapter
7
+ from pydantic.dataclasses import dataclass
8
+
9
+
10
+ @dataclass
11
+ class DatasetMetadata:
12
+ created_at: Optional[datetime]
13
+ sources: list["Dataset"]
14
+ tools: dict[str, str]
15
+ file_info: dict = Field(default_factory=dict)
16
+ metrics: dict = Field(default_factory=dict)
17
+ statistics: dict = Field(default_factory=dict)
18
+
19
+ @staticmethod
20
+ def read_metadata(metadata_path: str) -> "DatasetMetadata":
21
+ fs, _ = fsspec.url_to_fs(metadata_path)
22
+ return TypeAdapter(DatasetMetadata).validate_json(fs.read_bytes(metadata_path))
23
+
24
+ @staticmethod
25
+ def create_missing_metadata(data_path: str) -> "DatasetMetadata":
26
+ fs, _ = fsspec.url_to_fs(data_path)
27
+ file_info = fs.info(data_path)
28
+ return DatasetMetadata(None, [], {}, file_info, {}, {})
29
+
30
+
31
+ @dataclass
32
+ class Dataset:
33
+ metadata_path: str
34
+ data_path: str
35
+ metadata: DatasetMetadata
36
+
37
+ @staticmethod
38
+ def handle_path(path: str) -> tuple[str, str]:
39
+ if path.endswith(".parquet.metadata.json"):
40
+ return path, path[: -len(".metadata.json")]
41
+ if path.endswith(".parquet"):
42
+ return path + ".metadata.json", path
43
+ raise ValueError(f"Invalid path: {path}")
44
+
45
+ @staticmethod
46
+ def from_metadata(path: str):
47
+ metadata_path, data_path = Dataset.handle_path(path)
48
+
49
+ fs, _ = fsspec.url_to_fs(data_path)
50
+ if fs.exists(metadata_path):
51
+ metadata = DatasetMetadata.read_metadata(metadata_path)
52
+ else:
53
+ metadata = DatasetMetadata.create_missing_metadata(data_path)
54
+
55
+ return Dataset(metadata_path, data_path, metadata)
56
+
57
+ def read_dataframe(self, *args, **kwargs) -> pd.DataFrame:
58
+ return pd.read_parquet(self.data_path, *args, **kwargs)
59
+
60
+ @staticmethod
61
+ def write_dataframe(
62
+ df: pd.DataFrame,
63
+ path: str,
64
+ sources: Optional[list["Dataset"]] = None,
65
+ tools: Optional[dict[str, str]] = None,
66
+ metrics: Optional[dict[str, float]] = None,
67
+ statistics: Optional[dict[str, float]] = None,
68
+ *args,
69
+ **kwargs,
70
+ ):
71
+ sources = sources or []
72
+ tools = tools or {}
73
+ metrics = metrics or {}
74
+ statistics = statistics or {}
75
+ metadata_path, data_path = Dataset.handle_path(path)
76
+
77
+ fs, _ = fsspec.url_to_fs(data_path)
78
+ df.to_parquet(data_path, *args, **kwargs)
79
+ file_info: dict = fs.info(data_path)
80
+
81
+ metadata = DatasetMetadata(
82
+ datetime.now(),
83
+ sources,
84
+ tools,
85
+ file_info,
86
+ metrics,
87
+ statistics,
88
+ )
89
+ fs.write_bytes(
90
+ metadata_path, TypeAdapter(DatasetMetadata).dump_json(metadata, indent=2)
91
+ )
92
+
93
+ return Dataset(metadata_path, data_path, metadata)
94
+
95
+
96
+ def source_dataset(dataset: Union[str, Dataset]):
97
+ if isinstance(dataset, str):
98
+ return Dataset.from_metadata(dataset)
99
+ return dataset
@@ -0,0 +1,107 @@
1
+ from typing import Any, List, Optional, Union
2
+
3
+ import pandas as pd
4
+ from langchain_core.language_models import BaseChatModel
5
+
6
+ from aidial_rag_eval.dataframe import create_rag_eval_metrics_report
7
+ from aidial_rag_eval.dataframe.match_facts import DEFAULT_MATCHER
8
+ from aidial_rag_eval.dataset import Dataset, source_dataset
9
+ from aidial_rag_eval.generation.types import MetricBind
10
+ from aidial_rag_eval.retrieval.types import Matcher
11
+ from aidial_rag_eval.utils import get_tools_versions
12
+
13
+
14
+ def evaluate(
15
+ ground_truth: Union[str, Dataset],
16
+ answers: Union[str, Dataset],
17
+ dest: str,
18
+ matcher: Matcher = DEFAULT_MATCHER,
19
+ llm: Optional[BaseChatModel] = None,
20
+ fs: Any = None,
21
+ metric_binds: Optional[List[MetricBind]] = None,
22
+ max_concurrency: int = 8,
23
+ batch_size: int = 6,
24
+ show_progress_bar: bool = True,
25
+ ) -> Dataset:
26
+ """
27
+ Calculates RAG evaluation metrics from input
28
+ Parquet datasets and writes the results to the specified destination.
29
+
30
+ Parameters
31
+ -----------
32
+ ground_truth : Union[str, Dataset]
33
+ Path to the Parquet file containing ground truth answers or
34
+ a Dataset object specifying the Parquet file with ground truth answers.
35
+ The structure of the ground truth Parquet file
36
+ is described in `aidial_rag_eval.types.GroundTruthAnswers`.
37
+
38
+ answers : Union[str, Dataset]
39
+ Path to the Parquet file containing collected answers or
40
+ a Dataset object specifying the Parquet file with collected answers.
41
+ The structure of the collected answers Parquet file
42
+ is described in `aidial_rag_eval.types.CollectedAnswers`.
43
+
44
+ dest : str
45
+ Path for the output file in Parquet format.
46
+
47
+ matcher : Matcher, default=DEFAULT_MATCHER
48
+ An object responsible for matching facts
49
+ from the ground truth to the context in the answers.
50
+
51
+ llm : BaseChatModel, optional, default=None
52
+ A Langchain chat model used for calculating generation metrics.
53
+
54
+ fs : Any, optional, default=None
55
+ File system to be used.
56
+
57
+ metric_binds : List[MetricBind], optional, default=None
58
+ A list of string constants from `aidial_rag_eval.metric_binds`
59
+ specifying the generation metrics.
60
+
61
+ max_concurrency : int, default=8
62
+ The maximum number of concurrent requests to the LLM.
63
+
64
+ batch_size : int, default=6
65
+ The maximum number objects are processed in a single prompt for simple tasks.
66
+
67
+ show_progress_bar : bool, default=True
68
+ To display a progress bar during LLM requests.
69
+
70
+ Returns
71
+ ------------
72
+ Dataset
73
+ Returns global statistics for each metric.
74
+ """
75
+ ground_truth_dataset = source_dataset(ground_truth)
76
+ answers_dataset = source_dataset(answers)
77
+
78
+ ground_truth_df = ground_truth_dataset.read_dataframe(filesystem=fs)
79
+ answers_df = answers_dataset.read_dataframe(filesystem=fs)
80
+
81
+ df_final = create_rag_eval_metrics_report(
82
+ ground_truth_df,
83
+ answers_df,
84
+ matcher,
85
+ llm,
86
+ metric_binds=metric_binds,
87
+ max_concurrency=max_concurrency,
88
+ batch_size=batch_size,
89
+ show_progress_bar=show_progress_bar,
90
+ )
91
+ aggregated_metrics = df_final.mean(numeric_only=True)
92
+ assert isinstance(aggregated_metrics, pd.Series)
93
+
94
+ metrics = Dataset.write_dataframe(
95
+ df_final,
96
+ dest,
97
+ sources=[ground_truth_dataset, answers_dataset],
98
+ tools=get_tools_versions(),
99
+ metrics=aggregated_metrics.to_dict(),
100
+ statistics={
101
+ "Ground truth size": len(ground_truth_df),
102
+ "Answers size": len(answers_df),
103
+ "Evaluation data size": len(df_final),
104
+ },
105
+ filesystem=fs,
106
+ )
107
+ return metrics
File without changes
@@ -0,0 +1,48 @@
1
+ from typing import Optional, Tuple
2
+
3
+ from aidial_rag_eval.facts.match_str_facts import match_str_facts
4
+ from aidial_rag_eval.retrieval.types import ContextChunk, FactMatchResult
5
+ from aidial_rag_eval.types import Context, Facts
6
+
7
+
8
+ class CitationMatcher:
9
+ Fact = str
10
+
11
+ @staticmethod
12
+ def _canonize(text: str) -> tuple[str, list[int]]:
13
+ text = text.replace("\t", " ")
14
+ text = "".join([c.lower() if c.isalnum() else " " for c in text])
15
+
16
+ new_text = ""
17
+ num_skipped_chars_for_pos = []
18
+
19
+ # skip duplicated spaces
20
+ for i, c in enumerate(text):
21
+ if c == " " and new_text[-1:] == " ":
22
+ continue
23
+ new_text += c
24
+ num_skipped_chars_for_pos.append(i - len(new_text) + 1)
25
+
26
+ num_skipped_chars_for_pos.append(len(text) - len(new_text))
27
+
28
+ return new_text, num_skipped_chars_for_pos
29
+
30
+ @staticmethod
31
+ def _match(fact: Fact, context_chunk: ContextChunk) -> Optional[Tuple[int, int]]:
32
+ canonized_fact, _ = CitationMatcher._canonize(fact)
33
+ canonized_context_chunk, skipped_chars = CitationMatcher._canonize(
34
+ context_chunk
35
+ )
36
+
37
+ start = canonized_context_chunk.find(canonized_fact)
38
+ if start == -1:
39
+ return None
40
+ end = start + len(canonized_fact)
41
+
42
+ start = start + skipped_chars[start]
43
+ end = end + skipped_chars[end]
44
+ return start, end
45
+
46
+ @staticmethod
47
+ def match_facts(facts: Facts[Fact], context: Context) -> FactMatchResult:
48
+ return match_str_facts(facts, context, CitationMatcher._match)
@@ -0,0 +1,19 @@
1
+ from typing import Optional, Tuple
2
+
3
+ from aidial_rag_eval.facts.match_str_facts import match_str_facts
4
+ from aidial_rag_eval.retrieval.types import ContextChunk, FactMatchResult
5
+ from aidial_rag_eval.types import Context, Facts
6
+
7
+
8
+ class ExactStringMatcher:
9
+ Fact = str
10
+
11
+ @staticmethod
12
+ def _match(fact: Fact, context_chunk: ContextChunk) -> Optional[Tuple[int, int]]:
13
+ if fact == context_chunk:
14
+ return (0, len(context_chunk))
15
+ return None
16
+
17
+ @staticmethod
18
+ def match_facts(facts: Facts[Fact], context: Context) -> FactMatchResult:
19
+ return match_str_facts(facts, context, ExactStringMatcher._match)