ragbits-evaluate 0.0.30.dev29302392__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 (59) hide show
  1. ragbits/evaluate/__init__.py +0 -0
  2. ragbits/evaluate/agent_simulation/__init__.py +87 -0
  3. ragbits/evaluate/agent_simulation/context.py +140 -0
  4. ragbits/evaluate/agent_simulation/conversation.py +333 -0
  5. ragbits/evaluate/agent_simulation/deepeval_evaluator.py +92 -0
  6. ragbits/evaluate/agent_simulation/logger.py +165 -0
  7. ragbits/evaluate/agent_simulation/metrics/__init__.py +19 -0
  8. ragbits/evaluate/agent_simulation/metrics/builtin.py +221 -0
  9. ragbits/evaluate/agent_simulation/metrics/collectors.py +142 -0
  10. ragbits/evaluate/agent_simulation/models.py +37 -0
  11. ragbits/evaluate/agent_simulation/results.py +200 -0
  12. ragbits/evaluate/agent_simulation/scenarios.py +129 -0
  13. ragbits/evaluate/agent_simulation/simulation.py +245 -0
  14. ragbits/evaluate/cli.py +150 -0
  15. ragbits/evaluate/config.py +11 -0
  16. ragbits/evaluate/dataloaders/__init__.py +3 -0
  17. ragbits/evaluate/dataloaders/base.py +95 -0
  18. ragbits/evaluate/dataloaders/document_search.py +61 -0
  19. ragbits/evaluate/dataloaders/exceptions.py +25 -0
  20. ragbits/evaluate/dataloaders/gaia.py +78 -0
  21. ragbits/evaluate/dataloaders/hotpot_qa.py +95 -0
  22. ragbits/evaluate/dataloaders/human_eval.py +70 -0
  23. ragbits/evaluate/dataloaders/question_answer.py +56 -0
  24. ragbits/evaluate/dataset_generator/__init__.py +0 -0
  25. ragbits/evaluate/dataset_generator/pipeline.py +141 -0
  26. ragbits/evaluate/dataset_generator/prompts/__init__.py +0 -0
  27. ragbits/evaluate/dataset_generator/prompts/corpus_generation.py +21 -0
  28. ragbits/evaluate/dataset_generator/prompts/qa.py +83 -0
  29. ragbits/evaluate/dataset_generator/tasks/__init__.py +0 -0
  30. ragbits/evaluate/dataset_generator/tasks/corpus_generation.py +67 -0
  31. ragbits/evaluate/dataset_generator/tasks/filter/__init__.py +0 -0
  32. ragbits/evaluate/dataset_generator/tasks/filter/base.py +43 -0
  33. ragbits/evaluate/dataset_generator/tasks/filter/dont_know.py +34 -0
  34. ragbits/evaluate/dataset_generator/tasks/text_generation/__init__.py +0 -0
  35. ragbits/evaluate/dataset_generator/tasks/text_generation/base.py +66 -0
  36. ragbits/evaluate/dataset_generator/tasks/text_generation/qa.py +96 -0
  37. ragbits/evaluate/dataset_generator/utils.py +43 -0
  38. ragbits/evaluate/evaluator.py +244 -0
  39. ragbits/evaluate/factories/__init__.py +42 -0
  40. ragbits/evaluate/metrics/__init__.py +3 -0
  41. ragbits/evaluate/metrics/base.py +89 -0
  42. ragbits/evaluate/metrics/document_search.py +90 -0
  43. ragbits/evaluate/metrics/gaia.py +84 -0
  44. ragbits/evaluate/metrics/hotpot_qa.py +51 -0
  45. ragbits/evaluate/metrics/human_eval.py +105 -0
  46. ragbits/evaluate/metrics/question_answer.py +205 -0
  47. ragbits/evaluate/optimizer.py +210 -0
  48. ragbits/evaluate/pipelines/__init__.py +37 -0
  49. ragbits/evaluate/pipelines/base.py +64 -0
  50. ragbits/evaluate/pipelines/document_search.py +106 -0
  51. ragbits/evaluate/pipelines/gaia.py +249 -0
  52. ragbits/evaluate/pipelines/hotpot_qa.py +342 -0
  53. ragbits/evaluate/pipelines/human_eval.py +323 -0
  54. ragbits/evaluate/pipelines/question_answer.py +96 -0
  55. ragbits/evaluate/py.typed +0 -0
  56. ragbits/evaluate/utils.py +160 -0
  57. ragbits_evaluate-0.0.30.dev29302392.dist-info/METADATA +58 -0
  58. ragbits_evaluate-0.0.30.dev29302392.dist-info/RECORD +59 -0
  59. ragbits_evaluate-0.0.30.dev29302392.dist-info/WHEEL +4 -0
