openevals 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.
evaluators/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ from .exact import exact_match, exact_match_async
2
+ from .llm import create_llm_as_judge, create_async_llm_as_judge
3
+
4
+ __all__ = [
5
+ "exact_match",
6
+ "exact_match_async",
7
+ "create_llm_as_judge",
8
+ "create_async_llm_as_judge",
9
+ ]
evaluators/exact.py ADDED
@@ -0,0 +1,56 @@
1
+ from evaluators.types import EvaluatorResult
2
+ from evaluators.utils import _run_evaluator, _arun_evaluator
3
+
4
+ import json
5
+ from typing import Any
6
+
7
+
8
+ def _scorer(inputs: Any, outputs: Any) -> bool:
9
+ if inputs is None or outputs is None:
10
+ raise ValueError("Exact match requires both inputs and outputs")
11
+ # Convert both to JSON strings for deep comparison
12
+ inputs_json = json.dumps(inputs, sort_keys=True)
13
+ outputs_json = json.dumps(outputs, sort_keys=True)
14
+ return inputs_json == outputs_json
15
+
16
+
17
+ def exact_match(*, inputs: Any, outputs: Any, **kwargs: Any) -> EvaluatorResult:
18
+ """
19
+ Performs exact matching between input and output values.
20
+
21
+ Args:
22
+ inputs (Any): Inputs to compare
23
+ outputs (Any): Outputs to compare
24
+
25
+ Returns:
26
+ EvaluatorResult: Contains match result
27
+ """
28
+
29
+ def get_score():
30
+ return _scorer(inputs, outputs)
31
+
32
+ return _run_evaluator(
33
+ run_name="exact_match", scorer=get_score, feedback_key="exact_match"
34
+ )
35
+
36
+
37
+ async def exact_match_async(
38
+ *, inputs: Any, outputs: Any, **kwargs: Any
39
+ ) -> EvaluatorResult:
40
+ """
41
+ Performs exact matching between input and output values.
42
+
43
+ Args:
44
+ inputs (Any): Inputs to compare
45
+ outputs (Any): Outputs to compare
46
+
47
+ Returns:
48
+ EvaluatorResult: Contains match result
49
+ """
50
+
51
+ async def get_score():
52
+ return _scorer(inputs, outputs)
53
+
54
+ return await _arun_evaluator(
55
+ run_name="exact_match", scorer=get_score, feedback_key="exact_match"
56
+ )
@@ -0,0 +1,13 @@
1
+ from .match import (
2
+ create_json_match_evaluator,
3
+ create_async_json_match_evaluator,
4
+ SYSTEM_PROMPT,
5
+ USER_PROMPT,
6
+ )
7
+
8
+ __all__ = [
9
+ "create_json_match_evaluator",
10
+ "create_async_json_match_evaluator",
11
+ "SYSTEM_PROMPT",
12
+ "USER_PROMPT",
13
+ ]
@@ -0,0 +1,437 @@
1
+ from typing import Literal, Optional, Dict, Any, Union
2
+ from evaluators.types import EvaluatorResult, SimpleEvaluator, SimpleAsyncEvaluator
3
+ from evaluators.utils import _run_evaluator, _arun_evaluator
4
+ from evaluators.llm import (
5
+ _create_llm_as_judge_scorer,
6
+ _create_async_llm_as_judge_scorer,
7
+ ModelClient,
8
+ LangChainLikeModel,
9
+ )
10
+
11
+ SYSTEM_PROMPT = """You are an LLM that evaluates the accuracy of structured outputs.
12
+ Make sure to evaluate each key the users ask you to evaluate separately. Assign the score
13
+ for each key based on its own criteria - DO NOT convolute the scores of different keys.
14
+ Also only evaluate the output vs. the reference output based on the criteria. DO NOT EVALUATE
15
+ BASED ON ANYTHING ELSE. If the output does not match the reference output in some way that
16
+ is not mentioned in the criteria that is not a problem and you should ignore those discrepancies.
17
+ Only focus on finding discrepancies based on the criteria.
18
+ """
19
+
20
+ USER_PROMPT = """Please evaluate the accuracy of the following output keys according to these criteria:
21
+ {rubric}
22
+ <Outputs>
23
+ {outputs}
24
+ </Outputs>
25
+ <Expected Outputs>
26
+ {reference_outputs}
27
+ </Expected Outputs>"""
28
+
29
+
30
+ def _prepare_parameters(
31
+ *,
32
+ outputs: Any,
33
+ reference_outputs: Any,
34
+ rubric: Dict[str, str],
35
+ exclude_keys: list[str],
36
+ use_reasoning: bool,
37
+ ):
38
+ json_schema = {
39
+ "type": "object",
40
+ "title": "structured_match_score",
41
+ "description": "Scores measuring the accuracy of structured outputs",
42
+ "properties": {},
43
+ "required": [],
44
+ "additionalProperties": False,
45
+ }
46
+
47
+ scores = {}
48
+ formatted_rubric = ""
49
+ use_list_reducer = False
50
+ if isinstance(outputs, list):
51
+ use_list_reducer = True
52
+ if not isinstance(reference_outputs, list):
53
+ raise ValueError(
54
+ "If outputs is a list, reference_outputs must also be a list"
55
+ )
56
+ outputs_to_use = {}
57
+ for i in range(len(outputs)):
58
+ for key, value in outputs[i].items():
59
+ outputs_to_use[f"{key}_{i}"] = value
60
+ outputs = outputs_to_use
61
+ reference_outputs_to_use = {}
62
+ for i in range(len(reference_outputs)):
63
+ for key, value in reference_outputs[i].items():
64
+ reference_outputs_to_use[f"{key}_{i}"] = value
65
+ reference_outputs = reference_outputs_to_use
66
+
67
+ for raw_key, value in outputs.items():
68
+ if use_list_reducer:
69
+ key = raw_key[: raw_key.rfind("_")]
70
+ else:
71
+ key = raw_key
72
+ if key in exclude_keys:
73
+ continue
74
+ if raw_key not in reference_outputs:
75
+ scores[raw_key] = 0
76
+ continue
77
+ if key not in rubric and reference_outputs[raw_key] == value:
78
+ scores[raw_key] = 1
79
+ elif key not in rubric:
80
+ scores[raw_key] = 0
81
+ else:
82
+ key_criteria = rubric[key]
83
+ formatted_rubric += f"Key: {key}, Criteria: {key_criteria}\n"
84
+ if not use_reasoning:
85
+ json_schema["properties"][raw_key] = {
86
+ "type": "boolean",
87
+ "description": f"Does the output for key {key}, follow the criteria? {key_criteria}",
88
+ }
89
+ else:
90
+ json_schema["properties"][raw_key] = {
91
+ "type": "object",
92
+ "properties": {
93
+ "reasoning": {
94
+ "type": "string",
95
+ "description": f"Reasoning for the score you assigned to key {key}",
96
+ },
97
+ "score": {
98
+ "type": "boolean",
99
+ "description": f"Does the output for key {key}, follow the criteria? {key_criteria}",
100
+ },
101
+ },
102
+ "required": ["score", "reasoning"],
103
+ "additionalProperties": False,
104
+ }
105
+ for raw_key, value in reference_outputs.items():
106
+ if use_list_reducer:
107
+ key = raw_key[: raw_key.rfind("_")]
108
+ else:
109
+ key = raw_key
110
+ if key not in exclude_keys and raw_key not in outputs:
111
+ scores[raw_key] = 0
112
+
113
+ return (
114
+ outputs,
115
+ reference_outputs,
116
+ json_schema,
117
+ scores,
118
+ formatted_rubric,
119
+ use_list_reducer,
120
+ )
121
+
122
+
123
+ def _aggregate_results(
124
+ *,
125
+ scores: dict,
126
+ use_list_reducer: bool,
127
+ aggregator: Optional[Literal["average", "all"]],
128
+ list_aggregator: Literal["average", "all"],
129
+ ) -> dict:
130
+ if use_list_reducer:
131
+ scores_aggregated_across_list = {}
132
+ keys = set([k[: k.rfind("_")] for k in scores.keys()])
133
+ if list_aggregator == "average":
134
+ for key in keys:
135
+ scores_aggregated_across_list[key] = sum(
136
+ [
137
+ (
138
+ float(scores[k]["score"])
139
+ if isinstance(scores[k], dict)
140
+ else scores[k]
141
+ )
142
+ for k in scores
143
+ if k[: k.rfind("_")] == key
144
+ ]
145
+ ) / len([scores[k] for k in scores if k[: k.rfind("_")] == key])
146
+ elif list_aggregator == "all":
147
+ for key in keys:
148
+ scores_aggregated_across_list[key] = (
149
+ 0
150
+ if 0
151
+ in [
152
+ (
153
+ float(scores[k]["score"])
154
+ if isinstance(scores[k], dict)
155
+ else scores[k]
156
+ )
157
+ for k in scores
158
+ if k[: k.rfind("_")] == key
159
+ ]
160
+ else 1
161
+ )
162
+ scores = scores_aggregated_across_list
163
+
164
+ score = None
165
+ if aggregator == "average":
166
+ score = sum(
167
+ [float(v["score"]) if isinstance(v, dict) else v for v in scores.values()]
168
+ ) / len(scores)
169
+ elif aggregator == "all":
170
+ score = (
171
+ 0
172
+ if any(
173
+ [
174
+ (float(v["score"]) if isinstance(v, dict) else v) != 1
175
+ for v in scores.values()
176
+ ]
177
+ )
178
+ else 1
179
+ )
180
+
181
+ # If there is an aggregator, return a single result
182
+ if score is not None:
183
+ return score
184
+ else:
185
+ results = {}
186
+ for key, value in scores.items():
187
+ results[key] = value
188
+ if len(results) == 1:
189
+ ans = list(results.values())[0]
190
+ if isinstance(ans, dict):
191
+ return (float(ans["score"]), ans["reasoning"])
192
+ return ans
193
+ return results
194
+
195
+
196
+ def create_json_match_evaluator(
197
+ *,
198
+ aggregator: Optional[Literal["average", "all"]] = None,
199
+ list_aggregator: Literal["average", "all"] = "all",
200
+ rubric: Dict[str, str] = {},
201
+ exclude_keys: list[str] = [],
202
+ judge: Optional[
203
+ Union[
204
+ ModelClient,
205
+ LangChainLikeModel,
206
+ ]
207
+ ] = None,
208
+ model: str = "openai:o3-mini",
209
+ use_reasoning: bool = True,
210
+ ) -> SimpleEvaluator:
211
+ """
212
+ Create an evaluator to evaluate the accuracy of structured outputs.
213
+
214
+ Parameters:
215
+ aggregator (Optional[Literal["average", "all"]]): The aggregation method to use for combining the keys of each structured object.
216
+ Defaults to None. If None, will return a single EvaluatorResult for each key that appears in either
217
+ the outputs or the reference_outputs or both. If "average", will return a single EvaluatorResult that
218
+ is the average of the feedback for each key in the outputs/reference_outputs. If "all", will return
219
+ a single EvaluatorResult that is a combined and statement of the feedback for each key in the outputs/reference_outputs.
220
+ If "all"/"average" the feedback key returned will be called "structured_match_score
221
+ list_aggregator (Literal["average", "all"]): The aggregation method to use when evaluating a list of outputs.
222
+ Defaults to "all". If "all", the score for a single feedback key will be a combined and statement of the scores for
223
+ that key across all elements of the list. If "average", the score for a single feedback key will be the
224
+ average of the scores for that key across all elements of the list
225
+ rubric (Optional[Dict[str,str]]): The rubric to use for the judge. Each entry of the dict is a
226
+ key/value pair where the key is the structured output key and the value is the criteria for the LLM to
227
+ evaluate that key on against the reference output.
228
+ exclude_keys (Optional[list[str]]): The keys to exclude from the evaluation. Use this if there are
229
+ keys in your structured output you don't care about evaluating. Every key not in `exclude_keys` or in `rubric`
230
+ will be evaluated for exact match with the reference output.
231
+ judge (ModelClient or LangChainLikeModel): The judge to use for the evaluation.
232
+ model (str): The model to use for the evaluation.
233
+ use_reasoning (bool): Whether to use reasoning for the keys in `rubric`. Defaults to True.
234
+
235
+ Returns:
236
+ A function that takes in outputs and reference_outputs and returns an EvaluatorResult or list of EvaluatorResults.
237
+ """
238
+
239
+ def wrapped_evaluator(
240
+ *,
241
+ outputs: Any,
242
+ reference_outputs: Any,
243
+ **kwargs,
244
+ ) -> EvaluatorResult | list[EvaluatorResult]:
245
+ def _scorer(
246
+ *,
247
+ outputs: Any,
248
+ reference_outputs: Any,
249
+ rubric: Optional[str] = None,
250
+ exclude_keys: Optional[list[str]] = None,
251
+ use_reasoning: Optional[bool] = None,
252
+ ) -> Union[float, bool, dict]:
253
+ (
254
+ outputs,
255
+ reference_outputs,
256
+ json_schema,
257
+ scores,
258
+ formatted_rubric,
259
+ use_list_reducer,
260
+ ) = _prepare_parameters(
261
+ outputs=outputs,
262
+ reference_outputs=reference_outputs,
263
+ rubric=rubric,
264
+ exclude_keys=exclude_keys,
265
+ use_reasoning=use_reasoning,
266
+ )
267
+
268
+ scorer = None
269
+ if len(formatted_rubric) > 0:
270
+ output_keys = "\n".join(
271
+ [f"{key}: {outputs[key]}" for key in json_schema["properties"]]
272
+ )
273
+ expected_output_keys = "\n".join(
274
+ [
275
+ f"{key}: {reference_outputs[key]}"
276
+ for key in json_schema["properties"]
277
+ ]
278
+ )
279
+ scorer = _create_llm_as_judge_scorer(
280
+ system=SYSTEM_PROMPT,
281
+ prompt=USER_PROMPT,
282
+ schema=json_schema,
283
+ judge=judge,
284
+ model=model,
285
+ )
286
+ else:
287
+ formatted_rubric, output_keys, expected_output_keys = None, None, None
288
+ if scorer is not None:
289
+ llm_scores = scorer(
290
+ outputs=output_keys,
291
+ reference_outputs=expected_output_keys,
292
+ rubric=rubric,
293
+ )
294
+ scores.update(llm_scores)
295
+
296
+ return _aggregate_results(
297
+ scores=scores,
298
+ use_list_reducer=use_list_reducer,
299
+ aggregator=aggregator,
300
+ list_aggregator=list_aggregator,
301
+ )
302
+
303
+ return _run_evaluator(
304
+ run_name="structured_match_evaluator",
305
+ scorer=_scorer,
306
+ feedback_key="structured_match_score",
307
+ rubric=rubric,
308
+ outputs=outputs,
309
+ reference_outputs=reference_outputs,
310
+ exclude_keys=exclude_keys,
311
+ use_reasoning=use_reasoning,
312
+ **kwargs,
313
+ )
314
+
315
+ return wrapped_evaluator
316
+
317
+
318
+ def create_async_json_match_evaluator(
319
+ *,
320
+ aggregator: Optional[Literal["average", "all"]] = None,
321
+ list_aggregator: Literal["average", "all"] = "all",
322
+ rubric: Dict[str, str] = {},
323
+ exclude_keys: list[str] = [],
324
+ judge: Optional[
325
+ Union[
326
+ ModelClient,
327
+ LangChainLikeModel,
328
+ ]
329
+ ] = None,
330
+ model: str = "openai:o3-mini",
331
+ use_reasoning: bool = True,
332
+ ) -> SimpleAsyncEvaluator:
333
+ """
334
+ Create an evaluator to evaluate the accuracy of structured outputs.
335
+
336
+ Parameters:
337
+ aggregator (Optional[Literal["average", "all"]]): The aggregation method to use for combining the keys of each structured object.
338
+ Defaults to None. If None, will return a single EvaluatorResult for each key that appears in either
339
+ the outputs or the reference_outputs or both. If "average", will return a single EvaluatorResult that
340
+ is the average of the feedback for each key in the outputs/reference_outputs. If "all", will return
341
+ a single EvaluatorResult that is a combined and statement of the feedback for each key in the outputs/reference_outputs.
342
+ If "all"/"average" the feedback key returned will be called "structured_match_score
343
+ list_aggregator (Literal["average", "all"]): The aggregation method to use when evaluating a list of outputs.
344
+ Defaults to "all". If "all", the score for a single feedback key will be a combined and statement of the scores for
345
+ that key across all elements of the list. If "average", the score for a single feedback key will be the
346
+ average of the scores for that key across all elements of the list
347
+ rubric (Optional[Dict[str,str]]): The rubric to use for the judge. Each entry of the dict is a
348
+ key/value pair where the key is the structured output key and the value is the criteria for the LLM to
349
+ evaluate that key on against the reference output.
350
+ exclude_keys (Optional[list[str]]): The keys to exclude from the evaluation. Use this if there are
351
+ keys in your structured output you don't care about evaluating. Every key not in `exclude_keys` or in `rubric`
352
+ will be evaluated for exact match with the reference output.
353
+ judge (ModelClient or LangChainLikeModel): The judge to use for the evaluation.
354
+ model (str): The model to use for the evaluation.
355
+ use_reasoning (bool): Whether to use reasoning for the keys in `rubric`. Defaults to True.
356
+
357
+ Returns:
358
+ A function that takes in outputs and reference_outputs and returns an EvaluatorResult or list of EvaluatorResults.
359
+ """
360
+
361
+ async def wrapped_evaluator(
362
+ *,
363
+ outputs: Any,
364
+ reference_outputs: Any,
365
+ **kwargs,
366
+ ) -> EvaluatorResult | list[EvaluatorResult]:
367
+ async def _ascorer(
368
+ *,
369
+ outputs: Any,
370
+ reference_outputs: Any,
371
+ rubric: Optional[str] = None,
372
+ exclude_keys: Optional[list[str]] = None,
373
+ use_reasoning: Optional[bool] = None,
374
+ ) -> Union[float, bool, dict]:
375
+ (
376
+ outputs,
377
+ reference_outputs,
378
+ json_schema,
379
+ scores,
380
+ formatted_rubric,
381
+ use_list_reducer,
382
+ ) = _prepare_parameters(
383
+ outputs=outputs,
384
+ reference_outputs=reference_outputs,
385
+ rubric=rubric,
386
+ exclude_keys=exclude_keys,
387
+ use_reasoning=use_reasoning,
388
+ )
389
+
390
+ scorer = None
391
+ if len(formatted_rubric) > 0:
392
+ output_keys = "\n".join(
393
+ [f"{key}: {outputs[key]}" for key in json_schema["properties"]]
394
+ )
395
+ expected_output_keys = "\n".join(
396
+ [
397
+ f"{key}: {reference_outputs[key]}"
398
+ for key in json_schema["properties"]
399
+ ]
400
+ )
401
+ scorer = _create_async_llm_as_judge_scorer(
402
+ system=SYSTEM_PROMPT,
403
+ prompt=USER_PROMPT,
404
+ schema=json_schema,
405
+ judge=judge,
406
+ model=model,
407
+ )
408
+ else:
409
+ formatted_rubric, output_keys, expected_output_keys = None, None, None
410
+ if scorer is not None:
411
+ llm_scores = await scorer(
412
+ outputs=output_keys,
413
+ reference_outputs=expected_output_keys,
414
+ rubric=rubric,
415
+ )
416
+ scores.update(llm_scores)
417
+
418
+ return _aggregate_results(
419
+ scores=scores,
420
+ use_list_reducer=use_list_reducer,
421
+ aggregator=aggregator,
422
+ list_aggregator=list_aggregator,
423
+ )
424
+
425
+ return await _arun_evaluator(
426
+ run_name="structured_match_evaluator",
427
+ scorer=_ascorer,
428
+ feedback_key="structured_match_score",
429
+ rubric=rubric,
430
+ outputs=outputs,
431
+ reference_outputs=reference_outputs,
432
+ exclude_keys=exclude_keys,
433
+ use_reasoning=use_reasoning,
434
+ **kwargs,
435
+ )
436
+
437
+ return wrapped_evaluator