Skip to main content

agent-report-card

Five-minute demo  ·  The checks  ·  The judge's exam  ·  Newsletter

PyPI version CI status License: MIT Python 3.10+ No API keys Judge exam: 30/30 Author: satsawat.ai

You have a bot that answers questions from your own documents, and someone has to sign off before it reaches customers. This tool asks the bot your list of questions and writes the sign-off document itself: one page in markdown, a plain text file with light formatting marks that GitHub and most editors show as a tidy document. The page carries a verdict, the wrong answers quoted word for word, and a line under each wrong answer saying where it broke.

You do not sign up for anything and you do not paste in any secret key. Everything runs on your machine. You write your questions in one YAML file (a plain text file with an indented layout), run one command, and get one markdown report. When a second model helps with the grading, you can give that model its own exam first, and the report warns you if you skipped it.

The report is written for a non-engineer stakeholder who never installs the tool. They read the verdict. Your automatic build (CI) reads the command's exit code: a small number, explained below, that says pass, fail, or could-not-run. The report is plain text, so you can keep it in git and see what changed line by line.

What you need to know first

The terms this page uses, in plain words.

  • A RAG bot is a chatbot that first looks up passages in your documents and then writes an answer from them. RAG stands for retrieval-augmented generation. Retrieval is the look-up step and generation is the writing step. The report keeps the two apart, because a wrong answer built from the wrong passage is a different bug from a wrong answer built from the right passage. This page says "the bot" from here on.
  • An endpoint is the web address your bot listens on, such as http://localhost:8000. The number after the colon is the port. The tool sends each question to that address as a web request (an HTTP POST) and reads the answer out of the structured reply (JSON).
  • A test file is the YAML file holding your questions. Each question, together with the answer you expect and the checks it must pass, is one case. The tool's own messages sometimes call the whole file a suite.
  • A check is one yes-or-no test on one answer, such as "does the answer contain 3.2%" or "did the bot leak a stack trace" (an error dump from its own code). There are 19 checks, fixed in code. 14 of them are plain Python comparisons, so the same answer always gets the same result. This page calls them the code checks, and the report calls the same thing the deterministic route. The other 5 need a judge.
  • The judge is a second language model, a program of the same kind as the chatbot itself. It reads an answer and returns pass or fail on questions code cannot decide, such as whether a paraphrase is still correct. Here the judge runs on your own machine through Ollama. Ollama is a free program that downloads and runs models locally. The default judge model is qwen3.6:27b, a model of about 27 billion parameters. Parameters are a model's internal settings, and more of them usually means a bigger model that answers better but needs more memory to run. Using a judge is optional.
  • Hallucination, as this tool counts it, is a claim in the answer that the retrieved passages do not support. A claim can be true in the world and still count, because the test is support in the passages, not truth. The tool calls this grounding. Measuring it needs the judge.
  • A gate is a bar the whole run must clear, such as accuracy at least 80%. One failed gate makes the verdict NOT READY. The default gates also say every case you tag critical in the test file must pass every code check that applies to it.
  • The exit code is the number a command hands back when it finishes. CI (continuous integration) is the script that runs on every push to your code, on GitHub Actions or similar, and it fails the build when a command exits with anything other than 0. This tool exits 0 when the bot passes, 1 when a gate failed, and 2 when the tool itself could not run.

⚡ Five minutes, no model, no keys

pip install agent-report-card
agent-report-card demo --judge none

You need Python 3.10 or newer. Stock macOS python3 may be 3.9. If python3 --version says so, install a newer Python (from python.org, or with a version manager such as pyenv or uv) and run pip from that one. To work on the tool itself, clone the repo and pip install -e . instead.

The demo command starts a fake support bot bundled with the tool, for the fictional Northstar Telecom, with nine flaws planted on purpose. The terminal output below calls this fake bot the fixture bot. It then asks that bot the bundled 21 questions and grades every answer. Nothing leaves your machine. The terminal shows this:

