Skip to content

feat: PR Regression Testing Workflow - #199

Merged
AnkitaNaik merged 42 commits into
mainfrom
feat/pr_regression_testing
Oct 5, 2026
Merged

AnkitaNaik merged 42 commits into
mainfrom
feat/pr_regression_testing

Conversation

@AnkitaNaik

@AnkitaNaik AnkitaNaik commented Sep 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds a PR-triggered regression evaluation flow for cuga-agent using scripts maintained in cuga-eval.

This PR introduces setup and runtime scripts for a self-hosted GitHub Actions runner that can respond to /run-pr-eval comments on PRs and execute selected benchmark evaluations. It supports AppWorld and M3 runs, configurable agents, provider selection, model overrides, task filtering, and structured PR comment output.

Changes

  • Added scripts/pr-regression-testing/setup-pr-regression-workflow.sh to bootstrap the runner VM.
  • Added scripts/pr-regression-testing/run-pr-regression-eval.sh to parse /run-pr-eval comments and run evaluations.
  • Added documentation for runner setup, required secrets, supported command parameters, and example PR comments.
  • Added support for rits and litellm provider configuration in PR regression runs.
  • Updated ReAct agent setup to support RITS-backed chat model usage.
  • Updated evaluation report output so PR workflows can capture and post structured markdown reports.
  • Skips Langfuse data fetching when no trace ID is available.

Usage

Example PR comment:

/run-pr-eval benchmark=appworld eval_key=test_hard provider=rits agent=react

Sample Output

PR Evaluation

  • Triggered by: @AnkitaNaik
  • Command: /run-pr-eval benchmark=appworld eval_key=test_hard provider=rits agent=react
  • PR head SHA: feac9b5d332e3143c5bb6eee31fdb5741da0975b
  • Result: SUCCESS
Requested parameters
  • Model: openai/gpt-oss-120b-a100
  • Agent: react
  • Benchmark: appworld
  • Provider: rits
  • Eval key: test_hard
  • Num tasks: 4
  • Task IDs: benchmark default

Evaluation Metrics

Run metadata
  • Repository: AnkitaNaik/cuga-agent
  • PR: 3
  • Commit: feac9b5d332e3143c5bb6eee31fdb5741da0975b
  • Runner: prtesting
  • Agent: react
  • Benchmark: appworld
  • Provider: rits
Evaluation configuration
  • Agent: react
  • Benchmark: appworld
  • Provider: rits
  • Agent setting config: settings.rits.toml
  • Model: openai/gpt-oss-120b-a100
  • Requested num_tasks: 4
  • Eval key: test_hard
  • Task IDs: selected by eval_key

EVALUATION COMPLETE

Total Tasks: 45
Completed: 0/45 (0.0%)
Avg Steps: 9.24
Avg Duration: 86.06s

Evaluation Report

Summary

  • Pass@1: 0/45 (0.0%)
  • Total Tokens: 0
  • Avg Tokens / Task: 0.0
  • Total LLM Calls: 0
  • Avg LLM Calls / Task: 0.0
  • Total Duration: 3875.1s
  • Avg Duration / Task: 86.1s

Per-Task Results

Task Result Tokens Cost LLM Calls Cache Tokens Duration Steps
80acbaf_1 ✗ 0 $0.0000 0 0 78.4s 13
e70b117_1 ✗ 0 $0.0000 0 0 106.5s 6
6d59d90_1 ✗ 0 $0.0000 0 0 74.7s 6
988af8e_1 ✗ 0 $0.0000 0 0 83.0s 13
efc3cea_1 ✗ 0 $0.0000 0 0 108.0s 12
9a16324_1 ✗ 0 $0.0000 0 0 144.6s 11
36705ae_1 ✗ 0 $0.0000 0 0 74.7s 5
321ec38_1 ✗ 0 $0.0000 0 0 74.8s 10
fa327a6_1 ✗ 0 $0.0000 0 0 220.0s 13
a1d3dfd_1 ✗ 0 $0.0000 0 0 68.2s 6
fb05fed_1 ✗ 0 $0.0000 0 0 75.1s 13
d6d8cb6_1 ✗ 0 $0.0000 0 0 62.4s 13
18670a5_1 ✗ 0 $0.0000 0 0 86.0s 13
4ac4a8d_1 ✗ 0 $0.0000 0 0 58.1s 6
ec437da_1 ✗ 0 $0.0000 0 0 65.5s 7
92fe421_1 ✗ 0 $0.0000 0 0 63.3s 7
f37e804_1 ✗ 0 $0.0000 0 0 117.7s 11
a7179fa_1 ✗ 0 $0.0000 0 0 82.1s 13
23d431c_1 ✗ 0 $0.0000 0 0 78.9s 13
a53a8fd_1 ✗ 0 $0.0000 0 0 83.3s 13
f99d726_1 ✗ 0 $0.0000 0 0 50.1s 6
adb1060_1 ✗ 0 $0.0000 0 0 58.9s 5
33e202d_1 ✗ 0 $0.0000 0 0 126.9s 13
f6f2e67_1 ✗ 0 $0.0000 0 0 184.0s 12
2d9f728_1 ✗ 0 $0.0000 0 0 32.6s 3
6f4b9a5_1 ✗ 0 $0.0000 0 0 72.7s 13
d18139b_1 ✗ 0 $0.0000 0 0 158.1s 12
042a9fc_1 ✗ 0 $0.0000 0 0 46.1s 5
3b8fb7a_1 ✗ 0 $0.0000 0 0 70.1s 6
9dabbc9_1 ✗ 0 $0.0000 0 0 35.1s 4
652485c_1 ✗ 0 $0.0000 0 0 64.0s 4
0de03ea_1 ✗ 0 $0.0000 0 0 85.7s 3
2c544f9_1 ✗ 0 $0.0000 0 0 49.5s 7
b9c5c9a_1 ✗ 0 $0.0000 0 0 95.0s 11
90adc3f_1 ✗ 0 $0.0000 0 0 102.5s 9
f323bae_1 ✗ 0 $0.0000 0 0 72.7s 8
f861c32_1 ✗ 0 $0.0000 0 0 60.9s 10
0a9d82a_1 ✗ 0 $0.0000 0 0 45.1s 8
9016950_1 ✗ 0 $0.0000 0 0 78.4s 12
8ce6779_1 ✗ 0 $0.0000 0 0 46.0s 8
83a7951_1 ✗ 0 $0.0000 0 0 116.6s 10
32616b5_1 ✗ 0 $0.0000 0 0 112.4s 11
986aa4e_1 ✗ 0 $0.0000 0 0 108.6s 9
6b6ca61_1 ✗ 0 $0.0000 0 0 102.6s 10
bde252e_1 ✗ 0 $0.0000 0 0 92.8s 13

Difficulty Breakdown

Diff Tasks Pass@1 Tokens Tok/Task LLM Calls LLM/Task Duration Dur/Task
3 45 0.0% 0 0.0 0 0.0 3872.8s 86.1s

Test-Set Breakdown (AppWorld)

Test Set Tasks Pass@1 Tokens Tok/Task LLM Calls LLM/Task Duration Dur/Task
challenge 24 0.0% 0 0.0 0 0.0 2225.2s 92.7s
normal 21 0.0% 0 0.0 0 0.0 1647.6s 78.5s
Run summary
  • Started: 2026-09-05 13:49:26 UTC
  • Finished: 2026-09-05 14:55:08 UTC
  • Duration: 65m 42s
  • Exit code: 0
  • Result: SUCCESS

@coderabbitai

coderabbitai Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: cuga-project/cuga-eval/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ed00e67b-ec2f-4ce0-9fd8-b76021325116
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@AnkitaNaik
AnkitaNaik marked this pull request as draft September 5, 2026 13:24
@AnkitaNaik
AnkitaNaik marked this pull request as ready for review September 5, 2026 13:40
exit 2
fi

if [[ ! "${NUM_TASKS}" =~ ^[0-9]+$ ]] || [[ "${NUM_TASKS}" -lt 1 ]]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The regex ^[0-9]+$ lets leading-zero values like 08/09 through, but bash then parses ${NUM_TASKS} -lt 1 as octal and errors — inside an if, so set -e doesn't catch it, and the malformed value passes validation. The same octal-parse failure later hits the task-count trim at line 265, so it silently no-ops and the full/untrimmed task list runs while the posted comment still reports e.g. Num tasks: 08.

Verified empirically:

$ NUM_TASKS="08"; [[ ! "$NUM_TASKS" =~ ^[0-9]+$ ]] || [[ "$NUM_TASKS" -lt 1 ]]
bash: [[: 08: value too great for base (error token is "08")
# condition still evaluates false -> treated as valid

Suggested fix: force base-10 arithmetic and normalize away the leading zero:

if [[ ! "${NUM_TASKS}" =~ ^[0-9]+$ ]] || (( 10#${NUM_TASKS} < 1 )); then
  echo "ERROR: num_tasks must be a positive integer."
  ...
fi
NUM_TASKS=$((10#${NUM_TASKS}))

Comment thread benchmarks/helpers/react_agent.py Outdated

def __init__(self, config: dict[str, Any]):
model_name = config.get("model_name", "openai/gpt-oss-120b-a100")
end_point = config.get(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_create_llm always passes "end_point": api_base even when api_base is None (no LITE_LLM_URL/OPENAI_BASE_URL/RITS_BASE_URL set). config.get("end_point", DEFAULT_URL) only falls back to the default when the key is absent, not when it's None — so this raises AttributeError: 'NoneType' object has no attribute 'rstrip' instead of using the documented default RITS URL. Only avoided today because run-pr-regression-eval.sh always exports OPENAI_BASE_URL first; any direct/local use of settings.rits.toml without those env vars crashes.

Suggested fix:

end_point = config.get("end_point") or "https://inference-3scale-apicast-production.apps.rits.fmaas.res.ibm.com/gpt-oss-120b-a100"

Comment thread benchmarks/helpers/react_agent.py Outdated
print("\n" + "=" * 80)
print("EVALUATION COMPLETE")
print("=" * 80)
print("######## REPORT START ########")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This REPORT START/REPORT END wrapping was added here but not in the identical _print_appworld_summary in appworld_eval.py (cuga, line 466) or appworld_eval_codeact.py (codeact, line 93) — each file has its own separate copy, and only this one was updated. eval.sh --agent cuga/--agent codeact do dispatch to those other two files, even though this script supports agent=cuga/agent=codeact. If the workflow parses these markers to build the PR comment, cuga/codeact runs would silently drop this section.

Suggested fix: mirror the same two print(...) additions ("######## REPORT START ########" / "######## REPORT END ########") around the equivalent summary block in both other files.

Comment thread benchmarks/helpers/compare_report.py Outdated
Comment thread benchmarks/helpers/compare_report.py Outdated
@haroldship

Copy link
Copy Markdown
Collaborator

This PR's scripts are the payload that cuga-agent#759's workflow invokes, and #759 currently has CHANGES_REQUESTED (including from me) over security issues in that workflow — several of which point at code living in this PR, not just the workflow YAML:

  • The requested fix to vendor/pin the eval script in-repo (instead of invoking $HOME/pr-regression-testing/cuga-eval/scripts/... on a persistent self-hosted runner) is about run-pr-regression-eval.sh itself — merging this PR doesn't resolve that, since the path/invocation contract is defined here.
  • The requested fix to pass validated flags rather than the raw PR comment body into the eval script also touches this PR's CLI surface: run-pr-regression-eval.sh currently takes the entire comment as $1 and tokenizes it itself. If #759 moves parameter validation into the workflow, this script's interface will need to change to match.
  • The REPORT START/REPORT END convention this PR introduces is exactly the mechanism flagged in #759 as letting PR-controlled output get dropped into a GitHub comment unescaped (fake report content, backtick breakout, etc.) — any sanitization added on the #759 side will likely need a corresponding change here in how report content reaches those markers.

Not blocking this PR's own scripts from being reviewed on their merits, but flagging so the sequencing ("merge this after #759") doesn't end up meaning "merge this, then immediately rework it once #759's security fixes land."

@AnkitaNaik

Copy link
Copy Markdown
Collaborator Author

Thanks for the review @haroldship. I addressed the PR-regression runner and reporting issues called out here and in the related cuga-agent workflow review.

run-pr-regression-eval.sh now handles num_tasks with base-10 normalization, so values like 08 no longer pass validation and then break bash arithmetic as octal. The script also accepts sanitized workflow input and CLI-style flags, tolerates sha= metadata from the cuga-agent workflow, validates supported parameters, and fails closed on unknown arguments.

For the React/RITS path, ChatRits now falls back correctly when end_point is None, avoids duplicating /v1, and the RITS-specific branch prioritizes RITS_BASE_URL/RITS_API_KEY before LiteLLM/OpenAI-compatible fallbacks.

For report output, generate_eval_report now defaults back to the normal bundle/local behavior: no REPORT START/END sentinels, no forced <details>, and plain-text stdout for eval --output. PR-comment formatting is now explicit opt-in via for_pr_comment/stdout-markdown and is requested only by the PR-regression path. I also made the AppWorld summary marker behavior consistent across cuga, react, and codeact evaluators, gated by PR_EVAL_FOR_PR_COMMENT, so local runs keep clean terminal output.

I added tests for the compare_report PR-comment opt-in behavior and stdout branching. The README/setup guidance was updated for the SHA-pinned /run-pr-eval flow and the current artifact/report behavior.

Normalize num_tasks as base-10, accept sanitized workflow parameters, and
document the SHA-pinned PR eval flow. Fix RITS React-agent defaults and env
precedence, and make PR comment report formatting an explicit opt-in so local
eval reports keep their normal heading and plain stdout behavior.
@haroldship

Copy link
Copy Markdown
Collaborator

Re-reviewed after the latest fixes. All 6 issues from my earlier pass are genuinely resolved and test-covered (octal num_tasks parsing, ChatRits None-crash, RITS/LiteLLM env priority, REPORT START/END mirroring across all three appworld eval scripts, and the compare_report.py opt-in gating with new tests). Thanks for working through those.

Found a few more while re-checking end-to-end, two of which I'd want fixed before merge since they silently corrupt the artifacts this PR exists to produce:

1. Bundle report.md gets the PR-comment formatting baked in, permanently.
benchmarks/{appworld,bpo,m3,oak_health_insurance}/eval.sh all generate the eval report into a single temp file (REPORT_TMP/report_tmp), then use that same file both as compare_report's PR-comment output (--for-pr-comment --stdout-markdown, active whenever PR_EVAL_FOR_PR_COMMENT=true — which run-pr-regression-eval.sh defaults to true) and as the --report input passed to bundle.py assemble, which reads it verbatim into the bundle's report.md. Every bundle produced via /run-pr-eval ends up with ######## REPORT START ######## sentinels and raw <details> HTML in its permanent report instead of the normal # Evaluation Report doc.

Suggested fix: generate two separate temp files — one for the PR-comment extraction, one plain-markdown for the bundle — rather than sharing one.

2. run-pr-regression-eval.sh never passes --sdk, so agent=cuga misses the token accounting this repo already built for exactly this use case — and ends up reporting $0 cost / 0 LLM calls.
eval.sh only routes agent=cuga to eval_appworld_sdk.py — the evaluator with agent-side "Run Receipt" token tracking from #182, built specifically so token stats don't depend on Langfuse — when --sdk is passed explicitly. run-pr-regression-eval.sh never passes it, so agent=cuga silently falls through to the legacy appworld_eval.py, which has no Run Receipt integration and sources total_llm_calls/total_cost/node_timings/llm_call_details/generation_timings/total_cache_input_tokens from langfuse_data exclusively (only total_tokens has a non-Langfuse fallback). Since this same PR also defaults DYNACONF_ADVANCED_FEATURES__LANGFUSE_TRACING to false for PR-regression runs, the combination means a documented, supported /run-pr-eval agent=cuga ... comment reports a $0.0000/0-call run for real, billed evaluations, indistinguishable from a genuinely free one.

Suggested fix: pass --sdk in run-pr-regression-eval.sh whenever agent=cuga — that alone recovers real per-task token counts independent of Langfuse. (Note it won't fully close the cost gap — Run Receipt doesn't cover total_cost either, per #182's own commit notes — but it's a real, already-built fix for the token-count half of this and looks like a simple oversight rather than a deliberate omission.)

3. (minor) DEFAULT_RITS_BASE_URL doesn't track model_name overrides.
It's hardcoded to the gpt-oss-120b-a100 RITS route. /run-pr-eval provider=rits model_name=<other-model> without an explicit RITS_BASE_URL will silently hit the wrong model's endpoint since RITS routes per-model via URL path.

4. (minor, doc bug) README documents an optional LITELLM_BASE_URL repo variable; the script only ever reads OPENAI_BASE_URL.

@haroldship

Copy link
Copy Markdown
Collaborator

One more on the --sdk / cost-zeroing issue from earlier (#2 above). --agent cuga should run with --sdk in this workflow. It's a no-op for --agent react/--agent codeact since eval.sh's dispatch checks AGENT before USE_SDK, and it fixes the token accounting gap for cuga.

One thing to watch: run-pr-regression-eval.sh also runs benchmark=m3, and m3/eval.sh has no --sdk concept. Its arg parser forwards any unrecognized flag straight into benchmarks.m3.eval_m3's argparse, which will hard error on --sdk ("unrecognized arguments"). So --sdk needs to go only in the appworld branch of EVAL_ARGS, not added unconditionally:

if [[ "${BENCHMARK}" == "appworld" ]]; then
  ...
  EVAL_ARGS+=(--sdk)
  ...
else
  EVAL_ARGS+=(--m3-data --max-samples-per-domain "${NUM_TASKS}")
  ...
fi

@haroldship haroldship left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One more on the --sdk / cost-zeroing issue from earlier (#2 above). --agent cuga should run with --sdk in this workflow. It's a no-op for --agent react/--agent codeact since eval.sh's dispatch checks AGENT before USE_SDK, and it fixes the token accounting gap for cuga.

One thing to watch: run-pr-regression-eval.sh also runs benchmark=m3, and m3/eval.sh has no --sdk concept. Its arg parser forwards any unrecognized flag straight into benchmarks.m3.eval_m3's argparse, which will hard error on --sdk ("unrecognized arguments"). So --sdk needs to go only in the appworld branch of EVAL_ARGS, not added unconditionally:

if [[ "${BENCHMARK}" == "appworld" ]]; then
  ...
  EVAL_ARGS+=(--sdk)
  ...
else
  EVAL_ARGS+=(--m3-data --max-samples-per-domain "${NUM_TASKS}")
  ...
fi

@AnkitaNaik
AnkitaNaik requested a review from haroldship October 5, 2026 16:01
@AnkitaNaik
AnkitaNaik merged commit 059701b into main Oct 5, 2026
6 checks passed
@AnkitaNaik
AnkitaNaik deleted the feat/pr_regression_testing branch October 5, 2026 16:06
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.

[Bug]: Track follow-up for DYNACONF_ADVANCED_FEATURES__LANGFUSE_TRACING behavior in eval runs

2 participants