"""Tests for the grader, driven by a fake structured-output model (no API calls).""" from typing import Any, cast from langchain_core.language_models import BaseChatModel from skill_eval_runner.grading import AssertionResult, GradingResponse, grade class _FakeMessage: """Minimal raw message exposing ``usage_metadata`` for token extraction.""" def __init__(self, input_tokens: int, output_tokens: int) -> None: self.usage_metadata = { "input_tokens": input_tokens, "output_tokens": output_tokens, } class _FakeStructured: """Stand-in for the runnable from ``with_structured_output(include_raw=True)``.""" def __init__(self, response: GradingResponse, message: _FakeMessage) -> None: self._response = response self._message = message def invoke(self, messages: Any) -> dict[str, Any]: """Return the include_raw envelope: raw message + parsed model.""" return { "raw": self._message, "parsed": self._response, "parsing_error": None, } class _FakeModel: """Minimal model exposing only ``with_structured_output`` for the grader.""" def __init__( self, response: GradingResponse, message: _FakeMessage | None = None ) -> None: self._response = response self._message = message or _FakeMessage(0, 0) def with_structured_output( self, schema: Any, method: str | None = None, include_raw: bool = False ) -> _FakeStructured: """Return a fake structured runnable wrapping the canned response.""" return _FakeStructured(self._response, self._message) def test_grade_no_assertions_skips_model() -> None: """No assertions yields an empty, zeroed summary and no grader tokens.""" model = cast(BaseChatModel, _FakeModel(GradingResponse(assertion_results=[]))) result = grade("anything", [], model) assert result.grading["summary"]["total"] == 0 assert (result.input_tokens, result.output_tokens) == (0, 0) def test_grade_counts_passes() -> None: """The summary counts passed assertions and computes the pass rate.""" response = GradingResponse( assertion_results=[ AssertionResult(text="a", passed=True, evidence="e"), AssertionResult(text="b", passed=False, evidence="e"), ] ) model = cast(BaseChatModel, _FakeModel(response)) result = grade("output", ["a", "b"], model) assert result.grading["summary"] == { "passed": 1, "failed": 1, "total": 2, "pass_rate": 0.5, } def test_grade_captures_grader_tokens() -> None: """The grader call's token usage is returned for cost accounting.""" response = GradingResponse( assertion_results=[AssertionResult(text="a", passed=True, evidence="e")] ) model = cast(BaseChatModel, _FakeModel(response, _FakeMessage(120, 30))) result = grade("output", ["a"], model) assert (result.input_tokens, result.output_tokens) == (120, 30)