Skip to content

Proposal: optional ResultSet reporter for EvalPort interop (no new deps, doesn't touch trace schema) #52

Description

@adhabnr-ux

Summary

A small, opt-in reporter — agentdiff.reporters.openeval — that renders a DiffReport as an EvalPort ResultSet (JSON), so a CI regression-gate run from AgentDiff can land in the same results pipeline/dashboard as evals from other frameworks. No new dependency, no change to the trace schema or the comparison engine — same shape as the existing reporters/pr.py / reporters/markdown.py / reporters/terminal.py modules, just another sink for a DiffReport.

Why I think this fits (and where it doesn't)

I read through engine/comparator.py, models/report.py, and the three existing reporters before writing this, so I want to be precise about the actual overlap, not a generic "let's integrate!" pitch.

AgentDiff's DiffReport isn't a per-input correctness grade — it's a deterministic structural diff between two trajectories (TDI via LCS alignment, WEI, loop detection, RSR, resource deltas). EvalPort's TestCase/expected_output/llm_judge grader machinery genuinely doesn't apply here, and I'm not proposing any of that. EvalPort's spec is explicit that it "does not define a trace format" and only references trace IDs — so I'm not proposing AgentDiff adopt EvalPort's AgentTrace shape either; schema/agent_trace.schema.json stays exactly as-is.

The part that does line up is the result layer, not the input layer. EvalPort's ResultSet is just: one result per evaluated unit, each carrying grader_results (id/type/score/passed/reason), rolling up to a summary. That maps cleanly onto one DiffReport if each of AgentDiff's checks (TDI, WEI, loop count, RSR, cost/latency/token delta) becomes one grader_result. The spec's grader type system is explicitly open for this: any non-standard type string is valid as long as params.handler is set (spec/SPEC.md §"Type openness (normative)"), which is exactly the "framework-native, not a fake standard grader" case — same pattern the repo's own crewai-openeval-adapter uses for gr_tool_selection (type: "custom", params.handler: "crewai:tools_subset").

Concretely, one comparison run (baseline_id vs candidate_id) becomes one results[] entry:

# agentdiff/reporters/openeval.py  (sketch — not a working PR yet)
from agentdiff.models.report import DiffReport

def generate_openeval_resultset(
    report: DiffReport,
    *,
    suite_id: str = "agentdiff",
    run_id: str,
    started_at: str,
    completed_at: str,
) -> dict:
    def grader(gid, gtype, score, passed, reason):
        return {
            "grader_id": gid,
            "type": gtype,
            "score": score,
            "passed": passed,
            "reason": reason,
            "params": {"handler": f"agentdiff:{gtype}"},  # required for non-standard types
        }

    grader_results = [
        grader("gr_trajectory_divergence", "agentdiff_tdi",
                1.0 - report.trajectory_divergence_index,
                report.trajectory_divergence_index <= 0.3,  # DEFAULT_MAX_DIVERGENCE in reporters/pr.py
                f"TDI={report.trajectory_divergence_index:.4f}"),
        grader("gr_wasted_effort", "agentdiff_wei",
                1.0 - report.candidate_wei, report.candidate_wei <= report.baseline_wei,
                f"candidate WEI={report.candidate_wei:.4f} (baseline {report.baseline_wei:.4f})"),
        grader("gr_loop_detection", "agentdiff_loops",
                0.0 if report.loops_detected else 1.0, not report.loops_detected,
                f"{len(report.loops_detected)} loop(s) detected"),
        grader("gr_recovery_ratio", "agentdiff_rsr",
                None, report.recovery_step_ratio <= 1.5,
                f"RSR={report.recovery_step_ratio:.2f} "
                f"({report.candidate_recovery_steps}/{report.baseline_recovery_steps})"),
        grader("gr_resource_delta", "agentdiff_resource_delta", None,
                report.cost_delta_percentage <= 10.0,
                f"cost {report.cost_delta_percentage:+.2f}%, "
                f"latency {report.latency_delta_percentage:+.2f}%, "
                f"tokens {report.token_delta_percentage:+.2f}%"),
    ]
    passed = sum(1 for g in grader_results if g["passed"])

    return {
        "version": "1.0.0",
        "suite_id": suite_id,
        "run_id": run_id,
        "started_at": started_at,
        "completed_at": completed_at,
        "results": [{
            "test_case_id": f"{report.baseline_id}__vs__{report.candidate_id}",
            "grader_results": grader_results,
            "passed": report.passed,
            "metadata": {
                "gate_provenance": report.gate_provenance,
                "violations": [v.message for v in report.violations],
                "warnings": [w.message for w in report.warnings],
            },
        }],
        "summary": {
            "total": len(grader_results),
            "passed": passed,
            "failed": len(grader_results) - passed,
            "pass_rate": passed / len(grader_results),
        },
    }

Honest caveats

  • Narrow, real value today. EvalPort's own adoption notes (spec/ADOPTION.md) are candid that native framework support hasn't landed anywhere yet — this only helps if you're already aggregating results from multiple eval tools into one EvalPort-shaped store/dashboard. I'm not claiming demand exists inside this repo's userbase; I genuinely don't know if it does.
  • One-way, and deliberately so. No TestCase import direction — AgentDiff doesn't consume single graded inputs, so there's nothing sensible to import. This is export-only, same direction as reporters/pr.py.
  • Threshold values in the sketch above are illustrative defaults, not what a real PR would hardcode — a real implementation would thread through whatever thresholds the caller already passed to assert_no_regressions/generate_pr_markdown rather than re-deriving them.
  • Zero new dependency: the sketch above is a plain dict, no evalport-sdk import required, so it doesn't trip the "adds a dependency" review gate in CONTRIBUTING.md. It also doesn't touch schema/ at all.

Since CONTRIBUTING.md asks to open an issue first for anything that touches public API surface, I'm posting this as a proposal rather than a PR — happy to build it as feat/openeval-resultset-reporter against current main (0.5.0) if this is a direction you'd want, or to hear why it isn't.

— Sahi, independent contributor (not affiliated with this project)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalCommunity feature proposal under discussionwaiting-on-evidenceDecision deferred until adoption/user-demand evidence lands

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions