Skip to content

ai(observability): add a code-based (Hog) evaluation for review output quality #10232

Description

@JSONbored

Part of epic #8286 (Phase 3 — AI observability). Follow-on to #10218 (content capture) and #10226 ($ai_metric).

Why Hog, and why not the other two

PostHog offers three evaluation types. Only one fits this deployment:

Type Cost Verdict
Code-based (Hog) Free — no LLM, runs in milliseconds Use this
Sentiment Free — local ML model on PostHog's side Skip — no value here
LLM-as-a-judge One LLM API call per evaluation, billed to a pinned cloud key Skip — see below
  • LLM-judge is structurally wrong for this stack. Its provider enum is openai | anthropic | gemini | openrouter | fireworks | azure_openai | together_ai | minimax | zeabur — there is no ollama or self-hosted option. Enabling it means a new cloud bill on a deployment that is deliberately 100% self-hosted, and 7,434 of the last 13,997 AI events are free local ollama embeddings, so a paid cloud judge would mostly be grading calls that cost nothing.
  • Sentiment classifies user-message sentiment. The "user messages" here are PR diffs and review prompts. There is no human emotion in a diff to measure.

Hog is free, deterministic, needs no model, and checks the things that actually matter for a review engine.

Event-budget constraint (this is the real cost)

Evaluations are free to run, but each one emits an $ai_evaluation event, and PostHog's LLM-analytics free tier is 100K events/month. Current run rate is ~2,300 AI events/day (~70K/month). Evaluating all 6,563 generations at 100% rollout would add ~33K/month and push the project over the free tier.

Mitigations, both first-class in the API:

  • conditions[].rollout_percentage (0–100) samples matching events.
  • target: "trace" evaluates once per trace instead of once per generation. With trace grouping now live (~7 generations/trace), this is roughly a 7× reduction on its own.

Start sampled and trace-targeted; widen only if the signal proves useful.

What Hog can read — verified, not assumed

Probed live with llma-evaluation-test-hog (3 recent generations, target: generation):

source: return not properties.$ai_is_error
→ 3/3 samples returned true, no errors

So generation properties ($ai_is_error, $ai_model, $ai_provider, $ai_latency, $ai_total_cost_usd, repo, pullNumber) are readable today.

The same probe returned empty input_preview / output_preview, confirming content is not yet flowing — #10218 is merged but has not reached the ORB image. Any check that inspects the prompt or completion is blocked until it does.

Candidate checks

Available now (properties only):

  • A review generation that succeeded but reported zero cost and zero latency — a shape that suggests a degenerate or short-circuited call rather than a real one.
  • A claude-code generation whose latency exceeds the configured effort tier's timeout, which should be impossible and indicates the ladder is misconfigured.

Blocked on content capture reaching the ORB (#10218):

  • The review returned parseable JSON matching the reviewer contract (assessment, blockers, nits, suggestions, confidence). A malformed envelope is the single highest-value deterministic check — it is exactly what extractCliText / the JSON-mode fallback exist to survive, and today a malformed response degrades silently.
  • The output is non-empty and not truncated — catches claude_code_empty_output and clipped completions as a quality signal rather than only as an error.
  • A blocker was asserted with supporting evidence (a file path or line reference present in the blocker text). A blocker with no locus is the shape that produces a bad auto-close.

Pick the JSON-contract check first; it is the one with a real failure mode already documented in the codebase.

Deliverables

  • Validate the Hog source with llma-evaluation-test-hog (sample_count up to 10) before creating anything — the tool exists precisely so the code is proven against real samples first.
  • Create one hog evaluation via llma-evaluation-create with evaluation_type: "hog", output_type: "boolean", and output_config.allows_na: true so non-review generations (embeddings, vision, advisory) return N/A instead of a fabricated fail.
  • Scope it with conditions[].properties so it only runs on review generations, not on the free ollama embedding traffic.
  • Set target: "trace" and a starting rollout_percentage well under 100, then re-check the monthly $ai_evaluation volume against the 100K free tier before widening.
  • Create it with enabled: false first, confirm the sampled results read correctly in the Traces view, then enable.
  • Add the resulting pass rate to the AI observability dashboard (id 1934420) alongside the existing error-rate and spend panels.

Blocked by

The content-inspecting checks need #10218 live on the ORB. The property-only checks can ship immediately.

Note on $ai_evaluation

$ai_evaluation is PostHog's output, not ours — the docs are explicit that evaluations "run on new $ai_generation events; results are $ai_evaluation events". There is no SDK helper because clients are not meant to emit it. This is why #10226 deliberately excluded it and used $ai_metric for LoopOver-native quality signal instead. The two are complementary: $ai_metric carries what the engine already computes (reviewer votes, judgment agreement, gate/rule precision); $ai_evaluation carries what PostHog derives from the generation itself.

Activity

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

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions