hmunachii commited on
Commit
78e3a77
·
verified ·
1 Parent(s): 57ae862

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +222 -0
README.md ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: cactus-needle
3
+ pipeline_tag: text-generation
4
+ license: apache-2.0
5
+ tags:
6
+ - tool-calling
7
+ - function-calling
8
+ - on-device
9
+ - edge
10
+ - quantization
11
+ - webassembly
12
+ ---
13
+
14
+ ![Needle 2](banner.png)
15
+
16
+ # Needle 2
17
+
18
+ Needle 2 is a 45M-parameter foundation tool call/use model for tiny devices; run it or embed it in phones, wearables, watches, TVs, smart home, small robots.
19
+ Compressed to CQ2-bit with Cactus Quants, the whole model ships as a single 14MB binary that runs a full session in 28MB of RAM.
20
+ A Raspberry Pi 5 prefills at 1.3k tok/s and decodes at 500+; an iPhone 17 Pro prefills at 3k+ and decodes at 1k+.
21
+
22
+ - **Self-contained**: model baked into the binary, no runtime, no downloads, no network.
23
+ - **Runs everywhere**: ARM64, x86-64, ARMv7, RISC-V, Cortex-M, and WebAssembly, on Apple, Windows, Linux, Android, Raspberry Pi.
24
+ - **Simple contract**: tool calls come back as structured data, text in, JSON out; a byte-level grammar compiled from your schemas constrains every token.
25
+ - **Confidence-gated**: every response carries a calibrated confidence score from a learned head; set a threshold, act above it, escalate below it.
26
+ - **Tool retrieval**: declare a large catalogue and a built-in retrieval head renders only the top ten tools per turn, with the grammar constrained to that subset.
27
+ - **Bounded memory**: a 256-token sliding window with the tools pinned as KV sinks; session memory caps near 14MB no matter how long the conversation runs.
28
+
29
+ ![Size-quality frontier: mobile-class and below](frontier.svg)
30
+
31
+ ## Quickstart with Python
32
+
33
+ ```sh
34
+ pip install cactus-needle
35
+ ```
36
+
37
+ The PyPI package is a small pure-Python shim; the first `import needle` downloads the engine for your platform (~13 MB, cached in `~/.cache/cactus-needle`), so the first run needs network. Every run after is offline.
38
+
39
+ ```python
40
+ import needle
41
+
42
+ tools = [
43
+ {"name": "set_timer",
44
+ "parameters": {"type": "object", "properties": {"minutes": {"type": "integer"}, "label": {"type": "string"}}}},
45
+ {"name": "play_music",
46
+ "parameters": {"type": "object", "properties": {"query": {"type": "string"}}}},
47
+ ]
48
+
49
+ agent = needle.Needle(tools=tools)
50
+ # with many tools, persist their embeddings across runs:
51
+ # agent = needle.Needle(tools=tools, tool_index_path="tools.idx")
52
+
53
+ response = agent.complete("set a 10 minute timer for the pasta")
54
+
55
+ if response["type"] == "call":
56
+ tool_result = run_tool(response["function_calls"][0])
57
+ response = agent.complete(tool_result)
58
+ response = agent.complete("and put on some jazz")
59
+
60
+ agent.reset()
61
+ ```
62
+
63
+ Every turn returns one JSON object:
64
+
65
+ ```json
66
+ {
67
+ "type": "call",
68
+ "success": true,
69
+ "error": null,
70
+ "error_code": null,
71
+ "function_calls": [ { "name": "set_lights", "arguments": { "room": "living room", "on": true, "brightness": 30 } } ],
72
+ "reasoning": "'living room' -> room; 'dim' -> on true, brightness 30",
73
+ "confidence": 0.94,
74
+ "prefill_tps": 4300.0,
75
+ "decode_tps": 850.0,
76
+ "peak_ram_mb": 28.0
77
+ }
78
+ ```
79
+
80
+ ## Behaviour
81
+
82
+ Needle solves every problem as a function call. The context declares what may be called; the model answers with calls. Performing an action and extracting structured data are the same operation, the only difference is what you declare.
83
+
84
+ - A request no declared tool can serve is refused with the empty call `[]`. That is the whole contract for off-topic input; there is no free-text fallback.
85
+ - Arguments contain only values evidenced by the input. An optional field with no evidence is omitted, not guessed; omission is the field-level `[]`.
86
+ - `reasoning` is the model's short derivation of each argument from its source span (`'ten minutes' -> minutes 10`). It is generated unconstrained; only the call itself is grammar-constrained, so the JSON cannot be malformed while the derivation stays legible.
87
+ - After you execute a call, pass the result back as the next `complete()`. The model continues from it, and later arguments may depend on earlier results: `search_for_contact` first, then `send_instant_message` with the returned `contact_id`. A final step may answer in plain text from the results: `"type": "respond"` with empty `function_calls`.
88
+ - A session shares one toolset. Later turns are bare queries against the same tools; `reset()` rewinds the conversation and keeps the tools loaded.
89
+
90
+ ## System facts
91
+
92
+ An optional system turn carries environment state as facts, never instructions:
93
+
94
+ ```
95
+ date: 2026-07-21 Tue 14:30; locale: en-US; device: phone; battery: 62%
96
+ ```
97
+
98
+ Recognized keys are `date`, `locale`, `device`, `battery`, `network`, `location`, `user`, and `assistant`. The model resolves relative language against them: "tomorrow at 7" becomes an absolute time only when a `date:` fact licenses it, otherwise the human phrase passes through verbatim. `assistant:` declares the identity the model binds to. Pass the turn with `--system system.txt` on the CLI or `needle.Needle(tools=tools, system="date: ...")` in Python. Needle trains with and without the turn, so omitting it is safe; instructions placed there do not steer the model.
99
+
100
+ ## Deploy Needle
101
+
102
+ Download the folder for your platform from the release:
103
+
104
+ | your device | folder | command-line | library |
105
+ | --- | --- | --- | --- |
106
+ | Mac (Apple Silicon) | `macos-arm64` | `needle` | `libneedle.a` |
107
+ | Linux x86-64 (PC, server, AMD) | `linux-x86_64` | `needle` | `libneedle.a` |
108
+ | Linux ARM64 (Raspberry Pi, server) | `linux-arm64` | `needle` | `libneedle.a` |
109
+ | Linux ARMv7 (32-bit) | `linux-armv7` | `needle` | `libneedle.a` |
110
+ | Linux RISC-V | `linux-riscv64` | `needle` | `libneedle.a` |
111
+ | Linux MIPS32el (Ingenic cameras, routers) | `linux-mipsel` | `needle` | `libneedle.a` |
112
+ | Windows x64 | `windows-x86_64` | `needle.exe` | `libneedle.a` |
113
+ | Windows ARM | `windows-arm64` | `needle.exe` | `libneedle.a` |
114
+ | Android | `android-arm64` / `android-armv7` / `android-riscv64` | `needle` | `libneedle.a` |
115
+ | iOS / watchOS / tvOS | `ios-arm64` / `watchos-arm64` / `tvos-arm64` | - | `libneedle.a` |
116
+ | Cortex-M (bare-metal/RTOS) | `cortex-m4` / `cortex-m7` / `cortex-m55` | - | `libneedle.a` |
117
+ | Browser / Node (WebAssembly) | `wasm` | - | `needle.js` + `needle.wasm` |
118
+
119
+ To run it, use the command-line binary. On macOS, Linux, or Android:
120
+
121
+ ```sh
122
+ # answer one query and exit
123
+ ./needle --tools tools.json --prompt "dim the living room to 30"
124
+
125
+ # or an HTTP server on localhost:8080 (POST /complete {"input": "..."})
126
+ ./needle --tools tools.json --serve
127
+
128
+ # with a large tool catalogue, persist tool embeddings across runs
129
+ ./needle --tools tools.json --tool-index tools.idx --serve
130
+ ```
131
+
132
+ `tools.json` is a JSON array of the functions the assistant may call:
133
+
134
+ ```json
135
+ [
136
+ {
137
+ "name": "set_lights",
138
+ "description": "Turn a room's lights on or off and set brightness",
139
+ "parameters": {
140
+ "type": "object",
141
+ "properties": {
142
+ "room": { "type": "string" },
143
+ "on": { "type": "boolean" },
144
+ "brightness": { "type": "integer", "description": "0 to 100" }
145
+ },
146
+ "required": ["room", "on"]
147
+ }
148
+ },
149
+ {
150
+ "name": "play_music",
151
+ "description": "Play music matching a mood, genre, or artist",
152
+ "parameters": {
153
+ "type": "object",
154
+ "properties": { "query": { "type": "string" } },
155
+ "required": ["query"]
156
+ }
157
+ },
158
+ {
159
+ "name": "send_message",
160
+ "description": "Text a contact",
161
+ "parameters": {
162
+ "type": "object",
163
+ "properties": {
164
+ "to": { "type": "string" },
165
+ "body": { "type": "string" }
166
+ },
167
+ "required": ["to", "body"]
168
+ }
169
+ }
170
+ ]
171
+ ```
172
+
173
+ ## Tool retrieval
174
+
175
+ Ten or fewer declared tools render directly. Above that, retrieval engages: at init every tool schema is embedded once by a built-in contrastive head,
176
+ each turn embeds the query, and only the ten highest-scoring tools enter the context, with the grammar rebuilt over just that subset. an unselected
177
+ tool is unreachable, not merely unlikely. `--tool-index <path>` (CLI) or `tool_index_path` (Python) persists the embeddings on disk, keyed by a fingerprint
178
+ over the schemas and the model; a matching fingerprint loads instantly, a changed schema re-embeds only what changed.
179
+
180
+ ## Confidence
181
+
182
+ The `confidence` field is the minimum of two signals: a calibrated post-hoc head that scores the full prompt plus the call the model just produced, and
183
+ the decoding probability of the call tokens. A call is accepted only when both agree, so the failure mode is escalation, not wrong execution. The contract:
184
+ pick a threshold for your product, act at or above it, re-ask or route to a bigger model below it. Off-topic requests return the empty call `[]`.
185
+
186
+ ## Custom weights
187
+
188
+ - `needle_load(cact, n)` borrows the caller's buffer: no copy is made, the pointer must stay valid and unmodified until the next `needle_load` call or process exit. This is the load path when weights are not embedded (WebAssembly, or bytes fetched over a network); to load a `.cact` from disk, read the file and pass the bytes.
189
+ - Embedded builds keep the weights in the binary's read-only section; all weight bytes stay file-backed and evictable, nothing is copied to the heap.
190
+
191
+ ## Extraction
192
+
193
+ Extraction is the same exchange as tool calling: declare the record schema as the only tool and pass the content as the prompt; the passage sits where the query sits, and the returned call's `arguments` are the extracted fields. With one declared tool the grammar admits exactly one call of that name, the `tool_choice` equivalent, so schema conformance is guaranteed rather than requested. There is no separate JSON mode.
194
+
195
+ `schema.json` describes the record to extract:
196
+
197
+ ```json
198
+ [
199
+ {
200
+ "name": "receipt",
201
+ "description": "A purchase receipt shared as text",
202
+ "parameters": {
203
+ "type": "object",
204
+ "properties": {
205
+ "merchant": { "type": "string" },
206
+ "total": { "type": "number" },
207
+ "currency": { "type": "string" },
208
+ "line_items": { "type": "array", "items": { "type": "object" } }
209
+ },
210
+ "required": ["merchant", "total"]
211
+ }
212
+ }
213
+ ]
214
+ ```
215
+
216
+ ```sh
217
+ ./needle --tools schema.json --prompt "GreenMart receipt: oat milk 3.50, total 7.75 paid by visa"
218
+ ```
219
+
220
+ ```json
221
+ { "type": "call", "function_calls": [ { "name": "receipt", "arguments": { "merchant": "GreenMart", "total": 7.75 } } ] }
222
+ ```