eval-harness 0.2.18
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.
- package/README.md +1046 -0
- package/README.zh-CN.md +597 -0
- package/bin/eval-harness.js +65 -0
- package/config/eval.example.json +70 -0
- package/docs/agent-runner-compatibility.md +47 -0
- package/docs/runners.md +82 -0
- package/package.json +43 -0
- package/prompts/judge.md +27 -0
- package/pyproject.toml +48 -0
- package/schemas/batch-comparison.schema.json +64 -0
- package/schemas/batch-summary.schema.json +85 -0
- package/schemas/evaluation.schema.json +81 -0
- package/schemas/experiment.schema.json +63 -0
- package/schemas/history-index.schema.json +37 -0
- package/schemas/history-record.schema.json +56 -0
- package/schemas/model-report.schema.json +38 -0
- package/schemas/next-action.schema.json +62 -0
- package/schemas/skill-improvement-input.schema.json +65 -0
- package/src/eval/__init__.py +3 -0
- package/src/eval/__main__.py +5 -0
- package/src/eval/cli.py +11150 -0
- package/src/eval/runners/__init__.py +0 -0
- package/src/eval/runners/base.py +197 -0
- package/src/eval/runners/claude.py +516 -0
- package/src/eval/runners/codex.py +412 -0
- package/src/eval/runners/mimocode.py +676 -0
- package/src/eval/runners/opencode.py +554 -0
- package/src/eval/schema.py +151 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def read_json(path: Path) -> dict[str, Any]:
|
|
10
|
+
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def schema_type_matches(value: Any, expected: str) -> bool:
|
|
14
|
+
if expected == "object":
|
|
15
|
+
return isinstance(value, dict)
|
|
16
|
+
if expected == "array":
|
|
17
|
+
return isinstance(value, list)
|
|
18
|
+
if expected == "string":
|
|
19
|
+
return isinstance(value, str)
|
|
20
|
+
if expected == "integer":
|
|
21
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
22
|
+
if expected == "number":
|
|
23
|
+
return isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
24
|
+
if expected == "boolean":
|
|
25
|
+
return isinstance(value, bool)
|
|
26
|
+
if expected == "null":
|
|
27
|
+
return value is None
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def schema_value_type(value: Any) -> str:
|
|
32
|
+
if value is None:
|
|
33
|
+
return "null"
|
|
34
|
+
if isinstance(value, bool):
|
|
35
|
+
return "boolean"
|
|
36
|
+
if isinstance(value, int):
|
|
37
|
+
return "integer"
|
|
38
|
+
if isinstance(value, float):
|
|
39
|
+
return "number"
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
return "string"
|
|
42
|
+
if isinstance(value, list):
|
|
43
|
+
return "array"
|
|
44
|
+
if isinstance(value, dict):
|
|
45
|
+
return "object"
|
|
46
|
+
return type(value).__name__
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_schema_document(data: Any, schema: dict[str, Any], path: str = "$") -> list[str]:
|
|
50
|
+
errors: list[str] = []
|
|
51
|
+
expected_type = schema.get("type")
|
|
52
|
+
expected_types = expected_type if isinstance(expected_type, list) else ([expected_type] if expected_type else [])
|
|
53
|
+
if expected_types and not any(schema_type_matches(data, str(item)) for item in expected_types):
|
|
54
|
+
errors.append(f"{path}: expected {'/'.join(str(item) for item in expected_types)}, got {schema_value_type(data)}")
|
|
55
|
+
return errors
|
|
56
|
+
|
|
57
|
+
if "const" in schema and data != schema["const"]:
|
|
58
|
+
errors.append(f"{path}: expected const {schema['const']!r}, got {data!r}")
|
|
59
|
+
if "enum" in schema and data not in schema.get("enum", []):
|
|
60
|
+
errors.append(f"{path}: expected one of {schema.get('enum')!r}, got {data!r}")
|
|
61
|
+
|
|
62
|
+
if isinstance(data, dict):
|
|
63
|
+
errors.extend(validate_object(data, schema, path))
|
|
64
|
+
if isinstance(data, list):
|
|
65
|
+
errors.extend(validate_array(data, schema, path))
|
|
66
|
+
if isinstance(data, str):
|
|
67
|
+
errors.extend(validate_string(data, schema, path))
|
|
68
|
+
if isinstance(data, (int, float)) and not isinstance(data, bool):
|
|
69
|
+
errors.extend(validate_number(data, schema, path))
|
|
70
|
+
return errors
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate_object(data: dict[str, Any], schema: dict[str, Any], path: str) -> list[str]:
|
|
74
|
+
errors: list[str] = []
|
|
75
|
+
for key in schema.get("required", []) or []:
|
|
76
|
+
if key not in data:
|
|
77
|
+
errors.append(f"{path}.{key}: missing required property")
|
|
78
|
+
|
|
79
|
+
properties = schema.get("properties") or {}
|
|
80
|
+
for key, child_schema in properties.items():
|
|
81
|
+
if key in data and isinstance(child_schema, dict):
|
|
82
|
+
errors.extend(validate_schema_document(data[key], child_schema, f"{path}.{key}"))
|
|
83
|
+
|
|
84
|
+
additional = schema.get("additionalProperties", True)
|
|
85
|
+
if additional is False:
|
|
86
|
+
extra = sorted(key for key in data if key not in properties)
|
|
87
|
+
errors.extend(f"{path}.{key}: additional property not allowed" for key in extra)
|
|
88
|
+
elif isinstance(additional, dict):
|
|
89
|
+
for key, value in data.items():
|
|
90
|
+
if key not in properties:
|
|
91
|
+
errors.extend(validate_schema_document(value, additional, f"{path}.{key}"))
|
|
92
|
+
return errors
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def validate_array(data: list[Any], schema: dict[str, Any], path: str) -> list[str]:
|
|
96
|
+
errors: list[str] = []
|
|
97
|
+
min_items = schema.get("minItems")
|
|
98
|
+
max_items = schema.get("maxItems")
|
|
99
|
+
if isinstance(min_items, int) and len(data) < min_items:
|
|
100
|
+
errors.append(f"{path}: expected at least {min_items} item(s), got {len(data)}")
|
|
101
|
+
if isinstance(max_items, int) and len(data) > max_items:
|
|
102
|
+
errors.append(f"{path}: expected at most {max_items} item(s), got {len(data)}")
|
|
103
|
+
if isinstance(schema.get("items"), dict):
|
|
104
|
+
for index, item in enumerate(data):
|
|
105
|
+
errors.extend(validate_schema_document(item, schema["items"], f"{path}[{index}]"))
|
|
106
|
+
return errors
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def validate_string(data: str, schema: dict[str, Any], path: str) -> list[str]:
|
|
110
|
+
errors: list[str] = []
|
|
111
|
+
min_length = schema.get("minLength")
|
|
112
|
+
max_length = schema.get("maxLength")
|
|
113
|
+
if isinstance(min_length, int) and len(data) < min_length:
|
|
114
|
+
errors.append(f"{path}: expected length >= {min_length}, got {len(data)}")
|
|
115
|
+
if isinstance(max_length, int) and len(data) > max_length:
|
|
116
|
+
errors.append(f"{path}: expected length <= {max_length}, got {len(data)}")
|
|
117
|
+
pattern = schema.get("pattern")
|
|
118
|
+
if isinstance(pattern, str) and re.search(pattern, data) is None:
|
|
119
|
+
errors.append(f"{path}: expected string matching pattern {pattern!r}, got {data!r}")
|
|
120
|
+
return errors
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_number(data: int | float, schema: dict[str, Any], path: str) -> list[str]:
|
|
124
|
+
errors: list[str] = []
|
|
125
|
+
minimum = schema.get("minimum")
|
|
126
|
+
maximum = schema.get("maximum")
|
|
127
|
+
if isinstance(minimum, (int, float)) and data < minimum:
|
|
128
|
+
errors.append(f"{path}: expected >= {minimum}, got {data}")
|
|
129
|
+
if isinstance(maximum, (int, float)) and data > maximum:
|
|
130
|
+
errors.append(f"{path}: expected <= {maximum}, got {data}")
|
|
131
|
+
return errors
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def load_schema(schema_dir: Path, schema_name: str) -> dict[str, Any]:
|
|
135
|
+
schema_path = schema_dir / schema_name
|
|
136
|
+
if not schema_path.exists() and not schema_dir.is_absolute():
|
|
137
|
+
source_tree_schema = Path(__file__).resolve().parents[2] / schema_dir / schema_name
|
|
138
|
+
if source_tree_schema.exists():
|
|
139
|
+
schema_path = source_tree_schema
|
|
140
|
+
if not schema_path.exists():
|
|
141
|
+
raise RuntimeError(f"schema not found: {schema_path}")
|
|
142
|
+
return read_json(schema_path)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def validate_json_artifact(path: Path, schema_dir: Path, schema_name: str) -> list[str]:
|
|
146
|
+
try:
|
|
147
|
+
data = read_json(path)
|
|
148
|
+
except Exception as exc: # noqa: BLE001 - validation command reports exact broken artifact.
|
|
149
|
+
return [f"{path}: invalid json: {exc}"]
|
|
150
|
+
schema = load_schema(schema_dir, schema_name)
|
|
151
|
+
return [f"{path}: {error}" for error in validate_schema_document(data, schema)]
|