The support bot suite, end to end
Everything in the course, in one directory, gating one build. This is the suite promised in lesson 0.
Four files. The bot, the judge, the questions and the config. Nothing here is new: every piece was built alone in an earlier lesson and the list below says which.
The pieces
| File | What it is | Lesson |
|---|---|---|
| rag_bot.py | The application, answering from documents | 5 and 22 |
| pretend_promptfoo.py | The judge, so the rubrics run with no key | 13, 14 and 22 |
| house_style.py | A rule that is a function | 10 |
| tests.csv | The questions, and the checks that need no model | 7 |
| promptfooconfig.yaml | What runs against what | 2, 11 and 12 |
The questions
query,__expected
Where is order A17?,contains: 3 March
How long does a refund take?,contains: five working days
Where is order B99?,contains: could not findThree rows, each with a deterministic check. The variable is query rather than question, because the context assertions from lesson 22 require that name.
The config
description: Support bot suite
providers:
- file://rag_bot.py
prompts:
- "{{query}}"
defaultTest:
transform: JSON.parse(output).answer
options:
provider: file://pretend_promptfoo.py
assert:
- type: python
value: file://house_style.py
metric: Style
tests: file://tests.csvdefaultTest does three jobs at once here. It unwraps the bot's JSON so every check sees the answer, it points the model-graded checks at the local judge, and it adds the house style rule to every test without repeating it three times.
Running it
promptfoo evalThree questions, each checked twice: once for the fact it has to contain and once against the house style. All three pass, and the run took no key and no money.
The cells show the raw JSON the bot returned, because the table prints what the provider said. transform runs on the way into the assertions, so the checks saw the sentence rather than the wrapper.
The failure that matters
A suite is only worth what it catches. Change the bot to answer confidently when it has found nothing, which is the failure a retrieval application actually has.
promptfoo evalOne row goes red, and it names two separate problems. The answer for B99 no longer says it could not find anything, so the check from the CSV fails, and it uses a word the shop does not use, so the house style rule fails as well. Neither of those is a crash, and neither would have been noticed by a test that was not looking for it.
Where to go next
Add a real model with the two lines from lesson 21 and keep this one as the fast gate. Add the workflow from lesson 20. Then hand tests.csv to the person who answers these questions for a living and let them add twenty rows.
What this course left out
Promptfoo publishes 385 documentation pages. This course taught the part you need to test an application; here is what is real and not here.
| Area | What it is |
|---|---|
| Red teaming | Seventy plugins and thirty strategies for generated attacks. Lesson 23 |
| The provider catalogue | Around ninety providers: every model API, HTTP, WebSocket, browser, MCP |
| Assertions not covered | moderation, classifier, similar, select-best, max-score, guardrails, ruby |
| Scenarios | Grouping tests so one set of data runs through several configurations |
| Datasets from elsewhere | Google Sheets, HuggingFace datasets, generated test data |
| The node package | Running evals from JavaScript instead of the command line |
| Tracing | OpenTelemetry spans from inside a provider, for finding where time went |
| Self-hosting and sharing | Running the viewer as a service for a team |
| ModelAudit | Static scanning of model files for unsafe content, a separate tool |
| Enterprise | Teams, roles, audit logs, remediation reports |
- Add a fourth row to
tests.csvfor a question your own handbook should answer. - Add an
llm-rubrictodefaultTestso every answer is graded for staying on topic. - Put the workflow file from lesson 20 in
.github/workflows/and open a pull request that breaks the bot.
This is what real progress feels like.