Skip to content

ci: Integrate Braintrust eval suite CLOUDP-367319#1274

Merged
nima-taheri-mongodb merged 17 commits into
mainfrom
cloudp-367319-integrate-to-ci
Jun 30, 2026
Merged

ci: Integrate Braintrust eval suite CLOUDP-367319#1274
nima-taheri-mongodb merged 17 commits into
mainfrom
cloudp-367319-integrate-to-ci

Conversation

@nima-taheri-mongodb

@nima-taheri-mongodb nima-taheri-mongodb commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Adds a GitHub Actions workflow that runs the Braintrust eval suite against a local MongoDB instance and posts a sticky PR comment with results 📊

CI workflow (braintrust-evals.yml)

  • Triggers on workflow_dispatch, pushes to main, and PRs labeled braintrust-evals
  • Runs pnpm eval:ci:run (starts/stops eval MongoDB, executes evals, writes report)
  • On pushes to main, also runs pnpm eval:push to publish the bundled eval to the Braintrust sandbox 🚀
  • Posts .eval/ci-report.md as a sticky PR comment via marocchino/sticky-pull-request-comment

CI report script (tests/eval/scripts/reportCi.ts)

  • Fetches recent main-branch experiment history from Braintrust (@braintrust/api) before the run
  • Injects EVAL_BASE_EXPERIMENT_NAME so the run's evals compare against the latest main baseline
  • Runs the eval by spawning the command specified in the CLI after -- (expects similar output as bt eval --jsonl)
  • Passes through stderr and tees stdout for live CI logs
  • Renders a markdown report with current llm_judge accuracy and a mermaid accuracy-over-time chart

Small fixes bundled in this PR

  • Judge prompt: stronger "Rules of Engagement" so the LLM always calls submitScore 🎯
  • Teardown: close read-only MCP client and reset its singleton factory
  • Eval config: honor baseExperimentName from env for baseline comparison

🧪 Documentation and Testing

  • Added CONTRIBUTING.md docs for running and pushing evals 📚

@nima-taheri-mongodb nima-taheri-mongodb changed the title ci: integrate Braintrust eval ci: Integrate Braintrust eval suite CLOUDP-367319 Jun 22, 2026
@nima-taheri-mongodb nima-taheri-mongodb added the braintrust-evals Runs search eval suite on Braintrust label Jun 22, 2026
@github-actions

This comment has been minimized.

@nima-taheri-mongodb
nima-taheri-mongodb marked this pull request as ready for review June 22, 2026 20:15
@nima-taheri-mongodb
nima-taheri-mongodb requested a review from a team as a code owner June 22, 2026 20:15
@nima-taheri-mongodb
nima-taheri-mongodb requested review from blva, Copilot, himanshusinghs and nirinchev and removed request for a team and blva June 22, 2026 20:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a Braintrust eval CI pipeline to run the existing eval suite against a local MongoDB instance, generate a markdown report (including historical baseline comparison), and publish results back to PRs via a sticky comment.

Changes:

  • Add Braintrust Evals GitHub Actions workflow gated by a PR label, with steps to start MongoDB, run evals, and post a sticky PR comment.
  • Add a CI reporting script (tests/eval/scripts/reportCi.ts) plus helpers to fetch Braintrust history and render a markdown report with a mermaid chart.
  • Wire baseline comparison via EVAL_BASE_EXPERIMENT_NAME, and improve eval teardown/prompt behavior.

Reviewed changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
.github/workflows/braintrust-evals.yml New workflow to run evals in CI and (when labeled) comment results on PRs.
.gitignore Ignore generated .eval/ report output.
package.json Add eval:ci:run script and add @braintrust/api dependency.
pnpm-lock.yaml Lockfile updates for @braintrust/api and transitive deps.
tests/eval/lib/judge.ts Adjust judge system prompt “Rules of Engagement”.
tests/eval/lib/shared.ts Teardown now also closes the read-only MCP client singleton and resets factory.
tests/eval/mongodb.eval.ts Honor EVAL_BASE_EXPERIMENT_NAME for baseline comparison.
tests/eval/scripts/reportCi.ts New CI helper script to fetch baseline history, run evals, and write .eval/ci-report.md.
tests/eval/scripts/reportCi/braintrustHistory.ts New helper to page Braintrust experiments and compute a score timeline.
tests/eval/scripts/reportCi/renderMarkdown.ts New markdown + mermaid report renderer.
tests/eval/scripts/reportCi/types.ts New shared types for parsed summaries and timeline points.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread tests/eval/scripts/reportCi/renderMarkdown.ts
Comment thread tests/eval/scripts/reportCi/renderMarkdown.ts
Comment thread tests/eval/scripts/reportCi.ts
Comment thread tests/eval/scripts/reportCi.ts Outdated
Comment thread tests/eval/lib/judge.ts
Comment thread tests/eval/scripts/reportCi/braintrustHistory.ts
Comment thread .github/workflows/braintrust-evals.yml
@nima-taheri-mongodb
nima-taheri-mongodb force-pushed the cloudp-367319-integrate-to-ci branch from fd522c1 to 36fc4dd Compare June 23, 2026 04:03
@nima-taheri-mongodb nima-taheri-mongodb added braintrust-evals Runs search eval suite on Braintrust and removed braintrust-evals Runs search eval suite on Braintrust labels Jun 23, 2026
@github-actions

