Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b426763
feat(wren): add a torch-free onnx embedding backend for wren memory
audi0417 Aug 28, 2026
9ce124c
test(wren): cover the onnx backend and run the memory suite under eit…
audi0417 Aug 28, 2026
cbbf3b6
ci(wren): run the memory suite against the torch-free onnx extra
audi0417 Aug 28, 2026
3429099
fix(wren): warn when the requested embedding backend is not installed
audi0417 Aug 28, 2026
f2d322a
ci(wren): run the embedding parity test with both extras installed
audi0417 Aug 28, 2026
aa48668
ci(wren): fail the torch-free job on nvidia-* and triton too
audi0417 Aug 28, 2026
119db61
feat(wren): report the live embedding backend in `wren memory status`
audi0417 Aug 28, 2026
9b9ed3f
docs(wren): docstring the onnx backend's remaining public surface
audi0417 Aug 28, 2026
a9a1fcb
ci(wren): do not persist credentials in the parity job's checkout
audi0417 Aug 28, 2026
7b09025
fix(wren): raise a dedicated error when onnx cannot match a model's p…
audi0417 Sep 3, 2026
f64608a
ci(wren): compare backends on the shipped default model
audi0417 Sep 3, 2026
320c072
perf(wren): chunk onnx encoding at the sentence-transformers batch size
audi0417 Sep 4, 2026
15498ff
fix(wren): route every onnx backend failure to the user, from any com…
audi0417 Sep 4, 2026
7b00446
docs(wren): document the memory-onnx extra
audi0417 Sep 4, 2026
2e44632
test(wren): gate the onnx runtime test on the memory-onnx extra
audi0417 Sep 4, 2026
0e8162b
fix(wren): handle backend errors on the group, and stop miscounting them
audi0417 Sep 4, 2026
2cb00a1
fix(wren): give _runtime the same OSError discrimination as _read_json
audi0417 Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/wren-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,95 @@ jobs:
WREN_EMBEDDING_MODEL: paraphrase-MiniLM-L3-v2
run: uv run --no-sync pytest tests/unit/test_memory.py -v

test-memory-onnx:
name: memory tests (onnx, torch-free)
runs-on: ubuntu-latest
defaults:
run:
working-directory: core/wren
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
./core/wren-core-py/target/
key: ${{ runner.os }}-cargo-wren-${{ hashFiles('**/Cargo.lock') }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build wren-core-py wheel
working-directory: core/wren-core-py
run: |
uv sync --locked --no-install-project
uv run --no-sync maturin build
- name: Install dependencies
run: |
uv sync --locked --extra memory-onnx
uv pip install --reinstall --no-index --find-links ../wren-core-py/target/wheels/ wren-core-py
- name: Assert the install is torch-free
# The whole point of this extra: on linux-x86_64 the sentence-transformers
# path resolves the CUDA build of torch plus triton and sixteen nvidia-*
# packages. Checking torch alone would miss a resolution that drops
# torch but keeps the CUDA payload, which is where the gigabytes are.
run: |
if uv run --no-sync python -c "import importlib.util as u; raise SystemExit(0 if u.find_spec('torch') else 1)"; then
echo "::error::torch was installed by the memory-onnx extra"
exit 1
fi
cuda=$(uv pip list --format=freeze | grep -iE '^(nvidia-|triton==)' || true)
if [ -n "$cuda" ]; then
echo "::error::CUDA packages were installed by the memory-onnx extra"
echo "$cuda"
exit 1
fi
- name: Cache ONNX model
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: hf-onnx-paraphrase-MiniLM-L3-v2
- name: Run memory tests
env:
WREN_EMBEDDING_MODEL: paraphrase-MiniLM-L3-v2
run: uv run --no-sync pytest tests/unit/test_memory.py -v

test-embedding-parity:
name: embedding backend parity
runs-on: ubuntu-latest
defaults:
run:
working-directory: core/wren
steps:
- uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install both embedding extras
# Each of the two jobs above installs one backend, so the parity test
# importorskips out of both. It is the assertion existing stores
# depend on — a store written by one backend has to stay readable by
# the other — so it needs a job where both are present.
run: uv sync --locked --extra memory --extra memory-onnx
- name: Cache models
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: hf-parity-paraphrase-multilingual-MiniLM-L12-v2
- name: Compare backends
run: uv run --no-sync pytest tests/unit/test_memory.py -v -k TestOnnxVectorParity

test-mcp:
name: mcp tests
runs-on: ubuntu-latest
Expand Down
10 changes: 9 additions & 1 deletion core/wren/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pip install 'wrenai[spark]' # Spark
pip install 'wrenai[athena]' # Athena
pip install 'wrenai[oracle]' # Oracle
pip install 'wrenai[memory]' # Schema & query memory (LanceDB)
pip install 'wrenai[memory-onnx]' # Same, torch-free (onnxruntime instead of PyTorch)
pip install 'wrenai[ui]' # Browser-based profile form (starlette + uvicorn)
pip install 'wrenai[main]' # memory + interactive prompts + ui
pip install 'wrenai[all]' # All connectors + main
Expand Down Expand Up @@ -133,7 +134,8 @@ flags.
| `strict_mode` | `false` | When `true`, every table in a query must be defined in the MDL. Queries referencing undeclared tables are rejected before execution. |
| `denied_functions` | `[]` | List of function names (case-insensitive) that are forbidden in queries. |

**6. (Optional) Index schema for semantic search** (requires `wrenai[memory]`):
**6. (Optional) Index schema for semantic search** (requires `wrenai[memory]`,
or `wrenai[memory-onnx]` for a torch-free install):

```bash
wren memory index # index MDL schema
Expand All @@ -143,6 +145,12 @@ wren memory recall -q "best customers" # retrieve similar past queries
wren memory watch # auto-reindex on schema/query changes
```

`memory-onnx` runs the same weights through onnxruntime instead of PyTorch, so
it produces the same vectors and existing indexes stay valid -- it is a much
smaller install, not a different index. When both extras are present, onnx is
used; set `WREN_EMBEDDING_BACKEND=sentence-transformers` to override, and
`wren memory status` reports which one is live.

**7. (Optional) Build a shareable GenBI app** — turn the context layer into a
browser-side dashboard (powered by `wren-core-wasm`) and deploy it to Vercel or
Cloudflare Pages. The CLI owns the build instruction + deterministic state; an
Expand Down
13 changes: 13 additions & 0 deletions core/wren/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ spark = ["pyspark>=3.5"]
athena = ["pyathena[pandas]>=3"]
oracle = ["oracledb>=2"]
memory = ["lancedb>=0.6", "sentence-transformers>=3.0.0"]
# Alternative to `memory`, not an addition: same vectors, no torch. Deliberately
# excluded from `all`, which installs the sentence-transformers backend.
memory-onnx = [
"lancedb>=0.6",
"onnxruntime>=1.17",
"tokenizers>=0.15",
# >=0.25: EntryNotFoundError / LocalEntryNotFoundError moved into
# huggingface_hub.errors there. embeddings.py imports them inside an
# `except OSError` handler, so on an older hub the ImportError would
# replace the error being classified.
"huggingface-hub>=0.25",
"numpy>=1.24",
]
interactive = ["InquirerPy>=0.3.4"]
ui = ["starlette>=1.3.1", "uvicorn>=0.29", "jinja2>=3.1", "python-multipart>=0.0.31"]
mcp = ["mcp[cli]>=1.19"]
Expand Down
35 changes: 35 additions & 0 deletions core/wren/src/wren/memory/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,37 @@

import typer
import yaml
from typer.core import TyperGroup


class _MemoryGroup(TyperGroup):
"""Report an embedding backend that cannot serve the model, from any command.

These failures surface lazily from the first encode, so every command that
embeds can raise one -- `index`, `store`, `recall`, `fetch`, and whatever
is added next. Handling them per call site means each new command has to
remember; handling them here means it inherits. Each message already names
the way out, so echoing it is enough.

Scoped to `OnnxBackendError` rather than `RuntimeError`: `typer.Exit`
subclasses `RuntimeError`, so the wider catch would rewrite every exit code
in this sub-app to 1.
"""

def invoke(self, ctx):
from wren.memory.embeddings import OnnxBackendError # noqa: PLC0415

try:
return super().invoke(ctx)
except OnnxBackendError as e:
typer.echo(str(e), err=True)
raise typer.Exit(1) from e


memory_app = typer.Typer(
name="memory",
help="Schema and query memory backed by LanceDB.",
cls=_MemoryGroup,
)

_WREN_HOME = Path(os.environ.get("WREN_HOME", Path.home() / ".wren"))
Expand Down Expand Up @@ -220,6 +247,9 @@ def index(
pass # instructions are optional; never fail index because of them

mem_store = _get_store(path)

# Backend errors are RuntimeErrors, so they pass this branch untouched and
# reach _MemoryGroup -- none of them is a manifest problem.
try:
result = mem_store.index_schema(manifest, seed_queries=not no_seed)
except ValueError as e:
Expand Down Expand Up @@ -464,6 +494,11 @@ def status(
if info["backend"] == "grep":
typer.echo(f" knowledge/sql: {info['pairs']} pair(s)")
return
# Two backends produce these vectors and only one of them pulls torch, so
# which one is live is the first thing to check when an install is bigger
# or slower than expected.
if info.get("embedding_backend"):
typer.echo(f" embeddings: {info['embedding_backend']} ({info['model']})")
tables = info.get("tables", {})
if not tables:
typer.echo("No tables indexed yet.")
Expand Down
Loading
Loading