wrote board_questions.yaml (the bundled demo suite)
fixture bot serving on http://127.0.0.1:8000 (fictional Northstar Telecom; flaws planted on purpose)
accuracy 84% (16/19) · hallucination n/a (grounding needs the judge; drop --judge none to evaluate it) · 3 failures · NOT READY -> report.md

The command exits with code 1, which is the intended outcome here and not a broken install. Those flaws are planted so the report has something to find. Three files land in the current directory: board_questions.yaml (the test file), report.md (the report), and report.scores.json (the raw result for every case, in a file layout that may change between versions).

What each part of that last line means:

  • accuracy 84% (16/19): the test file holds 21 questions. 2 of them are questions the bot should refuse, and those two are scored separately. That leaves 19 answerable questions. Of those 19, 16 passed every correctness check the test file lists for them. The demo test file sets its own accuracy floor at 90% (the default is 80%), which is why 84% fails the gate. Next to the 84% the report also prints a plausible range, 62% to 94%. With only 19 questions the true pass rate could sit anywhere in that range, so it is wide on purpose. One flipped answer moves accuracy by about 5 points. The report labels this range a 95% interval.
  • hallucination n/a: the hallucination number needs the judge, and this run had none. The report prints n/a with the reason. It never prints a pass it did not measure.
  • 3 failures: the banner counts only the 3 wrong answers. In all, 8 cases failed at least one check of any kind: those same 3, plus 5 that answered correctly but leaked a trace, ran slow, or skipped a citation. The report lists all 8 below the banner, and says right above the list why the banner count is the narrower number.

The three exit codes:

exit code meaning
0 PASS or PASS WITH WARNINGS. Ship.
1 At least one gate failed. The bot is not ready.
2 The tool itself could not run: bad YAML, unreachable endpoint, or a judge was asked for and is not there.

demo starts the bot and grades it in one go. The command you will use on your own bot is run. To try run against the demo bot, keep the bot up and use a second terminal:

agent-report-card demo --keep-serving --port 8000 --judge none
# in another terminal, from the same directory:
agent-report-card run --tests board_questions.yaml --endpoint http://localhost:8000 --judge none

The first command grades once on its own, then keeps the bot serving until you press Ctrl-C. --port 8000 errors clearly if something else owns the port. Without the flag, demo falls back to a free port and tells you.

To bring the judge in, install Ollama, pull the default model with ollama pull qwen3.6:27b, and drop --judge none. The 5 judge checks then light up too. Expect a judged run to take minutes rather than seconds. The report header records how long grading took, and that number is not how fast the bot answered. The bot's own response time is a separate row in the report's Scorecard table. Without Ollama, demo falls back to no judge and prints a notice. run is stricter: if you asked for a judge and there is none, it stops with exit code 2 and says so, instead of quietly grading without one.

agent-report-card demo, the real terminal output

After the demo, take the twenty-minute tour. examples/ walks from a four-question test file to a CI gate, one runnable file at a time. It also shows how to come up with the questions to ask. No tool does that for you. The same tour is also a notebook you can run, examples/tutorial.ipynb, and every output there comes from a real run. To start on your own bot, agent-report-card init writes a commented starter test file showing every supported field.

What the report says

See a report before installing anything. One saved example per verdict, all real runs against the bundled bot:

verdict report terminal output how it happened
NOT READY demo_report_judged.md log the flawed demo test file, graded with the default judge. Three gates fail: accuracy, hallucination, and a critical case that leaked a stack trace
NOT READY, no judge demo_report.md log the same test file with no judge, so hallucination shows n/a
PASS sample_pass.md log the flaw-free subset (examples/release_questions.yaml), graded with the judge. Every gate green and no warnings
PASS WITH WARNINGS sample_pass_with_warnings.md log the same clean test file without a judge. The hallucination gate needs the judge, so it cannot be evaluated, and the report records that as a warning rather than a silent pass

