ipygraph 0.1.2__tar.gz → 1.14.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.
- {ipygraph-0.1.2 → ipygraph-1.14.0}/PKG-INFO +72 -62
- ipygraph-1.14.0/README.md +158 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/_version.py +1 -1
- ipygraph-1.14.0/myextension/app.py +29 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/labextension/package.json +2 -2
- ipygraph-0.1.2/myextension/labextension/static/694.cb943d1ae1290ce8.js → ipygraph-1.14.0/myextension/labextension/static/694.255a445d5c77b9d5.js +1 -1
- ipygraph-0.1.2/myextension/labextension/static/remoteEntry.358ed3d3675a8367.js → ipygraph-1.14.0/myextension/labextension/static/remoteEntry.5e908823ed118c33.js +7 -7
- {ipygraph-0.1.2 → ipygraph-1.14.0}/package.json +1 -1
- {ipygraph-0.1.2 → ipygraph-1.14.0}/pyproject.toml +4 -0
- ipygraph-0.1.2/README.md +0 -149
- {ipygraph-0.1.2 → ipygraph-1.14.0}/.copier-answers.yml +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/.gitignore +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/.prettierignore +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/.yarnrc.yml +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/CHANGELOG.md +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/LICENSE +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/NOTICE +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/RELEASE.md +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/babel.config.js +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/install.json +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/jest.config.js +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/jupyter-config/server-config/myextension.json +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/__init__.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/agent.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/agent_providers.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/contents_manager.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/handlers.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/kaggle.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/kernel_manager.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/labextension/static/232.b7f55d8d983399ee.js +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/labextension/static/style.js +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/myextension/labextension/static/third-party-licenses.json +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/setup.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/__tests__/myextension.spec.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/agentActions.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/agentApi.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/agentPanel.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/agentTools.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/api.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/branchSelector.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/contract.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/datasetPicker.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/diagramView.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/document.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/dropUpload.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/graphView.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/icon.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/index.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/mdRender.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/model.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/parallel.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/secretsPanel.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/src/widget.ts +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/style/base.css +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/style/index.css +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/style/index.js +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/test_backend.py +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/tsconfig.json +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/tsconfig.test.json +0 -0
- {ipygraph-0.1.2 → ipygraph-1.14.0}/yarn.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipygraph
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.14.0
|
|
4
4
|
Summary: Turn your Jupyter notebook into a branching graph of experiments — a JupyterLab extension with a plan-first AI copilot, branch isolation, and fork-join parallel execution.
|
|
5
5
|
Project-URL: Homepage, https://github.com/jupyterlab/jupyterlab-ipygb
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/jupyterlab/jupyterlab-ipygb/issues
|
|
@@ -221,18 +221,21 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
221
221
|
Requires-Python: >=3.10
|
|
222
222
|
Requires-Dist: dill
|
|
223
223
|
Requires-Dist: jupyter-server<3,>=2.0.0
|
|
224
|
+
Requires-Dist: jupyterlab<5,>=4.0.0
|
|
224
225
|
Description-Content-Type: text/markdown
|
|
225
226
|
|
|
226
|
-
<
|
|
227
|
+
<p align="center">
|
|
228
|
+
<img src="docs/images/logo-banner.png" alt="ipyGraph — Notebooks that branch" width="620">
|
|
229
|
+
</p>
|
|
227
230
|
|
|
228
231
|
<p align="center"><b>Turn your Jupyter notebook into a branching graph of experiments.</b></p>
|
|
229
232
|
|
|
230
233
|
<p align="center">
|
|
231
|
-
<img src="docs/images/plan-view-detail.png" alt="ipyGraph plan view — a chess evaluation model built as a branching graph of self-describing steps" width="
|
|
234
|
+
<img src="docs/images/plan-view-detail.png" alt="ipyGraph plan view — a chess evaluation model built as a branching graph of self-describing steps" width="820">
|
|
232
235
|
</p>
|
|
233
236
|
|
|
234
237
|
[](https://github.com/jupyterlab/jupyterlab-ipygb/actions/workflows/build.yml)
|
|
235
|
-
JupyterLab 4
|
|
238
|
+
JupyterLab 4 · `.ipygb` file format · runs at `/graph` · source-available
|
|
236
239
|
|
|
237
240
|
---
|
|
238
241
|
|
|
@@ -244,92 +247,105 @@ Notebooks are linear. **Experimentation isn't.**
|
|
|
244
247
|
|
|
245
248
|
## What ipyGraph is
|
|
246
249
|
|
|
247
|
-
**ipyGraph** (a.k.a. *Graphbook
|
|
250
|
+
**ipyGraph** (a.k.a. *Graphbook*) is a JupyterLab 4 extension that turns a notebook into a **branching DAG of cells**. Every experiment is a *branch* on a living graph instead of a copy-pasted block you'll never find again. A shared data-prep step forks into competing models; each branch keeps its own state; you compare tips and keep the winner.
|
|
248
251
|
|
|
249
252
|
It ships with a **plan-first AI copilot** that designs the experiment graph, writes the code, runs it, and iterates — and an execution engine where **one branch can never silently contaminate another**.
|
|
250
253
|
|
|
251
|
-
|
|
254
|
+
You launch it with its own command and it opens at `/graph`:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pip install ipygraph
|
|
258
|
+
jupytergraph # opens JupyterLab at http://localhost:8888/graph
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## The `.ipygb` file format
|
|
262
|
+
|
|
263
|
+
ipyGraph adds a new document type: **`.ipygb`** (a "Graphbook"). It's a **superset of the Jupyter notebook format** — the same `nbformat` JSON with cells, outputs, and metadata — with a little extra per-cell `ipygb` metadata that records the graph:
|
|
264
|
+
|
|
265
|
+
```jsonc
|
|
266
|
+
"metadata": {
|
|
267
|
+
"ipygb": {
|
|
268
|
+
"parent_id": "root-1", // which cell's outputs this one builds on
|
|
269
|
+
"branch_name": "encode-nn", // the branch this cell starts (optional)
|
|
270
|
+
"parent_ids": ["a", "b"], // 2+ parents = a merge cell
|
|
271
|
+
"description": "…", // shown in Plan view
|
|
272
|
+
"inputs": { … } // explicit variables a merge consumes
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Because it's a superset, the kernel, outputs, and rendering are ordinary Jupyter — nothing proprietary about how your code runs. And you can always **export any root→tip path back to a plain `.ipynb`** to share or hand off.
|
|
252
278
|
|
|
253
279
|
---
|
|
254
280
|
|
|
255
281
|
## See it
|
|
256
282
|
|
|
257
|
-
The screenshots below are a real ipyGraph project
|
|
283
|
+
The screenshots below are a real ipyGraph project — *predicting a chess engine's position evaluation from the board.* A shared prep step splits into two feature representations (tabular vs. board-planes), which feed different model families.
|
|
258
284
|
|
|
259
285
|
**Plan view — the whole experiment reads like a flowchart, and every step documents itself:**
|
|
260
286
|
|
|
261
|
-
<p align="center"><img src="docs/images/graph-plan-view.png" alt="Plan view: the full experiment graph" width="
|
|
287
|
+
<p align="center"><img src="docs/images/graph-plan-view.png" alt="Plan view: the full experiment graph" width="820"></p>
|
|
262
288
|
|
|
263
289
|
**Code view — those same nodes are real, runnable Jupyter cells with live outputs:**
|
|
264
290
|
|
|
265
|
-
<p align="center"><img src="docs/images/graph-code-view.png" alt="Code view: the same graph as runnable cells" width="
|
|
291
|
+
<p align="center"><img src="docs/images/graph-code-view.png" alt="Code view: the same graph as runnable cells" width="820"></p>
|
|
266
292
|
|
|
267
293
|
**The AI copilot builds and iterates on the graph beside you (Autopilot is on by default):**
|
|
268
294
|
|
|
269
|
-
<p align="center"><img src="docs/images/graph-with-assistant.png" alt="ipyGraph assistant panel" width="
|
|
295
|
+
<p align="center"><img src="docs/images/graph-with-assistant.png" alt="ipyGraph assistant panel with a live conversation" width="820"></p>
|
|
270
296
|
|
|
271
297
|
---
|
|
272
298
|
|
|
273
|
-
##
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
- **
|
|
280
|
-
- **
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
- **Describe a goal, get a plan on the graph.** The agent drafts the experiment as plan cells first — you approve, then it builds. It thinks in *dataflow*: shared prep on the trunk, a separate encoder per model family, models under their encoder.
|
|
284
|
-
- **Multi-agent under the hood.** An architect-vs-critic design debate picks the model shortlist; parallel coder sub-agents fill in the branches concurrently.
|
|
285
|
-
- **Finds data for you.** A full-screen dataset picker searches Kaggle and profiles candidates so you can choose the right one.
|
|
286
|
-
- **Uses your Claude subscription** via the Claude Agent SDK (no API key needed), or bring an API key for Anthropic / OpenAI / Gemini.
|
|
287
|
-
- **Minimal questions, fast turns** — it infers sensible defaults, asks only when a choice genuinely matters, and shows a live progress spinner while it works.
|
|
288
|
-
|
|
289
|
-
### 🔒 Reproducible, isolated execution
|
|
290
|
-
- **A cell's variables come only from its own path.** One branch can never be polluted by code you ran on another branch.
|
|
291
|
-
- **▶ Run** a single cell, or **⇥ Run to here** to rebuild a path from the start on a clean, isolated kernel.
|
|
292
|
-
- **Fork-point kernel snapshots.** State is saved at each branch point (fast, on-disk), so a sibling branch **resumes from a saved copy instead of replaying the whole path** — and the copy rolls forward as you iterate down a branch.
|
|
293
|
-
- **True fork-join parallelism.** *"Run Parallel"* on a 1-prep → 3-model graph runs the prep **once** and trains the three models **concurrently** on separate kernels.
|
|
294
|
-
- **Staleness badges.** A cell warns you — *"changed after last run"* / *"upstream changed"* — when an edit made its last result stale, naming the variables that drifted.
|
|
295
|
-
|
|
296
|
-
### 🔀 Merge & compare
|
|
297
|
-
- **Visual merge contracts.** Combine branches by **clicking the variables you want** right in each branch's code — hover to trace every occurrence, click to add it to the merge. No copy-paste, no name clashes.
|
|
298
|
-
- Merge cells receive their inputs across kernels via an explicit, editable contract.
|
|
299
|
-
|
|
300
|
-
### 📓 Fits your existing workflow
|
|
301
|
-
- **It's still Jupyter.** Real kernels, real outputs, MathJax, the works.
|
|
302
|
-
- **Notebook side-view.** Open the linear notebook beside the graph any time.
|
|
303
|
-
- **Export a path → `.ipynb`.** Pick a root-to-tip path and save it as an ordinary notebook to share or hand off.
|
|
299
|
+
## Parallel execution
|
|
300
|
+
|
|
301
|
+
Branches are independent, so ipyGraph runs them like an experiment *should* run: **shared prep once, models concurrently.**
|
|
302
|
+
|
|
303
|
+
<p align="center"><img src="docs/images/parallelism.png" alt="Fork-join parallelism: shared prep runs once, branches train in parallel on separate kernels, a merge compares them" width="820"></p>
|
|
304
|
+
|
|
305
|
+
- **Fork-join, not replay.** *Run Parallel* on a 1-prep → 3-model graph runs the prep **once**, then trains the three models **at the same time**, each on its own fresh kernel. A 10-model bake-off pays for data loading once, not ten times.
|
|
306
|
+
- **Fork-point kernel snapshots.** The kernel's state is saved at each branch point, so a sibling branch **resumes from a saved copy** instead of re-running the whole path — and the copy rolls forward as you iterate down a branch. Tweaking the deepest cell costs one cell's runtime plus a state restore.
|
|
307
|
+
- **True isolation.** A branch's variables come only from its own path. One branch can never be polluted by code you ran on another — proven by identical results whether a branch runs alone or beside others.
|
|
308
|
+
- **▶ Run** a single cell or **⇥ Run to here** to rebuild a path from scratch on a clean isolated kernel.
|
|
304
309
|
|
|
305
310
|
---
|
|
306
311
|
|
|
312
|
+
## More features
|
|
313
|
+
|
|
314
|
+
- 🕸️ **The graph is the interface.** Graph View is the default; flip any card between **Plan view** (titles + prose) and **Code view** (editors + outputs). Edit code, rename branches, and click-to-edit descriptions in place. Arrow-key navigation; a **👁 Watch agent** button pans to whatever the agent is touching.
|
|
315
|
+
- 🤖 **Plan-first AI copilot.** Describe a goal → it drafts the plan on the graph, you approve, then it builds and iterates. Multi-agent: an architect-vs-critic debate picks the model shortlist; parallel coder sub-agents fill the branches. Finds data for you via a full-screen Kaggle dataset picker. Uses your Claude subscription (Claude Agent SDK, no API key) or an Anthropic/OpenAI/Gemini key.
|
|
316
|
+
- 🔀 **Visual merge contracts.** Combine branches by **clicking the variables you want** in each branch's code — hover to trace every occurrence, click to add it to the merge. No copy-paste, no name clashes.
|
|
317
|
+
- 🩺 **Staleness badges.** A cell warns you — *"changed after last run"* / *"upstream changed"* — when an edit made its last result stale, naming the variables that drifted.
|
|
318
|
+
- 📓 **Still Jupyter.** Real kernels, outputs, MathJax. Open the linear notebook side-by-side, and export any path to a plain `.ipynb`.
|
|
319
|
+
|
|
307
320
|
## Why it's useful
|
|
308
321
|
|
|
309
|
-
- **You never lose an experiment
|
|
310
|
-
- **
|
|
311
|
-
- **Iteration is cheap
|
|
312
|
-
- **The graph is self-documenting
|
|
313
|
-
- **AI does the busywork, you keep control
|
|
322
|
+
- **You never lose an experiment** — every model you try is a branch you can see, re-run, and compare.
|
|
323
|
+
- **Results are actually reproducible** — path isolation + "Run to here" means a number came from *that branch's code and nothing else*.
|
|
324
|
+
- **Iteration is cheap** — fork snapshots + fork-join mean expensive prep is paid for once and deep edits re-run one cell.
|
|
325
|
+
- **The graph is self-documenting** — Plan view turns a notebook into something a teammate (or future you) can read top to bottom.
|
|
326
|
+
- **AI does the busywork, you keep control** — it plans, codes, runs, and compares; you approve the plan and own the graph.
|
|
314
327
|
|
|
315
328
|
Great for **ML engineers and researchers** running model bake-offs, feature-engineering sweeps, and architecture searches — anyone whose "quick experiment" notebook always spirals.
|
|
316
329
|
|
|
317
330
|
---
|
|
318
331
|
|
|
319
|
-
## Install
|
|
332
|
+
## Install & run
|
|
320
333
|
|
|
321
334
|
```bash
|
|
322
335
|
pip install ipygraph
|
|
336
|
+
jupytergraph # launches JupyterLab at /graph
|
|
323
337
|
```
|
|
324
338
|
|
|
325
|
-
Then
|
|
339
|
+
Then create a new **Graphbook (.ipygb)** from the launcher, or open an existing `.ipygb` — it opens in Graph View by default.
|
|
340
|
+
|
|
341
|
+
**Requirements:** JupyterLab ≥ 4, Python ≥ 3.10. For the AI copilot, either the `claude` CLI (subscription login) or an API key for your provider. Kaggle dataset search needs Kaggle credentials (entered once, stored server-side).
|
|
326
342
|
|
|
327
|
-
|
|
343
|
+
> `jupytergraph` is JupyterLab mounted at `/graph` with ipyGraph branding. Everything else — kernels, terminals, other extensions — works exactly as in JupyterLab.
|
|
328
344
|
|
|
329
345
|
## Quick start
|
|
330
346
|
|
|
331
|
-
1. Launcher → **Graphbook (.ipygb)**.
|
|
332
|
-
2. Open the **Assistant**
|
|
347
|
+
1. Run `jupytergraph`, then Launcher → **Graphbook (.ipygb)**.
|
|
348
|
+
2. Open the **Assistant** and describe what to build — e.g. *"train tree and neural models to predict chess move quality, and iterate."*
|
|
333
349
|
3. Approve the plan it draws on the graph → it builds, runs, and compares the branches.
|
|
334
350
|
4. Fork your own branches, **Run Parallel**, and **Save a path as `.ipynb`** when you're happy.
|
|
335
351
|
|
|
@@ -337,26 +353,20 @@ Then start JupyterLab and create a new **Graphbook (.ipygb)** from the launcher,
|
|
|
337
353
|
|
|
338
354
|
## Development
|
|
339
355
|
|
|
340
|
-
|
|
356
|
+
You will need NodeJS to build the extension. `jlpm` is JupyterLab's pinned yarn.
|
|
341
357
|
|
|
342
358
|
```bash
|
|
343
|
-
|
|
344
|
-
python -m venv .venv
|
|
345
|
-
source .venv/bin/activate
|
|
359
|
+
python -m venv .venv && source .venv/bin/activate
|
|
346
360
|
pip install --editable "."
|
|
347
|
-
|
|
348
|
-
# Link the dev build into JupyterLab
|
|
349
361
|
jupyter labextension develop . --overwrite
|
|
350
|
-
|
|
351
|
-
# Rebuild the TypeScript after changes
|
|
352
|
-
jlpm build
|
|
362
|
+
jlpm build # rebuild the TypeScript after changes
|
|
353
363
|
```
|
|
354
364
|
|
|
355
|
-
Watch mode (rebuild on save) in one terminal,
|
|
365
|
+
Watch mode (rebuild on save) in one terminal, the app in another:
|
|
356
366
|
|
|
357
367
|
```bash
|
|
358
368
|
jlpm watch
|
|
359
|
-
|
|
369
|
+
jupytergraph
|
|
360
370
|
```
|
|
361
371
|
|
|
362
372
|
Tests: `jlpm test` (Jest) and Playwright/Galata integration tests under [`ui-tests`](./ui-tests/README.md). Packaging: see [RELEASE](RELEASE.md).
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/logo-banner.png" alt="ipyGraph — Notebooks that branch" width="620">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><b>Turn your Jupyter notebook into a branching graph of experiments.</b></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="docs/images/plan-view-detail.png" alt="ipyGraph plan view — a chess evaluation model built as a branching graph of self-describing steps" width="820">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
[](https://github.com/jupyterlab/jupyterlab-ipygb/actions/workflows/build.yml)
|
|
12
|
+
JupyterLab 4 · `.ipygb` file format · runs at `/graph` · source-available
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The problem
|
|
17
|
+
|
|
18
|
+
Every ML notebook starts clean and ends as a 2,000-line scroll of dead cells, half-tried models, and *"wait — which run produced this number?"* You try five approaches, comment out four, lose track of which features fed which model, and can never reproduce the good result. AI can write the code for you now — but your **experiments** are still a linear mess.
|
|
19
|
+
|
|
20
|
+
Notebooks are linear. **Experimentation isn't.**
|
|
21
|
+
|
|
22
|
+
## What ipyGraph is
|
|
23
|
+
|
|
24
|
+
**ipyGraph** (a.k.a. *Graphbook*) is a JupyterLab 4 extension that turns a notebook into a **branching DAG of cells**. Every experiment is a *branch* on a living graph instead of a copy-pasted block you'll never find again. A shared data-prep step forks into competing models; each branch keeps its own state; you compare tips and keep the winner.
|
|
25
|
+
|
|
26
|
+
It ships with a **plan-first AI copilot** that designs the experiment graph, writes the code, runs it, and iterates — and an execution engine where **one branch can never silently contaminate another**.
|
|
27
|
+
|
|
28
|
+
You launch it with its own command and it opens at `/graph`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install ipygraph
|
|
32
|
+
jupytergraph # opens JupyterLab at http://localhost:8888/graph
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## The `.ipygb` file format
|
|
36
|
+
|
|
37
|
+
ipyGraph adds a new document type: **`.ipygb`** (a "Graphbook"). It's a **superset of the Jupyter notebook format** — the same `nbformat` JSON with cells, outputs, and metadata — with a little extra per-cell `ipygb` metadata that records the graph:
|
|
38
|
+
|
|
39
|
+
```jsonc
|
|
40
|
+
"metadata": {
|
|
41
|
+
"ipygb": {
|
|
42
|
+
"parent_id": "root-1", // which cell's outputs this one builds on
|
|
43
|
+
"branch_name": "encode-nn", // the branch this cell starts (optional)
|
|
44
|
+
"parent_ids": ["a", "b"], // 2+ parents = a merge cell
|
|
45
|
+
"description": "…", // shown in Plan view
|
|
46
|
+
"inputs": { … } // explicit variables a merge consumes
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Because it's a superset, the kernel, outputs, and rendering are ordinary Jupyter — nothing proprietary about how your code runs. And you can always **export any root→tip path back to a plain `.ipynb`** to share or hand off.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## See it
|
|
56
|
+
|
|
57
|
+
The screenshots below are a real ipyGraph project — *predicting a chess engine's position evaluation from the board.* A shared prep step splits into two feature representations (tabular vs. board-planes), which feed different model families.
|
|
58
|
+
|
|
59
|
+
**Plan view — the whole experiment reads like a flowchart, and every step documents itself:**
|
|
60
|
+
|
|
61
|
+
<p align="center"><img src="docs/images/graph-plan-view.png" alt="Plan view: the full experiment graph" width="820"></p>
|
|
62
|
+
|
|
63
|
+
**Code view — those same nodes are real, runnable Jupyter cells with live outputs:**
|
|
64
|
+
|
|
65
|
+
<p align="center"><img src="docs/images/graph-code-view.png" alt="Code view: the same graph as runnable cells" width="820"></p>
|
|
66
|
+
|
|
67
|
+
**The AI copilot builds and iterates on the graph beside you (Autopilot is on by default):**
|
|
68
|
+
|
|
69
|
+
<p align="center"><img src="docs/images/graph-with-assistant.png" alt="ipyGraph assistant panel with a live conversation" width="820"></p>
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Parallel execution
|
|
74
|
+
|
|
75
|
+
Branches are independent, so ipyGraph runs them like an experiment *should* run: **shared prep once, models concurrently.**
|
|
76
|
+
|
|
77
|
+
<p align="center"><img src="docs/images/parallelism.png" alt="Fork-join parallelism: shared prep runs once, branches train in parallel on separate kernels, a merge compares them" width="820"></p>
|
|
78
|
+
|
|
79
|
+
- **Fork-join, not replay.** *Run Parallel* on a 1-prep → 3-model graph runs the prep **once**, then trains the three models **at the same time**, each on its own fresh kernel. A 10-model bake-off pays for data loading once, not ten times.
|
|
80
|
+
- **Fork-point kernel snapshots.** The kernel's state is saved at each branch point, so a sibling branch **resumes from a saved copy** instead of re-running the whole path — and the copy rolls forward as you iterate down a branch. Tweaking the deepest cell costs one cell's runtime plus a state restore.
|
|
81
|
+
- **True isolation.** A branch's variables come only from its own path. One branch can never be polluted by code you ran on another — proven by identical results whether a branch runs alone or beside others.
|
|
82
|
+
- **▶ Run** a single cell or **⇥ Run to here** to rebuild a path from scratch on a clean isolated kernel.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## More features
|
|
87
|
+
|
|
88
|
+
- 🕸️ **The graph is the interface.** Graph View is the default; flip any card between **Plan view** (titles + prose) and **Code view** (editors + outputs). Edit code, rename branches, and click-to-edit descriptions in place. Arrow-key navigation; a **👁 Watch agent** button pans to whatever the agent is touching.
|
|
89
|
+
- 🤖 **Plan-first AI copilot.** Describe a goal → it drafts the plan on the graph, you approve, then it builds and iterates. Multi-agent: an architect-vs-critic debate picks the model shortlist; parallel coder sub-agents fill the branches. Finds data for you via a full-screen Kaggle dataset picker. Uses your Claude subscription (Claude Agent SDK, no API key) or an Anthropic/OpenAI/Gemini key.
|
|
90
|
+
- 🔀 **Visual merge contracts.** Combine branches by **clicking the variables you want** in each branch's code — hover to trace every occurrence, click to add it to the merge. No copy-paste, no name clashes.
|
|
91
|
+
- 🩺 **Staleness badges.** A cell warns you — *"changed after last run"* / *"upstream changed"* — when an edit made its last result stale, naming the variables that drifted.
|
|
92
|
+
- 📓 **Still Jupyter.** Real kernels, outputs, MathJax. Open the linear notebook side-by-side, and export any path to a plain `.ipynb`.
|
|
93
|
+
|
|
94
|
+
## Why it's useful
|
|
95
|
+
|
|
96
|
+
- **You never lose an experiment** — every model you try is a branch you can see, re-run, and compare.
|
|
97
|
+
- **Results are actually reproducible** — path isolation + "Run to here" means a number came from *that branch's code and nothing else*.
|
|
98
|
+
- **Iteration is cheap** — fork snapshots + fork-join mean expensive prep is paid for once and deep edits re-run one cell.
|
|
99
|
+
- **The graph is self-documenting** — Plan view turns a notebook into something a teammate (or future you) can read top to bottom.
|
|
100
|
+
- **AI does the busywork, you keep control** — it plans, codes, runs, and compares; you approve the plan and own the graph.
|
|
101
|
+
|
|
102
|
+
Great for **ML engineers and researchers** running model bake-offs, feature-engineering sweeps, and architecture searches — anyone whose "quick experiment" notebook always spirals.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Install & run
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install ipygraph
|
|
110
|
+
jupytergraph # launches JupyterLab at /graph
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Then create a new **Graphbook (.ipygb)** from the launcher, or open an existing `.ipygb` — it opens in Graph View by default.
|
|
114
|
+
|
|
115
|
+
**Requirements:** JupyterLab ≥ 4, Python ≥ 3.10. For the AI copilot, either the `claude` CLI (subscription login) or an API key for your provider. Kaggle dataset search needs Kaggle credentials (entered once, stored server-side).
|
|
116
|
+
|
|
117
|
+
> `jupytergraph` is JupyterLab mounted at `/graph` with ipyGraph branding. Everything else — kernels, terminals, other extensions — works exactly as in JupyterLab.
|
|
118
|
+
|
|
119
|
+
## Quick start
|
|
120
|
+
|
|
121
|
+
1. Run `jupytergraph`, then Launcher → **Graphbook (.ipygb)**.
|
|
122
|
+
2. Open the **Assistant** and describe what to build — e.g. *"train tree and neural models to predict chess move quality, and iterate."*
|
|
123
|
+
3. Approve the plan it draws on the graph → it builds, runs, and compares the branches.
|
|
124
|
+
4. Fork your own branches, **Run Parallel**, and **Save a path as `.ipynb`** when you're happy.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
You will need NodeJS to build the extension. `jlpm` is JupyterLab's pinned yarn.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python -m venv .venv && source .venv/bin/activate
|
|
134
|
+
pip install --editable "."
|
|
135
|
+
jupyter labextension develop . --overwrite
|
|
136
|
+
jlpm build # rebuild the TypeScript after changes
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Watch mode (rebuild on save) in one terminal, the app in another:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
jlpm watch
|
|
143
|
+
jupytergraph
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Tests: `jlpm test` (Jest) and Playwright/Galata integration tests under [`ui-tests`](./ui-tests/README.md). Packaging: see [RELEASE](RELEASE.md).
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
**ipyGraph / Graphbook** is **source-available**, not OSI open source. See [`LICENSE`](./LICENSE) for the full terms and [`NOTICE`](./NOTICE) for third-party components.
|
|
153
|
+
|
|
154
|
+
- **Free for individuals and any non-commercial use** — development, evaluation, personal projects, academic research, and teaching.
|
|
155
|
+
- **Free for an organization's internal use up to 20 users (seats).**
|
|
156
|
+
- **A paid commercial license (with a 5% royalty) is required** to charge for something built on it, offer it as a hosted/SaaS service, or exceed 20 internal users. Contact **anishchelliah.cr@gmail.com** to arrange one.
|
|
157
|
+
|
|
158
|
+
This license covers only this project's own code. JupyterLab, Lumino, React, and other dependencies keep their own licenses (see [`NOTICE`](./NOTICE)); nothing here changes them.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`jupytergraph` — launch a JupyterLab server branded for ipyGraph and served
|
|
3
|
+
at /graph instead of /lab.
|
|
4
|
+
|
|
5
|
+
Same JupyterLab under the hood, just mounted at /graph so the product feels
|
|
6
|
+
like its own tool rather than "a JupyterLab extension". The server loads
|
|
7
|
+
JupyterLab via its own entry point, so a subclass's traits don't reach the
|
|
8
|
+
mounted app — the reliable lever is `LabApp.app_url` config, which we
|
|
9
|
+
default to /graph (still overridable on the command line).
|
|
10
|
+
"""
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from jupyterlab.labapp import LabApp
|
|
14
|
+
|
|
15
|
+
GRAPH_URL = "/graph"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
argv = sys.argv[1:]
|
|
20
|
+
# Default the mount + landing page to /graph unless the user overrode it.
|
|
21
|
+
if not any(a.startswith("--LabApp.app_url") for a in argv):
|
|
22
|
+
argv = [f"--LabApp.app_url={GRAPH_URL}"] + argv
|
|
23
|
+
if not any(a.startswith("--LabApp.default_url") for a in argv):
|
|
24
|
+
argv = [f"--LabApp.default_url={GRAPH_URL}"] + argv
|
|
25
|
+
LabApp.launch_instance(argv=argv)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myextension",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Turn your Jupyter notebook into a branching graph of experiments — a JupyterLab extension with a plan-first AI copilot, branch isolation, and fork-join parallel execution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"extension": true,
|
|
127
127
|
"outputDir": "myextension/labextension",
|
|
128
128
|
"_build": {
|
|
129
|
-
"load": "static/remoteEntry.
|
|
129
|
+
"load": "static/remoteEntry.5e908823ed118c33.js",
|
|
130
130
|
"extension": "./extension",
|
|
131
131
|
"style": "./style"
|
|
132
132
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";(self.rspackChunkmyextension=self.rspackChunkmyextension||[]).push([[694],{7549(e,t,n){n.r(t),n.d(t,{default:()=>eY});var l=n(4064),i=n(277),o=n(1404),s=n(
|
|
1
|
+
"use strict";(self.rspackChunkmyextension=self.rspackChunkmyextension||[]).push([[694],{7549(e,t,n){n.r(t),n.d(t,{default:()=>eY});var l=n(4064),i=n(277),o=n(1404),s=n(2024),a=n(49),r=n(2012),d=n(2200),c=n(1758),p=n(8705),h=n(9996),u=n(5285),g=n(9645),f=n(7989),m=n(1660),y=n(4207);async function _(e,t={}){let n=y.ServerConnection.makeSettings(),l=o.URLExt.join(n.baseUrl,"ipygb",...e.split("/")),i=await y.ServerConnection.makeRequest(l,t,n),s={};try{s=await i.json()}catch(e){}if(!i.ok)throw Error(s.error||`HTTP ${i.status}`);return s}function b(e){return _("agent/config",{method:"POST",body:JSON.stringify(e)})}function v(e){return _(`kaggle/search?q=${encodeURIComponent(e)}`)}function x(e){return _("kaggle/download",{method:"POST",body:JSON.stringify({ref:e})})}function w(e){return _(`kaggle/files?ref=${encodeURIComponent(e)}`)}function C(e){return _("agent",{method:"POST",body:JSON.stringify(e)})}function k(e){let t=[],n=e=>{e&&!t.includes(e)&&t.push(e)};for(let t of e.split("\n")){if(/^\s/.test(t)||/^\s*#/.test(t))continue;let e=t.match(/^([A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*)\s*(?::[^=]+)?\s*(?:[-+*/|&^%@]|\/\/|\*\*|<<|>>)?=(?!=)/);if(e){e[1].split(",").forEach(e=>n(e.trim()));continue}if(e=t.match(/^(?:def|class)\s+([A-Za-z_]\w*)/)){n(e[1]);continue}(e=t.match(/^for\s+([A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*)\s+in\s/))&&e[1].split(",").forEach(e=>n(e.trim()))}return t}let E=`
|
|
2
2
|
def __ipygb_inspect():
|
|
3
3
|
import json, types
|
|
4
4
|
out = []
|