evalwise 0.1.0__tar.gz
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.
- evalwise-0.1.0/.gitignore +38 -0
- evalwise-0.1.0/PKG-INFO +255 -0
- evalwise-0.1.0/README.md +192 -0
- evalwise-0.1.0/examples/audio_data.json +6 -0
- evalwise-0.1.0/examples/code_data.json +14 -0
- evalwise-0.1.0/examples/image_data.json +12 -0
- evalwise-0.1.0/examples/sample_audio.wav +0 -0
- evalwise-0.1.0/examples/sample_cat.jpeg +0 -0
- evalwise-0.1.0/examples/summarization_data.json +14 -0
- evalwise-0.1.0/examples/test_audio.py +29 -0
- evalwise-0.1.0/examples/test_basic.py +26 -0
- evalwise-0.1.0/examples/test_code_generation.py +53 -0
- evalwise-0.1.0/examples/test_image_generation.py +63 -0
- evalwise-0.1.0/examples/test_summarization.py +52 -0
- evalwise-0.1.0/pyproject.toml +91 -0
- evalwise-0.1.0/src/evalwise/__init__.py +23 -0
- evalwise-0.1.0/src/evalwise/__main__.py +6 -0
- evalwise-0.1.0/src/evalwise/audio/__init__.py +5 -0
- evalwise-0.1.0/src/evalwise/audio/assertions.py +335 -0
- evalwise-0.1.0/src/evalwise/cli.py +251 -0
- evalwise-0.1.0/src/evalwise/core/__init__.py +7 -0
- evalwise-0.1.0/src/evalwise/core/context.py +47 -0
- evalwise-0.1.0/src/evalwise/core/dataset.py +102 -0
- evalwise-0.1.0/src/evalwise/core/result.py +142 -0
- evalwise-0.1.0/src/evalwise/core/suite.py +243 -0
- evalwise-0.1.0/src/evalwise/image/__init__.py +5 -0
- evalwise-0.1.0/src/evalwise/image/assertions.py +382 -0
- evalwise-0.1.0/src/evalwise/text/__init__.py +5 -0
- evalwise-0.1.0/src/evalwise/text/assertions.py +854 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Byte-compiled
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
venv/
|
|
14
|
+
.venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
|
|
23
|
+
# Testing
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.coverage
|
|
26
|
+
htmlcov/
|
|
27
|
+
.mypy_cache/
|
|
28
|
+
|
|
29
|
+
# Model weights / downloaded artifacts
|
|
30
|
+
*.pt
|
|
31
|
+
*.pth
|
|
32
|
+
*.ckpt
|
|
33
|
+
*.safetensors
|
|
34
|
+
*.bin
|
|
35
|
+
|
|
36
|
+
# OS
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
evalwise-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: evalwise
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic-first AI evaluation for text, image, audio, and video
|
|
5
|
+
Project-URL: Homepage, https://github.com/shreyaspj20/evalwise
|
|
6
|
+
Project-URL: Documentation, https://github.com/shreyaspj20/evalwise#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/shreyaspj20/evalwise
|
|
8
|
+
Author-email: Shreyas Reddy <shreyaspj20@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: ai,evaluation,llm,machine-learning,ml,testing
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: click>=8.0
|
|
21
|
+
Requires-Dist: httpx>=0.25
|
|
22
|
+
Requires-Dist: jsonschema>=4.0
|
|
23
|
+
Requires-Dist: langdetect>=1.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Requires-Dist: rich>=13.0
|
|
26
|
+
Requires-Dist: textstat>=0.7
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: decord>=0.6; extra == 'all'
|
|
29
|
+
Requires-Dist: librosa>=0.10; extra == 'all'
|
|
30
|
+
Requires-Dist: open-clip-torch>=2.20; extra == 'all'
|
|
31
|
+
Requires-Dist: openai-whisper>=20231117; extra == 'all'
|
|
32
|
+
Requires-Dist: opencv-python>=4.8; extra == 'all'
|
|
33
|
+
Requires-Dist: pillow>=10.0; extra == 'all'
|
|
34
|
+
Requires-Dist: sentence-transformers>=2.0; extra == 'all'
|
|
35
|
+
Requires-Dist: soundfile>=0.12; extra == 'all'
|
|
36
|
+
Requires-Dist: torch>=2.0; extra == 'all'
|
|
37
|
+
Requires-Dist: transformers>=4.35; extra == 'all'
|
|
38
|
+
Requires-Dist: ultralytics>=8.0; extra == 'all'
|
|
39
|
+
Provides-Extra: audio
|
|
40
|
+
Requires-Dist: librosa>=0.10; extra == 'audio'
|
|
41
|
+
Requires-Dist: openai-whisper>=20231117; extra == 'audio'
|
|
42
|
+
Requires-Dist: soundfile>=0.12; extra == 'audio'
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: mypy>=1.5; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
48
|
+
Provides-Extra: embeddings
|
|
49
|
+
Requires-Dist: sentence-transformers>=2.0; extra == 'embeddings'
|
|
50
|
+
Provides-Extra: image
|
|
51
|
+
Requires-Dist: open-clip-torch>=2.20; extra == 'image'
|
|
52
|
+
Requires-Dist: pillow>=10.0; extra == 'image'
|
|
53
|
+
Requires-Dist: torch>=2.0; extra == 'image'
|
|
54
|
+
Requires-Dist: transformers>=4.35; extra == 'image'
|
|
55
|
+
Requires-Dist: ultralytics>=8.0; extra == 'image'
|
|
56
|
+
Provides-Extra: nli
|
|
57
|
+
Requires-Dist: torch>=2.0; extra == 'nli'
|
|
58
|
+
Requires-Dist: transformers>=4.35; extra == 'nli'
|
|
59
|
+
Provides-Extra: video
|
|
60
|
+
Requires-Dist: decord>=0.6; extra == 'video'
|
|
61
|
+
Requires-Dist: opencv-python>=4.8; extra == 'video'
|
|
62
|
+
Description-Content-Type: text/markdown
|
|
63
|
+
|
|
64
|
+
# EvalWise
|
|
65
|
+
|
|
66
|
+
**Deterministic-first AI evaluation for text, image, audio, and video.**
|
|
67
|
+
|
|
68
|
+
The eval SDK that doesn't default to "ask another AI if this is good."
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install evalwise
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Why EvalWise?
|
|
75
|
+
|
|
76
|
+
Most eval tools jump straight to LLM-as-judge. That's:
|
|
77
|
+
- **Expensive** — every eval is another API call
|
|
78
|
+
- **Non-deterministic** — same input, different scores
|
|
79
|
+
- **Ungrounded** — "Score: 4/5" tells you nothing
|
|
80
|
+
|
|
81
|
+
EvalWise flips the default: **deterministic checks first, LLM-judge only when you have to.**
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from evalwise import Suite, Assert
|
|
87
|
+
|
|
88
|
+
suite = Suite("summarization")
|
|
89
|
+
|
|
90
|
+
@suite.test
|
|
91
|
+
def test_format(response: str):
|
|
92
|
+
Assert.bullet_count(response, exactly=3)
|
|
93
|
+
Assert.word_count(response, max=200)
|
|
94
|
+
Assert.json_valid(response)
|
|
95
|
+
|
|
96
|
+
@suite.test
|
|
97
|
+
def test_factuality(response: str, source: str):
|
|
98
|
+
Assert.entails(response, source=source)
|
|
99
|
+
Assert.no_contradiction(response, source=source)
|
|
100
|
+
|
|
101
|
+
# Run against a dataset
|
|
102
|
+
results = suite.run(dataset="./golden_set.json")
|
|
103
|
+
results.assert_pass_rate(threshold=0.95)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## CLI
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Run eval suite
|
|
110
|
+
evalwise run tests/test_summary.py --dataset golden.json
|
|
111
|
+
|
|
112
|
+
# Run in CI (exit code 1 on failure)
|
|
113
|
+
evalwise run tests/ --ci --threshold 0.95
|
|
114
|
+
|
|
115
|
+
# Create sample eval file
|
|
116
|
+
evalwise init
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Text Assertions
|
|
120
|
+
|
|
121
|
+
| Assertion | What it checks |
|
|
122
|
+
|-----------|----------------|
|
|
123
|
+
| `contains(text, substring)` | Substring present |
|
|
124
|
+
| `not_contains(text, substring)` | Substring absent |
|
|
125
|
+
| `regex(text, pattern)` | Pattern match |
|
|
126
|
+
| `json_valid(text)` | Parseable JSON |
|
|
127
|
+
| `json_schema(text, schema)` | Matches JSON schema |
|
|
128
|
+
| `word_count(text, min, max)` | Word count in range |
|
|
129
|
+
| `bullet_count(text, exactly)` | Bullet point count |
|
|
130
|
+
| `readability(text, min_score)` | Flesch Reading Ease |
|
|
131
|
+
| `language_is(text, "en")` | Correct language |
|
|
132
|
+
| `code_parses(text, "python")` | Valid syntax |
|
|
133
|
+
| `code_runs(text)` | Executes without error |
|
|
134
|
+
| `entails(text, source)` | Follows from source (NLI) |
|
|
135
|
+
| `no_contradiction(text, source)` | No contradictions |
|
|
136
|
+
| `embedding_similarity(text, ref)` | Semantic similarity |
|
|
137
|
+
| `urls_valid(text)` | All URLs return 2xx |
|
|
138
|
+
|
|
139
|
+
## Image Assertions
|
|
140
|
+
|
|
141
|
+
Image evals cover prompt alignment, object detection, resolution/aspect ratio, NSFW safety, and similarity. Object detection uses **YOLO** via `ultralytics`.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from evalwise.image import ImageAssert
|
|
145
|
+
|
|
146
|
+
# Prompt alignment with CLIP
|
|
147
|
+
ImageAssert.clip_score(image, "a cat on a couch", threshold=0.25)
|
|
148
|
+
|
|
149
|
+
# Object detection
|
|
150
|
+
ImageAssert.contains_object(image, "cat", confidence=0.5)
|
|
151
|
+
ImageAssert.object_count(image, "person", exactly=2, confidence=0.5)
|
|
152
|
+
|
|
153
|
+
# Metadata
|
|
154
|
+
ImageAssert.resolution_is(image, width=1024, height=1024)
|
|
155
|
+
ImageAssert.resolution_min(image, width=512, height=512)
|
|
156
|
+
ImageAssert.aspect_ratio(image, ratio=1.0, tolerance=0.1)
|
|
157
|
+
ImageAssert.format_is(image, "PNG")
|
|
158
|
+
|
|
159
|
+
# Safety
|
|
160
|
+
ImageAssert.nsfw_below(image, threshold=0.1)
|
|
161
|
+
|
|
162
|
+
# Similarity to a reference image
|
|
163
|
+
ImageAssert.image_similarity(image, reference, threshold=0.8)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Image generation example
|
|
167
|
+
|
|
168
|
+
`examples/test_image_generation.py` shows a complete eval suite. The dataset can include per-image thresholds and object lists:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"image_path": "./examples/sample_cat.jpeg",
|
|
173
|
+
"prompt": "a person sitting on a couch with a dog and a cat",
|
|
174
|
+
"min_width": 200,
|
|
175
|
+
"min_height": 100,
|
|
176
|
+
"required_objects": ["dog"],
|
|
177
|
+
"confidence": 0.25
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Audio Assertions
|
|
182
|
+
|
|
183
|
+
Audio assertions that use Whisper (`transcription_contains`, `transcription_equals`, `language_is`) require **ffmpeg** to be installed on your system in addition to the `evalwise[audio]` Python dependencies.
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from evalwise.audio import AudioAssert
|
|
187
|
+
|
|
188
|
+
AudioAssert.transcription_contains(audio, "hello world")
|
|
189
|
+
AudioAssert.transcription_equals(audio, expected_text)
|
|
190
|
+
AudioAssert.language_is(audio, "en")
|
|
191
|
+
AudioAssert.duration_between(audio, min_sec=5, max_sec=30)
|
|
192
|
+
AudioAssert.sample_rate_is(audio, hz=44100)
|
|
193
|
+
AudioAssert.no_silence(audio, max_silence_sec=1.0)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Installation
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Core (text assertions)
|
|
200
|
+
pip install evalwise
|
|
201
|
+
|
|
202
|
+
# With image support
|
|
203
|
+
pip install evalwise[image]
|
|
204
|
+
|
|
205
|
+
# With audio support (also requires ffmpeg system binary)
|
|
206
|
+
pip install evalwise[audio]
|
|
207
|
+
|
|
208
|
+
# On macOS: brew install ffmpeg
|
|
209
|
+
# On Ubuntu: sudo apt install ffmpeg
|
|
210
|
+
# On Windows: winget install Gyan.FFmpeg
|
|
211
|
+
|
|
212
|
+
# Everything
|
|
213
|
+
pip install evalwise[all]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### What each extra gives you
|
|
217
|
+
|
|
218
|
+
| Extra | Capabilities enabled | Example tests |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| *(none)* | 22 of 25 text assertions | `test_basic.py` |
|
|
221
|
+
| `[nli]` | `Assert.entails`, `Assert.no_contradiction` | `test_summarization.py` |
|
|
222
|
+
| `[embeddings]` | `Assert.embedding_similarity` | — |
|
|
223
|
+
| `[image]` | CLIP, YOLO object detection, NSFW, resolution, similarity | `test_image_generation.py` |
|
|
224
|
+
| `[audio]` | Whisper transcription, language, duration, sample rate | `test_audio.py` |
|
|
225
|
+
| `[video]` | Video assertions (extra only, no example yet) | — |
|
|
226
|
+
| `[all]` | All of the above | — |
|
|
227
|
+
|
|
228
|
+
### System dependencies
|
|
229
|
+
|
|
230
|
+
Some examples also require system binaries:
|
|
231
|
+
|
|
232
|
+
| Capability | System binary | Install |
|
|
233
|
+
|---|---|---|
|
|
234
|
+
| Audio transcription | `ffmpeg` | `brew install ffmpeg` / `apt install ffmpeg` |
|
|
235
|
+
|
|
236
|
+
## Philosophy
|
|
237
|
+
|
|
238
|
+
1. **Deterministic by default** — Same input, same result. Always.
|
|
239
|
+
2. **Cheap first** — Check format, length, syntax before calling models.
|
|
240
|
+
3. **Grounded scores** — Know exactly why something failed.
|
|
241
|
+
4. **LLM-judge as last resort** — Only for truly subjective criteria.
|
|
242
|
+
|
|
243
|
+
## Comparison
|
|
244
|
+
|
|
245
|
+
| | EvalWise | Promptfoo | Braintrust | LangSmith |
|
|
246
|
+
|---|--------|-----------|------------|-----------|
|
|
247
|
+
| Deterministic-first | ✓ | Partial | ✗ | ✗ |
|
|
248
|
+
| Image/Audio evals | ✓ | ✗ | ✗ | ✗ |
|
|
249
|
+
| Python-native | ✓ | YAML | SDK | SDK |
|
|
250
|
+
| No account required | ✓ | ✓ | ✗ | ✗ |
|
|
251
|
+
| OSS | ✓ | ✓ | Partial | ✗ |
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT
|
evalwise-0.1.0/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# EvalWise
|
|
2
|
+
|
|
3
|
+
**Deterministic-first AI evaluation for text, image, audio, and video.**
|
|
4
|
+
|
|
5
|
+
The eval SDK that doesn't default to "ask another AI if this is good."
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install evalwise
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Why EvalWise?
|
|
12
|
+
|
|
13
|
+
Most eval tools jump straight to LLM-as-judge. That's:
|
|
14
|
+
- **Expensive** — every eval is another API call
|
|
15
|
+
- **Non-deterministic** — same input, different scores
|
|
16
|
+
- **Ungrounded** — "Score: 4/5" tells you nothing
|
|
17
|
+
|
|
18
|
+
EvalWise flips the default: **deterministic checks first, LLM-judge only when you have to.**
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from evalwise import Suite, Assert
|
|
24
|
+
|
|
25
|
+
suite = Suite("summarization")
|
|
26
|
+
|
|
27
|
+
@suite.test
|
|
28
|
+
def test_format(response: str):
|
|
29
|
+
Assert.bullet_count(response, exactly=3)
|
|
30
|
+
Assert.word_count(response, max=200)
|
|
31
|
+
Assert.json_valid(response)
|
|
32
|
+
|
|
33
|
+
@suite.test
|
|
34
|
+
def test_factuality(response: str, source: str):
|
|
35
|
+
Assert.entails(response, source=source)
|
|
36
|
+
Assert.no_contradiction(response, source=source)
|
|
37
|
+
|
|
38
|
+
# Run against a dataset
|
|
39
|
+
results = suite.run(dataset="./golden_set.json")
|
|
40
|
+
results.assert_pass_rate(threshold=0.95)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## CLI
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Run eval suite
|
|
47
|
+
evalwise run tests/test_summary.py --dataset golden.json
|
|
48
|
+
|
|
49
|
+
# Run in CI (exit code 1 on failure)
|
|
50
|
+
evalwise run tests/ --ci --threshold 0.95
|
|
51
|
+
|
|
52
|
+
# Create sample eval file
|
|
53
|
+
evalwise init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Text Assertions
|
|
57
|
+
|
|
58
|
+
| Assertion | What it checks |
|
|
59
|
+
|-----------|----------------|
|
|
60
|
+
| `contains(text, substring)` | Substring present |
|
|
61
|
+
| `not_contains(text, substring)` | Substring absent |
|
|
62
|
+
| `regex(text, pattern)` | Pattern match |
|
|
63
|
+
| `json_valid(text)` | Parseable JSON |
|
|
64
|
+
| `json_schema(text, schema)` | Matches JSON schema |
|
|
65
|
+
| `word_count(text, min, max)` | Word count in range |
|
|
66
|
+
| `bullet_count(text, exactly)` | Bullet point count |
|
|
67
|
+
| `readability(text, min_score)` | Flesch Reading Ease |
|
|
68
|
+
| `language_is(text, "en")` | Correct language |
|
|
69
|
+
| `code_parses(text, "python")` | Valid syntax |
|
|
70
|
+
| `code_runs(text)` | Executes without error |
|
|
71
|
+
| `entails(text, source)` | Follows from source (NLI) |
|
|
72
|
+
| `no_contradiction(text, source)` | No contradictions |
|
|
73
|
+
| `embedding_similarity(text, ref)` | Semantic similarity |
|
|
74
|
+
| `urls_valid(text)` | All URLs return 2xx |
|
|
75
|
+
|
|
76
|
+
## Image Assertions
|
|
77
|
+
|
|
78
|
+
Image evals cover prompt alignment, object detection, resolution/aspect ratio, NSFW safety, and similarity. Object detection uses **YOLO** via `ultralytics`.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from evalwise.image import ImageAssert
|
|
82
|
+
|
|
83
|
+
# Prompt alignment with CLIP
|
|
84
|
+
ImageAssert.clip_score(image, "a cat on a couch", threshold=0.25)
|
|
85
|
+
|
|
86
|
+
# Object detection
|
|
87
|
+
ImageAssert.contains_object(image, "cat", confidence=0.5)
|
|
88
|
+
ImageAssert.object_count(image, "person", exactly=2, confidence=0.5)
|
|
89
|
+
|
|
90
|
+
# Metadata
|
|
91
|
+
ImageAssert.resolution_is(image, width=1024, height=1024)
|
|
92
|
+
ImageAssert.resolution_min(image, width=512, height=512)
|
|
93
|
+
ImageAssert.aspect_ratio(image, ratio=1.0, tolerance=0.1)
|
|
94
|
+
ImageAssert.format_is(image, "PNG")
|
|
95
|
+
|
|
96
|
+
# Safety
|
|
97
|
+
ImageAssert.nsfw_below(image, threshold=0.1)
|
|
98
|
+
|
|
99
|
+
# Similarity to a reference image
|
|
100
|
+
ImageAssert.image_similarity(image, reference, threshold=0.8)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Image generation example
|
|
104
|
+
|
|
105
|
+
`examples/test_image_generation.py` shows a complete eval suite. The dataset can include per-image thresholds and object lists:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"image_path": "./examples/sample_cat.jpeg",
|
|
110
|
+
"prompt": "a person sitting on a couch with a dog and a cat",
|
|
111
|
+
"min_width": 200,
|
|
112
|
+
"min_height": 100,
|
|
113
|
+
"required_objects": ["dog"],
|
|
114
|
+
"confidence": 0.25
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Audio Assertions
|
|
119
|
+
|
|
120
|
+
Audio assertions that use Whisper (`transcription_contains`, `transcription_equals`, `language_is`) require **ffmpeg** to be installed on your system in addition to the `evalwise[audio]` Python dependencies.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from evalwise.audio import AudioAssert
|
|
124
|
+
|
|
125
|
+
AudioAssert.transcription_contains(audio, "hello world")
|
|
126
|
+
AudioAssert.transcription_equals(audio, expected_text)
|
|
127
|
+
AudioAssert.language_is(audio, "en")
|
|
128
|
+
AudioAssert.duration_between(audio, min_sec=5, max_sec=30)
|
|
129
|
+
AudioAssert.sample_rate_is(audio, hz=44100)
|
|
130
|
+
AudioAssert.no_silence(audio, max_silence_sec=1.0)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Installation
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Core (text assertions)
|
|
137
|
+
pip install evalwise
|
|
138
|
+
|
|
139
|
+
# With image support
|
|
140
|
+
pip install evalwise[image]
|
|
141
|
+
|
|
142
|
+
# With audio support (also requires ffmpeg system binary)
|
|
143
|
+
pip install evalwise[audio]
|
|
144
|
+
|
|
145
|
+
# On macOS: brew install ffmpeg
|
|
146
|
+
# On Ubuntu: sudo apt install ffmpeg
|
|
147
|
+
# On Windows: winget install Gyan.FFmpeg
|
|
148
|
+
|
|
149
|
+
# Everything
|
|
150
|
+
pip install evalwise[all]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### What each extra gives you
|
|
154
|
+
|
|
155
|
+
| Extra | Capabilities enabled | Example tests |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| *(none)* | 22 of 25 text assertions | `test_basic.py` |
|
|
158
|
+
| `[nli]` | `Assert.entails`, `Assert.no_contradiction` | `test_summarization.py` |
|
|
159
|
+
| `[embeddings]` | `Assert.embedding_similarity` | — |
|
|
160
|
+
| `[image]` | CLIP, YOLO object detection, NSFW, resolution, similarity | `test_image_generation.py` |
|
|
161
|
+
| `[audio]` | Whisper transcription, language, duration, sample rate | `test_audio.py` |
|
|
162
|
+
| `[video]` | Video assertions (extra only, no example yet) | — |
|
|
163
|
+
| `[all]` | All of the above | — |
|
|
164
|
+
|
|
165
|
+
### System dependencies
|
|
166
|
+
|
|
167
|
+
Some examples also require system binaries:
|
|
168
|
+
|
|
169
|
+
| Capability | System binary | Install |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| Audio transcription | `ffmpeg` | `brew install ffmpeg` / `apt install ffmpeg` |
|
|
172
|
+
|
|
173
|
+
## Philosophy
|
|
174
|
+
|
|
175
|
+
1. **Deterministic by default** — Same input, same result. Always.
|
|
176
|
+
2. **Cheap first** — Check format, length, syntax before calling models.
|
|
177
|
+
3. **Grounded scores** — Know exactly why something failed.
|
|
178
|
+
4. **LLM-judge as last resort** — Only for truly subjective criteria.
|
|
179
|
+
|
|
180
|
+
## Comparison
|
|
181
|
+
|
|
182
|
+
| | EvalWise | Promptfoo | Braintrust | LangSmith |
|
|
183
|
+
|---|--------|-----------|------------|-----------|
|
|
184
|
+
| Deterministic-first | ✓ | Partial | ✗ | ✗ |
|
|
185
|
+
| Image/Audio evals | ✓ | ✗ | ✗ | ✗ |
|
|
186
|
+
| Python-native | ✓ | YAML | SDK | SDK |
|
|
187
|
+
| No account required | ✓ | ✓ | ✗ | ✗ |
|
|
188
|
+
| OSS | ✓ | ✓ | Partial | ✗ |
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"response": "```python\nprint('Hello, World!')\n```",
|
|
4
|
+
"expected_output": "Hello, World!"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"response": "```python\nx = 5\ny = 3\nprint(x + y)\n```",
|
|
8
|
+
"expected_output": "8"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"response": "```python\ndef greet(name):\n return f'Hello, {name}!'\nprint(greet('EvalWise'))\n```",
|
|
12
|
+
"expected_output": "Hello, EvalWise!"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"image_path": "./examples/sample_cat.jpeg",
|
|
4
|
+
"prompt": "a person in an orange shirt sitting on a couch holding a brown and white dog and petting a grey and white tabby cat",
|
|
5
|
+
"min_width": 200,
|
|
6
|
+
"min_height": 100,
|
|
7
|
+
"ratio": 1.4,
|
|
8
|
+
"tolerance": 0.1,
|
|
9
|
+
"required_objects": ["dog"],
|
|
10
|
+
"confidence": 0.25
|
|
11
|
+
}
|
|
12
|
+
]
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"response": "- The study found that exercise improves mental health\n- Participants who exercised 3x/week showed 40% reduction in anxiety\n- Results were consistent across age groups",
|
|
4
|
+
"source": "A new study published in the Journal of Mental Health examined 500 participants over 6 months. Researchers found that regular exercise significantly improves mental health outcomes. Participants who exercised three times per week showed a 40% reduction in anxiety symptoms compared to the control group. The positive effects were observed consistently across all age groups from 18 to 65."
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"response": "- Climate change is accelerating faster than predicted\n- Arctic ice loss reached record levels in 2024\n- Scientists call for immediate action",
|
|
8
|
+
"source": "New data from NASA satellites reveals that climate change is accelerating at a rate faster than previous models predicted. Arctic sea ice reached its lowest extent on record in 2024, losing 15% more ice than the previous year. Climate scientists are calling for immediate and aggressive action to reduce carbon emissions, warning that current policies are insufficient to prevent catastrophic warming."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"response": "- Apple announced the iPhone 20 with holographic display\n- Battery life extended to 5 days\n- Price starts at $1,299",
|
|
12
|
+
"source": "Apple unveiled its latest smartphone, the iPhone 16, at its annual fall event. The new device features an improved OLED display with higher brightness and a new A18 chip. Battery life has been improved by 20% over the previous generation. The base model starts at $999, with the Pro version at $1,199."
|
|
13
|
+
}
|
|
14
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Example: Evaluating speech/audio generation."""
|
|
2
|
+
|
|
3
|
+
from evalwise import Suite
|
|
4
|
+
from evalwise.audio import AudioAssert
|
|
5
|
+
|
|
6
|
+
suite = Suite("audio_evals")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@suite.test
|
|
10
|
+
def test_transcription(audio_path: str, expected_text: str):
|
|
11
|
+
"""Check if speech contains expected text (using Whisper)."""
|
|
12
|
+
AudioAssert.transcription_contains(audio_path, expected_text)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@suite.test
|
|
16
|
+
def test_language(audio_path: str):
|
|
17
|
+
"""Check audio is in English."""
|
|
18
|
+
AudioAssert.language_is(audio_path, "en")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@suite.test
|
|
22
|
+
def test_duration(audio_path: str):
|
|
23
|
+
"""Check audio duration is reasonable."""
|
|
24
|
+
AudioAssert.duration_between(audio_path, min_sec=1, max_sec=60)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# To run (install deps first):
|
|
28
|
+
# pip install openai-whisper librosa soundfile
|
|
29
|
+
# evalwise run examples/test_audio.py --dataset examples/audio_data.json
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Basic example - no external dependencies required."""
|
|
2
|
+
|
|
3
|
+
from evalwise import Suite, Assert
|
|
4
|
+
|
|
5
|
+
suite = Suite("basic_evals")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@suite.test
|
|
9
|
+
def test_format(response: str):
|
|
10
|
+
"""Test basic format checks."""
|
|
11
|
+
# These use only built-in Python - no dependencies
|
|
12
|
+
Assert.contains(response, "-") # Has bullet points
|
|
13
|
+
Assert.word_count(response, min=5, max=100)
|
|
14
|
+
Assert.line_count(response, min=1, max=10)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@suite.test
|
|
18
|
+
def test_structure(response: str):
|
|
19
|
+
"""Test structure checks."""
|
|
20
|
+
Assert.bullet_count(response, min=1)
|
|
21
|
+
Assert.not_contains(response, "ERROR")
|
|
22
|
+
Assert.not_contains(response, "undefined")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# To run:
|
|
26
|
+
# evalwise run examples/test_basic.py --dataset examples/summarization_data.json
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Example: Evaluating code generation."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from evalwise import Assert, Suite
|
|
8
|
+
|
|
9
|
+
suite = Suite("code_generation_evals")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _extract_code(response: str) -> str:
|
|
13
|
+
"""Extract code from a markdown block if present."""
|
|
14
|
+
code_match = re.search(r"```(?:\w+)?\n(.*?)```", response, re.DOTALL)
|
|
15
|
+
return code_match.group(1) if code_match else response
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@suite.test
|
|
19
|
+
def test_syntax(response: str):
|
|
20
|
+
"""Ensure generated code is syntactically valid."""
|
|
21
|
+
Assert.code_parses(response, language="python")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@suite.test
|
|
25
|
+
def test_execution(response: str):
|
|
26
|
+
"""Ensure generated code executes without error."""
|
|
27
|
+
code = _extract_code(response)
|
|
28
|
+
Assert.code_runs(code)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@suite.test
|
|
32
|
+
def test_output(response: str, expected_output: str):
|
|
33
|
+
"""Ensure code produces expected output."""
|
|
34
|
+
code = _extract_code(response)
|
|
35
|
+
|
|
36
|
+
old_stdout = sys.stdout
|
|
37
|
+
sys.stdout = buffer = io.StringIO()
|
|
38
|
+
try:
|
|
39
|
+
exec(code)
|
|
40
|
+
actual_output = buffer.getvalue().strip()
|
|
41
|
+
finally:
|
|
42
|
+
sys.stdout = old_stdout
|
|
43
|
+
|
|
44
|
+
Assert.custom(
|
|
45
|
+
passed=actual_output == expected_output,
|
|
46
|
+
name="output_matches",
|
|
47
|
+
expected=expected_output,
|
|
48
|
+
actual=actual_output,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# To run:
|
|
53
|
+
# evalwise run examples/test_code_generation.py --dataset examples/code_data.json
|