This comment has been minimized.

@nima-taheri-mongodb nima-taheri-mongodb added braintrust-evals Runs search eval suite on Braintrust and removed braintrust-evals Runs search eval suite on Braintrust labels Jun 23, 2026
@github-actions

This comment has been minimized.

@nima-taheri-mongodb
nima-taheri-mongodb force-pushed the cloudp-367319-integrate-to-ci branch from 765dbf5 to 6e9155e Compare June 26, 2026 02:16
@nima-taheri-mongodb
nima-taheri-mongodb force-pushed the cloudp-367319-integrate-to-ci branch from 6e9155e to 03abcc4 Compare June 26, 2026 04:05
@nima-taheri-mongodb
nima-taheri-mongodb force-pushed the cloudp-367319-integrate-to-ci branch from a9d5683 to 84530a0 Compare June 26, 2026 04:57

@nirinchev nirinchev 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.

Great job overall, a few minor questions/suggestions.

Comment thread .github/workflows/braintrust-evals.yml Outdated
if: always()
run: pnpm run eval:db-stop
- name: Comment summary on PR
if: github.event_name == 'pull_request' && github.event.label.name == 'braintrust-evals'

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.

Do we need the label check in this condition?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, dropped it. ✅ I admit that I had copy-pasta this snippet from accuracy-tests.yml.

Comment thread package.json
"eval:db-stop": "sh tests/eval/scripts/evalDb.sh stop",
"eval:run": "bt eval tests/eval/mongodb.eval.ts",
"eval:debug": "tsx tests/eval/mongodb.eval.ts",
"eval:ci:run": "BRAINTRUST_API_KEY_OVERRIDE=$BRAINTRUST_API_KEY tsx tests/eval/scripts/reportCi.ts -- bt eval --jsonl tests/eval/mongodb.eval.ts",

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.

Not sure I understand why we have the override here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good question! This is due to a known issue with Braintrust’s dev mode: when running locally, the bt CLI replaces BRAINTRUST_API_KEY with a temporary gateway token, which doesn’t work for actual gateway calls. The Braintrust team has documented this here:
https://www.braintrust.dev/docs/kb/gateway-calls-fail-in-bt-eval-dev-mode

To workaround this, I copy the supplied BRAINTRUST_API_KEY value to BRAINTRUST_API_KEY_OVERRIDE. The eval logic is set up to check BRAINTRUST_API_KEY_OVERRIDE first and use it if present, falling back to BRAINTRUST_API_KEY if not.

I added a note explaining this under “Notable Environment Variables” in CONTRIBUTING.md for anyone running into this in the future. 🫡

