Skip to content

feat(eval): reproducible incident benchmark for the agent - #57

Merged
AhmadHammad21 merged 4 commits into
mainfrom
feat/eval-runner
Jun 2, 2026
Merged

AhmadHammad21 merged 4 commits into
mainfrom
feat/eval-runner

Conversation

@AhmadHammad21

@AhmadHammad21 AhmadHammad21 commented May 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds demos/eval/ — a small framework that runs the agent against known-bad AWS / Azure scenarios and scores its submit_investigation output against ground truth. Foundation for the README's "X/N root causes found, $Y/run, Zs median" line and a regression net every PR can run against.

Verified: 1/1 PASS on a real Lambda-crashing investigation (45.6s, $0.0014, 11 tool calls).

What's in the box

  • demos/eval/run.py — discover scenarios, run end-to-end (setup → POST /chat with SSE consumption → score → teardown), print a table, write a Markdown + JSON report. Single-file CLI, stdlib only.
  • demos/eval/scoring.py — pure scoring functions (category match, evidence-keyword overlap with 50% threshold, services intersection with AWS/Amazon prefix tolerance, expected-tools any-of). Side-effect-free so the contract is unit-testable.
  • demos/eval/scenarios/ — one directory per scenario with a single scenario.json that points at an existing demo script + adds prompt and ground truth. Three to start:
    • 001_lambda_crashing (e2_lambda_crashing → KeyError on user_id)
    • 002_lambda_throttled (e1_lambda_throttled → reserved concurrency 0)
    • 003_azure_vm_deallocated (vm_deallocated → powerState deallocated)
  • demos/eval/README.md — implementation reference (how scenarios compose, scoring internals).
  • apps/documentation/eval.md — user-facing guide (running, authoring scenarios, model-choice caveats).
  • apps/backend/tests/test_eval/test_scoring.py — 19 unit tests pinning the scorer contract. Total suite: 232 green.
  • Makefile: make eval target.

Design choices worth knowing

  • Multi-category ground truth: root_cause_category accepts a string OR a list. Real incidents are often defensibly classifiable two ways (a Lambda crashing on a missing key is both COMPONENT_FAILURE and SYSTEM_CHANGE); the scorer shouldn't punish a defensible call. First run uncovered exactly this — see lesson baked into the docs.
  • --setup-profile flag: the agent runtime should be read-only (least privilege, what iam_setup.md ships), but eval setup/teardown needs write perms. The flag rewrites AWS env for setup/teardown subprocesses ONLY; the agent /chat keeps the original (read-only) creds.
  • Auto .env loader: reads apps/backend/.env (and a repo-root .env if present) into os.environ at startup so demo subprocesses inherit AWS_PROFILE / keys without manual shell exports.
  • Per-run subprocess logs in demos/eval/results/<timestamp>/ so a cloud error (AccessDeniedException, etc.) is never lost to terminal truncation. results/ is gitignored.
  • Not a pytest target: this is integration code that hits real cloud + LLM. Documented as such. Run nightly in a dedicated CI lane or manually before releases.

Test plan

  • cd apps/backend && uv run pytest tests/test_eval/ — 19 passing
  • cd apps/backend && uv run pytest — 232 passing
  • End-to-end against openrouter/openai/gpt-4o-mini on 001_lambda_crashing — PASS in 45.6s, $0.0014
  • Run remaining scenarios (002, 003) — follow-up, low risk
  • Add more scenarios from the existing demo set — follow-up (7 more demos available)

What this unlocks

  • A real README headline once we sweep all scenarios.
  • An empirical model-comparison story (Sonnet vs GPT-4o vs Gemma — different cost/accuracy trade-offs visible per run).
  • A clear failure-mode log: "this model is too small to close the loop on tool use" becomes visible immediately, not three weeks into a customer test.

Adds demos/eval/ — a small framework for running the agent against known-bad
AWS/Azure scenarios and scoring its submit_investigation output against
ground truth. Foundation for the README's "X/N root causes found, $Y, Zs
median" headline and a regression net every PR can run against.

Layout
- demos/eval/run.py — discover scenarios, run end-to-end (setup -> POST
  /chat with SSE consumption -> teardown), print a table, write a Markdown
  report. Single-file CLI, stdlib only.
- demos/eval/scoring.py — pure scoring functions (root-cause category
  match, evidence-keyword overlap with a 50% threshold, services
  intersection with AWS/Amazon prefix tolerance, expected-tools any-of).
  Side-effect-free so the scoring contract is unit-testable.
- demos/eval/scenarios/ — one directory per scenario with a single
  scenario.json that points at an existing demo script + adds prompt and
  ground truth. Three to start:
  * 001_lambda_crashing       (e2_lambda_crashing  -> KeyError on user_id)
  * 002_lambda_throttled      (e1_lambda_throttled -> reserved concurrency 0)
  * 003_azure_vm_deallocated  (vm_deallocated      -> powerState deallocated)
