Python authoring surface that compiles declarative flows to the flowbench canonical IR
Project description
flowbench (Python SDK)
The Python authoring surface: declarative flows that compile to the canonical
IR (ADR 0002) and execute on the Go engine at full VU scale, or run here
directly (python file.py) as the Python-driven producer, writing to the same
run store (ADR 0012).
Install
pip install flowbench # or: uv add flowbench
Python 3.10+, and httpx is the only dependency. Running a flow at VU scale,
or resolving a named target, also needs the flowbench engine binary — see
installation.
Passing base_url= to flow.run() skips target resolution and needs nothing
but this package.
Prompt observation
FlowBench never makes the LLM call, templates a prompt, or sets a model
parameter (ADR 0009). Your code calls whatever SDK you already use, and
ctx.prompt(...) wraps that call so the exchange is recorded:
client = OpenAI(api_key=flowbench.secret(os.environ["OPENAI_API_KEY"]))
@flow.step
def classify(ctx):
messages = [{"role": "system", "content": SYSTEM},
{"role": "user", "content": str(ctx.user["ticket"])}]
with ctx.prompt("classify", template=SYSTEM, pace="20/m", burst=3) as p:
reply = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
p.record(messages, reply.choices[0].message.content, usage=reply.usage)
expect(p.completion).to_contain("refund")
- The pair is always captured, every iteration, whatever the outcome — a diff needs both sides. Redaction and the size cap still apply.
template=is the prompt's identity. It is hashed, so the diff view can tell "the prompt changed" from "the output changed under the same prompt"; without one the recorded content is hashed instead, which moves whenever a substituted value does.variant="concise"is a label, not machinery: what varies is your code, and the label gives that version its own span identity (classify@concise) so folding, metrics and diffs stay per-variant. Because it is an identity, renaming one splits a flame graph into a before and an after — a clean run warns on stderr when a name earlier runs of the same scenario recorded is no longer there.pace="20/m"is a client-side ceiling keyed to the observation name, so a run over 500 fixture rows doesn't trip the provider's rate limit in the first place.burst=lets the first N calls go unspaced. A wait shows up as its ownpacespan beside the observation.timeout=is measured, not enforced — the call is your SDK's, and FlowBench has no handle to cancel it. An overrun fails the observation after the fact; pass the same bound to your SDK's own timeout to actually cut it short.- The provider's HTTP resolves as child spans of the observation, so the round
trip is never opaque in the flame graph. A provider
429classifies asthrottled, notfailed(ADR 0006). Async clients count: a batch gathered inside one observation lands under it, call by call. Two observations open concurrently do not — gather the calls inside one, or take them in turn.
Observation only runs on the Python-driven path: flowbench run file.py
compiles to IR the Go engine executes, and there is no Python there to wrap, so
compiling a flow that observes a prompt is refused rather than silently
dropped.
Development
The toolchain is uv. It provisions the
interpreter pinned in .python-version and the dev dependencies in the
lockfile, so no manual venv or pip install is needed.
uv sync # create .venv and install the package + dev deps
uv run pytest # unit tests
uv run ruff check . # lint
uv run ruff format . # format
The Go conformance suite (internal/conformance) compiles each tests/flows/*.py
fixture and diffs the resulting IR against the YAML parser's output for the
.flow.yaml of the same name — a flow written twice must compile to one
representation. It picks up sdk-python/.venv automatically, so run uv sync
before go test ./..., or point FLOWBENCH_PYTHON at an interpreter of your
own. Without either it skips rather than fails.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flowbench-0.1.1.tar.gz.
File metadata
- Download URL: flowbench-0.1.1.tar.gz
- Upload date:
- Size: 72.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c98146f84f13f9247bbb64ef2b2cc61058a85006b73b4c29d2289c6755022b2
|
|
| MD5 |
4a8012664c4ab16d096806dd6bedf504
|
|
| BLAKE2b-256 |
0b1a633cc729d6232701fe585d12700b38d5c7c8b13353f3c8c3c0102c3ab234
|
File details
Details for the file flowbench-0.1.1-py3-none-any.whl.
File metadata
- Download URL: flowbench-0.1.1-py3-none-any.whl
- Upload date:
- Size: 54.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38d60c6a0d637d4f7e3f8b35200e7db9e780ad4f9cbaaf83f4817bd63ff95dc7
|
|
| MD5 |
15ac2e2a96fe56ab18d69b6dba45182c
|
|
| BLAKE2b-256 |
6fbee35d68136b72ad055d1c755636b6e1ff02c68579c38fc5900bc0865d21a2
|