cactus-react-native 0.2.10 → 0.2.11

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.
Files changed (38) hide show
  1. package/README.md +52 -776
  2. package/android/src/main/CMakeLists.txt +2 -1
  3. package/android/src/main/java/com/cactus/CactusPackage.java +5 -5
  4. package/android/src/main/java/com/cactus/LlamaContext.java +1 -67
  5. package/android/src/main/jniLibs/arm64-v8a/libcactus.so +0 -0
  6. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8.so +0 -0
  7. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2.so +0 -0
  8. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_dotprod.so +0 -0
  9. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_dotprod_i8mm.so +0 -0
  10. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_i8mm.so +0 -0
  11. package/android/src/newarch/java/com/cactus/CactusModule.java +0 -2
  12. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Info.plist +0 -0
  13. package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
  14. package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/Info.plist +0 -0
  15. package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/_CodeSignature/CodeResources +1 -1
  16. package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
  17. package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/Info.plist +0 -0
  18. package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/_CodeSignature/CodeResources +1 -1
  19. package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
  20. package/lib/commonjs/index.js.map +1 -1
  21. package/lib/commonjs/projectId.js +2 -1
  22. package/lib/commonjs/projectId.js.map +1 -1
  23. package/lib/commonjs/tts.js +124 -12
  24. package/lib/commonjs/tts.js.map +1 -1
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/module/projectId.js +2 -1
  27. package/lib/module/projectId.js.map +1 -1
  28. package/lib/module/tts.js +123 -12
  29. package/lib/module/tts.js.map +1 -1
  30. package/lib/typescript/index.d.ts +1 -1
  31. package/lib/typescript/index.d.ts.map +1 -1
  32. package/lib/typescript/projectId.d.ts +1 -1
  33. package/lib/typescript/projectId.d.ts.map +1 -1
  34. package/lib/typescript/tts.d.ts +46 -2
  35. package/lib/typescript/tts.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/index.ts +1 -0
  38. package/src/tts.ts +209 -15
package/README.md CHANGED
@@ -1,828 +1,104 @@
1
- # Cactus React Native
1
+ ![Cactus Logo](assets/logo.png)
2
2
 
3
- Run LLMs, VLMs, and TTS models directly on mobile devices.
3
+ Official React-Native plugin for Cactus, a framework for deploying LLM/VLM/TTS models locally in your app. Requires iOS 12.0+, Android API 24+ and Yarn. For iOS apps, ensure you have cocoapods or install with `brew install cocoapods`. For Android apps, you need Java 17 installed. Expo is strongly recommended.
4
4
 