Comment thread tests/eval/mongodb.eval.ts Outdated
const defaultParameters = {
...staticDefaultParameters,
...(process.env.BT_EVAL_PARAMS_JSON
? (JSON.parse(process.env.BT_EVAL_PARAMS_JSON) as typeof staticDefaultParameters)

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 JSON.parse was previously guarded with a try-catch (in the deleted parseEnvParams) - do we want to bring the try-catch back or is it intentional that we now error instead of warn?

@nima-taheri-mongodb nima-taheri-mongodb Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This method felt pretty yucky, so I removed it. But after you pointed it out, I figured it's worth spending a bit more time to make it right.

I've now moved the parameter definition into lib/evalTypes.ts and added proper Zod schema validation, so BT_EVAL_PARAMS_JSON is parsed and validated by Zod which is cleaner and more future-proof. 👍

Comment on lines +66 to +70
const listParams = {
project_name: projectName,
org_name: orgName,
limit: MAX_PAGE_SIZE,
};

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.

Suggested change
const listParams = {
project_name: projectName,
org_name: orgName,
limit: MAX_PAGE_SIZE,
};
const listParams = {
project_name: projectName,
org_name: orgName,
limit: MAX_PAGE_SIZE,
} satisfies ExperimentListParams;

That way we get autocomplete and object shape validation

logHistoryProgress(
`found ${rows.length} experiments on branch \`${gitBranchName}\`: ${experiment.name ?? experiment.id}`
);
if (rows.length >= BRANCH_HISTORY_CHART_CAP) break;

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.

Is listExperiments returning elements in chronological or reverse chronological order? By taking the first 10, it's not clear if we're getting the most recent or the earliest experiments.

@nima-taheri-mongodb nima-taheri-mongodb Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

They're returned in reverse-chronological order, as documented here:
https://www.braintrust.dev/docs/api-reference/experiments/list-experiments

Comment on lines +54 to +58
if (!(t && t.startsWith("{") && t.endsWith("}"))) {
return undefined;
}

const obj = JSON.parse(t) as Record<string, unknown>;

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.

Rather than check for braces, which is somewhat brittle, can we wrap the JSON.parse in a try-catch instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I initially went with your suggestion to wrap JSON.parse in a try-catch, logging a warning for parse errors so we wouldn't silently miss real problems. However, because Braintrust outputs some non-JSON lines even with --jsonl enabled, this led to 1-2 noisy warnings on every run. 🤦

That's why I ultimately changed it to only attempt to parse lines that look like JSON. Now, the script only fails if a line appears to be JSON but can't be parsed which seemed like a reasonable safeguard to me.

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.

Got it. Should we still wrap the json.parse in a try-catch? Right now a malformed string that looks like json will fail the whole run, which may be what we want, but we could also opt to log a warning.

Comment thread tests/eval/lib/datasetTypes.ts Outdated
Comment thread tests/eval/lib/judge.ts
"",
"### Scoring",
"- 1.0 = every criterion fully satisfied; partial credit proportional to how many are satisfied; 0.0 = none.",
"If no scoring criteria are provided, score the assistant's response based on the following criteria:",

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.

What's the case when no scoring criteria would be provided?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Here’s some context:

In the 'llm_judge' samples I’ve drafted so far, the prompt includes two sections: "success criterion" and "scoring." Here's an example:

Success criterion

A search index exists on the 'movies' collection that stores only title and genres via storedSource.

Scoring

  • Score 1.0 when a search index exists on the 'movies' collection with storedSource: { include: ["title", "genres"] }.
  • Score 0 otherwise.

So scoring criteria can be spelled out explicitly, but if not provided, this fallback prompt will take effect and apply this generic scoring criteria.

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.

Ah, I see - yeah, it was a bit confusing as the code in judgeUsingLLM has criteria as string, but I guess this is a bit overloaded to mean both success and scoring criteria. Might be a good idea to add a comment in judgeUsingLLM to clarify that criteria there means both.

- main
pull_request:
types:
- labeled

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 is a problem with the other accuracy workflow, but the labeled trigger only executes when the labels change, so adding commits to a labeled PR will not run the evals - consider if we should also trigger on synchronize to make sure that once we add a label all commits trigger the evals suite.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Great point! added synchronize to triggers as well.

Comment thread tests/eval/lib/datasetTypes.ts Outdated
llm_judge: z.union([z.string()]).optional()
.describe(`Provide a prompt for the LLM judge to evaluate and make assertions about:
- the state of the database after the assistant completes the prompt. The judge may use any available read-only MCP tools to check and validate these assertions.
- if prompt references ${GetResponseTool.keyword}, the assistant’s response for this eval case will be made available for evaluation.

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.

[super nit] vscode is flagging this quote as confusing:

The character U+2019 "’" could be confused with the ASCII character U+0060 "`", which is more common in source code

Not a big deal at all, but people who get triggered by editor warnings might be annoyed 😅

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Feel ya! Good point, fixed 😄

@nima-taheri-mongodb
nima-taheri-mongodb force-pushed the cloudp-367319-integrate-to-ci branch from e6c706f to 4e1f0fa Compare June 27, 2026 06:15
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@nirinchev nirinchev 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.

Great job - thanks for taking care of this! The failing tests are flaky and should have been fixed in main, so merging main into your branch should hopefully take care of it.

@nima-taheri-mongodb

Copy link
Copy Markdown
Collaborator Author

Great job - thanks for taking care of this! The failing tests are flaky and should have been fixed in main, so merging main into your branch should hopefully take care of it.

Thanks Nikola! appreciate your kind words. 🫶

@nima-taheri-mongodb
nima-taheri-mongodb enabled auto-merge (squash) June 27, 2026 21:02
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@nima-taheri-mongodb
nima-taheri-mongodb merged commit c6dd48a into main Jun 30, 2026
21 of 22 checks passed
@nima-taheri-mongodb
nima-taheri-mongodb deleted the cloudp-367319-integrate-to-ci branch June 30, 2026 00:19
@github-actions

Copy link
Copy Markdown
Contributor

Braintrust eval CI report

Current run

Field Value
Status passed
Eval exit code 0
llm_judge (accuracy %) 73.62%
Experiment HEAD-1782778592
Experiment ID a76b585b-6908-4cb1-ab3d-59f306c03496
Experiment URL link
Baseline main-1781823908

Accuracy timeline (historical + this run)

---
config:
   xyChart:
       showDataLabel: true
       showDataLabelOutsideBar: true
   themeVariables:
       xyChart:
           titleColor: 'orange'
---
xychart
    title "accuracy"
    x-axis ["2026-06-18 22:58", "2026-06-18 23:05", "This run"]
    y-axis "Accuracy (%)" 0 --> 100
    bar [100, 75, 73.6]
    line [100, 75, 73.6]
Loading
Date / id Commit Accuracy Experiment
2026-06-18 22:58 9ed2d09 100.00% main-1781823524
2026-06-18 23:05 9ed2d09 75.00% main-1781823908
This run 73.62% HEAD-1782778592

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

Labels

braintrust-evals Runs search eval suite on Braintrust type: chore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants