MCPcopy Index your code
hub / github.com/Geekgineer/needle-rs

github.com/Geekgineer/needle-rs @v0.1.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.1.0 ↗ · + Follow
409 symbols 1,141 edges 34 files 97 documented · 24% updated 50d agov0.1.0 · 2026-05-18★ 42
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

needle-rs

<a href="https://needle-rs.pages.dev"><b>→ Live demo</b></a>







<a href="https://github.com/Geekgineer/needle-rs/actions/workflows/ci.yml"><img src="https://github.com/Geekgineer/needle-rs/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"/></a>
<a href="https://github.com/Geekgineer/needle-rs/actions/workflows/release.yml"><img src="https://github.com/Geekgineer/needle-rs/actions/workflows/release.yml/badge.svg" alt="Release"/></a>
<a href="https://github.com/Geekgineer/needle-rs/actions/workflows/wasm-demo.yml"><img src="https://github.com/Geekgineer/needle-rs/actions/workflows/wasm-demo.yml/badge.svg?branch=main" alt="Demo"/></a>
<a href="#parity"><img src="https://img.shields.io/badge/parity-560%2F560%20token--exact-brightgreen?style=flat-square" alt="Parity 560/560"/></a>
<a href="https://crates.io/crates/needle-rs"><img src="https://img.shields.io/crates/v/needle-rs?style=flat-square&color=CE422B" alt="crates.io"/></a>
<a href="https://www.npmjs.com/package/needle-rs"><img src="https://img.shields.io/npm/v/needle-rs?style=flat-square&color=CE422B" alt="npm"/></a>
<a href="https://pypi.org/project/needle-rs/"><img src="https://img.shields.io/pypi/v/needle-rs?style=flat-square&color=CE422B" alt="PyPI"/></a>
<a href="https://github.com/Geekgineer/needle-rs/raw/v0.1.0/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT"/></a>







<a href="#quick-start">Quick start</a> &nbsp;·&nbsp;
<a href="#how-it-works">How it works</a> &nbsp;·&nbsp;
<a href="#parity">Parity</a> &nbsp;·&nbsp;
<a href="https://github.com/cactus-compute/needle">Upstream model</a>

A pure-Rust + WebAssembly runtime for Needle by Cactus Compute — a 26M-parameter transformer that maps (query, tool list) → JSON call in one forward pass. Deploys to browsers, edge workers, CLIs, Python, and no_std embedded targets. No server, no API key.

Tool calling usually means either a paid API call or hundreds of megabytes on disk. needle-rs ships the whole agent in 23 MB.

Stack Deploy size Latency Cost Privacy
OpenAI function calling SDK + hosted API 300–800 ms $ per token leaves device
llama.cpp + 1B local model 700 MB+ varies free local
ONNX Runtime Web + a model 8 MB + your model varies free local
needle-rs + Needle 258 KB + 22 MB ~280 ms free local

Same answer to "did the user ask for a flight booking?" — at a fraction of the footprint.

**Browser / Node**
npm install needle-rs
import init, { NeedleWasm } from "needle-rs";

await init();
const engine = NeedleWasm.load(weights, vocab);

engine.run(
  "Book a flight from London to JFK tomorrow",
  toolsJson
);
// → {"name":"book_flight","arguments":{...}}
**Rust**
cargo add needle-infer
use needle_infer::NeedleEngine;

let engine = NeedleEngine::load(
    "needle.safetensors",
    "vocab.txt"
)?;
let result = engine.run(query, tools_json);
println!("{}", result.text);
**Python**
pip install needle-rs
from needle_rs import NeedleEngine

engine = NeedleEngine.load(
    "needle.safetensors",
    "vocab.txt",
)
result = engine.run(query, tools_json)
print(result)
# → [{"name":"book_flight","arguments":{...}}]

Get the weights

huggingface-cli download Abdalrahman/needle-rs-safetensors \
  needle.safetensors vocab.txt --local-dir weights/

Or load directly from a URL in the browser — no install step.

Target Status Binary
Browser (WASM)258 KB
Node.js (WASM)258 KB
Cloudflare Workers258 KB
Linux / macOS / Windows CLI533 KB
Python (native wheel)pip install needle-rs
C / C++ / Go / Swift (via FFI)557 KB
no_std embedded (Rust)size varies
iOS / Android (use Cactus)
Apple NPU / Snapdragon NPU (use Cactus)

Cactus's official engine targets mobile and NPUs with hand-tuned ARM SIMD. needle-rs targets everywhere else. The two stacks are complementary.

Needle is a 26M-parameter encoder-decoder transformer with a small twist: it's trained to do exactly one thing — emit a function-call JSON object from a query and a tool list. That focus is why a model this small works at all.

1 Encoder–decoder SAN. The encoder reads the query and tool definitions once. The decoder generates output JSON token by token, attending to the encoder's cached KV. Single forward pass per call.
2 INT4 quantization. All attention and FFN weights are packed 4-bit nibbles with per-32-row scales. Matvec dequantizes on the fly — the full f32 weight matrix is never materialized. AVX2 on x86_64, NEON on aarch64, scalar fallback for WASM.
3 Constrained decoding. A character-level trie over valid tool names and argument keys, plus a three-state JSON machine, masks logits at every step. Output is always syntactically valid JSON pointing at a real tool — never a hallucinated function name, never broken syntax.
4 Two schema formats. Accepts both the flat {"location": {"type": "string"}} style and OpenAI's {"type":"object","properties":{...}} style. The Python reference handles only the flat form.
5 Greedy by design. Tool calling is a routing task, not a generation task — temperature would only introduce errors. needle-rs is argmax-only and intentionally does not support stochastic sampling.

Architecture deep-dive: ARCHITECTURE.md.

 

A common failure mode for from-scratch model reimplementations is silent drift — outputs that look right but diverge at the third decimal place, with rare and untraceable downstream bugs. needle-rs rejects that. The Rust engine is required to produce the exact same token ID sequence as the Python/JAX reference on every input, at every decode step.

The test suite generates 560 inference examples by running the Python model on a diverse input set: five tool-name conventions (snake_case, camelCase, PascalCase, UPPER_SNAKE_CASE, kebab-case), parameter counts from 0 to 8, tool arrays from 1 to 20 entries, and a spread of natural-language query phrasings. For each example we capture the Python model's complete output token sequence. The Rust engine is then run on every example and required to produce the identical sequence.

560 / 560 pass. Not approximately — same argmax decision at every step.

Token-exact parity is checked on every CI run. Any change that drifts gets caught before merge. The reference vectors are committed to the repo, so the parity contract is version-pinned and reproducible without re-running Python:

Plus 55 unit tests on the constrained decoder covering edge cases the parity suite doesn't reach (empty tool arrays, parameter-less tools, name-collision under snake_case normalization, max-length queries).

JavaScript / TypeScript

engine.run(query, tools)                              // → string
engine.run_stream(query, tools, (id, piece) => {})    // per-token callback → final string
engine.run_batch([{ query, tools }, ...])             // → string[]
engine.encode_contrastive(text)                       // → Float32Array | null
engine.retrieve_tools(query, descriptionsJson, topK)  // semantic tool routing

Rust

engine.run(query, tools_json);
engine.run_stream(query, tools_json, |_id, piece| print!("{piece}"));
engine.run_batch(&[(q1, t1), (q2, t2)]);
engine.encode_contrastive(text);            // → Option<Vec<f32>>
engine.retrieve_tools(query, descs, k);     // → Vec<(usize, f32)>

C (and anything with FFI)

#include "needle.h"

NeedleHandle h  = needle_load("needle.safetensors", "vocab.txt");
const char *out = needle_run(h, query, tools_json);
printf("%s\n", out);
needle_free_str((char *)out);
needle_free(h);

Full header: crates/needle-c/include/needle.h. Null-safe throughout; errors via thread-local needle_last_error().

Intel i7-1185G7 (Tiger Lake, 4-core), Linux, release build, median of 5 runs.

End-to-end (load + infer)283 ms
Warm inference only~80 ms
INT4 matvec 512×512 (AVX2)83 µs · 3.2 Gelem/s
INT4 matvec 2048×512 (AVX2)311 µs · 3.1 Gelem/s

Apple Silicon NEON path is implemented but unbenchmarked — M-series numbers welcome via PR.

Footprint, stripped release:

WASM module258 KB
CLI binary533 KB
C shared library557 KB
Weights (INT4 SafeTensors)22 MB
Runtime dependencies1 (libm; WASM adds wasm-bindgen)

Full methodology and raw numbers: BENCHMARKS.md.

**✓ Browser-side intent routing** Decide which API to call before making the network request. Sub-second, zero servers. **✓ Edge function dispatch** Tool calling inside Cloudflare Workers, Vercel Edge, Deno Deploy — anywhere with a WASM runtime. **✓ On-device privacy** User queries never leave the browser tab. Useful for health

Core symbols most depended-on inside this repo

Shape

Function 296
Method 85
Class 25
Enum 3

Languages

Rust85%
Python14%
TypeScript1%
C++1%

Modules by API surface

crates/needle-infer/tests/constrained_unit.rs60 symbols
crates/needle-infer/src/constrained.rs39 symbols
crates/needle-infer/src/engine.rs36 symbols
tools/gen_e2e_vectors.py34 symbols
crates/needle-core/tests/parity_tests.rs25 symbols
crates/needle-infer/src/tokenizer.rs22 symbols
crates/needle-infer/tests/functional.rs17 symbols
crates/needle-infer/src/safetensors.rs17 symbols
crates/needle-core/src/quant.rs13 symbols
crates/needle-c/src/lib.rs13 symbols
crates/needle-c/tests/ffi_smoke.rs12 symbols
crates/needle-core/src/model.rs11 symbols

For agents

$ claude mcp add needle-rs \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page