5
- ## Installation
5
+ ## Resources
6
+ [![cactus](https://img.shields.io/badge/cactus-000000?logo=github&logoColor=white)](https://github.com/cactus-compute/cactus) [![HuggingFace](https://img.shields.io/badge/HuggingFace-FFD21E?logo=huggingface&logoColor=black)](https://huggingface.co/Cactus-Compute/models?sort=downloads) [![Discord](https://img.shields.io/badge/Discord-5865F2?logo=discord&logoColor=white)](https://discord.gg/bNurx3AXTJ) [![Documentation](https://img.shields.io/badge/Documentation-4285F4?logo=googledocs&logoColor=white)](https://cactuscompute.com/docs/react-native)
6
7
 
7
- ```json
8
- {
9
- "dependencies": {
10
- "cactus-react-native": "^0.2.4",
11
- "react-native-fs": "^2.20.0"
12
- }
13
- }
8
+ ## Installation
9
+ Execute the following command in your project terminal:
10
+ ```bash
11
+ npm install cactus-react-native
12
+ # or
13
+ yarn add cactus-react-native
14
14
  ```
15
+ *N/B*: To build locally or use this repo, see instructions in `example/README.md`
15
16
 
16
- **Setup:**
17
- - iOS: `cd ios && npx pod-install`
18
- - Android: Ensure `minSdkVersion` 24+
19
-
20
- ## Quick Start
21
-
17
+ ## Text Completion
22
18
  ```typescript
23
19
  import { CactusLM } from 'cactus-react-native';
24
- import RNFS from 'react-native-fs';
25
-
26
- const modelPath = `${RNFS.DocumentDirectoryPath}/model.gguf`;
27
20
 
28
21
  const { lm, error } = await CactusLM.init({
29
- model: modelPath,
30
- n_ctx: 2048,
31
- n_threads: 4,
22
+ model: '/path/to/model.gguf', // this is a local model file inside the app sandbox
23
+ n_ctx: 2048,
32
24
  });
33
25
 
34
- if (error) throw error;
35
-
36
26
  const messages = [{ role: 'user', content: 'Hello!' }];
37
- const result = await lm.completion(messages, { n_predict: 100 });
38
- console.log(result.text);
39
- lm.release();
40
- ```
41
-
42
- ## Streaming Chat
43
-
44
- ```typescript
45
- import React, { useState, useEffect } from 'react';
46
- import { View, Text, TextInput, TouchableOpacity, ScrollView, ActivityIndicator } from 'react-native';
47
- import { CactusLM } from 'cactus-react-native';
48
- import RNFS from 'react-native-fs';
49
-
50
- interface Message {
51
- role: 'user' | 'assistant';
52
- content: string;
53
- }
54
-
55
- export default function ChatScreen() {
56
- const [lm, setLM] = useState<CactusLM | null>(null);
57
- const [messages, setMessages] = useState<Message[]>([]);
58
- const [input, setInput] = useState('');
59
- const [isLoading, setIsLoading] = useState(true);
60
- const [isGenerating, setIsGenerating] = useState(false);
61
-
62
- useEffect(() => {
63
- initializeModel();
64
- return () => {
65
- lm?.release();
66
- };
67
- }, []);
68
-
69
- const initializeModel = async () => {
70
- try {
71
- const modelUrl = 'https://huggingface.co/Cactus-Compute/Qwen3-600m-Instruct-GGUF/resolve/main/Qwen3-0.6B-Q8_0.gguf';
72
- const modelPath = await downloadModel(modelUrl, 'qwen-600m.gguf');
73
-
74
- const { lm: model, error } = await CactusLM.init({
75
- model: modelPath,
76
- n_ctx: 2048,
77
- n_threads: 4,
78
- n_gpu_layers: 99,
79
- });
80
-
81
- if (error) throw error;
82
- setLM(model);
83
- } catch (error) {
84
- console.error('Failed to initialize model:', error);
85
- } finally {
86
- setIsLoading(false);
87
- }
88
- };
89
-
90
- const downloadModel = async (url: string, filename: string): Promise<string> => {
91
- const path = `${RNFS.DocumentDirectoryPath}/${filename}`;
92
-
93
- if (await RNFS.exists(path)) return path;
94
-
95
- console.log('Downloading model...');
96
- await RNFS.downloadFile({
97
- fromUrl: url,
98
- toFile: path,
99
- progress: (res) => {
100
- const progress = res.bytesWritten / res.contentLength;
101
- console.log(`Download progress: ${(progress * 100).toFixed(1)}%`);
102
- },
103
- }).promise;
104
-
105
- return path;
106
- };
107
-
108
- const sendMessage = async () => {
109
- if (!lm || !input.trim() || isGenerating) return;
110
-
111
- const userMessage: Message = { role: 'user', content: input.trim() };
112
- const newMessages = [...messages, userMessage];
113
- setMessages([...newMessages, { role: 'assistant', content: '' }]);
114
- setInput('');
115
- setIsGenerating(true);
116
-
117
- try {
118
- let response = '';
119
- await lm.completion(newMessages, {
120
- n_predict: 200,
121
- temperature: 0.7,
122
- stop: ['</s>', '<|end|>'],
123
- }, (token) => {
124
- response += token.token;
125
- setMessages(prev => [
126
- ...prev.slice(0, -1),
127
- { role: 'assistant', content: response }
128
- ]);
129
- });
130
- } catch (error) {
131
- console.error('Generation failed:', error);
132
- setMessages(prev => [
133
- ...prev.slice(0, -1),
134
- { role: 'assistant', content: 'Error generating response' }
135
- ]);
136
- } finally {
137
- setIsGenerating(false);
138
- }
139
- };
140
-
141
- if (isLoading) {
142
- return (
143
- <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
144
- <ActivityIndicator size="large" />
145
- <Text style={{ marginTop: 16 }}>Loading model...</Text>
146
- </View>
147
- );
148
- }
149
-
150
- return (
151
- <View style={{ flex: 1, backgroundColor: '#f5f5f5' }}>
152
- <ScrollView style={{ flex: 1, padding: 16 }}>
153
- {messages.map((msg, index) => (
154
- <View
155
- key={index}
156
- style={{
157
- backgroundColor: msg.role === 'user' ? '#007AFF' : '#ffffff',
158
- padding: 12,
159
- marginVertical: 4,
160
- borderRadius: 12,
161
- alignSelf: msg.role === 'user' ? 'flex-end' : 'flex-start',
162
- maxWidth: '80%',
163
- shadowColor: '#000',
164
- shadowOffset: { width: 0, height: 1 },
165
- shadowOpacity: 0.2,
166
- shadowRadius: 2,
167
- elevation: 2,
168
- }}
169
- >
170
- <Text style={{
171
- color: msg.role === 'user' ? '#ffffff' : '#000000',
172
- fontSize: 16,
173
- }}>
174
- {msg.content}
175
- </Text>
176
- </View>
177
- ))}
178
- </ScrollView>
179
-
180
- <View style={{
181
- flexDirection: 'row',
182
- padding: 16,
183
- backgroundColor: '#ffffff',
184
- borderTopWidth: 1,
185
- borderTopColor: '#e0e0e0',
186
- }}>
187
- <TextInput
188
- style={{
189
- flex: 1,
190
- borderWidth: 1,
191
- borderColor: '#e0e0e0',
192
- borderRadius: 20,
193
- paddingHorizontal: 16,
194
- paddingVertical: 10,
195
- fontSize: 16,
196
- backgroundColor: '#f8f8f8',
197
- }}
198
- value={input}
199
- onChangeText={setInput}
200
- placeholder="Type a message..."
201
- multiline
202
- onSubmitEditing={sendMessage}
203
- />
204
- <TouchableOpacity
205
- onPress={sendMessage}
206
- disabled={isGenerating || !input.trim()}
207
- style={{
208
- backgroundColor: isGenerating ? '#cccccc' : '#007AFF',
209
- borderRadius: 20,
210
- paddingHorizontal: 16,
211
- paddingVertical: 10,
212
- marginLeft: 8,
213
- justifyContent: 'center',
214
- }}
215
- >
216
- <Text style={{ color: '#ffffff', fontWeight: 'bold' }}>
217
- {isGenerating ? '...' : 'Send'}
218
- </Text>
219
- </TouchableOpacity>
220
- </View>
221
- </View>
222
- );
223
- }
27
+ const params = { n_predict: 100, temperature: 0.7 };
28
+ const response = await lm.completion(messages, params);
224
29
  ```
225
-
226
- ## Core APIs
227
-
228
- ### CactusLM
229
-
230
- ```typescript
30
+ ## Embeddings
31
+ ```typescript
231
32
  import { CactusLM } from 'cactus-react-native';
232
33
 
233
34
  const { lm, error } = await CactusLM.init({
234
- model: '/path/to/model.gguf',
235
- n_ctx: 2048,
236
- n_threads: 4,
237
- n_gpu_layers: 99,
238
- embedding: true,
35
+ model: '/path/to/model.gguf', // local model file inside the app sandbox
36
+ n_ctx: 2048,
37
+ embedding: true,
239
38
  });
240
39
 
241
- const messages = [{ role: 'user', content: 'What is AI?' }];
242
- const result = await lm.completion(messages, {
243
- n_predict: 200,
244
- temperature: 0.7,
245
- stop: ['</s>'],
246
- });
247
-
248
- const embedding = await lm.embedding('Your text here');
249
- await lm.rewind();
250
- await lm.release();
40
+ const text = 'Your text to embed';
41
+ const params = { normalize: true };
42
+ const result = await lm.embedding(text, params);
251
43
  ```
252
-
253
- ### CactusVLM
254
-
44
+ ## Visual Language Models
255
45
  ```typescript
256
46
  import { CactusVLM } from 'cactus-react-native';
257
47
 
258
48
  const { vlm, error } = await CactusVLM.init({
259
- model: '/path/to/vision-model.gguf',
260
- mmproj: '/path/to/mmproj.gguf',
261
- n_ctx: 2048,
49
+ model: '/path/to/vision-model.gguf', // local model file inside the app sandbox
50
+ mmproj: '/path/to/mmproj.gguf', // local model file inside the app sandbox
262
51
  });
263
52
 
264
53
  const messages = [{ role: 'user', content: 'Describe this image' }];
265
- const result = await vlm.completion(messages, {
266
- images: ['/path/to/image.jpg'],
267
- n_predict: 200,
268
- temperature: 0.3,
269
- });
270
-
271
- await vlm.release();
272
- ```
273
-
274
- ### CactusTTS
275
-
276
- ```typescript
277
- import { CactusTTS, initLlama } from 'cactus-react-native';
278
-
279
- const context = await initLlama({
280
- model: '/path/to/tts-model.gguf',
281
- n_ctx: 1024,
282
- });
283
54
 
284
- const tts = await CactusTTS.init(context, '/path/to/vocoder.gguf');
285
-
286
- const audio = await tts.generate(
287
- 'Hello, this is text-to-speech',
288
- '{"speaker_id": 0}'
289
- );
290
-
291
- await tts.release();
292
- ```
293
-
294
- ## Advanced Usage
295
-
296
- ### Model Manager
297
-
298
- ```typescript
299
- class ModelManager {
300
- private models = new Map<string, CactusLM | CactusVLM>();
301
-
302
- async loadLM(name: string, modelPath: string): Promise<CactusLM> {
303
- if (this.models.has(name)) {
304
- return this.models.get(name) as CactusLM;
305
- }
306
-
307
- const { lm, error } = await CactusLM.init({
308
- model: modelPath,
309
- n_ctx: 2048,
310
- });
311
-
312
- if (error) throw error;
313
- this.models.set(name, lm);
314
- return lm;
315
- }
316
-
317
- async loadVLM(name: string, modelPath: string, mmprojPath: string): Promise<CactusVLM> {
318
- if (this.models.has(name)) {
319
- return this.models.get(name) as CactusVLM;
320
- }
321
-
322
- const { vlm, error } = await CactusVLM.init({
323
- model: modelPath,
324
- mmproj: mmprojPath,
325
- });
326
-
327
- if (error) throw error;
328
- this.models.set(name, vlm);
329
- return vlm;
330
- }
331
-
332
- async releaseModel(name: string): Promise<void> {
333
- const model = this.models.get(name);
334
- if (model) {
335
- await model.release();
336
- this.models.delete(name);
337
- }
338
- }
339
-
340
- async releaseAll(): Promise<void> {
341
- await Promise.all(
342
- Array.from(this.models.values()).map(model => model.release())
343
- );
344
- this.models.clear();
345
- }
346
- }
347
-
348
- const modelManager = new ModelManager();
349
- ```
350
-
351
- ### File Management Hook
352
-
353
- ```typescript
354
- import { useState, useCallback } from 'react';
355
- import RNFS from 'react-native-fs';
356
-
357
- interface DownloadProgress {
358
- progress: number;
359
- isDownloading: boolean;
360
- error: string | null;
361
- }
362
-
363
- export const useModelDownload = () => {
364
- const [downloads, setDownloads] = useState<Map<string, DownloadProgress>>(new Map());
365
-
366
- const downloadModel = useCallback(async (url: string, filename: string): Promise<string> => {
367
- const path = `${RNFS.DocumentDirectoryPath}/${filename}`;
368
-
369
- if (await RNFS.exists(path)) {
370
- const stats = await RNFS.stat(path);
371
- if (stats.size > 0) return path;
372
- }
373
-
374
- setDownloads(prev => new Map(prev.set(filename, {
375
- progress: 0,
376
- isDownloading: true,
377
- error: null,
378
- })));
379
-
380
- try {
381
- await RNFS.downloadFile({
382
- fromUrl: url,
383
- toFile: path,
384
- progress: (res) => {
385
- const progress = res.bytesWritten / res.contentLength;
386
- setDownloads(prev => new Map(prev.set(filename, {
387
- progress,
388
- isDownloading: true,
389
- error: null,
390
- })));
391
- },
392
- }).promise;
393
-
394
- setDownloads(prev => new Map(prev.set(filename, {
395
- progress: 1,
396
- isDownloading: false,
397
- error: null,
398
- })));
399
-
400
- return path;
401
- } catch (error) {
402
- setDownloads(prev => new Map(prev.set(filename, {
403
- progress: 0,
404
- isDownloading: false,
405
- error: error.message,
406
- })));
407
- throw error;
408
- }
409
- }, []);
410
-
411
- return { downloadModel, downloads };
55
+ const params = {
56
+ images: ['/absolute/path/to/image.jpg'],
57
+ n_predict: 200,
58
+ temperature: 0.3,
412
59
  };
413
- ```
414
-
415
- ### Vision Chat Component
416
-
417
- ```typescript
418
- import React, { useState, useEffect } from 'react';
419
- import { View, Text, TouchableOpacity, Image, Alert } from 'react-native';
420
- import { launchImageLibrary } from 'react-native-image-picker';
421
- import { CactusVLM } from 'cactus-react-native';
422
- import RNFS from 'react-native-fs';
423
-
424
- export default function VisionChat() {
425
- const [vlm, setVLM] = useState<CactusVLM | null>(null);
426
- const [imagePath, setImagePath] = useState<string | null>(null);
427
- const [response, setResponse] = useState('');
428
- const [isLoading, setIsLoading] = useState(true);
429
- const [isAnalyzing, setIsAnalyzing] = useState(false);
430
-
431
- useEffect(() => {
432
- initializeVLM();
433
- return () => {
434
- vlm?.release();
435
- };
436
- }, []);
437
60
 
438
- const initializeVLM = async () => {
439
- try {
440
- const modelUrl = 'https://huggingface.co/Cactus-Compute/SmolVLM2-500m-Instruct-GGUF/resolve/main/SmolVLM2-500M-Video-Instruct-Q8_0.gguf';
441
- const mmprojUrl = 'https://huggingface.co/Cactus-Compute/SmolVLM2-500m-Instruct-GGUF/resolve/main/mmproj-SmolVLM2-500M-Video-Instruct-Q8_0.gguf';
442
-
443
- const [modelPath, mmprojPath] = await Promise.all([
444
- downloadFile(modelUrl, 'smolvlm-model.gguf'),
445
- downloadFile(mmprojUrl, 'smolvlm-mmproj.gguf'),
446
- ]);
447
-
448
- const { vlm: model, error } = await CactusVLM.init({
449
- model: modelPath,
450
- mmproj: mmprojPath,
451
- n_ctx: 2048,
452
- });
453
-
454
- if (error) throw error;
455
- setVLM(model);
456
- } catch (error) {
457
- console.error('Failed to initialize VLM:', error);
458
- Alert.alert('Error', 'Failed to initialize vision model');
459
- } finally {
460
- setIsLoading(false);
461
- }
462
- };
463
-
464
- const downloadFile = async (url: string, filename: string): Promise<string> => {
465
- const path = `${RNFS.DocumentDirectoryPath}/${filename}`;
466
-
467
- if (await RNFS.exists(path)) return path;
468
-
469
- await RNFS.downloadFile({ fromUrl: url, toFile: path }).promise;
470
- return path;
471
- };
472
-
473
- const pickImage = () => {
474
- launchImageLibrary(
475
- {
476
- mediaType: 'photo',
477
- quality: 0.8,
478
- includeBase64: false,
479
- },
480
- (response) => {
481
- if (response.assets && response.assets[0]) {
482
- setImagePath(response.assets[0].uri!);
483
- setResponse('');
484
- }
485
- }
486
- );
487
- };
488
-
489
- const analyzeImage = async () => {
490
- if (!vlm || !imagePath) return;
491
-
492
- setIsAnalyzing(true);
493
- try {
494
- const messages = [{ role: 'user', content: 'Describe this image in detail' }];
495
-
496
- let analysisResponse = '';
497
- const result = await vlm.completion(messages, {
498
- images: [imagePath],
499
- n_predict: 300,
500
- temperature: 0.3,
501
- }, (token) => {
502
- analysisResponse += token.token;
503
- setResponse(analysisResponse);
504
- });
505
-
506
- setResponse(analysisResponse || result.text);
507
- } catch (error) {
508
- console.error('Analysis failed:', error);
509
- Alert.alert('Error', 'Failed to analyze image');
510
- } finally {
511
- setIsAnalyzing(false);
512
- }
513
- };
514
-
515
- if (isLoading) {
516
- return (
517
- <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
518
- <Text>Loading vision model...</Text>
519
- </View>
520
- );
521
- }
522
-
523
- return (
524
- <View style={{ flex: 1, padding: 16 }}>
525
- <Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 20 }}>
526
- Vision Chat
527
- </Text>
528
-
529
- {imagePath && (
530
- <Image
531
- source={{ uri: imagePath }}
532
- style={{
533
- width: '100%',
534
- height: 200,
535
- borderRadius: 8,
536
- marginBottom: 16,
537
- }}
538
- resizeMode="contain"
539
- />
540
- )}
541
-
542
- <View style={{ flexDirection: 'row', marginBottom: 16 }}>
543
- <TouchableOpacity
544
- onPress={pickImage}
545
- style={{
546
- backgroundColor: '#007AFF',
547
- padding: 12,
548
- borderRadius: 8,
549
- marginRight: 8,
550
- flex: 1,
551
- }}
552
- >
553
- <Text style={{ color: 'white', textAlign: 'center', fontWeight: 'bold' }}>
554
- Pick Image
555
- </Text>
556
- </TouchableOpacity>
557
-
558
- <TouchableOpacity
559
- onPress={analyzeImage}
560
- disabled={!imagePath || isAnalyzing}
561
- style={{
562
- backgroundColor: !imagePath || isAnalyzing ? '#cccccc' : '#34C759',
563
- padding: 12,
564
- borderRadius: 8,
565
- flex: 1,
566
- }}
567
- >
568
- <Text style={{ color: 'white', textAlign: 'center', fontWeight: 'bold' }}>
569
- {isAnalyzing ? 'Analyzing...' : 'Analyze'}
570
- </Text>
571
- </TouchableOpacity>
572
- </View>
573
-
574
- <View style={{
575
- flex: 1,
576
- backgroundColor: '#f8f8f8',
577
- borderRadius: 8,
578
- padding: 16,
579
- }}>
580
- <Text style={{ fontSize: 16, lineHeight: 24 }}>
581
- {response || 'Select an image and tap Analyze to get started'}
582
- </Text>
583
- </View>
584
- </View>
585
- );
586
- }
61
+ const response = await vlm.completion(messages, params);
587
62
  ```
588
-
589
- ### Cloud Fallback
590
-
591
- ```typescript
592
- const { vlm } = await CactusVLM.init({
593
- model: '/path/to/model.gguf',
594
- mmproj: '/path/to/mmproj.gguf',
595
- }, undefined, 'your_cactus_token');
596
-
597
- const result = await vlm.completion(messages, {
598
- images: ['/path/to/image.jpg'],
599
- mode: 'localfirst', // ("remotefirst", "local", "remote")
600
- });
601
- ```
602
-
603
- ### Embeddings & Similarity
604
-
63
+ ## Cloud Fallback
605
64
  ```typescript
606
65
  const { lm } = await CactusLM.init({
607
66
  model: '/path/to/model.gguf',
608
- embedding: true,
609
- });
610
-
611
- const embedding1 = await lm.embedding('machine learning');
612
- const embedding2 = await lm.embedding('artificial intelligence');
613
-
614
- function cosineSimilarity(a: number[], b: number[]): number {
615
- const dotProduct = a.reduce((sum, ai, i) => sum + ai * b[i], 0);
616
- const magnitudeA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
617
- const magnitudeB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
618
- return dotProduct / (magnitudeA * magnitudeB);
619
- }
620
-
621
- const similarity = cosineSimilarity(embedding1.embedding, embedding2.embedding);
622
- console.log('Similarity:', similarity);
623
- ```
624
-
625
- ## Error Handling & Performance
626
-
627
- ### Production Error Handling
628
-
629
- ```typescript
630
- async function safeModelInit(modelPath: string): Promise<CactusLM> {
631
- const configs = [
632
- { model: modelPath, n_ctx: 4096, n_gpu_layers: 99 },
633
- { model: modelPath, n_ctx: 2048, n_gpu_layers: 99 },
634
- { model: modelPath, n_ctx: 2048, n_gpu_layers: 0 },
635
- { model: modelPath, n_ctx: 1024, n_gpu_layers: 0 },
636
- ];
637
-
638
- for (const config of configs) {
639
- try {
640
- const { lm, error } = await CactusLM.init(config);
641
- if (error) throw error;
642
- return lm;
643
- } catch (error) {
644
- console.warn('Config failed:', config, error.message);
645
- if (configs.indexOf(config) === configs.length - 1) {
646
- throw new Error(`All configurations failed. Last error: ${error.message}`);
647
- }
648
- }
649
- }
650
-
651
- throw new Error('Model initialization failed');
652
- }
653
-
654
- async function safeCompletion(lm: CactusLM, messages: any[], retries = 3): Promise<any> {
655
- for (let i = 0; i < retries; i++) {
656
- try {
657
- return await lm.completion(messages, { n_predict: 200 });
658
- } catch (error) {
659
- if (error.message.includes('Context is busy') && i < retries - 1) {
660
- await new Promise(resolve => setTimeout(resolve, 1000));
661
- continue;
662
- }
663
- throw error;
664
- }
665
- }
666
- }
667
- ```
668
-
669
- ### Memory Management
670
-
671
- ```typescript
672
- import { AppState, AppStateStatus } from 'react-native';
673
-
674
- class AppModelManager {
675
- private modelManager = new ModelManager();
676
-
677
- constructor() {
678
- AppState.addEventListener('change', this.handleAppStateChange);
679
- }
680
-
681
- private handleAppStateChange = (nextAppState: AppStateStatus) => {
682
- if (nextAppState === 'background') {
683
- // Release non-essential models when app goes to background
684
- this.modelManager.releaseAll();
685
- }
686
- };
687
-
688
- async getModel(name: string, modelPath: string): Promise<CactusLM> {
689
- try {
690
- return await this.modelManager.loadLM(name, modelPath);
691
- } catch (error) {
692
- // Handle low memory by releasing other models
693
- await this.modelManager.releaseAll();
694
- return await this.modelManager.loadLM(name, modelPath);
695
- }
696
- }
697
- }
698
- ```
699
-
700
- ### Performance Optimization
67
+ n_ctx: 2048,
68
+ }, undefined, 'your_cactus_token');
701
69
 