Each log holds a command line, then the per-case console stream and the exit code as captured from the run. The command line at the top is not captured. scripts/make_gallery.py, the script that regenerates all four, writes it in.

The judged run's report opens with this line:

accuracy 84% (16/19) · hallucination 6% (1/16) · 3 failures · NOT READY

One number is new here, hallucination 6% (1/16). The 16 is the count of cases where the bot returned the passages it used, since grounding can only be checked against returned passages. In 1 of those 16 the judge found a claim the passages do not support, which is the planted flaw (an invented industry ranking). The demo test file sets the hallucination ceiling at 5%, the same as the default. 6% is over it, so the judged run fails three gates where the no-judge run failed two.

CI re-runs the no-judge demo on every push. If the accuracy or the failure count on this page stops matching, the build fails. CI also checks that the 6% quoted here matches the judged report saved in reports/.

Pass --html report.html and you also get the same report as one self-contained HTML file, for the stakeholder who would rather receive an attachment. The file has its styling inline, contains no JavaScript, and makes no external requests. A print stylesheet turns it into a clean PDF. GitHub shows .html as source, so download it or open it locally. It is a second rendering of the same document, and a test asserts the two carry identical numbers.

🧭 Why this exists

Other evaluation tools hand you a spreadsheet of numbers, a web page, or a link into someone else's service. Nothing hands a non-engineer a one-page markdown verdict with the failures quoted, which is the document teams hand-write before every "is it safe to ship" meeting. This tool treats that document as the product. Hosted evaluation services, the ones you log into on someone else's servers, get acquired and shut down, and your quality bar should not depend on one. This tool is MIT licensed and runs locally, and its output is a text file, so nobody can deprecate it.

📋 The checks that matter

14 code checks and 5 judge checks. The set is fixed on purpose. There is no way to plug in your own checks and no menu of fifty metrics to pick from. The 5 judge checks return only a binary pass or fail from the local model. The full catalog, with what each check catches, is in CHECKS.md, generated from the same list of checks the code uses.

Accuracy is computed two ways: once by the code checks, once by the judge. The report shows both, side by side. When the two disagree on a case, that case lands in a "needs human review" section instead of being averaged away. The code checks decide the verdict. The judge is never the sole authority on a number.

Gates come from your test file's gates block, or from this tool's defaults when you omit it: accuracy at least 80%, hallucination at most 5%, and critical cases must pass. Every report's "Bars used" line says which of the two it applied. A critical case must pass every code check that applies to it, not only the correctness ones, so a correct answer that leaks a stack trace fails the run. In the demo the leaking case is q12, and the critical-cases gate is the second gate the demo fails.

There is one exception. If the judge scored badly on its own exam (explained below), a hallucination rate over the ceiling counts as a warning, not a gate failure. The report says so on the gate line. The exit code stays 0 and the quoted cases need a human.

A warning softens PASS to PASS WITH WARNINGS and leaves the exit code at 0. Warnings cover things such as the two accuracy routes disagreeing on a case, a judge call that failed, and a gate that needs the judge on a run with no judge. One more triggers a warning: a case that declares an expected answer but has no code check to verify it, on a no-judge run.

🔌 What your endpoint must return

By default the tool expects your bot to accept and return this, one request per question:

POST {endpoint}/ask          {"question": "..."}
  -> {"answer": "...",
      "contexts": [{"text": "...", "source": "doc.md"}, ...]}   # optional

contexts are the passages the bot says it used. Returning them unlocks the grounding checks and the check that says whether retrieval or generation broke. The source field on each passage unlocks the citation checks. Without contexts, those checks render as n/a with the reason instead of as passes. Citations are the one exception. Normally a missing field just skips its check and shows n/a. But if you told the tool which document to expect (the expected_sources field) and the bot cites nothing, the cites_expected_source check fails instead of skipping, because citing nothing is itself a wrong answer to "did it cite the right document".

