Skip to content

Commit 266e024

Browse files
committed
Add JupyterLite browser-companion proof-of-concept (#1072)
First concrete step of the Python web companion discussed in #1072: a JupyterLite site that runs aima notebooks entirely in the browser via Pyodide, no server or local install. - lite/ holds the build config, an offline-installable setup, and three PoC notebooks (Welcome, search, games) restricted to modules that import only Pyodide-provided packages (numpy). The aima wheel is built at deploy time and bundled into piplite so notebooks do piplite.install('aima', deps=False) offline (TF/keras/cv2/cvxopt are unavailable in Pyodide). - docs.yml builds the site into docs/_build/html/lite and publishes it alongside the API docs at /lite/ on GitHub Pages. The step is continue-on-error so a Pyodide toolchain hiccup can never block the reference-docs deploy. - README gets a JupyterLite badge, a 'try in browser' link, and a lite/ entry in the project layout. Verified: the static site builds and bundles the aima wheel into the piplite index; in a numpy-only env with aima installed --no-deps (the closest headless proxy for Pyodide) both PoC notebooks import and run. In-browser execution still needs real-browser verification, so #1072 stays open.
1 parent b8f8592 commit 266e024

11 files changed

Lines changed: 304 additions & 2 deletions

File tree

.github/workflows/docs.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ jobs:
3131
# -W --keep-going: fail the build if any page can't be imported/rendered,
3232
# so an incomplete API reference can never be published silently again
3333
run: sphinx-build -b html -W --keep-going docs docs/_build/html
34+
- name: Build the JupyterLite browser companion (issue #1072)
35+
# Published alongside the API docs at /lite/. Kept non-fatal so a Pyodide
36+
# toolchain hiccup can never block the reference-docs deploy.
37+
continue-on-error: true
38+
run: |
39+
pip install -r lite/requirements-lite.txt
40+
bash lite/build.sh "$PWD/docs/_build/html/lite"
3441
- uses: actions/upload-pages-artifact@v3
3542
with:
3643
path: docs/_build/html

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,8 @@ target/
7878
# for macOS
7979
.DS_Store
8080
._.DS_Store
81+
82+
# JupyterLite browser companion build artifacts (see lite/README.md)
83+
lite/_output/
84+
lite/.jupyterlite.doit.db
85+
lite/aima-*.whl

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22

3-
# `aima-python` [![tests](https://github.com/aimacode/aima-python/actions/workflows/tests.yml/badge.svg)](https://github.com/aimacode/aima-python/actions/workflows/tests.yml) [![docs](https://github.com/aimacode/aima-python/actions/workflows/docs.yml/badge.svg)](https://aimacode.github.io/aima-python/) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/aimacode/aima-python/master)
3+
# `aima-python` [![tests](https://github.com/aimacode/aima-python/actions/workflows/tests.yml/badge.svg)](https://github.com/aimacode/aima-python/actions/workflows/tests.yml) [![docs](https://github.com/aimacode/aima-python/actions/workflows/docs.yml/badge.svg)](https://aimacode.github.io/aima-python/) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/aimacode/aima-python/master) [![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://aimacode.github.io/aima-python/lite/)
44

55

66
Python code for the book *[Artificial Intelligence: A Modern Approach](http://aima.cs.berkeley.edu).* You can use this in conjunction with a course on AI, or for study on your own. We're looking for [solid contributors](https://github.com/aimacode/aima-python/blob/master/CONTRIBUTING.md) to help.
@@ -22,11 +22,12 @@ The 4th edition of the book is out now in 2020, and thus we are updating the cod
2222

2323
# Structure of the Project
2424

25-
When complete, this project will have Python implementations for all the pseudocode algorithms in the book, as well as tests and examples of use. The code is organised into three top-level folders:
25+
When complete, this project will have Python implementations for all the pseudocode algorithms in the book, as well as tests and examples of use. The code is organised into these top-level folders:
2626

2727
- **`aima/`** — the importable Python package: one module per major topic (e.g. `aima/search.py`) with the implementations of the pseudocode algorithms and their support functions/classes/data.
2828
- **`notebooks/`** — the Jupyter notebooks that explain and demonstrate the code (e.g. `notebooks/search.ipynb`), plus the per-chapter `notebooks/chapterNN/` demos. Each notebook starts with a `%run bootstrap.ipynb` cell that puts the repo root on `sys.path`, so `from aima import ...` works wherever the notebook is launched. A GitHub Action ([`notebooks-to-py.yml`](.github/workflows/notebooks-to-py.yml)) keeps a readable, diffable `.py` mirror of every notebook beside it (generated with [jupytext](https://jupytext.readthedocs.io)); the `.ipynb` is the source of truth, so edit the notebook, not the generated `.py`.
2929
- **`tests/`** — a lightweight test suite (e.g. `tests/test_search.py`), using `assert` statements, designed for use with [`py.test`](http://pytest.org/latest/) but also usable on their own.
30+
- **`lite/`** — a [JupyterLite](https://jupyterlite.readthedocs.io) proof-of-concept that runs a few notebooks entirely in the browser via [Pyodide](https://pyodide.org), no install required (try it [here](https://aimacode.github.io/aima-python/lite/); see [`lite/README.md`](lite/README.md) and [issue #1072](https://github.com/aimacode/aima-python/issues/1072)).
3031

3132
# Python 3.9 and up
3233

lite/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# aima-python in the browser (JupyterLite proof-of-concept)
2+
3+
This folder builds a **JupyterLite** site that runs a few `aima` notebooks
4+
entirely in the browser via [Pyodide](https://pyodide.org) — no server, no
5+
local install. It is the first concrete step of the "Python web companion"
6+
discussed in [issue #1072](https://github.com/aimacode/aima-python/issues/1072).
7+
8+
When deployed, it lives next to the API docs on GitHub Pages:
9+
10+
- API docs: <https://aimacode.github.io/aima-python/>
11+
- Try in browser: <https://aimacode.github.io/aima-python/lite/>
12+
13+
## What runs (and what does not)
14+
15+
Pyodide ships scientific-Python wheels (numpy, scipy, matplotlib, networkx,
16+
pandas, …), so the *lightweight* parts of `aima` work in the browser. The
17+
heavy native dependencies in `requirements.txt` — TensorFlow/Keras, OpenCV
18+
(`cv2`), `cvxopt`, `qpsolvers` — are **not** available in Pyodide, so notebooks
19+
that need them (deep learning, parts of perception, LP-based game theory)
20+
cannot run here. The `aima` wheel is therefore installed with `deps=False` and
21+
the demo notebooks are restricted to modules that import only Pyodide-provided
22+
packages.
23+
24+
The proof-of-concept ships two such notebooks:
25+
26+
- `content/search.ipynb` — BFS / A* on the Romania map (`aima.search`, numpy only).
27+
- `content/games.ipynb` — minimax / alpha-beta on Tic-Tac-Toe (`aima.games`, numpy only).
28+
29+
`content/Welcome.ipynb` shows the one-cell install pattern used by every notebook.
30+
31+
## Build locally
32+
33+
```bash
34+
cd lite
35+
./build.sh # builds the aima wheel + runs `jupyter lite build`
36+
python -m http.server -d _output 8000 # then open http://localhost:8000
37+
```
38+
39+
`build.sh` installs the build toolchain from `requirements-lite.txt`, builds an
40+
`aima` wheel from the repo root, and bundles it into the JupyterLite site so the
41+
notebooks can `piplite.install("aima", deps=False)` offline.
42+
43+
## Status / next steps
44+
45+
This is a **proof of concept** (issue #1072 stays open until it is a complete
46+
companion). Remaining work:
47+
48+
- Verify in-browser execution across browsers (Pyodide runs only in a real
49+
browser, so this cannot be checked in headless CI — the CI job only proves the
50+
static site *builds*).
51+
- Port more lightweight notebooks (logic, csp, planning, probability) once their
52+
in-browser behaviour is confirmed; csp needs `sortedcontainers`, logic needs
53+
`networkx` (both pure-Python, installable in Pyodide).
54+
- Decide whether to grow this into a full MyST / Jupyter Book textbook companion.

lite/build.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
# Build the JupyterLite proof-of-concept site (see README.md).
3+
#
4+
# Usage: ./build.sh [OUTPUT_DIR]
5+
# OUTPUT_DIR defaults to lite/_output. CI passes docs/_build/html/lite so the
6+
# companion is published under the GitHub Pages site at /lite/.
7+
set -euo pipefail
8+
9+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
ROOT="$(cd "$HERE/.." && pwd)"
11+
OUTPUT_DIR="${1:-$HERE/_output}"
12+
# resolve to an absolute path (jupyter lite build runs from $HERE)
13+
mkdir -p "$OUTPUT_DIR"
14+
OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
15+
16+
# 1. Build an aima wheel from the repo root and drop it next to the lite config
17+
# so piplite can install it offline (deps=False, since TF/keras/cv2/cvxopt
18+
# are not available in Pyodide).
19+
rm -f "$HERE"/aima-*.whl
20+
python -m build --wheel --outdir "$HERE" "$ROOT"
21+
22+
# 2. Build the static JupyterLite site.
23+
cd "$HERE"
24+
jupyter lite build --output-dir "$OUTPUT_DIR"
25+
26+
echo
27+
echo "Built JupyterLite site at: $OUTPUT_DIR"
28+
echo "Serve it locally with:"
29+
echo " python -m http.server -d \"$OUTPUT_DIR\" 8000"

lite/content/Welcome.ipynb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# aima-python in the browser\n",
8+
"\n",
9+
"This is a [JupyterLite](https://jupyterlite.readthedocs.io) site running\n",
10+
"entirely in your browser via [Pyodide](https://pyodide.org) — there is no\n",
11+
"server. It is a proof of concept for a Python web companion to *Artificial\n",
12+
"Intelligence: A Modern Approach* (see\n",
13+
"[issue #1072](https://github.com/aimacode/aima-python/issues/1072)).\n",
14+
"\n",
15+
"Every notebook starts by installing the `aima` package into the browser kernel\n",
16+
"with the cell below. Pyodide already provides numpy/scipy/matplotlib/networkx,\n",
17+
"so `aima`'s heavy native dependencies (TensorFlow/Keras, OpenCV, cvxopt) are\n",
18+
"skipped with `deps=False`; the demo notebooks use only the lightweight modules.\n",
19+
"\n",
20+
"Open **search.ipynb** and **games.ipynb** from the file browser on the left."
21+
]
22+
},
23+
{
24+
"cell_type": "code",
25+
"execution_count": null,
26+
"metadata": {},
27+
"outputs": [],
28+
"source": [
29+
"import piplite\n",
30+
"await piplite.install(\"aima\", deps=False)\n",
31+
"\n",
32+
"from aima.search import romania_map\n",
33+
"print(\"aima loaded — Romania map has\", len(romania_map.locations), \"cities\")"
34+
]
35+
}
36+
],
37+
"metadata": {
38+
"kernelspec": {
39+
"display_name": "Python (Pyodide)",
40+
"language": "python",
41+
"name": "python"
42+
},
43+
"language_info": {
44+
"name": "python"
45+
}
46+
},
47+
"nbformat": 4,
48+
"nbformat_minor": 5
49+
}

lite/content/games.ipynb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Adversarial search in the browser\n",
8+
"\n",
9+
"Minimax and alpha-beta on Tic-Tac-Toe from chapter 5, running fully in your\n",
10+
"browser with `aima.games`."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"metadata": {},
17+
"outputs": [],
18+
"source": [
19+
"import piplite\n",
20+
"await piplite.install(\"aima\", deps=False)"
21+
]
22+
},
23+
{
24+
"cell_type": "code",
25+
"execution_count": null,
26+
"metadata": {},
27+
"outputs": [],
28+
"source": [
29+
"from aima.games import TicTacToe, minmax_decision, alpha_beta_search\n",
30+
"\n",
31+
"game = TicTacToe()\n",
32+
"state = game.initial\n",
33+
"print(\"minimax optimal first move: \", minmax_decision(state, game))\n",
34+
"print(\"alpha-beta optimal first move:\", alpha_beta_search(state, game))"
35+
]
36+
},
37+
{
38+
"cell_type": "code",
39+
"execution_count": null,
40+
"metadata": {},
41+
"outputs": [],
42+
"source": [
43+
"import random\n",
44+
"from aima.games import alpha_beta_player, random_player\n",
45+
"\n",
46+
"# An optimal alpha-beta player never loses: against a random opponent it wins\n",
47+
"# or draws every game (utility >= 0 for the first player).\n",
48+
"random.seed(0)\n",
49+
"results = [game.play_game(alpha_beta_player, random_player) for _ in range(10)]\n",
50+
"print(\"alpha-beta vs random, 10 games (first-player utility):\", results)\n",
51+
"print(\"never loses:\", all(u >= 0 for u in results))"
52+
]
53+
}
54+
],
55+
"metadata": {
56+
"kernelspec": {
57+
"display_name": "Python (Pyodide)",
58+
"language": "python",
59+
"name": "python"
60+
},
61+
"language_info": {
62+
"name": "python"
63+
}
64+
},
65+
"nbformat": 4,
66+
"nbformat_minor": 5
67+
}

lite/content/search.ipynb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Search in the browser\n",
8+
"\n",
9+
"Uninformed and informed search on the Romania map from chapter 3, running\n",
10+
"fully in your browser with `aima.search`. Run the cells top to bottom."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"metadata": {},
17+
"outputs": [],
18+
"source": [
19+
"import piplite\n",
20+
"await piplite.install(\"aima\", deps=False)"
21+
]
22+
},
23+
{
24+
"cell_type": "code",
25+
"execution_count": null,
26+
"metadata": {},
27+
"outputs": [],
28+
"source": [
29+
"from aima.search import GraphProblem, romania_map\n",
30+
"from aima.search import breadth_first_graph_search, uniform_cost_search, astar_search\n",
31+
"\n",
32+
"problem = GraphProblem(\"Arad\", \"Bucharest\", romania_map)\n",
33+
"\n",
34+
"for name, algorithm in [\n",
35+
" (\"Breadth-first\", breadth_first_graph_search),\n",
36+
" (\"Uniform-cost\", uniform_cost_search),\n",
37+
" (\"A* (straight-line heuristic)\", astar_search),\n",
38+
"]:\n",
39+
" goal = algorithm(problem)\n",
40+
" print(f\"{name:30s} {['Arad'] + goal.solution()} (cost {goal.path_cost})\")"
41+
]
42+
},
43+
{
44+
"cell_type": "markdown",
45+
"metadata": {},
46+
"source": [
47+
"A* finds the optimal route Arad → Sibiu → Rimnicu → Pitesti → Bucharest\n",
48+
"(cost 418), while breadth-first returns a shorter-in-hops but costlier path."
49+
]
50+
}
51+
],
52+
"metadata": {
53+
"kernelspec": {
54+
"display_name": "Python (Pyodide)",
55+
"language": "python",
56+
"name": "python"
57+
},
58+
"language_info": {
59+
"name": "python"
60+
}
61+
},
62+
"nbformat": 4,
63+
"nbformat_minor": 5
64+
}

lite/jupyter-lite.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"jupyter-config-data": {
3+
"litePluginSettings": {
4+
"@jupyterlite/pyodide-kernel-extension:kernel": {
5+
"loadPyodideOptions": {
6+
"packages": ["numpy", "networkx", "sortedcontainers"]
7+
}
8+
}
9+
}
10+
}
11+
}

lite/jupyter_lite_config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"LiteBuildConfig": {
3+
"contents": ["content"]
4+
},
5+
"PipliteAddon": {
6+
"piplite_urls": ["aima-4.0.0-py3-none-any.whl"]
7+
}
8+
}

0 commit comments

Comments
 (0)