llm-evals 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +163 -87
- data/exe/llm-evals +60 -5
- data/lib/.DS_Store +0 -0
- data/lib/llm/.DS_Store +0 -0
- data/lib/llm/evals/actions/adapter.rb +50 -0
- data/lib/llm/evals/actions/error_budget.rb +63 -0
- data/lib/llm/evals/actions/error_diagnosis.rb +51 -0
- data/lib/llm/evals/actions/error_streak.rb +83 -0
- data/lib/llm/evals/actions/experiment.rb +251 -0
- data/lib/llm/evals/actions/verify.rb +61 -0
- data/lib/llm/evals/actions.rb +9 -0
- data/lib/llm/evals/cli/cases.rb +10 -0
- data/lib/llm/evals/cli/command.rb +32 -99
- data/lib/llm/evals/cli/compare.rb +0 -20
- data/lib/llm/evals/cli/contender_selector.rb +61 -0
- data/lib/llm/evals/cli/error_pause.rb +81 -0
- data/lib/llm/evals/cli/experiment_pool.rb +97 -0
- data/lib/llm/evals/cli/formatting.rb +8 -0
- data/lib/llm/evals/cli/leaderboard.rb +66 -12
- data/lib/llm/evals/cli/model_colors.rb +3 -0
- data/lib/llm/evals/cli/model_command.rb +152 -315
- data/lib/llm/evals/cli/model_options.rb +80 -0
- data/lib/llm/evals/cli/option_groups.rb +32 -0
- data/lib/llm/evals/cli/progress_bars.rb +92 -5
- data/lib/llm/evals/cli/render.rb +6 -6
- data/lib/llm/evals/cli/report_dispatcher/console.rb +65 -0
- data/lib/llm/evals/cli/report_dispatcher/html_output.rb +50 -0
- data/lib/llm/evals/cli/report_dispatcher/json_output.rb +29 -0
- data/lib/llm/evals/cli/report_dispatcher/output.rb +56 -0
- data/lib/llm/evals/cli/report_dispatcher/pipeline_output.rb +38 -0
- data/lib/llm/evals/cli/report_dispatcher.rb +133 -0
- data/lib/llm/evals/cli/run.rb +13 -22
- data/lib/llm/evals/cli/run_planner.rb +97 -0
- data/lib/llm/evals/cli/run_record.rb +68 -0
- data/lib/llm/evals/cli/standings.rb +39 -17
- data/lib/llm/evals/cli/status_box.rb +51 -0
- data/lib/llm/evals/cli/table.rb +103 -0
- data/lib/llm/evals/cli/text.rb +22 -0
- data/lib/llm/evals/cli/tracker_resolver.rb +52 -0
- data/lib/llm/evals/cli/upgrade_config.rb +60 -0
- data/lib/llm/evals/cli/verify.rb +5 -1
- data/lib/llm/evals/cli/version.rb +4 -0
- data/lib/llm/evals/cli/weights.rb +48 -0
- data/lib/llm/evals/cli.rb +39 -6
- data/lib/llm/evals/config/contender_builder.rb +30 -0
- data/lib/llm/evals/config/error_limits.rb +41 -0
- data/lib/llm/evals/config/listing.rb +153 -0
- data/lib/llm/evals/config/results.rb +42 -0
- data/lib/llm/evals/config/score_weights.rb +25 -0
- data/lib/llm/evals/config/upgrade.rb +107 -0
- data/lib/llm/evals/config.rb +90 -79
- data/lib/llm/evals/datasets/case.rb +46 -0
- data/lib/llm/evals/datasets/discovery.rb +143 -0
- data/lib/llm/evals/datasets.rb +9 -0
- data/lib/llm/evals/launcher.rb +57 -3
- data/lib/llm/evals/models/contender.rb +51 -0
- data/lib/llm/evals/models/model.rb +87 -0
- data/lib/llm/evals/models/throttle.rb +170 -0
- data/lib/llm/evals/models.rb +9 -0
- data/lib/llm/evals/providers/base.rb +61 -16
- data/lib/llm/evals/providers/catalog.rb +5 -2
- data/lib/llm/evals/providers/open_router.rb +54 -7
- data/lib/llm/evals/providers/registry.rb +49 -16
- data/lib/llm/evals/providers/type_safe/client.rb +67 -0
- data/lib/llm/evals/providers/type_safe.rb +113 -12
- data/lib/llm/evals/providers.rb +9 -0
- data/lib/llm/evals/render/reports/call_stats_table.rb +44 -0
- data/lib/llm/evals/render/reports/html/dataset_section.rb +51 -0
- data/lib/llm/evals/render/reports/html/format.rb +38 -0
- data/lib/llm/evals/render/reports/html/global_standings.rb +41 -0
- data/lib/llm/evals/render/reports/html/ranking.rb +86 -0
- data/lib/llm/evals/render/reports/html/view_model.rb +116 -0
- data/lib/llm/evals/render/reports/html.rb +60 -181
- data/lib/llm/evals/render/reports/json/decoder.rb +137 -0
- data/lib/llm/evals/render/reports/json/encoder.rb +106 -0
- data/lib/llm/evals/render/reports/json.rb +38 -141
- data/lib/llm/evals/render/reports/pipeline.rb +55 -20
- data/lib/llm/evals/render/reports/terminal.rb +8 -1
- data/lib/llm/evals/render.rb +9 -0
- data/lib/llm/evals/results/provider_response.rb +25 -0
- data/lib/llm/evals/results/reply.rb +38 -0
- data/lib/llm/evals/results/run_outcome.rb +15 -0
- data/lib/llm/evals/results/run_result.rb +40 -0
- data/lib/llm/evals/results/score.rb +45 -0
- data/lib/llm/evals/results/warnings.rb +149 -0
- data/lib/llm/evals/results.rb +9 -0
- data/lib/llm/evals/runtime/configuration.rb +295 -0
- data/lib/llm/evals/runtime/environment.rb +90 -0
- data/lib/llm/evals/runtime/helper.rb +153 -0
- data/lib/llm/evals/runtime.rb +2 -67
- data/lib/llm/evals/scoring/metric.rb +47 -0
- data/lib/llm/evals/scoring/schema_validator.rb +102 -0
- data/lib/llm/evals/scoring/scorer.rb +49 -0
- data/lib/llm/evals/scoring/weighted_score.rb +148 -0
- data/lib/llm/evals/scoring.rb +9 -0
- data/lib/llm/evals/stats/call_stats.rb +91 -0
- data/lib/llm/evals/stats/usage.rb +46 -0
- data/lib/llm/evals/stats.rb +29 -0
- data/lib/llm/evals/testing/echo_adapter.rb +14 -9
- data/lib/llm/evals/testing/fake_model.rb +8 -6
- data/lib/llm/evals/testing/fake_provider.rb +39 -8
- data/lib/llm/evals/testing.rb +9 -0
- data/lib/llm/evals/trackers/base.rb +110 -0
- data/lib/llm/evals/trackers/braintrust.rb +40 -3
- data/lib/llm/evals/trackers/local.rb +38 -2
- data/lib/llm/evals/trackers/logfire.rb +56 -3
- data/lib/llm/evals/trackers/registry.rb +29 -7
- data/lib/llm/evals/trackers.rb +9 -0
- data/lib/llm/evals/version.rb +3 -1
- data/lib/llm/evals.rb +17 -7
- metadata +91 -24
- data/lib/llm/evals/adapter.rb +0 -47
- data/lib/llm/evals/case.rb +0 -36
- data/lib/llm/evals/completion.rb +0 -11
- data/lib/llm/evals/configuration.rb +0 -173
- data/lib/llm/evals/contender.rb +0 -31
- data/lib/llm/evals/discovery.rb +0 -95
- data/lib/llm/evals/experiment.rb +0 -158
- data/lib/llm/evals/helper.rb +0 -98
- data/lib/llm/evals/metric.rb +0 -26
- data/lib/llm/evals/model.rb +0 -55
- data/lib/llm/evals/reply.rb +0 -18
- data/lib/llm/evals/run_result.rb +0 -14
- data/lib/llm/evals/schema_validator.rb +0 -84
- data/lib/llm/evals/score.rb +0 -26
- data/lib/llm/evals/scorer.rb +0 -45
- data/lib/llm/evals/tracker.rb +0 -80
- data/lib/llm/evals/usage.rb +0 -33
- data/lib/llm/evals/verify.rb +0 -49
- data/lib/llm/evals/warnings.rb +0 -98
- data/lib/llm/evals/weighted_score.rb +0 -116
- /data/lib/llm/evals/{templates → render/templates}/html_report.css +0 -0
- /data/lib/llm/evals/{templates → render/templates}/html_report.html.erb +0 -0
- /data/lib/llm/evals/{templates → render/templates}/html_report.js +0 -0
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm-evals
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Konstantin Gredeskoul
|
|
@@ -163,6 +163,20 @@ dependencies:
|
|
|
163
163
|
- - "~>"
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
165
|
version: '1.6'
|
|
166
|
+
- !ruby/object:Gem::Dependency
|
|
167
|
+
name: ruby-limiter
|
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - "~>"
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '2.3'
|
|
173
|
+
type: :runtime
|
|
174
|
+
prerelease: false
|
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
176
|
+
requirements:
|
|
177
|
+
- - "~>"
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: '2.3'
|
|
166
180
|
description: |2-
|
|
167
181
|
|
|
168
182
|
Run multi-case evaluation suites against datasets using any model available
|
|
@@ -186,64 +200,117 @@ files:
|
|
|
186
200
|
- README.md
|
|
187
201
|
- exe/evals
|
|
188
202
|
- exe/llm-evals
|
|
203
|
+
- lib/.DS_Store
|
|
204
|
+
- lib/llm/.DS_Store
|
|
189
205
|
- lib/llm/evals.rb
|
|
190
|
-
- lib/llm/evals/
|
|
191
|
-
- lib/llm/evals/
|
|
206
|
+
- lib/llm/evals/actions.rb
|
|
207
|
+
- lib/llm/evals/actions/adapter.rb
|
|
208
|
+
- lib/llm/evals/actions/error_budget.rb
|
|
209
|
+
- lib/llm/evals/actions/error_diagnosis.rb
|
|
210
|
+
- lib/llm/evals/actions/error_streak.rb
|
|
211
|
+
- lib/llm/evals/actions/experiment.rb
|
|
212
|
+
- lib/llm/evals/actions/verify.rb
|
|
192
213
|
- lib/llm/evals/cli.rb
|
|
193
214
|
- lib/llm/evals/cli/cases.rb
|
|
194
215
|
- lib/llm/evals/cli/command.rb
|
|
195
216
|
- lib/llm/evals/cli/compare.rb
|
|
217
|
+
- lib/llm/evals/cli/contender_selector.rb
|
|
218
|
+
- lib/llm/evals/cli/error_pause.rb
|
|
219
|
+
- lib/llm/evals/cli/experiment_pool.rb
|
|
196
220
|
- lib/llm/evals/cli/formatting.rb
|
|
197
221
|
- lib/llm/evals/cli/leaderboard.rb
|
|
198
222
|
- lib/llm/evals/cli/model_colors.rb
|
|
199
223
|
- lib/llm/evals/cli/model_command.rb
|
|
224
|
+
- lib/llm/evals/cli/model_options.rb
|
|
225
|
+
- lib/llm/evals/cli/option_groups.rb
|
|
200
226
|
- lib/llm/evals/cli/progress_bars.rb
|
|
201
227
|
- lib/llm/evals/cli/render.rb
|
|
228
|
+
- lib/llm/evals/cli/report_dispatcher.rb
|
|
229
|
+
- lib/llm/evals/cli/report_dispatcher/console.rb
|
|
230
|
+
- lib/llm/evals/cli/report_dispatcher/html_output.rb
|
|
231
|
+
- lib/llm/evals/cli/report_dispatcher/json_output.rb
|
|
232
|
+
- lib/llm/evals/cli/report_dispatcher/output.rb
|
|
233
|
+
- lib/llm/evals/cli/report_dispatcher/pipeline_output.rb
|
|
202
234
|
- lib/llm/evals/cli/run.rb
|
|
235
|
+
- lib/llm/evals/cli/run_planner.rb
|
|
236
|
+
- lib/llm/evals/cli/run_record.rb
|
|
203
237
|
- lib/llm/evals/cli/standings.rb
|
|
238
|
+
- lib/llm/evals/cli/status_box.rb
|
|
239
|
+
- lib/llm/evals/cli/table.rb
|
|
240
|
+
- lib/llm/evals/cli/text.rb
|
|
241
|
+
- lib/llm/evals/cli/tracker_resolver.rb
|
|
242
|
+
- lib/llm/evals/cli/upgrade_config.rb
|
|
204
243
|
- lib/llm/evals/cli/verify.rb
|
|
205
244
|
- lib/llm/evals/cli/version.rb
|
|
206
|
-
- lib/llm/evals/
|
|
245
|
+
- lib/llm/evals/cli/weights.rb
|
|
207
246
|
- lib/llm/evals/config.rb
|
|
208
|
-
- lib/llm/evals/
|
|
209
|
-
- lib/llm/evals/
|
|
210
|
-
- lib/llm/evals/
|
|
211
|
-
- lib/llm/evals/
|
|
212
|
-
- lib/llm/evals/
|
|
247
|
+
- lib/llm/evals/config/contender_builder.rb
|
|
248
|
+
- lib/llm/evals/config/error_limits.rb
|
|
249
|
+
- lib/llm/evals/config/listing.rb
|
|
250
|
+
- lib/llm/evals/config/results.rb
|
|
251
|
+
- lib/llm/evals/config/score_weights.rb
|
|
252
|
+
- lib/llm/evals/config/upgrade.rb
|
|
253
|
+
- lib/llm/evals/datasets.rb
|
|
254
|
+
- lib/llm/evals/datasets/case.rb
|
|
255
|
+
- lib/llm/evals/datasets/discovery.rb
|
|
213
256
|
- lib/llm/evals/launcher.rb
|
|
214
|
-
- lib/llm/evals/
|
|
215
|
-
- lib/llm/evals/
|
|
257
|
+
- lib/llm/evals/models.rb
|
|
258
|
+
- lib/llm/evals/models/contender.rb
|
|
259
|
+
- lib/llm/evals/models/model.rb
|
|
260
|
+
- lib/llm/evals/models/throttle.rb
|
|
261
|
+
- lib/llm/evals/providers.rb
|
|
216
262
|
- lib/llm/evals/providers/base.rb
|
|
217
263
|
- lib/llm/evals/providers/catalog.rb
|
|
218
264
|
- lib/llm/evals/providers/open_router.rb
|
|
219
265
|
- lib/llm/evals/providers/registry.rb
|
|
220
266
|
- lib/llm/evals/providers/type_safe.rb
|
|
267
|
+
- lib/llm/evals/providers/type_safe/client.rb
|
|
268
|
+
- lib/llm/evals/render.rb
|
|
269
|
+
- lib/llm/evals/render/reports/call_stats_table.rb
|
|
221
270
|
- lib/llm/evals/render/reports/html.rb
|
|
271
|
+
- lib/llm/evals/render/reports/html/dataset_section.rb
|
|
272
|
+
- lib/llm/evals/render/reports/html/format.rb
|
|
273
|
+
- lib/llm/evals/render/reports/html/global_standings.rb
|
|
274
|
+
- lib/llm/evals/render/reports/html/ranking.rb
|
|
275
|
+
- lib/llm/evals/render/reports/html/view_model.rb
|
|
222
276
|
- lib/llm/evals/render/reports/json.rb
|
|
277
|
+
- lib/llm/evals/render/reports/json/decoder.rb
|
|
278
|
+
- lib/llm/evals/render/reports/json/encoder.rb
|
|
223
279
|
- lib/llm/evals/render/reports/pipeline.rb
|
|
224
280
|
- lib/llm/evals/render/reports/terminal.rb
|
|
225
|
-
- lib/llm/evals/
|
|
226
|
-
- lib/llm/evals/
|
|
281
|
+
- lib/llm/evals/render/templates/html_report.css
|
|
282
|
+
- lib/llm/evals/render/templates/html_report.html.erb
|
|
283
|
+
- lib/llm/evals/render/templates/html_report.js
|
|
284
|
+
- lib/llm/evals/results.rb
|
|
285
|
+
- lib/llm/evals/results/provider_response.rb
|
|
286
|
+
- lib/llm/evals/results/reply.rb
|
|
287
|
+
- lib/llm/evals/results/run_outcome.rb
|
|
288
|
+
- lib/llm/evals/results/run_result.rb
|
|
289
|
+
- lib/llm/evals/results/score.rb
|
|
290
|
+
- lib/llm/evals/results/warnings.rb
|
|
227
291
|
- lib/llm/evals/runtime.rb
|
|
228
|
-
- lib/llm/evals/
|
|
229
|
-
- lib/llm/evals/
|
|
230
|
-
- lib/llm/evals/
|
|
231
|
-
- lib/llm/evals/
|
|
232
|
-
- lib/llm/evals/
|
|
233
|
-
- lib/llm/evals/
|
|
292
|
+
- lib/llm/evals/runtime/configuration.rb
|
|
293
|
+
- lib/llm/evals/runtime/environment.rb
|
|
294
|
+
- lib/llm/evals/runtime/helper.rb
|
|
295
|
+
- lib/llm/evals/scoring.rb
|
|
296
|
+
- lib/llm/evals/scoring/metric.rb
|
|
297
|
+
- lib/llm/evals/scoring/schema_validator.rb
|
|
298
|
+
- lib/llm/evals/scoring/scorer.rb
|
|
299
|
+
- lib/llm/evals/scoring/weighted_score.rb
|
|
300
|
+
- lib/llm/evals/stats.rb
|
|
301
|
+
- lib/llm/evals/stats/call_stats.rb
|
|
302
|
+
- lib/llm/evals/stats/usage.rb
|
|
303
|
+
- lib/llm/evals/testing.rb
|
|
234
304
|
- lib/llm/evals/testing/echo_adapter.rb
|
|
235
305
|
- lib/llm/evals/testing/fake_model.rb
|
|
236
306
|
- lib/llm/evals/testing/fake_provider.rb
|
|
237
|
-
- lib/llm/evals/
|
|
307
|
+
- lib/llm/evals/trackers.rb
|
|
308
|
+
- lib/llm/evals/trackers/base.rb
|
|
238
309
|
- lib/llm/evals/trackers/braintrust.rb
|
|
239
310
|
- lib/llm/evals/trackers/local.rb
|
|
240
311
|
- lib/llm/evals/trackers/logfire.rb
|
|
241
312
|
- lib/llm/evals/trackers/registry.rb
|
|
242
|
-
- lib/llm/evals/usage.rb
|
|
243
|
-
- lib/llm/evals/verify.rb
|
|
244
313
|
- lib/llm/evals/version.rb
|
|
245
|
-
- lib/llm/evals/warnings.rb
|
|
246
|
-
- lib/llm/evals/weighted_score.rb
|
|
247
314
|
homepage: https://github.com/kigster/llm-evals
|
|
248
315
|
licenses:
|
|
249
316
|
- MIT
|
data/lib/llm/evals/adapter.rb
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LLM
|
|
4
|
-
module Evals
|
|
5
|
-
# What a suite implements to run one case: everything the model sees, and
|
|
6
|
-
# everything done to its answer before it is scored. An adapter never
|
|
7
|
-
# scores; that is the {Scorer}'s, and keeping them apart is what lets one
|
|
8
|
-
# suite compare prompts and another compare models with the same runner.
|
|
9
|
-
#
|
|
10
|
-
# @abstract Subclass and override {#call}.
|
|
11
|
-
class Adapter
|
|
12
|
-
# Called once per dataset before its cases run; load what the dataset
|
|
13
|
-
# shares (a schema, a flow definition) here, outside the threads.
|
|
14
|
-
#
|
|
15
|
-
# @param dataset [String]
|
|
16
|
-
# @return [void]
|
|
17
|
-
def prepare(_dataset) = nil
|
|
18
|
-
|
|
19
|
-
# Runs one case against one model.
|
|
20
|
-
#
|
|
21
|
-
# @param kase [Case]
|
|
22
|
-
# @param model [Model]
|
|
23
|
-
# @return [Reply] with `error:` set when the model failed to answer; raise for a transport failure
|
|
24
|
-
def call(kase, model:)
|
|
25
|
-
raise NotImplementedError, "#{self.class}#call(kase, model:) must return an LLM::Evals::Reply"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# What the scorer needs to know about a case beyond the case itself: a
|
|
29
|
-
# schema, field types, the prompt. Passed to {Scorer#call} as `context:`.
|
|
30
|
-
#
|
|
31
|
-
# @param kase [Case]
|
|
32
|
-
# @return [Hash]
|
|
33
|
-
def context(_kase) = {}
|
|
34
|
-
|
|
35
|
-
# Offline checks of a dataset's shared files. `llm-evals verify` prints them.
|
|
36
|
-
#
|
|
37
|
-
# @param dataset [String]
|
|
38
|
-
# @return [Array<String>] problems; empty when the dataset is sound
|
|
39
|
-
def verify(_dataset) = []
|
|
40
|
-
|
|
41
|
-
# Recorded with each Braintrust experiment, e.g. which prompt version ran.
|
|
42
|
-
#
|
|
43
|
-
# @return [Hash]
|
|
44
|
-
def metadata = {}
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
data/lib/llm/evals/case.rb
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LLM
|
|
4
|
-
module Evals
|
|
5
|
-
# One eval case, as {Discovery} found it on disk: the text the model is
|
|
6
|
-
# given, the answer a perfect reply would produce, and where it came from.
|
|
7
|
-
#
|
|
8
|
-
# @!attribute [r] id
|
|
9
|
-
# @return [String] unique within its dataset, e.g. "clear/003"
|
|
10
|
-
# @!attribute [r] dataset
|
|
11
|
-
# @return [String] the folder under datasets/, e.g. "colors"
|
|
12
|
-
# @!attribute [r] variant
|
|
13
|
-
# @return [String] the folder under cases/, e.g. "clear" or "vague"
|
|
14
|
-
# @!attribute [r] input
|
|
15
|
-
# @return [String] input.txt, stripped
|
|
16
|
-
# @!attribute [r] expected
|
|
17
|
-
# @return [Object, nil] expected.json parsed; nil when missing or unparsable
|
|
18
|
-
# @!attribute [r] path
|
|
19
|
-
# @return [String] the case's directory
|
|
20
|
-
# @!attribute [r] metadata
|
|
21
|
-
# @return [Hash{Symbol => Object}] number, difficulty, and any `expected_error`
|
|
22
|
-
Case = Data.define(:id, :dataset, :variant, :input, :expected, :path, :metadata) do
|
|
23
|
-
# @return [Integer, nil] the NNN/ level as a number, when the case has one
|
|
24
|
-
def number = metadata[:number]
|
|
25
|
-
|
|
26
|
-
# @return [Integer] the number, else 0; what the tables sort and show difficulty by
|
|
27
|
-
def difficulty = metadata.fetch(:difficulty, number || 0)
|
|
28
|
-
|
|
29
|
-
# @return [String] "dataset/id", unique across the suite
|
|
30
|
-
def key = "#{dataset}/#{id}"
|
|
31
|
-
|
|
32
|
-
# @return [Boolean] whether expected.json was there and parsed
|
|
33
|
-
def expected? = !expected.nil?
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
data/lib/llm/evals/completion.rb
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LLM
|
|
4
|
-
module Evals
|
|
5
|
-
# What a {Model} returns from one call: the text, what it cost, and the
|
|
6
|
-
# provider's raw response for an adapter that needs more than the text.
|
|
7
|
-
Completion = Data.define(:text, :usage, :raw) do
|
|
8
|
-
def initialize(text:, usage: Usage.zero, raw: nil) = super(text: text.to_s, usage: Usage.from(usage), raw: raw)
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LLM
|
|
4
|
-
module Evals
|
|
5
|
-
# Everything a suite tells the gem, through {LLM::Evals.configure}.
|
|
6
|
-
# Paths are relative to `root`, the directory the eval helper lives in.
|
|
7
|
-
class Configuration
|
|
8
|
-
DEFAULT_GLOB = "datasets/*/cases/**/input.txt"
|
|
9
|
-
DEFAULT_NAME = "llm-evals"
|
|
10
|
-
DEFAULT_CONFIG = ".llm-evals.config.yml"
|
|
11
|
-
|
|
12
|
-
attr_writer :adapter, :scorer
|
|
13
|
-
attr_reader :name, :root, :env_files, :cases_glob, :hooks, :providers, :trackers, :description
|
|
14
|
-
|
|
15
|
-
def initialize
|
|
16
|
-
@name = DEFAULT_NAME
|
|
17
|
-
@root = Dir.pwd
|
|
18
|
-
@env_files = []
|
|
19
|
-
@cases_glob = DEFAULT_GLOB
|
|
20
|
-
@config_path = DEFAULT_CONFIG
|
|
21
|
-
@hooks = []
|
|
22
|
-
@providers = Providers::Registry.default
|
|
23
|
-
@trackers = Trackers::Registry.default
|
|
24
|
-
@description = "Run a suite of cases against one or more models and score the replies."
|
|
25
|
-
@project = nil
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Names the suite. The name titles the help and tags the experiments.
|
|
29
|
-
#
|
|
30
|
-
# @param name [String]
|
|
31
|
-
# @param root [String] where .llm-evals.config.yml and datasets/ live; the helper's directory
|
|
32
|
-
# @param env_files [Array<String>] dotenv files to load, relative to root; the first that exists wins nothing,
|
|
33
|
-
# all that exist load, and an exported variable beats them all
|
|
34
|
-
# @param description [String, nil] one line for the help's header
|
|
35
|
-
# @return [void]
|
|
36
|
-
def suite(name, root:, env_files: [], description: nil)
|
|
37
|
-
@name = name
|
|
38
|
-
@root = File.expand_path(root)
|
|
39
|
-
@env_files = env_files.map { File.expand_path(it, @root) }
|
|
40
|
-
@description = description if description
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Where the cases are: a glob relative to root ending in the input file.
|
|
44
|
-
# Each match is a case; the expected answer is `expected.json` beside it.
|
|
45
|
-
#
|
|
46
|
-
# @param glob [String]
|
|
47
|
-
# @return [void]
|
|
48
|
-
def cases(glob)
|
|
49
|
-
@cases_glob = glob
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# @param object [Adapter]
|
|
53
|
-
# @return [void]
|
|
54
|
-
|
|
55
|
-
# @param object [Adapter, nil] setting; reading when nil
|
|
56
|
-
# @return [Adapter]
|
|
57
|
-
def adapter(object = nil)
|
|
58
|
-
@adapter = object unless object.nil?
|
|
59
|
-
@adapter
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# @param object [Scorer, nil] setting; reading when nil
|
|
63
|
-
# @return [Scorer]
|
|
64
|
-
def scorer(object = nil)
|
|
65
|
-
@scorer = object unless object.nil?
|
|
66
|
-
@scorer
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# @param object [Scorer]
|
|
70
|
-
# @return [void]
|
|
71
|
-
|
|
72
|
-
# Runs once, after the env files are loaded and before any model is
|
|
73
|
-
# called. The place to reset a library that read ENV at require time.
|
|
74
|
-
#
|
|
75
|
-
# @yield
|
|
76
|
-
# @return [void]
|
|
77
|
-
def before_run(&block)
|
|
78
|
-
@hooks << block
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Adds a provider, ahead of the defaults, so a suite's own transport
|
|
82
|
-
# wins the ids it claims.
|
|
83
|
-
#
|
|
84
|
-
# @param provider [Providers::Base]
|
|
85
|
-
# @return [void]
|
|
86
|
-
def provider(provider)
|
|
87
|
-
@providers.register(provider)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Adds a tracker class, so a run can choose it by name with `--tracker`
|
|
91
|
-
# or `tracker:`, or have its env key select it.
|
|
92
|
-
#
|
|
93
|
-
# @param klass [Class<Tracker>]
|
|
94
|
-
# @return [void]
|
|
95
|
-
def tracker(klass)
|
|
96
|
-
@trackers.register(klass)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# @param path [String, nil] setting, relative to root; reading when nil
|
|
100
|
-
# @return [String] the absolute path of the lineup file, `.llm-evals.config.yml` at the root by default
|
|
101
|
-
def config(path = nil)
|
|
102
|
-
@config_path = path unless path.nil?
|
|
103
|
-
File.expand_path(@config_path, root)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# @param name [String, nil] setting; reading when nil
|
|
107
|
-
# @return [String, nil] the Braintrust project, when .llm-evals.config.yml does not name one
|
|
108
|
-
def project(name = nil)
|
|
109
|
-
@project = name unless name.nil?
|
|
110
|
-
@project
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# @return [Discovery] the cases, as the glob finds them
|
|
114
|
-
def discovery = Discovery.new(root: root, glob: cases_glob)
|
|
115
|
-
|
|
116
|
-
# @return [Config] the lineup file
|
|
117
|
-
def lineup = Config.new(config, direct_providers: providers.direct_names)
|
|
118
|
-
|
|
119
|
-
# @return [Runtime]
|
|
120
|
-
def runtime = Runtime.new(env_files: env_files, hooks: hooks, app_name: name)
|
|
121
|
-
|
|
122
|
-
# @return [Boolean] whether an adapter and a scorer are registered
|
|
123
|
-
def complete? = !adapter.nil? && !scorer.nil?
|
|
124
|
-
|
|
125
|
-
# Fails a command that needs the suite when no helper was loaded, with
|
|
126
|
-
# the fix in the message, instead of a NoMethodError on nil.
|
|
127
|
-
#
|
|
128
|
-
# @return [self]
|
|
129
|
-
# @raise [Error]
|
|
130
|
-
def require_suite!
|
|
131
|
-
resolve_from_config!
|
|
132
|
-
return self if complete?
|
|
133
|
-
|
|
134
|
-
raise Error,
|
|
135
|
-
"no suite is configured: register an adapter and a scorer in evals/eval_helper.rb, or set " \
|
|
136
|
-
"adapter: and scorer: (with require:) in #{config}; --helper PATH or LLM_EVALS_HELPER names another helper"
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Fills whatever the helper left unset from the config file's `require:`,
|
|
140
|
-
# `adapter:` and `scorer:` keys, so a suite can be config-only: its Ruby
|
|
141
|
-
# lives in its own gem and `evals/` holds the file and the datasets. The
|
|
142
|
-
# helper, when there is one, was loaded first and wins.
|
|
143
|
-
#
|
|
144
|
-
# @return [void]
|
|
145
|
-
# @raise [Error] naming the key and the value when a require fails or a constant is unknown
|
|
146
|
-
def resolve_from_config!
|
|
147
|
-
return if complete? || !File.exist?(config)
|
|
148
|
-
|
|
149
|
-
file = lineup
|
|
150
|
-
file.requires.each do |feature|
|
|
151
|
-
require feature
|
|
152
|
-
rescue LoadError => e
|
|
153
|
-
raise Error, "#{config}: require: #{feature} could not be loaded (#{e.message}); is its gem in the bundle?"
|
|
154
|
-
end
|
|
155
|
-
@adapter ||= instantiate("adapter", file.adapter_name)
|
|
156
|
-
@scorer ||= instantiate("scorer", file.scorer_name) # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
private
|
|
160
|
-
|
|
161
|
-
# @return [Object, nil] a new instance of the named constant; nil when no name is set
|
|
162
|
-
def instantiate(key, name)
|
|
163
|
-
return nil if name.nil? || name.empty?
|
|
164
|
-
|
|
165
|
-
Object.const_get(name).new
|
|
166
|
-
rescue NameError, ArgumentError => e
|
|
167
|
-
raise Error,
|
|
168
|
-
"#{config}: #{key}: #{name} could not be instantiated (#{e.class}: #{e.message}); " \
|
|
169
|
-
"name a constant the require: key loads, taking no arguments"
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
data/lib/llm/evals/contender.rb
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LLM
|
|
4
|
-
module Evals
|
|
5
|
-
# One model at one setting: what a single experiment runs. Built by
|
|
6
|
-
# {Config} from the lineup file, or from `-m` on the command line.
|
|
7
|
-
#
|
|
8
|
-
# `provider` is nil for a `vendors:` entry, which OpenRouter serves, and
|
|
9
|
-
# the provider's name for a `direct:` entry. It travels with the
|
|
10
|
-
# contender rather than being read back out of the id, so `typesafe/jev-router`
|
|
11
|
-
# (Jev on OpenRouter) and a direct `typesafe/jev-latest` can never be confused.
|
|
12
|
-
Contender = Data.define(:model, :effort, :temperature, :max_tokens, :provider) do
|
|
13
|
-
def initialize(model:, effort: "default", temperature: "default", max_tokens: nil, provider: nil) = super
|
|
14
|
-
|
|
15
|
-
# @return [Boolean] whether the entry came from `direct:`
|
|
16
|
-
def direct? = !provider.nil?
|
|
17
|
-
|
|
18
|
-
# @return [String] unique across the lineup: "typesafe:typesafe/jev-latest" for a direct entry
|
|
19
|
-
def key = direct? ? "#{provider}:#{model}" : model
|
|
20
|
-
|
|
21
|
-
# The settings as a {Model} takes them: "default" means nil, send nothing.
|
|
22
|
-
#
|
|
23
|
-
# @return [Hash{Symbol => Object}]
|
|
24
|
-
def settings
|
|
25
|
-
{ effort: effort.to_s == "default" || effort.nil? ? nil : effort.to_s,
|
|
26
|
-
temperature: temperature.to_s == "default" || temperature.nil? ? nil : Float(temperature),
|
|
27
|
-
max_tokens: max_tokens.nil? ? nil : Integer(max_tokens) }
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
data/lib/llm/evals/discovery.rb
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LLM
|
|
4
|
-
module Evals
|
|
5
|
-
# Finds the cases under a root with a glob and decodes each path into a
|
|
6
|
-
# {Case}. The default layout is
|
|
7
|
-
#
|
|
8
|
-
# datasets/<dataset>/cases/<variant>/[NNN/]input.txt
|
|
9
|
-
# datasets/<dataset>/cases/<variant>/[NNN/]expected.json
|
|
10
|
-
#
|
|
11
|
-
# The folder after `datasets/` is the dataset, the folder holding the
|
|
12
|
-
# input is the variant, unless it is a number, in which case it is the
|
|
13
|
-
# case number and its parent is the variant.
|
|
14
|
-
class Discovery
|
|
15
|
-
EXPECTED = "expected.json"
|
|
16
|
-
METADATA = "metadata.json"
|
|
17
|
-
|
|
18
|
-
attr_reader :root, :glob
|
|
19
|
-
|
|
20
|
-
# @param root [String] an absolute directory
|
|
21
|
-
# @param glob [String] relative to root
|
|
22
|
-
def initialize(root:, glob: Configuration::DEFAULT_GLOB)
|
|
23
|
-
@root = root
|
|
24
|
-
@glob = glob
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# @return [Array<String>] every dataset that has at least one case, sorted
|
|
28
|
-
def datasets = all.map(&:dataset).uniq.sort
|
|
29
|
-
|
|
30
|
-
# @param dataset [String, nil] one dataset; nil for all of them
|
|
31
|
-
# @param only [Array<String>] id prefixes to keep ("clear", "clear/00"); empty keeps all
|
|
32
|
-
# @return [Array<Case>] sorted by dataset, variant and number
|
|
33
|
-
# @raise [Error] when the dataset does not exist
|
|
34
|
-
def cases(dataset: nil, only: [])
|
|
35
|
-
found = all
|
|
36
|
-
if dataset
|
|
37
|
-
raise Error, "no dataset #{dataset.inspect}; have #{datasets.join(", ")}" unless datasets.include?(dataset)
|
|
38
|
-
|
|
39
|
-
found = found.select { it.dataset == dataset }
|
|
40
|
-
end
|
|
41
|
-
only = only.flat_map { it.split(",") }.map(&:strip).reject(&:empty?)
|
|
42
|
-
found = found.select { |kase| only.any? { kase.id.start_with?(it) || kase.key.start_with?(it) } } unless only.empty?
|
|
43
|
-
found
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# @return [Array<Case>]
|
|
47
|
-
def all
|
|
48
|
-
@all ||= Dir[File.join(root, glob)].map { decode(it) }
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Builds the case at one input file.
|
|
52
|
-
#
|
|
53
|
-
# @param input_path [String]
|
|
54
|
-
# @return [Case]
|
|
55
|
-
def decode(input_path)
|
|
56
|
-
dir = File.dirname(input_path)
|
|
57
|
-
parts = Pathname(dir).relative_path_from(Pathname(root)).each_filename.to_a
|
|
58
|
-
number = parts.last.match?(/\A\d+\z/) ? parts.last : nil
|
|
59
|
-
variant = number ? parts[-2] : parts.last
|
|
60
|
-
expected, problem = read_expected(File.join(dir, EXPECTED))
|
|
61
|
-
Case.new(
|
|
62
|
-
id: [variant, number].compact.join("/"),
|
|
63
|
-
dataset: dataset_of(parts),
|
|
64
|
-
variant: variant,
|
|
65
|
-
input: File.read(input_path).strip,
|
|
66
|
-
expected: expected,
|
|
67
|
-
path: dir,
|
|
68
|
-
metadata: metadata(dir, number, problem)
|
|
69
|
-
)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
# The folder after `datasets/`, else the first folder, so a glob that
|
|
75
|
-
# does not say `datasets` still groups by its top level.
|
|
76
|
-
def dataset_of(parts)
|
|
77
|
-
index = parts.index("datasets")
|
|
78
|
-
index && parts[index + 1] ? parts[index + 1] : parts.first
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def read_expected(path)
|
|
82
|
-
return [nil, "#{EXPECTED} is missing"] unless File.exist?(path)
|
|
83
|
-
|
|
84
|
-
[JSON.parse(File.read(path)), nil]
|
|
85
|
-
rescue JSON::ParserError => e
|
|
86
|
-
[nil, "#{EXPECTED} is not valid JSON: #{e.message.lines.first&.strip}"]
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def metadata(dir, number, problem)
|
|
90
|
-
extra = File.exist?(File.join(dir, METADATA)) ? JSON.parse(File.read(File.join(dir, METADATA))).transform_keys(&:to_sym) : {}
|
|
91
|
-
{ number: number&.to_i, difficulty: number.to_i, expected_error: problem }.compact.merge(extra)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|