mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
76a5ec8cb3
* Decouple agent runs from judging in skill-validator Allow baseline and treatment agent runs to execute in one parallel pool, with judging deferred to a final step, removing the serial precompute-baseline -> evaluate barrier. evaluate --no-judge: runs the requested agent arms and persists sessions.db + metrics, but skips all judging and requires no baseline file (exits 0 on successful runs). Forces session persistence; mutually exclusive with --baseline-out/--baseline-from; requires --results-dir. Only the agent model is validated (judge model recorded as metadata); warns when zero runs persist. rejudge --baseline-dir: cross-directory judging that pairs each treatment run with its baseline run by the BaselineStore key (prompt SHA + target SHA, persisted in a new sessions.db baseline_key column), runs the same pairwise/independent judges and pass/fail gates evaluate uses, and writes verdicts/reports. Baseline and treatment must share --model (rejected if either side mixes models); effective judge model resolves to --judge-model, else the treatment DB's, else the baseline DB's, rejecting a persisted mismatch. The inline per-run judging is extracted into a shared JudgeRunGroup helper reused by both the same-dir and cross-dir paths (no duplicated judging logic). Adds tests for baseline_key round-trip + legacy migration, ComputeScenarioKey, cross-dir pairing, and model/judge-model compatibility. Updates README and InvestigatingResults docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Hoist ComputeScenarioKey to scenario level to avoid per-run fixture hashing Compute the baseline key once per scenario instead of once per run, so multi-run evaluations (--runs N) don't re-hash fixture files N times. The key is threaded into ExecuteRun/ExecuteAgentRun as a parameter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Memoize scenario-key fixture hashing for the duration of an evaluation Route the persisted baseline_key through an evaluation-scoped BaselineStore cache (ComputeScenarioKeyCached) instead of the static ComputeScenarioKey, so a scenario's setup fixtures are hashed at most once across every run, arm, and scenario of an evaluation. When a baseline store already exists (--baseline-from /--baseline-out) its warm input-hash cache is reused; otherwise a dedicated, side-effect-free cache (BaselineStore.ForKeyCache) is threaded through. Addresses the PR review comment about ComputeScenarioKey re-hashing large fixture directories. Adds a test asserting the cached variant equals the canonical static computation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump session schema version to 3 for the baseline_key column The baseline_key column was added without bumping schema_info.version, so external tools keying off the version couldn't detect the new column. Introduce a single SchemaVersion constant (= 3), stamp it after migrations run, and drop the drift-prone version literal from the raw CREATE SQL so SetSchemaInfo is the sole source of truth. Update the schema-info and legacy-upgrade tests to expect version 3. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fail fast on judging-dependent options under --no-judge; fix README prefix Under --no-judge the noise test and overfitting fix cannot run (they require judging), so reject --noise-skills-dir and --overfitting-fix up front instead of silently ignoring them, mirroring the existing --baseline-out/--baseline-from guard. Add EvaluateCommandTests covering these early rejections (they short- circuit before any model/network call). Also prefix the decoupled-runs README example with 'skill-validator' for consistency with the other CLI invocations in the doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
193 lines
6.1 KiB
C#
193 lines
6.1 KiB
C#
using SkillValidator.Evaluate;
|
|
|
|
namespace SkillValidator.Tests;
|
|
|
|
public class RejudgeCommandTests
|
|
{
|
|
private static SessionRecord Rec(
|
|
string id,
|
|
string role,
|
|
int runIndex,
|
|
string? baselineKey,
|
|
string skill = "skill",
|
|
string scenario = "scn",
|
|
string model = "model-x",
|
|
string? metrics = "{}") =>
|
|
new(
|
|
Id: id,
|
|
SkillName: skill,
|
|
SkillPath: "/path/" + skill,
|
|
ScenarioName: scenario,
|
|
RunIndex: runIndex,
|
|
Role: role,
|
|
Model: model,
|
|
ConfigDir: "cfg",
|
|
WorkDir: "/work",
|
|
Prompt: "prompt",
|
|
SkillSha: "sha",
|
|
RubricJson: null,
|
|
Status: "completed",
|
|
MetricsJson: metrics,
|
|
JudgeJson: null,
|
|
PairwiseJson: null,
|
|
BaselineKey: baselineKey);
|
|
|
|
[Fact]
|
|
public void PairCrossDir_MatchesByBaselineKeyAndRunIndex()
|
|
{
|
|
var baseline = new[]
|
|
{
|
|
Rec("b0", "baseline", 0, "K1"),
|
|
Rec("b1", "baseline", 1, "K1"),
|
|
};
|
|
var treatment = new[]
|
|
{
|
|
Rec("t0", "with-skill-isolated", 0, "K1"),
|
|
Rec("t1", "with-skill-isolated", 1, "K1"),
|
|
};
|
|
|
|
var pairing = RejudgeCommand.PairCrossDir(baseline, treatment);
|
|
|
|
Assert.Empty(pairing.Unmatched);
|
|
Assert.Equal(2, pairing.Pairs.Count);
|
|
Assert.Equal("b0", pairing.Pairs.Single(p => p.RunIndex == 0).Baseline.Id);
|
|
Assert.Equal("b1", pairing.Pairs.Single(p => p.RunIndex == 1).Baseline.Id);
|
|
Assert.Equal("t0", pairing.Pairs.Single(p => p.RunIndex == 0).Isolated.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void PairCrossDir_FallsBackToFirstBaseline_WhenRunIndexMissing()
|
|
{
|
|
var baseline = new[] { Rec("b0", "baseline", 0, "K1") };
|
|
var treatment = new[] { Rec("t2", "with-skill-isolated", 2, "K1") };
|
|
|
|
var pairing = RejudgeCommand.PairCrossDir(baseline, treatment);
|
|
|
|
var pair = Assert.Single(pairing.Pairs);
|
|
Assert.Equal("b0", pair.Baseline.Id);
|
|
Assert.Equal(2, pair.RunIndex);
|
|
}
|
|
|
|
[Fact]
|
|
public void PairCrossDir_ReportsUnmatched_WhenNoBaselineKeyMatches()
|
|
{
|
|
var baseline = new[] { Rec("b0", "baseline", 0, "K1") };
|
|
var treatment = new[] { Rec("t0", "with-skill-isolated", 0, "K2") };
|
|
|
|
var pairing = RejudgeCommand.PairCrossDir(baseline, treatment);
|
|
|
|
Assert.Empty(pairing.Pairs);
|
|
var unmatched = Assert.Single(pairing.Unmatched);
|
|
Assert.Contains("scn", unmatched);
|
|
}
|
|
|
|
[Fact]
|
|
public void PairCrossDir_IncludesPluginRole()
|
|
{
|
|
var baseline = new[] { Rec("b0", "baseline", 0, "K1") };
|
|
var treatment = new[]
|
|
{
|
|
Rec("iso", "with-skill-isolated", 0, "K1"),
|
|
Rec("plug", "with-skill-plugin", 0, "K1"),
|
|
};
|
|
|
|
var pairing = RejudgeCommand.PairCrossDir(baseline, treatment);
|
|
|
|
var pair = Assert.Single(pairing.Pairs);
|
|
Assert.Equal("iso", pair.Isolated.Id);
|
|
Assert.NotNull(pair.Plugin);
|
|
Assert.Equal("plug", pair.Plugin!.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void PairCrossDir_SupportsAgentRolesAndReusedBaseline()
|
|
{
|
|
var baseline = new[] { Rec("b0", "baseline-reused", 0, "K1") };
|
|
var treatment = new[] { Rec("a0", "with-agent-isolated", 0, "K1") };
|
|
|
|
var pairing = RejudgeCommand.PairCrossDir(baseline, treatment);
|
|
|
|
var pair = Assert.Single(pairing.Pairs);
|
|
Assert.Equal("b0", pair.Baseline.Id);
|
|
Assert.Equal("a0", pair.Isolated.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateCrossDirCompat_RejectsModelMismatch()
|
|
{
|
|
var (ok, effective, error) = RejudgeCommand.ValidateCrossDirCompat(
|
|
baselineModel: "model-a", treatmentModel: "model-b",
|
|
baselineJudgeModel: "judge", treatmentJudgeModel: "judge", explicitJudgeModel: null);
|
|
|
|
Assert.False(ok);
|
|
Assert.Null(effective);
|
|
Assert.Contains("model-a", error);
|
|
Assert.Contains("model-b", error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateCrossDirCompat_RejectsJudgeModelMismatch()
|
|
{
|
|
var (ok, effective, error) = RejudgeCommand.ValidateCrossDirCompat(
|
|
"model-x", "model-x", "judge-a", "judge-b", explicitJudgeModel: null);
|
|
|
|
Assert.False(ok);
|
|
Assert.Null(effective);
|
|
Assert.Contains("judge-a", error);
|
|
Assert.Contains("judge-b", error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateCrossDirCompat_ExplicitJudgeOverridesMismatch()
|
|
{
|
|
var (ok, effective, error) = RejudgeCommand.ValidateCrossDirCompat(
|
|
"model-x", "model-x", "judge-a", "judge-b", explicitJudgeModel: "judge-c");
|
|
|
|
Assert.True(ok);
|
|
Assert.Equal("judge-c", effective);
|
|
Assert.Null(error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateCrossDirCompat_PrefersTreatmentJudgeModel()
|
|
{
|
|
var (ok, effective, _) = RejudgeCommand.ValidateCrossDirCompat(
|
|
"model-x", "model-x", baselineJudgeModel: null, treatmentJudgeModel: "judge-t", explicitJudgeModel: null);
|
|
|
|
Assert.True(ok);
|
|
Assert.Equal("judge-t", effective);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateCrossDirCompat_FallsBackToBaselineJudgeModel()
|
|
{
|
|
var (ok, effective, _) = RejudgeCommand.ValidateCrossDirCompat(
|
|
"model-x", "model-x", baselineJudgeModel: "judge-b", treatmentJudgeModel: null, explicitJudgeModel: null);
|
|
|
|
Assert.True(ok);
|
|
Assert.Equal("judge-b", effective);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateCrossDirCompat_FailsWhenNoJudgeModelAvailable()
|
|
{
|
|
var (ok, effective, error) = RejudgeCommand.ValidateCrossDirCompat(
|
|
"model-x", "model-x", baselineJudgeModel: null, treatmentJudgeModel: null, explicitJudgeModel: null);
|
|
|
|
Assert.False(ok);
|
|
Assert.Null(effective);
|
|
Assert.NotNull(error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateCrossDirCompat_AcceptsMatchingJudgeModels()
|
|
{
|
|
var (ok, effective, error) = RejudgeCommand.ValidateCrossDirCompat(
|
|
"model-x", "model-x", "judge", "judge", explicitJudgeModel: null);
|
|
|
|
Assert.True(ok);
|
|
Assert.Equal("judge", effective);
|
|
Assert.Null(error);
|
|
}
|
|
}
|