- demos/eval/README.md — running, adding scenarios, ground-truth schema,
  cost guard, and the read-only-runtime vs write-capable-setup pattern.
- Makefile: make eval target.

Design choices worth knowing
- Multi-category ground truth: root_cause_category accepts a string OR a
  list. Real incidents are often defensibly classifiable two ways (a Lambda
  crashing on a missing key is both COMPONENT_FAILURE and SYSTEM_CHANGE);
  the scorer shouldn't punish a defensible call. Documented in the README.
- --setup-profile flag: the agent runtime should be read-only (least
  privilege, what the docs ship), but eval setup/teardown needs write
  permissions. The flag rewrites AWS env for setup/teardown subprocesses
  ONLY; the agent /chat keeps the original (read-only) creds.
- Auto .env loader: reads apps/backend/.env (and a repo-root .env if
  present) into os.environ at startup so demo subprocesses inherit
  AWS_PROFILE / AWS_ACCESS_KEY_ID without manual shell exports.
- Per-run subprocess logs in demos/eval/results/<timestamp>/ so the actual
  cloud error is never lost to terminal truncation.

Testability
- apps/backend/tests/test_eval/test_scoring.py — 19 unit tests pinning the
  scorer contract (correct pass, wrong category, multi-category list,
  missing log inspection, generic evidence, soft-metric preservation on
  failure). Total suite 232 green.

First end-to-end run: 1/1 PASS, 45.6s, $0.0014 on Lambda-crashing.
- apps/documentation/eval.md — how to run the eval, the read-only-runtime
  vs write-capable-setup pattern, scenario authoring, ground-truth schema,
  scoring rules, the model-choice caveat (smaller models skip the
  structured submit_investigation step and fail the eval).
- .gitignore: demos/eval/results/. The per-run output (report.md +
  subprocess logs) is per-machine and shouldn't be in version control.
- Remove the four accidentally-committed test runs from the previous
  commit on this branch.
…enario

Brings the suite from 3 to 10 scenarios, all reusing existing demos/aws
scripts. Each is a single scenario.json — no Python duplication. Ground
truth uses the multi-category list pattern wherever an incident is
defensibly classifiable two ways (most of them are).

New scenarios
- 004 alarm_in_alarm           (e3 -> alarm in ALARM state)
- 005 lambda_dynamodb_throttle (h1 -> cross-service: Lambda errors caused
                                     by DynamoDB ProvisionedThroughputExceeded)
- 006 iam_permission_regression (h2 -> AccessDenied after DetachRolePolicy)
- 007 lambda_vpc_no_egress     (h3 -> Lambda in VPC, security group has no egress)
- 008 bad_deploy               (m1 -> errors correlate with UpdateFunctionCode)
- 009 timeout_misconfig        (m2 -> timeout lowered to 1s)
- 010 ecs_image_pull           (m3 -> CannotPullContainerError on bad image tag)

Planned (designed, not yet executable)
- demos/eval/scenarios/_planned/011_aks_dns_resolution.md — AKS pods can't
  pull from ACR because the cluster's VNet isn't linked to the private DNS
  zone. Real-world enterprise Azure case described by a practicing SRE.
  Setup script TODO (AKS provisioning is 10-15 min so not blind-scaffolded).
  Underscore prefix on the directory keeps the runner from picking it up.

The runner discovers only directories containing scenario.json directly, so
_planned/* stays out of the suite until the demo script is implemented.

10 scenarios discovered + 0 broken stubs in the active suite.
…benchmark

Make the incident benchmark fair across models and publish the results.

- run.py: recover a submit_investigation payload from the final message text
  when a model emits it as JSON instead of calling the tool (gpt-oss); tag such
  runs in the report. Takes gpt-oss-120b from 3/10 to 9/10.
- 004 alarm_in_alarm: back the alarm with a real failing Lambda (regressed
  pricing -> TypeError) so the root cause is genuinely discoverable, instead of
  a forced-state alarm over a synthetic metric with no resource behind it.
- chat.py: extract text from list-form content blocks, capture reasoning
  tokens, and guard list-shaped tool results.
- scoring/scenarios: match services against the summary, accept multi-category
  ground truth, calibrate keywords.
- docs: new benchmark.md (KPIs, ROI, cost-at-scale) + README headline table;
  eval.md notes the salvage fallback.
- tests: pin the salvage parser and the loosened scoring contract.
@AhmadHammad21
AhmadHammad21 merged commit 6d069f2 into main Jun 2, 2026
2 checks passed
@AhmadHammad21
AhmadHammad21 deleted the feat/eval-runner branch June 2, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant