Add daily eval CI workflow with Slack notifications - #3042
Conversation
|
@lezama let's use Buildkite for any actions that build the app, and use GH actions only for basic stuff like static analysis, PR gardening, auto-merges, etc. |
673db65 to
42695c8
Compare
bc6cc2a to
e15062d
Compare
42695c8 to
fda473f
Compare
|
@wojtekn updated to use buildkite, but needs lots of review, not sure what I am doing. |
e15062d to
4c01b4c
Compare
fda473f to
f6aa7d9
Compare
There was a problem hiding this comment.
Hey @lezama 👋 I'm checking in on this one since it has been idle for a bit. I found STU-1417, which has the broader goal of having an eval framework so we can track Studio AI quality over time, so this approach makes sense to me.
I tested this on Buildkite (build). I created a new build on this branch, using these env vars:
EVAL_PIPELINE=true
EVAL_TEST_FILTER=1
EVAL_PIPELINE=true makes the new Buildkite step to run, and EVAL_TEST_FILTER=1 runs only one eval case, to make the test quicker and cheaper. I also omitted setting EVAL_SLACK_CHANNEL to avoid sending a notification for now.
The new Agent Evaluation step was picked up correctly, dependencies installed, and the CLI built, and eval/results.json was uploaded.
However, the eval itself did not actually run successfully. PromptFoo reported provider errors for all test cases: Could not find any valid files in the command: node ../apps/cli/dist/cli/eval-runner.mjs
So it seems that the Buildkite wiring mostly works, but the eval command is being run from the wrong directory.
# Conflicts: # .buildkite/pipeline.yml # apps/cli/ai/eval-runner.ts
📊 Performance Test ResultsComparing 2b787f1 vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
|
hey @gcsecsey , thanks for the reminder about this one! I gave it a pass and rebase to bring it up to date, also pushed Add optional manual trigger to run the agent eval on a PR In my local testing we need #3682 for tests to pass. |
|
Thanks @lezama for updating this, I merged in the trunk changes and also noticed that we're using the wrong paths, so I also pushed a fix for that. The newly added Agent Evaluation step now runs, but fails on each test case to call the provider. I think we might be missing an |
There was a problem hiding this comment.
Pull request overview
Adds a scheduled (and optionally manual) Buildkite step to run the Studio Code agent PromptFoo eval suite and notify a Slack channel with pass/fail results, plus a small CI-oriented tweak to eval-runner environment resolution.
Changes:
- Adds an “Agent Evaluation” step to
.buildkite/pipeline.yml, intended to run on scheduled builds (and optionally on PRs via an input gate). - Introduces
.buildkite/commands/run-eval.shto install deps, build the CLI, run PromptFoo evals, and post a Slack summary. - Updates
apps/cli/ai/eval-runner.tsto skip provider/env resolution whenANTHROPIC_API_KEYis already present.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
apps/cli/ai/eval-runner.ts |
Skips provider/env resolution when credentials are already present (CI path). |
.buildkite/pipeline.yml |
Adds an input gate + eval step wiring for scheduled or opt-in runs. |
.buildkite/commands/run-eval.sh |
New CI script to run PromptFoo evals and send a Slack notification from results JSON. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Slack-standard good (#36a64f) and danger (#e01e5a) attachment colors. | ||
| payload=$(jq --arg url "$RUN_URL" ' | ||
| .results.stats as $s | | ||
| [.results.results[] | | ||
| "• " + (.testCase.description // .vars.caseId // "unknown") + ": " + | ||
| (if .success then "✅" else "❌" end) | ||
| ] | join("\n") as $lines | | ||
| (($s.successes + $s.failures + $s.errors) | tostring) as $total | | ||
| (if $s.failures == 0 and $s.errors == 0 | ||
| then ":white_check_mark: Agent eval: " + ($s.successes | tostring) + "/" + $total + " passed" | ||
| else ":x: Agent eval: " + ($s.failures | tostring) + "/" + $total + " failed" | ||
| end) as $header | | ||
| (if $s.failures == 0 and $s.errors == 0 then "#36a64f" else "#e01e5a" end) as $color | | ||
| { | ||
| channel: $ENV.EVAL_SLACK_CHANNEL, | ||
| username: "Studio Eval", | ||
| icon_emoji: ":test_tube:", | ||
| attachments: [{ | ||
| color: $color, | ||
| blocks: [ | ||
| { type: "section", text: { type: "mrkdwn", text: $header } }, | ||
| { type: "section", text: { type: "mrkdwn", text: $lines } }, | ||
| { type: "context", elements: [{ type: "mrkdwn", text: ("<" + $url + "|View build>") }] } | ||
| ] | ||
| }] | ||
| } | ||
| ' "$RESULTS_FILE") | ||
|
|
||
| if curl -sf -o /dev/null -X POST https://slack.com/api/chat.postMessage \ | ||
| -H "Authorization: Bearer $SLACK_TOKEN" \ | ||
| -H 'Content-Type: application/json; charset=utf-8' \ | ||
| --data-binary "$payload"; then | ||
| echo "Sent to $EVAL_SLACK_CHANNEL" | ||
| else | ||
| echo "Warning: Slack notification failed" | ||
| fi |
There was a problem hiding this comment.
Just a thought, not sure if it would be a worthwhile improvement: I wonder if this couldn't be replaced by a Fastlane action call (since we have utils to send slack messages). At the same time, that would require a install_gems and using the Ruby runtime, which this script saves so 🤷
There was a problem hiding this comment.
I think it's better to keep the curl version for now, to avoid pulling in install_gems + the Ruby runtime just for sending the Slack message.
There was a problem hiding this comment.
to avoid pulling in install_gems + the Ruby runtime just for sending the Slack message.
I'll push back on this. Given the script runs on the mac queue, which already has Ruby available, I think the advantage of delegating the Slack plumbing to fastlane would be worth while: less custom code for us to maintain.
This sounds like a refactor, though, no need to block the PR for it.
|
Hey @iangmaia 👋 thanks for the review! I think we have a blocker with the env vars, and I'm hoping you can help. When I tested this on Buildkite, the step builds and runs fine, but every eval case fails on the provider call. My guess is that Is there a mechanism we can use to expose |
| exit "$eval_status" | ||
| fi | ||
|
|
||
| RUN_URL="${BUILDKITE_BUILD_URL:-https://buildkite.com}" |
There was a problem hiding this comment.
Might it be cleaner to fail if BUILDKITE_BUILD_URL is not available? It's a core expectation of the CI environment after all.
| # Agent evaluation. Runs automatically on the daily schedule (or with | ||
| # EVAL_PIPELINE=true), and optionally on a PR via the gate above. | ||
| # Setup details in scripts/eval/README.md. | ||
| - label: ":test_tube: Agent Evaluation" | ||
| key: agent_eval | ||
| depends_on: input-eval | ||
| agents: | ||
| queue: mac | ||
| command: bash .buildkite/commands/run-eval.sh | ||
| plugins: [$CI_TOOLKIT_PLUGIN, $NVM_PLUGIN] | ||
| artifact_paths: | ||
| - scripts/eval/results.json | ||
| timeout_in_minutes: 25 | ||
| if: build.source == 'schedule' || build.env('EVAL_PIPELINE') == 'true' || (build.branch != 'trunk' && build.tag !~ /^v[0-9]+/) |
There was a problem hiding this comment.
If the intention is to run this step on a schedule, then with this setup where the only call is in pipeline.yml, the schedule would require running the whole pipeline "only" to get the eval result.
Is this acceptable?
The alternative is to have a dedicated pipeline, something like agent_eval_pipeline.yml with a step like this one and no if conditions, and configure the schedule to run that pipeline.
The downside would be that we'd need to duplicate the step between agent_eval_pipeline.yml and pipeline.yml (with the pipeline.yml version having all of the if checks it currently has minus build.source == 'schedule' and possibly build.env('EVAL_PIPELINE') == 'true'.
There was a problem hiding this comment.
The downside would be that we'd need to duplicate the step between agent_eval_pipeline.yml and pipeline.yml (with the pipeline.yml version having all of the if checks it currently has minus build.source == 'schedule' and possibly build.env('EVAL_PIPELINE') == 'true'.
Thinking about it, but I'm not 100% sure, given the depends_on: input-eval, we might do entirely away with the if in this step, and rely entirely on the conditions defined in the input-eval step.
There was a problem hiding this comment.
If the intention is to run this step on a schedule, then with this setup where the only call is in pipeline.yml, the schedule would require running the whole pipeline "only" to get the eval result.
I'm not sure about the motivation for this change, but I'm not sure it's imperative that we run this on a schedule, but to run this often enough to catch any regressions. @lezama please correct me if I'm wrong.
Would it make sense to run this step automatically for builds from trunk? That'd ensure it runs at least daily.
@gcsecsey yes there is. Let's sync out of GitHub about how. (Update: Internal ref p1782452433458179-slack-C06DRMD6VPZ) |
Summary
Adds a daily agent evaluation step to the existing Studio Buildkite pipeline. Runs automatically only on scheduled builds, otherwise it's behind a manual opt-in button.
Stacked on #3031 — merge that first.
Changes
.buildkite/pipeline.yml— Eval step gated onbuild.source == 'schedule'orEVAL_PIPELINE=true.buildkite/commands/run-eval.sh— Installs deps, builds CLI, runs eval, posts results to Slackapps/cli/ai/eval-runner.ts— Skip provider resolution whenANTHROPIC_API_KEYis already set (needed in CI where there's no Studio auth)Setup (after merge)
An admin adds a schedule in the Studio pipeline settings:
0 6 * * *trunkEVAL_SLACK_CHANNEL=C08LZ79MKG9ANTHROPIC_API_KEYandSLACK_TOKENare already available from the pipeline's existing secrets. To trigger manually: new build with env varEVAL_PIPELINE=true.Slack notification