Metadata-Version: 2.4
Name: zeppelin-embed
Version: 0.8.0
Summary: ctypes bindings for the Zeppelin Embed C ABI
License-Expression: GPL-3.0-only
Project-URL: Homepage, https://github.com/zepdb/zeppelin-embed
Project-URL: Repository, https://github.com/zepdb/zeppelin-embed
Project-URL: Issues, https://github.com/zepdb/zeppelin-embed/issues
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: numpy>=2.0
Provides-Extra: dev
Requires-Dist: mypy>=1.17; extra == "dev"
Requires-Dist: pytest>=8.4; extra == "dev"
Requires-Dist: ruff>=0.12; extra == "dev"

# Zeppelin Embed for Python

Python 3.11+ bindings for the Zeppelin Embed C ABI. Platform wheels include the
native library, so a wheel installation does not require a separate Rust build.

```bash
python -m pip install zeppelin-embed
```

```python
from pathlib import Path

import numpy as np
import zeppelin_embed as ze

vectors = np.asarray([[0.0, 0.5, 1.0, 1.5]], dtype=np.float32)
with ze.open(Path("example-store")) as store:
    store.ingest([1], vectors, texts=["first document"])
    result = store.query(vector=vectors[0], text="first", k=1)
    print(result.hits[0].doc_id)
```

## Native library loading

The loader uses `ZEPPELIN_EMBED_LIBRARY` when it is set. Otherwise, an installed
wheel loads its bundled library from `zeppelin_embed/.dylibs`. Source-tree use
also recognizes the workspace's debug and release Cargo outputs for development.
An invalid override fails loudly and does not fall back to another library.

The arm64 wheel (the only published wheel, including text) requires macOS 13
or newer on Apple silicon. Other platforms are not part of this PyPI release.

## Text API (macOS arm64)

Apple silicon wheels include the text feature and MLX's `mlx.metallib` beside
its native library. Other builds raise `ze.TextUnavailable` with
"text API is available on macOS arm64 only" from text entry points.
No separate MLX install or shader-library path is needed.

MLX keeps a process-global buffer cache, capped at 4 GiB by default during text
ingest. On an Apple M3 Max with 128 GiB, a full FiQA ingest (57,638 documents)
with the release FP16 default model peaked at about 7.3 GB process footprint
(about 112 GB before the cap), with max RSS about 4.8 GB (ZE-528). The binding
has no cache-limit option; set `ZE_MLX_CACHE_LIMIT_BYTES` (bytes) before the
first text open to override the cap. Invalid values are errors; the first
setter in a process wins.

```python
import zeppelin_embed as ze

with ze.open_model("text-store") as store:  # downloads the default model on first use
    store.ingest_text([7], ["the bronze zeppelin"])
    for legs in ("dense", "lexical", "hybrid"):
        result = store.query_text("bronze zeppelin", k=1, legs=legs, tier="exact")
        print(result.hits, result.diagnostics)
    print(store.health())
    store.maintain(wall_time_ns=10_000_000, bytes=1_000_000)
    store.delete_text([7])  # removes every chunk belonging to document 7
```

To download and verify a model from Hugging Face, call
`ze.open_model("text-store", repo=repo, revision=commit_sha,
manifest_sha256=manifest_sha256, cache_dir=None, offline=False, progress=None)`.
Omit all three pins (`ze.open_model("text-store")`) to use
`ze.DEFAULT_MODEL_REF`: `aghatage/zeppelin-embed-clean71-arctic-m-v2` at
`d64b817c4021a3f1b9bded3152d9afdf9ac813e7`. Explicit pins must be supplied together;
a partial set is an error.
The native resolver honors `ZE_MODEL_CACHE`, `ZE_MODEL_OFFLINE`, and
`ZE_MODEL_ENDPOINT`. The first open downloads and verifies the pinned model;
subsequent opens reuse the verified cache. `cache_dir` overrides `ZE_MODEL_CACHE`
and the default `~/Library/Caches/zeppelin-embed/models`.
`progress(done, total)` runs synchronously with byte counts. Intermediate reports
require both at least 64 KiB and at least 100 ms since the last report; each
verified file also reports completion. Initial `(0, 0)` means fetching the
manifest. Callbacks must not reenter model resolution; callback exceptions
propagate after resolution.
`offline=True` (or `ZE_MODEL_OFFLINE=1`) prohibits network access and raises
`ze.NotFound` on a cache miss. `ze.open_text("text-store", "model.zem")` opens a
preinstalled bundle. See the [model card](https://huggingface.co/aghatage/zeppelin-embed-clean71-arctic-m-v2/blob/d64b817c4021a3f1b9bded3152d9afdf9ac813e7/README.md)
and [root text API guide](../../README.md#text-api-macos-apple-silicon) for model
limits, download size, and measured performance.

`ingest_text` and `query_text` accept `cancel_token=ze.CancelToken()` or
`deadline_ns` (a relative monotonic budget), mutually exclusive. Cancellation
raises `ze.Cancelled`; expiry raises `ze.Timeout`. Ingest may leave acknowledged
partial writes. Its report includes generation, documents, chunks, tokens, batches,
and worker completion. Set `chunk_max` and `chunk_overlap` to split long documents
into token windows. Queries return stored text, core diagnostics, backend facts,
and stage timings when the native runtime enables them. Text `maintain` executes
one budgeted native maintenance call.

Explicit `store.observed_compute_units()` loads the CoreML compute plan on demand and returns planned placement counts (not an execution trace), or `None` without a CoreML query model.

## Development

The package is built from the repository because the wheel build compiles
`crates/zeppelin-embed-ffi` with the locked Rust 1.93 dependency graph:

```bash
python -m pip wheel --no-deps ./bindings/python -w wheelhouse
```

Run all binding checks on macOS arm64, outside a process sandbox (MLX uses Metal):

```bash
cargo build --locked -p zeppelin-embed-ffi --features text
ZEPPELIN_EMBED_LIBRARY="$PWD/target/debug/libzeppelin_embed_ffi.dylib" \
  python -m pytest bindings/python/tests
```

The text tests use the committed synthetic bundle
`crates/zeppelin-embed-text/tests/fixtures/ze115/gte.zem` and a local HTTP server
for model download, verified cache reuse, and offline behavior. The built-in default is pinned to the published Hugging Face model. The ignored
Rust test `real_default_model_downloads_and_answers_queries` verifies a real
download and dense, lexical and hybrid retrieval outside the sandbox.

Build and verify the installed wheel in a fresh venv and temporary directory:

```bash
python -m pip wheel --no-deps ./bindings/python -w wheelhouse
python bindings/python/tests/verify_wheel.py wheelhouse/zeppelin_embed-*.whl
```

The verifier clears source-library overrides, copies the synthetic fixture into
the temporary directory, and tests ingest plus all three query legs using only
the installed package and its colocated shader library.

## Releasing

Manually dispatching `python-release.yml` with release tag `v<version>` builds
and verifies the macOS arm64 wheel, then uploads it with PyPI Trusted
Publishing. Configure
the PyPI publisher with owner `zepdb`, repository `zeppelin-embed`, workflow
`python-release.yml`, and environment `pypi`.

### Prepare lexical queries after open

Call `store.warm_lexical()` before exact or `last_as_prefix=True` queries to
prepare the lexical assembly and prefix vocabulary, including record-only
namespaces. Repeated calls reuse preparation; mutations invalidate it.
Optional `cancel_token` and `deadline_ns` controls are mutually exclusive.
A cancelled call may retain a complete assembly and can be retried.
