evalring 0.2.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.
@@ -0,0 +1,179 @@
1
+ """
2
+ Concrete implementations of datasets.
3
+ """
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import pandas as pd
9
+
10
+ from .base import BaseDataset, DataSample
11
+
12
+
13
+ class JSONDataset(BaseDataset):
14
+ """Dataset loaded from a JSON file."""
15
+
16
+ def load_data(
17
+ self,
18
+ source: str | Path | pd.DataFrame,
19
+ text_field: str = "text",
20
+ label_field: str = "label",
21
+ id_field: str | None = "id",
22
+ **kwargs,
23
+ ) -> None:
24
+ """
25
+ Load data from a JSON file.
26
+
27
+ Args:
28
+ source: Path to JSON file
29
+ text_field: Key for input text
30
+ label_field: Key for target output
31
+ id_field: Key for sample ID (auto-generated if None)
32
+ """
33
+ if isinstance(source, pd.DataFrame):
34
+ raise TypeError("JSONDataset expects a file path; use DataFrameDataset instead.")
35
+
36
+ filepath = Path(source)
37
+ if not filepath.exists():
38
+ raise FileNotFoundError(f"File not found: {filepath}")
39
+
40
+ with open(filepath) as f:
41
+ data = json.load(f)
42
+
43
+ if not isinstance(data, list):
44
+ raise ValueError("JSON data must be a list of objects")
45
+
46
+ for i, item in enumerate(data):
47
+ sample_id = str(item.get(id_field, i)) if id_field else str(i)
48
+
49
+ # Extract metadata (everything except text and label)
50
+ metadata = {
51
+ k: v for k, v in item.items() if k not in [text_field, label_field, id_field]
52
+ }
53
+
54
+ sample = DataSample(
55
+ id=sample_id,
56
+ input_text=item.get(text_field, ""),
57
+ target_output=item.get(label_field),
58
+ metadata=metadata,
59
+ )
60
+ self.add_sample(sample)
61
+
62
+ def validate_data(self) -> bool:
63
+ """Validate that all samples have input text and target output."""
64
+ for sample in self._samples:
65
+ if not sample.input_text or sample.target_output is None:
66
+ return False
67
+ return True
68
+
69
+
70
+ class CSVDataset(BaseDataset):
71
+ """Dataset loaded from a CSV file."""
72
+
73
+ def load_data(
74
+ self,
75
+ source: str | Path | pd.DataFrame,
76
+ text_field: str = "text",
77
+ label_field: str = "label",
78
+ id_field: str | None = None,
79
+ **kwargs,
80
+ ) -> None:
81
+ """
82
+ Load data from a CSV file.
83
+
84
+ Args:
85
+ source: Path to CSV file
86
+ text_field: Column name for input text
87
+ label_field: Column name for target output
88
+ id_field: Column name for sample ID (auto-generated if None)
89
+ """
90
+ if isinstance(source, pd.DataFrame):
91
+ raise TypeError("CSVDataset expects a file path; use DataFrameDataset instead.")
92
+
93
+ filepath = Path(source)
94
+ if not filepath.exists():
95
+ raise FileNotFoundError(f"File not found: {filepath}")
96
+
97
+ df = pd.read_csv(filepath, **kwargs)
98
+
99
+ if text_field not in df.columns:
100
+ raise ValueError(f"Text field '{text_field}' not found in CSV columns")
101
+
102
+ if label_field not in df.columns:
103
+ raise ValueError(f"Label field '{label_field}' not found in CSV columns")
104
+
105
+ for i, row in df.iterrows():
106
+ sample_id = str(row[id_field]) if id_field and id_field in df.columns else str(i)
107
+
108
+ # Extract metadata
109
+ metadata_cols = [c for c in df.columns if c not in [text_field, label_field, id_field]]
110
+ metadata = {col: row[col] for col in metadata_cols}
111
+
112
+ sample = DataSample(
113
+ id=sample_id,
114
+ input_text=str(row[text_field]),
115
+ target_output=row[label_field],
116
+ metadata=metadata,
117
+ )
118
+ self.add_sample(sample)
119
+
120
+ def validate_data(self) -> bool:
121
+ """Validate that all samples have input text and target output."""
122
+ for sample in self._samples:
123
+ if not sample.input_text or pd.isna(sample.target_output):
124
+ return False
125
+ return True
126
+
127
+
128
+ class DataFrameDataset(BaseDataset):
129
+ """Dataset loaded from a pandas DataFrame."""
130
+
131
+ def load_data(
132
+ self,
133
+ source: str | Path | pd.DataFrame,
134
+ text_field: str = "text",
135
+ label_field: str = "label",
136
+ id_field: str | None = None,
137
+ **kwargs,
138
+ ) -> None:
139
+ """
140
+ Load data from a pandas DataFrame.
141
+
142
+ Args:
143
+ source: pandas DataFrame
144
+ text_field: Column name for input text
145
+ label_field: Column name for target output
146
+ id_field: Column name for sample ID (auto-generated if None)
147
+ """
148
+ if not isinstance(source, pd.DataFrame):
149
+ raise ValueError("Source must be a pandas DataFrame")
150
+
151
+ df = source
152
+
153
+ if text_field not in df.columns:
154
+ raise ValueError(f"Text field '{text_field}' not found in DataFrame columns")
155
+
156
+ if label_field not in df.columns:
157
+ raise ValueError(f"Label field '{label_field}' not found in DataFrame columns")
158
+
159
+ for i, row in df.iterrows():
160
+ sample_id = str(row[id_field]) if id_field and id_field in df.columns else str(i)
161
+
162
+ # Extract metadata
163
+ metadata_cols = [c for c in df.columns if c not in [text_field, label_field, id_field]]
164
+ metadata = {col: row[col] for col in metadata_cols}
165
+
166
+ sample = DataSample(
167
+ id=sample_id,
168
+ input_text=str(row[text_field]),
169
+ target_output=row[label_field],
170
+ metadata=metadata,
171
+ )
172
+ self.add_sample(sample)
173
+
174
+ def validate_data(self) -> bool:
175
+ """Validate that all samples have input text and target output."""
176
+ for sample in self._samples:
177
+ if not sample.input_text or pd.isna(sample.target_output):
178
+ return False
179
+ return True
@@ -0,0 +1,36 @@
1
+ """
2
+ Evaluator module for EvalRing.
3
+ """
4
+
5
+ from .base import BaseEvaluator, EvaluationMetrics, EvaluationResult
6
+ from .implementations import ClassificationEvaluator
7
+ from .llm_judge import (
8
+ EvalSteps,
9
+ JudgeMetric,
10
+ JudgeTemplate,
11
+ JudgeVerdict,
12
+ LLMJudge,
13
+ LLMJudgeEvaluator,
14
+ OpenAIJudge,
15
+ Rubric,
16
+ RubricLevel,
17
+ ScoringCriteria,
18
+ )
19
+
20
+ __all__ = [
21
+ "BaseEvaluator",
22
+ "EvaluationResult",
23
+ "EvaluationMetrics",
24
+ "ClassificationEvaluator",
25
+ # LLM-as-a-Judge
26
+ "Rubric",
27
+ "RubricLevel",
28
+ "ScoringCriteria",
29
+ "JudgeVerdict",
30
+ "EvalSteps",
31
+ "JudgeTemplate",
32
+ "JudgeMetric",
33
+ "LLMJudge",
34
+ "OpenAIJudge",
35
+ "LLMJudgeEvaluator",
36
+ ]
@@ -0,0 +1,165 @@
1
+ """
2
+ Base classes for evaluators in the EvalRing framework.
3
+ """
4
+
5
+ import json
6
+ from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from ..agent.base import BaseAgent
13
+ from ..dataset.base import BaseDataset
14
+ from ..utils.global_cache import GlobalCache
15
+
16
+
17
+ @dataclass
18
+ class EvaluationMetrics:
19
+ """Container for evaluation metrics."""
20
+
21
+ metrics: dict[str, float] = field(default_factory=dict)
22
+ per_sample_metrics: list[dict[str, float]] = field(default_factory=list)
23
+ metadata: dict[str, Any] = field(default_factory=dict)
24
+
25
+ def add_metric(self, name: str, value: float) -> None:
26
+ """Add a metric."""
27
+ self.metrics[name] = value
28
+
29
+ def get_metric(self, name: str, default: float | None = None) -> float | None:
30
+ """Get a metric by name."""
31
+ return self.metrics.get(name, default)
32
+
33
+ def to_dict(self) -> dict[str, Any]:
34
+ """Convert to dictionary."""
35
+ return {
36
+ "metrics": self.metrics,
37
+ "per_sample_metrics": self.per_sample_metrics,
38
+ "metadata": self.metadata,
39
+ }
40
+
41
+
42
+ @dataclass
43
+ class EvaluationResult:
44
+ """Container for evaluation results."""
45
+
46
+ agent_name: str
47
+ dataset_name: str
48
+ metrics: EvaluationMetrics
49
+ duration: float
50
+ timestamp: datetime
51
+ task_name: str
52
+ version: str = "1.0"
53
+ metadata: dict[str, Any] = field(default_factory=dict)
54
+
55
+ def to_dict(self) -> dict[str, Any]:
56
+ """Convert to dictionary."""
57
+ return {
58
+ "agent_name": self.agent_name,
59
+ "dataset_name": self.dataset_name,
60
+ "metrics": self.metrics.to_dict(),
61
+ "duration": self.duration,
62
+ "timestamp": self.timestamp.isoformat(),
63
+ "task_name": self.task_name,
64
+ "version": self.version,
65
+ "metadata": self.metadata,
66
+ }
67
+
68
+ def save(self, filepath: str | Path) -> None:
69
+ """Save evaluation results to file."""
70
+ with open(filepath, "w") as f:
71
+ json.dump(self.to_dict(), f, indent=2)
72
+
73
+
74
+ class BaseEvaluator(ABC):
75
+ """
76
+ Abstract base class for evaluators in EvalRing.
77
+
78
+ This class orchestrates the evaluation process, managing agents,
79
+ datasets, and metric calculation.
80
+ """
81
+
82
+ def __init__(self, name: str = "evaluator", output_dir: str | Path | None = None, **kwargs):
83
+ self.name = name
84
+ self.output_dir = Path(output_dir) if output_dir else Path("./results")
85
+ self.output_dir.mkdir(parents=True, exist_ok=True)
86
+ self._metadata = kwargs
87
+
88
+ # Initialize global cache
89
+ cache_mode = kwargs.get("cache_mode", "both")
90
+ self.global_cache = GlobalCache(mode=cache_mode)
91
+
92
+ @abstractmethod
93
+ def evaluate(
94
+ self, agent: BaseAgent, dataset: BaseDataset, task_name: str, version: str = "1.0", **kwargs
95
+ ) -> EvaluationResult:
96
+ """
97
+ Run evaluation on the given agent and dataset.
98
+
99
+ Args:
100
+ agent: The agent to evaluate
101
+ dataset: The dataset to evaluate on
102
+ task_name: Name of the evaluation task
103
+ version: Version of the evaluation
104
+ **kwargs: Additional evaluation parameters
105
+
106
+ Returns:
107
+ EvaluationResult containing metrics and metadata
108
+ """
109
+ pass
110
+
111
+ def validate_inputs(self, agent: BaseAgent, dataset: BaseDataset) -> bool:
112
+ """
113
+ Validate agent and dataset before evaluation.
114
+
115
+ Args:
116
+ agent: Agent to validate
117
+ dataset: Dataset to validate
118
+
119
+ Returns:
120
+ True if inputs are valid, False otherwise
121
+ """
122
+ if not isinstance(agent, BaseAgent):
123
+ raise ValueError("Agent must inherit from BaseAgent")
124
+
125
+ if not isinstance(dataset, BaseDataset):
126
+ raise ValueError("Dataset must inherit from BaseDataset")
127
+
128
+ if len(dataset) == 0:
129
+ raise ValueError("Dataset cannot be empty")
130
+
131
+ # Validate data integrity and enforce unique per-sample IDs.
132
+ if hasattr(dataset, "validate_data") and not dataset.validate_data():
133
+ raise ValueError("Dataset validation failed")
134
+ if hasattr(dataset, "assert_unique_ids"):
135
+ dataset.assert_unique_ids(context="evaluator.validate_inputs")
136
+
137
+ return True
138
+
139
+ def save_results(self, result: EvaluationResult, filename: str | None = None) -> Path:
140
+ """
141
+ Save evaluation results to file.
142
+
143
+ Args:
144
+ result: Evaluation result to save
145
+ filename: Optional filename (auto-generated if not provided)
146
+
147
+ Returns:
148
+ Path to the saved file
149
+ """
150
+ if filename is None:
151
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
152
+ filename = f"evaluation_{result.agent_name}_{result.task_name}_{timestamp}.json"
153
+
154
+ filepath = self.output_dir / filename
155
+ result.save(filepath)
156
+ return filepath
157
+
158
+ def get_info(self) -> dict[str, Any]:
159
+ """Get evaluator information."""
160
+ return {
161
+ "name": self.name,
162
+ "type": self.__class__.__name__,
163
+ "output_dir": str(self.output_dir),
164
+ "metadata": self._metadata,
165
+ }