cactus-react-native 0.1.2 → 0.1.4

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 (77) hide show
  1. package/README.md +874 -146
  2. package/android/src/main/CMakeLists.txt +1 -1
  3. package/android/src/main/java/com/cactus/Cactus.java +0 -134
  4. package/android/src/main/java/com/cactus/LlamaContext.java +0 -22
  5. package/android/src/main/jni.cpp +1 -53
  6. package/android/src/main/jniLibs/arm64-v8a/libcactus.so +0 -0
  7. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8.so +0 -0
  8. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2.so +0 -0
  9. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_dotprod.so +0 -0
  10. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_dotprod_i8mm.so +0 -0
  11. package/android/src/main/jniLibs/arm64-v8a/libcactus_v8_2_i8mm.so +0 -0
  12. package/android/src/newarch/java/com/cactus/CactusModule.java +0 -20
  13. package/android/src/oldarch/java/com/cactus/CactusModule.java +0 -20
  14. package/ios/CMakeLists.txt +6 -6
  15. package/ios/Cactus.mm +0 -80
  16. package/ios/CactusContext.h +0 -6
  17. package/ios/CactusContext.mm +0 -27
  18. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus.h +0 -6
  19. package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
  20. package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/Headers/cactus.h +0 -6
  21. package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
  22. package/ios/cactus.xcframework/tvos-arm64/cactus.framework/Headers/cactus.h +0 -6
  23. package/ios/cactus.xcframework/tvos-arm64/cactus.framework/cactus +0 -0
  24. package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/Headers/cactus.h +0 -6
  25. package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
  26. package/lib/commonjs/NativeCactus.js +0 -1
  27. package/lib/commonjs/NativeCactus.js.map +1 -1
  28. package/lib/commonjs/index.js +55 -37
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/commonjs/lm.js +72 -0
  31. package/lib/commonjs/lm.js.map +1 -0
  32. package/lib/commonjs/telemetry.js +97 -0
  33. package/lib/commonjs/telemetry.js.map +1 -0
  34. package/lib/commonjs/tools.js +21 -60
  35. package/lib/commonjs/tools.js.map +1 -1
  36. package/lib/commonjs/tts.js +32 -0
  37. package/lib/commonjs/tts.js.map +1 -0
  38. package/lib/commonjs/vlm.js +83 -0
  39. package/lib/commonjs/vlm.js.map +1 -0
  40. package/lib/module/NativeCactus.js +0 -2
  41. package/lib/module/NativeCactus.js.map +1 -1
  42. package/lib/module/index.js +38 -38
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/lm.js +67 -0
  45. package/lib/module/lm.js.map +1 -0
  46. package/lib/module/telemetry.js +92 -0
  47. package/lib/module/telemetry.js.map +1 -0
  48. package/lib/module/tools.js +21 -58
  49. package/lib/module/tools.js.map +1 -1
  50. package/lib/module/tts.js +27 -0
  51. package/lib/module/tts.js.map +1 -0
  52. package/lib/module/vlm.js +78 -0
  53. package/lib/module/vlm.js.map +1 -0
  54. package/lib/typescript/NativeCactus.d.ts +0 -10
  55. package/lib/typescript/NativeCactus.d.ts.map +1 -1
  56. package/lib/typescript/index.d.ts +6 -18
  57. package/lib/typescript/index.d.ts.map +1 -1
  58. package/lib/typescript/lm.d.ts +17 -0
  59. package/lib/typescript/lm.d.ts.map +1 -0
  60. package/lib/typescript/telemetry.d.ts +21 -0
  61. package/lib/typescript/telemetry.d.ts.map +1 -0
  62. package/lib/typescript/tools.d.ts +0 -3
  63. package/lib/typescript/tools.d.ts.map +1 -1
  64. package/lib/typescript/tts.d.ts +10 -0
  65. package/lib/typescript/tts.d.ts.map +1 -0
  66. package/lib/typescript/vlm.d.ts +22 -0
  67. package/lib/typescript/vlm.d.ts.map +1 -0
  68. package/package.json +2 -1
  69. package/src/NativeCactus.ts +0 -22
  70. package/src/index.ts +75 -78
  71. package/src/lm.ts +89 -0
  72. package/src/telemetry.ts +123 -0
  73. package/src/tools.ts +17 -58
  74. package/src/tts.ts +45 -0
  75. package/src/vlm.ts +112 -0
  76. package/android/src/main/jniLibs/x86_64/libcactus.so +0 -0
  77. package/android/src/main/jniLibs/x86_64/libcactus_x86_64.so +0 -0
@@ -0,0 +1,123 @@
1
+ import { Platform } from 'react-native'
2
+ import type { ContextParams } from './index';
3
+ // Import package.json to get version
4
+ const packageJson = require('../package.json');
5
+
6
+ interface TelemetryRecord {
7
+ os: 'iOS' | 'Android';
8
+ os_version: string;
9
+ framework: string;
10
+ framework_version: string;
11
+ telemetry_payload?: Record<string, any>;
12
+ error_payload?: Record<string, any>;
13
+ timestamp: string;
14
+ model_filename: string;
15
+ n_ctx?: number;
16
+ n_gpu_layers?: number;
17
+ }
18
+
19
+ interface TelemetryConfig {
20
+ supabaseUrl: string;
21
+ supabaseKey: string;
22
+ table?: string;
23
+ }
24
+
25
+ export class Telemetry {
26
+ private static instance: Telemetry | null = null;
27
+ private config: Required<TelemetryConfig>;
28
+
29
+ private constructor(config: TelemetryConfig) {
30
+ this.config = {
31
+ table: 'telemetry',
32
+ ...config
33
+ };
34
+ }
35
+
36
+ private static getFilename(path: string): string {
37
+ try {
38
+ return path.split('/').pop() || path.split('\\').pop() || 'unknown';
39
+ } catch {
40
+ return 'unknown';
41
+ }
42
+ }
43
+
44
+ static autoInit(): void {
45
+ if (!Telemetry.instance) {
46
+ Telemetry.instance = new Telemetry({
47
+ supabaseUrl: 'https://vlqqczxwyaodtcdmdmlw.supabase.co',
48
+ supabaseKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZscXFjenh3eWFvZHRjZG1kbWx3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTE1MTg2MzIsImV4cCI6MjA2NzA5NDYzMn0.nBzqGuK9j6RZ6mOPWU2boAC_5H9XDs-fPpo5P3WZYbI', // Anon!
49
+ });
50
+ }
51
+ }
52
+
53
+ static init(config: TelemetryConfig): void {
54
+ if (!Telemetry.instance) {
55
+ Telemetry.instance = new Telemetry(config);
56
+ }
57
+ }
58
+
59
+ static track(payload: Record<string, any>, options: ContextParams): void {
60
+ if (!Telemetry.instance) {
61
+ Telemetry.autoInit();
62
+ }
63
+ Telemetry.instance!.trackInternal(payload, options);
64
+ }
65
+
66
+ static error(error: Error, options: ContextParams): void {
67
+ if (!Telemetry.instance) {
68
+ Telemetry.autoInit();
69
+ }
70
+ Telemetry.instance!.errorInternal(error, options);
71
+ }
72
+
73
+ private trackInternal(payload: Record<string, any>, options: ContextParams): void {
74
+ const record: TelemetryRecord = {
75
+ os: Platform.OS === 'ios' ? 'iOS' : 'Android',
76
+ os_version: Platform.Version.toString(),
77
+ framework: 'react-native',
78
+ framework_version: packageJson.version,
79
+ telemetry_payload: payload,
80
+ timestamp: new Date().toISOString(),
81
+ model_filename: Telemetry.getFilename(options.model),
82
+ n_ctx: options.n_ctx,
83
+ n_gpu_layers: options.n_gpu_layers
84
+ };
85
+
86
+ this.sendRecord(record).catch(() => {});
87
+ }
88
+
89
+ private errorInternal(error: Error, options: ContextParams): void {
90
+ const errorPayload = {
91
+ message: error.message,
92
+ stack: error.stack,
93
+ name: error.name,
94
+ };
95
+
96
+ const record: TelemetryRecord = {
97
+ os: Platform.OS === 'ios' ? 'iOS' : 'Android',
98
+ os_version: Platform.Version.toString(),
99
+ framework: 'react-native',
100
+ framework_version: packageJson.version,
101
+ error_payload: errorPayload,
102
+ timestamp: new Date().toISOString(),
103
+ model_filename: Telemetry.getFilename(options.model),
104
+ n_ctx: options.n_ctx,
105
+ n_gpu_layers: options.n_gpu_layers
106
+ };
107
+
108
+ this.sendRecord(record).catch(() => {});
109
+ }
110
+
111
+ private async sendRecord(record: TelemetryRecord): Promise<void> {
112
+ await (globalThis as any).fetch(`${this.config.supabaseUrl}/rest/v1/${this.config.table}`, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'apikey': this.config.supabaseKey,
116
+ 'Authorization': `Bearer ${this.config.supabaseKey}`,
117
+ 'Content-Type': 'application/json',
118
+ 'Prefer': 'return=minimal'
119
+ },
120
+ body: JSON.stringify([record])
121
+ });
122
+ }
123
+ }
package/src/tools.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { CactusOAICompatibleMessage } from "./chat";
2
1
  import type { NativeCompletionResult } from "./NativeCactus";
3
2
 
4
3
  interface Parameter {
@@ -55,73 +54,33 @@ export class Tools {
55
54
  }
56
55
  }
57
56
 
58
- export function injectToolsIntoMessages(messages: CactusOAICompatibleMessage[], tools: Tools): CactusOAICompatibleMessage[] {
59
- const newMessages = [...messages];
60
- const toolsSchemas = tools.getSchemas();
61
- const promptToolInjection = `You have access to the following functions. Use them if required -
62
- ${JSON.stringify(toolsSchemas, null, 2)}
63
- Only use an available tool if needed. If a tool is chosen, respond ONLY with a JSON object matching the following schema:
64
- \`\`\`json
65
- {
66
- "tool_name": "<name of the tool>",
67
- "tool_input": {
68
- "<parameter_name>": "<parameter_value>",
69
- ...
70
- }
71
- }
72
- \`\`\`
73
- Remember, if you are calling a tool, you must respond with the JSON object and the JSON object ONLY!
74
- If no tool is needed, respond normally.
75
- `;
76
-
77
- const systemMessage = newMessages.find(m => m.role === 'system');
78
- if (!systemMessage) {
79
- newMessages.unshift({
80
- role: 'system',
81
- content: promptToolInjection
82
- });
83
- } else {
84
- systemMessage.content = `${systemMessage.content}\n\n${promptToolInjection}`;
85
- }
86
-
87
- return newMessages;
88
- }
89
-
90
57
  export async function parseAndExecuteTool(result: NativeCompletionResult, tools: Tools): Promise<{toolCalled: boolean, toolName?: string, toolInput?: any, toolOutput?: any}> {
91
- const match = result.content.match(/```json\s*([\s\S]*?)\s*```/);
92
-
93
- if (!match || !match[1]) return {toolCalled: false};
58
+ if (!result.tool_calls || result.tool_calls.length === 0) {
59
+ // console.log('No tool calls found');
60
+ return {toolCalled: false};
61
+ }
94
62
 
95
63
  try {
96
- const jsonContent = JSON.parse(match[1]);
97
- const { tool_name, tool_input } = jsonContent;
98
- // console.log('Calling tool:', tool_name, tool_input);
99
- const toolOutput = await tools.execute(tool_name, tool_input) || true;
64
+ const toolCall = result.tool_calls[0];
65
+ if (!toolCall) {
66
+ // console.log('No tool call found');
67
+ return {toolCalled: false};
68
+ }
69
+ const toolName = toolCall.function.name;
70
+ const toolInput = JSON.parse(toolCall.function.arguments);
71
+
72
+ // console.log('Calling tool:', toolName, toolInput);
73
+ const toolOutput = await tools.execute(toolName, toolInput);
100
74
  // console.log('Tool called result:', toolOutput);
101
75
 
102
76
  return {
103
77
  toolCalled: true,
104
- toolName: tool_name,
105
- toolInput: tool_input,
78
+ toolName,
79
+ toolInput,
106
80
  toolOutput
107
81
  };
108
82
  } catch (error) {
109
- // console.error('Error parsing JSON:', match, error);
83
+ // console.error('Error parsing tool call:', error);
110
84
  return {toolCalled: false};
111
85
  }
112
- }
113
-
114
- export function updateMessagesWithToolCall(messages: CactusOAICompatibleMessage[], toolName: string, toolInput: any, toolOutput: any): CactusOAICompatibleMessage[] {
115
- const newMessages = [...messages];
116
-
117
- newMessages.push({
118
- role: 'function-call',
119
- content: JSON.stringify({name: toolName, arguments: toolInput}, null, 2)
120
- })
121
- newMessages.push({
122
- role: 'function-response',
123
- content: JSON.stringify(toolOutput, null, 2)
124
- })
125
-
126
- return newMessages;
127
86
  }
