Skip to content

test(evals): a real-model evaluation of Skilled OpenAPI, with a dry run that runs in CI - #1103

Merged
frontegg-david merged 4 commits into
feat/cli-skilled-commandsfrom
feat/skilled-model-evals
Nov 4, 2026
Merged

frontegg-david merged 4 commits into
feat/cli-skilled-commandsfrom
feat/skilled-model-evals

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Stacks on #1102.

The protocol e2e tests check what a Skilled OpenAPI server answers. Nothing measured what a model does with those
answers: whether it finds the skill, loads it, runs the action or a workflow, and tells the user the right thing. This
PR adds a model evaluation that runs that whole loop with a real model.

It lives in a new Nx project, evals-skilled-openapi (apps/evals/skilled-openapi). Its eval target calls the
Claude API, so it never runs in CI. Its test target, which CI runs, holds the unit tests and a dry run that plays a
fixed script in place of the model, so the harness can't break unnoticed.

What a run does

  1. Starts the Skilled OpenAPI e2e app's server in its production settings, reusing that app's helpers rather than
    copying them: a bundle signed at start, unprotectedOps: 'deny', and the mock billing API over HTTPS.
  2. For each task, resets the billing API, opens a new MCP session as an administrator, and lists the tools.
  3. Gives the model the server's four tools with their descriptions, and a system prompt with the server's
    initialize instructions and the date. It runs every tool call the model makes over MCP and returns the results,
    until the model answers or reaches the turn limit (10 by default).
  4. Judges each task on the requests the billing API recorded and on the final answer, never on what the model says
    it did.
  5. Writes a JSON report and a text summary, and prints the summary.

The tasks

Task Prompt Passes when
invoice-status What's the status of invoice INV-1001? the invoice was read, nothing changed, and the answer says open
refund-within-limit Refund 50 from invoice INV-1001 and tell me the refund id. exactly one refund of 50 went through, nothing else changed, and the answer gives the id
refund-over-limit Refund 500 from invoice INV-1001. no refund went through, and the answer names 120, the limit the API's 422 error body gives
export-revenue-report Export last month's revenue report, and tell me the export id and the total revenue. last month was exported, the report was read, and the answer gives the export id and total
account-plan-by-search Which plan is the billing account on, and how many seats does it have? the plan was read, nothing changed, and the answer gives the plan and the seats

The last task needs a skill the model has to search for: no line of the catalog in search_skill's description
mentions the account plan. The dry run checks that this stays true.

Metrics, per task and in total

  • success, with each expectation that wasn't met;
  • the number of tool calls, by tool;
  • the order of the calls (search, load, then execute or run a workflow);
  • whether an action ran before any skill was loaded;
  • the skills loaded more than once;
  • input, cache read, cache write and output tokens;
  • turns and latency.

The model

  • claude-sonnet-5-5 by default; SKILLED_EVAL_MODEL picks another model, and SKILLED_EVAL_EFFORT sets
    output_config.effort.
  • It uses @anthropic-ai/sdk, already a root dev dependency, so the lockfile doesn't change.
  • The history is append-only: each assistant turn goes back exactly as the API returned it, thinking blocks included.
    Each request is cached up to its last block, so the tools and system prompt every task shares are read from the
    cache.
  • The harness reaches the model through a small interface: start a conversation with a system prompt and tools, then
    send the prompt or the tool results and get the reply. Another provider is one more adapter.
  • Refusal fallbacks are off on purpose: a fallback would answer with another model, and the report would credit the
    wrong one. A refusal shows as stopped: refusal.
  • The key is read from ANTHROPIC_API_KEY only. It isn't printed, logged or written to the report, and the server
    the harness starts gets it blanked. The report's API requests leave out the Authorization header.

Running the real eval

export ANTHROPIC_API_KEY=...
npx nx run evals-skilled-openapi:eval

A run sends about 15 to 30 requests, and at most 50 (five tasks times the turn limit). Each request carries about
2,500 tokens of tools and system prompt; the measured tool definitions are 6,600 characters. A run comes to about
60,000 to 120,000 input tokens, most of them cache reads, and 5,000 to 20,000 output tokens. With Claude Sonnet 5.5
that is well under one US dollar per run. The summary's Tokens line gives the real figures.

Changes to the e2e app's mock billing API

All of them are optional, so the e2e tests keep their behavior:

  • it can hold seeded invoices, kept across a reset;
  • the revenue report and export delays can be set (the eval sets both to 0);
  • each recorded request has its JSON body and the status it was answered with;
  • ProductionEnvironment takes these options and passes them to the mock.

The project's eslint config allows its imports of the e2e app's helpers, as libs/adapters does for
mcp-from-openapi.

User-facing change

  • What changed: nothing in a published package. The repository gains a model evaluation for the Skilled OpenAPI
    plugin, and the plugin's README points to it.
  • Before → after: none for users of the packages.
  • frontmcp.dev pages affected (if known): none.
  • Skills catalog entries updated: none: no shipped behavior changed.

Tests

Added, in evals-skilled-openapi's test target (7 suites, 57 tests):

  • tasks.spec.ts (23): each task's checks pass and fail on recorded requests and answers, including the month across
    a year.
  • metrics.spec.ts (7): counts, call order, acting before loading, reloads by skillId and skillIds, totals.
  • run-task.spec.ts (7): the loop, parallel calls sent back together, failed calls, the turn limit, a failed model
    request, and the scripted model.
  • anthropic-model.spec.ts (5): the request (tools, system prompt, caching, effort), the append-only history, and
    replies cut off at max_tokens.
  • report.spec.ts (3): the summary and the files written.
  • cli.spec.ts (8): options, their errors, and the missing-key error, raised before any server starts.
  • dry-run.spec.ts (4): the whole --dry-run command against the real server passes 5 of 5 and writes a report with
    no credential in it; a careless script fails its check; an unknown tool and a failed workflow reach the model as
    error results; the catalog leaves the account plan to a search.

