apple-llm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/chunk-FQTRQ3KP.js +1590 -0
- package/dist/cli.cjs +2027 -0
- package/dist/cli.js +456 -0
- package/dist/index.cjs +1667 -0
- package/dist/index.d.cts +690 -0
- package/dist/index.d.ts +690 -0
- package/dist/index.js +80 -0
- package/package.json +61 -0
- package/swift/helper.swift +900 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 apple-llm contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# apple-llm
|
|
2
|
+
|
|
3
|
+
Apple's on-device and Private Cloud Compute LLMs, from Node. No API key, no
|
|
4
|
+
account, no developer program membership.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install apple-llm
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { AppleLLM, probe } from 'apple-llm';
|
|
12
|
+
|
|
13
|
+
await probe();
|
|
14
|
+
// { device: { available, contextSize, variant }, cloud: { available, installed } }
|
|
15
|
+
|
|
16
|
+
const llm = new AppleLLM({ tier: 'device' }); // 'device' | 'cloud' | 'auto'
|
|
17
|
+
await llm.text('Summarize this', { system: 'You are terse.' });
|
|
18
|
+
await llm.json('Extract the fields', { schema }); // guaranteed to match, on device
|
|
19
|
+
llm.close();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
ESM + CJS, fully typed, **zero runtime dependencies**. A small Swift helper is
|
|
23
|
+
compiled on first use and cached; there is no postinstall script, so installing
|
|
24
|
+
on Linux or an Intel Mac always succeeds.
|
|
25
|
+
|
|
26
|
+
## Read this before you use it
|
|
27
|
+
|
|
28
|
+
- **macOS 26+ on Apple Silicon.** No fallback anywhere else. Import always
|
|
29
|
+
works; `probe()` returns `available: false` with an actionable reason.
|
|
30
|
+
- **The on-device model is small** (~20B sparse, 1–4B active, 8192-token context
|
|
31
|
+
on macOS 27). Good at classification, extraction, tagging, rewriting and short
|
|
32
|
+
prose. **Bad at code generation and long reasoning.**
|
|
33
|
+
- **The cloud tier is not local.** It sends your prompt to Apple's Private Cloud
|
|
34
|
+
Compute, off your machine. Free but quota'd, reached through a private
|
|
35
|
+
Shortcuts action Apple can change in any OS release, and with no constrained
|
|
36
|
+
decoding — so `json()` there is a request, not a guarantee.
|
|
37
|
+
- **Streaming is text-only, tools are Apple's built-ins.** `stream()` gives
|
|
38
|
+
partials-as-they-arrive; `tools: ['ocr', 'barcode', 'spotlight']` enables
|
|
39
|
+
on-device Vision/Spotlight tools. Generic user-supplied function calling is
|
|
40
|
+
still a later version.
|
|
41
|
+
|
|
42
|
+
## Two traps worth knowing
|
|
43
|
+
|
|
44
|
+
**Never set `temperature: 0`.** Constrained decoding already guarantees the
|
|
45
|
+
schema, so greedy decoding buys nothing and reliably degenerates — it padded an
|
|
46
|
+
unbounded array forever, then ran away inside a single string (2.7KB of
|
|
47
|
+
`"tasks-tasks-tasks-…"`), turning a 2s call into 20s. The default is `0.4`.
|
|
48
|
+
Apple honours `maxItems` but ignores `maxLength`: bound your arrays.
|
|
49
|
+
|
|
50
|
+
**Keep the client alive.** One long-lived helper process holds the model
|
|
51
|
+
resident; spawning one per call measured ~17s against ~1.5s. Construct
|
|
52
|
+
`AppleLLM` once, `close()` when you are done. Both traps produce *correct*
|
|
53
|
+
output, only slower, so neither looks like a bug.
|
|
54
|
+
|
|
55
|
+
Apple serialises inference regardless — 4 concurrent requests measured 29.19s
|
|
56
|
+
against 29.45s sequentially — so the request queue serialises deliberately.
|
|
57
|
+
|
|
58
|
+
## What macOS 27 adds
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const { device, cloud } = await probe();
|
|
62
|
+
device.capabilities; // { vision, guidedGeneration, reasoning, toolCalling }
|
|
63
|
+
device.useCases; // ['general', 'contentTagging']
|
|
64
|
+
cloud.quota; // { status: 'belowLimit' | 'limitReached', approachingLimit, resetDate }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
On this machine the on-device model reports vision, guided generation and tool
|
|
68
|
+
calling, but **not** reasoning. `cloud.quota` is real quota state read from
|
|
69
|
+
`PrivateCloudComputeLanguageModel.quotaUsage` — the entitlement that blocks PCC
|
|
70
|
+
*inference* does not block reading it — so an exhausted quota becomes an
|
|
71
|
+
immediate `QuotaError` rather than a wasted Shortcuts round trip.
|
|
72
|
+
|
|
73
|
+
**Count tokens before sending**, turning a `ContextLengthError` into arithmetic:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const { tokens, contextSize } = await llm.countTokens(prompt, { system });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Reproducible output without the `temperature: 0` trap.** Greedy decoding is
|
|
80
|
+
deterministic *and* degenerates; seeded top-k is deterministic and does not:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
await llm.text(prompt, { sampling: { mode: 'topK', k: 50, seed: 42 }, temperature: 0.9 });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
It works only because the helper uses a fresh session per request — reusing one
|
|
87
|
+
changes the transcript and with it the output.
|
|
88
|
+
|
|
89
|
+
**Vision**, by path — optionally labelled for follow-up turns. A missing file
|
|
90
|
+
is an error, never a silent drop:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
await llm.text('What is in this image?', { images: ['./photo.png'] });
|
|
94
|
+
await llm.text('What is in the image labelled chart?', {
|
|
95
|
+
images: [{ path: './scan.png', label: 'chart' }],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Streaming, conversations, tools, documents, and Write-with-Siri presets:**
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
await llm.stream('Count to three.', { onDelta: (d) => process.stdout.write(d) });
|
|
103
|
+
|
|
104
|
+
const chat = llm.conversation('trip-planning');
|
|
105
|
+
await chat.text('My cat is called Biscuit.');
|
|
106
|
+
await chat.text('What is my cat called?'); // Biscuit.
|
|
107
|
+
await chat.history(); // mirrored turns, oldest first
|
|
108
|
+
|
|
109
|
+
await llm.text('Read the total.', { images: ['./receipt.png'], tools: ['ocr'] });
|
|
110
|
+
await llm.text('Which notes mention the bridge?', { tools: ['spotlight'] });
|
|
111
|
+
await llm.text('Compare these quotes.', { documents: ['./a.md', './b.md'] });
|
|
112
|
+
|
|
113
|
+
await llm.rewrite('gonna grab a bite', { instruction: 'Make it formal.' });
|
|
114
|
+
await llm.proofread('Their going to the store...');
|
|
115
|
+
await llm.summarize(longText);
|
|
116
|
+
await llm.askScreen('What is on this schedule?');
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**A tagging-specialised model**, `permissive` guardrails for rewriting, and
|
|
120
|
+
`prewarm()` to load model assets up front (worth little once they are resident —
|
|
121
|
+
0.31s against 0.36s here — but real on a cold system):
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const llm = new AppleLLM({ tier: 'device', useCase: 'contentTagging' });
|
|
125
|
+
await new AppleLLM({ guardrails: 'permissive' }).text('Rewrite more formally: …');
|
|
126
|
+
await llm.prewarm();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Errors
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { QuotaError, ModelUnavailableError } from 'apple-llm';
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`ModelUnavailableError` (`.reason` is `appleIntelligenceNotEnabled` /
|
|
136
|
+
`modelNotReady` / `deviceNotEligible` / …), `SchemaRejectedError`,
|
|
137
|
+
`ContextLengthError`, `QuotaError` (`.resetDate`), `TimeoutError`,
|
|
138
|
+
`SetupRequiredError`, `RefusalError`.
|
|
139
|
+
|
|
140
|
+
## CLI
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
apple-llm probe
|
|
144
|
+
apple-llm setup-cloud [--web-search] # one-time, generates + signs locally
|
|
145
|
+
apple-llm run --tier device --system "You are terse." -
|
|
146
|
+
apple-llm run --tier cloud --schema schema.json "Extract the fields"
|
|
147
|
+
apple-llm run --image photo.png "What is in this image?"
|
|
148
|
+
apple-llm run --image scan.png::chart "What is in the image labelled chart?"
|
|
149
|
+
apple-llm run --tool ocr --image receipt.png "Read the total."
|
|
150
|
+
apple-llm run --document a.md --document b.md "Compare these."
|
|
151
|
+
apple-llm run --session trip --stream "What is my cat called?"
|
|
152
|
+
apple-llm history --session trip
|
|
153
|
+
apple-llm reset --session trip
|
|
154
|
+
apple-llm ask-screen "What is on this schedule?"
|
|
155
|
+
apple-llm rewrite "gonna grab a bite"
|
|
156
|
+
apple-llm run --use-case contentTagging --schema tags.json "A recipe for sourdough…"
|
|
157
|
+
apple-llm run --seed 42 "Reproducible output"
|
|
158
|
+
apple-llm count --system "You are terse." - # tokens before sending
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Schemas
|
|
162
|
+
|
|
163
|
+
`json()` runs your JSON Schema through `toAppleSchema()`, which rewrites it into
|
|
164
|
+
the restricted dialect Apple's `GenerationSchema` decoder accepts — eight rules
|
|
165
|
+
covering unions, `x-order`, enums, titles, `$ref`-by-title, `additionalProperties`,
|
|
166
|
+
string-typed `const`, and empty objects. See the
|
|
167
|
+
[full notes in the repo](https://github.com/jagdish/apple-llm#the-generationschema-dialect).
|
|
168
|
+
|
|
169
|
+
The details matter more than they look: constrained decoding makes a schema
|
|
170
|
+
mistake invisible but total. A union collapsed to the wrong branch does not warn
|
|
171
|
+
— it makes the right answer unreachable.
|
|
172
|
+
|
|
173
|
+
## Credit
|
|
174
|
+
|
|
175
|
+
Extracted from [api-scribe](https://github.com/jagdish/api-scribe) (MIT), where
|
|
176
|
+
both routes were discovered and shipped. MIT.
|