@@ -0,0 +1,105 @@
1
+ import math
2
+ from statistics import mean
3
+
4
+ from ragbits.evaluate.metrics.base import Metric
5
+ from ragbits.evaluate.pipelines.human_eval import HumanEvalResult
6
+
7
+
8
+ class HumanEvalPassAtK(Metric[HumanEvalResult]):
9
+ """
10
+ Computes pass@k over HumanEval tasks.
11
+ Measures the fraction of tasks with at least one passing sample out of k attempts.
12
+ """
13
+
14
+ def __init__(self, k: int = 1) -> None:
15
+ super().__init__()
16
+ self.k = k
17
+
18
+ async def compute(self, results: list[HumanEvalResult]) -> dict:
19
+ """Compute pass@k averaged over tasks.
20
+
21
+ Returns:
22
+ Dictionary with humaneval_pass@k: fraction of tasks with at least one passing sample.
23
+ """
24
+ values = []
25
+ for r in results:
26
+ n = len(r.passed_mask)
27
+ m = sum(1 for x in r.passed_mask if x)
28
+ k = min(self.k, n)
29
+ if n == 0 or k == 0:
30
+ values.append(0.0)
31
+ continue
32
+ if m == 0:
33
+ values.append(0.0)
34
+ continue
35
+ if m == n:
36
+ values.append(1.0)
37
+ continue
38
+ # 1 - C(n-m, k) / C(n, k)
39
+ denom = math.comb(n, k)
40
+ numer = math.comb(n - m, k) if n - m >= k else 0
41
+ values.append(1.0 - (numer / denom))
42
+ return {f"humaneval_pass@{self.k}": float(mean(values)) if values else 0.0}
43
+
44
+
45
+ class HumanEvalQualityPerf(Metric[HumanEvalResult]):
46
+ """
47
+ Code quality and execution performance metrics:
48
+ - humaneval_compile_rate: fraction of samples that compiled
49
+ - humaneval_syntax_error_rate: fraction of samples with syntax error (compile failed)
50
+ - humaneval_assert_fail_rate: fraction of samples that ran but failed assertions
51
+ - humaneval_runtime_error_rate: fraction of samples with other runtime errors
52
+ - humaneval_timeout_rate: fraction of samples that timed out
53
+ - humaneval_tasks_solved: fraction of tasks with any passing sample
54
+ - humaneval_avg_exec_time_sec: average exec time over compilable runs
55
+ """
56
+
57
+ @staticmethod
58
+ async def compute(results: list[HumanEvalResult]) -> dict:
59
+ """Compute code quality and execution performance metrics.
60
+
61
+ Returns:
62
+ Dictionary with compile rates, error rates, tasks solved rate, and average execution time.
63
+ """
64
+ total_samples = sum(len(r.passed_mask) for r in results)
65
+ compiled = 0
66
+ syntax_errors = 0
67
+ assert_fails = 0
68
+ runtime_errors = 0
69
+ timeouts = 0
70
+ any_pass = sum(1 for r in results if any(r.passed_mask))
71
+ durations: list[float] = []
72
+
73
+ for r in results:
74
+ for ok, err, dur in zip(r.compile_ok_mask, r.errors, r.exec_durations_sec, strict=False):
75
+ if ok:
76
+ compiled += 1
77
+ durations.append(dur)
78
+ if err:
79
+ if err.startswith("AssertionError"):
80
+ assert_fails += 1
81
+ elif err.startswith("TimeoutError"):
82
+ timeouts += 1
83
+ else:
84
+ runtime_errors += 1
85
+ else:
86
+ # Compile failed: count as syntax error
87
+ syntax_errors += 1
88
+
89
+ compile_rate = (compiled / total_samples) if total_samples else 0.0
90
+ syntax_error_rate = (syntax_errors / total_samples) if total_samples else 0.0
91
+ assert_fail_rate = (assert_fails / total_samples) if total_samples else 0.0
92
+ runtime_error_rate = (runtime_errors / total_samples) if total_samples else 0.0
93
+ timeout_rate = (timeouts / total_samples) if total_samples else 0.0
94
+ tasks_solved = (any_pass / len(results)) if results else 0.0
95
+ avg_exec_time = float(mean(durations)) if durations else 0.0
96
+
97
+ return {
98
+ "humaneval_compile_rate": float(compile_rate),
99
+ "humaneval_syntax_error_rate": float(syntax_error_rate),
100
+ "humaneval_assert_fail_rate": float(assert_fail_rate),
101
+ "humaneval_runtime_error_rate": float(runtime_error_rate),
102
+ "humaneval_timeout_rate": float(timeout_rate),
103
+ "humaneval_tasks_solved": float(tasks_solved),
104
+ "humaneval_avg_exec_time_sec": avg_exec_time,
105
+ }
@@ -0,0 +1,205 @@
1
+ import asyncio
2
+ from abc import ABC, abstractmethod
3
+ from asyncio import AbstractEventLoop
4
+ from itertools import chain
5
+ from typing import Generic, TypeVar
6
+
7
+ from continuous_eval.llm_factory import LLMInterface
8
+ from continuous_eval.metrics.base import LLMBasedMetric
9
+ from continuous_eval.metrics.generation.text import (
10
+ LLMBasedAnswerCorrectness,
11
+ LLMBasedAnswerRelevance,
12
+ LLMBasedFaithfulness,
13
+ LLMBasedStyleConsistency,
14
+ )
15
+ from typing_extensions import Self
16
+
17
+ from ragbits.agents.types import QuestionAnswerPromptOutputT
18
+ from ragbits.core.llms.base import LLM
19
+ from ragbits.core.utils.helpers import batched
20
+ from ragbits.evaluate.metrics.base import Metric
21
+ from ragbits.evaluate.pipelines.question_answer import QuestionAnswerResult
22
+
23
+ MetricT = TypeVar("MetricT", bound=LLMBasedMetric)
24
+
25
+
26
+ class _MetricLMM(LLMInterface):
27
+ """
28
+ Implementation of required interface of Relari generative metrics based on LiteLMM.
29
+ """
30
+
31
+ def __init__(self, llm: LLM, loop: AbstractEventLoop) -> None:
32
+ self._llm = llm
33
+ self._loop = loop
34
+
35
+ def run(self, prompt: dict[str, str], temperature: float = 0, max_tokens: int = 1024) -> str:
36
+ formatted_prompt = [
37
+ {"role": "system", "content": prompt["system_prompt"]},
38
+ {"role": "user", "content": prompt["user_prompt"]},
39
+ ]
40
+ options = self._llm.options_cls(
41
+ temperature=temperature,
42
+ max_tokens=max_tokens,
43
+ )
44
+ return asyncio.run_coroutine_threadsafe(
45
+ self._llm.generate(formatted_prompt, options=options),
46
+ self._loop,
47
+ ).result()
48
+
49
+
50
+ class QuestionAnswerMetric(Generic[MetricT], Metric[QuestionAnswerResult], ABC):
51
+ """
52
+ Metric for question answer evaluation based on Relari backend.
53
+ More details can be found [here](https://docs.relari.ai/category/text-generation).
54
+ """
55
+
56
+ metric_cls: type[MetricT]
57
+
58
+ def __init__(self, llm: LLM, batch_size: int = 15, weight: float = 1.0) -> None:
59
+ """
60
+ Initialize the agent metric.
61
+
62
+ Args:
63
+ llm: Judge LLM instance.
64
+ batch_size: Batch size for metric computation.
65
+ weight: Metric value weight in the final score, used during optimization.
66
+ """
67
+ super().__init__(weight=weight)
68
+ self.llm = llm
69
+ self.batch_size = batch_size
70
+
71
+ @classmethod
72
+ def from_config(cls, config: dict) -> Self:
73
+ """
74
+ Create an instance of `QuestionAnswerMetric` from a configuration dictionary.
75
+
76
+ Args:
77
+ config: A dictionary containing configuration settings for the metric.
78
+
79
+ Returns:
80
+ An instance of the metric class initialized with the provided configuration.
81
+ """
82
+ config["llm"] = LLM.from_config(config["llm"])
83
+ config["batch_size"] = config.get("batch_size", 15)
84
+ config["weight"] = config.get("weight", 1.0)
85
+ return super().from_config(config)
86
+
87
+ async def compute(self, results: list[QuestionAnswerResult[QuestionAnswerPromptOutputT]]) -> dict:
88
+ """
89
+ Compute the metric.
90
+
91
+ Args:
92
+ results: The evaluation results.
93
+
94
+ Returns:
95
+ The computed metric.
96
+ """
97
+ metric = self.metric_cls(_MetricLMM(self.llm, loop=asyncio.get_running_loop()))
98
+ metric_results = chain.from_iterable(
99
+ [
100
+ await asyncio.gather(*[asyncio.to_thread(self._call_metric, metric, result) for result in batch])
101
+ for batch in batched(results, self.batch_size)
102
+ ]
103
+ )
104
+ return metric.aggregate(list(metric_results))
105
+
106
+ @staticmethod
107
+ @abstractmethod
108
+ def _call_metric(metric: MetricT, result: QuestionAnswerResult[QuestionAnswerPromptOutputT]) -> dict:
109
+ """
110
+ Call the metric with the proper arguments.
111
+ """
112
+
113
+
114
+ class QuestionAnswerAnswerCorrectness(QuestionAnswerMetric[LLMBasedAnswerCorrectness]):
115
+ """
116
+ Metric checking answer correctness based on LLM.
117
+ More details can be found [here](https://docs.relari.ai/metrics/Generation/LLM-Based/llm_correctness).
118
+ """
119
+
120
+ metric_cls: type[LLMBasedAnswerCorrectness] = LLMBasedAnswerCorrectness
121
+
122
+ @staticmethod
123
+ def _call_metric(
124
+ metric: LLMBasedAnswerCorrectness,
125
+ result: QuestionAnswerResult[QuestionAnswerPromptOutputT],
126
+ ) -> dict:
127
+ return metric(
128
+ question=result.question,
129
+ answer=(
130
+ result.predicted_result.content
131
+ if isinstance(result.predicted_result.content, str)
132
+ else result.predicted_result.content.answer
133
+ ),
134
+ ground_truth_answers=result.reference_answer,
135
+ )
136
+
137
+
138
+ class QuestionAnswerAnswerFaithfulness(QuestionAnswerMetric[LLMBasedFaithfulness]):
139
+ """
140
+ Metric checking answer faithfulness based on LLM.
141
+ More details can be found [here](https://docs.relari.ai/metrics/Generation/LLM-Based/llm_faithfulness).
142
+ """
143
+
144
+ metric_cls: type[LLMBasedFaithfulness] = LLMBasedFaithfulness
145
+
146
+ @staticmethod
147
+ def _call_metric(
148
+ metric: LLMBasedFaithfulness,
149
+ result: QuestionAnswerResult[QuestionAnswerPromptOutputT],
150
+ ) -> dict:
151
+ return metric(
152
+ question=result.question,
153
+ answer=(
154
+ result.predicted_result.content
155
+ if isinstance(result.predicted_result.content, str)
156
+ else result.predicted_result.content.answer
157
+ ),
158
+ retrieved_context=result.reference_context,
159
+ )
160
+
161
+
162
+ class QuestionAnswerAnswerRelevance(QuestionAnswerMetric[LLMBasedAnswerRelevance]):
163
+ """
164
+ Metric checking answer relevance based on LLM.
165
+ More details can be found [here](https://docs.relari.ai/metrics/Generation/LLM-Based/llm_relevance).
166
+ """
167
+
168
+ metric_cls: type[LLMBasedAnswerRelevance] = LLMBasedAnswerRelevance
169
+
170
+ @staticmethod
171
+ def _call_metric(
172
+ metric: LLMBasedAnswerRelevance,
173
+ result: QuestionAnswerResult[QuestionAnswerPromptOutputT],
174
+ ) -> dict:
175
+ return metric(
176
+ question=result.question,
177
+ answer=(
178
+ result.predicted_result.content
179
+ if isinstance(result.predicted_result.content, str)
180
+ else result.predicted_result.content.answer
181
+ ),
182
+ )
183
+
184
+
185
+ class QuestionAnswerAnswerConsistency(QuestionAnswerMetric[LLMBasedStyleConsistency]):
186
+ """
187
+ Metric checking answer relevance based on LLM.
188
+ More details can be found [here](https://docs.relari.ai/metrics/Generation/LLM-Based/llm_style).
189
+ """
190
+
191
+ metric_cls: type[LLMBasedStyleConsistency] = LLMBasedStyleConsistency
192
+
193
+ @staticmethod
194
+ def _call_metric(
195
+ metric: LLMBasedStyleConsistency,
196
+ result: QuestionAnswerResult[QuestionAnswerPromptOutputT],
197
+ ) -> dict:
198
+ return metric(
199
+ answer=(
200
+ result.predicted_result.content
201
+ if isinstance(result.predicted_result.content, str)
202
+ else result.predicted_result.content.answer
203
+ ),
204
+ ground_truth_answers=result.reference_answer,
205
+ )
@@ -0,0 +1,210 @@
1
+ import asyncio
2
+ import warnings
3
+ from collections.abc import Callable
4
+ from copy import deepcopy
5
+
6
+ import optuna
7
+ from optuna import Trial
8
+ from pydantic import BaseModel
9
+
10
+ from ragbits.core.utils.config_handling import WithConstructionConfig, import_by_path
11
+ from ragbits.evaluate.dataloaders.base import DataLoader
12
+ from ragbits.evaluate.evaluator import Evaluator, EvaluatorConfig
13
+ from ragbits.evaluate.metrics.base import MetricSet
14
+ from ragbits.evaluate.pipelines.base import EvaluationPipeline
15
+ from ragbits.evaluate.utils import setup_optuna_neptune_callback
16
+
17
+
18
+ class OptimizerConfig(BaseModel):
19
+ """
20
+ Schema for the optimizer config.
21
+ """
22
+
23
+ evaluator: EvaluatorConfig
24
+ optimizer: dict | None = None
25
+ neptune_callback: bool = False
26
+
27
+
28
+ class Optimizer(WithConstructionConfig):
29
+ """
30
+ Optimizer class.
31
+ """
32
+
33
+ def __init__(self, direction: str = "maximize", n_trials: int = 10, max_retries_for_trial: int = 1) -> None:
34
+ """
35
+ Initialize the pipeline optimizer.
36
+
37
+ Args:
38
+ direction: Direction of optimization.
39
+ n_trials: The number of trials for each process.
40
+ max_retries_for_trial: The number of retires for single process.
41
+ """
42
+ self.direction = direction
43
+ self.n_trials = n_trials
44
+ self.max_retries_for_trial = max_retries_for_trial
45
+ # workaround for optuna not allowing different choices for different trials
46
+ # TODO check how optuna handles parallelism. discuss if we want to have parallel studies
47
+ self._choices_cache: dict[str, list] = {}
48
+
49
+ @classmethod
50
+ def run_from_config(cls, config: dict) -> list[tuple[dict, float, dict[str, float]]]:
51
+ """
52
+ Run the optimization process configured with a config object.
53
+
54
+ Args:
55
+ config: Optimizer config.
56
+
57
+ Returns:
58
+ List of tested configs with associated scores and metrics.
59
+ """
60
+ optimizer_config = OptimizerConfig.model_validate(config)
61
+ evaluator_config = EvaluatorConfig.model_validate(optimizer_config.evaluator)
62
+
63
+ dataloader: DataLoader = DataLoader.subclass_from_config(evaluator_config.evaluation.dataloader)
64
+ metricset: MetricSet = MetricSet.from_config(evaluator_config.evaluation.metrics)
65
+
66
+ pipeline_class = import_by_path(evaluator_config.evaluation.pipeline.type)
67
+ pipeline_config = dict(evaluator_config.evaluation.pipeline.config)
68
+ callbacks = [setup_optuna_neptune_callback()] if optimizer_config.neptune_callback else []
69
+
70
+ optimizer = cls.from_config(optimizer_config.optimizer or {})
71
+ return optimizer.optimize(
72
+ pipeline_class=pipeline_class,
73
+ pipeline_config=pipeline_config,
74
+ metricset=metricset,
75
+ dataloader=dataloader,
76
+ callbacks=callbacks,
77
+ )
78
+
79
+ def optimize(
80
+ self,
81
+ pipeline_class: type[EvaluationPipeline],
82
+ pipeline_config: dict,
83
+ dataloader: DataLoader,
84
+ metricset: MetricSet,
85
+ callbacks: list[Callable] | None = None,
86
+ ) -> list[tuple[dict, float, dict[str, float]]]:
87
+ """
88
+ Run the optimization process for given parameters.
89
+
90
+ Args:
91
+ pipeline_class: Pipeline to be optimized.
92
+ pipeline_config: Configuration defining the optimization process.
93
+ dataloader: Data loader.
94
+ metricset: Metrics to be optimized.
95
+ callbacks: Experiment callbacks.
96
+
97
+ Returns:
98
+ List of tested configs with associated scores and metrics.
99
+ """
100
+
101
+ def objective(trial: Trial) -> float:
102
+ return self._objective(
103
+ trial=trial,
104
+ pipeline_class=pipeline_class,
105
+ pipeline_config=pipeline_config,
106
+ dataloader=dataloader,
107
+ metricset=metricset,
108
+ )
109
+
110
+ study = optuna.create_study(direction=self.direction)
111
+ study.optimize(
112
+ func=objective,
113
+ n_trials=self.n_trials,
114
+ callbacks=callbacks,
115
+ )
116
+ return sorted(
117
+ [
118
+ (
119
+ trial.user_attrs["config"],
120
+ trial.user_attrs["score"],
121
+ trial.user_attrs["metrics"],
122
+ )
123
+ for trial in study.get_trials()
124
+ ],
125
+ key=lambda x: -x[1] if self.direction == "maximize" else x[1],
126
+ )
127
+
128
+ def _objective(
129
+ self,
130
+ trial: Trial,
131
+ pipeline_class: type[EvaluationPipeline],
132
+ pipeline_config: dict,
133
+ dataloader: DataLoader,
134
+ metricset: MetricSet,
135
+ ) -> float:
136
+ """
137
+ Run a single experiment.
138
+ """
139
+ evaluator = Evaluator()
140
+ event_loop = asyncio.get_event_loop()
141
+
142
+ score = 1e16 if self.direction == "maximize" else -1e16
143
+ metrics_values = None
144
+ config_for_trial = None
145
+
146
+ for attempt in range(1, self.max_retries_for_trial + 1):
147
+ try:
148
+ config_for_trial = deepcopy(pipeline_config)
149
+ self._set_values_for_optimized_params(cfg=config_for_trial, trial=trial, ancestors=[])
150
+ pipeline = pipeline_class.from_config(config_for_trial)
151
+
152
+ results = event_loop.run_until_complete(
153
+ evaluator.compute(
154
+ pipeline=pipeline,
155
+ dataloader=dataloader,
156
+ metricset=metricset,
157
+ )
158
+ )
159
+ score = sum(results.metrics.values())
160
+ metrics_values = results.metrics
161
+ break
162
+ except Exception as exc:
163
+ message = (
164
+ f"Execution of the trial failed: {exc}. A retry will be initiated"
165
+ if attempt < self.max_retries_for_trial
166
+ else f"Execution of the trial failed: {exc}. Setting the score to {score}"
167
+ )
168
+ warnings.warn(message=message, category=UserWarning)
169
+
170
+ trial.set_user_attr("score", score)
171
+ trial.set_user_attr("metrics", metrics_values)
172
+ trial.set_user_attr("config", config_for_trial)
173
+
174
+ return score
175
+
176
+ def _set_values_for_optimized_params(self, cfg: dict, trial: Trial, ancestors: list[str]) -> None: # noqa: PLR0912
177
+ """
178
+ Recursive method for sampling parameter values for optuna trial.
179
+ """
180
+ for key, value in cfg.items():
181
+ if isinstance(value, dict):
182
+ if value.get("optimize"):
183
+ param_id = f"{'.'.join(ancestors)}.{key}" # type: ignore
184
+ choices = value.get("choices")
185
+ values_range = value.get("range")
186
+ if choices and values_range:
187
+ raise ValueError("Choices and range cannot be defined in couple")
188
+ choices_index = self._choices_cache.get(param_id)
189
+ if choices and not choices_index:
190
+ choices_index = list(range(len(choices)))
191
+ self._choices_cache[param_id] = choices_index
192
+ if values_range:
193
+ if isinstance(values_range[0], float) and isinstance(values_range[1], float):
194
+ cfg[key] = trial.suggest_float(name=param_id, low=values_range[0], high=values_range[1])
195
+ elif isinstance(values_range[0], int) and isinstance(values_range[1], int):
196
+ cfg[key] = trial.suggest_int(name=param_id, low=values_range[0], high=values_range[1])
197
+ else:
198
+ if not choices:
199
+ raise ValueError("Either choices or range must be specified")
200
+ choice_idx = trial.suggest_categorical(name=param_id, choices=choices_index) # type: ignore
201
+ choice = choices[choice_idx]
202
+ if isinstance(choice, dict):
203
+ self._set_values_for_optimized_params(choice, trial, ancestors + [key, str(choice_idx)]) # type: ignore
204
+ cfg[key] = choice
205
+ else:
206
+ self._set_values_for_optimized_params(value, trial, ancestors + [key]) # type: ignore
207
+ elif isinstance(value, list):
208
+ for param in value:
209
+ if isinstance(param, dict):
210
+ self._set_values_for_optimized_params(param, trial, ancestors + [key]) # type: ignore
@@ -0,0 +1,37 @@
1
+ from ragbits.core.utils.config_handling import WithConstructionConfig
2
+ from ragbits.document_search import DocumentSearch
3
+ from ragbits.evaluate.pipelines.base import EvaluationData, EvaluationPipeline, EvaluationResult
4
+ from ragbits.evaluate.pipelines.document_search import DocumentSearchPipeline
5
+ from ragbits.evaluate.pipelines.gaia import GaiaPipeline
6
+ from ragbits.evaluate.pipelines.hotpot_qa import HotpotQAPipeline
7
+ from ragbits.evaluate.pipelines.human_eval import HumanEvalPipeline
8
+
9
+ __all__ = [
10
+ "DocumentSearchPipeline",
11
+ "EvaluationData",
12
+ "EvaluationPipeline",
13
+ "EvaluationResult",
14
+ "GaiaPipeline",
15
+ "HotpotQAPipeline",
16
+ "HumanEvalPipeline",
17
+ ]
18
+
19
+ _target_to_evaluation_pipeline: dict[type[WithConstructionConfig], type[EvaluationPipeline]] = {
20
+ DocumentSearch: DocumentSearchPipeline,
21
+ }
22
+
23
+
24
+ def get_evaluation_pipeline_for_target(evaluation_target: WithConstructionConfig) -> EvaluationPipeline:
25
+ """
26
+ A function instantiating evaluation pipeline for given WithConstructionConfig object
27
+ Args:
28
+ evaluation_target: WithConstructionConfig object to be evaluated
29
+ Returns:
30
+ instance of evaluation pipeline
31
+ Raises:
32
+ ValueError for classes with no registered evaluation pipeline
33
+ """
34
+ for supported_type, evaluation_pipeline_type in _target_to_evaluation_pipeline.items():
35
+ if isinstance(evaluation_target, supported_type):
36
+ return evaluation_pipeline_type(evaluation_target=evaluation_target)
37
+ raise ValueError(f"Evaluation pipeline not implemented for {evaluation_target.__class__}")
@@ -0,0 +1,64 @@
1
+ from abc import ABC, abstractmethod
2
+ from collections.abc import Iterable
3
+ from dataclasses import dataclass
4
+ from types import ModuleType
5
+ from typing import ClassVar, Generic, TypeVar
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from ragbits.core.utils.config_handling import WithConstructionConfig
10
+ from ragbits.evaluate import pipelines
11
+
12
+ EvaluationDataT = TypeVar("EvaluationDataT", bound="EvaluationData")
13
+ EvaluationResultT = TypeVar("EvaluationResultT", bound="EvaluationResult")
14
+ EvaluationTargetT = TypeVar("EvaluationTargetT", bound=WithConstructionConfig)
15
+
16
+
17
+ class EvaluationData(BaseModel, ABC):
18
+ """
19
+ Represents the data for a single evaluation.
20
+ """
21
+
22
+
23
+ @dataclass
24
+ class EvaluationResult(ABC):
25
+ """
26
+ Represents the result of a single evaluation.
27
+ """
28
+
29
+
30
+ class EvaluationPipeline(WithConstructionConfig, Generic[EvaluationTargetT, EvaluationDataT, EvaluationResultT], ABC):
31
+ """
32
+ Evaluation pipeline.
33
+ """
34
+
35
+ default_module: ClassVar[ModuleType | None] = pipelines
36
+ configuration_key: ClassVar[str] = "pipeline"
37
+
38
+ def __init__(self, evaluation_target: EvaluationTargetT) -> None:
39
+ """
40
+ Initialize the evaluation pipeline.
41
+
42
+ Args:
43
+ evaluation_target: Evaluation target instance.
44
+ """
45
+ super().__init__()
46
+ self.evaluation_target = evaluation_target
47
+
48
+ async def prepare(self) -> None:
49
+ """
50
+ Prepare pipeline for evaluation. Optional step.
51
+ """
52
+ pass
53
+
54
+ @abstractmethod
55
+ async def __call__(self, data: Iterable[EvaluationDataT]) -> Iterable[EvaluationResultT]:
56
+ """
57
+ Run the evaluation pipeline.
58
+
59
+ Args:
60
+ data: The evaluation data.
61
+
62
+ Returns:
63
+ The evaluation result.
64
+ """