Repository navigation
Add a behaviour test suite for the plugin - #108
Merged
Merged
Conversation
This adds five evaluation cases that run Claude with and without the plugin installed and score the answers, so a change to the skill text can be checked against what the model actually does instead of only against the wording of the documentation. The problem: the existing tests read the documentation. Nothing checked behaviour. A rule could be deleted from the skill and every test would still pass. The fix: five cases under evals/, each one a user question plus a set of checks on the answer. Four cases ask for something the plugin is meant to change; the fifth is a plain Python question that the plugin must stay out of. Every FactIQ tool is answered from a canned file in evals/mocks/factiq/, so the runs need no login, cost no server quota, and return the same data every time. The fixtures are real payloads, with one source link blanked on purpose so a case can check that a missing link is declared rather than invented. The evidence: over three runs of each case on each side, the four "should change" cases score 1.00, 1.00, 1.00 and 0.97 with the plugin against 0.17, 0.33, 0.40 and 0.42 without it. The Python case scores 1.00 on both sides, which is the intended result for a question the plugin should ignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s that could not fail The suite now compares the full plugin with a copy that keeps the mocked FactIQ tools but has no skill and no scripts, so the score difference is what the skill adds. Three checks that passed no matter what the model did are fixed, three cases are added, and the suite is wired to run by hand from GitHub Actions while the unit tests run on every push. Checks that could not fail: the "must not call a FactIQ tool" checks named the tools mcp__factiq__*, but inside the sandbox they are mcp__plugin_factiq_factiq__*, so they matched nothing. The trade-query check accepted any command that mentioned the generator's file name. The built-in with-and-without comparison removed the MCP server from the second arm, so every data case failed there for want of tools. New cases: a quick chart whose written spec is graded as a file, a filing where the same figure is reported on two lines, and the year-over-year case with the server's gap note removed so it measures the skill's rule. The get_series and search_company_filings mocks answer by series id and company from fixtures; the tool list is the real 14-tool list. Evidence from one run (agent Opus, judge Sonnet, three runs per case) is in the pull request. The suite found that the written chart spec omits the required id key in two of three runs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The tests need only the standard library, but the runner's Python has no pytest, so the job failed before collecting a test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds a behaviour test suite for the plugin: eight cases that run a real model against the skill with the FactIQ data server mocked, and a runner that scores every case twice, once with the full plugin and once with the same mocked tools but no skill and no scripts. The difference between the two scores is what the skill and the bundled scripts add. In the run reported below, the plugin arm passes every case at the chosen bar, and the suite has already found one defect in the plugin's own output: the chart spec it writes omits the required
idkey in two of three runs.The problem
The unit tests in
tests/check the wording of the documentation and the output of the bundled scripts. Nothing checked what Claude does with the skill: whether it computes year-over-year by calendar date across a missing month, whether it adds a filing line to its own duplicate, whether it writes a chart spec the way the reference describes, or whether it stays out of the way on a question that has nothing to do with data.What the suite is
Each case is a directory under
evals/with aprompt.md(the user's message and the run settings) and agraders/directory (one check per file). Checks are either exact pattern matches, tool-call counts, a file-exists check, or a judgment by a second model that votes three times. The FactIQ server is never called: the files inevals/mocks/factiq/answer each tool with real payloads captured from the server. Two of the mocks answer according to their arguments, so a request for seriesLNS14000000gets the unemployment fixture and any other series id gets an error, and a filings search forPKGgets the Packaging Corporation of America fixture.The eight cases:
latest-value-direct-answerquick-chart-of-a-trendyoy-across-a-missing-monthyoy-without-a-server-hintbilateral-trade-sqlfilings-duplicate-linequote-and-source-disciplineunrelated-coding-requestWhy the comparison uses a tools-only baseline
The built-in with-and-without comparison in
claude plugin evalremoves the whole plugin from the second arm, including its MCP server. That arm has no data tools at all, so every data case fails there for a reason that has nothing to do with the skill. The runner inevals/run_ablation.pyinstead builds a temporary copy of the plugin that keeps only the manifest and the MCP configuration, and runs both copies with the same mocks, the same model, the same judge, and the same tool grants. What the baseline gets wrong and the plugin gets right is the skill's contribution.Evidence
One run of the whole suite, agent model Opus, judge model Sonnet, Claude Code 2.1.269, three runs per case, started 2026-09-12 UTC. No run errored or aborted.
bilateral-trade-sqlfilings-duplicate-linelatest-value-direct-answerquick-chart-of-a-trendquote-and-source-disciplineunrelated-coding-requestyoy-across-a-missing-monthyoy-without-a-server-hintCost: 14.93 US dollars for the plugin arm and 9.49 for the baseline arm.
Three cases score the same in both arms: the two year-over-year cases, the filings case, and the coding case. Opus gets those right from the tool output alone, so today they guard against regressions rather than measure the skill. The trade query, the chart, the short answer, and the source links are where the skill changes the outcome.
The three misses in the plugin arm, in detail:
quick-chart-of-a-trend, two runs of three. The writtenchart.jsonhadtitle,type,xField,seriesanddatain the documented wide-row form, a title that stated the change, the October 2025 gap left empty, and a BLS source, but noidkey. The chart-spec reference listsidas required. That is a defect in the plugin's output, and a follow-up should either make the skill emit it or drop it from the required list.latest-value-direct-answer, one run of three. The answer was two sentences: 4.1 percent in August 2026, per the BLS Current Population Survey, down from 4.5 percent in November 2025. Two of three judge votes failed it on the "answers in prose" check. I read this as judge disagreement, not a defect, and left the check as it is.quote-and-source-discipline, one run of three. The answer set the analyst's "71% to 72%" remark apart from the management quotes, as the case requires, but quoted a fragment of it in a caveat without the supplied link. The link check is weight 1 of 12 for that case.Before this change, the cases as first written could not have produced this table. The "must not call a FactIQ tool" checks named the tools
mcp__factiq__*, but inside the sandbox they aremcp__plugin_factiq_factiq__*, so those checks passed no matter what the model did. The trade-query check accepted any command that mentioned the generator's file name, including reading it. The "without plugin" arm had no tools, so its scores said nothing about the skill. All three are fixed here.How it runs
tests/run on every push tomainand on every pull request (.github/workflows/tests.yml)..github/workflows/behaviour-evals.ymlruns it by hand from the Actions tab, with the model, the number of runs per case, and the pass bar chosen there. The job needs anANTHROPIC_API_KEYrepository secret, which has not been set up yet, so the workflow is not yet verified in CI. Locally the same command is documented inevals/README.md.Details
evals/run_ablation.py: builds the tools-only baseline, runs both arms, writesevals/results/<timestamp>/comparison.mdwith every case and every check in both arms, and exits 1 when a plugin-arm case scores below the bar.evals/mocks/factiq/_tools.json: the real tool list from the server, 14 tools with their real descriptions and input schemas, so the model sees the same tool surface it would see in use.evals/mocks/factiq/get_series.mdandsearch_company_filings.md: answer fromfixtures/<series_id>.jsonandfixtures/<company>.json. Thetransformargument is ignored on purpose: a request for a year-over-year transform receives plain values, which is what the year-over-year cases need.LNS14000000, 31 monthly values, no October 2025 observation) and Packaging Corporation of America segment revenue for the quarter ended June 2026 (eight facts, the segment total present twice, with the server's warning note stripped so the case measures the skill's rule). The NVIDIA gross-margin transcript rows from the first version are kept, with one source link blanked on purpose.evals/README.mdexplains the sandbox: tool names, that the plugin root variable is empty inside a run, and the rules for writing a check that can actually fail.🤖 Generated with Claude Code