Skip to content

feat: add promptfoo integration for prompt evaluation - #1

Open
jcpowermac wants to merge 2 commits into
openshift-splat-team:mainfrom
jcpowermac:feat/promptfoo-eval
Open

feat: add promptfoo integration for prompt evaluation#1
jcpowermac wants to merge 2 commits into
openshift-splat-team:mainfrom
jcpowermac:feat/promptfoo-eval

Conversation

@jcpowermac

@jcpowermac jcpowermac commented Jun 3, 2026

Copy link
Copy Markdown

Adds a promptfoo-based eval harness that tests prompt templates against real LLM outputs using the existing render + complete pipeline via a custom Python provider. Supports model comparison, parameter sweeps, and CI gating.

New files:

  • evals/provider.py — custom provider bridging promptfoo to prompts.render() + llm.complete()
  • evals/promptfooconfig.yaml — single-model eval config (reads LITELLM_MODEL from env)
  • evals/promptfooconfig.compare.yaml — multi-model comparison config
  • evals/assertions/ — Pydantic schema validation, tier ordering, repo name checks
  • evals/fixtures/ — test cases for identify_repos (3) and run_review (3)

Makefile targets: eval, eval-view, eval-compare, eval-ci

Summary by CodeRabbit

  • New Features

    • Added prompt evaluation infrastructure with new make commands: eval, eval-view, eval-compare, and eval-ci
    • Added evaluation test cases for validating repository identification and code review scenarios
    • Added validation checks for LLM output structures and repository naming requirements
  • Chores

    • Updated .gitignore to exclude evaluation directories and temporary build artifacts

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jcpowermac, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 12 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 789ff9d7-fa7d-4c5a-906a-d0f610b33da5

📥 Commits

Reviewing files that changed from the base of the PR and between 3d7f503 and bf8bf35.

📒 Files selected for processing (11)
  • .gitignore
  • Makefile
  • evals/assertions/check_repo_names.py
  • evals/assertions/check_tier_ordering.py
  • evals/assertions/validate_pydantic.py
  • evals/fixtures/identify_repos.yaml
  • evals/fixtures/run_review.yaml
  • evals/llama-swap.example.yaml
  • evals/promptfooconfig.compare.yaml
  • evals/promptfooconfig.yaml
  • evals/provider.py
📝 Walkthrough

Walkthrough

This PR adds a complete Promptfoo-based evaluation framework for the SDLC agent. It includes configurations that define how prompts are evaluated, a custom provider bridging Promptfoo to the project's LLM and template pipeline, assertion validators for output validation, test fixtures exercising repo identification and review workflows, and Makefile targets for running evaluations locally and in CI.

Changes

Evaluation Framework

