feat(eval): LLM-as-judge evaluators for finding actionability and review self-consistency
Two llm_as_judge evaluators score the review generation directly: a NUMERIC 0-1 on finding actionability, a BOOLEAN on whether the summary agrees with the findings. Both run on every observation whose trace name is pr-review or opencode-review. The judge is kimi-k2.7-code through the headroom hub. Local Ollama returns Anthropic-format responses but the thinking blocks lack the signature field Langfuse Zod schema requires; the evaluator preflight fails as Invalid JSON response. A small judge-proxy pod on 8802 forwards to the hub and patches every thinking block with a synthetic signature before returning. Trace + generation output now includes the findings themselves (capped at 25) rather than just the count, so a judge has something to grade. generation input/output mirrors the trace so an observation-level evaluator can read them. Idempotent: existing evaluators and rules are skipped on re-run, not duplicated. The connection is upserted on provider.
This commit is contained in:
@@ -127,6 +127,51 @@ Coverage is bounded by the dataset, not by the traces: items only exist for PRs
|
||||
with a row in `feedback.db`, and a review that posted no comment leaves a trace
|
||||
but no row. That is why a run links fewer items than there are traces.
|
||||
|
||||
## Evaluators: `eval_judges.py`
|
||||
|
||||
Behaviour scores answer "how many, how severe, how much" — computable from data
|
||||
already in hand. Two things they cannot answer:
|
||||
|
||||
- **Was the finding any good?** Specificity vs. hedge, generic advice vs.
|
||||
fix-it-now advice — the difference between a useful review and one a
|
||||
developer scrolls past.
|
||||
- **Did the summary match the findings?** Claiming "no issues" above two
|
||||
criticals, or describing a problem in prose that never became a finding.
|
||||
|
||||
These need a judge. `eval_judges.py` registers two `llm_as_judge` evaluators
|
||||
against the trace names this project emits (`pr-review`, `opencode-review`)
|
||||
and wires a sampling=1 rule per evaluator. Both run on every observation in a
|
||||
matching trace; the only observations in those traces are the review itself.
|
||||
|
||||
| evaluator | output | what it answers |
|
||||
|---|---|---|
|
||||
| `finding_actionability` | NUMERIC 0–1 | How specific and fixable is each finding? |
|
||||
| `review_self_consistency` | BOOLEAN | Does the summary agree with the findings? |
|
||||
|
||||
The judge is a different model from the reviewer (`kimi-k2.7-code` through the
|
||||
headroom hub). A model grading its own output agrees with itself for reasons
|
||||
that have nothing to do with quality. The judges are also asked only what they
|
||||
can answer from the review itself — never whether a finding is correct, since
|
||||
that needs the diff the trace does not carry.
|
||||
|
||||
### Why the judge goes through `judge-proxy` (port 8802)
|
||||
|
||||
The headroom hub in front of local Ollama returns Anthropic-format responses,
|
||||
but every `thinking` content block is missing the `signature` field real
|
||||
Claude emits. Langfuse's Zod schema requires it; the omission fails the
|
||||
evaluator preflight as `Invalid JSON response`. The `judge-proxy` pod sits in
|
||||
front of the hub on `100.74.17.70:8802` and patches every thinking block with
|
||||
a synthetic signature before forwarding the response. The model is unchanged;
|
||||
only the wire shape is fixed.
|
||||
|
||||
```bash
|
||||
python3 pilot/eval_judges.py --dry-run # show what would be created
|
||||
python3 pilot/eval_judges.py # create the LLM connection, evaluators, rules
|
||||
```
|
||||
|
||||
Idempotent: existing evaluators and rules are skipped, not duplicated. The
|
||||
connection is upserted on `provider` so re-runs return the same record.
|
||||
|
||||
## Running it
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user