judgeval 0.0.1__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.
- judgeval/__init__.py +83 -0
- judgeval/clients.py +19 -0
- judgeval/common/__init__.py +8 -0
- judgeval/common/exceptions.py +28 -0
- judgeval/common/logger.py +189 -0
- judgeval/common/tracer.py +587 -0
- judgeval/common/utils.py +763 -0
- judgeval/constants.py +55 -0
- judgeval/data/__init__.py +14 -0
- judgeval/data/api_example.py +111 -0
- judgeval/data/datasets/__init__.py +4 -0
- judgeval/data/datasets/dataset.py +407 -0
- judgeval/data/datasets/ground_truth.py +54 -0
- judgeval/data/datasets/utils.py +74 -0
- judgeval/data/example.py +76 -0
- judgeval/data/result.py +83 -0
- judgeval/data/scorer_data.py +86 -0
- judgeval/evaluation_run.py +130 -0
- judgeval/judges/__init__.py +7 -0
- judgeval/judges/base_judge.py +44 -0
- judgeval/judges/litellm_judge.py +49 -0
- judgeval/judges/mixture_of_judges.py +248 -0
- judgeval/judges/together_judge.py +55 -0
- judgeval/judges/utils.py +45 -0
- judgeval/judgment_client.py +244 -0
- judgeval/run_evaluation.py +355 -0
- judgeval/scorers/__init__.py +30 -0
- judgeval/scorers/base_scorer.py +51 -0
- judgeval/scorers/custom_scorer.py +134 -0
- judgeval/scorers/judgeval_scorers/__init__.py +21 -0
- judgeval/scorers/judgeval_scorers/answer_relevancy.py +19 -0
- judgeval/scorers/judgeval_scorers/contextual_precision.py +19 -0
- judgeval/scorers/judgeval_scorers/contextual_recall.py +19 -0
- judgeval/scorers/judgeval_scorers/contextual_relevancy.py +22 -0
- judgeval/scorers/judgeval_scorers/faithfulness.py +19 -0
- judgeval/scorers/judgeval_scorers/hallucination.py +19 -0
- judgeval/scorers/judgeval_scorers/json_correctness.py +32 -0
- judgeval/scorers/judgeval_scorers/summarization.py +20 -0
- judgeval/scorers/judgeval_scorers/tool_correctness.py +19 -0
- judgeval/scorers/prompt_scorer.py +439 -0
- judgeval/scorers/score.py +427 -0
- judgeval/scorers/utils.py +175 -0
- judgeval-0.0.1.dist-info/METADATA +40 -0
- judgeval-0.0.1.dist-info/RECORD +46 -0
- judgeval-0.0.1.dist-info/WHEEL +4 -0
- judgeval-0.0.1.dist-info/licenses/LICENSE.md +202 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
|
3
|
+
from judgeval.data.datasets.ground_truth import GroundTruthExample
|
4
|
+
from judgeval.data import Example
|
5
|
+
|
6
|
+
|
7
|
+
def examples_to_ground_truths(examples: List[Example]) -> List[GroundTruthExample]:
|
8
|
+
"""
|
9
|
+
Convert a list of `Example` objects to a list of `GroundTruthExample` objects.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
examples (List[Example]): A list of `Example` objects to convert.
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
List[GroundTruthExample]: A list of `GroundTruthExample` objects.
|
16
|
+
"""
|
17
|
+
|
18
|
+
if not isinstance(examples, list):
|
19
|
+
raise TypeError("Input should be a list of `Example` objects")
|
20
|
+
|
21
|
+
ground_truths = []
|
22
|
+
ground_truths = []
|
23
|
+
for e in examples:
|
24
|
+
g_truth = {
|
25
|
+
"input": e.input,
|
26
|
+
"actual_output": e.actual_output,
|
27
|
+
"expected_output": e.expected_output,
|
28
|
+
"context": e.context,
|
29
|
+
"retrieval_context": e.retrieval_context,
|
30
|
+
"tools_called": e.tools_called,
|
31
|
+
"expected_tools": e.expected_tools,
|
32
|
+
}
|
33
|
+
ground_truths.append(GroundTruthExample(**g_truth))
|
34
|
+
return ground_truths
|
35
|
+
|
36
|
+
|
37
|
+
def ground_truths_to_examples(
|
38
|
+
ground_truths: List[GroundTruthExample],
|
39
|
+
_alias: Optional[str] = None,
|
40
|
+
_id: Optional[str] = None,
|
41
|
+
) -> List[Example]:
|
42
|
+
"""
|
43
|
+
Converts a list of `GroundTruthExample` objects to a list of `Example` objects.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
ground_truths (List[GroundTruthExample]): A list of `GroundTruthExample` objects to convert.
|
47
|
+
_alias (Optional[str]): The alias of the dataset.
|
48
|
+
_id (Optional[str]): The ID of the dataset.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
List[Example]: A list of `Example` objects.
|
52
|
+
"""
|
53
|
+
|
54
|
+
if not isinstance(ground_truths, list):
|
55
|
+
raise TypeError("Input should be a list of `GroundTruthExample` objects")
|
56
|
+
|
57
|
+
examples = []
|
58
|
+
for index, ground_truth in enumerate(ground_truths):
|
59
|
+
e = Example(
|
60
|
+
input=ground_truth.input,
|
61
|
+
actual_output=ground_truth.actual_output,
|
62
|
+
expected_output=ground_truth.expected_output,
|
63
|
+
context=ground_truth.context,
|
64
|
+
retrieval_context=ground_truth.retrieval_context,
|
65
|
+
additional_metadata=ground_truth.additional_metadata,
|
66
|
+
tools_called=ground_truth.tools_called,
|
67
|
+
expected_tools=ground_truth.expected_tools,
|
68
|
+
comments=ground_truth.comments,
|
69
|
+
_dataset_alias=_alias,
|
70
|
+
_dataset_id=_id,
|
71
|
+
_dataset_rank=index,
|
72
|
+
)
|
73
|
+
examples.append(e)
|
74
|
+
return examples
|
judgeval/data/example.py
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
"""
|
2
|
+
Classes for representing examples in a dataset.
|
3
|
+
"""
|
4
|
+
|
5
|
+
|
6
|
+
from typing import TypeVar, Optional, Any, Dict, List
|
7
|
+
from pydantic import BaseModel
|
8
|
+
from enum import Enum
|
9
|
+
from datetime import datetime
|
10
|
+
|
11
|
+
|
12
|
+
Input = TypeVar('Input')
|
13
|
+
Output = TypeVar('Output')
|
14
|
+
|
15
|
+
class ExampleParams(Enum):
|
16
|
+
INPUT = "input"
|
17
|
+
ACTUAL_OUTPUT = "actual_output"
|
18
|
+
EXPECTED_OUTPUT = "expected_output"
|
19
|
+
CONTEXT = "context"
|
20
|
+
RETRIEVAL_CONTEXT = "retrieval_context"
|
21
|
+
TOOLS_CALLED = "tools_called"
|
22
|
+
EXPECTED_TOOLS = "expected_tools"
|
23
|
+
REASONING = "reasoning"
|
24
|
+
|
25
|
+
|
26
|
+
class Example(BaseModel):
|
27
|
+
input: Input
|
28
|
+
actual_output: Output
|
29
|
+
expected_output: Optional[str] = None
|
30
|
+
context: Optional[List[str]] = None
|
31
|
+
retrieval_context: Optional[List[str]] = None
|
32
|
+
additional_metadata: Optional[Dict[str, Any]] = None
|
33
|
+
tools_called: Optional[List[str]] = None
|
34
|
+
expected_tools: Optional[List[str]] = None
|
35
|
+
name: Optional[str] = None
|
36
|
+
example_id: Optional[str] = None
|
37
|
+
timestamp: Optional[str] = None
|
38
|
+
trace_id: Optional[str] = None
|
39
|
+
|
40
|
+
def __init__(self, **data):
|
41
|
+
super().__init__(**data)
|
42
|
+
# Set timestamp if not provided
|
43
|
+
if self.timestamp is None:
|
44
|
+
self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
45
|
+
|
46
|
+
def to_dict(self):
|
47
|
+
return {
|
48
|
+
"input": self.input,
|
49
|
+
"actual_output": self.actual_output,
|
50
|
+
"expected_output": self.expected_output,
|
51
|
+
"context": self.context,
|
52
|
+
"retrieval_context": self.retrieval_context,
|
53
|
+
"additional_metadata": self.additional_metadata,
|
54
|
+
"tools_called": self.tools_called,
|
55
|
+
"expected_tools": self.expected_tools,
|
56
|
+
"name": self.name,
|
57
|
+
"example_id": self.example_id,
|
58
|
+
"timestamp": self.timestamp,
|
59
|
+
"trace_id": self.trace_id
|
60
|
+
}
|
61
|
+
|
62
|
+
def __str__(self):
|
63
|
+
return (
|
64
|
+
f"Example(input={self.input}, "
|
65
|
+
f"actual_output={self.actual_output}, "
|
66
|
+
f"expected_output={self.expected_output}, "
|
67
|
+
f"context={self.context}, "
|
68
|
+
f"retrieval_context={self.retrieval_context}, "
|
69
|
+
f"additional_metadata={self.additional_metadata}, "
|
70
|
+
f"tools_called={self.tools_called}, "
|
71
|
+
f"expected_tools={self.expected_tools}, "
|
72
|
+
f"name={self.name}, "
|
73
|
+
f"example_id={self.example_id}, "
|
74
|
+
f"timestamp={self.timestamp}, "
|
75
|
+
f"trace_id={self.trace_id})"
|
76
|
+
)
|
judgeval/data/result.py
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import List, Union, Optional
|
3
|
+
|
4
|
+
from judgeval.data import ScorerData, ProcessExample
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class ScoringResult:
|
8
|
+
"""
|
9
|
+
A ScoringResult contains the output of one or more scorers applied to a single example.
|
10
|
+
Ie: One input, one actual_output, one expected_output, etc..., and 1+ scorer (Faithfulness, Hallucination, Summarization, etc...)
|
11
|
+
|
12
|
+
Args:
|
13
|
+
success (bool): Whether the evaluation was successful.
|
14
|
+
This means that all scorers applied to this example returned a success.
|
15
|
+
scorer_data (List[ScorerData]): The scorers data for the evaluated example
|
16
|
+
input (Optional[str]): The input to the example
|
17
|
+
actual_output (Optional[str]): The actual output of the example
|
18
|
+
expected_output (Optional[str]): The expected output of the example
|
19
|
+
context (Optional[List[str]]): The context of the example
|
20
|
+
retrieval_context (Optional[List[str]]): The retrieval context of the example
|
21
|
+
trace_id (Optional[str]): The trace id of the example
|
22
|
+
|
23
|
+
"""
|
24
|
+
# Fields for scoring outputs
|
25
|
+
success: bool # used for unit testing
|
26
|
+
scorers_data: Union[List[ScorerData], None]
|
27
|
+
|
28
|
+
# Inputs from the original example
|
29
|
+
input: Optional[str] = None
|
30
|
+
actual_output: Optional[str] = None
|
31
|
+
expected_output: Optional[str] = None
|
32
|
+
context: Optional[List[str]] = None
|
33
|
+
retrieval_context: Optional[List[str]] = None
|
34
|
+
trace_id: Optional[str] = None
|
35
|
+
|
36
|
+
example_id: Optional[str] = None
|
37
|
+
eval_run_name: Optional[str] = None
|
38
|
+
|
39
|
+
def to_dict(self) -> dict:
|
40
|
+
"""Convert the ScoringResult instance to a dictionary, properly serializing scorer_data."""
|
41
|
+
return {
|
42
|
+
"success": self.success,
|
43
|
+
"scorers_data": [scorer_data.to_dict() for scorer_data in self.scorers_data] if self.scorers_data else None,
|
44
|
+
"input": self.input,
|
45
|
+
"actual_output": self.actual_output,
|
46
|
+
"expected_output": self.expected_output,
|
47
|
+
"context": self.context,
|
48
|
+
"retrieval_context": self.retrieval_context,
|
49
|
+
"trace_id": self.trace_id,
|
50
|
+
"example_id": self.example_id
|
51
|
+
}
|
52
|
+
|
53
|
+
def __str__(self) -> str:
|
54
|
+
return f"ScoringResult(\
|
55
|
+
success={self.success}, \
|
56
|
+
scorer_data={self.scorers_data}, \
|
57
|
+
input={self.input}, \
|
58
|
+
actual_output={self.actual_output}, \
|
59
|
+
expected_output={self.expected_output}, \
|
60
|
+
context={self.context}, \
|
61
|
+
retrieval_context={self.retrieval_context}, \
|
62
|
+
trace_id={self.trace_id})"
|
63
|
+
|
64
|
+
|
65
|
+
def generate_scoring_result(
|
66
|
+
process_example: ProcessExample,
|
67
|
+
) -> ScoringResult:
|
68
|
+
"""
|
69
|
+
Creates a final ScoringResult object for an evaluation run based on the results from a completed LLMApiTestCase.
|
70
|
+
|
71
|
+
When an LLMTestCase is executed, it turns into an LLMApiTestCase and the progress of the evaluation run is tracked.
|
72
|
+
At the end of the evaluation run, we create a TestResult object out of the completed LLMApiTestCase.
|
73
|
+
"""
|
74
|
+
return ScoringResult(
|
75
|
+
success=process_example.success,
|
76
|
+
scorers_data=process_example.scorers_data,
|
77
|
+
input=process_example.input,
|
78
|
+
actual_output=process_example.actual_output,
|
79
|
+
expected_output=process_example.expected_output,
|
80
|
+
context=process_example.context,
|
81
|
+
retrieval_context=process_example.retrieval_context,
|
82
|
+
trace_id=process_example.trace_id
|
83
|
+
)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
Implementation of the ScorerData class.
|
3
|
+
|
4
|
+
ScorerData holds the information related to a single, completed Scorer evaluation run.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import List, Union, Optional, Dict
|
8
|
+
from pydantic import BaseModel, Field
|
9
|
+
|
10
|
+
from judgeval.scorers import CustomScorer
|
11
|
+
|
12
|
+
class ScorerData(BaseModel):
|
13
|
+
"""
|
14
|
+
ScorerData holds the information related to a single, completed Scorer evaluation run.
|
15
|
+
|
16
|
+
For example, if running the Judgment Faithfulness scorer on an example, the ScorerData
|
17
|
+
object will contain whether the example passed its threshold expectation, as well as more detailed
|
18
|
+
information surrounding the evaluation run such as the claims and verdicts generated by the
|
19
|
+
judge model(s).
|
20
|
+
"""
|
21
|
+
name: str
|
22
|
+
threshold: float
|
23
|
+
success: bool
|
24
|
+
score: Optional[float] = None
|
25
|
+
reason: Optional[str] = None
|
26
|
+
strict_mode: Optional[bool] = None
|
27
|
+
evaluation_model: Union[List[str], str] = None
|
28
|
+
error: Optional[str] = None
|
29
|
+
evaluation_cost: Union[float, None] = None
|
30
|
+
verbose_logs: Optional[str] = None
|
31
|
+
additional_metadata: Optional[Dict] = None
|
32
|
+
|
33
|
+
def to_dict(self) -> dict:
|
34
|
+
"""Convert the ScorerData instance to a JSON-serializable dictionary."""
|
35
|
+
return {
|
36
|
+
"name": self.name,
|
37
|
+
"threshold": self.threshold,
|
38
|
+
"success": self.success,
|
39
|
+
"score": self.score,
|
40
|
+
"reason": self.reason,
|
41
|
+
"strict_mode": self.strict_mode,
|
42
|
+
"evaluation_model": self.evaluation_model,
|
43
|
+
"error": self.error,
|
44
|
+
"evaluation_cost": self.evaluation_cost,
|
45
|
+
"verbose_logs": self.verbose_logs,
|
46
|
+
"additional_metadata": self.additional_metadata
|
47
|
+
}
|
48
|
+
|
49
|
+
|
50
|
+
def create_scorer_data(scorer: CustomScorer) -> ScorerData:
|
51
|
+
"""
|
52
|
+
After a `scorer` is run, it contains information about the example that was evaluated
|
53
|
+
using the scorer. For example, after computing Faithfulness, the `scorer` object will contain
|
54
|
+
whether the example passed its threshold, the score, the reason for score, etc.
|
55
|
+
|
56
|
+
This function takes an executed `scorer` object and produces a ScorerData object that
|
57
|
+
contains the output of the scorer run that can be exported to be logged as a part of
|
58
|
+
the ScorerResult.
|
59
|
+
"""
|
60
|
+
if scorer.error is not None: # error occurred during eval run
|
61
|
+
return ScorerData(
|
62
|
+
name=scorer.__name__,
|
63
|
+
threshold=scorer.threshold,
|
64
|
+
score=None,
|
65
|
+
reason=None,
|
66
|
+
success=False,
|
67
|
+
strict_mode=scorer.strict_mode,
|
68
|
+
evaluation_model=scorer.evaluation_model,
|
69
|
+
error=scorer.error,
|
70
|
+
evaluation_cost=scorer.evaluation_cost,
|
71
|
+
verbose_logs=scorer.verbose_logs,
|
72
|
+
)
|
73
|
+
else: # standard execution, no error
|
74
|
+
return ScorerData(
|
75
|
+
name=scorer.__name__,
|
76
|
+
score=scorer.score,
|
77
|
+
threshold=scorer.threshold,
|
78
|
+
reason=scorer.reason,
|
79
|
+
success=scorer._success_check(),
|
80
|
+
strict_mode=scorer.strict_mode,
|
81
|
+
evaluation_model=scorer.evaluation_model,
|
82
|
+
error=None,
|
83
|
+
evaluation_cost=scorer.evaluation_cost,
|
84
|
+
verbose_logs=scorer.verbose_logs,
|
85
|
+
additional_metadata=scorer.additional_metadata,
|
86
|
+
)
|
@@ -0,0 +1,130 @@
|
|
1
|
+
from typing import List, Optional, Dict, Any, Union
|
2
|
+
from pydantic import BaseModel, field_validator
|
3
|
+
|
4
|
+
from judgeval.data import Example
|
5
|
+
from judgeval.scorers import CustomScorer, JudgmentScorer
|
6
|
+
from judgeval.judges import judgevalJudge
|
7
|
+
from judgeval.constants import ACCEPTABLE_MODELS
|
8
|
+
from judgeval.common.logger import debug, error
|
9
|
+
|
10
|
+
|
11
|
+
class EvaluationRun(BaseModel):
|
12
|
+
"""
|
13
|
+
Stores example and evaluation scorers together for running an eval task
|
14
|
+
|
15
|
+
Args:
|
16
|
+
project_name (str): The name of the project the evaluation results belong to
|
17
|
+
eval_name (str): A name for this evaluation run
|
18
|
+
examples (List[Example]): The examples to evaluate
|
19
|
+
scorers (List[Union[JudgmentScorer, CustomScorer]]): A list of scorers to use for evaluation
|
20
|
+
model (str): The model used as a judge when using LLM as a Judge
|
21
|
+
aggregator (Optional[str]): The aggregator to use for evaluation if using Mixture of Judges
|
22
|
+
metadata (Optional[Dict[str, Any]]): Additional metadata to include for this evaluation run, e.g. comments, dataset name, purpose, etc.
|
23
|
+
judgment_api_key (Optional[str]): The API key for running evaluations on the Judgment API
|
24
|
+
"""
|
25
|
+
|
26
|
+
# The user will specify whether they want log_results when they call run_eval
|
27
|
+
log_results: bool = False # NOTE: log_results has to be set first because it is used to validate project_name and eval_name
|
28
|
+
project_name: Optional[str] = None
|
29
|
+
eval_name: Optional[str] = None
|
30
|
+
examples: List[Example]
|
31
|
+
scorers: List[Union[JudgmentScorer, CustomScorer]]
|
32
|
+
model: Union[str, List[str], judgevalJudge]
|
33
|
+
aggregator: Optional[str] = None
|
34
|
+
metadata: Optional[Dict[str, Any]] = None
|
35
|
+
# API Key will be "" until user calls client.run_eval(), then API Key will be set
|
36
|
+
judgment_api_key: Optional[str] = ""
|
37
|
+
|
38
|
+
def model_dump(self, **kwargs):
|
39
|
+
data = super().model_dump(**kwargs)
|
40
|
+
|
41
|
+
data["scorers"] = [
|
42
|
+
scorer.to_dict() \
|
43
|
+
if hasattr(scorer, "to_dict") else {"score_type": scorer.score_type, "threshold": scorer.threshold}
|
44
|
+
for scorer in self.scorers
|
45
|
+
]
|
46
|
+
return data
|
47
|
+
|
48
|
+
@field_validator('log_results', mode='before')
|
49
|
+
def validate_log_results(cls, v):
|
50
|
+
if not isinstance(v, bool):
|
51
|
+
raise ValueError(f"log_results must be a boolean. Received {v} of type {type(v)}")
|
52
|
+
return v
|
53
|
+
|
54
|
+
@field_validator('project_name')
|
55
|
+
def validate_project_name(cls, v, values):
|
56
|
+
if values.data.get('log_results', False) and not v:
|
57
|
+
debug("No project name provided when log_results is True")
|
58
|
+
error("Validation failed: Project name required when logging results")
|
59
|
+
raise ValueError("Project name is required when log_results is True. Please include the project_name argument.")
|
60
|
+
return v
|
61
|
+
|
62
|
+
@field_validator('eval_name')
|
63
|
+
def validate_eval_name(cls, v, values):
|
64
|
+
if values.data.get('log_results', False) and not v:
|
65
|
+
debug("No eval name provided when log_results is True")
|
66
|
+
error("Validation failed: Eval name required when logging results")
|
67
|
+
raise ValueError("Eval name is required when log_results is True. Please include the eval_run_name argument.")
|
68
|
+
return v
|
69
|
+
|
70
|
+
@field_validator('examples')
|
71
|
+
def validate_examples(cls, v):
|
72
|
+
if not v:
|
73
|
+
raise ValueError("Examples cannot be empty.")
|
74
|
+
for ex in v:
|
75
|
+
if not isinstance(ex, Example):
|
76
|
+
raise ValueError(f"Invalid type for Example: {type(ex)}")
|
77
|
+
return v
|
78
|
+
|
79
|
+
@field_validator('scorers')
|
80
|
+
def validate_scorers(cls, v):
|
81
|
+
if not v:
|
82
|
+
raise ValueError("Scorers cannot be empty.")
|
83
|
+
for s in v:
|
84
|
+
if not isinstance(s, JudgmentScorer) and not isinstance(s, CustomScorer):
|
85
|
+
raise ValueError(f"Invalid type for Scorer: {type(s)}")
|
86
|
+
return v
|
87
|
+
|
88
|
+
@field_validator('model')
|
89
|
+
def validate_model(cls, v, values):
|
90
|
+
if not v:
|
91
|
+
raise ValueError("Model cannot be empty.")
|
92
|
+
# Check if model is a judgevalJudge
|
93
|
+
if isinstance(v, judgevalJudge):
|
94
|
+
# Verify all scorers are CustomScorer when using judgevalJudge
|
95
|
+
scorers = values.data.get('scorers', [])
|
96
|
+
if not all(isinstance(s, CustomScorer) for s in scorers):
|
97
|
+
raise ValueError("When using a judgevalJudge model, all scorers must be CustomScorer type")
|
98
|
+
return v
|
99
|
+
|
100
|
+
# Check if model is string or list of strings
|
101
|
+
if isinstance(v, str):
|
102
|
+
if v not in ACCEPTABLE_MODELS:
|
103
|
+
raise ValueError(f"Model name {v} not recognized.")
|
104
|
+
return v
|
105
|
+
|
106
|
+
if isinstance(v, list):
|
107
|
+
if not all(isinstance(m, str) for m in v):
|
108
|
+
raise ValueError("When providing a list of models, all elements must be strings")
|
109
|
+
for m in v:
|
110
|
+
if m not in ACCEPTABLE_MODELS:
|
111
|
+
raise ValueError(f"Model name {m} not recognized.")
|
112
|
+
return v
|
113
|
+
raise ValueError(f"Model must be one of: string, list of strings, or judgevalJudge instance. Received type {type(v)}.")
|
114
|
+
|
115
|
+
@field_validator('aggregator', mode='before')
|
116
|
+
def validate_aggregator(cls, v, values):
|
117
|
+
model = values.data.get('model')
|
118
|
+
if isinstance(model, list) and v is None:
|
119
|
+
raise ValueError("Aggregator cannot be empty.")
|
120
|
+
|
121
|
+
if isinstance(model, list) and not isinstance(v, str):
|
122
|
+
raise ValueError("Aggregator must be a string if provided.")
|
123
|
+
|
124
|
+
if v is not None and v not in ACCEPTABLE_MODELS:
|
125
|
+
raise ValueError(f"Model name {v} not recognized.")
|
126
|
+
|
127
|
+
return v
|
128
|
+
|
129
|
+
class Config:
|
130
|
+
arbitrary_types_allowed = True
|
@@ -0,0 +1,7 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from judgeval.judges.base_judge import judgevalJudge
|
3
|
+
from judgeval.judges.litellm_judge import LiteLLMJudge
|
4
|
+
from judgeval.judges.together_judge import TogetherJudge
|
5
|
+
from judgeval.judges.mixture_of_judges import MixtureOfJudges
|
6
|
+
|
7
|
+
__all__ = ["judgevalJudge", "LiteLLMJudge", "TogetherJudge", "MixtureOfJudges"]
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"""
|
2
|
+
Implements the base class for all Judgeval Judge models.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
from typing import Optional, List
|
7
|
+
|
8
|
+
|
9
|
+
class judgevalJudge(ABC):
|
10
|
+
def __init__(self, model_name: Optional[str] = None, *args, **kwargs):
|
11
|
+
self.model_name = model_name
|
12
|
+
self.model = self.load_model(*args, **kwargs)
|
13
|
+
|
14
|
+
@abstractmethod
|
15
|
+
def load_model(self, *args, **kwargs):
|
16
|
+
"""Loads a model, that will be responsible for scoring.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
A model object
|
20
|
+
"""
|
21
|
+
pass
|
22
|
+
|
23
|
+
@abstractmethod
|
24
|
+
def generate(self, *args, **kwargs) -> str:
|
25
|
+
"""Runs the model to output LLM response.
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
A string.
|
29
|
+
"""
|
30
|
+
pass
|
31
|
+
|
32
|
+
@abstractmethod
|
33
|
+
async def a_generate(self, *args, **kwargs) -> str:
|
34
|
+
"""Runs the model to output LLM response.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
A string.
|
38
|
+
"""
|
39
|
+
pass
|
40
|
+
|
41
|
+
@abstractmethod
|
42
|
+
def get_model_name(self, *args, **kwargs) -> str:
|
43
|
+
pass
|
44
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import pydantic
|
2
|
+
from typing import List, Union, Mapping
|
3
|
+
|
4
|
+
from judgeval import *
|
5
|
+
from judgeval.judges import judgevalJudge
|
6
|
+
from judgeval.common.utils import afetch_litellm_api_response, fetch_litellm_api_response
|
7
|
+
from judgeval.common.logger import debug, error
|
8
|
+
|
9
|
+
BASE_CONVERSATION = [
|
10
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
11
|
+
] # for string inputs, we need to add the user query to a base conversation, since LiteLLM only accepts a list of dictionaries as a chat history
|
12
|
+
|
13
|
+
|
14
|
+
class LiteLLMJudge(judgevalJudge):
|
15
|
+
def __init__(self, model: str = "gpt-4o-mini", **kwargs):
|
16
|
+
debug(f"Initializing LiteLLMJudge with model={model}")
|
17
|
+
self.model = model
|
18
|
+
self.kwargs = kwargs
|
19
|
+
super().__init__(model_name=model)
|
20
|
+
|
21
|
+
def generate(self, input: Union[str, List[Mapping[str, str]]], schema: pydantic.BaseModel = None) -> str:
|
22
|
+
debug(f"Generating response for input type: {type(input)}")
|
23
|
+
if isinstance(input, str):
|
24
|
+
convo = BASE_CONVERSATION + [{"role": "user", "content": input}]
|
25
|
+
return fetch_litellm_api_response(model=self.model, messages=convo, response_format=schema)
|
26
|
+
elif isinstance(input, list):
|
27
|
+
return fetch_litellm_api_response(model=self.model, messages=input, response_format=schema)
|
28
|
+
else:
|
29
|
+
error(f"Invalid input type received: {type(input)}")
|
30
|
+
raise TypeError(f"Input must be a string or a list of dictionaries. Input type of: {type(input)}")
|
31
|
+
|
32
|
+
async def a_generate(self, input: Union[str, List[Mapping[str, str]]], schema: pydantic.BaseModel = None) -> str:
|
33
|
+
debug(f"Async generating response for input type: {type(input)}")
|
34
|
+
if isinstance(input, str):
|
35
|
+
convo = BASE_CONVERSATION + [{"role": "user", "content": input}]
|
36
|
+
response = await afetch_litellm_api_response(model=self.model, messages=convo, response_format=schema)
|
37
|
+
return response
|
38
|
+
elif isinstance(input, list):
|
39
|
+
response = await afetch_litellm_api_response(model=self.model, messages=input, response_format=schema)
|
40
|
+
return response
|
41
|
+
else:
|
42
|
+
error(f"Invalid input type received: {type(input)}")
|
43
|
+
raise TypeError(f"Input must be a string or a list of dictionaries. Input type of: {type(input)}")
|
44
|
+
|
45
|
+
def load_model(self):
|
46
|
+
return self.model
|
47
|
+
|
48
|
+
def get_model_name(self) -> str:
|
49
|
+
return self.model
|