roadtest 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roadtest-0.1.0/.gitignore +15 -0
- roadtest-0.1.0/LICENSE +21 -0
- roadtest-0.1.0/PKG-INFO +331 -0
- roadtest-0.1.0/README.md +302 -0
- roadtest-0.1.0/examples/command/README.md +30 -0
- roadtest-0.1.0/examples/http-json/README.md +32 -0
- roadtest-0.1.0/examples/http-streaming/README.md +33 -0
- roadtest-0.1.0/examples/openai-compatible/README.md +34 -0
- roadtest-0.1.0/examples/python-function/README.md +29 -0
- roadtest-0.1.0/pyproject.toml +56 -0
- roadtest-0.1.0/skills/roadtest/SKILL.md +65 -0
- roadtest-0.1.0/skills/roadtest/references/ci.md +114 -0
- roadtest-0.1.0/skills/roadtest/references/scenarios.md +188 -0
- roadtest-0.1.0/skills/roadtest/references/setup.md +237 -0
- roadtest-0.1.0/skills/roadtest/references/triage.md +65 -0
- roadtest-0.1.0/src/roadtest/__init__.py +8 -0
- roadtest-0.1.0/src/roadtest/__main__.py +3 -0
- roadtest-0.1.0/src/roadtest/agents.py +535 -0
- roadtest-0.1.0/src/roadtest/checks.py +130 -0
- roadtest-0.1.0/src/roadtest/cli.py +523 -0
- roadtest-0.1.0/src/roadtest/config.py +246 -0
- roadtest-0.1.0/src/roadtest/demo/__init__.py +1 -0
- roadtest-0.1.0/src/roadtest/demo/agent.py +147 -0
- roadtest-0.1.0/src/roadtest/demo/scenarios/support.yaml +126 -0
- roadtest-0.1.0/src/roadtest/errors.py +27 -0
- roadtest-0.1.0/src/roadtest/judge.py +113 -0
- roadtest-0.1.0/src/roadtest/llm.py +167 -0
- roadtest-0.1.0/src/roadtest/report.py +204 -0
- roadtest-0.1.0/src/roadtest/results.py +151 -0
- roadtest-0.1.0/src/roadtest/runner.py +248 -0
- roadtest-0.1.0/src/roadtest/scaffold.py +149 -0
- roadtest-0.1.0/src/roadtest/scenarios.py +277 -0
- roadtest-0.1.0/src/roadtest/simulate.py +52 -0
- roadtest-0.1.0/src/roadtest/template.py +122 -0
- roadtest-0.1.0/src/roadtest/templates/report.html +620 -0
- roadtest-0.1.0/tests/conftest.py +137 -0
- roadtest-0.1.0/tests/test_adapters.py +134 -0
- roadtest-0.1.0/tests/test_cli.py +173 -0
- roadtest-0.1.0/tests/test_config.py +130 -0
- roadtest-0.1.0/tests/test_demo_end_to_end.py +91 -0
- roadtest-0.1.0/tests/test_live_models.py +100 -0
- roadtest-0.1.0/tests/test_prompts.py +67 -0
- roadtest-0.1.0/tests/test_results.py +145 -0
- roadtest-0.1.0/tests/test_runner.py +143 -0
- roadtest-0.1.0/tests/test_scenarios.py +152 -0
roadtest-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fenil Ramoliya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
roadtest-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: roadtest
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Road-test your AI agent before it ships: scenario-based evals for any agent, written by your coding agent.
|
|
5
|
+
Project-URL: Homepage, https://github.com/fenil210/roadtest
|
|
6
|
+
Project-URL: Documentation, https://github.com/fenil210/roadtest#readme
|
|
7
|
+
Project-URL: Issues, https://github.com/fenil210/roadtest/issues
|
|
8
|
+
Author: Fenil Ramoliya
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent-skills,agents,ai,evals,evaluation,llm,llm-judge,testing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: pydantic>=2.6
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: rich>=13.7
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="docs/assets/logo.svg" width="84" alt="">
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<h1 align="center">roadtest</h1>
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<b>Road-test your AI agent before it ships.</b><br>
|
|
38
|
+
Open-source evals for any agent. Your coding agent writes the scenarios.<br>
|
|
39
|
+
roadtest runs them, grades them, and stops a bad release.
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/v/roadtest?color=34D399" alt="PyPI"></a>
|
|
44
|
+
<a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/pyversions/roadtest" alt="Python versions"></a>
|
|
45
|
+
<a href="https://github.com/fenil210/roadtest/actions/workflows/ci.yml"><img src="https://github.com/fenil210/roadtest/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
46
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT licence"></a>
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<img src="docs/assets/demo.gif" width="820" alt="roadtest demo: twelve scenarios run against an agent, two planted bugs are caught, and the gate fails">
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<p align="center">
|
|
54
|
+
<a href="docs/assets/launch.mp4"><img src="docs/assets/launch-poster.png" width="420" alt="Watch the launch video"></a><br>
|
|
55
|
+
<sub><a href="docs/assets/launch.mp4">Watch the 55-second tour</a> (sound on)</sub>
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
## Why roadtest
|
|
59
|
+
|
|
60
|
+
- **It works with the agent you already have.** Point it at an HTTP endpoint, a Python function or
|
|
61
|
+
a command. There is no SDK to adopt, no code to wrap and no tracing to install. LangGraph, CrewAI,
|
|
62
|
+
the OpenAI Agents SDK, your own loop: roadtest tests it from the outside, the way your users do.
|
|
63
|
+
- **Your coding agent does the setup.** `roadtest init` installs a skill that teaches Claude Code,
|
|
64
|
+
Codex, Cursor and Copilot how to connect your agent and write scenarios for it. You say
|
|
65
|
+
"write 20 scenarios" and review a YAML file.
|
|
66
|
+
- **It answers one question: can this ship?** Scenarios are either `gate` (must pass) or `watch`
|
|
67
|
+
(tracked). The command exits non-zero when a gate scenario fails, so it drops into CI unchanged.
|
|
68
|
+
|
|
69
|
+
## Try it in 30 seconds
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install roadtest
|
|
73
|
+
roadtest demo
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The demo runs twelve scenarios against a small bundled agent that has two bugs planted in it, and
|
|
77
|
+
shows you roadtest catching them. It needs no setup and no API key.
|
|
78
|
+
|
|
79
|
+
## Use it on your agent
|
|
80
|
+
|
|
81
|
+
### 1. Add roadtest to your project
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install roadtest
|
|
85
|
+
roadtest init
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This creates `evals/roadtest.yaml`, a starter scenario file, and the roadtest skill for your coding
|
|
89
|
+
agent.
|
|
90
|
+
|
|
91
|
+
### 2. Let your coding agent set it up
|
|
92
|
+
|
|
93
|
+
Open Claude Code, Codex, Cursor or Copilot in the project and say:
|
|
94
|
+
|
|
95
|
+
> Set up roadtest for my agent, then write 20 scenarios and run them.
|
|
96
|
+
|
|
97
|
+
The agent reads your code, fills in `evals/roadtest.yaml`, checks the connection with
|
|
98
|
+
`roadtest doctor`, writes scenarios that cover your agent's real behaviour, runs them, and tells
|
|
99
|
+
you which failures look like real bugs.
|
|
100
|
+
|
|
101
|
+
Prefer to do it by hand? It is one small file. See [connecting an agent](#connecting-an-agent).
|
|
102
|
+
|
|
103
|
+
### 3. Run it
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
roadtest run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
✓ order-status-in-transit 0.6s
|
|
111
|
+
✓ refund-within-window 0.8s
|
|
112
|
+
✗ refund-outside-window 0.7s
|
|
113
|
+
✓ prompt-injection 0.5s
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
gate 9/10 passed 90% 1 failed
|
|
117
|
+
watch 0/1 passed 0% 1 failed
|
|
118
|
+
|
|
119
|
+
✗ refund-outside-window A refund outside the 30-day window is declined
|
|
120
|
+
does not call start_refund: called: lookup_order, start_refund
|
|
121
|
+
judge: declines-the-refund: The reply says "I've started a refund of €35.00".
|
|
122
|
+
It approves the refund and never mentions the 30-day window.
|
|
123
|
+
|
|
124
|
+
✗ Gate failed 1 of 10 gate scenarios did not pass
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Every run also writes an HTML report with each conversation, each tool call and the judge's
|
|
128
|
+
reasoning for every verdict:
|
|
129
|
+
|
|
130
|
+
<p align="center">
|
|
131
|
+
<img src="docs/assets/report.png" width="820" alt="The roadtest HTML report: a failed gate, the scenario list, and one expanded scenario with its conversation and checks">
|
|
132
|
+
</p>
|
|
133
|
+
|
|
134
|
+
## What a scenario looks like
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
# evals/scenarios/refunds.yaml
|
|
138
|
+
scenarios:
|
|
139
|
+
- id: refund-outside-window
|
|
140
|
+
title: A refund outside the 30-day window is declined
|
|
141
|
+
why: refunding outside the policy window costs money on every late request
|
|
142
|
+
user: Please refund order A-1002.
|
|
143
|
+
expect:
|
|
144
|
+
tools_called: [lookup_order] # code checks: free, instant, exact
|
|
145
|
+
tools_not_called: [start_refund]
|
|
146
|
+
contains_any: ["30 days", "30-day"]
|
|
147
|
+
rubric: # graded by a judge model, pass or fail
|
|
148
|
+
- Declines the refund and explains that the order is outside the 30-day window.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Conversations with several turns, and users simulated by a model, work the same way:
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
- id: refund-remembers-order
|
|
155
|
+
turns:
|
|
156
|
+
- user: Hi, I have a question about order A-1001.
|
|
157
|
+
- user: Can I get a refund for it?
|
|
158
|
+
expect:
|
|
159
|
+
tools_called: [start_refund]
|
|
160
|
+
|
|
161
|
+
- id: impatient-customer-refund
|
|
162
|
+
simulate:
|
|
163
|
+
persona: An impatient customer who writes very short messages.
|
|
164
|
+
goal: Get a refund for order A-1001 because the helmet does not fit.
|
|
165
|
+
expect:
|
|
166
|
+
rubric:
|
|
167
|
+
- The agent asked for the order number before it started the refund.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| `expect` key | Passes when |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `contains`, `contains_any`, `not_contains` | the reply has all, any or none of the strings |
|
|
173
|
+
| `matches` | every regular expression matches the reply |
|
|
174
|
+
| `tools_called`, `tools_any`, `tools_not_called` | the agent called all, any or none of the tools |
|
|
175
|
+
| `max_seconds` | the reply arrived in time |
|
|
176
|
+
| `rubric` | a judge model agrees with every line |
|
|
177
|
+
| `checks` | your own Python function returns true |
|
|
178
|
+
|
|
179
|
+
The full format is in the [scenarios guide](skills/roadtest/references/scenarios.md).
|
|
180
|
+
|
|
181
|
+
## Connecting an agent
|
|
182
|
+
|
|
183
|
+
`evals/roadtest.yaml` tells roadtest how to reach your agent. For an HTTP endpoint:
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
agent:
|
|
187
|
+
url: http://localhost:8000/chat
|
|
188
|
+
headers:
|
|
189
|
+
Authorization: "Bearer ${AGENT_API_KEY}" # from your shell or a .env file
|
|
190
|
+
body:
|
|
191
|
+
message: "{{message}}"
|
|
192
|
+
session_id: "{{session_id}}" # one conversation per scenario
|
|
193
|
+
response:
|
|
194
|
+
text: reply # where the answer is in the JSON
|
|
195
|
+
tool_calls: "tool_calls[*].name" # optional: lets scenarios check tools
|
|
196
|
+
|
|
197
|
+
targets: # the same agent elsewhere
|
|
198
|
+
staging:
|
|
199
|
+
url: https://staging.example.com/chat
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Then check it:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
roadtest probe "Where is my order?" # shows the request, the raw reply and what roadtest read
|
|
206
|
+
roadtest doctor # config, scenarios, agent and models in one check
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
| Your agent is | Use | |
|
|
210
|
+
| --- | --- | --- |
|
|
211
|
+
| an HTTP endpoint returning JSON or text | `type: http` | [example](examples/http-json) |
|
|
212
|
+
| a streaming endpoint (SSE or JSON lines) | `type: http` with `format: sse` | [example](examples/http-streaming) |
|
|
213
|
+
| an OpenAI-compatible endpoint | `type: http` with `messages: "{{messages}}"` | [example](examples/openai-compatible) |
|
|
214
|
+
| a Python function or object, or any other protocol | `type: python` | [example](examples/python-function) |
|
|
215
|
+
| a program in another language | `type: command` | [example](examples/command) |
|
|
216
|
+
|
|
217
|
+
Sessions, streaming, server-issued conversation ids and every option are in the
|
|
218
|
+
[setup guide](skills/roadtest/references/setup.md).
|
|
219
|
+
|
|
220
|
+
## Commands
|
|
221
|
+
|
|
222
|
+
| Command | What it does |
|
|
223
|
+
| --- | --- |
|
|
224
|
+
| `roadtest init` | Add roadtest to a project: config, starter scenarios, coding-agent skill |
|
|
225
|
+
| `roadtest demo` | A full run against the bundled demo agent |
|
|
226
|
+
| `roadtest doctor` | Check that the config, the agent and the models all work |
|
|
227
|
+
| `roadtest probe "hi"` | Send one message and show exactly what came back |
|
|
228
|
+
| `roadtest validate` | Check the scenario files without running them |
|
|
229
|
+
| `roadtest run` | Run scenarios. Narrow with ids, `--tag`, `--tier`; add `--target`, `--repeat` |
|
|
230
|
+
| `roadtest report` | Show a past run again, or open its HTML report |
|
|
231
|
+
| `roadtest compare` | What changed between two runs: regressions, fixes, new scenarios |
|
|
232
|
+
| `roadtest baseline` | Pin a run as the baseline that `compare` uses |
|
|
233
|
+
| `roadtest guide` | Print a guide, for you or for your coding agent |
|
|
234
|
+
|
|
235
|
+
## Before and after every deploy
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
# .github/workflows/evals.yml
|
|
239
|
+
- run: pip install roadtest
|
|
240
|
+
- run: roadtest run # on pull requests: exits 1 if a gate scenario fails
|
|
241
|
+
env:
|
|
242
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
roadtest run --tag core --target prod # after a deploy: a short check that production behaves
|
|
247
|
+
roadtest run --repeat 3 # nightly: a scenario passes only if all three runs pass
|
|
248
|
+
roadtest compare # what regressed since the baseline
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The pass and fail summary lands on the GitHub job summary page automatically. Complete workflows are
|
|
252
|
+
in the [CI guide](skills/roadtest/references/ci.md).
|
|
253
|
+
|
|
254
|
+
## Coding agent support
|
|
255
|
+
|
|
256
|
+
`roadtest init` installs the skill into the project. You can also install it on its own.
|
|
257
|
+
|
|
258
|
+
| Agent | How the skill gets there |
|
|
259
|
+
| --- | --- |
|
|
260
|
+
| Claude Code | `roadtest init` writes `.claude/skills/roadtest/`. Or as a plugin: `/plugin marketplace add fenil210/roadtest`, then `/plugin install roadtest@roadtest` |
|
|
261
|
+
| Codex, Cursor, GitHub Copilot, OpenCode, Amp | `roadtest init` writes `.agents/skills/roadtest/`, which they all read |
|
|
262
|
+
| Any of them | `npx skills add fenil210/roadtest` |
|
|
263
|
+
| Anything else that can run a shell command | `roadtest guide setup`, `roadtest guide scenarios`, `roadtest guide triage`, `roadtest guide ci` print the same instructions |
|
|
264
|
+
|
|
265
|
+
The skill covers four jobs: connecting your agent, writing scenarios, explaining failures and
|
|
266
|
+
adding roadtest to CI.
|
|
267
|
+
|
|
268
|
+
## Models
|
|
269
|
+
|
|
270
|
+
roadtest uses two models, both through your OpenAI API key:
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
models:
|
|
274
|
+
judge: gpt-6-luna # decides pass or fail for each rubric line
|
|
275
|
+
generator: gpt-6.1-terra # plays the user in simulated conversations
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Set `OPENAI_API_KEY` in your shell or in a `.env` file. Change either model to any id your key can
|
|
279
|
+
use. `base_url` accepts any server that speaks the OpenAI Chat Completions API. Code checks need no
|
|
280
|
+
model at all: `roadtest run --no-judge` runs with no key.
|
|
281
|
+
|
|
282
|
+
## How it works
|
|
283
|
+
|
|
284
|
+
```text
|
|
285
|
+
scenario.yaml ──▶ adapter ──▶ your agent (HTTP, Python function or command)
|
|
286
|
+
│ reply + tool calls
|
|
287
|
+
▼
|
|
288
|
+
code checks judge model
|
|
289
|
+
(text, tools, time) (one rubric line → pass or fail, with reasoning)
|
|
290
|
+
│
|
|
291
|
+
▼
|
|
292
|
+
gate: every must-pass scenario passed? ──▶ exit code, report, run.json
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
A few choices worth knowing:
|
|
296
|
+
|
|
297
|
+
- **Pass or fail, never a score.** Each rubric line gets a yes or a no with a written reason. There
|
|
298
|
+
are no 1-to-5 scales to argue about.
|
|
299
|
+
- **A broken judge is not a failed agent.** If the agent is unreachable or the judge call fails, the
|
|
300
|
+
scenario is an `error`, reported apart from real failures.
|
|
301
|
+
- **Repeats are strict.** With `--repeat 3` a scenario passes only if all three runs pass, because
|
|
302
|
+
an agent that fails one time in three has a bug.
|
|
303
|
+
- **Everything is a file.** Scenarios, config and the baseline live in your repo and go through
|
|
304
|
+
code review like the rest of your code.
|
|
305
|
+
|
|
306
|
+
## FAQ
|
|
307
|
+
|
|
308
|
+
**Do I need to change my agent's code?**
|
|
309
|
+
No. If it has an endpoint, roadtest calls it. To check which tools were called, the reply has to
|
|
310
|
+
include them; the [setup guide](skills/roadtest/references/setup.md#tool-calls) shows the options.
|
|
311
|
+
|
|
312
|
+
**How many scenarios do I need?**
|
|
313
|
+
Start with about 20 that cover what the agent is for, then add one each time you find a bug.
|
|
314
|
+
|
|
315
|
+
**What does a run cost?**
|
|
316
|
+
Your agent's own cost per scenario, plus one small judge call per rubric line. Code checks are free.
|
|
317
|
+
|
|
318
|
+
**My agent is not deterministic. Will results flip?**
|
|
319
|
+
Sometimes, and that is information. Use `--repeat` to measure it, and move anything exact into code
|
|
320
|
+
checks.
|
|
321
|
+
|
|
322
|
+
**Can I use it without a coding agent?**
|
|
323
|
+
Yes. Scenarios are plain YAML and the [guides](skills/roadtest/references) are written for people too.
|
|
324
|
+
|
|
325
|
+
## Contributing
|
|
326
|
+
|
|
327
|
+
Bug reports, ideas and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
328
|
+
|
|
329
|
+
## Licence
|
|
330
|
+
|
|
331
|
+
[MIT](LICENSE)
|
roadtest-0.1.0/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/logo.svg" width="84" alt="">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">roadtest</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Road-test your AI agent before it ships.</b><br>
|
|
9
|
+
Open-source evals for any agent. Your coding agent writes the scenarios.<br>
|
|
10
|
+
roadtest runs them, grades them, and stops a bad release.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/v/roadtest?color=34D399" alt="PyPI"></a>
|
|
15
|
+
<a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/pyversions/roadtest" alt="Python versions"></a>
|
|
16
|
+
<a href="https://github.com/fenil210/roadtest/actions/workflows/ci.yml"><img src="https://github.com/fenil210/roadtest/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
17
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT licence"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="docs/assets/demo.gif" width="820" alt="roadtest demo: twelve scenarios run against an agent, two planted bugs are caught, and the gate fails">
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<a href="docs/assets/launch.mp4"><img src="docs/assets/launch-poster.png" width="420" alt="Watch the launch video"></a><br>
|
|
26
|
+
<sub><a href="docs/assets/launch.mp4">Watch the 55-second tour</a> (sound on)</sub>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
## Why roadtest
|
|
30
|
+
|
|
31
|
+
- **It works with the agent you already have.** Point it at an HTTP endpoint, a Python function or
|
|
32
|
+
a command. There is no SDK to adopt, no code to wrap and no tracing to install. LangGraph, CrewAI,
|
|
33
|
+
the OpenAI Agents SDK, your own loop: roadtest tests it from the outside, the way your users do.
|
|
34
|
+
- **Your coding agent does the setup.** `roadtest init` installs a skill that teaches Claude Code,
|
|
35
|
+
Codex, Cursor and Copilot how to connect your agent and write scenarios for it. You say
|
|
36
|
+
"write 20 scenarios" and review a YAML file.
|
|
37
|
+
- **It answers one question: can this ship?** Scenarios are either `gate` (must pass) or `watch`
|
|
38
|
+
(tracked). The command exits non-zero when a gate scenario fails, so it drops into CI unchanged.
|
|
39
|
+
|
|
40
|
+
## Try it in 30 seconds
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install roadtest
|
|
44
|
+
roadtest demo
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The demo runs twelve scenarios against a small bundled agent that has two bugs planted in it, and
|
|
48
|
+
shows you roadtest catching them. It needs no setup and no API key.
|
|
49
|
+
|
|
50
|
+
## Use it on your agent
|
|
51
|
+
|
|
52
|
+
### 1. Add roadtest to your project
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install roadtest
|
|
56
|
+
roadtest init
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This creates `evals/roadtest.yaml`, a starter scenario file, and the roadtest skill for your coding
|
|
60
|
+
agent.
|
|
61
|
+
|
|
62
|
+
### 2. Let your coding agent set it up
|
|
63
|
+
|
|
64
|
+
Open Claude Code, Codex, Cursor or Copilot in the project and say:
|
|
65
|
+
|
|
66
|
+
> Set up roadtest for my agent, then write 20 scenarios and run them.
|
|
67
|
+
|
|
68
|
+
The agent reads your code, fills in `evals/roadtest.yaml`, checks the connection with
|
|
69
|
+
`roadtest doctor`, writes scenarios that cover your agent's real behaviour, runs them, and tells
|
|
70
|
+
you which failures look like real bugs.
|
|
71
|
+
|
|
72
|
+
Prefer to do it by hand? It is one small file. See [connecting an agent](#connecting-an-agent).
|
|
73
|
+
|
|
74
|
+
### 3. Run it
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
roadtest run
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
✓ order-status-in-transit 0.6s
|
|
82
|
+
✓ refund-within-window 0.8s
|
|
83
|
+
✗ refund-outside-window 0.7s
|
|
84
|
+
✓ prompt-injection 0.5s
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
gate 9/10 passed 90% 1 failed
|
|
88
|
+
watch 0/1 passed 0% 1 failed
|
|
89
|
+
|
|
90
|
+
✗ refund-outside-window A refund outside the 30-day window is declined
|
|
91
|
+
does not call start_refund: called: lookup_order, start_refund
|
|
92
|
+
judge: declines-the-refund: The reply says "I've started a refund of €35.00".
|
|
93
|
+
It approves the refund and never mentions the 30-day window.
|
|
94
|
+
|
|
95
|
+
✗ Gate failed 1 of 10 gate scenarios did not pass
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Every run also writes an HTML report with each conversation, each tool call and the judge's
|
|
99
|
+
reasoning for every verdict:
|
|
100
|
+
|
|
101
|
+
<p align="center">
|
|
102
|
+
<img src="docs/assets/report.png" width="820" alt="The roadtest HTML report: a failed gate, the scenario list, and one expanded scenario with its conversation and checks">
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
## What a scenario looks like
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
# evals/scenarios/refunds.yaml
|
|
109
|
+
scenarios:
|
|
110
|
+
- id: refund-outside-window
|
|
111
|
+
title: A refund outside the 30-day window is declined
|
|
112
|
+
why: refunding outside the policy window costs money on every late request
|
|
113
|
+
user: Please refund order A-1002.
|
|
114
|
+
expect:
|
|
115
|
+
tools_called: [lookup_order] # code checks: free, instant, exact
|
|
116
|
+
tools_not_called: [start_refund]
|
|
117
|
+
contains_any: ["30 days", "30-day"]
|
|
118
|
+
rubric: # graded by a judge model, pass or fail
|
|
119
|
+
- Declines the refund and explains that the order is outside the 30-day window.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Conversations with several turns, and users simulated by a model, work the same way:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
- id: refund-remembers-order
|
|
126
|
+
turns:
|
|
127
|
+
- user: Hi, I have a question about order A-1001.
|
|
128
|
+
- user: Can I get a refund for it?
|
|
129
|
+
expect:
|
|
130
|
+
tools_called: [start_refund]
|
|
131
|
+
|
|
132
|
+
- id: impatient-customer-refund
|
|
133
|
+
simulate:
|
|
134
|
+
persona: An impatient customer who writes very short messages.
|
|
135
|
+
goal: Get a refund for order A-1001 because the helmet does not fit.
|
|
136
|
+
expect:
|
|
137
|
+
rubric:
|
|
138
|
+
- The agent asked for the order number before it started the refund.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| `expect` key | Passes when |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| `contains`, `contains_any`, `not_contains` | the reply has all, any or none of the strings |
|
|
144
|
+
| `matches` | every regular expression matches the reply |
|
|
145
|
+
| `tools_called`, `tools_any`, `tools_not_called` | the agent called all, any or none of the tools |
|
|
146
|
+
| `max_seconds` | the reply arrived in time |
|
|
147
|
+
| `rubric` | a judge model agrees with every line |
|
|
148
|
+
| `checks` | your own Python function returns true |
|
|
149
|
+
|
|
150
|
+
The full format is in the [scenarios guide](skills/roadtest/references/scenarios.md).
|
|
151
|
+
|
|
152
|
+
## Connecting an agent
|
|
153
|
+
|
|
154
|
+
`evals/roadtest.yaml` tells roadtest how to reach your agent. For an HTTP endpoint:
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
agent:
|
|
158
|
+
url: http://localhost:8000/chat
|
|
159
|
+
headers:
|
|
160
|
+
Authorization: "Bearer ${AGENT_API_KEY}" # from your shell or a .env file
|
|
161
|
+
body:
|
|
162
|
+
message: "{{message}}"
|
|
163
|
+
session_id: "{{session_id}}" # one conversation per scenario
|
|
164
|
+
response:
|
|
165
|
+
text: reply # where the answer is in the JSON
|
|
166
|
+
tool_calls: "tool_calls[*].name" # optional: lets scenarios check tools
|
|
167
|
+
|
|
168
|
+
targets: # the same agent elsewhere
|
|
169
|
+
staging:
|
|
170
|
+
url: https://staging.example.com/chat
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then check it:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
roadtest probe "Where is my order?" # shows the request, the raw reply and what roadtest read
|
|
177
|
+
roadtest doctor # config, scenarios, agent and models in one check
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
| Your agent is | Use | |
|
|
181
|
+
| --- | --- | --- |
|
|
182
|
+
| an HTTP endpoint returning JSON or text | `type: http` | [example](examples/http-json) |
|
|
183
|
+
| a streaming endpoint (SSE or JSON lines) | `type: http` with `format: sse` | [example](examples/http-streaming) |
|
|
184
|
+
| an OpenAI-compatible endpoint | `type: http` with `messages: "{{messages}}"` | [example](examples/openai-compatible) |
|
|
185
|
+
| a Python function or object, or any other protocol | `type: python` | [example](examples/python-function) |
|
|
186
|
+
| a program in another language | `type: command` | [example](examples/command) |
|
|
187
|
+
|
|
188
|
+
Sessions, streaming, server-issued conversation ids and every option are in the
|
|
189
|
+
[setup guide](skills/roadtest/references/setup.md).
|
|
190
|
+
|
|
191
|
+
## Commands
|
|
192
|
+
|
|
193
|
+
| Command | What it does |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| `roadtest init` | Add roadtest to a project: config, starter scenarios, coding-agent skill |
|
|
196
|
+
| `roadtest demo` | A full run against the bundled demo agent |
|
|
197
|
+
| `roadtest doctor` | Check that the config, the agent and the models all work |
|
|
198
|
+
| `roadtest probe "hi"` | Send one message and show exactly what came back |
|
|
199
|
+
| `roadtest validate` | Check the scenario files without running them |
|
|
200
|
+
| `roadtest run` | Run scenarios. Narrow with ids, `--tag`, `--tier`; add `--target`, `--repeat` |
|
|
201
|
+
| `roadtest report` | Show a past run again, or open its HTML report |
|
|
202
|
+
| `roadtest compare` | What changed between two runs: regressions, fixes, new scenarios |
|
|
203
|
+
| `roadtest baseline` | Pin a run as the baseline that `compare` uses |
|
|
204
|
+
| `roadtest guide` | Print a guide, for you or for your coding agent |
|
|
205
|
+
|
|
206
|
+
## Before and after every deploy
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
# .github/workflows/evals.yml
|
|
210
|
+
- run: pip install roadtest
|
|
211
|
+
- run: roadtest run # on pull requests: exits 1 if a gate scenario fails
|
|
212
|
+
env:
|
|
213
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
roadtest run --tag core --target prod # after a deploy: a short check that production behaves
|
|
218
|
+
roadtest run --repeat 3 # nightly: a scenario passes only if all three runs pass
|
|
219
|
+
roadtest compare # what regressed since the baseline
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The pass and fail summary lands on the GitHub job summary page automatically. Complete workflows are
|
|
223
|
+
in the [CI guide](skills/roadtest/references/ci.md).
|
|
224
|
+
|
|
225
|
+
## Coding agent support
|
|
226
|
+
|
|
227
|
+
`roadtest init` installs the skill into the project. You can also install it on its own.
|
|
228
|
+
|
|
229
|
+
| Agent | How the skill gets there |
|
|
230
|
+
| --- | --- |
|
|
231
|
+
| Claude Code | `roadtest init` writes `.claude/skills/roadtest/`. Or as a plugin: `/plugin marketplace add fenil210/roadtest`, then `/plugin install roadtest@roadtest` |
|
|
232
|
+
| Codex, Cursor, GitHub Copilot, OpenCode, Amp | `roadtest init` writes `.agents/skills/roadtest/`, which they all read |
|
|
233
|
+
| Any of them | `npx skills add fenil210/roadtest` |
|
|
234
|
+
| Anything else that can run a shell command | `roadtest guide setup`, `roadtest guide scenarios`, `roadtest guide triage`, `roadtest guide ci` print the same instructions |
|
|
235
|
+
|
|
236
|
+
The skill covers four jobs: connecting your agent, writing scenarios, explaining failures and
|
|
237
|
+
adding roadtest to CI.
|
|
238
|
+
|
|
239
|
+
## Models
|
|
240
|
+
|
|
241
|
+
roadtest uses two models, both through your OpenAI API key:
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
models:
|
|
245
|
+
judge: gpt-6-luna # decides pass or fail for each rubric line
|
|
246
|
+
generator: gpt-6.1-terra # plays the user in simulated conversations
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Set `OPENAI_API_KEY` in your shell or in a `.env` file. Change either model to any id your key can
|
|
250
|
+
use. `base_url` accepts any server that speaks the OpenAI Chat Completions API. Code checks need no
|
|
251
|
+
model at all: `roadtest run --no-judge` runs with no key.
|
|
252
|
+
|
|
253
|
+
## How it works
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
scenario.yaml ──▶ adapter ──▶ your agent (HTTP, Python function or command)
|
|
257
|
+
│ reply + tool calls
|
|
258
|
+
▼
|
|
259
|
+
code checks judge model
|
|
260
|
+
(text, tools, time) (one rubric line → pass or fail, with reasoning)
|
|
261
|
+
│
|
|
262
|
+
▼
|
|
263
|
+
gate: every must-pass scenario passed? ──▶ exit code, report, run.json
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
A few choices worth knowing:
|
|
267
|
+
|
|
268
|
+
- **Pass or fail, never a score.** Each rubric line gets a yes or a no with a written reason. There
|
|
269
|
+
are no 1-to-5 scales to argue about.
|
|
270
|
+
- **A broken judge is not a failed agent.** If the agent is unreachable or the judge call fails, the
|
|
271
|
+
scenario is an `error`, reported apart from real failures.
|
|
272
|
+
- **Repeats are strict.** With `--repeat 3` a scenario passes only if all three runs pass, because
|
|
273
|
+
an agent that fails one time in three has a bug.
|
|
274
|
+
- **Everything is a file.** Scenarios, config and the baseline live in your repo and go through
|
|
275
|
+
code review like the rest of your code.
|
|
276
|
+
|
|
277
|
+
## FAQ
|
|
278
|
+
|
|
279
|
+
**Do I need to change my agent's code?**
|
|
280
|
+
No. If it has an endpoint, roadtest calls it. To check which tools were called, the reply has to
|
|
281
|
+
include them; the [setup guide](skills/roadtest/references/setup.md#tool-calls) shows the options.
|
|
282
|
+
|
|
283
|
+
**How many scenarios do I need?**
|
|
284
|
+
Start with about 20 that cover what the agent is for, then add one each time you find a bug.
|
|
285
|
+
|
|
286
|
+
**What does a run cost?**
|
|
287
|
+
Your agent's own cost per scenario, plus one small judge call per rubric line. Code checks are free.
|
|
288
|
+
|
|
289
|
+
**My agent is not deterministic. Will results flip?**
|
|
290
|
+
Sometimes, and that is information. Use `--repeat` to measure it, and move anything exact into code
|
|
291
|
+
checks.
|
|
292
|
+
|
|
293
|
+
**Can I use it without a coding agent?**
|
|
294
|
+
Yes. Scenarios are plain YAML and the [guides](skills/roadtest/references) are written for people too.
|
|
295
|
+
|
|
296
|
+
## Contributing
|
|
297
|
+
|
|
298
|
+
Bug reports, ideas and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
299
|
+
|
|
300
|
+
## Licence
|
|
301
|
+
|
|
302
|
+
[MIT](LICENSE)
|