Layer / File(s) Summary
Evaluation Configuration
evals/promptfooconfig.yaml, evals/promptfooconfig.compare.yaml
Two Promptfoo configurations define prompt passthrough wiring, provider setup with environment-variable defaults, JSON output assertions, and fixture-driven tests for identify_repos and run_review scenarios. The compare config includes four local model providers for multi-model evaluation.
Custom Promptfoo Provider
evals/provider.py
Custom provider bridges Promptfoo templating to project's prompts.render() and LLM completion pipeline. Preprocesses variables with optional OpenShift dependency injection, renders Jinja2 templates, and routes to structured or unstructured completion based on TEMPLATE_MODEL_MAP.
Assertion Validators
evals/assertions/check_repo_names.py, evals/assertions/check_tier_ordering.py, evals/assertions/validate_pydantic.py
Three assertion modules validate LLM outputs: validate_pydantic checks compliance with Pydantic model schemas, check_repo_names enforces openshift/* or operator-framework/* naming patterns, and check_tier_ordering validates that PR sequence steps are not blocked by higher-numbered tiers.
Test Fixtures
evals/fixtures/identify_repos.yaml, evals/fixtures/run_review.yaml
Test fixtures with embedded diffs exercise repo-identification outputs (MCO cgroup changes, ingress operator TLS ciphers, CSI driver addition) and review scenarios (OS change handling, debug endpoint, structured logging refactor). Assertions validate model schema compliance, repo naming, and approval status.
Build Integration
Makefile, .gitignore
Four new Make targets (eval, eval-view, eval-compare, eval-ci) run promptfoo locally or in CI with result output and failure handling. .gitignore ignores .promptfoo/, output/, and __pycache__ artifacts under evals/.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A framework blooms for agents to test,
With fixtures, validators, and the best,
Promptfoo now whispers prompts through the night,
Evaluating outputs with assertions done right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: adding promptfoo integration for prompt evaluation. It is concise, specific, and clearly reflects the primary purpose of all changeset files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jcpowermac

Copy link
Copy Markdown
Author

Haven't tested this yet...

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
evals/assertions/check_tier_ordering.py (1)

36-36: 💤 Low value

s["step"] assumes the key always exists.

If model output omits step for any entry, this comprehension (and the later step['step'] on line 50) raises KeyError instead of producing an assertion failure. Since the goal is to grade output, prefer .get("step") and skip malformed entries, or guard explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@evals/assertions/check_tier_ordering.py` at line 36, The comprehension
building step_map assumes every entry in steps has a "step" key and will
KeyError; change the logic in check_tier_ordering to use s.get("step") and skip
entries where it's missing (e.g., only include entries with a truthy step value)
and update later access sites that use step['step'] to use step.get("step") or
guard with an explicit check so malformed entries produce an assertion failure
instead of raising KeyError; specifically update the creation of step_map and
any subsequent uses of step['step'] to handle missing keys safely.
evals/provider.py (1)

93-93: 💤 Low value

__template_path pop has no fallback.

vars_.pop("__template_path") raises an uncaught KeyError if a fixture omits the key, surfacing as an opaque provider crash rather than a clear assertion error. A guarded pop with a descriptive error keeps eval failures diagnosable.

♻️ Optional guard
-    template_path = vars_.pop("__template_path")
+    template_path = vars_.pop("__template_path", None)
+    if not template_path:
+        return {"error": "missing __template_path in test vars"}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@evals/provider.py` at line 93, The code calls vars_.pop("__template_path")
which raises a KeyError if the fixture omits the key; change this to safely
retrieve the value and raise a clear assertion on missing input. Replace the
direct pop with a guarded retrieval (e.g., use vars_.pop("__template_path",
None) or vars_.get("__template_path")) and then assert/template-check for None
and raise a descriptive AssertionError mentioning "__template_path" so failures
in provider.py (where template_path is used) surface as clear test/fixture
errors rather than an opaque KeyError.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@evals/assertions/check_repo_names.py`:
- Line 27: The list comprehension building invalid repo names uses r.get("name",
"") for the predicate but then accesses r["name"], which can raise KeyError for
repos missing the name key; update the comprehension so it consistently uses
r.get("name", "") (or extract name = r.get("name", "") into a local variable)
when testing against _VALID_ORG_RE and when producing the invalid list
(referencing the variable/ r.get call in the expression that assigns invalid).

In `@evals/promptfooconfig.yaml`:
- Line 13: The config currently sets llm_api_base to an empty string when
LLM_API_BASE is unset, which then overrides BaseAgentSettings.llm_api_base;
change the YAML to stop injecting an explicit empty string (e.g. replace
"${LLM_API_BASE:-}" with "${LLM_API_BASE}" or remove the default) so the value
is not forwarded as "" and/or add normalization in provider._make_settings to
treat "" as None (check llm_api_base and set to None when it is an empty string)
to preserve the intended BaseAgentSettings.llm_api_base default.

In `@evals/provider.py`:
- Around line 67-72: In _make_settings, normalize an empty llm_api_base string
to None so the LiteLLM client gets the provider default instead of an empty URL:
retrieve llm_api_base from config (the "llm_api_base" key) and if it's an empty
string convert it to None before passing it into _EvalSettings (leave None
unchanged and pass through any non-empty string); update the llm_api_base
argument construction in _make_settings accordingly so litellm_model and
llm_api_key behavior remains the same.

In `@Makefile`:
- Around line 166-176: Makefile targets eval, eval-view, eval-compare, and
eval-ci currently call npx -y promptfoo@latest which can drift; update these
targets to use a pinned promptfoo version (e.g., define a PROMPTFOO_VERSION
variable like PROMPTFOO_VERSION := 0.121.14 and replace promptfoo@latest with
promptfoo@$(PROMPTFOO_VERSION)) so CI and local evals are reproducible; ensure
all four targets reference the variable consistently.

---

Nitpick comments:
In `@evals/assertions/check_tier_ordering.py`:
- Line 36: The comprehension building step_map assumes every entry in steps has
a "step" key and will KeyError; change the logic in check_tier_ordering to use
s.get("step") and skip entries where it's missing (e.g., only include entries
with a truthy step value) and update later access sites that use step['step'] to
use step.get("step") or guard with an explicit check so malformed entries
produce an assertion failure instead of raising KeyError; specifically update
the creation of step_map and any subsequent uses of step['step'] to handle
missing keys safely.

In `@evals/provider.py`:
- Line 93: The code calls vars_.pop("__template_path") which raises a KeyError
if the fixture omits the key; change this to safely retrieve the value and raise
a clear assertion on missing input. Replace the direct pop with a guarded
retrieval (e.g., use vars_.pop("__template_path", None) or
vars_.get("__template_path")) and then assert/template-check for None and raise
a descriptive AssertionError mentioning "__template_path" so failures in
provider.py (where template_path is used) surface as clear test/fixture errors
rather than an opaque KeyError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: adbbc3e1-e213-45c5-bc89-3cd35d5d6f72

📥 Commits

Reviewing files that changed from the base of the PR and between a5f2c6a and 3d7f503.

📒 Files selected for processing (10)
  • .gitignore
  • Makefile
  • evals/assertions/check_repo_names.py
  • evals/assertions/check_tier_ordering.py
  • evals/assertions/validate_pydantic.py
  • evals/fixtures/identify_repos.yaml
  • evals/fixtures/run_review.yaml
  • evals/promptfooconfig.compare.yaml
  • evals/promptfooconfig.yaml
  • evals/provider.py

Comment thread evals/assertions/check_repo_names.py Outdated
Comment thread evals/promptfooconfig.yaml Outdated
Comment thread evals/provider.py
Comment thread Makefile Outdated
Adds a promptfoo-based eval harness that tests prompt templates against
real LLM outputs using the existing render + complete pipeline via a
custom Python provider. Supports model comparison, parameter sweeps,
and CI gating.

New files:
- evals/provider.py — custom provider bridging promptfoo to prompts.render() + llm.complete()
- evals/promptfooconfig.yaml — single-model eval config (reads LITELLM_MODEL from env)
- evals/promptfooconfig.compare.yaml — multi-model comparison config
- evals/assertions/ — Pydantic schema validation, tier ordering, repo name checks
- evals/fixtures/ — test cases for identify_repos (3) and run_review (3)

Makefile targets: eval, eval-view, eval-compare, eval-ci

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jcpowermac
jcpowermac force-pushed the feat/promptfoo-eval branch from 3d7f503 to 9d602de Compare June 3, 2026 19:48
Adds evals/llama-swap.example.yaml with four models (Qwen3-14B,
Qwen3-30B-A3B, Gemma-4-12B, Gemma-3-12B) so eval-compare can run
against a single GPU via automatic model swapping. Updates the compare
config to default to llama-swap's port (8080).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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