Draft: lighteval converter (for #189) - #235
Conversation
Converts lighteval results_*.json into EvaluationLogs, one per measured
task, following the lm_eval converter layout.
Handles the two traps in lighteval's `results` mapping: `{metric}_stderr`
lives in the same dict as its metric (attached as uncertainty, never
emitted as a metric), and the mapping also holds rows lighteval averaged
itself (`<parent>:_average|<fewshot>` and `all`), which are skipped and
recorded rather than emitted as siblings of their own parts.
A task whose every metric aggregated to NaN was dropped from the file with nothing recorded anywhere. Its key is now listed on the logs the same file did produce.
mrshu
left a comment
There was a problem hiding this comment.
⚒️ review-anvil report
Review decision: COMMENT — This run is comment-only; two high-priority and six medium suggestions remain.
Result: The draft covers several tricky lighteval shapes well. Stable identity and complete source accounting need attention before publication.
Scope: Add a dependency-free lighteval aggregate converter with CLI, docs, fixture, and offline tests.
Checks: 3 single-reviewer concerns checked; 1 confirmed and 2 narrowed.
Second check: Targeted, 2 reviewers; all findings kept, with deployment, count, credential, and test guidance clarified.
Earlier review comments
No earlier review comments were found.
What I noticed
| ID | Priority | Topic | Code location | What I noticed |
|---|---|---|---|---|
| RAV-RUN1-R1-F001 | high | evaluation identity | every_eval_ever/converters/lighteval/adapter.py:375 |
evaluation_id includes retrieval time, so identical input gets a new identity on every conversion. |
| RAV-RUN1-R1-F002 | high | source accounting | every_eval_ever/converters/lighteval/adapter.py:524 |
A directory whose measured tasks all lack finite scores can exit successfully with zero records and no failure ledger. |
| RAV-RUN1-R1-F004 | medium | score coverage | every_eval_ever/converters/lighteval/adapter.py:185 |
Capped runs expose original dataset size, but do not clearly preserve the smaller population that produced the score. |
| RAV-RUN1-R1-F005 | medium | metric semantics | every_eval_ever/converters/lighteval/adapter.py:287 |
An undeclared result key is published with an assumed higher-is-better direction and no verified scale. |
| RAV-RUN1-R1-F006 | medium | metric identity | every_eval_ever/converters/lighteval/adapter.py:297 |
Every metric omits metric_id, including global metrics such as accuracy. |
| RAV-RUN1-R1-F007 | medium | coverage totals | every_eval_ever/converters/lighteval/adapter.py:538 |
total_records combines task-level output logs with file-level failures. |
| RAV-RUN1-R1-F008 | medium | failure reporting | every_eval_ever/cli.py:234 |
Single-file parse or conversion failures exit before a structured failure report is saved. |
| RAV-RUN1-R1-F009 | medium | credential filtering | every_eval_ever/converters/lighteval/utils.py:185 |
Supported nested model configuration such as env_vars is serialized without recursive secret filtering. |
ID legend: RUN is the observed PR review run, R is the origin round, and F is a finding.
Non-blocking low-priority suggestions (2 items)
- RAV-RUN1-R1-F003 [low] deployment metadata — Supported logs normally publish
deployment_typeandmodel_availabilityasunknown, with no CLI override for known facts. - RAV-RUN1-R1-F010 [low] test coverage — The thorough synthetic fixture is useful. An end-to-end publication assertion would also prove strict output parsing through the real CLI.
Things to try (10 items)
- [high] evaluation identity — Stable raw model identity, task, source timestamp, and a non-secret run-config digest can form an idempotent ID. (
RAV-RUN1-R1-P001; coversRAV-RUN1-R1-F001) - [high] source accounting — A file with measured but unconvertible tasks can become a failure; a file containing only intentional derived rows can remain an exclusion. (
RAV-RUN1-R1-P002; coversRAV-RUN1-R1-F002) - [low] deployment metadata — Validated CLI overrides could follow the existing platform and engine override pattern. (
RAV-RUN1-R1-P003; coversRAV-RUN1-R1-F003) - [medium] score coverage — Both original and effective document counts can stay visible, with the evaluated count identified as the score population. (
RAV-RUN1-R1-P004; coversRAV-RUN1-R1-F004) - [medium] metric semantics — Source metric specs or an operator definition can supply direction and scale. Unresolved keys can enter the failure ledger after valid metrics are preserved. (
RAV-RUN1-R1-P005; coversRAV-RUN1-R1-F005) - [medium] metric identity — One metric-definition resolver can provide ID, direction, type, and bounds without duplicate maps. (
RAV-RUN1-R1-P006; coversRAV-RUN1-R1-F006) - [medium] coverage totals — Results files can remain the source-record grain, with output-log count reported separately. (
RAV-RUN1-R1-P007; coversRAV-RUN1-R1-F007) - [medium] failure reporting — A small lighteval-local single-file result path can reuse directory reporting behavior before raising. (
RAV-RUN1-R1-P008; coversRAV-RUN1-R1-F008) - [medium] credential filtering — One recursive sanitizer can protect nested mappings and lists before model configuration is serialized. (
RAV-RUN1-R1-P009; coversRAV-RUN1-R1-F009) - [low] test coverage — The existing fixture can drive one CLI publication test that strictly parses and validates each emitted file. (
RAV-RUN1-R1-P010; coversRAV-RUN1-R1-F010)
Run details
- Target: PR #235 at
75a03c269eb60dbaf5247ec4a36dc79af18dcbdf(11 files, +1663/-4) - Run ordinal: 1
- Rounds: 1/1 completed; adaptive off
- Mix: 3 codex-exec
- Focus: correctness, maintainability, simplicity, production blast radius, converter content checks, and constructive suggestions
- Earlier review comments: none
- Finding counts: 0 critical, 2 high, 6 medium, 2 low, 0 nit
- Checks: concerns=3; confirmed=1; narrowed=2; set-aside=0; removed=0
- Second check: targeted; reviewers=2; kept=10; clarified=4; set-aside=0; removed=0
- Fixes applied: 0 (review-only)
Reviewed with review-anvil.
| retrieved_timestamp = get_current_unix_timestamp() | ||
| eval_timestamp = metadata_args.get('evaluation_timestamp') | ||
|
|
||
| evaluation_id = f'{task_key}/{model_info.id}/{retrieved_timestamp}' |
There was a problem hiding this comment.
RAV-RUN1-R1-F001 [high] evaluation-identity — evaluation_id includes the current retrieval time. The same source file therefore gets a different logical identity on every conversion, which allows duplicate evaluations on re-ingest.
A stable ID can use the raw model identity, task key, source timestamp, and a deterministic digest of non-secret run configuration. Retrieval time can remain only in retrieved_timestamp.
| failures: list[SourceRecordFailure] = [] | ||
| for results_file in results_files: | ||
| try: | ||
| all_logs.extend( |
There was a problem hiding this comment.
RAV-RUN1-R1-F002 [high] source-accounting — An input file whose measured tasks all lack finite scores returns an empty list without a failure. A directory made from those files then reports zero converted logs and exits successfully, so automation cannot distinguish total conversion loss.
A file with measured but unconvertible tasks can enter the failure ledger. Files containing only intentionally skipped derived rows can stay explicit exclusions.
| source_type='hf_dataset', | ||
| hf_repo=hf_repo, | ||
| hf_split=evaluation_splits[0] if evaluation_splits else None, | ||
| samples_number=( |
There was a problem hiding this comment.
RAV-RUN1-R1-F004 [medium] score-coverage — Capped or deduplicated runs put original_num_docs in samples_number, while uncertainty uses effective_num_docs. The dataset size is valid source context, but the record does not clearly preserve the smaller population that produced the score.
Both counts can remain visible, with the effective count identified as score coverage and the original count retained as dataset provenance.
| higher_is_better = higher_is_better_for(metric_spec, metric_name) | ||
|
|
||
| metric_details = {} | ||
| if higher_is_better is None: |
There was a problem hiding this comment.
RAV-RUN1-R1-F005 [medium] metric-semantics — When a result key has no matching metric spec, the converter assumes higher-is-better and publishes no verified scale or bounds. A finite custom key does not prove that direction.
Source metric specs or an operator-supplied definition can establish the semantics. Unresolved keys can be reported after the valid metrics are preserved.
| # Preserve metrics whose mathematical range is not yet known | ||
| # without falsely declaring them continuous and unbounded. | ||
| metric_details['bounds_status'] = 'unknown' | ||
| metric_config = MetricConfig( |
There was a problem hiding this comment.
RAV-RUN1-R1-F006 [medium] metric-identity — Both MetricConfig branches omit metric_id, including common metrics such as accuracy. metric_name keeps the source label but does not provide the cross-source join key.
The same metric-definition mapping used for direction and bounds can supply canonical global IDs and stable namespaced IDs for defined lighteval-specific metrics.
|
|
||
| return SourceConversionResult( | ||
| source_name=f'lighteval evaluations under {dir_path}', | ||
| total_records=len(all_logs) + len(failures), |
There was a problem hiding this comment.
RAV-RUN1-R1-F007 [medium] coverage-totals — total_records adds task-level output logs to file-level failures. One successful file can contribute several counts, while one failed file contributes one, so the coverage denominator has no consistent source unit.
Results files can remain the source-record grain, with converted output-log count reported separately.
|
|
||
| log_path = Path(args.log_path) | ||
| input_result: SourceConversionResult[Any] | None = None | ||
| if log_path.is_file(): |
There was a problem hiding this comment.
RAV-RUN1-R1-F008 [medium] failure-reporting — The single-file branch calls transform_from_file before it creates a SourceConversionResult. Parse or conversion errors exit non-zero, but the structured failure report is never saved; directory input reports the same failure class correctly.
A small lighteval-local file-result path can give both entry modes the same report-before-raise behavior.
|
|
||
| flattened: Dict[str, str] = {} | ||
| redacted: List[str] = [] | ||
| for key, value in model_config.items(): |
There was a problem hiding this comment.
RAV-RUN1-R1-F009 [medium] credential-filtering — Secret filtering checks only top-level model-config keys before nested values are serialized. Supported v0.13 configurations can carry unrestricted env_vars, so a nested token or API key can reach published additional_details.
One recursive sanitizer for mappings and lists can protect these values before serialization. Tests with provider-prefixed key names would cover the reachable path without recording secret values.
…s and accounting
F009 credential-filtering. The filter tested only top-level model-config keys,
and the else branch json.dumps() the whole value, so a nested env_vars mapping
carrying OPENAI_API_KEY was serialised intact into additional_details, which is
published. Sanitising is now recursive over mappings and lists, and redacted
keys are reported as dotted paths. Matching is on exact names plus suffixes
(_key, _token, _secret, _password, _credentials) rather than substrings, so
tokenizer and max_tokens are not mistaken for secrets.
F001 evaluation-identity. evaluation_id was task/model/retrieved_timestamp,
where retrieved_timestamp is the conversion time, so the same source file got a
new identity on every conversion and re-ingest could duplicate it. It is now
keyed on the raw source identity — task key, the model name as the file states
it, the run's own wall-clock stamp — plus a digest of the non-secret config so
runs differing only in settings stay distinct. Conversion time remains in
retrieved_timestamp alone.
Worth flagging: CONTRIBUTING.md line 102 prescribes exactly the unstable form
({benchmark_name/model_id/retrieved_timestamp}), while the conversion skill's
fields.md says to key on a stable value and never on `now`. This follows
fields.md; the two documents still disagree.
F002 source-accounting. A file whose measured tasks all lacked finite scores
returned an empty list with no failure, so a directory of such files converted
zero records and exited successfully. That case now enters the failure ledger.
Files holding only derived aggregate rows are recorded as explicit exclusions
instead, since dropping those is deliberate.
F007 coverage-totals. total_records summed task-level logs with file-level
failures, so one good file contributed several counts and one bad file
contributed one. The source-record grain is now the results file; converted-log
count is already reported separately by failure_report().
F008 failure-reporting. The single-file CLI branch raised before building a
SourceConversionResult, so parse errors exited non-zero with no structured
report while directory input reported the same failure correctly. Both entry
modes now go through transform_from_file_result, which also removes the
duplicated per-file handling in the directory walk.
F006 metric-identity. metric_id was unset in both MetricConfig branches, so
records carried no cross-source join key. Names with an unambiguous canonical
identity map to it; everything else gets a stable lighteval/<name> id rather
than an invented one, and metric_id_source records which route was taken so a
later pass can resolve the namespaced ones against the eval-card-registry.
F005 metric-semantics. An undeclared direction is still assumed higher-is-better
because the schema requires one, but operators can now declare it via
metric_directions, and names left unresolved are reported on the eval metadata
instead of the guess passing unnoticed.
F004 score-coverage. samples_number stays the dataset size as provenance; when
a run is capped or deduplicated the smaller population that actually produced
the score is recorded as scored_num_docs, so both counts are visible.
39 lighteval tests (was 31), full suite 428 passed / 20 skipped, ruff clean,
and convert -> validate still passes 3/3.
|
All eight findings are addressed in deb0b8d, so this is ready for another pass whenever you want to run one. The two high ones: F009 was the worst of them and slightly worse than the report describes. The filter tested only top-level keys, and the else branch F001 — the defect is real, but the cause is a documentation conflict rather than an oversight. CONTRIBUTING.md line 102 prescribes The rest: F002 files with measured but unconvertible tasks now enter the failure ledger, while derived-only files are recorded as explicit exclusions; F007 the source-record grain is the results file, not a mix of task logs and file failures; F008 both CLI entry modes go through a shared F006 I did the conservative version. Names with an unambiguous cross-source identity map to it, everything else gets a stable 39 lighteval tests now (was 31), full suite 428 passed / 20 skipped, ruff clean, and convert → validate still passes 3/3. One question: the numbering skips F003. Dropped deliberately, or did a finding not post? |
# Conflicts: # AGENTS.md
`--include_details` reads the details parquet lighteval writes next to a run's results and publishes it as an instance-level `<uuid>_samples.jsonl`, with the aggregate's `detailed_evaluation_results` pointing at it. Mirrors lm_eval's `--include_samples`, including its partial-conversion behaviour: a run made without `save_details` still publishes its aggregates, but records a per-task failure and exits non-zero. Two details of lighteval's output shape the mapping: - `Doc.choices` is the options the model was shown for a multiple-choice task and the *reference answers* for a generative one. Only the first is published as `input.choices`; `input.reference` carries the gold either way, and `doc.sampling_methods` is what tells the two apart. - Values come out of parquet as numpy scalars, and numpy's integer types do not subclass `int`, so every numeric check unwraps first. Without that, a task whose `gold_index` is a list loses its whole reference. Locating the details file needs its own helper because `results_path_template` can move the results directory while details stay put, so the two are not always siblings; the search walks up to the shared output directory. The new `lighteval` extra is only pyarrow, to read the parquet. lighteval itself stays out of the project: it needs datasets>=4 while crfm-helm pins datasets~=3.1.
…tself The fixture here was not hand-copied: `scripts/upstream_smoke/lighteval_smoke.py` drives lighteval's own `Pipeline` with their `DummyModelConfig` (random logprobs, fixed text, no weights and no inference), so the parquet layout, the numpy dtypes and the task-key spelling are all upstream's. Two tasks, one multiple-choice and one generative, because a fake model only exercises the metrics its task defines. The test runs the real CLI and then the real validator at the canonical `data/<collection>/<developer>/<model>/` path, which is where the semantic checks fire -- so it asserts the converter's output is submittable, not merely that some fields have the values we expected. Exit 0 only: warning-only is valid locally but not merge-ready. `--refresh` regenerates the fixture against whatever lighteval is installed. Read the diff before committing it; it will just as happily record an upstream regression as an intended change. Scope is shape, not semantics: a metric switching percent to proportion passes this, as does a changed prompt template.
The committed fixture pins a past release, so it cannot tell us that a new one renamed a field or moved a file -- it keeps the old spelling and CI stays green until a user hits it. This job installs the current lighteval ad hoc, has its fake model produce a run, converts it and validates it. Not on pull_request: an upstream release should not turn an unrelated PR red, and a scheduled failure names the version that broke it. lighteval is installed with `--with`, never locked, for the datasets conflict with crfm-helm. Python 3.12 because 3.10 resolution yields a torch/transformers mismatch, and UV_TORCH_BACKEND=cpu to keep a CUDA torch off the runner.
mrshu
left a comment
There was a problem hiding this comment.
⚒️ review-anvil report
Review decision: COMMENT — The latest commit addresses many earlier concerns
and the focused suite is green; a few important trust-boundary and conversion
edge cases remain as focused suggestions.
Result: Stable ordinary IDs, recursive model-info redaction, file-level
failure reporting, metric IDs, and score-population provenance are meaningful
improvements. Three high-priority, five medium-priority, and two low-priority
follow-ups remain.
Scope: Adds a LightEval aggregate converter, two CLI entry points, shared
publication and accounting integration, documentation, a realistic fixture,
and offline tests.
Checks: 5 concerns checked; 3 kept (1 narrowed), 1 lowered in priority, and
1 ruled out.
Second check: targeted, 2 Codex reviewers; 10 findings kept, 7 fix paths
clarified, 0 set aside, 0 removed.
Earlier review comments
Earlier review comments (10 unique items)
- Fixed:
RAV-RUN1-R1-F004now records original and effective document counts;
RAV-RUN1-R1-F007now uses source-file totals and separate output counts;
RAV-RUN1-R1-F008now writes structured single-file failure reports. - Narrower but still present:
RAV-RUN1-R1-F001is stable for normal timestamped
files, but accepted timestamp-less names can collide;RAV-RUN1-R1-F009
recursively sanitizes model-info fields, but another raw projection can still
publish credentials. - Still present:
RAV-RUN1-R1-F002,RAV-RUN1-R1-F003,
RAV-RUN1-R1-F005,RAV-RUN1-R1-F006, andRAV-RUN1-R1-F010remain in
narrower forms described below.
What I noticed
| ID | Priority | Topic | Code location | What I noticed |
|---|---|---|---|---|
| RAV-RUN1-R1-F009 | high | credential filtering | every_eval_ever/converters/lighteval/adapter.py:232 |
The recursive sanitizer is a strong improvement for model_info, but generation configuration rereads raw generation_parameters and serializes unfamiliar keys. A credential there, or an Authorization value in nested headers, can still reach the published EvaluationLog. |
| RAV-RUN2-R1-F001 | high | model routing | every_eval_ever/converters/lighteval/adapter.py:106 |
A valid bare model name leaves developer=None. The adapter can build the log, but the shared publisher rejects its one-component model ID because it has no safe developer route. (inline) |
| RAV-RUN1-R1-F002 | high | partial accounting | every_eval_ever/converters/lighteval/adapter.py:587 |
A measured task with no finite score becomes a metadata note when a sibling task succeeds. The remaining logs publish and the CLI exits zero, so automation cannot detect the missing task. |
| RAV-RUN1-R1-F001 | medium | stable identity | every_eval_ever/converters/lighteval/adapter.py:426 |
Ordinary timestamped files now reconvert idempotently. Accepted names such as results_latest.json use unknown and omit task-specific run/content identity, so distinct runs can still collide. |
| RAV-RUN1-R1-F005 | medium | metric semantics | every_eval_ever/converters/lighteval/adapter.py:320 |
When required metric direction is unavailable, the converter still publishes an assumed higher-is-better value. The internal override is not exposed by either CLI; unknown optional bounds can remain explicitly unknown. |
| RAV-RUN1-R1-F006 | medium | metric identity | every_eval_ever/converters/lighteval/utils.py:185 |
The private metric table labels values canonical without a registry lookup, pinned snapshot, revision, or review status. This makes global join-key claims difficult to verify and lets ID, direction, and bounds drift independently. |
| RAV-RUN2-R1-F002 | medium | task isolation | every_eval_ever/converters/lighteval/adapter.py:598 |
If a later task raises, the file-level exception boundary reports the failure but loses valid sibling logs accumulated earlier in the loop. (inline) |
| RAV-RUN2-R1-F003 | medium | CLI provenance | every_eval_ever/converters/lighteval/__main__.py:25 |
The module entry point defaults to an empty organization and first_party; the top-level command defaults to unknown and third_party. Identical input therefore gets different provenance based only on entry point. (inline) |
Non-blocking low-priority findings (2 items)
- RAV-RUN1-R1-F003 [low] deployment metadata — Both deployment axes still
becomeunknown, and neither supported CLI accepts known operator values. - RAV-RUN1-R1-F010 [low] integration tests — The thorough adapter tests do
not yet exercise a real CLI parse, atomic publication, and semantic validation
of the final datastore path.
Things to try
Things to try (8 suggestions)
- [high] credential filtering — Sanitize model configuration once before
every projection, explicitly handle credential-bearing header/cookie fields,
keep redaction bookkeeping out of stable identity, and scan the complete
serialized log in tests. (RAV-RUN2-R1-P001; coversRAV-RUN1-R1-F009) - [high] model routing — Resolve known bare names with shared helpers and
registry resolution, add a bounded/offline path plus explicit identity
override, and turn unresolved unsafe routes into structured source failures.
The untouched raw name can remain the record-identity input.
(RAV-RUN2-R1-P002; coversRAV-RUN2-R1-F001) - [high] task accounting and isolation — Parse each file once and convert
measured tasks independently. Keep valid logs, aggregate failed-task details
into one file-level failure, publish successes, then exit nonzero so the
existing file-grain denominator stays coherent. (RAV-RUN2-R1-P003; covers
RAV-RUN1-R1-F002andRAV-RUN2-R1-F002) - [medium] stable identity — For an unparseable timestamp, hash a canonical
task-local, secret-safe payload with raw model, sanitized generation settings,
relevant task configuration, and that task's result. Exclude paths, secrets,
redaction lists, canonical registry IDs, and CLI provenance.
(RAV-RUN2-R1-P004; coversRAV-RUN1-R1-F001) - [medium] metric semantics — Use one validated metric-spec mapping with
canonical ID, direction, score type, and bounds together. Unresolved required
direction can enter the file-level failure ledger while known siblings remain
publishable; genuinely unknown optional bounds can stay labeled and omitted.
(RAV-RUN2-R1-P005; coversRAV-RUN1-R1-F005) - [medium] metric identity — Vendor a minimal revision-pinned registry
snapshot for known emitted metrics, deriving ID, direction, type, and bounds
from that one source. Unknowns can remain namespaced and explicitly unresolved.
(RAV-RUN2-R1-P006; coversRAV-RUN1-R1-F006) - [medium] CLI provenance — Reuse one LightEval parser/default definition in
both entry points, preserving current option aliases, and add a default/override
parity test. (RAV-RUN2-R1-P007; coversRAV-RUN2-R1-F003) - [low] metadata and integration — Add enum-validated deployment and
availability overrides through the shared parser, then exercise an offline
real-CLI publication and validate every emitted JSON semantically.
(RAV-RUN2-R1-P008; coversRAV-RUN1-R1-F003andRAV-RUN1-R1-F010)
Run details
- Target: PR #235 at
deb0b8ddbe69fe9212b8191d7bdfe73a86d4ad0e
(11 files, +2103/-4) - Run ordinal: 2
- Rounds: 1/1 completed; adaptive off; material findings remain
- Mix: 3 Codex reviewers
- Focus: correctness, source accounting, credentials, publication behavior,
simplicity, maintainability, and positive suggestion-oriented language - Earlier review comments: 10 unique concerns; 3 fixed and 7 narrower or still
present - Finding counts: 0 critical, 3 high, 5 medium, 2 low, 0 nit
- Verification: exact-head
tests/test_lighteval_adapter.py— 39 passed; no
GitHub check runs were reported for this head - Checks: concerns=5; kept=3 (narrowed=1)/ruled-out=1/lowered=1/set-aside=0
- Second check: targeted; reviewers=2; kept=10/clarified=7/set-aside=0/removed=0;
approval changed no - Set aside: 0 items
Reviewed with review-anvil.
| 'lighteval results file has no config_general.model_name' | ||
| ) | ||
|
|
||
| developer = None |
There was a problem hiding this comment.
RAV-RUN2-R1-F001 [high] model routing — A bare model_name leaves developer unset here. The adapter can build the log, but publication rejects a one-component model ID without a developer route. Shared model helpers, registry resolution, and an explicit identity override can cover known and operator-supplied names; an unresolved unsafe route can remain a structured source failure.
| f'none with a finite score: {", ".join(sorted(skipped_keys))}' | ||
| ) | ||
|
|
||
| results = [] |
There was a problem hiding this comment.
RAV-RUN2-R1-F002 [medium] task isolation — These sibling tasks share one file-level exception boundary. If a later task raises, this function never returns the valid logs already accumulated, so the result contains only the file failure. Per-task conversion can retain valid siblings and aggregate failed-task detail into the existing file-grain report.
| parser.add_argument( | ||
| '--source_organization_name', | ||
| type=str, | ||
| default='', |
There was a problem hiding this comment.
RAV-RUN2-R1-F003 [medium] CLI provenance — This entry point defaults organization to an empty string and evaluator relationship to first_party, while the top-level LightEval command uses unknown and third_party. Sharing one parser/default definition would keep identical inputs from receiving different implicit provenance.
|
Picking this up on the lighteval side rather than opening a competing PR — your three commits stay as they are, and everything below is additive on top of them (plus a merge of What's added
New:
Tests + fixture. The test runs the real CLI and then the real validator at the canonical Weekly upstream smoke ( Two things running it against real output turned upBoth were mine, in the new instance-level code, and both would have been invisible against a hand-written fixture:
One thing I left alone deliberately: the Scope, stated plainlyShape, not semantics. A metric that switches from percent to proportion upstream passes all of this, as does a changed prompt template. What it does catch is a converter that stops finding the details file, stops resolving the gold, or starts emitting records the datastore would reject. There's no Locally: |
…d and we missed huggingface/lighteval#1326 (merged today) made LiteLLMModelConfig.api_key a SecretStr and excluded BOTH api_key and inference_server_auth at the dump site. This converter's redaction knew about the first and not the second: matching is on exact names plus the suffixes _key/_token/_secret/_password/_credentials, none of which catch a field ending in _auth. So inference_server_auth was serialised into additional_details, which is published. The upstream fix does not make this guard redundant. Every results file written before it still holds the value in cleartext, and archived results files are precisely what a converter is pointed at. Adds the _auth suffix plus the exact names auth and inference_server_auth. The suffix is now value-aware, because widening it turned up a false positive in the first test written for it: requires_auth ends in _auth but a boolean cannot carry a secret, and redacting it would delete provenance for nothing. Exact names still redact whatever they hold; a suffix match requires a value a credential could actually be, and anything not positively known to be harmless still redacts -- a missed credential in a published record is unrecoverable, an over-redacted setting is not. Two tests: one pins inference_server_auth (and that the server ADDRESS, which is provenance rather than a credential, survives), one pins that requires_auth and authorized_users are not swallowed. 459 passed, 20 skipped; ruff clean.
mrshu
left a comment
There was a problem hiding this comment.
⚒️ review-anvil report
Review decision: COMMENT — The new sidecar and smoke coverage are valuable,
but two sample-integrity failures and several focused metadata issues remain.
Result: The focused tests and all four CI matrices pass. Six new suggestions
survived verification, and nine earlier findings remain present without new
inline threads.
Scope: Adds LightEval aggregate and instance conversion, CLI publication,
real upstream fixtures, and scheduled release-smoke coverage.
Checks: 4 concerns checked; all 4 confirmed.
Second check: targeted, 2 Codex reviewers; 6 findings kept and 5 fix paths
clarified.
Earlier review comments
Earlier review comments (13 unique findings)
- Still present at high priority:
RAV-RUN1-R1-F009can publish raw nested
generation credentials;RAV-RUN2-R1-F001cannot publish unresolved bare
model names;RAV-RUN1-R1-F002still omits a measured task when a valid
sibling lets the file publish successfully. - Still present at medium priority:
RAV-RUN1-R1-F001can collide for accepted
timestamp-less filenames;RAV-RUN1-R1-F005assumes unknown metric
direction;RAV-RUN1-R1-F006uses an unpinned private canonical metric map;
RAV-RUN2-R1-F002loses valid sibling tasks after a later task error; and
RAV-RUN2-R1-F003keeps different provenance defaults in the two CLIs. - Still present at low priority:
RAV-RUN1-R1-F003has no CLI overrides for
known deployment metadata. - Fixed on the current head:
RAV-RUN1-R1-F004,RAV-RUN1-R1-F007,
RAV-RUN1-R1-F008, andRAV-RUN1-R1-F010now preserve both sample counts,
use file-grain totals, report single-file failures, and test real CLI
publication.
What I noticed
| ID | Priority | Topic | Code location | What I noticed |
|---|---|---|---|---|
| RAV-RUN3-R1-F001 | high | instance metric linkage | every_eval_ever/converters/lighteval/instance_level_adapter.py:192 |
A multi-metric details row publishes only its first finite score. Neither aggregate nor instance records carry a result join ID. (inline) |
| RAV-RUN3-R1-F002 | high | details file routing | every_eval_ever/converters/lighteval/utils.py:226 |
Details lookup searches every model after the requested model subtree. A missing file can attach another model's samples. (inline) |
| RAV-RUN3-R1-F003 | medium | sample hash | every_eval_ever/converters/lighteval/instance_level_adapter.py:201 |
The sample hash uses default JSON whitespace, so it differs from the repository's cross-adapter canonical digest. (inline) |
| RAV-RUN3-R1-F004 | medium | instance correctness | every_eval_ever/converters/lighteval/instance_level_adapter.py:259 |
Every metric uses score == 1.0 for correctness. Continuous metrics can therefore publish a false correctness claim. (inline) |
| RAV-RUN3-R1-F005 | medium | instance score validation | every_eval_ever/converters/lighteval/instance_level_adapter.py:388 |
A row without a finite metric becomes a fabricated score of 0.0 and avoids the failure ledger. (inline) |
Non-blocking low-priority finding
- RAV-RUN3-R1-F006 [low] documentation — The root converter table still
says LightEval instance output is unavailable, although--include_details
now publishes it.
Things to try
Things to try (6 suggestions)
- [high] instance metric linkage — Create result IDs on aggregate metrics,
then fan out each sample with those exact stored IDs and verify join-set
equality. (RAV-RUN3-R1-P001; coversRAV-RUN3-R1-F001) - [high] details file routing — Resolve only the requested model, run date,
and task path. Treat missing or ambiguous details as conversion failures.
(RAV-RUN3-R1-P002; coversRAV-RUN3-R1-F002) - [medium] sample hash — Use the documented compact JSON recipe and pin one
expected digest. (RAV-RUN3-R1-P003; coversRAV-RUN3-R1-F003) - [medium] instance correctness — Use a reviewed binary-metric set. Mark
correctness not applicable in metadata for all other metrics.
(RAV-RUN3-R1-P004; coversRAV-RUN3-R1-F004) - [medium] instance score validation — Fail the containing details task
when any row has no finite metric. Reuse the existing task failure ledger.
(RAV-RUN3-R1-P005; coversRAV-RUN3-R1-F005) - [low] documentation — Update only the root table to name
--include_detailsand itssave_details=Truerequirement.
(RAV-RUN3-R1-P006; coversRAV-RUN3-R1-F006)
Run details
- Target: PR #235 at
927da0d470d743e9952f70f903461196ce5a28c1
(21 files, +3457/-5) - Run ordinal: 3
- Rounds: 1/1 completed; adaptive off; material findings remain
- Mix: 3 Codex reviewers
- Focus: correctness, blast radius, simplicity, maintainability, sidecar
semantics, smoke coverage, and positive suggestion-oriented language - Earlier review comments: 13 unique findings; 9 still present and 4 fixed
- Finding counts: 0 critical, 5 high, 8 medium, 2 low, 0 nit, including prior
carry-forwards - Verification: exact-head LightEval tests — 39 passed, 1 optional skip;
GitHub Actions passed all four core/full locked/loose matrices - Checks: concerns=4; confirmed=4/ruled-out=0/set-aside=0/lowered=0
- Second check: targeted; reviewers=2; kept=6/clarified=5/set-aside=0/removed=0;
approval changed no - Action lock: the first two-auditor launch returned no verifiable rows because
of a dispatch error. All 5 inline requests use the exact frozen source prose,
and the review event is forced to COMMENT.
Reviewed with review-anvil.
| generations, post_processed, logprobs, choices | ||
| ) | ||
|
|
||
| primary_metric, score = _primary_metric(metrics) |
There was a problem hiding this comment.
Multi-metric samples lose scores and cannot join aggregate results
_primary_metric keeps only the first finite metric, and neither output carries evaluation_result_id. A sample with K metrics publishes one structured score and leaves it unlinked.
What to change
Create each deterministic evaluation_result_id on the aggregate result. Emit one instance record for each finite metric using that exact stored ID. Add a multi-metric CLI publication test that checks exact aggregate/instance join-set equality, unique IDs, and K records per sample for K finite metrics.
| model_root = details_root / model_name.strip('/') | ||
| if model_root.is_dir(): | ||
| roots.append(model_root) | ||
| roots.append(details_root) |
There was a problem hiding this comment.
Details lookup can attach samples from another model
find_details_file searches the full details root after the requested model subtree. A missing requested-model file can therefore select a same-task file from another model.
What to change
Resolve only the exact requested-model, run-date, and task path. Treat an absent or ambiguous match as a details-conversion failure. Add coverage with two model trees and a missing requested-model file.
|
|
||
| # Build the sample hash from input + reference so the same dataset row | ||
| # hashes alike across models and harnesses. | ||
| hash_input = json.dumps( |
There was a problem hiding this comment.
LightEval sample hashes differ from the cross-adapter recipe
The hash payload sorts keys but keeps default JSON whitespace. Identical input and references therefore get a different digest from other EEE adapters.
What to change
Serialize raw and the full reference list with sort_keys=True and separators=(',', ':') before UTF-8 SHA-256 hashing. Add a fixed input and expected digest as a regression vector.
| # that sample, so 1.0 is exactly correct for acc/em and this | ||
| # says "not a perfect score" for anything continuous. The full | ||
| # per-sample metric mapping is in metadata either way. | ||
| is_correct=score == 1.0, |
There was a problem hiding this comment.
Continuous scores are published as answer correctness
Every metric uses score == 1.0 for is_correct. A graded metric with value 1.0 can therefore become a correct answer even when correctness is not defined.
What to change
Use a tight reviewed set of source metrics known to emit binary per-sample correctness. Keep is_correct false and record is_correct_applicable=false in instance metadata for unknown or graded metrics. Add tests for acc and em plus a continuous metric with score 1.0 that remains not correct and not applicable.
| return _is_finite(value) | ||
|
|
||
|
|
||
| def _primary_metric(metrics: Dict[str, Any]) -> tuple[Optional[str], float]: |
There was a problem hiding this comment.
Rows without finite metrics become fabricated zero scores
_primary_metric returns (None, 0.0), and _transform_row publishes that value. Missing source data becomes a valid-looking failed sample.
What to change
Fail and account the containing details task when any row has no finite metric. Include the row index and sample ID in the error. Add tests for empty and all-nonfinite metric mappings, plus a mixed mapping that still emits its finite metrics.
Draft for #189, which is assigned to @gbemike and stays theirs. They said they'd
been delayed by a PC problem and were happy to build on a draft, so this is that
draft — a starting point to take, cannibalise or bin. Not asking for
reassignment, and not asking for this to be merged over their work.
Adds
every_eval_ever/converters/lighteval/, following thelm_evallayout(
__init__/__main__/adapter/utils), plusconvert lightevalin thetop-level CLI and an offline fixture test. Pure JSON parsing, so lighteval isn't
a dependency and nothing needs guarding in the
corematrix.The question I'd like an opinion on
lighteval's
results(MetricsLogger.metric_aggregated) mixes rows it measuredwith rows it averaged.
aggregate()appends a per-parent mean under<parent>:_average|<fewshot>and a mean over everything under the literal keyall, into the same dict as the real tasks. Emit every key and you publish anaggregate and its own parts as siblings.
I skip the averaged rows and record which keys were skipped in
source_metadata.additional_details.lighteval_derived_rows_not_converted, so theomission is visible rather than silent. Reasons:
allaverages across every task-metric pairregardless of whether those are comparable.
aggregate()builds them frommetric_aggregated[subtask].keys(), whichincludes the
_stderrentries. So the averaged rows carry a mean of standarderrors, which isn't the standard error of the mean. Emitting that as
uncertainty would publish a number that is simply wrong.
config_tasks, so there's nosource_datafor them, andpublish_evaluation_logsneeds one.The counter-argument is that
allis what people quote from a lighteval run, anddropping it loses the headline number. If you'd rather have them with a marker on
the record, that's a small change and I'd rather you decide it than me.
Other calls worth a look
stderr.
MetricsLoggerwrites{metric}_stderrinto the same dict as themetric, so a naive loop over
results[task]yieldsacc_stderras its ownmetric. It's attached as
score_details.uncertainty.standard_errorinstead. It'sset to
float("nan")when the estimate overflows, and lighteval dumps that as abare
NaNtoken; that reads as "no uncertainty reported", so the field isomitted rather than written as
0.0. There's a fixture and a test for exactlythis.
stderr
method. Not always bootstrap —get_stderr_functionreturns theanalytic
mean_stderrwhen the aggregation's name containsmean. The converterapplies the same rule against the
corpus_level_fnrecorded inconfig_tasks,and omits
methodwhen the file doesn't say. Labelling everythingbootstrapwould have been wrong about half the time.
Timestamps.
config_general.start_timeandend_timearetime.perf_counter()values despite the docstring calling them Unix timestamps,so they can't be converted to an instant. The only wall clock in a run is the
date_idinresults_{date_id}.json(datetime.now().isoformat()with:swapped for
-). I parse that back to ISO-8601 and use it. Note this differsfrom the
lm_evalandinspectconverters, which emit a Unix epoch — going toepoch here would mean inventing a timezone for a naive local time, so I left it
as the string the source actually recorded. Happy to switch if you'd rather have
consistency across converters.
total_evaluation_time_secondesreally is spelled that way upstream, so theconverter reads that key; it's written out under the corrected spelling.
lighteval_shais"?"for any pip install (it shells out to git againstits own source tree). It's recorded in
eval_library.additional_detailswhenreal and omitted when
"?", and it never becomeseval_library.version— a gitSHA isn't a version.
Non-finite scores.
aggregate()sets a metric to NaN onOverflowError. Themetric is skipped and counted in
source_metadata.additional_details.metrics_dropped_non_finite, because thevalidator rejects a non-finite score and coercing it to 0 would invent a result.
If that takes out every metric of a task, the task produces no record at all, so
its key is listed under
tasks_without_finite_scoreson the logs the same filedid produce — otherwise it would disappear silently.
ModelInfo.name/idfromconfig_general.model_name,developerfromthe org prefix, and everything else stringified into
additional_details. Oninference_engine: lighteval dumpsmodel_configvia pydanticmodel_dump(),which carries no discriminator for the config subclass, so the backend isn't
recoverable from the file — it's a CLI flag rather than a guess.
inference_platformis read frommodel_config.providerwhen present (LiteLLMand inference-provider runs state it outright), CLI flag otherwise.
One thing I'd flag upstream too:
LiteLLMModelConfig.api_keyis a plainstr, andmodel_dump()puts it in the results file. Anyone who passed a key in--model-argshas it sitting inresults_*.jsonin cleartext. The converterdrops credential-bearing keys before they reach
additional_detailsand recordswhich key names it dropped. Worth knowing about regardless of what happens to
this PR.
Fixture
Written from the upstream writers at
v0.13.0(
logging/evaluation_tracker.py,logging/info_loggers.py), not captured from arun — lighteval pulls torch, which I didn't want to make a prerequisite for
reading this diff. Everything in it traces to a line in those files, but it has
not been checked against a real run, and that's the thing most worth a second
pair of eyes. It contains a bare
NaNtoken on purpose: that's whatjson.dumpsemits withallow_nanleft at its default, which is how lightevalcalls it.
Verification
uv run pytest tests— 31 new tests pass, no existing test changes behaviouruv run ruff check .— cleanconvert lightevalthenevery_eval_ever validate, 3/3 records pass with semantic checks onRun locally on Windows, where four tests unrelated to this change already fail on
main(path separators and CRLF). CI here is sitting ataction_required— itneeds a maintainer to approve the workflow run for a first-time contributor.
Shared-file change
One line outside the new directory:
SupportedLibrarygains aLIGHTEVALmember. The alternative was returning
CUSTOM, which would be false.