cactus-react-native 0.1.3 → 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.
- package/README.md +5 -3
- package/ios/cactus.xcframework/ios-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
- package/ios/cactus.xcframework/tvos-arm64_x86_64-simulator/cactus.framework/cactus +0 -0
- package/lib/commonjs/NativeCactus.js +10 -0
- package/lib/commonjs/chat.js +37 -0
- package/lib/commonjs/grammar.js +560 -0
- package/lib/commonjs/index.js +459 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lm.js +72 -0
- package/lib/commonjs/lm.js.map +1 -1
- package/lib/commonjs/telemetry.js +97 -0
- package/lib/commonjs/telemetry.js.map +1 -0
- package/lib/commonjs/tools.js +79 -0
- package/lib/commonjs/tools.js.map +1 -0
- package/lib/commonjs/tts.js +32 -0
- package/lib/commonjs/tts.js.map +1 -1
- package/lib/commonjs/vlm.js +83 -0
- package/lib/commonjs/vlm.js.map +1 -0
- package/lib/module/NativeCactus.js +8 -0
- package/lib/module/chat.js +33 -0
- package/lib/module/grammar.js +553 -0
- package/lib/module/index.js +392 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/lm.js +67 -0
- package/lib/module/lm.js.map +1 -0
- package/lib/module/telemetry.js +92 -0
- package/lib/module/telemetry.js.map +1 -0
- package/lib/module/tools.js +73 -0
- package/lib/module/tools.js.map +1 -0
- package/lib/module/tts.js +27 -0
- package/lib/module/tts.js.map +1 -1
- package/lib/module/vlm.js +78 -0
- package/lib/module/vlm.js.map +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/lm.d.ts +9 -33
- package/lib/typescript/lm.d.ts.map +1 -1
- package/lib/typescript/telemetry.d.ts +21 -0
- package/lib/typescript/telemetry.d.ts.map +1 -0
- package/lib/typescript/tools.d.ts +0 -3
- package/lib/typescript/tools.d.ts.map +1 -1
- package/lib/typescript/tts.d.ts.map +1 -1
- package/lib/typescript/vlm.d.ts +12 -34
- package/lib/typescript/vlm.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +64 -41
- package/src/lm.ts +45 -5
- package/src/telemetry.ts +123 -0
- package/src/tools.ts +17 -58
- package/src/vlm.ts +50 -8
- package/android/src/main/jniLibs/x86_64/libcactus.so +0 -0
- package/android/src/main/jniLibs/x86_64/libcactus_x86_64.so +0 -0
package/src/lm.ts
CHANGED
|
@@ -7,20 +7,43 @@ import type {
|
|
|
7
7
|
EmbeddingParams,
|
|
8
8
|
NativeEmbeddingResult,
|
|
9
9
|
} from './index'
|
|
10
|
+
import { Telemetry } from './telemetry'
|
|
11
|
+
|
|
12
|
+
interface CactusLMReturn {
|
|
13
|
+
lm: CactusLM | null
|
|
14
|
+
error: Error | null
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
export class CactusLM {
|
|
12
18
|
private context: LlamaContext
|
|
19
|
+
private initParams: ContextParams
|
|
13
20
|
|
|
14
|
-
private constructor(context: LlamaContext) {
|
|
21
|
+
private constructor(context: LlamaContext, initParams: ContextParams) {
|
|
15
22
|
this.context = context
|
|
23
|
+
this.initParams = initParams
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
static async init(
|
|
19
27
|
params: ContextParams,
|
|
20
28
|
onProgress?: (progress: number) => void,
|
|
21
|
-
): Promise<
|
|
22
|
-
const
|
|
23
|
-
|
|
29
|
+
): Promise<CactusLMReturn> {
|
|
30
|
+
const configs = [
|
|
31
|
+
params,
|
|
32
|
+
{ ...params, n_gpu_layers: 0 }
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const config of configs) {
|
|
36
|
+
try {
|
|
37
|
+
const context = await initLlama(config, onProgress);
|
|
38
|
+
return { lm: new CactusLM(context, config), error: null };
|
|
39
|
+
} catch (e) {
|
|
40
|
+
Telemetry.error(e as Error, config);
|
|
41
|
+
if (configs.indexOf(config) === configs.length - 1) {
|
|
42
|
+
return { lm: null, error: e as Error };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { lm: null, error: new Error('Failed to initialize CactusLM') };
|
|
24
47
|
}
|
|
25
48
|
|
|
26
49
|
async completion(
|
|
@@ -28,7 +51,24 @@ export class CactusLM {
|
|
|
28
51
|
params: CompletionParams = {},
|
|
29
52
|
callback?: (data: any) => void,
|
|
30
53
|
): Promise<NativeCompletionResult> {
|
|
31
|
-
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
let firstTokenTime: number | null = null;
|
|
56
|
+
|
|
57
|
+
const wrappedCallback = callback ? (data: any) => {
|
|
58
|
+
if (firstTokenTime === null) firstTokenTime = Date.now();
|
|
59
|
+
callback(data);
|
|
60
|
+
} : undefined;
|
|
61
|
+
|
|
62
|
+
const result = await this.context.completion({ messages, ...params }, wrappedCallback);
|
|
63
|
+
|
|
64
|
+
Telemetry.track({
|
|
65
|
+
event: 'completion',
|
|
66
|
+
tok_per_sec: (result as any).timings?.predicted_per_second,
|
|
67
|
+
toks_generated: (result as any).timings?.predicted_n,
|
|
68
|
+
ttft: firstTokenTime ? firstTokenTime - startTime : null,
|
|
69
|
+
}, this.initParams);
|
|
70
|
+
|
|
71
|
+
return result;
|
|
32
72
|
}
|
|
33
73
|
|
|
34
74
|
async embedding(
|
package/src/telemetry.ts
ADDED
|
@@ -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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
105
|
-
toolInput
|
|
78
|
+
toolName,
|
|
79
|
+
toolInput,
|
|
106
80
|
toolOutput
|
|
107
81
|
};
|
|
108
82
|
} catch (error) {
|
|
109
|
-
// console.error('Error parsing
|
|
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/vlm.ts
CHANGED
|
@@ -10,6 +10,12 @@ import type {
|
|
|
10
10
|
CactusOAICompatibleMessage,
|
|
11
11
|
NativeCompletionResult,
|
|
12
12
|
} from './index'
|
|
13
|
+
import { Telemetry } from './telemetry'
|
|
14
|
+
|
|
15
|
+
interface CactusVLMReturn {
|
|
16
|
+
vlm: CactusVLM | null
|
|
17
|
+
error: Error | null
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
export type VLMContextParams = ContextParams & {
|
|
15
21
|
mmproj: string
|
|
@@ -21,21 +27,37 @@ export type VLMCompletionParams = Omit<CompletionParams, 'prompt'> & {
|
|
|
21
27
|
|
|
22
28
|
export class CactusVLM {
|
|
23
29
|
private context: LlamaContext
|
|
30
|
+
private initParams: VLMContextParams
|
|
24
31
|
|
|
25
|
-
private constructor(context: LlamaContext) {
|
|
32
|
+
private constructor(context: LlamaContext, initParams: VLMContextParams) {
|
|
26
33
|
this.context = context
|
|
34
|
+
this.initParams = initParams
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
static async init(
|
|
30
38
|
params: VLMContextParams,
|
|
31
39
|
onProgress?: (progress: number) => void,
|
|
32
|
-
): Promise<
|
|
33
|
-
const
|
|
40
|
+
): Promise<CactusVLMReturn> {
|
|
41
|
+
const configs = [
|
|
42
|
+
params,
|
|
43
|
+
{ ...params, n_gpu_layers: 0 }
|
|
44
|
+
];
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
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
|
+
}
|
|
37
59
|
|
|
38
|
-
return new CactusVLM
|
|
60
|
+
return {vlm: null, error: new Error('Failed to initialize CactusVLM')}
|
|
39
61
|
}
|
|
40
62
|
|
|
41
63
|
async completion(
|
|
@@ -43,20 +65,40 @@ export class CactusVLM {
|
|
|
43
65
|
params: VLMCompletionParams = {},
|
|
44
66
|
callback?: (data: any) => void,
|
|
45
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;
|
|
46
77
|
if (params.images && params.images.length > 0) {
|
|
47
78
|
const formattedPrompt = await this.context.getFormattedChat(messages)
|
|
48
79
|
const prompt =
|
|
49
80
|
typeof formattedPrompt === 'string'
|
|
50
81
|
? formattedPrompt
|
|
51
82
|
: formattedPrompt.prompt
|
|
52
|
-
|
|
83
|
+
result = await multimodalCompletion(
|
|
53
84
|
this.context.id,
|
|
54
85
|
prompt,
|
|
55
86
|
params.images,
|
|
56
87
|
{ ...params, prompt, emit_partial_completion: !!callback },
|
|
57
88
|
)
|
|
89
|
+
} else {
|
|
90
|
+
result = await this.context.completion({ messages, ...params }, wrappedCallback)
|
|
58
91
|
}
|
|
59
|
-
|
|
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;
|
|
60
102
|
}
|
|
61
103
|
|
|
62
104
|
async rewind(): Promise<void> {
|
|
Binary file
|
|
Binary file
|