wizardflow 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.
- wizardflow-0.1.0/.gitignore +16 -0
- wizardflow-0.1.0/CONTRIBUTING.md +51 -0
- wizardflow-0.1.0/LICENSE +21 -0
- wizardflow-0.1.0/PKG-INFO +340 -0
- wizardflow-0.1.0/README.md +293 -0
- wizardflow-0.1.0/assets/demo.gif +0 -0
- wizardflow-0.1.0/examples/data_types.py +84 -0
- wizardflow-0.1.0/examples/multibranch.html +129 -0
- wizardflow-0.1.0/examples/multibranch.md +105 -0
- wizardflow-0.1.0/examples/multibranch.py +76 -0
- wizardflow-0.1.0/examples/quickstart.html +92 -0
- wizardflow-0.1.0/examples/quickstart.md +50 -0
- wizardflow-0.1.0/examples/quickstart.py +45 -0
- wizardflow-0.1.0/pyproject.toml +45 -0
- wizardflow-0.1.0/scripts/build_ui.py +75 -0
- wizardflow-0.1.0/src/wizardflow/__init__.py +143 -0
- wizardflow-0.1.0/src/wizardflow/_render.py +38 -0
- wizardflow-0.1.0/src/wizardflow/_ui/404.html +25 -0
- wizardflow-0.1.0/src/wizardflow/_ui/__next.__PAGE__.txt +10 -0
- wizardflow-0.1.0/src/wizardflow/_ui/__next._full.txt +22 -0
- wizardflow-0.1.0/src/wizardflow/_ui/__next._head.txt +6 -0
- wizardflow-0.1.0/src/wizardflow/_ui/__next._index.txt +6 -0
- wizardflow-0.1.0/src/wizardflow/_ui/__next._tree.txt +5 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/05-c3ty_6dwfk.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/09s72r58ijxwx.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0mz_v1wicwnmn.css +2 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0u1x9l49cgb30.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0ux_7aev0a2kt.js +36 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0vq8h3vx9uqg5.js +114 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/142r2alz4_x6t.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/14mrh2-p_w84d.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/18clj6xsyhc_j.css +3 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/2-j1b74l3lxxu.css +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/27jktro2p5rq9.js +4 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/2gj5bmeov-r8f.js +2 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/310vm2bl3xxpt.js +31 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/3n7dm2ojtyzwn.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/turbopack-2fblmukzx7kws.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/icon.3okpzkln1vq00.svg +19 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe/_buildManifest.js +11 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe/_clientMiddlewareManifest.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe/_ssgManifest.js +1 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._full.txt +17 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._head.txt +6 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._index.txt +6 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._not-found/__PAGE__.txt +5 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._not-found.txt +5 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._tree.txt +2 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found.html +25 -0
- wizardflow-0.1.0/src/wizardflow/_ui/_not-found.txt +17 -0
- wizardflow-0.1.0/src/wizardflow/_ui/icon.svg +19 -0
- wizardflow-0.1.0/src/wizardflow/_ui/index.html +25 -0
- wizardflow-0.1.0/src/wizardflow/_ui/index.txt +22 -0
- wizardflow-0.1.0/src/wizardflow/_ui/opengraph-image +0 -0
- wizardflow-0.1.0/src/wizardflow/_ui/robots.txt +5 -0
- wizardflow-0.1.0/src/wizardflow/_ui/sitemap.xml +9 -0
- wizardflow-0.1.0/src/wizardflow/cli.py +316 -0
- wizardflow-0.1.0/src/wizardflow/client.py +470 -0
- wizardflow-0.1.0/src/wizardflow/constants.py +44 -0
- wizardflow-0.1.0/src/wizardflow/html.py +152 -0
- wizardflow-0.1.0/src/wizardflow/markdown.py +122 -0
- wizardflow-0.1.0/src/wizardflow/py.typed +0 -0
- wizardflow-0.1.0/tests/conftest.py +6 -0
- wizardflow-0.1.0/tests/test_cli.py +104 -0
- wizardflow-0.1.0/tests/test_html.py +140 -0
- wizardflow-0.1.0/tests/test_markdown.py +163 -0
- wizardflow-0.1.0/tests/test_trace.py +468 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
# uv resolves this in CI (uv run / uv build); a zero-dependency library doesn't
|
|
11
|
+
# ship a lockfile, and tracking it would only sweep it into the sdist.
|
|
12
|
+
uv.lock
|
|
13
|
+
# Generated traces from the example scripts (e.g. examples/quickstart.json) —
|
|
14
|
+
# regenerated on each run, not tracked. Scoped to examples/ so it never touches
|
|
15
|
+
# the embedded UI bundle (src/wizardflow/_ui/), which must ship in the package.
|
|
16
|
+
/examples/*.json
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Contributing to the WizardFlow Python SDK
|
|
2
|
+
|
|
3
|
+
This SDK lives in `sdk/python/` of the WizardFlow monorepo. It's pure Python with
|
|
4
|
+
no runtime dependencies; `pytest` is the only dev dependency.
|
|
5
|
+
|
|
6
|
+
## Local development
|
|
7
|
+
|
|
8
|
+
Work against a local checkout with an editable install:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
cd sdk/python
|
|
12
|
+
pip install -e ".[dev]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`tests/conftest.py` puts `src/` on the path, so the suite also runs without any
|
|
16
|
+
install.
|
|
17
|
+
|
|
18
|
+
## Tests
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd sdk/python
|
|
22
|
+
pytest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The tests pin the emitted schema shape and the recording semantics (step
|
|
26
|
+
folding, completed-only persistence, atomic write, `id=` targeting, unknown-node
|
|
27
|
+
fast-fail, …).
|
|
28
|
+
|
|
29
|
+
## Schema contract
|
|
30
|
+
|
|
31
|
+
The JSON this SDK serializes to is defined by `src/types/agenttrace.ts` in the
|
|
32
|
+
monorepo frontend — that TypeScript type is the schema of record. Change it and
|
|
33
|
+
the serializer (`src/wizardflow/client.py`) plus its tests must change in
|
|
34
|
+
lockstep; they must not drift.
|
|
35
|
+
|
|
36
|
+
## Refreshing the bundled UI
|
|
37
|
+
|
|
38
|
+
The bundled viewer lives in `src/wizardflow/_ui/` and is committed so a
|
|
39
|
+
standalone SDK checkout (and the published package) works without the website
|
|
40
|
+
source or a Node build step. Refresh it from the monorepo frontend after UI
|
|
41
|
+
changes:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd sdk/python
|
|
45
|
+
python scripts/build_ui.py
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That script runs the shared Next frontend with
|
|
49
|
+
`NEXT_PUBLIC_WIZARDFLOW_TARGET=local`, copies the static export into
|
|
50
|
+
`src/wizardflow/_ui/`, and removes hosted-only legal route artifacts (`Impressum`
|
|
51
|
+
/ `Datenschutz`). The SDK UI keeps the GitHub project link.
|
wizardflow-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leon Koch
|
|
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,340 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wizardflow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for recording agent flows into the WizardFlow / AgentTrace file format.
|
|
5
|
+
Project-URL: Homepage, https://getwizardflow.com
|
|
6
|
+
Project-URL: Documentation, https://getwizardflow.com
|
|
7
|
+
Author: Leon Koch
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Leon Koch
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: agents,langgraph,llm,observability,tracing
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Classifier: Typing :: Typed
|
|
43
|
+
Requires-Python: >=3.9
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# WizardFlow Python SDK
|
|
49
|
+
|
|
50
|
+
**A lightweight tracer for Python agents.** Drop three calls into your code —
|
|
51
|
+
`init`, `log`, `end_message` — and turn a messy multi-agent run into a portable
|
|
52
|
+
trace you can replay as an interactive graph or export to Markdown/HTML. Because
|
|
53
|
+
the trace is just a JSON file, anyone can replay it: hand it to a teammate or PM
|
|
54
|
+
and they drop it into **[getwizardflow.com](https://getwizardflow.com)** in the
|
|
55
|
+
browser — no Python, no install. Lightweight by design: pure Python, **zero
|
|
56
|
+
runtime dependencies**, no daemon, no setup.
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
The JSON it produces is a small, documented schema: a `graph { nodes, edges }`
|
|
61
|
+
plus `messages[] → steps[] → payloads[] { label, value }`.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install wizardflow
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
No runtime dependencies. Developing the SDK itself? See
|
|
70
|
+
[CONTRIBUTING.md](https://github.com/lkleonk/wizardflow/blob/main/sdk/python/CONTRIBUTING.md).
|
|
71
|
+
|
|
72
|
+
## Quickstart
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import wizardflow
|
|
76
|
+
|
|
77
|
+
wizardflow.init(
|
|
78
|
+
output_dir="traces", # where trace files are written
|
|
79
|
+
file_prefix="run", # optional; defaults to "wizardflow"
|
|
80
|
+
description="A small router-based agent run.",
|
|
81
|
+
nodes=["user_input", "router", "planner", "tool_node", "final_response"],
|
|
82
|
+
edges=[("user_input", "router"), ("router", "planner")],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Every log names its message in the first argument.
|
|
86
|
+
wizardflow.log("msg-1", "router", "llm_input", prompt) # same node, two payloads ->
|
|
87
|
+
wizardflow.log("msg-1", "router", "llm_output", output) # folded into one step
|
|
88
|
+
wizardflow.log("msg-1", "tool_node") # visited, no payloads
|
|
89
|
+
wizardflow.end_message("msg-1") # -> writes the trace
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
There is **no `save()`** and no autosave: `log()` only accumulates in memory,
|
|
93
|
+
and `end_message(id)` is the one call that writes the trace. Writes happen at
|
|
94
|
+
message boundaries — one write per finished message, however many `log()` calls
|
|
95
|
+
it contains. `init()` returns a client and also stashes it as the module
|
|
96
|
+
default, so the bare `wizardflow.log(...)` form above works.
|
|
97
|
+
|
|
98
|
+
**Concurrency-safe.** Multi-agent setups end messages from many threads/tasks at
|
|
99
|
+
once; an internal lock serializes the mutation-and-write so the shared part file
|
|
100
|
+
is never corrupted and no message is lost or duplicated. Each write is atomic, so
|
|
101
|
+
the file on disk is always a complete, valid trace.
|
|
102
|
+
|
|
103
|
+
## Targeting a message
|
|
104
|
+
|
|
105
|
+
The first argument to `log` is the message id, so overlapping messages never
|
|
106
|
+
collide — interleave them freely (as concurrent agents do) and each step routes
|
|
107
|
+
to the right message:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
wizardflow.log("msg-1", "classifier", "input", text_a)
|
|
111
|
+
wizardflow.log("msg-2", "classifier", "input", text_b) # a different message
|
|
112
|
+
wizardflow.log("msg-1", "generator", "output", answer_a)
|
|
113
|
+
wizardflow.end_message("msg-1") # writes msg-1
|
|
114
|
+
wizardflow.end_message("msg-2") # writes msg-2
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Pass the **string `id`**, never a handle object — safe to hand to a callback or
|
|
118
|
+
across threads. A message is created on first reference and finalized by
|
|
119
|
+
`end_message`; `end_message(id, title="...")` optionally gives it a human title.
|
|
120
|
+
|
|
121
|
+
## LangGraph: automatic topology
|
|
122
|
+
|
|
123
|
+
Instead of listing `nodes`/`edges` by hand, read them straight from a compiled
|
|
124
|
+
LangGraph app:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
app = workflow.compile(checkpointer=memory)
|
|
128
|
+
|
|
129
|
+
wizardflow.init_from_langgraph(app, output_dir="traces", file_prefix="trace")
|
|
130
|
+
|
|
131
|
+
wizardflow.log("msg-1", "planner", "Input", state) # runtime logging unchanged
|
|
132
|
+
wizardflow.end_message("msg-1")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You can keep the extracted LangGraph topology and still choose node accent
|
|
136
|
+
colors for important nodes:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
wizardflow.init_from_langgraph(
|
|
140
|
+
app,
|
|
141
|
+
output_dir="traces",
|
|
142
|
+
file_prefix="trace",
|
|
143
|
+
node_colors={
|
|
144
|
+
"router": "#A78BFA",
|
|
145
|
+
"retriever": "#22D3EE",
|
|
146
|
+
"generator": "#60A5FA",
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
By default, a color key that does not match an extracted node id raises a
|
|
152
|
+
`WizardFlowError` so typos fail fast. With `silent=True`, unknown color keys are
|
|
153
|
+
ignored.
|
|
154
|
+
|
|
155
|
+
It extracts node ids (keeping `__start__` / `__end__`) and directed edges, and
|
|
156
|
+
marks runtime branches with `"conditional": true` (deterministic and parallel
|
|
157
|
+
fan-out edges stay plain):
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"edges": [
|
|
162
|
+
{ "source": "__start__", "target": "router" },
|
|
163
|
+
{ "source": "router", "target": "planner", "conditional": true },
|
|
164
|
+
{ "source": "planner", "target": "final_response" }
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
LangGraph is **not** a dependency — extraction is duck-typed on `app.get_graph()`.
|
|
170
|
+
A non-LangGraph object raises `LangGraphExtractionError`; missing conditional
|
|
171
|
+
metadata never fails extraction (the edge is just emitted plain). Call it
|
|
172
|
+
**after `compile()`**, when the topology actually exists.
|
|
173
|
+
|
|
174
|
+
## API (v0.1)
|
|
175
|
+
|
|
176
|
+
- `init(output_dir=, file_prefix="wizardflow", name=, description=, nodes=, edges=, meta=, silent=False, max_bytes=256_000) -> Client`
|
|
177
|
+
- `description` lands in `meta.description` (matches the schema field).
|
|
178
|
+
- `nodes=` enables fast-fail: `log()` to an undeclared node raises
|
|
179
|
+
`UnknownNodeError` immediately (unless silenced).
|
|
180
|
+
- `output_dir` is optional; omitted, traces are written in cwd.
|
|
181
|
+
- `file_prefix` is optional; omitted, filenames start with `wizardflow`.
|
|
182
|
+
- `max_bytes` caps each part file before rotation (see below).
|
|
183
|
+
- `init_from_langgraph(app, output_dir=, file_prefix="wizardflow", name=, description=, meta=, node_colors=, silent=False, max_bytes=...) -> Client`
|
|
184
|
+
- same as `init`, but `nodes`/`edges` come from `app.get_graph()`.
|
|
185
|
+
- `node_colors` maps extracted node ids to CSS colors such as `"#A78BFA"`.
|
|
186
|
+
- `log(id, node, label=None, content=None)` — the first positional is the
|
|
187
|
+
**message id**, the second is the node. With `label`/`content` it records a
|
|
188
|
+
payload; bare `log(id, "node")` records a visit with no payloads. The message
|
|
189
|
+
is created on first reference; this only accumulates in memory.
|
|
190
|
+
- `end_message(id, title=None)` — finalize a message and write the trace;
|
|
191
|
+
returns the current trace path. The **only** call that touches disk. Optional `title`
|
|
192
|
+
sets the message's human title. Idempotent.
|
|
193
|
+
- `Client.current_path` — the trace file currently being written.
|
|
194
|
+
- `to_dict()` / `to_json()` — inspect the active part (completed messages).
|
|
195
|
+
|
|
196
|
+
### How saving works
|
|
197
|
+
|
|
198
|
+
`end_message` triggers an **atomic write**: write to `<part>.tmp`, then
|
|
199
|
+
`os.replace` over the part file. The file is always a complete, loadable
|
|
200
|
+
`AgentTraceFile`. Only **completed** messages are written; an in-progress
|
|
201
|
+
message lives in memory until it ends.
|
|
202
|
+
|
|
203
|
+
### Rotation (no single huge file)
|
|
204
|
+
|
|
205
|
+
There's no natural "end" to a chatbot trace, so the SDK caps file size instead.
|
|
206
|
+
Each run writes a timestamped entry file whose name carries the run-start time:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
wizardflow__2026-06-08T16-29-09-123Z.json
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The name's `wizardflow` is the `file_prefix`; the timestamp is captured when
|
|
213
|
+
`init()` creates the client. If the active part would exceed `max_bytes` (~256
|
|
214
|
+
KB by default), it's sealed and the next message starts a fresh part:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
wizardflow__2026-06-08T16-29-09-123Z.json
|
|
218
|
+
wizardflow__2026-06-08T16-29-09-123Z__part2.json
|
|
219
|
+
wizardflow__2026-06-08T16-29-09-123Z__part3.json
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Rotation only ever happens at a **message boundary**, never mid-message (a lone
|
|
223
|
+
message larger than the cap gets its own oversized part). A smaller cap keeps
|
|
224
|
+
each rewrite — and the write lock held across it — short, which matters when
|
|
225
|
+
many agents end messages concurrently; raise it for fewer files at the cost of
|
|
226
|
+
heavier rewrites. `max_bytes` is clamped to a hard ceiling (1 MB) so an
|
|
227
|
+
accidental huge value can't stall concurrent writers. Each part is a
|
|
228
|
+
**self-contained, valid trace** (full graph + its slice of messages), chained
|
|
229
|
+
via `meta`:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
"meta": { "part": 2, "prevPart": "...Z.json", "nextPart": "...__part3.json" }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
A single-part trace stays clean (no `part` metadata). There's no `partCount` —
|
|
236
|
+
the total is genuinely unknown while a continuous run is still logging; follow
|
|
237
|
+
`nextPart` to walk to the end. Since names are timestamped, read the real file
|
|
238
|
+
back from the client's `current_path` (also the path `end_message` returns).
|
|
239
|
+
|
|
240
|
+
### Logging
|
|
241
|
+
|
|
242
|
+
Rotation emits an `INFO` notice on the `wizardflow` logger. Following library
|
|
243
|
+
convention, the SDK attaches a `NullHandler` and configures nothing — you see
|
|
244
|
+
nothing unless you opt in:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
import logging
|
|
248
|
+
logging.getLogger("wizardflow").setLevel(logging.INFO)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
This is separate from `silent=` (which governs raise-vs-swallow for *errors*).
|
|
252
|
+
|
|
253
|
+
### Step folding
|
|
254
|
+
|
|
255
|
+
Consecutive `log()` calls to the **same** node within a message fold into a
|
|
256
|
+
single step with multiple payloads (e.g. a router step carrying both
|
|
257
|
+
`llm_input` and `llm_output`). A `log()` to a different node starts a new step.
|
|
258
|
+
|
|
259
|
+
## Examples
|
|
260
|
+
|
|
261
|
+
Fuller runnable examples live in
|
|
262
|
+
[`examples/`](https://github.com/lkleonk/wizardflow/tree/main/sdk/python/examples):
|
|
263
|
+
|
|
264
|
+
- **`quickstart.py`** — a small linear agent; two messages, the second finalized
|
|
265
|
+
with an `end_message(..., title=...)` title.
|
|
266
|
+
- **`multibranch.py`** — `router` fans out into a planner/tool path and a
|
|
267
|
+
retriever path that rejoin at `generator`. Two messages take different
|
|
268
|
+
branches, so each logs only the nodes it actually visited.
|
|
269
|
+
|
|
270
|
+
## CLI
|
|
271
|
+
|
|
272
|
+
The `wizardflow` command has three subcommands: `ui`, `md`, and `html`. Every
|
|
273
|
+
one takes the trace file as a positional argument **or** via `--path` (pass one,
|
|
274
|
+
not both):
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
wizardflow ui run.json
|
|
278
|
+
wizardflow md run.json
|
|
279
|
+
wizardflow html run.json
|
|
280
|
+
# --path is equivalent everywhere:
|
|
281
|
+
wizardflow ui --path run.json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### `wizardflow ui` — local viewer
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
wizardflow ui run.json [--host 127.0.0.1] [--port 0] [--no-open]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Binds a stdlib HTTP server, serves the static WizardFlow UI bundled in the SDK
|
|
291
|
+
package, and opens the selected trace in your browser.
|
|
292
|
+
|
|
293
|
+
| flag | default | meaning |
|
|
294
|
+
| --- | --- | --- |
|
|
295
|
+
| `--host` | `127.0.0.1` | interface to bind |
|
|
296
|
+
| `--port` | `0` | port to bind; `0` asks the OS for a free port |
|
|
297
|
+
| `--no-open` | off | print the local URL instead of launching a browser |
|
|
298
|
+
|
|
299
|
+
### `wizardflow md` — export to Markdown
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
wizardflow md run.json # -> stdout
|
|
303
|
+
wizardflow md run.json -o run.md # -> file
|
|
304
|
+
wizardflow md run.json --no-mermaid # omit the graph diagram
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Renders the full trace as Markdown: a metadata table, a Mermaid `flowchart` of
|
|
308
|
+
the graph, then each message's steps and payloads (scalars inline; multi-line
|
|
309
|
+
strings and dict/list values in fenced code blocks; conditional edges drawn
|
|
310
|
+
dashed).
|
|
311
|
+
|
|
312
|
+
| flag | default | meaning |
|
|
313
|
+
| --- | --- | --- |
|
|
314
|
+
| `-o`, `--output` | — | write to this file instead of stdout |
|
|
315
|
+
| `--mermaid` | on | include the Mermaid graph diagram |
|
|
316
|
+
| `--no-mermaid` | — | omit the Mermaid graph diagram |
|
|
317
|
+
|
|
318
|
+
### `wizardflow html` — export to HTML
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
wizardflow html run.json # -> stdout
|
|
322
|
+
wizardflow html run.json -o run.html # -> file
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Emits a single self-contained document — inline CSS, **no JavaScript, no
|
|
326
|
+
external assets** — that opens offline in any browser and follows your OS
|
|
327
|
+
light/dark mode. Messages-only by design: no graph/Mermaid (use `md` for that).
|
|
328
|
+
|
|
329
|
+
| flag | default | meaning |
|
|
330
|
+
| --- | --- | --- |
|
|
331
|
+
| `-o`, `--output` | — | write to this file instead of stdout |
|
|
332
|
+
|
|
333
|
+
Rendered samples (`*.md`, `*.html`) live in the [repo's
|
|
334
|
+
`examples/`](https://github.com/lkleonk/wizardflow/tree/main/sdk/python/examples).
|
|
335
|
+
|
|
336
|
+
## Status / not yet
|
|
337
|
+
|
|
338
|
+
- **Timestamps** are wall-clock at log time — fine for slow/live runs, wrong if
|
|
339
|
+
steps fire faster than ms resolution or you import after the fact. (Open
|
|
340
|
+
design item: explicit per-step timestamps.)
|