The field names above are the code's actual defaults. If your bot's reply has a different shape, point the tool at the right fields with dot-paths, which are short addresses into the reply such as answer: choices.0.message.content. Many bot servers copy OpenAI's reply layout, and that one path works for all of them. If your bot returns no passages at all, set contexts: null in the test file and the grounding checks switch off. The bot can be written in any language, since the tool only ever sees HTTP. agent-report-card init writes a commented starter test file showing every supported field.

Test files are strictly validated. An unknown key, a duplicate case id, or two fields that contradict each other stops the tool before it sends a single request, with the line number, the case id, and a one-line fix. JSON is a subset of YAML, so --tests suite.json goes through the same loader with the same validation.

⚖ The judge takes the exam it grades

Every judged report names its judge, its temperature, and its prompt hash. Temperature 0 means the model picks its most likely answer every time, so repeated calls are as repeatable as the model allows. The prompt hash is a short fingerprint of the exact wording sent to the judge, so two runs can be compared knowing whether the judge was asked the same thing.

The judge also sits an exam of 30 pairs, run with agent-report-card judge-check. Each pair is an answer and the pass or fail label a human gave it. 15 answers are correct and 15 are wrong. Five of the wrong ones contain a number that is off by less than 1%, the kind of slip a judge is most likely to wave through. The exam is a separate command. Nothing runs it for you. Until you run it, every judged report says "Judge not calibrated" and tells you to trust the deterministic column first. Once you have, every later judged report embeds the score.

On the saved run, qwen3.6:27b scored 30/30: zero false passes, zero false fails, and 5/5 on the five near-miss numbers. A false pass is a wrong answer marked correct, the dangerous direction. A false fail is a correct answer marked wrong. The result is in reports/. Thirty pairs is enough to say "zero mistakes on these thirty", not enough to call the judge trustworthy in general. If the judge agrees with the answer key on fewer than 80% of the pairs (the bar this tool sets), the report's verdict line tells you to trust the code checks instead.

