Metadata-Version: 2.3
Name: esp32kb
Version: 0.1.2
Summary: Build an on-device knowledge base (text chunks + INT8 MiniLM embeddings) for the ESP32-P4 vector-search demo.
Keywords: esp32,esp32-p4,embeddings,vector-search,tinyml,onnx,minilm
Author: Simone Salerno
Author-email: Simone Salerno <info@salernosimone.com>
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Embedded Systems
Requires-Dist: numpy
Requires-Dist: onnxruntime
Requires-Dist: transformers
Requires-Dist: typer
Requires-Python: >=3.10
Project-URL: Homepage, https://github.com/salernosimone/python-esp32kb
Project-URL: Repository, https://github.com/salernosimone/python-esp32kb
Project-URL: Issues, https://github.com/salernosimone/python-esp32kb/issues
Description-Content-Type: text/markdown

# esp32kb

Build an on-device knowledge base for the KB ESP32-P4 demo — a MiniLM-L3
embedding search that runs entirely on the board, with no PC attached at
runtime.

Point the tool at a folder of markdown/text documents and it emits everything
the firmware flashes: sentence-aware chunks, INT8 embeddings, the packed
`kb.img` container (vectors + text + file index + WordPiece vocabulary) and
the tiny generated headers the sketch compiles against.

## Install

```sh
uvx esp32kb --help            # or: pip install esp32kb
# from a checkout:
uv run --project esp32kb esp32kb --help
```

## Usage

```sh
# 1. documents -> kb.img + generated headers
esp32kb embed --root path/to/documents --sketch-dir path/to/Arduino/project

# 2. print the arduino-cli command that flashes app + model + kb.img
esp32kb flash --sketch-dir path/to/Arduino/project   # then answer "y" to run it
```

Everything defaults relative to `--sketch-dir` (the current folder by default),
so the demo ships with its MiniLM-L3 model and one flag is enough.

### `esp32kb embed`

| flag | default | meaning |
|------|---------|---------|
| `--root` / `-r` | required | folder with the documents to index (recursively) |
| `--output-folder` / `-o` | `<sketch-dir>/model/kb` | where the `.bin` files and `kb.img` are written |
| `--sketch-dir` / `-s` | current folder | holds `KB.ino`/`VectorSearch.h`; `kb_config.h` and `kb_queries.h` go here |
| `--onnx` | `<sketch-dir>/model/maskseq.onnx` if present, else bundled | float32 MiniLM-L3 the PC embeds with |
| `--queries` / `-q` | sampled from the corpus | text file, one demo query per line |

### `esp32kb flash`

| flag | default | meaning |
|------|---------|---------|
| `--sketch-dir` / `-s` | current folder | the sketch to compile |
| `--model` | `<sketch-dir>/model/minilm_l3.espdl` | INT8 model to flash |
| `--kb` | `<sketch-dir>/model/kb/kb.img` | knowledge base to flash |
| `--port` / `-p` | `$PORT` or `/dev/ttyUSB0` | serial port |
| `--fqbn` | P4 board spec | arduino-cli board spec |
| `--library` | `$ARDUINO_LIBS` | extra Arduino library folder |
| `--yes` / `-y` | off | run without asking |
| `--print-only` | off | print the command and exit |
| `--port` | `$PORT` or `/dev/ttyUSB0` | serial port for the printed flash command |
| `--fqbn` | esp32p4 FQBN | board spec for the printed flash command |

The sequence length and embedding width are read straight from the ONNX model,
so the chunks, the INT8 vectors **and** the generated `kb_config.h` the
firmware compiles against can never disagree with the model. No flags to keep
in sync.

## Pipeline

```
documents
  -> chunks (<= seq-2 WordPiece tokens each)
  -> MiniLM-L3 embeddings (masked mean pool, L2 normalized)
  -> symmetric INT8 quantization, one global scale
  -> vectors.bin / meta.bin / chunks.bin / files.bin / vocab.txt
  -> kb.img        single little-endian container with a section table
  -> kb_config.h   KB_SEQ / KB_DIM / KB_TOPK + container magics (C++ constants)
  -> kb_queries.h  KB_EXAMPLES[] example query texts for the sketch
```

The vocabulary travels inside `kb.img`, so the device tokenizes queries itself.

## Additive attention mask

If the ONNX model exposes a second `attention_mask` input (the variant built
by `model/export_onnx_mask.py`), `esp32kb` feeds it the correct per-query
padding bias (0 on real tokens, negative on `[PAD]`). This preserves retrieval
quality at longer sequence lengths, where the frozen all-ones mask used by the
plain export otherwise collapses query embeddings. With the stock single-input
ONNX the behavior is unchanged.

## Outputs

| file | contents |
|------|----------|
| `vectors.bin` | 32 B header + N × dim INT8 (16-byte aligned rows) |
| `meta.bin` | 16 B header + N × 16 B records (file id, text offset/length) |
| `chunks.bin` | 16 B header + concatenated UTF-8 chunk text |
| `files.bin` | 16 B header + N × padded filename records |
| `vocab.txt` | WordPiece vocabulary, one token per line |
| `kb.img` | container: magic/version/section table + the payloads above |
| `kb_config.h` | `KB_SEQ`, `KB_DIM`, `KB_TOPK` and the container magics |
| `kb_queries.h` | `KB_EXAMPLES[]` example query texts for the sketch |
