react-native-nitro-mlx 0.1.1 → 0.2.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/ios/Sources/HybridLLM.swift +115 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/llm.js +23 -3
- package/lib/module/llm.js.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/llm.d.ts +21 -3
- package/lib/typescript/src/llm.d.ts.map +1 -1
- package/lib/typescript/src/specs/LLM.nitro.d.ts +29 -2
- package/lib/typescript/src/specs/LLM.nitro.d.ts.map +1 -1
- package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Bridge.hpp +87 -0
- package/nitrogen/generated/ios/MLXReactNative-Swift-Cxx-Umbrella.hpp +7 -0
- package/nitrogen/generated/ios/c++/HybridLLMSpecSwift.hpp +30 -2
- package/nitrogen/generated/ios/swift/HybridLLMSpec.swift +4 -1
- package/nitrogen/generated/ios/swift/HybridLLMSpec_cxx.swift +42 -7
- package/nitrogen/generated/ios/swift/LLMLoadOptions.swift +138 -0
- package/nitrogen/generated/ios/swift/LLMMessage.swift +47 -0
- package/nitrogen/generated/shared/c++/HybridLLMSpec.cpp +3 -0
- package/nitrogen/generated/shared/c++/HybridLLMSpec.hpp +12 -1
- package/nitrogen/generated/shared/c++/LLMLoadOptions.hpp +87 -0
- package/nitrogen/generated/shared/c++/LLMMessage.hpp +79 -0
- package/package.json +1 -9
- package/src/index.ts +2 -2
- package/src/llm.ts +32 -4
- package/src/specs/LLM.nitro.ts +34 -2
|
@@ -7,6 +7,7 @@ internal import MLXLMCommon
|
|
|
7
7
|
class HybridLLM: HybridLLMSpec {
|
|
8
8
|
private var session: ChatSession?
|
|
9
9
|
private var currentTask: Task<String, Error>?
|
|
10
|
+
private var container: Any?
|
|
10
11
|
private var lastStats: GenerationStats = GenerationStats(
|
|
11
12
|
tokenCount: 0,
|
|
12
13
|
tokensPerSecond: 0,
|
|
@@ -14,12 +15,15 @@ class HybridLLM: HybridLLMSpec {
|
|
|
14
15
|
totalTime: 0
|
|
15
16
|
)
|
|
16
17
|
private var modelFactory: ModelFactory = LLMModelFactory.shared
|
|
18
|
+
private var manageHistory: Bool = false
|
|
19
|
+
private var messageHistory: [LLMMessage] = []
|
|
17
20
|
|
|
18
21
|
var isLoaded: Bool { session != nil }
|
|
19
22
|
var isGenerating: Bool { currentTask != nil }
|
|
20
23
|
var modelId: String = ""
|
|
21
24
|
var debug: Bool = false
|
|
22
25
|
var systemPrompt: String = "You are a helpful assistant."
|
|
26
|
+
var additionalContext: LLMMessage = LLMMessage()
|
|
23
27
|
|
|
24
28
|
private func log(_ message: String) {
|
|
25
29
|
if debug {
|
|
@@ -27,21 +31,80 @@ class HybridLLM: HybridLLMSpec {
|
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
func
|
|
34
|
+
private func getMemoryUsage() -> String {
|
|
35
|
+
var taskInfo = mach_task_basic_info()
|
|
36
|
+
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
|
|
37
|
+
let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
|
|
38
|
+
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
|
|
39
|
+
task_info(mach_task_self_,
|
|
40
|
+
task_flavor_t(MACH_TASK_BASIC_INFO),
|
|
41
|
+
$0,
|
|
42
|
+
&count)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if result == KERN_SUCCESS {
|
|
47
|
+
let usedMB = Float(taskInfo.resident_size) / 1024.0 / 1024.0
|
|
48
|
+
return String(format: "%.1f MB", usedMB)
|
|
49
|
+
} else {
|
|
50
|
+
return "unknown"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private func getGPUMemoryUsage() -> String {
|
|
55
|
+
let snapshot = GPU.snapshot()
|
|
56
|
+
let allocatedMB = Float(snapshot.activeMemory) / 1024.0 / 1024.0
|
|
57
|
+
let cacheMB = Float(snapshot.cacheMemory) / 1024.0 / 1024.0
|
|
58
|
+
let peakMB = Float(snapshot.peakMemory) / 1024.0 / 1024.0
|
|
59
|
+
return String(format: "Allocated: %.1f MB, Cache: %.1f MB, Peak: %.1f MB",
|
|
60
|
+
allocatedMB, cacheMB, peakMB)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func load(modelId: String, options: LLMLoadOptions?) throws -> Promise<Void> {
|
|
31
64
|
return Promise.async { [self] in
|
|
65
|
+
MLX.GPU.set(cacheLimit: 2000000)
|
|
66
|
+
|
|
67
|
+
self.currentTask?.cancel()
|
|
68
|
+
self.currentTask = nil
|
|
69
|
+
self.session = nil
|
|
70
|
+
self.container = nil
|
|
71
|
+
MLX.GPU.clearCache()
|
|
72
|
+
|
|
73
|
+
let memoryAfterCleanup = self.getMemoryUsage()
|
|
74
|
+
let gpuAfterCleanup = self.getGPUMemoryUsage()
|
|
75
|
+
log("After cleanup - Host: \(memoryAfterCleanup), GPU: \(gpuAfterCleanup)")
|
|
76
|
+
|
|
32
77
|
let modelDir = await ModelDownloader.shared.getModelDirectory(modelId: modelId)
|
|
33
78
|
log("Loading from directory: \(modelDir.path)")
|
|
34
79
|
|
|
35
80
|
let config = ModelConfiguration(directory: modelDir)
|
|
36
|
-
let
|
|
81
|
+
let loadedContainer = try await modelFactory.loadContainer(
|
|
37
82
|
configuration: config
|
|
38
83
|
) { progress in
|
|
39
|
-
onProgress(progress.fractionCompleted)
|
|
84
|
+
options?.onProgress?(progress.fractionCompleted)
|
|
40
85
|
}
|
|
41
86
|
|
|
42
|
-
|
|
87
|
+
let memoryAfterContainer = self.getMemoryUsage()
|
|
88
|
+
let gpuAfterContainer = self.getGPUMemoryUsage()
|
|
89
|
+
log("Model loaded - Host: \(memoryAfterContainer), GPU: \(gpuAfterContainer)")
|
|
90
|
+
|
|
91
|
+
// Convert [LLMMessage]? to [String: Any]?
|
|
92
|
+
let additionalContextDict: [String: Any]? = if let messages = options?.additionalContext {
|
|
93
|
+
["messages": messages.map { ["role": $0.role, "content": $0.content] }]
|
|
94
|
+
} else {
|
|
95
|
+
nil
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
self.container = loadedContainer
|
|
99
|
+
self.session = ChatSession(loadedContainer, instructions: self.systemPrompt, additionalContext: additionalContextDict)
|
|
43
100
|
self.modelId = modelId
|
|
44
|
-
|
|
101
|
+
|
|
102
|
+
self.manageHistory = options?.manageHistory ?? false
|
|
103
|
+
self.messageHistory = options?.additionalContext ?? []
|
|
104
|
+
|
|
105
|
+
if self.manageHistory {
|
|
106
|
+
log("History management enabled with \(self.messageHistory.count) initial messages")
|
|
107
|
+
}
|
|
45
108
|
}
|
|
46
109
|
}
|
|
47
110
|
|
|
@@ -51,6 +114,10 @@ class HybridLLM: HybridLLMSpec {
|
|
|
51
114
|
}
|
|
52
115
|
|
|
53
116
|
return Promise.async { [self] in
|
|
117
|
+
if self.manageHistory {
|
|
118
|
+
self.messageHistory.append(LLMMessage(role: "user", content: prompt))
|
|
119
|
+
}
|
|
120
|
+
|
|
54
121
|
let task = Task<String, Error> {
|
|
55
122
|
log("Generating response for: \(prompt.prefix(50))...")
|
|
56
123
|
let result = try await session.respond(to: prompt)
|
|
@@ -63,6 +130,11 @@ class HybridLLM: HybridLLMSpec {
|
|
|
63
130
|
do {
|
|
64
131
|
let result = try await task.value
|
|
65
132
|
self.currentTask = nil
|
|
133
|
+
|
|
134
|
+
if self.manageHistory {
|
|
135
|
+
self.messageHistory.append(LLMMessage(role: "assistant", content: result))
|
|
136
|
+
}
|
|
137
|
+
|
|
66
138
|
return result
|
|
67
139
|
} catch {
|
|
68
140
|
self.currentTask = nil
|
|
@@ -77,6 +149,10 @@ class HybridLLM: HybridLLMSpec {
|
|
|
77
149
|
}
|
|
78
150
|
|
|
79
151
|
return Promise.async { [self] in
|
|
152
|
+
if self.manageHistory {
|
|
153
|
+
self.messageHistory.append(LLMMessage(role: "user", content: prompt))
|
|
154
|
+
}
|
|
155
|
+
|
|
80
156
|
let task = Task<String, Error> {
|
|
81
157
|
var result = ""
|
|
82
158
|
var tokenCount = 0
|
|
@@ -116,6 +192,11 @@ class HybridLLM: HybridLLMSpec {
|
|
|
116
192
|
do {
|
|
117
193
|
let result = try await task.value
|
|
118
194
|
self.currentTask = nil
|
|
195
|
+
|
|
196
|
+
if self.manageHistory {
|
|
197
|
+
self.messageHistory.append(LLMMessage(role: "assistant", content: result))
|
|
198
|
+
}
|
|
199
|
+
|
|
119
200
|
return result
|
|
120
201
|
} catch {
|
|
121
202
|
self.currentTask = nil
|
|
@@ -129,7 +210,36 @@ class HybridLLM: HybridLLMSpec {
|
|
|
129
210
|
currentTask = nil
|
|
130
211
|
}
|
|
131
212
|
|
|
213
|
+
func unload() throws {
|
|
214
|
+
let memoryBefore = getMemoryUsage()
|
|
215
|
+
let gpuBefore = getGPUMemoryUsage()
|
|
216
|
+
log("Before unload - Host: \(memoryBefore), GPU: \(gpuBefore)")
|
|
217
|
+
|
|
218
|
+
currentTask?.cancel()
|
|
219
|
+
currentTask = nil
|
|
220
|
+
session = nil
|
|
221
|
+
container = nil
|
|
222
|
+
messageHistory = []
|
|
223
|
+
manageHistory = false
|
|
224
|
+
modelId = ""
|
|
225
|
+
|
|
226
|
+
MLX.GPU.clearCache()
|
|
227
|
+
|
|
228
|
+
let memoryAfter = getMemoryUsage()
|
|
229
|
+
let gpuAfter = getGPUMemoryUsage()
|
|
230
|
+
log("After unload - Host: \(memoryAfter), GPU: \(gpuAfter)")
|
|
231
|
+
}
|
|
232
|
+
|
|
132
233
|
func getLastGenerationStats() throws -> GenerationStats {
|
|
133
234
|
return lastStats
|
|
134
235
|
}
|
|
236
|
+
|
|
237
|
+
func getHistory() throws -> [LLMMessage] {
|
|
238
|
+
return messageHistory
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func clearHistory() throws {
|
|
242
|
+
messageHistory = []
|
|
243
|
+
log("Message history cleared")
|
|
244
|
+
}
|
|
135
245
|
}
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["LLM","ModelManager","MLXModel"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,GAAG,
|
|
1
|
+
{"version":3,"names":["LLM","ModelManager","MLXModel"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,GAAG,QAAsB,UAAO;AACzC,SAASC,YAAY,QAAQ,mBAAgB;AAC7C,SAASC,QAAQ,QAAQ,aAAU","ignoreList":[]}
|
package/lib/module/llm.js
CHANGED
|
@@ -35,10 +35,10 @@ export const LLM = {
|
|
|
35
35
|
/**
|
|
36
36
|
* Load a model into memory. Downloads the model from HuggingFace if not already cached.
|
|
37
37
|
* @param modelId - HuggingFace model ID (e.g., 'mlx-community/Qwen3-0.6B-4bit')
|
|
38
|
-
* @param
|
|
38
|
+
* @param options - Callback invoked with loading progress (0-1)
|
|
39
39
|
*/
|
|
40
|
-
load(modelId,
|
|
41
|
-
return getInstance().load(modelId,
|
|
40
|
+
load(modelId, options) {
|
|
41
|
+
return getInstance().load(modelId, options);
|
|
42
42
|
},
|
|
43
43
|
/**
|
|
44
44
|
* Generate a complete response for a prompt. Blocks until generation is complete.
|
|
@@ -64,6 +64,13 @@ export const LLM = {
|
|
|
64
64
|
stop() {
|
|
65
65
|
getInstance().stop();
|
|
66
66
|
},
|
|
67
|
+
/**
|
|
68
|
+
* Unload the current model and release memory.
|
|
69
|
+
* Call this when you're done with the model to free up memory.
|
|
70
|
+
*/
|
|
71
|
+
unload() {
|
|
72
|
+
getInstance().unload();
|
|
73
|
+
},
|
|
67
74
|
/**
|
|
68
75
|
* Get statistics from the last generation.
|
|
69
76
|
* @returns Statistics including token count, tokens/sec, TTFT, and total time
|
|
@@ -71,6 +78,19 @@ export const LLM = {
|
|
|
71
78
|
getLastGenerationStats() {
|
|
72
79
|
return getInstance().getLastGenerationStats();
|
|
73
80
|
},
|
|
81
|
+
/**
|
|
82
|
+
* Get the message history if management is enabled.
|
|
83
|
+
* @returns Array of messages in the history
|
|
84
|
+
*/
|
|
85
|
+
getHistory() {
|
|
86
|
+
return getInstance().getHistory();
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Clear the message history.
|
|
90
|
+
*/
|
|
91
|
+
clearHistory() {
|
|
92
|
+
getInstance().clearHistory();
|
|
93
|
+
},
|
|
74
94
|
/** Whether a model is currently loaded and ready for generation */
|
|
75
95
|
get isLoaded() {
|
|
76
96
|
return getInstance().isLoaded;
|
package/lib/module/llm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NitroModules","instance","getInstance","createHybridObject","LLM","load","modelId","
|
|
1
|
+
{"version":3,"names":["NitroModules","instance","getInstance","createHybridObject","LLM","load","modelId","options","generate","prompt","stream","onToken","stop","unload","getLastGenerationStats","getHistory","clearHistory","isLoaded","isGenerating","debug","value","systemPrompt"],"sourceRoot":"../../src","sources":["llm.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,4BAA4B;AAGzD,IAAIC,QAAwB,GAAG,IAAI;AAOnC,SAASC,WAAWA,CAAA,EAAY;EAC9B,IAAI,CAACD,QAAQ,EAAE;IACbA,QAAQ,GAAGD,YAAY,CAACG,kBAAkB,CAAU,KAAK,CAAC;EAC5D;EACA,OAAOF,QAAQ;AACjB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMG,GAAG,GAAG;EACjB;AACF;AACA;AACA;AACA;EACEC,IAAIA,CAACC,OAAe,EAAEC,OAAuB,EAAiB;IAC5D,OAAOL,WAAW,CAAC,CAAC,CAACG,IAAI,CAACC,OAAO,EAAEC,OAAO,CAAC;EAC7C,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;EACEC,QAAQA,CAACC,MAAc,EAAmB;IACxC,OAAOP,WAAW,CAAC,CAAC,CAACM,QAAQ,CAACC,MAAM,CAAC;EACvC,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;EACEC,MAAMA,CAACD,MAAc,EAAEE,OAAgC,EAAmB;IACxE,OAAOT,WAAW,CAAC,CAAC,CAACQ,MAAM,CAACD,MAAM,EAAEE,OAAO,CAAC;EAC9C,CAAC;EAED;AACF;AACA;EACEC,IAAIA,CAAA,EAAS;IACXV,WAAW,CAAC,CAAC,CAACU,IAAI,CAAC,CAAC;EACtB,CAAC;EAED;AACF;AACA;AACA;EACEC,MAAMA,CAAA,EAAS;IACbX,WAAW,CAAC,CAAC,CAACW,MAAM,CAAC,CAAC;EACxB,CAAC;EAED;AACF;AACA;AACA;EACEC,sBAAsBA,CAAA,EAAoB;IACxC,OAAOZ,WAAW,CAAC,CAAC,CAACY,sBAAsB,CAAC,CAAC;EAC/C,CAAC;EAED;AACF;AACA;AACA;EACEC,UAAUA,CAAA,EAAc;IACtB,OAAOb,WAAW,CAAC,CAAC,CAACa,UAAU,CAAC,CAAC;EACnC,CAAC;EAED;AACF;AACA;EACEC,YAAYA,CAAA,EAAS;IACnBd,WAAW,CAAC,CAAC,CAACc,YAAY,CAAC,CAAC;EAC9B,CAAC;EAED;EACA,IAAIC,QAAQA,CAAA,EAAY;IACtB,OAAOf,WAAW,CAAC,CAAC,CAACe,QAAQ;EAC/B,CAAC;EAED;EACA,IAAIC,YAAYA,CAAA,EAAY;IAC1B,OAAOhB,WAAW,CAAC,CAAC,CAACgB,YAAY;EACnC,CAAC;EAED;EACA,IAAIZ,OAAOA,CAAA,EAAW;IACpB,OAAOJ,WAAW,CAAC,CAAC,CAACI,OAAO;EAC9B,CAAC;EAED;EACA,IAAIa,KAAKA,CAAA,EAAY;IACnB,OAAOjB,WAAW,CAAC,CAAC,CAACiB,KAAK;EAC5B,CAAC;EAED,IAAIA,KAAKA,CAACC,KAAc,EAAE;IACxBlB,WAAW,CAAC,CAAC,CAACiB,KAAK,GAAGC,KAAK;EAC7B,CAAC;EAED;AACF;AACA;AACA;AACA;EACE,IAAIC,YAAYA,CAAA,EAAW;IACzB,OAAOnB,WAAW,CAAC,CAAC,CAACmB,YAAY;EACnC,CAAC;EAED,IAAIA,YAAYA,CAACD,KAAa,EAAE;IAC9BlB,WAAW,CAAC,CAAC,CAACmB,YAAY,GAAGD,KAAK;EACpC;AACF,CAAC","ignoreList":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { LLM } from './llm';
|
|
1
|
+
export { LLM, type Message } from './llm';
|
|
2
2
|
export { ModelManager } from './modelManager';
|
|
3
3
|
export { MLXModel } from './models';
|
|
4
|
-
export type { GenerationStats, LLM as LLMSpec } from './specs/LLM.nitro';
|
|
4
|
+
export type { GenerationStats, LLM as LLMSpec, LLMLoadOptions } from './specs/LLM.nitro';
|
|
5
5
|
export type { ModelManager as ModelManagerSpec } from './specs/ModelManager.nitro';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAEnC,YAAY,EAAE,eAAe,EAAE,GAAG,IAAI,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACxF,YAAY,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,4BAA4B,CAAA"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type { GenerationStats } from './specs/LLM.nitro';
|
|
1
|
+
import type { GenerationStats, LLMLoadOptions } from './specs/LLM.nitro';
|
|
2
|
+
export type Message = {
|
|
3
|
+
role: 'user' | 'assistant' | 'system';
|
|
4
|
+
content: string;
|
|
5
|
+
};
|
|
2
6
|
/**
|
|
3
7
|
* LLM text generation using MLX on Apple Silicon.
|
|
4
8
|
*
|
|
@@ -25,9 +29,9 @@ export declare const LLM: {
|
|
|
25
29
|
/**
|
|
26
30
|
* Load a model into memory. Downloads the model from HuggingFace if not already cached.
|
|
27
31
|
* @param modelId - HuggingFace model ID (e.g., 'mlx-community/Qwen3-0.6B-4bit')
|
|
28
|
-
* @param
|
|
32
|
+
* @param options - Callback invoked with loading progress (0-1)
|
|
29
33
|
*/
|
|
30
|
-
load(modelId: string,
|
|
34
|
+
load(modelId: string, options: LLMLoadOptions): Promise<void>;
|
|
31
35
|
/**
|
|
32
36
|
* Generate a complete response for a prompt. Blocks until generation is complete.
|
|
33
37
|
* For streaming responses, use `stream()` instead.
|
|
@@ -46,11 +50,25 @@ export declare const LLM: {
|
|
|
46
50
|
* Stop the current generation. Safe to call even if not generating.
|
|
47
51
|
*/
|
|
48
52
|
stop(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Unload the current model and release memory.
|
|
55
|
+
* Call this when you're done with the model to free up memory.
|
|
56
|
+
*/
|
|
57
|
+
unload(): void;
|
|
49
58
|
/**
|
|
50
59
|
* Get statistics from the last generation.
|
|
51
60
|
* @returns Statistics including token count, tokens/sec, TTFT, and total time
|
|
52
61
|
*/
|
|
53
62
|
getLastGenerationStats(): GenerationStats;
|
|
63
|
+
/**
|
|
64
|
+
* Get the message history if management is enabled.
|
|
65
|
+
* @returns Array of messages in the history
|
|
66
|
+
*/
|
|
67
|
+
getHistory(): Message[];
|
|
68
|
+
/**
|
|
69
|
+
* Clear the message history.
|
|
70
|
+
*/
|
|
71
|
+
clearHistory(): void;
|
|
54
72
|
/** Whether a model is currently loaded and ready for generation */
|
|
55
73
|
readonly isLoaded: boolean;
|
|
56
74
|
/** Whether text is currently being generated */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../../src/llm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../../src/llm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAkB,MAAM,mBAAmB,CAAA;AAIxF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AASD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,GAAG;IACd;;;;OAIG;kBACW,MAAM,WAAW,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;OAKG;qBACc,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIzC;;;;;OAKG;mBACY,MAAM,WAAW,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAIzE;;OAEG;YACK,IAAI;IAIZ;;;OAGG;cACO,IAAI;IAId;;;OAGG;8BACuB,eAAe;IAIzC;;;OAGG;kBACW,OAAO,EAAE;IAIvB;;OAEG;oBACa,IAAI;IAIpB,mEAAmE;uBACnD,OAAO;IAIvB,gDAAgD;2BAC5B,OAAO;IAI3B,oEAAoE;sBACrD,MAAM;IAIrB,sCAAsC;WACzB,OAAO;IAQpB;;;;OAIG;kBACiB,MAAM;CAO3B,CAAA"}
|
|
@@ -12,6 +12,20 @@ export interface GenerationStats {
|
|
|
12
12
|
/** Total generation time in milliseconds */
|
|
13
13
|
totalTime: number;
|
|
14
14
|
}
|
|
15
|
+
export interface LLMMessage {
|
|
16
|
+
role: string;
|
|
17
|
+
content: string;
|
|
18
|
+
}
|
|
19
|
+
/** Options for loading a model.
|
|
20
|
+
*/
|
|
21
|
+
export interface LLMLoadOptions {
|
|
22
|
+
/** Callback invoked with loading progress (0-1) */
|
|
23
|
+
onProgress?: (progress: number) => void;
|
|
24
|
+
/** Additional context to provide to the model */
|
|
25
|
+
additionalContext?: LLMMessage[];
|
|
26
|
+
/** Whether to automatically manage message history */
|
|
27
|
+
manageHistory?: boolean;
|
|
28
|
+
}
|
|
15
29
|
/**
|
|
16
30
|
* Low-level LLM interface for text generation using MLX.
|
|
17
31
|
* @internal Use the `LLM` export from `react-native-nitro-mlx` instead.
|
|
@@ -22,9 +36,9 @@ export interface LLM extends HybridObject<{
|
|
|
22
36
|
/**
|
|
23
37
|
* Load a model into memory. Downloads from HuggingFace if not already cached.
|
|
24
38
|
* @param modelId - HuggingFace model ID (e.g., 'mlx-community/Qwen3-0.6B-4bit')
|
|
25
|
-
* @param
|
|
39
|
+
* @param options - Callback invoked with loading progress (0-1)
|
|
26
40
|
*/
|
|
27
|
-
load(modelId: string,
|
|
41
|
+
load(modelId: string, options?: LLMLoadOptions): Promise<void>;
|
|
28
42
|
/**
|
|
29
43
|
* Generate a complete response for a prompt.
|
|
30
44
|
* @param prompt - The input text to generate a response for
|
|
@@ -42,11 +56,24 @@ export interface LLM extends HybridObject<{
|
|
|
42
56
|
* Stop the current generation.
|
|
43
57
|
*/
|
|
44
58
|
stop(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Unload the current model and release memory.
|
|
61
|
+
*/
|
|
62
|
+
unload(): void;
|
|
45
63
|
/**
|
|
46
64
|
* Get statistics from the last generation.
|
|
47
65
|
* @returns Statistics including token count, speed, and timing
|
|
48
66
|
*/
|
|
49
67
|
getLastGenerationStats(): GenerationStats;
|
|
68
|
+
/**
|
|
69
|
+
* Get the message history if management is enabled.
|
|
70
|
+
* @returns Array of messages in the history
|
|
71
|
+
*/
|
|
72
|
+
getHistory(): LLMMessage[];
|
|
73
|
+
/**
|
|
74
|
+
* Clear the message history.
|
|
75
|
+
*/
|
|
76
|
+
clearHistory(): void;
|
|
50
77
|
/** Whether a model is currently loaded */
|
|
51
78
|
readonly isLoaded: boolean;
|
|
52
79
|
/** Whether text is currently being generated */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLM.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/LLM.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,4CAA4C;IAC5C,eAAe,EAAE,MAAM,CAAA;IACvB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAA;IACxB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,GAAI,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;IACzD;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"LLM.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/LLM.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,4CAA4C;IAC5C,eAAe,EAAE,MAAM,CAAA;IACvB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAA;IACxB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;GACG;AACH,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,UAAU,EAAE,CAAA;IAChC,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,GAAI,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;IACzD;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE9D;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEzC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEzE;;OAEG;IACH,IAAI,IAAI,IAAI,CAAA;IAEZ;;OAEG;IACH,MAAM,IAAI,IAAI,CAAA;IAEd;;;OAGG;IACH,sBAAsB,IAAI,eAAe,CAAA;IAEzC;;;OAGG;IACH,UAAU,IAAI,UAAU,EAAE,CAAA;IAE1B;;OAEG;IACH,YAAY,IAAI,IAAI,CAAA;IAEpB,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IAExB,2BAA2B;IAC3B,KAAK,EAAE,OAAO,CAAA;IACd,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAA;CACrB"}
|
|
@@ -14,6 +14,10 @@ namespace margelo::nitro::mlxreactnative { struct GenerationStats; }
|
|
|
14
14
|
namespace margelo::nitro::mlxreactnative { class HybridLLMSpec; }
|
|
15
15
|
// Forward declaration of `HybridModelManagerSpec` to properly resolve imports.
|
|
16
16
|
namespace margelo::nitro::mlxreactnative { class HybridModelManagerSpec; }
|
|
17
|
+
// Forward declaration of `LLMLoadOptions` to properly resolve imports.
|
|
18
|
+
namespace margelo::nitro::mlxreactnative { struct LLMLoadOptions; }
|
|
19
|
+
// Forward declaration of `LLMMessage` to properly resolve imports.
|
|
20
|
+
namespace margelo::nitro::mlxreactnative { struct LLMMessage; }
|
|
17
21
|
|
|
18
22
|
// Forward declarations of Swift defined types
|
|
19
23
|
// Forward declaration of `HybridLLMSpec_cxx` to properly resolve imports.
|
|
@@ -25,12 +29,15 @@ namespace MLXReactNative { class HybridModelManagerSpec_cxx; }
|
|
|
25
29
|
#include "GenerationStats.hpp"
|
|
26
30
|
#include "HybridLLMSpec.hpp"
|
|
27
31
|
#include "HybridModelManagerSpec.hpp"
|
|
32
|
+
#include "LLMLoadOptions.hpp"
|
|
33
|
+
#include "LLMMessage.hpp"
|
|
28
34
|
#include <NitroModules/Promise.hpp>
|
|
29
35
|
#include <NitroModules/PromiseHolder.hpp>
|
|
30
36
|
#include <NitroModules/Result.hpp>
|
|
31
37
|
#include <exception>
|
|
32
38
|
#include <functional>
|
|
33
39
|
#include <memory>
|
|
40
|
+
#include <optional>
|
|
34
41
|
#include <string>
|
|
35
42
|
#include <vector>
|
|
36
43
|
|
|
@@ -118,6 +125,77 @@ namespace margelo::nitro::mlxreactnative::bridge::swift {
|
|
|
118
125
|
return Func_void_double_Wrapper(std::move(value));
|
|
119
126
|
}
|
|
120
127
|
|
|
128
|
+
// pragma MARK: std::optional<std::function<void(double /* progress */)>>
|
|
129
|
+
/**
|
|
130
|
+
* Specialized version of `std::optional<std::function<void(double / * progress * /)>>`.
|
|
131
|
+
*/
|
|
132
|
+
using std__optional_std__function_void_double____progress______ = std::optional<std::function<void(double /* progress */)>>;
|
|
133
|
+
inline std::optional<std::function<void(double /* progress */)>> create_std__optional_std__function_void_double____progress______(const std::function<void(double /* progress */)>& value) noexcept {
|
|
134
|
+
return std::optional<std::function<void(double /* progress */)>>(value);
|
|
135
|
+
}
|
|
136
|
+
inline bool has_value_std__optional_std__function_void_double____progress______(const std::optional<std::function<void(double /* progress */)>>& optional) noexcept {
|
|
137
|
+
return optional.has_value();
|
|
138
|
+
}
|
|
139
|
+
inline std::function<void(double /* progress */)> get_std__optional_std__function_void_double____progress______(const std::optional<std::function<void(double /* progress */)>>& optional) noexcept {
|
|
140
|
+
return *optional;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// pragma MARK: std::vector<LLMMessage>
|
|
144
|
+
/**
|
|
145
|
+
* Specialized version of `std::vector<LLMMessage>`.
|
|
146
|
+
*/
|
|
147
|
+
using std__vector_LLMMessage_ = std::vector<LLMMessage>;
|
|
148
|
+
inline std::vector<LLMMessage> create_std__vector_LLMMessage_(size_t size) noexcept {
|
|
149
|
+
std::vector<LLMMessage> vector;
|
|
150
|
+
vector.reserve(size);
|
|
151
|
+
return vector;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// pragma MARK: std::optional<std::vector<LLMMessage>>
|
|
155
|
+
/**
|
|
156
|
+
* Specialized version of `std::optional<std::vector<LLMMessage>>`.
|
|
157
|
+
*/
|
|
158
|
+
using std__optional_std__vector_LLMMessage__ = std::optional<std::vector<LLMMessage>>;
|
|
159
|
+
inline std::optional<std::vector<LLMMessage>> create_std__optional_std__vector_LLMMessage__(const std::vector<LLMMessage>& value) noexcept {
|
|
160
|
+
return std::optional<std::vector<LLMMessage>>(value);
|
|
161
|
+
}
|
|
162
|
+
inline bool has_value_std__optional_std__vector_LLMMessage__(const std::optional<std::vector<LLMMessage>>& optional) noexcept {
|
|
163
|
+
return optional.has_value();
|
|
164
|
+
}
|
|
165
|
+
inline std::vector<LLMMessage> get_std__optional_std__vector_LLMMessage__(const std::optional<std::vector<LLMMessage>>& optional) noexcept {
|
|
166
|
+
return *optional;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// pragma MARK: std::optional<bool>
|
|
170
|
+
/**
|
|
171
|
+
* Specialized version of `std::optional<bool>`.
|
|
172
|
+
*/
|
|
173
|
+
using std__optional_bool_ = std::optional<bool>;
|
|
174
|
+
inline std::optional<bool> create_std__optional_bool_(const bool& value) noexcept {
|
|
175
|
+
return std::optional<bool>(value);
|
|
176
|
+
}
|
|
177
|
+
inline bool has_value_std__optional_bool_(const std::optional<bool>& optional) noexcept {
|
|
178
|
+
return optional.has_value();
|
|
179
|
+
}
|
|
180
|
+
inline bool get_std__optional_bool_(const std::optional<bool>& optional) noexcept {
|
|
181
|
+
return *optional;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// pragma MARK: std::optional<LLMLoadOptions>
|
|
185
|
+
/**
|
|
186
|
+
* Specialized version of `std::optional<LLMLoadOptions>`.
|
|
187
|
+
*/
|
|
188
|
+
using std__optional_LLMLoadOptions_ = std::optional<LLMLoadOptions>;
|
|
189
|
+
inline std::optional<LLMLoadOptions> create_std__optional_LLMLoadOptions_(const LLMLoadOptions& value) noexcept {
|
|
190
|
+
return std::optional<LLMLoadOptions>(value);
|
|
191
|
+
}
|
|
192
|
+
inline bool has_value_std__optional_LLMLoadOptions_(const std::optional<LLMLoadOptions>& optional) noexcept {
|
|
193
|
+
return optional.has_value();
|
|
194
|
+
}
|
|
195
|
+
inline LLMLoadOptions get_std__optional_LLMLoadOptions_(const std::optional<LLMLoadOptions>& optional) noexcept {
|
|
196
|
+
return *optional;
|
|
197
|
+
}
|
|
198
|
+
|
|
121
199
|
// pragma MARK: std::shared_ptr<Promise<std::string>>
|
|
122
200
|
/**
|
|
123
201
|
* Specialized version of `std::shared_ptr<Promise<std::string>>`.
|
|
@@ -200,6 +278,15 @@ namespace margelo::nitro::mlxreactnative::bridge::swift {
|
|
|
200
278
|
return Result<GenerationStats>::withError(error);
|
|
201
279
|
}
|
|
202
280
|
|
|
281
|
+
// pragma MARK: Result<std::vector<LLMMessage>>
|
|
282
|
+
using Result_std__vector_LLMMessage__ = Result<std::vector<LLMMessage>>;
|
|
283
|
+
inline Result_std__vector_LLMMessage__ create_Result_std__vector_LLMMessage__(const std::vector<LLMMessage>& value) noexcept {
|
|
284
|
+
return Result<std::vector<LLMMessage>>::withValue(value);
|
|
285
|
+
}
|
|
286
|
+
inline Result_std__vector_LLMMessage__ create_Result_std__vector_LLMMessage__(const std::exception_ptr& error) noexcept {
|
|
287
|
+
return Result<std::vector<LLMMessage>>::withError(error);
|
|
288
|
+
}
|
|
289
|
+
|
|
203
290
|
// pragma MARK: std::shared_ptr<Promise<bool>>
|
|
204
291
|
/**
|
|
205
292
|
* Specialized version of `std::shared_ptr<Promise<bool>>`.
|
|
@@ -14,16 +14,23 @@ namespace margelo::nitro::mlxreactnative { struct GenerationStats; }
|
|
|
14
14
|
namespace margelo::nitro::mlxreactnative { class HybridLLMSpec; }
|
|
15
15
|
// Forward declaration of `HybridModelManagerSpec` to properly resolve imports.
|
|
16
16
|
namespace margelo::nitro::mlxreactnative { class HybridModelManagerSpec; }
|
|
17
|
+
// Forward declaration of `LLMLoadOptions` to properly resolve imports.
|
|
18
|
+
namespace margelo::nitro::mlxreactnative { struct LLMLoadOptions; }
|
|
19
|
+
// Forward declaration of `LLMMessage` to properly resolve imports.
|
|
20
|
+
namespace margelo::nitro::mlxreactnative { struct LLMMessage; }
|
|
17
21
|
|
|
18
22
|
// Include C++ defined types
|
|
19
23
|
#include "GenerationStats.hpp"
|
|
20
24
|
#include "HybridLLMSpec.hpp"
|
|
21
25
|
#include "HybridModelManagerSpec.hpp"
|
|
26
|
+
#include "LLMLoadOptions.hpp"
|
|
27
|
+
#include "LLMMessage.hpp"
|
|
22
28
|
#include <NitroModules/Promise.hpp>
|
|
23
29
|
#include <NitroModules/Result.hpp>
|
|
24
30
|
#include <exception>
|
|
25
31
|
#include <functional>
|
|
26
32
|
#include <memory>
|
|
33
|
+
#include <optional>
|
|
27
34
|
#include <string>
|
|
28
35
|
#include <vector>
|
|
29
36
|
|
|
@@ -12,12 +12,20 @@
|
|
|
12
12
|
// Forward declaration of `HybridLLMSpec_cxx` to properly resolve imports.
|
|
13
13
|
namespace MLXReactNative { class HybridLLMSpec_cxx; }
|
|
14
14
|
|
|
15
|
+
// Forward declaration of `LLMLoadOptions` to properly resolve imports.
|
|
16
|
+
namespace margelo::nitro::mlxreactnative { struct LLMLoadOptions; }
|
|
17
|
+
// Forward declaration of `LLMMessage` to properly resolve imports.
|
|
18
|
+
namespace margelo::nitro::mlxreactnative { struct LLMMessage; }
|
|
15
19
|
// Forward declaration of `GenerationStats` to properly resolve imports.
|
|
16
20
|
namespace margelo::nitro::mlxreactnative { struct GenerationStats; }
|
|
17
21
|
|
|
18
22
|
#include <string>
|
|
19
23
|
#include <NitroModules/Promise.hpp>
|
|
24
|
+
#include "LLMLoadOptions.hpp"
|
|
25
|
+
#include <optional>
|
|
20
26
|
#include <functional>
|
|
27
|
+
#include "LLMMessage.hpp"
|
|
28
|
+
#include <vector>
|
|
21
29
|
#include "GenerationStats.hpp"
|
|
22
30
|
|
|
23
31
|
#include "MLXReactNative-Swift-Cxx-Umbrella.hpp"
|
|
@@ -86,8 +94,8 @@ namespace margelo::nitro::mlxreactnative {
|
|
|
86
94
|
|
|
87
95
|
public:
|
|
88
96
|
// Methods
|
|
89
|
-
inline std::shared_ptr<Promise<void>> load(const std::string& modelId, const std::
|
|
90
|
-
auto __result = _swiftPart.load(modelId,
|
|
97
|
+
inline std::shared_ptr<Promise<void>> load(const std::string& modelId, const std::optional<LLMLoadOptions>& options) override {
|
|
98
|
+
auto __result = _swiftPart.load(modelId, options);
|
|
91
99
|
if (__result.hasError()) [[unlikely]] {
|
|
92
100
|
std::rethrow_exception(__result.error());
|
|
93
101
|
}
|
|
@@ -116,6 +124,12 @@ namespace margelo::nitro::mlxreactnative {
|
|
|
116
124
|
std::rethrow_exception(__result.error());
|
|
117
125
|
}
|
|
118
126
|
}
|
|
127
|
+
inline void unload() override {
|
|
128
|
+
auto __result = _swiftPart.unload();
|
|
129
|
+
if (__result.hasError()) [[unlikely]] {
|
|
130
|
+
std::rethrow_exception(__result.error());
|
|
131
|
+
}
|
|
132
|
+
}
|
|
119
133
|
inline GenerationStats getLastGenerationStats() override {
|
|
120
134
|
auto __result = _swiftPart.getLastGenerationStats();
|
|
121
135
|
if (__result.hasError()) [[unlikely]] {
|
|
@@ -124,6 +138,20 @@ namespace margelo::nitro::mlxreactnative {
|
|
|
124
138
|
auto __value = std::move(__result.value());
|
|
125
139
|
return __value;
|
|
126
140
|
}
|
|
141
|
+
inline std::vector<LLMMessage> getHistory() override {
|
|
142
|
+
auto __result = _swiftPart.getHistory();
|
|
143
|
+
if (__result.hasError()) [[unlikely]] {
|
|
144
|
+
std::rethrow_exception(__result.error());
|
|
145
|
+
}
|
|
146
|
+
auto __value = std::move(__result.value());
|
|
147
|
+
return __value;
|
|
148
|
+
}
|
|
149
|
+
inline void clearHistory() override {
|
|
150
|
+
auto __result = _swiftPart.clearHistory();
|
|
151
|
+
if (__result.hasError()) [[unlikely]] {
|
|
152
|
+
std::rethrow_exception(__result.error());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
127
155
|
|
|
128
156
|
private:
|
|
129
157
|
MLXReactNative::HybridLLMSpec_cxx _swiftPart;
|
|
@@ -18,11 +18,14 @@ public protocol HybridLLMSpec_protocol: HybridObject {
|
|
|
18
18
|
var systemPrompt: String { get set }
|
|
19
19
|
|
|
20
20
|
// Methods
|
|
21
|
-
func load(modelId: String,
|
|
21
|
+
func load(modelId: String, options: LLMLoadOptions?) throws -> Promise<Void>
|
|
22
22
|
func generate(prompt: String) throws -> Promise<String>
|
|
23
23
|
func stream(prompt: String, onToken: @escaping (_ token: String) -> Void) throws -> Promise<String>
|
|
24
24
|
func stop() throws -> Void
|
|
25
|
+
func unload() throws -> Void
|
|
25
26
|
func getLastGenerationStats() throws -> GenerationStats
|
|
27
|
+
func getHistory() throws -> [LLMMessage]
|
|
28
|
+
func clearHistory() throws -> Void
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
public extension HybridLLMSpec_protocol {
|
|
@@ -159,14 +159,9 @@ open class HybridLLMSpec_cxx {
|
|
|
159
159
|
|
|
160
160
|
// Methods
|
|
161
161
|
@inline(__always)
|
|
162
|
-
public final func load(modelId: std.string,
|
|
162
|
+
public final func load(modelId: std.string, options: bridge.std__optional_LLMLoadOptions_) -> bridge.Result_std__shared_ptr_Promise_void___ {
|
|
163
163
|
do {
|
|
164
|
-
let __result = try self.__implementation.load(modelId: String(modelId),
|
|
165
|
-
let __wrappedFunction = bridge.wrap_Func_void_double(onProgress)
|
|
166
|
-
return { (__progress: Double) -> Void in
|
|
167
|
-
__wrappedFunction.call(__progress)
|
|
168
|
-
}
|
|
169
|
-
}())
|
|
164
|
+
let __result = try self.__implementation.load(modelId: String(modelId), options: options.value)
|
|
170
165
|
let __resultCpp = { () -> bridge.std__shared_ptr_Promise_void__ in
|
|
171
166
|
let __promise = bridge.create_std__shared_ptr_Promise_void__()
|
|
172
167
|
let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_void__(__promise)
|
|
@@ -236,6 +231,17 @@ open class HybridLLMSpec_cxx {
|
|
|
236
231
|
}
|
|
237
232
|
}
|
|
238
233
|
|
|
234
|
+
@inline(__always)
|
|
235
|
+
public final func unload() -> bridge.Result_void_ {
|
|
236
|
+
do {
|
|
237
|
+
try self.__implementation.unload()
|
|
238
|
+
return bridge.create_Result_void_()
|
|
239
|
+
} catch (let __error) {
|
|
240
|
+
let __exceptionPtr = __error.toCpp()
|
|
241
|
+
return bridge.create_Result_void_(__exceptionPtr)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
239
245
|
@inline(__always)
|
|
240
246
|
public final func getLastGenerationStats() -> bridge.Result_GenerationStats_ {
|
|
241
247
|
do {
|
|
@@ -247,4 +253,33 @@ open class HybridLLMSpec_cxx {
|
|
|
247
253
|
return bridge.create_Result_GenerationStats_(__exceptionPtr)
|
|
248
254
|
}
|
|
249
255
|
}
|
|
256
|
+
|
|
257
|
+
@inline(__always)
|
|
258
|
+
public final func getHistory() -> bridge.Result_std__vector_LLMMessage__ {
|
|
259
|
+
do {
|
|
260
|
+
let __result = try self.__implementation.getHistory()
|
|
261
|
+
let __resultCpp = { () -> bridge.std__vector_LLMMessage_ in
|
|
262
|
+
var __vector = bridge.create_std__vector_LLMMessage_(__result.count)
|
|
263
|
+
for __item in __result {
|
|
264
|
+
__vector.push_back(__item)
|
|
265
|
+
}
|
|
266
|
+
return __vector
|
|
267
|
+
}()
|
|
268
|
+
return bridge.create_Result_std__vector_LLMMessage__(__resultCpp)
|
|
269
|
+
} catch (let __error) {
|
|
270
|
+
let __exceptionPtr = __error.toCpp()
|
|
271
|
+
return bridge.create_Result_std__vector_LLMMessage__(__exceptionPtr)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@inline(__always)
|
|
276
|
+
public final func clearHistory() -> bridge.Result_void_ {
|
|
277
|
+
do {
|
|
278
|
+
try self.__implementation.clearHistory()
|
|
279
|
+
return bridge.create_Result_void_()
|
|
280
|
+
} catch (let __error) {
|
|
281
|
+
let __exceptionPtr = __error.toCpp()
|
|
282
|
+
return bridge.create_Result_void_(__exceptionPtr)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
250
285
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// LLMLoadOptions.swift
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import NitroModules
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents an instance of `LLMLoadOptions`, backed by a C++ struct.
|
|
13
|
+
*/
|
|
14
|
+
public typealias LLMLoadOptions = margelo.nitro.mlxreactnative.LLMLoadOptions
|
|
15
|
+
|
|
16
|
+
public extension LLMLoadOptions {
|
|
17
|
+
private typealias bridge = margelo.nitro.mlxreactnative.bridge.swift
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new instance of `LLMLoadOptions`.
|
|
21
|
+
*/
|
|
22
|
+
init(onProgress: ((_ progress: Double) -> Void)?, additionalContext: [LLMMessage]?, manageHistory: Bool?) {
|
|
23
|
+
self.init({ () -> bridge.std__optional_std__function_void_double____progress______ in
|
|
24
|
+
if let __unwrappedValue = onProgress {
|
|
25
|
+
return bridge.create_std__optional_std__function_void_double____progress______({ () -> bridge.Func_void_double in
|
|
26
|
+
let __closureWrapper = Func_void_double(__unwrappedValue)
|
|
27
|
+
return bridge.create_Func_void_double(__closureWrapper.toUnsafe())
|
|
28
|
+
}())
|
|
29
|
+
} else {
|
|
30
|
+
return .init()
|
|
31
|
+
}
|
|
32
|
+
}(), { () -> bridge.std__optional_std__vector_LLMMessage__ in
|
|
33
|
+
if let __unwrappedValue = additionalContext {
|
|
34
|
+
return bridge.create_std__optional_std__vector_LLMMessage__({ () -> bridge.std__vector_LLMMessage_ in
|
|
35
|
+
var __vector = bridge.create_std__vector_LLMMessage_(__unwrappedValue.count)
|
|
36
|
+
for __item in __unwrappedValue {
|
|
37
|
+
__vector.push_back(__item)
|
|
38
|
+
}
|
|
39
|
+
return __vector
|
|
40
|
+
}())
|
|
41
|
+
} else {
|
|
42
|
+
return .init()
|
|
43
|
+
}
|
|
44
|
+
}(), { () -> bridge.std__optional_bool_ in
|
|
45
|
+
if let __unwrappedValue = manageHistory {
|
|
46
|
+
return bridge.create_std__optional_bool_(__unwrappedValue)
|
|
47
|
+
} else {
|
|
48
|
+
return .init()
|
|
49
|
+
}
|
|
50
|
+
}())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var onProgress: ((_ progress: Double) -> Void)? {
|
|
54
|
+
@inline(__always)
|
|
55
|
+
get {
|
|
56
|
+
return { () -> ((_ progress: Double) -> Void)? in
|
|
57
|
+
if bridge.has_value_std__optional_std__function_void_double____progress______(self.__onProgress) {
|
|
58
|
+
let __unwrapped = bridge.get_std__optional_std__function_void_double____progress______(self.__onProgress)
|
|
59
|
+
return { () -> (Double) -> Void in
|
|
60
|
+
let __wrappedFunction = bridge.wrap_Func_void_double(__unwrapped)
|
|
61
|
+
return { (__progress: Double) -> Void in
|
|
62
|
+
__wrappedFunction.call(__progress)
|
|
63
|
+
}
|
|
64
|
+
}()
|
|
65
|
+
} else {
|
|
66
|
+
return nil
|
|
67
|
+
}
|
|
68
|
+
}()
|
|
69
|
+
}
|
|
70
|
+
@inline(__always)
|
|
71
|
+
set {
|
|
72
|
+
self.__onProgress = { () -> bridge.std__optional_std__function_void_double____progress______ in
|
|
73
|
+
if let __unwrappedValue = newValue {
|
|
74
|
+
return bridge.create_std__optional_std__function_void_double____progress______({ () -> bridge.Func_void_double in
|
|
75
|
+
let __closureWrapper = Func_void_double(__unwrappedValue)
|
|
76
|
+
return bridge.create_Func_void_double(__closureWrapper.toUnsafe())
|
|
77
|
+
}())
|
|
78
|
+
} else {
|
|
79
|
+
return .init()
|
|
80
|
+
}
|
|
81
|
+
}()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
var additionalContext: [LLMMessage]? {
|
|
86
|
+
@inline(__always)
|
|
87
|
+
get {
|
|
88
|
+
return { () -> [LLMMessage]? in
|
|
89
|
+
if bridge.has_value_std__optional_std__vector_LLMMessage__(self.__additionalContext) {
|
|
90
|
+
let __unwrapped = bridge.get_std__optional_std__vector_LLMMessage__(self.__additionalContext)
|
|
91
|
+
return __unwrapped.map({ __item in __item })
|
|
92
|
+
} else {
|
|
93
|
+
return nil
|
|
94
|
+
}
|
|
95
|
+
}()
|
|
96
|
+
}
|
|
97
|
+
@inline(__always)
|
|
98
|
+
set {
|
|
99
|
+
self.__additionalContext = { () -> bridge.std__optional_std__vector_LLMMessage__ in
|
|
100
|
+
if let __unwrappedValue = newValue {
|
|
101
|
+
return bridge.create_std__optional_std__vector_LLMMessage__({ () -> bridge.std__vector_LLMMessage_ in
|
|
102
|
+
var __vector = bridge.create_std__vector_LLMMessage_(__unwrappedValue.count)
|
|
103
|
+
for __item in __unwrappedValue {
|
|
104
|
+
__vector.push_back(__item)
|
|
105
|
+
}
|
|
106
|
+
return __vector
|
|
107
|
+
}())
|
|
108
|
+
} else {
|
|
109
|
+
return .init()
|
|
110
|
+
}
|
|
111
|
+
}()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var manageHistory: Bool? {
|
|
116
|
+
@inline(__always)
|
|
117
|
+
get {
|
|
118
|
+
return { () -> Bool? in
|
|
119
|
+
if bridge.has_value_std__optional_bool_(self.__manageHistory) {
|
|
120
|
+
let __unwrapped = bridge.get_std__optional_bool_(self.__manageHistory)
|
|
121
|
+
return __unwrapped
|
|
122
|
+
} else {
|
|
123
|
+
return nil
|
|
124
|
+
}
|
|
125
|
+
}()
|
|
126
|
+
}
|
|
127
|
+
@inline(__always)
|
|
128
|
+
set {
|
|
129
|
+
self.__manageHistory = { () -> bridge.std__optional_bool_ in
|
|
130
|
+
if let __unwrappedValue = newValue {
|
|
131
|
+
return bridge.create_std__optional_bool_(__unwrappedValue)
|
|
132
|
+
} else {
|
|
133
|
+
return .init()
|
|
134
|
+
}
|
|
135
|
+
}()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// LLMMessage.swift
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import NitroModules
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents an instance of `LLMMessage`, backed by a C++ struct.
|
|
13
|
+
*/
|
|
14
|
+
public typealias LLMMessage = margelo.nitro.mlxreactnative.LLMMessage
|
|
15
|
+
|
|
16
|
+
public extension LLMMessage {
|
|
17
|
+
private typealias bridge = margelo.nitro.mlxreactnative.bridge.swift
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new instance of `LLMMessage`.
|
|
21
|
+
*/
|
|
22
|
+
init(role: String, content: String) {
|
|
23
|
+
self.init(std.string(role), std.string(content))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var role: String {
|
|
27
|
+
@inline(__always)
|
|
28
|
+
get {
|
|
29
|
+
return String(self.__role)
|
|
30
|
+
}
|
|
31
|
+
@inline(__always)
|
|
32
|
+
set {
|
|
33
|
+
self.__role = std.string(newValue)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var content: String {
|
|
38
|
+
@inline(__always)
|
|
39
|
+
get {
|
|
40
|
+
return String(self.__content)
|
|
41
|
+
}
|
|
42
|
+
@inline(__always)
|
|
43
|
+
set {
|
|
44
|
+
self.__content = std.string(newValue)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -25,7 +25,10 @@ namespace margelo::nitro::mlxreactnative {
|
|
|
25
25
|
prototype.registerHybridMethod("generate", &HybridLLMSpec::generate);
|
|
26
26
|
prototype.registerHybridMethod("stream", &HybridLLMSpec::stream);
|
|
27
27
|
prototype.registerHybridMethod("stop", &HybridLLMSpec::stop);
|
|
28
|
+
prototype.registerHybridMethod("unload", &HybridLLMSpec::unload);
|
|
28
29
|
prototype.registerHybridMethod("getLastGenerationStats", &HybridLLMSpec::getLastGenerationStats);
|
|
30
|
+
prototype.registerHybridMethod("getHistory", &HybridLLMSpec::getHistory);
|
|
31
|
+
prototype.registerHybridMethod("clearHistory", &HybridLLMSpec::clearHistory);
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -13,13 +13,21 @@
|
|
|
13
13
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
14
|
#endif
|
|
15
15
|
|
|
16
|
+
// Forward declaration of `LLMLoadOptions` to properly resolve imports.
|
|
17
|
+
namespace margelo::nitro::mlxreactnative { struct LLMLoadOptions; }
|
|
16
18
|
// Forward declaration of `GenerationStats` to properly resolve imports.
|
|
17
19
|
namespace margelo::nitro::mlxreactnative { struct GenerationStats; }
|
|
20
|
+
// Forward declaration of `LLMMessage` to properly resolve imports.
|
|
21
|
+
namespace margelo::nitro::mlxreactnative { struct LLMMessage; }
|
|
18
22
|
|
|
19
23
|
#include <string>
|
|
20
24
|
#include <NitroModules/Promise.hpp>
|
|
25
|
+
#include "LLMLoadOptions.hpp"
|
|
26
|
+
#include <optional>
|
|
21
27
|
#include <functional>
|
|
22
28
|
#include "GenerationStats.hpp"
|
|
29
|
+
#include "LLMMessage.hpp"
|
|
30
|
+
#include <vector>
|
|
23
31
|
|
|
24
32
|
namespace margelo::nitro::mlxreactnative {
|
|
25
33
|
|
|
@@ -58,11 +66,14 @@ namespace margelo::nitro::mlxreactnative {
|
|
|
58
66
|
|
|
59
67
|
public:
|
|
60
68
|
// Methods
|
|
61
|
-
virtual std::shared_ptr<Promise<void>> load(const std::string& modelId, const std::
|
|
69
|
+
virtual std::shared_ptr<Promise<void>> load(const std::string& modelId, const std::optional<LLMLoadOptions>& options) = 0;
|
|
62
70
|
virtual std::shared_ptr<Promise<std::string>> generate(const std::string& prompt) = 0;
|
|
63
71
|
virtual std::shared_ptr<Promise<std::string>> stream(const std::string& prompt, const std::function<void(const std::string& /* token */)>& onToken) = 0;
|
|
64
72
|
virtual void stop() = 0;
|
|
73
|
+
virtual void unload() = 0;
|
|
65
74
|
virtual GenerationStats getLastGenerationStats() = 0;
|
|
75
|
+
virtual std::vector<LLMMessage> getHistory() = 0;
|
|
76
|
+
virtual void clearHistory() = 0;
|
|
66
77
|
|
|
67
78
|
protected:
|
|
68
79
|
// Hybrid Setup
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// LLMLoadOptions.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/JSIConverter.hpp>)
|
|
11
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
#if __has_include(<NitroModules/NitroDefines.hpp>)
|
|
16
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
17
|
+
#else
|
|
18
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
|
+
#endif
|
|
20
|
+
#if __has_include(<NitroModules/JSIHelpers.hpp>)
|
|
21
|
+
#include <NitroModules/JSIHelpers.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
// Forward declaration of `LLMMessage` to properly resolve imports.
|
|
27
|
+
namespace margelo::nitro::mlxreactnative { struct LLMMessage; }
|
|
28
|
+
|
|
29
|
+
#include <functional>
|
|
30
|
+
#include <optional>
|
|
31
|
+
#include "LLMMessage.hpp"
|
|
32
|
+
#include <vector>
|
|
33
|
+
|
|
34
|
+
namespace margelo::nitro::mlxreactnative {
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A struct which can be represented as a JavaScript object (LLMLoadOptions).
|
|
38
|
+
*/
|
|
39
|
+
struct LLMLoadOptions {
|
|
40
|
+
public:
|
|
41
|
+
std::optional<std::function<void(double /* progress */)>> onProgress SWIFT_PRIVATE;
|
|
42
|
+
std::optional<std::vector<LLMMessage>> additionalContext SWIFT_PRIVATE;
|
|
43
|
+
std::optional<bool> manageHistory SWIFT_PRIVATE;
|
|
44
|
+
|
|
45
|
+
public:
|
|
46
|
+
LLMLoadOptions() = default;
|
|
47
|
+
explicit LLMLoadOptions(std::optional<std::function<void(double /* progress */)>> onProgress, std::optional<std::vector<LLMMessage>> additionalContext, std::optional<bool> manageHistory): onProgress(onProgress), additionalContext(additionalContext), manageHistory(manageHistory) {}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
} // namespace margelo::nitro::mlxreactnative
|
|
51
|
+
|
|
52
|
+
namespace margelo::nitro {
|
|
53
|
+
|
|
54
|
+
// C++ LLMLoadOptions <> JS LLMLoadOptions (object)
|
|
55
|
+
template <>
|
|
56
|
+
struct JSIConverter<margelo::nitro::mlxreactnative::LLMLoadOptions> final {
|
|
57
|
+
static inline margelo::nitro::mlxreactnative::LLMLoadOptions fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
58
|
+
jsi::Object obj = arg.asObject(runtime);
|
|
59
|
+
return margelo::nitro::mlxreactnative::LLMLoadOptions(
|
|
60
|
+
JSIConverter<std::optional<std::function<void(double)>>>::fromJSI(runtime, obj.getProperty(runtime, "onProgress")),
|
|
61
|
+
JSIConverter<std::optional<std::vector<margelo::nitro::mlxreactnative::LLMMessage>>>::fromJSI(runtime, obj.getProperty(runtime, "additionalContext")),
|
|
62
|
+
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, "manageHistory"))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::mlxreactnative::LLMLoadOptions& arg) {
|
|
66
|
+
jsi::Object obj(runtime);
|
|
67
|
+
obj.setProperty(runtime, "onProgress", JSIConverter<std::optional<std::function<void(double)>>>::toJSI(runtime, arg.onProgress));
|
|
68
|
+
obj.setProperty(runtime, "additionalContext", JSIConverter<std::optional<std::vector<margelo::nitro::mlxreactnative::LLMMessage>>>::toJSI(runtime, arg.additionalContext));
|
|
69
|
+
obj.setProperty(runtime, "manageHistory", JSIConverter<std::optional<bool>>::toJSI(runtime, arg.manageHistory));
|
|
70
|
+
return obj;
|
|
71
|
+
}
|
|
72
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
73
|
+
if (!value.isObject()) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
jsi::Object obj = value.getObject(runtime);
|
|
77
|
+
if (!nitro::isPlainObject(runtime, obj)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (!JSIConverter<std::optional<std::function<void(double)>>>::canConvert(runtime, obj.getProperty(runtime, "onProgress"))) return false;
|
|
81
|
+
if (!JSIConverter<std::optional<std::vector<margelo::nitro::mlxreactnative::LLMMessage>>>::canConvert(runtime, obj.getProperty(runtime, "additionalContext"))) return false;
|
|
82
|
+
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, "manageHistory"))) return false;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// LLMMessage.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/JSIConverter.hpp>)
|
|
11
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
#if __has_include(<NitroModules/NitroDefines.hpp>)
|
|
16
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
17
|
+
#else
|
|
18
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
|
+
#endif
|
|
20
|
+
#if __has_include(<NitroModules/JSIHelpers.hpp>)
|
|
21
|
+
#include <NitroModules/JSIHelpers.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
#include <string>
|
|
29
|
+
|
|
30
|
+
namespace margelo::nitro::mlxreactnative {
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A struct which can be represented as a JavaScript object (LLMMessage).
|
|
34
|
+
*/
|
|
35
|
+
struct LLMMessage {
|
|
36
|
+
public:
|
|
37
|
+
std::string role SWIFT_PRIVATE;
|
|
38
|
+
std::string content SWIFT_PRIVATE;
|
|
39
|
+
|
|
40
|
+
public:
|
|
41
|
+
LLMMessage() = default;
|
|
42
|
+
explicit LLMMessage(std::string role, std::string content): role(role), content(content) {}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
} // namespace margelo::nitro::mlxreactnative
|
|
46
|
+
|
|
47
|
+
namespace margelo::nitro {
|
|
48
|
+
|
|
49
|
+
// C++ LLMMessage <> JS LLMMessage (object)
|
|
50
|
+
template <>
|
|
51
|
+
struct JSIConverter<margelo::nitro::mlxreactnative::LLMMessage> final {
|
|
52
|
+
static inline margelo::nitro::mlxreactnative::LLMMessage fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
53
|
+
jsi::Object obj = arg.asObject(runtime);
|
|
54
|
+
return margelo::nitro::mlxreactnative::LLMMessage(
|
|
55
|
+
JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "role")),
|
|
56
|
+
JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "content"))
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::mlxreactnative::LLMMessage& arg) {
|
|
60
|
+
jsi::Object obj(runtime);
|
|
61
|
+
obj.setProperty(runtime, "role", JSIConverter<std::string>::toJSI(runtime, arg.role));
|
|
62
|
+
obj.setProperty(runtime, "content", JSIConverter<std::string>::toJSI(runtime, arg.content));
|
|
63
|
+
return obj;
|
|
64
|
+
}
|
|
65
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
66
|
+
if (!value.isObject()) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
jsi::Object obj = value.getObject(runtime);
|
|
70
|
+
if (!nitro::isPlainObject(runtime, obj)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "role"))) return false;
|
|
74
|
+
if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "content"))) return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
} // namespace margelo::nitro
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-mlx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Nitro module package",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
@@ -89,14 +89,6 @@
|
|
|
89
89
|
"requireCleanWorkingDir": false
|
|
90
90
|
},
|
|
91
91
|
"plugins": {
|
|
92
|
-
"@release-it/bumper": {
|
|
93
|
-
"out": [
|
|
94
|
-
{
|
|
95
|
-
"file": "package.json",
|
|
96
|
-
"path": "version"
|
|
97
|
-
}
|
|
98
|
-
]
|
|
99
|
-
},
|
|
100
92
|
"@release-it/conventional-changelog": {
|
|
101
93
|
"preset": {
|
|
102
94
|
"name": "conventionalcommits",
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { LLM } from './llm'
|
|
1
|
+
export { LLM, type Message } from './llm'
|
|
2
2
|
export { ModelManager } from './modelManager'
|
|
3
3
|
export { MLXModel } from './models'
|
|
4
4
|
|
|
5
|
-
export type { GenerationStats, LLM as LLMSpec } from './specs/LLM.nitro'
|
|
5
|
+
export type { GenerationStats, LLM as LLMSpec, LLMLoadOptions } from './specs/LLM.nitro'
|
|
6
6
|
export type { ModelManager as ModelManagerSpec } from './specs/ModelManager.nitro'
|
package/src/llm.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { NitroModules } from 'react-native-nitro-modules'
|
|
2
|
-
import type { GenerationStats, LLM as LLMSpec } from './specs/LLM.nitro'
|
|
2
|
+
import type { GenerationStats, LLMLoadOptions, LLM as LLMSpec } from './specs/LLM.nitro'
|
|
3
3
|
|
|
4
4
|
let instance: LLMSpec | null = null
|
|
5
5
|
|
|
6
|
+
export type Message = {
|
|
7
|
+
role: 'user' | 'assistant' | 'system'
|
|
8
|
+
content: string
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
function getInstance(): LLMSpec {
|
|
7
12
|
if (!instance) {
|
|
8
13
|
instance = NitroModules.createHybridObject<LLMSpec>('LLM')
|
|
@@ -36,10 +41,10 @@ export const LLM = {
|
|
|
36
41
|
/**
|
|
37
42
|
* Load a model into memory. Downloads the model from HuggingFace if not already cached.
|
|
38
43
|
* @param modelId - HuggingFace model ID (e.g., 'mlx-community/Qwen3-0.6B-4bit')
|
|
39
|
-
* @param
|
|
44
|
+
* @param options - Callback invoked with loading progress (0-1)
|
|
40
45
|
*/
|
|
41
|
-
load(modelId: string,
|
|
42
|
-
return getInstance().load(modelId,
|
|
46
|
+
load(modelId: string, options: LLMLoadOptions): Promise<void> {
|
|
47
|
+
return getInstance().load(modelId, options)
|
|
43
48
|
},
|
|
44
49
|
|
|
45
50
|
/**
|
|
@@ -69,6 +74,14 @@ export const LLM = {
|
|
|
69
74
|
getInstance().stop()
|
|
70
75
|
},
|
|
71
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Unload the current model and release memory.
|
|
79
|
+
* Call this when you're done with the model to free up memory.
|
|
80
|
+
*/
|
|
81
|
+
unload(): void {
|
|
82
|
+
getInstance().unload()
|
|
83
|
+
},
|
|
84
|
+
|
|
72
85
|
/**
|
|
73
86
|
* Get statistics from the last generation.
|
|
74
87
|
* @returns Statistics including token count, tokens/sec, TTFT, and total time
|
|
@@ -77,6 +90,21 @@ export const LLM = {
|
|
|
77
90
|
return getInstance().getLastGenerationStats()
|
|
78
91
|
},
|
|
79
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Get the message history if management is enabled.
|
|
95
|
+
* @returns Array of messages in the history
|
|
96
|
+
*/
|
|
97
|
+
getHistory(): Message[] {
|
|
98
|
+
return getInstance().getHistory() as Message[]
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clear the message history.
|
|
103
|
+
*/
|
|
104
|
+
clearHistory(): void {
|
|
105
|
+
getInstance().clearHistory()
|
|
106
|
+
},
|
|
107
|
+
|
|
80
108
|
/** Whether a model is currently loaded and ready for generation */
|
|
81
109
|
get isLoaded(): boolean {
|
|
82
110
|
return getInstance().isLoaded
|
package/src/specs/LLM.nitro.ts
CHANGED
|
@@ -14,6 +14,22 @@ export interface GenerationStats {
|
|
|
14
14
|
totalTime: number
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface LLMMessage {
|
|
18
|
+
role: string
|
|
19
|
+
content: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Options for loading a model.
|
|
23
|
+
*/
|
|
24
|
+
export interface LLMLoadOptions {
|
|
25
|
+
/** Callback invoked with loading progress (0-1) */
|
|
26
|
+
onProgress?: (progress: number) => void
|
|
27
|
+
/** Additional context to provide to the model */
|
|
28
|
+
additionalContext?: LLMMessage[]
|
|
29
|
+
/** Whether to automatically manage message history */
|
|
30
|
+
manageHistory?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
/**
|
|
18
34
|
* Low-level LLM interface for text generation using MLX.
|
|
19
35
|
* @internal Use the `LLM` export from `react-native-nitro-mlx` instead.
|
|
@@ -22,9 +38,9 @@ export interface LLM extends HybridObject<{ ios: 'swift' }> {
|
|
|
22
38
|
/**
|
|
23
39
|
* Load a model into memory. Downloads from HuggingFace if not already cached.
|
|
24
40
|
* @param modelId - HuggingFace model ID (e.g., 'mlx-community/Qwen3-0.6B-4bit')
|
|
25
|
-
* @param
|
|
41
|
+
* @param options - Callback invoked with loading progress (0-1)
|
|
26
42
|
*/
|
|
27
|
-
load(modelId: string,
|
|
43
|
+
load(modelId: string, options?: LLMLoadOptions): Promise<void>
|
|
28
44
|
|
|
29
45
|
/**
|
|
30
46
|
* Generate a complete response for a prompt.
|
|
@@ -46,12 +62,28 @@ export interface LLM extends HybridObject<{ ios: 'swift' }> {
|
|
|
46
62
|
*/
|
|
47
63
|
stop(): void
|
|
48
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Unload the current model and release memory.
|
|
67
|
+
*/
|
|
68
|
+
unload(): void
|
|
69
|
+
|
|
49
70
|
/**
|
|
50
71
|
* Get statistics from the last generation.
|
|
51
72
|
* @returns Statistics including token count, speed, and timing
|
|
52
73
|
*/
|
|
53
74
|
getLastGenerationStats(): GenerationStats
|
|
54
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Get the message history if management is enabled.
|
|
78
|
+
* @returns Array of messages in the history
|
|
79
|
+
*/
|
|
80
|
+
getHistory(): LLMMessage[]
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Clear the message history.
|
|
84
|
+
*/
|
|
85
|
+
clearHistory(): void
|
|
86
|
+
|
|
55
87
|
/** Whether a model is currently loaded */
|
|
56
88
|
readonly isLoaded: boolean
|
|
57
89
|
/** Whether text is currently being generated */
|