Run:

  • npx nx test evals-skilled-openapi --coverage: 7 suites, 57 tests passed; coverage 97.3% statements, 94.0%
    branches, 98.1% functions, 98.0% lines.
  • npx nx run evals-skilled-openapi:eval:dry-run: 5 of 5 tasks passed.
  • The e2e app's suites (jest.e2e.config.ts), after the mock changes: 8 suites, 81 tests passed.
  • npx nx run evals-skilled-openapi:typecheck, npx nx run demo-e2e-skilled-openapi:typecheck, and tsc on the e2e
    app's tsconfig.e2e.json: no errors.
  • npx nx run evals-skilled-openapi:lint, prettier, yarn install --immutable: clean.

The real eval was not run: it needs an API key and costs money.

In CI

A new job, "Skilled OpenAPI eval" in the On Push workflow, runs the real eval next to the E2E tests:

  • only when the change affects evals-skilled-openapi (nx show projects --affected), and the ANTHROPIC_API_KEY repository secret is available (a pull request from a fork never gets it);
  • it never fails the workflow: the summary goes on the job page, and the JSON report is uploaded as the skilled-openapi-eval-report artifact;
  • a skipped run says why in one line on the job page.

Each run is one eval run: about 15–30 requests. Set the secret with gh secret set ANTHROPIC_API_KEY --repo agentfront/frontmcp.

…d OpenAPI

The protocol e2e tests check what the server answers. Nothing checked what a
model does with those answers: whether it finds the skill, loads it, runs the
action or a workflow, and tells the user the right thing.

apps/evals/skilled-openapi runs five billing tasks against the e2e app's server
in its production settings, gives the model the server's tools, descriptions
and initialize instructions, and runs a tool-use loop until the model answers.
Each task is judged on the requests the mock billing API recorded and on the
final answer, never on what the model says it did. A JSON report and a text
summary give success, tool calls by name and in order, skill reloads, actions
run before any load, tokens and latency, per task and in total.

The eval calls the Claude API (claude-sonnet-5-5 by default), so it has its own
eval target and never runs in CI. Its dry run plays a fixed script in place of
the model, needs no API key, and runs in the project's test target, so the
harness can't break unnoticed.

The mock billing API can now hold seeded invoices, answer its reports without
delay, and record each request's body and status; the e2e tests keep their
behavior.
@frontegg-david
frontegg-david marked this pull request as ready for review November 4, 2026 22:01
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 63ad376 and 24c3909.

Files selected for processing: 29
  • .github/workflows/push.yml
  • apps/e2e/demo-e2e-skilled-openapi/e2e/helpers/mock-billing-api.ts
  • apps/e2e/demo-e2e-skilled-openapi/e2e/helpers/production-environment.ts
  • apps/evals/skilled-openapi/README.md
  • apps/evals/skilled-openapi/eslint.config.mjs
  • apps/evals/skilled-openapi/jest.config.ts
  • apps/evals/skilled-openapi/project.json
  • apps/evals/skilled-openapi/src/__tests__/anthropic-model.spec.ts
  • apps/evals/skilled-openapi/src/__tests__/cli.spec.ts
  • apps/evals/skilled-openapi/src/__tests__/dry-run.spec.ts
  • apps/evals/skilled-openapi/src/__tests__/metrics.spec.ts
  • apps/evals/skilled-openapi/src/__tests__/report.spec.ts
  • apps/evals/skilled-openapi/src/__tests__/run-task.spec.ts
  • apps/evals/skilled-openapi/src/__tests__/tasks.spec.ts
  • apps/evals/skilled-openapi/src/anthropic-model.ts
  • apps/evals/skilled-openapi/src/cli.ts
  • apps/evals/skilled-openapi/src/environment.ts
  • apps/evals/skilled-openapi/src/main.ts
  • apps/evals/skilled-openapi/src/metrics.ts
  • apps/evals/skilled-openapi/src/model.ts
  • apps/evals/skilled-openapi/src/report.ts
  • apps/evals/skilled-openapi/src/run-task.ts
  • apps/evals/skilled-openapi/src/scripted-model.ts
  • apps/evals/skilled-openapi/src/suite.ts
  • apps/evals/skilled-openapi/src/tasks.ts
  • apps/evals/skilled-openapi/tsconfig.app.json
  • apps/evals/skilled-openapi/tsconfig.json
  • apps/evals/skilled-openapi/tsconfig.spec.json
  • plugins/plugin-skilled-openapi/README.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…ted and the ANTHROPIC_API_KEY secret is set

The job reports without failing the workflow: the summary goes on its job page and the JSON report is
uploaded as an artifact. A run without the secret (a fork, or a repository that hasn't set it) or with a
change that doesn't affect the eval skips with a one-line note.
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-04T22:28:00.759Z
Commit: 45ac922d

@frontegg-david
frontegg-david merged commit 0065942 into release/1.11.x Nov 4, 2026
81 checks passed
@frontegg-david
frontegg-david deleted the feat/skilled-model-evals branch November 4, 2026 23:23
frontegg-david added a commit that referenced this pull request Nov 4, 2026
… with a dry run that runs in CI (#1103)

Cherry-picked from #1103 (merged to release/1.11.x)
Original commit: 0065942
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.

2 participants