package/src/tts.ts ADDED
@@ -0,0 +1,45 @@
1
+ import {
2
+ LlamaContext,
3
+ initVocoder,
4
+ getFormattedAudioCompletion,
5
+ decodeAudioTokens,
6
+ releaseVocoder,
7
+ } from './index'
8
+ import type { NativeAudioDecodeResult } from './index'
9
+
10
+ export class CactusTTS {
11
+ private context: LlamaContext
12
+
13
+ private constructor(context: LlamaContext) {
14
+ this.context = context
15
+ }
16
+
17
+ static async init(
18
+ context: LlamaContext,
19
+ vocoderModelPath: string,
20
+ ): Promise<CactusTTS> {
21
+ await initVocoder(context.id, vocoderModelPath)
22
+ return new CactusTTS(context)
23
+ }
24
+
25
+ async generate(
26
+ textToSpeak: string,
27
+ speakerJsonStr: string,
28
+ ): Promise<NativeAudioDecodeResult> {
29
+ const { formatted_prompt } = await getFormattedAudioCompletion(
30
+ this.context.id,
31
+ speakerJsonStr,
32
+ textToSpeak,
33
+ )
34
+ // This part is simplified. In a real scenario, the tokens from
35
+ // the main model would be generated and passed to decodeAudioTokens.
36
+ // For now, we are assuming a direct path which may not be fully functional
37
+ // without the main model's token output for TTS.
38
+ const tokens = (await this.context.tokenize(formatted_prompt)).tokens
39
+ return decodeAudioTokens(this.context.id, tokens)
40
+ }
41
+
42
+ async release(): Promise<void> {
43
+ return releaseVocoder(this.context.id)
44
+ }
45
+ }
package/src/vlm.ts ADDED
@@ -0,0 +1,112 @@
1
+ import {
2
+ initLlama,
3
+ initMultimodal,
4
+ multimodalCompletion,
5
+ LlamaContext,
6
+ } from './index'
7
+ import type {
8
+ ContextParams,
9
+ CompletionParams,
10
+ CactusOAICompatibleMessage,
11
+ NativeCompletionResult,
12
+ } from './index'
13
+ import { Telemetry } from './telemetry'
14
+
15
+ interface CactusVLMReturn {
16
+ vlm: CactusVLM | null
17
+ error: Error | null
18
+ }
19
+
20
+ export type VLMContextParams = ContextParams & {
21
+ mmproj: string
22
+ }
23
+
24
+ export type VLMCompletionParams = Omit<CompletionParams, 'prompt'> & {
25
+ images?: string[]
26
+ }
27
+
28
+ export class CactusVLM {
29
+ private context: LlamaContext
30
+ private initParams: VLMContextParams
31
+
32
+ private constructor(context: LlamaContext, initParams: VLMContextParams) {
33
+ this.context = context
34
+ this.initParams = initParams
35
+ }
36
+
37
+ static async init(
38
+ params: VLMContextParams,
39
+ onProgress?: (progress: number) => void,
40
+ ): Promise<CactusVLMReturn> {
41
+ const configs = [
42
+ params,
43
+ { ...params, n_gpu_layers: 0 }
44
+ ];
45
+
46
+ for (const config of configs) {
47
+ try {
48
+ const context = await initLlama(config, onProgress)
49
+ // Explicitly disable GPU for the multimodal projector for stability.
50
+ await initMultimodal(context.id, params.mmproj, false)
51
+ return {vlm: new CactusVLM(context, params), error: null}
52
+ } catch (e) {
53
+ Telemetry.error(e as Error, config);
54
+ if (configs.indexOf(config) === configs.length - 1) {
55
+ return {vlm: null, error: e as Error}
56
+ }
57
+ }
58
+ }
59
+
60
+ return {vlm: null, error: new Error('Failed to initialize CactusVLM')}
61
+ }
62
+
63
+ async completion(
64
+ messages: CactusOAICompatibleMessage[],
65
+ params: VLMCompletionParams = {},
66
+ callback?: (data: any) => void,
67
+ ): Promise<NativeCompletionResult> {
68
+ const startTime = Date.now();
69
+ let firstTokenTime: number | null = null;
70
+
71
+ const wrappedCallback = callback ? (data: any) => {
72
+ if (firstTokenTime === null) firstTokenTime = Date.now();
73
+ callback(data);
74
+ } : undefined;
75
+
76
+ let result: NativeCompletionResult;
77
+ if (params.images && params.images.length > 0) {
78
+ const formattedPrompt = await this.context.getFormattedChat(messages)
79
+ const prompt =
80
+ typeof formattedPrompt === 'string'
81
+ ? formattedPrompt
82
+ : formattedPrompt.prompt
83
+ result = await multimodalCompletion(
84
+ this.context.id,
85
+ prompt,
86
+ params.images,
87
+ { ...params, prompt, emit_partial_completion: !!callback },
88
+ )
89
+ } else {
90
+ result = await this.context.completion({ messages, ...params }, wrappedCallback)
91
+ }
92
+
93
+ Telemetry.track({
94
+ event: 'completion',
95
+ tok_per_sec: (result as any).timings?.predicted_per_second,
96
+ toks_generated: (result as any).timings?.predicted_n,
97
+ ttft: firstTokenTime ? firstTokenTime - startTime : null,
98
+ num_images: params.images?.length,
99
+ }, this.initParams);
100
+
101
+ return result;
102
+ }
103
+
104
+ async rewind(): Promise<void> {
105
+ // @ts-ignore
106
+ return this.context?.rewind()
107
+ }
108
+
109
+ async release(): Promise<void> {
110
+ return this.context.release()
111
+ }
112
+ }