Add aiXamine adapter - #283
fatihdeniz wants to merge 8 commits into
Conversation
borgr
left a comment
There was a problem hiding this comment.
Reviewed this on a local rebase onto current main, since the fork CI gate has never released and the branch has therefore never been tested. Good news first — the rebase is clean, ruff check passes, and the full suite is 1345 passed, 1 skipped. The shape of the adapter is right, the catalog entry looks correct for nine collections on a weekly cadence, and emitting one aggregate log per (model, service) is the natural grain for this source.
Four things I'd want changed before this lands, two of them verified by running the code.
1. [high] A model with no developer disappears without a trace
_outputs_for (adapters/aixamine/adapter.py:279) returns early when _resolve_developer yields None. No failure, no exclusion, nothing in the report. I ran it against a report whose model carries no developer and a flat name:
total_records : 1
converted : 0
failures : 0
exclusions : 0
raise_if_incomplete -> returned normally (run would exit 0)
Under --all on the weekly cron, a change to the search payload's developer field could drop every report in the sweep and the job would still go green. raise_for_failed_records (helpers/io.py:392) returns early on an empty failure list, which is why the exit status cannot see this.
The hotfix commit was right that the run should not crash. The record just needs to say so:
def _outputs_for(report, model, catalog, out_root, retrieved_ts, outputs, failures):
"""Append this (report, model)'s service logs to outputs / failures."""
if _resolve_developer(model) is None:
failures.append(SourceRecordFailure(
source_ref=f"{SRC} report {report.get('_id')}",
reason="model has no developer and its name carries no namespace",
source_record={"model": model.get("name")}))
returnA test asserting the failure lands in the report would pin it.
2. [high] Two examinations of the same open-weights model share one evaluation_id
evaluation_id=f"{collection}/{developer}_{name.replace('/', '_')}" (adapter.py:200) has no run segment. For API models _dated_name appends the access date so successive examinations differ, but for accessType == "huggingface" it returns the bare name. Two reports with different scores collapse:
report r1 (Jan): aixamine_hallucination/meta-llama_meta-llama_Llama-3.1-8B-Instruct
report r2 (Jul): aixamine_hallucination/meta-llama_meta-llama_Llama-3.1-8B-Instruct
The schema asks for eval_name/model_id/retrieved_timestamp (eval_types.py:558). The report id is the stable choice here, since it stays constant across reruns of the same examination while retrieved_timestamp would change on every cron pass:
evaluation_id=f"{collection}/{model_id.replace('/', '_')}/{report.get('_id') or retrieved_ts}",That also drops the doubled developer visible above, where meta-llama appears twice because _dated_name already returns the namespaced name.
3. [medium] Every test in every service publishes the same metric_id
_metric_config sets metric_id=f"{SRC}.rate" for all 46 static and 5 dynamic tests. A consumer joining on metric_id pools a SimpleQA factuality score with a jailbreak refusal rate and a BBQ bias score as one quantity. Sibling adapters give each distinct quantity its own id — cocoabench.overall.accuracy_percent, bfcl.overall.rank. Suggested change, which keeps the unregistered namespace convention:
metric_id=f"{SRC}.{test_value}",metric_kind="accuracy" is worth a second look on the same grounds. A refusal rate on anthropic-redteam is not accuracy.
4. [medium] max_score=100.0 is asserted for all 51 tests
The docstring says aiXamine scores are a 0-100 rate, and that is plausible for the safety and refusal tests. Declaring it for every test means a consumer normalizing by max_score silently rescales any test that turns out to report a 0-1 proportion or an unbounded count. Does the API document the range per test, or is 0-100 an observation across the reports you captured? If the latter, leaving min_score/max_score unset for tests where nothing states the bound is the safer record.
Three smaller ones, none blocking.
EvalLibrary(name=SRC, version="unknown")(adapter.py:196) writes a placeholder the repo rejects elsewhere —require_identityinhelpers/io.py:203refuses the literal"unknown". Omitting the version says the same thing without asserting it.source_type="documentation"withevaluator_relationship=first_party. These numbers are aiXamine's own evaluation runs read from its API rather than values transcribed from a page, soevaluation_runlooks like the closer fit. Say if you read that enum the other way._bundle_from_fixtureuses bareopen()with no encoding and no context manager.json.loads((d / "report.json").read_text(encoding="utf-8"))matches the rest of the repo and closes the handle.
Happy to push the rebase I tested to your branch if that helps, and the CI gate needs a maintainer click either way — it is queued along with the others.
|
Thanks for the thorough pass, really helpful. Good call testing it on a rebase. Please go ahead and push the rebase you tested. I'll build the fixes on top of it. I have started working through them. Only for max_score: aiXamine scores are always normalized to a 0–100 range, so it is a real per-test bound rather than just an observation, so we'll keep it. |
|
Two things, and thanks for the quick turnaround.
The rebase. I rebased your three commits onto current Two ways to get it: Rebase it yourself, which is three commands and gives you the identical tree: That replays Or @borgr can push my verified rebase, since Whichever way, the CI approval click is still needed afterwards for the run to leave |
df77c8a to
1f0ffb8
Compare
|
Rebased your branch onto current Nothing of yours changed. Same three commits, same 503 insertions across the four files you touched, no content edits from me: Green locally on the rebase — The two findings from my review above are still open, and both are yours to judge rather than blockers I want to impose. The silent drop when |
|
Verdict — one word from mergeable. Everything I raised on the previous head is fixed and covered by tests. The only change left is the All four commits do what they say, and the tests you added pin the right things — Correcting a number from my previous comment. I quoted Two notes on your One thing I would change before merge, and it is one word.
- "paper": PAPER,
+ "paper_url": PAPER,Worth doing now rather than later. A published record is immutable under its UUID in the flat view, so renaming the key after these records exist means re-emitting every aiXamine record with fresh UUIDs. We are working through exactly that on another collection this week. Your One optional nit, take it or leave it. - evaluation_id=f"{collection}/{model_id.replace('/', '_')}/{report.get('_id') or retrieved_ts}",
+ evaluation_id=f"{collection}/{model_id.replace('/', '_')}/{report['_id']}",with the Also confirming a non-issue so you do not spend time on it. Push the key rename and #283 is ready to merge. The fork CI run needs a maintainer to release it, which we will do on the new head. |
|
I appreciate the detailed comments. I followed your recommendations. Now, (1) no-developer models record a failure, (2) Suite seems to be green ;) Thanks again for all the help! |
Adds an adapter for aiXamine (aixamine.qcri.org, paper), converting its public-API reports into EEE records — one aggregate log per (model, service) across 9 services.
adapters/aixamine/— the adapter + testscatalog.py— scheduled entry (9 collections, weekly)API models are date-stamped with their access date; HF models keep their repo id. Tests pass. Output validates clean.