The report also breaks the exam down by category and prints agreement corrected for chance (Cohen's kappa, a standard statistic that scores random guessing as 0). One overall number hides the failure that matters. A judge that gets 28 of 30 right reads as 93% correct even when both misses are subtle-numeric pairs, and that judge is useless at exactly the job this tool exists for. The exam is balanced 15 to 15, so a judge answering at random scores about 50% raw and 0.00 corrected. Every rate carries its denominator and an interval, and a category with fewer than five pairs shows the tally and no percentage. The saved judged report shows all of this rendered, and CHANGELOG.md has the release detail.

One honest war story from building this: the first exam run used the judge's default thinking mode, where the model writes out its reasoning before it answers. 18 of 30 pairs agreed, and the other 12 calls came back with no usable verdict, per the captured output. The tool recorded each of those 12 as judge_error rather than guessing, and 60% agreement sat below the 80% bar. So judge calls now tell Ollama to skip the thinking step, and fall back to the plain request for models that do not support that setting. The rerun scored 30/30, per the saved calibration JSON. The judge's own report card caught the judge's own failure before it graded anything real.

🌏 Languages

Everything is UTF-8. The code checks match substrings and numbers after NFC normalization, a standard way of writing accented and composed characters so that two spellings of the same text compare equal. Thai is written without spaces between words, and the checks still work on it (the tool's own tests include Thai cases). One thing to know: a short expected string can match across two Thai words by accident, so pick expected strings long enough to be unambiguous. The tool spots when the bot refused to answer by matching phrases from a built-in English list, and you can add phrases in any language under a patterns block in the test file. For the judge, language support is whatever your local model supports. The calibration exam is English only, and every judged report that carries an exam result says so.

🚫 What this deliberately is not

No dashboard, no web UI, no hosted service, no cloud sync, no model-versus-prompt comparison matrices, no synthetic test generation (a model inventing your questions for you), no way to plug in your own checks, no red-teaming (attacking the bot on purpose to find exploits). Other tools have them, and the weight of all that is a large part of why this tool is small. The list is fixed so you can plan around it.

CI runs on the free GitHub machines and covers everything except live judge calls, since there is no Ollama there. The judged reports are produced locally and saved into the repo. A script, scripts/verify_readme_claims.py, fails CI when a number this README quotes drifts from a fresh run or from the saved file behind it. It guards the banner's accuracy and failure count, the judged hallucination fraction, the judge's exam score, and how many of the five near-miss numbers the judge caught.

🗺 Roadmap

Today (0.2.0) you can grade a RAG bot's endpoint against a YAML test file and get a markdown or HTML report, with a local judge that reports its own error rate broken down by category. Per-release detail lives in CHANGELOG.md. This section is only about what comes next.

v0.2.x: any judge you want to run

Other judges: local model servers besides Ollama (LM Studio and vLLM, for example), and paid online models for anyone whose machine cannot run a large local model. Your key, your environment, read from one variable you set and never from a flag. Local stays the default, always. This is catch-up work rather than a reason to switch, because other tools already judge with whatever model you like. What stays different is that local remains the default, and the report always names the judge and says whether anything left your machine.

Also in this line: comparing two judges. The groundwork shipped in 0.2.0, but no command uses it yet. When it lands, a comparison will say "too close to separate on 30 items" rather than ranking the two, because 30 pairs can show a judge is usable and cannot show one is two points better than another.

v0.3: regression gating

Right now a run tells you where you stand, not whether you slipped. Regression gating compares a run against a stored baseline, so CI can fail on "worse than last week" rather than only on an absolute floor. The report.scores.json file written next to the report already carries everything needed, and its layout is left unstable on purpose so regression gating can reshape it. Wanted for sovereign-rag, which needs a regression gate rather than a threshold.

When it is unblocked: agent-trace mode

Today the tool grades one question and one answer. A multi-step agent fails differently. Step four goes wrong and steps five through ten inherit it, so a single end-to-end score tells you nothing about where to look. Agent-trace mode reads a run log and scores each step. It shows how one step's failure spreads into the later ones, which is what agent-failure-lab measures. Waiting on that project to fix the layout of its log file, because building on a layout that still changes would only move the breakage here.

Small things, if someone asks

A completeness check for multi-part questions, and grading a bare model endpoint directly. Nobody is blocked on either, so they wait for a real request. The second one comes with a warning: a bare model returns no retrieved passages, so most of the checks would render n/a and the report would grade generation only.

If the report ever needs a server to read it, this project has failed.


Written by Satsawat Natakarnkitkul, author of Why Your AI Agent Will Fail. Companion article: "Why Your AI Agent Needs a Performance Review (Literally)" at satsawat.ai · Newsletter: AI in Practice

License: MIT

Release files for agent-report-card 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agent-report-card 0.2.1
File Size Uploaded
agent_report_card-0.2.1.tar.gz 75.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agent-report-card 0.2.1
File Interpreter ABI Platform
agent_report_card-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 150.4 kB

Release files / agent_report_card-0.2.1.tar.gz

Download URL agent_report_card-0.2.1.tar.gz
Size 75.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d5211898d9489a31d13d4ee93d5012ca805d7474adf8e91d87a5ba0d78a269ce
BLAKE2b-256 checksum
How to use checksums
ce5ae6b3bd2d30ecd207d08b8e6ac4296f9b4394882ecbcb7ec3a7f0e82a9dbb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / agent_report_card-0.2.1-py3-none-any.whl

Download URL agent_report_card-0.2.1-py3-none-any.whl
Size 74.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
42046caec64d35f6affde316f156f4e736a5847bb8a9576dcf634a06075b69fb
BLAKE2b-256 checksum
How to use checksums
f14ac06958973da1de3db36dcd0a837939f2ce829de9947f1cbd8e558d9c6898
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page