workflowval 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.
- workflowval-0.1.0/.gitignore +15 -0
- workflowval-0.1.0/LICENSE +21 -0
- workflowval-0.1.0/PKG-INFO +215 -0
- workflowval-0.1.0/README.md +193 -0
- workflowval-0.1.0/pyproject.toml +88 -0
- workflowval-0.1.0/src/workflowval/__init__.py +3 -0
- workflowval-0.1.0/src/workflowval/cli.py +584 -0
- workflowval-0.1.0/src/workflowval/gen/__init__.py +194 -0
- workflowval-0.1.0/src/workflowval/gen/source.py +109 -0
- workflowval-0.1.0/src/workflowval/gen/style.md +17 -0
- workflowval-0.1.0/src/workflowval/gen/templates/.gitkeep +0 -0
- workflowval-0.1.0/src/workflowval/gen/templates/conftest.py.tmpl +332 -0
- workflowval-0.1.0/src/workflowval/loop/__init__.py +55 -0
- workflowval-0.1.0/src/workflowval/loop/runner.py +488 -0
- workflowval-0.1.0/src/workflowval/loop/stats.py +178 -0
- workflowval-0.1.0/src/workflowval/loop/trace.py +29 -0
- workflowval-0.1.0/src/workflowval/models/__init__.py +19 -0
- workflowval-0.1.0/src/workflowval/models/base.py +68 -0
- workflowval-0.1.0/src/workflowval/models/fake.py +55 -0
- workflowval-0.1.0/src/workflowval/models/litellm_adapter.py +153 -0
- workflowval-0.1.0/src/workflowval/pipeline.py +906 -0
- workflowval-0.1.0/src/workflowval/planner/__init__.py +62 -0
- workflowval-0.1.0/src/workflowval/prompts/__init__.py +34 -0
- workflowval-0.1.0/src/workflowval/prompts/planner.md +37 -0
- workflowval-0.1.0/src/workflowval/prompts/reviewer.md +30 -0
- workflowval-0.1.0/src/workflowval/prompts/task_answer_review.md +14 -0
- workflowval-0.1.0/src/workflowval/prompts/task_finding.md +17 -0
- workflowval-0.1.0/src/workflowval/prompts/task_fix_test.md +6 -0
- workflowval-0.1.0/src/workflowval/prompts/task_fix_workflow.md +7 -0
- workflowval-0.1.0/src/workflowval/prompts/task_patch.md +9 -0
- workflowval-0.1.0/src/workflowval/prompts/task_plan.md +13 -0
- workflowval-0.1.0/src/workflowval/prompts/task_plan_gaps.md +6 -0
- workflowval-0.1.0/src/workflowval/prompts/task_report.md +9 -0
- workflowval-0.1.0/src/workflowval/prompts/task_review_plan.md +17 -0
- workflowval-0.1.0/src/workflowval/prompts/task_review_results.md +19 -0
- workflowval-0.1.0/src/workflowval/prompts/task_triage.md +9 -0
- workflowval-0.1.0/src/workflowval/prompts/writer.md +11 -0
- workflowval-0.1.0/src/workflowval/py.typed +0 -0
- workflowval-0.1.0/src/workflowval/redact.py +97 -0
- workflowval-0.1.0/src/workflowval/report/__init__.py +162 -0
- workflowval-0.1.0/src/workflowval/report/curl.py +75 -0
- workflowval-0.1.0/src/workflowval/schemas/__init__.py +78 -0
- workflowval-0.1.0/src/workflowval/schemas/_base.py +43 -0
- workflowval-0.1.0/src/workflowval/schemas/auth.py +66 -0
- workflowval-0.1.0/src/workflowval/schemas/labels.py +31 -0
- workflowval-0.1.0/src/workflowval/schemas/results.py +222 -0
- workflowval-0.1.0/src/workflowval/schemas/review.py +94 -0
- workflowval-0.1.0/src/workflowval/schemas/state.py +32 -0
- workflowval-0.1.0/src/workflowval/schemas/stats.py +36 -0
- workflowval-0.1.0/src/workflowval/schemas/workflow.py +148 -0
- workflowval-0.1.0/src/workflowval/spec/__init__.py +38 -0
- workflowval-0.1.0/src/workflowval/spec/graph.py +196 -0
- workflowval-0.1.0/src/workflowval/spec/loader.py +102 -0
- workflowval-0.1.0/src/workflowval/spec/validation.py +98 -0
- workflowval-0.1.0/src/workflowval/testrun.py +159 -0
- workflowval-0.1.0/src/workflowval/tools/__init__.py +6 -0
- workflowval-0.1.0/src/workflowval/tools/base.py +49 -0
- workflowval-0.1.0/src/workflowval/tools/registry.py +637 -0
- workflowval-0.1.0/src/workflowval/triage/__init__.py +199 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 workflowval contributors
|
|
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.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: workflowval
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Model-agnostic CLI agent that infers multi-step API workflows from an OpenAPI spec, tests them with pytest and triages every failure.
|
|
5
|
+
Project-URL: Homepage, https://github.com/rkvf01/workflowval
|
|
6
|
+
Project-URL: Documentation, https://github.com/rkvf01/workflowval/tree/main/docs
|
|
7
|
+
Project-URL: Issues, https://github.com/rkvf01/workflowval/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
Requires-Dist: jsonpatch>=1.41
|
|
13
|
+
Requires-Dist: jsonpointer>=3.8
|
|
14
|
+
Requires-Dist: litellm[mcp]>=1.83
|
|
15
|
+
Requires-Dist: openapi-core>=0.19
|
|
16
|
+
Requires-Dist: openapi-spec-validator>=0.7
|
|
17
|
+
Requires-Dist: pydantic>=2.7
|
|
18
|
+
Requires-Dist: pytest>=8
|
|
19
|
+
Requires-Dist: ruff>=0.6
|
|
20
|
+
Requires-Dist: typer>=0.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# workflowval
|
|
24
|
+
|
|
25
|
+
Model-agnostic CLI agent that reads an OpenAPI spec, infers multi-step workflows, writes and runs
|
|
26
|
+
pytest tests against a live API, and triages every failure as a test bug, an API bug or spec drift.
|
|
27
|
+
|
|
28
|
+
**Status:** all six commands work: `test`, `plan`, `run`, `drift`, `inspect` and `report`.
|
|
29
|
+
|
|
30
|
+
## Start here
|
|
31
|
+
|
|
32
|
+
All the project's documents are in the [docs folder](https://github.com/rkvf01/workflowval/tree/main/docs).
|
|
33
|
+
|
|
34
|
+
- **[Requirements](https://github.com/rkvf01/workflowval/blob/main/docs/requirements.md):** what workflowval was built to do.
|
|
35
|
+
- **[Conclusions](https://github.com/rkvf01/workflowval/blob/main/docs/conclusions.md):** how it measured up, what helped and what didn't.
|
|
36
|
+
- **[Model comparison](https://github.com/rkvf01/workflowval/blob/main/docs/model-comparison.md):** two models on the same benchmark, side by side.
|
|
37
|
+
- **[Drift study](https://github.com/rkvf01/workflowval/blob/main/docs/drift-study.md):** the same tests against three implementations of one API.
|
|
38
|
+
- **Build notes:** decisions in [M0](https://github.com/rkvf01/workflowval/blob/main/docs/m0-review.md), then changes and known limits in [M1](https://github.com/rkvf01/workflowval/blob/main/docs/m1-notes.md), [M2](https://github.com/rkvf01/workflowval/blob/main/docs/m2-notes.md) and [M3](https://github.com/rkvf01/workflowval/blob/main/docs/m3-notes.md).
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
It needs Python 3.11 or later.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
pip install workflowval
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then, from the folder where you want the tests written:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
export OPENROUTER_API_KEY=...
|
|
52
|
+
export WORKFLOWVAL_MODEL=openrouter/<vendor>/<model>
|
|
53
|
+
workflowval inspect openapi.json
|
|
54
|
+
workflowval test openapi.json --base-url http://localhost:8000 --max-cost-usd 2
|
|
55
|
+
workflowval run tests/wf
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This writes `workflows/<id>.json`, `tests/wf/test_<id>.py` and `results.json` in the current folder,
|
|
59
|
+
plus `report.md`, `junit.xml`, bug reports and spec patches in `reports/`.
|
|
60
|
+
If the API needs a login, pass `--auth auth.json` to both `test` and `run`, and set the env vars that
|
|
61
|
+
file names. In a login config, a field like `user.email` nests the login body. See `benchmarks/targets.md` for
|
|
62
|
+
the demo targets.
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
`workflowval test` runs these steps, and saves its place after each one:
|
|
67
|
+
|
|
68
|
+
1. **Plan.** The Planner reads the spec and any docs, and writes one workflow file per multi-step scenario,
|
|
69
|
+
each with the result it expects.
|
|
70
|
+
2. **Review the plan.** The Reviewer looks for what the plan missed, as a user of the API would, and the
|
|
71
|
+
Planner adds workflows.
|
|
72
|
+
3. **Write tests.** Code, not a model, renders each workflow into a pytest file, so this step is free and
|
|
73
|
+
repeatable.
|
|
74
|
+
4. **Run and label.** The tests run against the live API. Only a result that doesn't match what the workflow
|
|
75
|
+
expected goes to the Reviewer, which labels it `TEST_BUG`, `API_BUG`, `SPEC_DRIFT`, `CONFLICT` or
|
|
76
|
+
`UNRESOLVED`.
|
|
77
|
+
5. **Fix test bugs only.** The Planner corrects a wrong workflow and the Writer corrects test code. A real
|
|
78
|
+
failure is never changed to make it pass. It stays as a labeled expected failure.
|
|
79
|
+
6. **Review the results.** The Reviewer sees the confirmed failures and asks for new workflows, including
|
|
80
|
+
ones that get past a known bug.
|
|
81
|
+
7. **Report.** Bug reports, spec patches, `report.md` and `junit.xml` go in `reports/`.
|
|
82
|
+
|
|
83
|
+
| Part | Where |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| Commands | `src/workflowval/cli.py`, `pipeline.py` |
|
|
86
|
+
| Agent loop, budgets and stop rules | `src/workflowval/loop/` |
|
|
87
|
+
| Model adapter, the only code that imports a provider library | `src/workflowval/models/` |
|
|
88
|
+
| Tools the roles may call, limited per role | `src/workflowval/tools/` |
|
|
89
|
+
| Data formats | `src/workflowval/schemas/` |
|
|
90
|
+
| Spec reading and response checks | `src/workflowval/spec/` |
|
|
91
|
+
| Test rendering | `src/workflowval/gen/` |
|
|
92
|
+
| Role prompts | `src/workflowval/prompts/` |
|
|
93
|
+
|
|
94
|
+
## What needs a model
|
|
95
|
+
|
|
96
|
+
| Needs no model or key | Needs a model and its key |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| `workflowval inspect` | `workflowval plan` |
|
|
99
|
+
| `workflowval run tests/wf` | `workflowval test` |
|
|
100
|
+
| Plain `pytest tests/wf` | `workflowval drift` |
|
|
101
|
+
| `workflowval report` | Labeling and fixing failures |
|
|
102
|
+
| This repo's own lint and tests | `uv run pytest -m live`, and the benchmark |
|
|
103
|
+
|
|
104
|
+
So you can generate tests once, commit them, and re-run them with no model cost. A failure already
|
|
105
|
+
reviewed as real counts as expected, so it doesn't fail the run. Any other failure makes `run` exit 1,
|
|
106
|
+
but nothing labels it; run `workflowval test` to get the diagnosis. `run` and `pytest` still need a
|
|
107
|
+
reachable API and, for a login API, its credentials.
|
|
108
|
+
|
|
109
|
+
## Reading the results
|
|
110
|
+
|
|
111
|
+
| Result | Meaning | Fails the run? |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| `PASSED` | The test passed | No |
|
|
114
|
+
| `EXPECTED` | It hit the failure already reviewed as real, and only that failure | No |
|
|
115
|
+
| `LOOKS FIXED` | A test marked as a known issue passed | No |
|
|
116
|
+
| `MISMATCH` | A response breaks the spec, though the test may pass | No, except in `drift` |
|
|
117
|
+
| `UNRESOLVED` | Fix attempts ran out, or the failure has no verdict | Yes |
|
|
118
|
+
|
|
119
|
+
## Benchmark
|
|
120
|
+
|
|
121
|
+
Scored against a seeded-bug app with 10 planted defects and one spec-docs conflict, for 3 rounds on one low-cost
|
|
122
|
+
model. See `docs/m3-notes.md` for what the numbers mean, and `benchmarks/seeded/README.md` for who wrote the app,
|
|
123
|
+
which affects how independent this score is.
|
|
124
|
+
|
|
125
|
+
| Metric | Median (range) | Target |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| Seeded bugs detected, of 10 | 10 (9 to 10) | 8 or more |
|
|
128
|
+
| Label accuracy on detected bugs, % | 70 (67 to 90) | 80 or more |
|
|
129
|
+
| Bugs a workflow could catch, of 10 | 10 (10 to 10) | 10 |
|
|
130
|
+
| False API_BUG on the clean build | 1 (1 to 1) | 1 or fewer |
|
|
131
|
+
| Tests green on the clean build, % | 98 (96 to 98) | 90 or more |
|
|
132
|
+
| CONFLICT reports | 0 (0 to 0) | 1 |
|
|
133
|
+
| Cost per round, USD | 0.31 (0.30 to 0.35) | reported only |
|
|
134
|
+
|
|
135
|
+
Label accuracy and the conflict report still miss their targets on this model. A second model met them on
|
|
136
|
+
the medians, at about 28 times the cost and with less steady detection: see `docs/model-comparison.md`.
|
|
137
|
+
|
|
138
|
+
## Known limits
|
|
139
|
+
|
|
140
|
+
- **Results vary by model and run,** since models choose the workflows and labels. Three rounds is a small sample.
|
|
141
|
+
- **Labels are the weak spot.** About a quarter of detected bugs got the wrong label on both models tested. Check
|
|
142
|
+
a label before acting on it.
|
|
143
|
+
- **A test stops at its first failure.** A bug in an early step can hide later bugs from every test that goes
|
|
144
|
+
through that step. This is deliberate: the failure stays a result, and getting further is the job of another test.
|
|
145
|
+
- **Finding a conflict isn't reliable** on the low-cost model, which wrote no CONFLICT reports in the benchmark.
|
|
146
|
+
- **The fix loop can be inconsistent.** On the same kind of edge case, it may rewrite what some tests expect and not
|
|
147
|
+
others. Rewrites that change what a test checks are printed as `CHANGED`, so review them.
|
|
148
|
+
- **Edge cases get missed,** and a `204` can raise a false alarm. See `docs/drift-study.md`.
|
|
149
|
+
- **Generated tests aren't sandboxed.** They run on your machine with your permissions, so use only specs and docs
|
|
150
|
+
you trust.
|
|
151
|
+
- **The cost cap can go slightly over,** since it judges by the dearest call so far.
|
|
152
|
+
- **A run resumes only after being killed.** One that stopped on a model error, a step limit or a cost limit doesn't
|
|
153
|
+
pick up where it stopped. Running it again starts a new run, which reuses a finished plan for the same spec.
|
|
154
|
+
- **The benchmark isn't fully independent,** because of who wrote the seeded app. See `benchmarks/seeded/README.md`.
|
|
155
|
+
|
|
156
|
+
## Safety and CI
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
workflowval test openapi.json --base-url https://staging.example.com --allow-remote --read-only
|
|
160
|
+
workflowval run tests/wf --ci --ci-max-api-bugs 2
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`--read-only` sends no writes. A non-local host needs `--allow-remote`, then `--yes`, `--ci` or `--read-only`.
|
|
164
|
+
Generated tests refuse any other host. `--ci` fails on anything unresolved, or on bugs above the limits. A killed
|
|
165
|
+
`workflowval test` picks up at its last step when run again. See `docs/m3-notes.md`.
|
|
166
|
+
|
|
167
|
+
`--max-cost-usd 2` stops a run before a model call could pass that cost, judging by the dearest call so far, so a
|
|
168
|
+
much larger call can still go slightly over. A call with no known price also stops the run, since the limit
|
|
169
|
+
can't be kept. Temporary provider errors, such as rate limits, are retried up to 5 times.
|
|
170
|
+
|
|
171
|
+
Secrets are hidden in everything saved or sent to a model: known values, auth headers and fields with names like
|
|
172
|
+
`token`. A hidden value isn't checked against the spec. `--no-redact` keeps recorded response bodies exact, and works
|
|
173
|
+
only with a local API, since those bodies may hold real secrets. Everything else stays hidden.
|
|
174
|
+
|
|
175
|
+
## Develop
|
|
176
|
+
|
|
177
|
+
This needs [uv](https://docs.astral.sh/uv/).
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
git clone https://github.com/rkvf01/workflowval.git && cd workflowval
|
|
181
|
+
uv sync
|
|
182
|
+
uv run ruff check . && uv run ruff format --check .
|
|
183
|
+
uv run mypy --strict src
|
|
184
|
+
uv run pytest --cov=workflowval.loop --cov=workflowval.spec --cov=workflowval.triage --cov-fail-under=80
|
|
185
|
+
uv run workflowval --help
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Unit tests use `FakeModel` and block network sockets, so they need no API key.
|
|
189
|
+
|
|
190
|
+
## Models and API keys
|
|
191
|
+
|
|
192
|
+
Live runs go through LiteLLM. Pick a model with `--model provider/name` or `WORKFLOWVAL_MODEL`, and
|
|
193
|
+
override per role with `--role-model writer=provider/name`. Provider keys come from environment
|
|
194
|
+
variables only, for example `OPENROUTER_API_KEY`. Local Ollama models (`ollama/<name>`) need no key.
|
|
195
|
+
|
|
196
|
+
To check the adapter against a real provider:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
export OPENROUTER_API_KEY=...
|
|
200
|
+
WORKFLOWVAL_LIVE_MODEL=openrouter/<vendor>/<model> uv run pytest -m live
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Stats
|
|
204
|
+
|
|
205
|
+
Every run writes model calls, tool calls and cost, per workflow and per role, to
|
|
206
|
+
`.workflowval/stats.json`, and adds a line to `.workflowval/stats_history.jsonl`. After 5 usable runs,
|
|
207
|
+
the run summary suggests a `--max-steps` value based on that history. It never changes the limit by
|
|
208
|
+
itself. See `docs/m0-review.md`.
|
|
209
|
+
|
|
210
|
+
Cost is the figure the provider returns with each reply, so tracking it adds no extra calls. To turn
|
|
211
|
+
it off, use `--no-cost-tracking` or set `WORKFLOWVAL_COST_TRACKING=off`.
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# workflowval
|
|
2
|
+
|
|
3
|
+
Model-agnostic CLI agent that reads an OpenAPI spec, infers multi-step workflows, writes and runs
|
|
4
|
+
pytest tests against a live API, and triages every failure as a test bug, an API bug or spec drift.
|
|
5
|
+
|
|
6
|
+
**Status:** all six commands work: `test`, `plan`, `run`, `drift`, `inspect` and `report`.
|
|
7
|
+
|
|
8
|
+
## Start here
|
|
9
|
+
|
|
10
|
+
All the project's documents are in the [docs folder](https://github.com/rkvf01/workflowval/tree/main/docs).
|
|
11
|
+
|
|
12
|
+
- **[Requirements](https://github.com/rkvf01/workflowval/blob/main/docs/requirements.md):** what workflowval was built to do.
|
|
13
|
+
- **[Conclusions](https://github.com/rkvf01/workflowval/blob/main/docs/conclusions.md):** how it measured up, what helped and what didn't.
|
|
14
|
+
- **[Model comparison](https://github.com/rkvf01/workflowval/blob/main/docs/model-comparison.md):** two models on the same benchmark, side by side.
|
|
15
|
+
- **[Drift study](https://github.com/rkvf01/workflowval/blob/main/docs/drift-study.md):** the same tests against three implementations of one API.
|
|
16
|
+
- **Build notes:** decisions in [M0](https://github.com/rkvf01/workflowval/blob/main/docs/m0-review.md), then changes and known limits in [M1](https://github.com/rkvf01/workflowval/blob/main/docs/m1-notes.md), [M2](https://github.com/rkvf01/workflowval/blob/main/docs/m2-notes.md) and [M3](https://github.com/rkvf01/workflowval/blob/main/docs/m3-notes.md).
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
It needs Python 3.11 or later.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pip install workflowval
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then, from the folder where you want the tests written:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
export OPENROUTER_API_KEY=...
|
|
30
|
+
export WORKFLOWVAL_MODEL=openrouter/<vendor>/<model>
|
|
31
|
+
workflowval inspect openapi.json
|
|
32
|
+
workflowval test openapi.json --base-url http://localhost:8000 --max-cost-usd 2
|
|
33
|
+
workflowval run tests/wf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This writes `workflows/<id>.json`, `tests/wf/test_<id>.py` and `results.json` in the current folder,
|
|
37
|
+
plus `report.md`, `junit.xml`, bug reports and spec patches in `reports/`.
|
|
38
|
+
If the API needs a login, pass `--auth auth.json` to both `test` and `run`, and set the env vars that
|
|
39
|
+
file names. In a login config, a field like `user.email` nests the login body. See `benchmarks/targets.md` for
|
|
40
|
+
the demo targets.
|
|
41
|
+
|
|
42
|
+
## How it works
|
|
43
|
+
|
|
44
|
+
`workflowval test` runs these steps, and saves its place after each one:
|
|
45
|
+
|
|
46
|
+
1. **Plan.** The Planner reads the spec and any docs, and writes one workflow file per multi-step scenario,
|
|
47
|
+
each with the result it expects.
|
|
48
|
+
2. **Review the plan.** The Reviewer looks for what the plan missed, as a user of the API would, and the
|
|
49
|
+
Planner adds workflows.
|
|
50
|
+
3. **Write tests.** Code, not a model, renders each workflow into a pytest file, so this step is free and
|
|
51
|
+
repeatable.
|
|
52
|
+
4. **Run and label.** The tests run against the live API. Only a result that doesn't match what the workflow
|
|
53
|
+
expected goes to the Reviewer, which labels it `TEST_BUG`, `API_BUG`, `SPEC_DRIFT`, `CONFLICT` or
|
|
54
|
+
`UNRESOLVED`.
|
|
55
|
+
5. **Fix test bugs only.** The Planner corrects a wrong workflow and the Writer corrects test code. A real
|
|
56
|
+
failure is never changed to make it pass. It stays as a labeled expected failure.
|
|
57
|
+
6. **Review the results.** The Reviewer sees the confirmed failures and asks for new workflows, including
|
|
58
|
+
ones that get past a known bug.
|
|
59
|
+
7. **Report.** Bug reports, spec patches, `report.md` and `junit.xml` go in `reports/`.
|
|
60
|
+
|
|
61
|
+
| Part | Where |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| Commands | `src/workflowval/cli.py`, `pipeline.py` |
|
|
64
|
+
| Agent loop, budgets and stop rules | `src/workflowval/loop/` |
|
|
65
|
+
| Model adapter, the only code that imports a provider library | `src/workflowval/models/` |
|
|
66
|
+
| Tools the roles may call, limited per role | `src/workflowval/tools/` |
|
|
67
|
+
| Data formats | `src/workflowval/schemas/` |
|
|
68
|
+
| Spec reading and response checks | `src/workflowval/spec/` |
|
|
69
|
+
| Test rendering | `src/workflowval/gen/` |
|
|
70
|
+
| Role prompts | `src/workflowval/prompts/` |
|
|
71
|
+
|
|
72
|
+
## What needs a model
|
|
73
|
+
|
|
74
|
+
| Needs no model or key | Needs a model and its key |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `workflowval inspect` | `workflowval plan` |
|
|
77
|
+
| `workflowval run tests/wf` | `workflowval test` |
|
|
78
|
+
| Plain `pytest tests/wf` | `workflowval drift` |
|
|
79
|
+
| `workflowval report` | Labeling and fixing failures |
|
|
80
|
+
| This repo's own lint and tests | `uv run pytest -m live`, and the benchmark |
|
|
81
|
+
|
|
82
|
+
So you can generate tests once, commit them, and re-run them with no model cost. A failure already
|
|
83
|
+
reviewed as real counts as expected, so it doesn't fail the run. Any other failure makes `run` exit 1,
|
|
84
|
+
but nothing labels it; run `workflowval test` to get the diagnosis. `run` and `pytest` still need a
|
|
85
|
+
reachable API and, for a login API, its credentials.
|
|
86
|
+
|
|
87
|
+
## Reading the results
|
|
88
|
+
|
|
89
|
+
| Result | Meaning | Fails the run? |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `PASSED` | The test passed | No |
|
|
92
|
+
| `EXPECTED` | It hit the failure already reviewed as real, and only that failure | No |
|
|
93
|
+
| `LOOKS FIXED` | A test marked as a known issue passed | No |
|
|
94
|
+
| `MISMATCH` | A response breaks the spec, though the test may pass | No, except in `drift` |
|
|
95
|
+
| `UNRESOLVED` | Fix attempts ran out, or the failure has no verdict | Yes |
|
|
96
|
+
|
|
97
|
+
## Benchmark
|
|
98
|
+
|
|
99
|
+
Scored against a seeded-bug app with 10 planted defects and one spec-docs conflict, for 3 rounds on one low-cost
|
|
100
|
+
model. See `docs/m3-notes.md` for what the numbers mean, and `benchmarks/seeded/README.md` for who wrote the app,
|
|
101
|
+
which affects how independent this score is.
|
|
102
|
+
|
|
103
|
+
| Metric | Median (range) | Target |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| Seeded bugs detected, of 10 | 10 (9 to 10) | 8 or more |
|
|
106
|
+
| Label accuracy on detected bugs, % | 70 (67 to 90) | 80 or more |
|
|
107
|
+
| Bugs a workflow could catch, of 10 | 10 (10 to 10) | 10 |
|
|
108
|
+
| False API_BUG on the clean build | 1 (1 to 1) | 1 or fewer |
|
|
109
|
+
| Tests green on the clean build, % | 98 (96 to 98) | 90 or more |
|
|
110
|
+
| CONFLICT reports | 0 (0 to 0) | 1 |
|
|
111
|
+
| Cost per round, USD | 0.31 (0.30 to 0.35) | reported only |
|
|
112
|
+
|
|
113
|
+
Label accuracy and the conflict report still miss their targets on this model. A second model met them on
|
|
114
|
+
the medians, at about 28 times the cost and with less steady detection: see `docs/model-comparison.md`.
|
|
115
|
+
|
|
116
|
+
## Known limits
|
|
117
|
+
|
|
118
|
+
- **Results vary by model and run,** since models choose the workflows and labels. Three rounds is a small sample.
|
|
119
|
+
- **Labels are the weak spot.** About a quarter of detected bugs got the wrong label on both models tested. Check
|
|
120
|
+
a label before acting on it.
|
|
121
|
+
- **A test stops at its first failure.** A bug in an early step can hide later bugs from every test that goes
|
|
122
|
+
through that step. This is deliberate: the failure stays a result, and getting further is the job of another test.
|
|
123
|
+
- **Finding a conflict isn't reliable** on the low-cost model, which wrote no CONFLICT reports in the benchmark.
|
|
124
|
+
- **The fix loop can be inconsistent.** On the same kind of edge case, it may rewrite what some tests expect and not
|
|
125
|
+
others. Rewrites that change what a test checks are printed as `CHANGED`, so review them.
|
|
126
|
+
- **Edge cases get missed,** and a `204` can raise a false alarm. See `docs/drift-study.md`.
|
|
127
|
+
- **Generated tests aren't sandboxed.** They run on your machine with your permissions, so use only specs and docs
|
|
128
|
+
you trust.
|
|
129
|
+
- **The cost cap can go slightly over,** since it judges by the dearest call so far.
|
|
130
|
+
- **A run resumes only after being killed.** One that stopped on a model error, a step limit or a cost limit doesn't
|
|
131
|
+
pick up where it stopped. Running it again starts a new run, which reuses a finished plan for the same spec.
|
|
132
|
+
- **The benchmark isn't fully independent,** because of who wrote the seeded app. See `benchmarks/seeded/README.md`.
|
|
133
|
+
|
|
134
|
+
## Safety and CI
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
workflowval test openapi.json --base-url https://staging.example.com --allow-remote --read-only
|
|
138
|
+
workflowval run tests/wf --ci --ci-max-api-bugs 2
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--read-only` sends no writes. A non-local host needs `--allow-remote`, then `--yes`, `--ci` or `--read-only`.
|
|
142
|
+
Generated tests refuse any other host. `--ci` fails on anything unresolved, or on bugs above the limits. A killed
|
|
143
|
+
`workflowval test` picks up at its last step when run again. See `docs/m3-notes.md`.
|
|
144
|
+
|
|
145
|
+
`--max-cost-usd 2` stops a run before a model call could pass that cost, judging by the dearest call so far, so a
|
|
146
|
+
much larger call can still go slightly over. A call with no known price also stops the run, since the limit
|
|
147
|
+
can't be kept. Temporary provider errors, such as rate limits, are retried up to 5 times.
|
|
148
|
+
|
|
149
|
+
Secrets are hidden in everything saved or sent to a model: known values, auth headers and fields with names like
|
|
150
|
+
`token`. A hidden value isn't checked against the spec. `--no-redact` keeps recorded response bodies exact, and works
|
|
151
|
+
only with a local API, since those bodies may hold real secrets. Everything else stays hidden.
|
|
152
|
+
|
|
153
|
+
## Develop
|
|
154
|
+
|
|
155
|
+
This needs [uv](https://docs.astral.sh/uv/).
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
git clone https://github.com/rkvf01/workflowval.git && cd workflowval
|
|
159
|
+
uv sync
|
|
160
|
+
uv run ruff check . && uv run ruff format --check .
|
|
161
|
+
uv run mypy --strict src
|
|
162
|
+
uv run pytest --cov=workflowval.loop --cov=workflowval.spec --cov=workflowval.triage --cov-fail-under=80
|
|
163
|
+
uv run workflowval --help
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Unit tests use `FakeModel` and block network sockets, so they need no API key.
|
|
167
|
+
|
|
168
|
+
## Models and API keys
|
|
169
|
+
|
|
170
|
+
Live runs go through LiteLLM. Pick a model with `--model provider/name` or `WORKFLOWVAL_MODEL`, and
|
|
171
|
+
override per role with `--role-model writer=provider/name`. Provider keys come from environment
|
|
172
|
+
variables only, for example `OPENROUTER_API_KEY`. Local Ollama models (`ollama/<name>`) need no key.
|
|
173
|
+
|
|
174
|
+
To check the adapter against a real provider:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
export OPENROUTER_API_KEY=...
|
|
178
|
+
WORKFLOWVAL_LIVE_MODEL=openrouter/<vendor>/<model> uv run pytest -m live
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Stats
|
|
182
|
+
|
|
183
|
+
Every run writes model calls, tool calls and cost, per workflow and per role, to
|
|
184
|
+
`.workflowval/stats.json`, and adds a line to `.workflowval/stats_history.jsonl`. After 5 usable runs,
|
|
185
|
+
the run summary suggests a `--max-steps` value based on that history. It never changes the limit by
|
|
186
|
+
itself. See `docs/m0-review.md`.
|
|
187
|
+
|
|
188
|
+
Cost is the figure the provider returns with each reply, so tracking it adds no extra calls. To turn
|
|
189
|
+
it off, use `--no-cost-tracking` or set `WORKFLOWVAL_COST_TRACKING=off`.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "workflowval"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Model-agnostic CLI agent that infers multi-step API workflows from an OpenAPI spec, tests them with pytest and triages every failure."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"httpx>=0.27",
|
|
11
|
+
"jsonpatch>=1.41",
|
|
12
|
+
"jsonpointer>=3.8",
|
|
13
|
+
"litellm[mcp]>=1.83", # Tool calls import mcp, which is only in this extra.
|
|
14
|
+
"openapi-core>=0.19",
|
|
15
|
+
"openapi-spec-validator>=0.7",
|
|
16
|
+
"pydantic>=2.7",
|
|
17
|
+
"pytest>=8",
|
|
18
|
+
"ruff>=0.6",
|
|
19
|
+
"typer>=0.12",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/rkvf01/workflowval"
|
|
24
|
+
Documentation = "https://github.com/rkvf01/workflowval/tree/main/docs"
|
|
25
|
+
Issues = "https://github.com/rkvf01/workflowval/issues"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
workflowval = "workflowval.cli:app"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
32
|
+
"fastapi>=0.115",
|
|
33
|
+
"mypy>=1.10",
|
|
34
|
+
"pytest-cov>=5",
|
|
35
|
+
"pytest-socket>=0.7",
|
|
36
|
+
"uvicorn>=0.30",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["hatchling"]
|
|
41
|
+
build-backend = "hatchling.build"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/workflowval"]
|
|
45
|
+
|
|
46
|
+
# A list of what ships, so nothing else in the folder can end up in the package.
|
|
47
|
+
[tool.hatch.build.targets.sdist]
|
|
48
|
+
only-include = ["src", "README.md", "LICENSE", "pyproject.toml"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 100
|
|
52
|
+
target-version = "py311"
|
|
53
|
+
src = ["src", "tests"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "S"]
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint.per-file-ignores]
|
|
59
|
+
"tests/**" = ["S101", "S105", "S106"]
|
|
60
|
+
"src/workflowval/gen/templates/**" = ["S101"]
|
|
61
|
+
|
|
62
|
+
[tool.mypy]
|
|
63
|
+
strict = true
|
|
64
|
+
python_version = "3.11"
|
|
65
|
+
mypy_path = "src"
|
|
66
|
+
plugins = ["pydantic.mypy"]
|
|
67
|
+
|
|
68
|
+
[[tool.mypy.overrides]]
|
|
69
|
+
module = ["litellm", "litellm.*"]
|
|
70
|
+
ignore_missing_imports = true
|
|
71
|
+
follow_imports = "skip"
|
|
72
|
+
|
|
73
|
+
[[tool.mypy.overrides]]
|
|
74
|
+
module = ["jsonpatch", "jsonpointer"]
|
|
75
|
+
ignore_missing_imports = true
|
|
76
|
+
|
|
77
|
+
[tool.pytest.ini_options]
|
|
78
|
+
testpaths = ["tests"]
|
|
79
|
+
addopts = "-ra --disable-socket --allow-unix-socket -m 'not live'"
|
|
80
|
+
markers = ["live: calls a real model provider; needs an API key and is never run in CI"]
|
|
81
|
+
|
|
82
|
+
[tool.coverage.run]
|
|
83
|
+
branch = true
|
|
84
|
+
source = ["workflowval"]
|
|
85
|
+
|
|
86
|
+
[tool.coverage.report]
|
|
87
|
+
show_missing = true
|
|
88
|
+
exclude_also = ["if TYPE_CHECKING:", "\\.\\.\\.$"]
|