Class: Insika::Evals::Runner
- Inherits:
-
Object
- Object
- Insika::Evals::Runner
- Defined in:
- lib/insika/evals/runner.rb
Overview
Replays golden cases through a Transport and evaluates each. Pure over the Transport (injected) — the fake makes the orchestration unit-testable offline; the HttpTransport makes a real run.
Multi-turn: a case's turns replay IN ORDER under one conversation id
(eval-<id>); earlier turns build context, the assertion runs on the LAST
turn's result. A turn that errors aborts the rest of that conversation (there's
nothing to continue from) and fails the case.
Defined Under Namespace
Classes: RunCase
Instance Method Summary collapse
-
#initialize(transport:, judge: nil, conv_map: {}, capabilities: nil, pairwise: nil) ⇒ Runner
constructor
judge: an Evals::Judge (optional).
-
#run(goldens) ⇒ Object
[Golden] -> [RunCase].
- #run_case(golden) ⇒ Object
Constructor Details
#initialize(transport:, judge: nil, conv_map: {}, capabilities: nil, pairwise: nil) ⇒ Runner
judge: an Evals::Judge (optional). When set, a case with a rubric whose turn ran cleanly gets a subjective verdict attached on top of the deterministic pass.
capabilities: what the DEPLOYMENT has, per agent — anything answering
#for(agent_id) with { "tools" =>, "capabilities" => } or nil. Used to skip a
case the deployment cannot satisfy, BEFORE spending a turn on
it. nil (or an unknown agent) = no resolution, and then a case with requires
RUNS and says so in the report: "could not rule it out" is not a reason to
stop testing something, and a suite that shrinks in silence is the failure
this feature exists to avoid.
pairwise: an Evals::Pairwise (optional). Only cases carrying a
reference: are compared, and the verdict never touches pass/fail — it is the
answer to "can we replace it", reported beside the suite's own verdict.
38 39 40 41 42 43 44 |
# File 'lib/insika/evals/runner.rb', line 38 def initialize(transport:, judge: nil, conv_map: {}, capabilities: nil, pairwise: nil) @transport = transport @judge = judge @conv_map = conv_map || {} @capabilities = capabilities @pairwise = pairwise end |
Instance Method Details
#run(goldens) ⇒ Object
[Golden] -> [RunCase]. Each RunCase carries the CaseResult (for the report) +
per-turn timings (for --mode perf).
48 49 50 |
# File 'lib/insika/evals/runner.rb', line 48 def run(goldens) goldens.map { |g| run_case(g) } end |
#run_case(golden) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/insika/evals/runner.rb', line 52 def run_case(golden) skip = skip_reason(golden) return RunCase.new(result: Assertions.skip(golden, skip), timings: []) if skip # A backend that resolves state from a pre-existing conversation (e.g. a # consumer needing a real Chat UUID as X-Chat-Id) supplies it via conv_map; otherwise the # synthetic "eval-<id>" keeps the adapter's own multi-turn continuation. conv = @conv_map[golden.id] || "eval-#{golden.id}" turns = [] timings = [] spent = [] cached = [] golden.user_turns.each do || outcome = @transport.turn(agent: golden.agent, conv: conv, message: ) timings << { ttfb: outcome.ttfb, total: outcome.total } spent << billed_tokens(outcome.usage) cached << cached_tokens(outcome.usage) turns << outcome.result break if outcome.result.error end last = turns.last # Tool/content assertions read the last turn (unchanged); the policy checks # read every turn — "one question per reply" is a rule about each of them. result = Assertions.evaluate(golden, last, turns: turns) # Subjective layer: only when a judge is configured, the case has a rubric, and # the turn ran cleanly (nothing to judge on an errored turn). result.judge = @judge.score(golden: golden, result: last) if @judge && result.rubric && result.error.nil? # Against the incumbent. Same rule as the judge: nothing to # compare on a turn that errored — half a conversation would lose the # comparison for a reason that has nothing to do with the agent. result.pairwise = @pairwise.compare(golden: golden, turns: turns) if @pairwise && result.error.nil? RunCase.new(result: result, timings: timings, tokens: sum_tokens(spent), cached: sum_tokens(cached)) end |