8000 Comparison view: an empty used set agrees with nothing, same_harness / same_model false by ynishi · Pull Request #53 · ynishi/evalhub · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/evalhub-server/src/api/relations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@
//!
//! The hub does not rank the groups or the Cards in them. `same_harness`
//! and `same_model` say whether each Card's harness and model fingerprints
//! equal those of every run the Card used from this Eval (its used set;
//! with an empty used set, the Eval version's header), and that is the
//! whole of its opinion.
//! equal those of every run the Card used from this Eval (its used set).
//! An empty used set gives false for both, with `runs_used: 0`: the Card
//! was compared with no run. The Eval version's header is never compared.
//! That is the whole of its opinion.
//!
//! # What each Card used
//!
Expand Down Expand Up @@ -474,8 +475,8 @@ pub struct ComparisonRowDto {
/// Per-facet fingerprints, hex, keyed by facet name.
pub fingerprints: BTreeMap<String, String>,
/// The Card's harness fingerprint equals that of every run in its used
/// set for this Eval (with an empty used set, the Eval version's
/// header fingerprint). `false` when any differs or is missing.
/// set for this Eval. `false` when any differs or is missing, or when
/// the used set is empty (`runs_used` is 0: no run to agree with).
pub same_harness: bool,
/// As `same_harness`, for the model fingerprint.
pub same_model: bool,
Expand Down
59 changes: 58 additions & 1 deletion crates/evalhub-server/tests/relations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,51 @@ fn eval_titled(title: &str) -> String {
v.to_string()
}

/// A Card on an Eval with no runs used no run, so it agrees with none:
/// both flags are false even though its model and harness equal the Eval
/// header's, and `runs_used: 0` says why.
#[tokio::test]
async fn comparison_view_an_empty_used_set_agrees_with_nothing() {
let hub = Hub::start_with_storage().await;
let alice = hub.user("alice", Scope::Write).await;
hub.ready_attachments(CARD).await;
hub.ready_attachments(EVAL).await;

let (status, _) = hub
.call(
Method::POST,
"/api/v1/evals/alice/no-runs",
Some(&alice),
Some(&eval_titled("no runs")),
)
.await;
assert_eq!(status, StatusCode::CREATED);
let (status, created) = hub
.call(
Method::POST,
"/api/v1/cards/alice/on-no-runs",
Some(&alice),
Some(&card_citing("header model", "alice/no-runs@1", None)),
)
.await;
assert_eq!(status, StatusCode::CREATED, "{created}");

let (status, view) = hub
.call(
Method::GET,
"/api/v1/evals/alice/no-runs/cards",
Some(&alice),
None,
)
.await;
assert_eq!(status, StatusCode::OK, "{view}");
let items = view["items"].as_array().unwrap();
assert_eq!(items.len(), 1, "{view}");
assert_eq!(items[0]["runs_used"], 0);
assert_eq!(items[0]["same_model"], false, "{view}");
assert_eq!(items[0]["same_harness"], false, "{view}");
}

/// Two Cards measured on one Eval line up, and the hub says which axes
/// agree without ranking them.
#[tokio::test]
Expand All @@ -57,8 +102,18 @@ async fn comparison_view_lines_cards_up() {
)
.await;
assert_eq!(status, StatusCode::CREATED);
// One run, on the Eval's model and harness: the Cards compare with it.
let (status, body) = hub
.put_run(
&alice,
"alice/single2-k4",
"r1",
json!({"core/duration_ms": 131000.0}),
)
.await;
assert!(status.is_success(), "{status} {body}");

// Same model as the Eval, and a second Card on another model.
// Same model as the run, and a second Card on another model.
for (name, title, model) in [
("first", "same model", None),
("second", "other model", Some("other-model")),
Expand Down Expand Up @@ -91,6 +146,8 @@ async fn comparison_view_lines_cards_up() {
.iter()
.map(|i| (i["name"].as_str().unwrap(), i))
.collect();
assert_eq!(by_name["first"]["runs_used"], 1);
assert_eq!(by_name["second"]["runs_used"], 1);
assert_eq!(by_name["first"]["same_model"], true);
assert_eq!(by_name["first"]["same_harness"], true);
assert_eq!(by_name["second"]["same_model"], false);
Expand Down
2 changes: 1 addition & 1 deletion crates/evalhub-server/tests/snapshots/boot__openapi.snap
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ expression: body
"type": "integer"
},
"same_harness": {
"description": "The Card's harness fingerprint equals that of every run in its used\nset for this Eval (with an empty used set, the Eval version's\nheader fingerprint). `false` when any differs or is missing.",
"description": "The Card's harness fingerprint equals that of every run in its used\nset for this Eval. `false` when any differs or is missing, or when\nthe used set is empty (`runs_used` is 0: no run to agree with).",
"type": "boolean"
},
"same_model": {
Expand Down
40 changes: 19 additions & 21 deletions crates/evalhub-store/src/relations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@
//! Card used: `same_harness` / `same_model` is true when the Card's
//! fingerprint equals that of *every* run in its used set for the Eval
//! record, and false when any used run's differs or is missing, or the
//! Card has none. A Card with an empty used set (the Eval had no runs when
//! it was posted, or its edge did not resolve then, so nothing was
//! recorded) is compared with the Eval version's header instead, which is
//! what the view did before runs were rows and what an Eval without runs
//! still means. Each row also carries the used set's summary: `runs_used`,
//! `used_set_hash` (the `evalhub_core::run::runs_hash` formula over the
//! used runs' current content hashes) and `changed_since_card` (the used
//! runs overwritten since the Card used them).
//! Card has none. An empty used set (the Eval had no runs when the Card
//! was posted, the Card's `attrs.runs` is an explicit `[]`, or its edge
//! did not resolve then, so nothing was recorded) gives false for both:
//! the Card was compared with no run, so it agrees with none. The Eval
//! version's header fingerprint is never compared; `runs_used: 0` on the
//! row says why the flags are false. Each row also carries the used
//! set's summary: `runs_used`, `used_set_hash` (the
//! `evalhub_core::run::runs_hash` formula over the used runs' current
//! content hashes) and `changed_since_card` (the used runs overwritten
//! since the Card used them).

use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};

Expand Down Expand Up @@ -284,9 +286,8 @@ pub struct ComparisonRow {
pub fingerprints: BTreeMap<String, Vec<u8>>,
/// The Card's harness fingerprint equals that of every run in its used
/// set for this Eval; `false` when any used run's differs or is
/// missing, or the Card has none. With an empty used set, the Eval
/// version's header fingerprint is compared instead (see the module
/// doc).
/// missing, the Card has none, or the used set is empty (`runs_used`
/// is 0: the Card was compared with no run). See the module doc.
pub same_harness: bool,
/// As `same_harness`, for the model fingerprint.
pub same_model: bool,
Expand Down Expand Up @@ -958,9 +959,10 @@ async fn fingerprints_of(
/// its fingerprints, the `same_harness` / `same_model` flags over its used
/// set, and the used set's summary. See the module doc.
///
/// An empty used set (the Eval had no runs when the Card was posted, or
/// the Card's `attrs.runs` is an explicit `[]`) compares with the Eval
/// version's header fingerprint, as the view did before 0.2.0.
/// An empty used set (the Eval had no runs when the Card was posted, the
/// Card's `attrs.runs` is an explicit `[]`, or its edge did not resolve)
/// gives `same_harness` / `same_model` false: there is no used run to
/// agree with, and the Eval version's header is not compared.
///
/// Cost: a handful of reads per Card, plus one read of every listed Card's
/// used set for the Eval record.
Expand All @@ -972,7 +974,6 @@ pub async fn cards_using_eval(
let Some(eval) = version_info(pool, eval_version_id).await? else {
return Ok(Vec::new());
};
let eval_fp = fingerprints_of(pool, eval_version_id).await?;
let text = eval.text();
let rows = sqlx::query!(
"SELECT DISTINCT rel.from_version_id
Expand Down Expand Up @@ -1015,12 +1016,9 @@ pub async fn cards_using_eval(
let Some(card) = fp.get(facet) else {
return false;
};
if runs.is_empty() {
// No used run to compare with: the Eval had no runs when
// the Card was posted, or the edge did not resolve then.
return eval_fp.get(facet) == Some(card);
}
runs.iter().all(|r| of_run(r) == Some(card))
// An empty used set agrees with nothing: `all` over no run
// would say true for a Card compared with no run.
!runs.is_empty() && runs.iter().all(|r| of_run(r) == Some(card))
};
let same_harness = same("harness", |r| r.harness.as_ref());
let same_model = same("model", |r| r.model.as_ref());
Expand Down
25 changes: 22 additions & 3 deletions crates/evalhub-store/tests/cards_runs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,12 @@ async fn same_model_compares_the_card_with_every_used_run() {
json!({"status": "ok", "model": {"id": "m2"}}),
)
.await;
for (name, uses) in [("only-r1", Some(json!({"runs": ["r1"]}))), ("both", None)] {
for (name, uses) in [
("only-r1", Some(json!({"runs": ["r1"]}))),
("both", None),
// An explicit empty list: the Card used no run of this Eval.
("none", Some(json!({"runs": []}))),
] {
card(
pool,
"alice",
Expand All @@ -1182,7 +1187,8 @@ async fn same_model_compares_the_card_with_every_used_run() {
.await
.unwrap();
}
// An Eval without runs: the header is compared, as before runs were rows.
// An Eval without runs: the Card used no run, so it agrees with none,
// even though its model equals the header's.
eval_header(pool, "alice", "bare", "b", "m1", Visibility::Public).await;
card(
pool,
Expand Down Expand Up @@ -1215,13 +1221,26 @@ async fn same_model_compares_the_card_with_every_used_run() {
assert!(row("both").same_harness);
assert_eq!(row("both").runs_used, 2);
assert!(row("both").changed_since_card.is_empty());
assert_eq!(row("none").runs_used, 0);
assert!(
!row("none").same_model,
"an empty used set agrees with nothing"
);
assert!(
!row("none").same_harness,
"an empty used set agrees with nothing"
);

let rows = relations::cards_using_eval(pool, latest("bare").await, &alice())
.await
.unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].same_model && rows[0].same_harness);
assert_eq!(rows[0].runs_used, 0);
assert!(!rows[0].same_model, "an empty used set agrees with nothing");
assert!(
!rows[0].same_harness,
"an empty used set agrees with nothing"
);
}

#[tokio::test]
Expand Down
58 changes: 53 additions & 5 deletions crates/evalhub-store/tests/relations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,50 @@ async fn edge(pool: &PgPool, from: Uuid, ty: &str, to: Option<Uuid>, external: O
.unwrap();
}

/// Record that Card version `card` used run `run_id` of the Eval record
/// `eval_record`: the run row (with its harness and model fingerprints)
/// and the `card_eval_runs` row that fixes it in the Card's used set.
async fn used_run(
pool: &PgPool,
card: Uuid,
eval_record: Uuid,
run_id: &str,
harness: &[u8],
model: &[u8],
) {
sqlx::query(
"INSERT INTO runs (record_id, run_id, status, body, content_hash) VALUES ($1, $2, 'ok', '{}', $3)",
)
.bind(eval_record)
.bind(run_id)
.bind(&[7u8; 32][..])
.execute(pool)
.await
.unwrap();
for (facet, value) in [("harness", harness), ("model", model)] {
sqlx::query(
"INSERT INTO run_fingerprints (record_id, run_id, facet, fingerprint) VALUES ($1, $2, $3, $4)",
)
.bind(eval_record)
.bind(run_id)
.bind(facet)
.bind(value)
.execute(pool)
.await
.unwrap();
}
sqlx::query(
"INSERT INTO card_eval_runs (card_version_id, eval_record_id, run_id, content_hash) VALUES ($1, $2, $3, $4)",
)
.bind(card)
.bind(eval_record)
.bind(run_id)
.bind(&[7u8; 32][..])
.execute(pool)
.await
.unwrap();
}

async fn fingerprint(pool: &PgPool, version: Uuid, facet: &str, value: &[u8]) {
sqlx::query("INSERT INTO fingerprints (version_id, facet, fingerprint) VALUES ($1, $2, $3)")
.bind(version)
Expand Down Expand Up @@ -359,17 +403,19 @@ async fn add_resolves_and_audits() {
async fn comparison_view() {
let db = common::db().await;
let pool = &db.pool;
let (_, eval) = common::seed_version(pool, "eval", "alice", "e", 1, "public", "e").await;
let (eval_rec, eval) = common::seed_version(pool, "eval", "alice", "e", 1, "public", "e").await;
fingerprint(pool, eval, "harness", b"H1").await;
fingerprint(pool, eval, "model", b"M1").await;

// Same harness, different model.
// Used r1: same harness as r1, different model.
let (_, c1) = common::seed_version(pool, "card", "alice", "c1", 1, "public", "c1").await;
fingerprint(pool, c1, "harness", b"H1").await;
fingerprint(pool, c1, "model", b"M2").await;
edge(pool, c1, USES_EVAL, Some(eval), None).await;
used_run(pool, c1, eval_rec, "r1", b"H1", b"M1").await;

// Same model via a textual edge; private to bob.
// A textual edge, private to bob: it fixed no used set, so there is no
// run to agree with, even though its model equals the Eval header's.
let (_, c2) = common::seed_version(pool, "card", "bob", "c2", 1, "private", "c2").await;
fingerprint(pool, c2, "harness", b"H9").await;
fingerprint(pool, c2, "model", b"M1").await;
Expand Down Expand Up @@ -399,6 +445,7 @@ async fn comparison_view() {
assert_eq!(rows[0].title.as_deref(), Some("c1"));
assert!(rows[0].same_harness);
assert!(!rows[0].same_model);
assert_eq!(rows[0].runs_used, 1);
assert_eq!(
rows[0].fingerprints.get("model").map(Vec::as_slice),
Some(&b"M2"[..])
Expand All @@ -409,8 +456,9 @@ async fn comparison_view() {
.unwrap();
assert_eq!(rows.len(), 2, "{rows:?}");
let c2row = rows.iter().find(|r| r.name == "c2").unwrap();
assert!(!c2row.same_harness);
assert!(c2row.same_model);
assert_eq!(c2row.runs_used, 0);
assert!(!c2row.same_harness, "an empty used set agrees with nothing");
assert!(!c2row.same_model, "an empty used set agrees with nothing");
assert_eq!(c2row.version_id, c2);

assert!(
Expand Down
4 changes: 2 additions & 2 deletions web/src/lib/api/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -877,8 +877,8 @@ export interface components {
runs_used: number;
/**
* @description The Card's harness fingerprint equals that of every run in its used
* set for this Eval (with an empty used set, the Eval version's
* header fingerprint). `false` when any differs or is missing.
* set for this Eval. `false` when any differs or is missing, or when
* the used set is empty (`runs_used` is 0: no run to agree with).
*/
same_harness: boolean;
/** @description As `same_harness`, for the model fingerprint. */
Expand Down
6 changes: 4 additions & 2 deletions web/src/routes/evals/[ns]/[name]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,15 @@
<td>
{#if row.same_model}<span
class="badge"
title="Its model fingerprint equals this Eval's.">yes</span
title="Its model fingerprint equals that of every run it used from this Eval."
>yes</span
>{:else}<span class="faint">no</span>{/if}
</td>
<td>
{#if row.same_harness}<span
class="badge"
title="Its harness fingerprint equals this Eval's.">yes</span
title="Its harness fingerprint equals that of every run it used from this Eval."
>yes</span
>{:else}<span class="faint">no</span>{/if}
</td>
</tr>
Expand Down
Loading
0