Class: Insika::Commands::JudgeShadowPairs
- Inherits:
-
Object
- Object
- Insika::Commands::JudgeShadowPairs
- Defined in:
- lib/insika/commands/judge_shadow_pairs.rb
Overview
C7 — turn complete shadow pairs into judged pairs, using the judges the operator ALREADY configured (settings, through JudgePanel — the one builder the eval CLI and the refinement gate use too). Never automatic: a Studio button or the CLI dispatches it, and it states its cost (2 provider calls per judge per pair, both orders — position-bias rule) before it runs.
Refusals are visible, a fake judge is not: no configured judges, or fewer models than the criterion demands, is a ValidationError — an unjudged window reads :invalid, where a window judged by judges nobody chose reads as evidence.
Constant Summary collapse
- DEFAULT_LIMIT =
50- DEFAULT_EXPIRE_HOURS =
24
Instance Method Summary collapse
-
#call(command) ⇒ Object
payload: { limit: Integer = 50, agent: String|nil, expire_after_hours: Integer|nil } -> { judged:, skipped:, expired:, failed:, models: [..] }.
-
#initialize(shadow_pairs:, settings_store:, criterion:, llm: nil, event_stream: nil) ⇒ JudgeShadowPairs
constructor
A new instance of JudgeShadowPairs.
Constructor Details
#initialize(shadow_pairs:, settings_store:, criterion:, llm: nil, event_stream: nil) ⇒ JudgeShadowPairs
Returns a new instance of JudgeShadowPairs.
22 23 24 25 26 27 28 |
# File 'lib/insika/commands/judge_shadow_pairs.rb', line 22 def initialize(shadow_pairs:, settings_store:, criterion:, llm: nil, event_stream: nil) @shadow_pairs = shadow_pairs @settings_store = settings_store @criterion = criterion @llm = llm @event_stream = event_stream end |
Instance Method Details
#call(command) ⇒ Object
payload: { limit: Integer = 50, agent: String|nil, expire_after_hours: Integer|nil } -> { judged:, skipped:, expired:, failed:, models: [..] }
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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 |
# File 'lib/insika/commands/judge_shadow_pairs.rb', line 32 def call(command) payload = command.payload limit = begin Integer(payload[:limit] || payload["limit"] || DEFAULT_LIMIT) rescue ArgumentError raise ValidationError, "limit must be an integer" end agent = Coercion.presence(payload[:agent] || payload["agent"]) expire_hours = begin Integer(payload[:expire_after_hours] || payload["expire_after_hours"] || DEFAULT_EXPIRE_HOURS) rescue ArgumentError raise ValidationError, "expire_after_hours must be an integer" end # Judges BEFORE the expire: a refusal (no judges, too few models) must # leave every pair untouched — expiring first would stamp :incomplete on # pairs and that is terminal evidence against a click that judged nothing. panel = Insika::Evals::JudgePanel.pairwise(settings, llm: @llm) raise ValidationError, "no judges configured in settings['evals'] — refusing to judge without judges" if panel.nil? pairwise, models = panel minimum = @criterion.rule.min_judge_models distinct = models.uniq if distinct.length < minimum raise ValidationError, "the criterion requires at least #{minimum} distinct judge models; " \ "#{distinct.length} configured (#{distinct.join(', ')})" end # A mirror that dropped a half stops looking like pending work. expired = @shadow_pairs.expire(older_than: Time.now.utc - (expire_hours * 3600)) judged = 0 failed = 0 @shadow_pairs.unjudged(limit: limit, agent: agent).each do |pair| if judge_pair(pair, pairwise, models) judged += 1 else failed += 1 end end skipped = @shadow_pairs.unjudged(agent: agent).length emit_summary(judged: judged, skipped: skipped, expired: expired, failed: failed, models: models) { judged: judged, skipped: skipped, expired: expired, failed: failed, models: models } end |