702
- ```typescript
703
- // Optimize for device capabilities
704
- const getOptimalConfig = () => {
705
- const { OS } = Platform;
706
- const isHighEndDevice = true; // Implement device detection logic
707
-
708
- return {
709
- n_ctx: isHighEndDevice ? 4096 : 2048,
710
- n_gpu_layers: OS === 'ios' ? 99 : 0, // iOS generally has better GPU support
711
- n_threads: isHighEndDevice ? 6 : 4,
712
- n_batch: isHighEndDevice ? 512 : 256,
713
- };
714
- };
70
+ // Try local first, fallback to cloud if local fails (its blazing fast)
71
+ const embedding = await lm.embedding('text', undefined, 'localfirst');
715
72
 
716
- const config = getOptimalConfig();
717
- const { lm } = await CactusLM.init({
718
- model: modelPath,
719
- ...config,
720
- });
73
+ // local (default): strictly only run on-device
74
+ // localfirst: fallback to cloud if device fails
75
+ // remotefirst: primarily remote, run local if API fails
76
+ // remote: strictly run on cloud
721
77
  ```
722
78
 
723
- ## Tool Calling with CactusAgent
724
-
725
- The `CactusAgent` class extends `CactusLM` with built-in tool calling capabilities:
726
-
79
+ ## Agents
727
80
  ```typescript
728
81
  import { CactusAgent } from 'cactus-react-native';
729
82
 
83
+ // we recommend Qwen 3 family, 0.6B is great
730
84
  const { agent, error } = await CactusAgent.init({
731
- model: '/path/to/model.gguf', // we recommend Qwen 3 family, 0.6B is great
732
- n_ctx: 2048,
85
+ model: '/path/to/model.gguf',
86
+ n_ctx: 2048,
733
87
  });
734
88
 
735
89
  const weatherTool = agent.addTool(
736
- (location: string) => `Weather in ${location}: 72°F, sunny`,
737
- 'Get current weather for a location',
738
- {
739
- location: { type: 'string', description: 'City name', required: true }
740
- }
90
+ (location: string) => `Weather in ${location}: 72°F, sunny`,
91
+ 'Get current weather for a location',
92
+ {
93
+ location: { type: 'string', description: 'City name', required: true }
94
+ }
741
95
  );
742
96
 
743
97
  const messages = [{ role: 'user', content: 'What\'s the weather in NYC?' }];
744
- const result = await agent.completionWithTools(messages, {
98
+ const result = await agent.completionWithTools(messages, {
745
99
  n_predict: 200,
746
100
  temperature: 0.7,
747
101
  });
748
102
 
749
103
  await agent.release();
750
- ```
751
-
752
- ### Custom Tools
753
-
754
- ```typescript
755
- // Math calculator tool
756
- const calculator = agent.addTool(
757
- (expression: string) => {
758
- try {
759
- return `Result: ${eval(expression)}`;
760
- } catch (e) {
761
- return 'Invalid expression';
762
- }
763
- },
764
- 'Evaluate mathematical expressions',
765
- {
766
- expression: { type: 'string', description: 'Math expression to evaluate', required: true }
767
- }
768
- );
769
- ```
770
-
771
- ## API Reference
772
-
773
- ### CactusLM
774
-
775
- **init(params, onProgress?, cactusToken?)**
776
- - `model: string` - Path to GGUF model file
777
- - `n_ctx?: number` - Context size (default: 2048)
778
- - `n_threads?: number` - CPU threads (default: 4)
779
- - `n_gpu_layers?: number` - GPU layers (default: 99)
780
- - `embedding?: boolean` - Enable embeddings (default: false)
781
- - `n_batch?: number` - Batch size (default: 512)
782
-
783
- **completion(messages, params?, callback?)**
784
- - `messages: Array<{role: string, content: string}>` - Chat messages
785
- - `n_predict?: number` - Max tokens (default: -1)
786
- - `temperature?: number` - Randomness 0.0-2.0 (default: 0.8)
787
- - `top_p?: number` - Nucleus sampling (default: 0.95)
788
- - `top_k?: number` - Top-k sampling (default: 40)
789
- - `stop?: string[]` - Stop sequences
790
- - `callback?: (token) => void` - Streaming callback
791
-
792
- **embedding(text, params?, mode?)**
793
- - `text: string` - Text to embed
794
- - `mode?: string` - 'local' | 'localfirst' | 'remotefirst' | 'remote'
795
-
796
- ### CactusVLM
797
-
798
- **init(params, onProgress?, cactusToken?)**
799
- - All CactusLM params plus:
800
- - `mmproj: string` - Path to multimodal projector
801
-
802
- **completion(messages, params?, callback?)**
803
- - All CactusLM completion params plus:
804
- - `images?: string[]` - Array of image paths
805
- - `mode?: string` - Cloud fallback mode
806
-
807
- ### Types
808
-
809
- ```typescript
810
- interface CactusOAICompatibleMessage {
811
- role: 'system' | 'user' | 'assistant';
812
- content: string;
813
- }
814
-
815
- interface NativeCompletionResult {
816
- text: string;
817
- tokens_predicted: number;
818
- tokens_evaluated: number;
819
- timings: {
820
- predicted_per_second: number;
821
- prompt_per_second: number;
822
- };
823
- }
824
-
825
- interface NativeEmbeddingResult {
826
- embedding: number[];
827
- }
828
- ```
104
+ ```