react-native-litert-lm 0.1.0 โ 0.1.1
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 +16 -13
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +115 -102
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/LiteRTLMRegistry.kt +32 -0
- package/android/src/main/java/dev/litert/litertlm/LiteRTLMInitProvider.kt +14 -0
- package/lib/specs/LiteRTLM.nitro.d.ts +4 -5
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +59 -12
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +5 -4
- package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +5 -4
- package/package.json +1 -1
- package/src/specs/LiteRTLM.nitro.ts +4 -5
package/README.md
CHANGED
|
@@ -9,8 +9,9 @@ High-performance LLM inference for React Native powered by [LiteRT-LM](https://g
|
|
|
9
9
|
- โก **GPU Acceleration** - GPU delegate (Android), Metal (iOS when available)
|
|
10
10
|
- ๐ฆ **Bundled Tokenizer** - No separate tokenization library needed
|
|
11
11
|
- ๐ **Streaming Support** - Token-by-token generation callbacks
|
|
12
|
-
- ๐ฑ **Cross-Platform** - Android API 26+
|
|
13
|
-
- ๐ง **Multimodal** - Image and audio input (Coming Soon
|
|
12
|
+
- ๐ฑ **Cross-Platform** - Android API 26+
|
|
13
|
+
- ๐ง **Multimodal** - Image and audio input (Coming Soon)
|
|
14
|
+
- ๐งต **Async API** - Non-blocking inference to prevent UI freezes
|
|
14
15
|
|
|
15
16
|
## Status
|
|
16
17
|
|
|
@@ -86,15 +87,15 @@ import { createLLM } from "react-native-litert-lm";
|
|
|
86
87
|
|
|
87
88
|
const llm = createLLM();
|
|
88
89
|
|
|
89
|
-
// Load a Gemma 3n model
|
|
90
|
-
llm.loadModel("/path/to/gemma-3n-e2b.litertlm", {
|
|
90
|
+
// Load a Gemma 3n model (async)
|
|
91
|
+
await llm.loadModel("/path/to/gemma-3n-e2b.litertlm", {
|
|
91
92
|
backend: "gpu",
|
|
92
93
|
temperature: 0.7,
|
|
93
94
|
maxTokens: 512,
|
|
94
95
|
});
|
|
95
96
|
|
|
96
|
-
// Generate response
|
|
97
|
-
const response = llm.sendMessage("What is the capital of France?");
|
|
97
|
+
// Generate response (async)
|
|
98
|
+
const response = await llm.sendMessage("What is the capital of France?");
|
|
98
99
|
console.log(response);
|
|
99
100
|
|
|
100
101
|
// Clean up
|
|
@@ -114,13 +115,15 @@ llm.sendMessageAsync("Tell me a story", (token, done) => {
|
|
|
114
115
|
|
|
115
116
|
```typescript
|
|
116
117
|
// Image input (for vision models)
|
|
117
|
-
|
|
118
|
+
// Note: Currently throws error on Android (Coming Soon)
|
|
119
|
+
const response = await llm.sendMessageWithImage(
|
|
118
120
|
"What's in this image?",
|
|
119
121
|
"/path/to/image.jpg",
|
|
120
122
|
);
|
|
121
123
|
|
|
122
124
|
// Audio input (for audio models)
|
|
123
|
-
|
|
125
|
+
// Note: Currently throws error on Android (Coming Soon)
|
|
126
|
+
const transcription = await llm.sendMessageWithAudio(
|
|
124
127
|
"Transcribe this audio",
|
|
125
128
|
"/path/to/audio.wav",
|
|
126
129
|
);
|
|
@@ -152,7 +155,7 @@ Download `.litertlm` models from [HuggingFace](https://huggingface.co/litert-com
|
|
|
152
155
|
|
|
153
156
|
Creates a new LLM inference engine instance.
|
|
154
157
|
|
|
155
|
-
### `loadModel(path, config?)
|
|
158
|
+
### `loadModel(path, config?): Promise<void>`
|
|
156
159
|
|
|
157
160
|
- `path: string` - Absolute path to `.litertlm` file
|
|
158
161
|
- `config.backend` - `'cpu'` | `'gpu'` | `'npu'` (default: `'gpu'`)
|
|
@@ -172,19 +175,19 @@ Creates a new LLM inference engine instance.
|
|
|
172
175
|
|
|
173
176
|
> โ ๏ธ **NPU Note**: NPU acceleration requires compatible hardware (Qualcomm Hexagon, MediaTek APU, etc.). If unavailable, LiteRT-LM automatically falls back to GPU.
|
|
174
177
|
|
|
175
|
-
### `sendMessage(message): string
|
|
178
|
+
### `sendMessage(message): Promise<string>`
|
|
176
179
|
|
|
177
|
-
Blocking generation. Returns complete response.
|
|
180
|
+
Blocking generation (executed on background thread). Returns complete response.
|
|
178
181
|
|
|
179
182
|
### `sendMessageAsync(message, callback)`
|
|
180
183
|
|
|
181
184
|
Streaming generation. Callback receives `(token, isDone)`.
|
|
182
185
|
|
|
183
|
-
### `sendMessageWithImage(message, imagePath): string
|
|
186
|
+
### `sendMessageWithImage(message, imagePath): Promise<string>`
|
|
184
187
|
|
|
185
188
|
Send a message with an image attachment (for vision models).
|
|
186
189
|
|
|
187
|
-
### `sendMessageWithAudio(message, audioPath): string
|
|
190
|
+
### `sendMessageWithAudio(message, audioPath): Promise<string>`
|
|
188
191
|
|
|
189
192
|
Send a message with an audio attachment (for audio models).
|
|
190
193
|
|
|
@@ -19,6 +19,7 @@ import com.margelo.nitro.dev.litert.litertlm.HybridLiteRTLMSpec
|
|
|
19
19
|
import com.margelo.nitro.dev.litert.litertlm.LLMConfig
|
|
20
20
|
import com.margelo.nitro.dev.litert.litertlm.Message
|
|
21
21
|
import com.margelo.nitro.dev.litert.litertlm.Role
|
|
22
|
+
import com.margelo.nitro.core.Promise
|
|
22
23
|
|
|
23
24
|
// Alias to avoid confusion with our generated Message type
|
|
24
25
|
typealias LiteRTMessage = com.google.ai.edge.litertlm.Message
|
|
@@ -35,6 +36,10 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
|
|
|
35
36
|
private const val TAG = "HybridLiteRTLM"
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
init {
|
|
40
|
+
LiteRTLMRegistry.register(this)
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
// LiteRT-LM Engine and Conversation
|
|
39
44
|
private var engine: Engine? = null
|
|
40
45
|
private var conversation: Conversation? = null
|
|
@@ -60,116 +65,124 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
|
|
|
60
65
|
private var maxTokens: Int = 1024
|
|
61
66
|
|
|
62
67
|
override val memorySize: Long
|
|
63
|
-
get() =
|
|
68
|
+
get() = 1024L * 1024L * 1024L // ~1GB (models are large)
|
|
64
69
|
|
|
65
70
|
// -------------------------------------------------------------------------
|
|
66
71
|
// loadModel - Initialize LiteRT-LM Engine and Conversation
|
|
67
72
|
// -------------------------------------------------------------------------
|
|
68
|
-
override fun loadModel(modelPath: String, config: LLMConfig?) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
override fun loadModel(modelPath: String, config: LLMConfig?): Promise<Unit> {
|
|
74
|
+
return Promise.parallel {
|
|
75
|
+
Log.i(TAG, "loadModel: $modelPath")
|
|
76
|
+
|
|
77
|
+
// Clean up existing resources
|
|
78
|
+
close()
|
|
79
|
+
|
|
80
|
+
// Apply configuration
|
|
81
|
+
config?.let { cfg ->
|
|
82
|
+
cfg.backend?.let { backend = it }
|
|
83
|
+
cfg.temperature?.let { temperature = it }
|
|
84
|
+
cfg.topK?.let { topK = it.toInt() }
|
|
85
|
+
cfg.topP?.let { topP = it }
|
|
86
|
+
cfg.maxTokens?.let { maxTokens = it.toInt() }
|
|
87
|
+
}
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
try {
|
|
90
|
+
// Map our Backend enum to LiteRT-LM Backend enum
|
|
91
|
+
val lmBackend = when (backend) {
|
|
92
|
+
Backend.GPU -> com.google.ai.edge.litertlm.Backend.GPU
|
|
93
|
+
Backend.NPU -> {
|
|
94
|
+
Log.i(TAG, "NPU backend requested - requires hardware support")
|
|
95
|
+
com.google.ai.edge.litertlm.Backend.NPU
|
|
96
|
+
}
|
|
97
|
+
else -> com.google.ai.edge.litertlm.Backend.CPU
|
|
90
98
|
}
|
|
91
|
-
else -> com.google.ai.edge.litertlm.Backend.CPU
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Vision backend: hardcoded to GPU (required by Gemma 3n)
|
|
95
|
-
val lmVisionBackend = com.google.ai.edge.litertlm.Backend.GPU
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
100
|
+
// Vision backend: hardcoded to GPU (required by Gemma 3n)
|
|
101
|
+
val lmVisionBackend = com.google.ai.edge.litertlm.Backend.GPU
|
|
102
|
+
|
|
103
|
+
// Audio backend: hardcoded to CPU (optimal for audio processing)
|
|
104
|
+
val lmAudioBackend = com.google.ai.edge.litertlm.Backend.CPU
|
|
105
|
+
|
|
106
|
+
Log.i(TAG, "Backend config: main=$lmBackend, vision=$lmVisionBackend (hardcoded), audio=$lmAudioBackend (hardcoded)")
|
|
107
|
+
|
|
108
|
+
// Get cache directory from application context
|
|
109
|
+
val cacheDirectory = LiteRTLMInitProvider.applicationContext?.cacheDir?.absolutePath
|
|
110
|
+
Log.i(TAG, "Using cache directory: $cacheDirectory")
|
|
111
|
+
|
|
112
|
+
// Create Engine configuration
|
|
113
|
+
val engineConfig = EngineConfig(
|
|
114
|
+
modelPath = modelPath,
|
|
115
|
+
backend = lmBackend,
|
|
116
|
+
visionBackend = lmVisionBackend,
|
|
117
|
+
audioBackend = lmAudioBackend,
|
|
118
|
+
maxNumTokens = maxTokens,
|
|
119
|
+
cacheDir = cacheDirectory
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Initialize Engine
|
|
123
|
+
engine = Engine(engineConfig).also { it.initialize() }
|
|
124
|
+
Log.i(TAG, "Engine created and initialized successfully")
|
|
125
|
+
|
|
126
|
+
// Create Conversation
|
|
127
|
+
createNewConversation()
|
|
128
|
+
Log.i(TAG, "Conversation created successfully")
|
|
129
|
+
|
|
130
|
+
} catch (e: Exception) {
|
|
131
|
+
Log.e(TAG, "Failed to load model: ${e.message}", e)
|
|
132
|
+
throw RuntimeException("Failed to load model: ${e.message}", e)
|
|
133
|
+
}
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
// -------------------------------------------------------------------------
|
|
132
|
-
// sendMessage -
|
|
138
|
+
// sendMessage - Helper for one-shot generation (internally uses Async)
|
|
133
139
|
// -------------------------------------------------------------------------
|
|
134
|
-
override fun sendMessage(message: String): String {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
140
|
+
override fun sendMessage(message: String): Promise<String> {
|
|
141
|
+
// Implement Promise-based sendMessage using suspend coroutine logic wrapped in Promise
|
|
142
|
+
// Since Promise.parallel expects a blocking block returning T,
|
|
143
|
+
// and sendMessageAsync is callback-based, we need to bridge them.
|
|
144
|
+
// HOWEVER, we can just use the synchronous `sendMessage` API of the SDK
|
|
145
|
+
// inside the `Promise.parallel` block, which moves it off the main thread!
|
|
146
|
+
return Promise.parallel {
|
|
147
|
+
ensureLoaded()
|
|
148
|
+
|
|
149
|
+
// Add user message to history
|
|
150
|
+
history.add(Message(Role.USER, message))
|
|
151
|
+
Log.i(TAG, "sendMessage (Promise): $message")
|
|
152
|
+
|
|
153
|
+
// Blocking inference (safe here because we are in Promise.parallel worker thread)
|
|
154
|
+
val userMsg = LiteRTMessage.of(message)
|
|
155
|
+
val responseMsg = conversation!!.sendMessage(userMsg)
|
|
156
|
+
|
|
157
|
+
// Extract text
|
|
158
|
+
val response = responseMsg.contents
|
|
159
|
+
.filterIsInstance<com.google.ai.edge.litertlm.Content.Text>()
|
|
160
|
+
.joinToString("") { it.text }
|
|
161
|
+
|
|
162
|
+
// Add model response to history
|
|
163
|
+
history.add(Message(Role.MODEL, response))
|
|
164
|
+
|
|
165
|
+
// Update stats
|
|
166
|
+
lastStats = GenerationStats(
|
|
167
|
+
promptTokens = message.length / 4.0,
|
|
168
|
+
completionTokens = response.length / 4.0,
|
|
169
|
+
totalTokens = (message.length + response.length) / 4.0,
|
|
170
|
+
timeToFirstToken = 0.0,
|
|
171
|
+
totalTime = 0.0,
|
|
172
|
+
tokensPerSecond = 0.0
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
response // Return the string
|
|
176
|
+
}
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
// -------------------------------------------------------------------------
|
|
170
180
|
// sendMessageAsync - Streaming inference
|
|
171
181
|
// -------------------------------------------------------------------------
|
|
172
182
|
override fun sendMessageAsync(message: String, onToken: (String, Boolean) -> Unit) {
|
|
183
|
+
// This is already async (void return), so we execute immediately on the calling thread
|
|
184
|
+
// (which is the Nitro specialized thread, not Main).
|
|
185
|
+
// The SDK's sendMessageAsync is non-blocking anyway.
|
|
173
186
|
ensureLoaded()
|
|
174
187
|
|
|
175
188
|
// Add user message to history
|
|
@@ -206,12 +219,8 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
|
|
|
206
219
|
}
|
|
207
220
|
|
|
208
221
|
try {
|
|
209
|
-
// Construct Message object
|
|
210
222
|
val userMsg = LiteRTMessage.of(message)
|
|
211
|
-
|
|
212
|
-
// LiteRT-LM async call - SDK handles threading
|
|
213
223
|
conversation!!.sendMessageAsync(userMsg, listener)
|
|
214
|
-
|
|
215
224
|
} catch (e: Exception) {
|
|
216
225
|
Log.e(TAG, "Failed into initiate async generation", e)
|
|
217
226
|
onToken("Error: ${e.message}", true)
|
|
@@ -221,14 +230,18 @@ class HybridLiteRTLM : HybridLiteRTLMSpec() {
|
|
|
221
230
|
// -------------------------------------------------------------------------
|
|
222
231
|
// Multimodal methods
|
|
223
232
|
// -------------------------------------------------------------------------
|
|
224
|
-
override fun sendMessageWithImage(message: String, imagePath: String): String {
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
override fun sendMessageWithImage(message: String, imagePath: String): Promise<String> {
|
|
234
|
+
return Promise.parallel {
|
|
235
|
+
// TODO: Implement image loading from path
|
|
236
|
+
throw RuntimeException("Multimodal (Image) not yet implemented in this wrapper")
|
|
237
|
+
}
|
|
227
238
|
}
|
|
228
239
|
|
|
229
|
-
override fun sendMessageWithAudio(message: String, audioPath: String): String {
|
|
230
|
-
|
|
231
|
-
|
|
240
|
+
override fun sendMessageWithAudio(message: String, audioPath: String): Promise<String> {
|
|
241
|
+
return Promise.parallel {
|
|
242
|
+
// TODO: Implement audio loading from path
|
|
243
|
+
throw RuntimeException("Multimodal (Audio) not yet implemented in this wrapper")
|
|
244
|
+
}
|
|
232
245
|
}
|
|
233
246
|
|
|
234
247
|
// -------------------------------------------------------------------------
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package com.margelo.nitro.dev.litert.litertlm
|
|
2
|
+
|
|
3
|
+
import java.util.Collections
|
|
4
|
+
import java.util.WeakHashMap
|
|
5
|
+
import android.util.Log
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Global registry to track active LiteRTLM instances.
|
|
9
|
+
* Used for memory trimming and cleanup.
|
|
10
|
+
*/
|
|
11
|
+
object LiteRTLMRegistry {
|
|
12
|
+
private const val TAG = "LiteRTLMRegistry"
|
|
13
|
+
|
|
14
|
+
// Use WeakSet-like structure to prevent leaks
|
|
15
|
+
private val instances = Collections.newSetFromMap(WeakHashMap<HybridLiteRTLM, Boolean>())
|
|
16
|
+
|
|
17
|
+
fun register(instance: HybridLiteRTLM) {
|
|
18
|
+
synchronized(instances) {
|
|
19
|
+
instances.add(instance)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fun onTrimMemory(level: Int) {
|
|
24
|
+
Log.w(TAG, "Received memory warning (level=$level). Releasing resources...")
|
|
25
|
+
synchronized(instances) {
|
|
26
|
+
instances.forEach { it.close() }
|
|
27
|
+
// Note: We don't clear the set here, as close() should be idempotent
|
|
28
|
+
// and the instance might still be ref-counted by JS.
|
|
29
|
+
// We just ensure the HEAVY native resources are gone.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -17,6 +17,20 @@ class LiteRTLMInitProvider : ContentProvider() {
|
|
|
17
17
|
override fun onCreate(): Boolean {
|
|
18
18
|
applicationContext = context?.applicationContext
|
|
19
19
|
Log.i(TAG, "LiteRTLMInitProvider initialized with context: $applicationContext")
|
|
20
|
+
|
|
21
|
+
applicationContext?.registerComponentCallbacks(object : android.content.ComponentCallbacks2 {
|
|
22
|
+
override fun onTrimMemory(level: Int) {
|
|
23
|
+
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
|
24
|
+
com.margelo.nitro.dev.litert.litertlm.LiteRTLMRegistry.onTrimMemory(level)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {}
|
|
29
|
+
override fun onLowMemory() {
|
|
30
|
+
com.margelo.nitro.dev.litert.litertlm.LiteRTLMRegistry.onTrimMemory(android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
20
34
|
return true
|
|
21
35
|
}
|
|
22
36
|
|
|
@@ -108,31 +108,30 @@ export interface LiteRTLM extends HybridObject<{
|
|
|
108
108
|
}> {
|
|
109
109
|
/**
|
|
110
110
|
* Load a .litertlm model file.
|
|
111
|
-
* @param modelPath Absolute path to the .litertlm file.
|
|
112
111
|
* @param config Optional configuration for backend and sampling.
|
|
113
112
|
* @throws Error if the model cannot be loaded.
|
|
114
113
|
*/
|
|
115
|
-
loadModel(modelPath: string, config?: LLMConfig): void
|
|
114
|
+
loadModel(modelPath: string, config?: LLMConfig): Promise<void>;
|
|
116
115
|
/**
|
|
117
116
|
* Send a text message and get the complete response (blocking).
|
|
118
117
|
* @param message User message text.
|
|
119
118
|
* @returns The model's response text.
|
|
120
119
|
*/
|
|
121
|
-
sendMessage(message: string): string
|
|
120
|
+
sendMessage(message: string): Promise<string>;
|
|
122
121
|
/**
|
|
123
122
|
* Send a text message with an image (multimodal).
|
|
124
123
|
* @param message User message text.
|
|
125
124
|
* @param imagePath Absolute path to an image file.
|
|
126
125
|
* @returns The model's response text.
|
|
127
126
|
*/
|
|
128
|
-
sendMessageWithImage(message: string, imagePath: string): string
|
|
127
|
+
sendMessageWithImage(message: string, imagePath: string): Promise<string>;
|
|
129
128
|
/**
|
|
130
129
|
* Send a text message with audio (multimodal).
|
|
131
130
|
* @param message User message text.
|
|
132
131
|
* @param audioPath Absolute path to an audio file (WAV).
|
|
133
132
|
* @returns The model's response text.
|
|
134
133
|
*/
|
|
135
|
-
sendMessageWithAudio(message: string, audioPath: string): string
|
|
134
|
+
sendMessageWithAudio(message: string, audioPath: string): Promise<string>;
|
|
136
135
|
/**
|
|
137
136
|
* Send a message with streaming response.
|
|
138
137
|
* Tokens are delivered via callback as they are generated.
|
|
@@ -18,6 +18,9 @@ namespace margelo::nitro::litertlm { struct LLMConfig; }
|
|
|
18
18
|
// Forward declaration of `Backend` to properly resolve imports.
|
|
19
19
|
namespace margelo::nitro::litertlm { enum class Backend; }
|
|
20
20
|
|
|
21
|
+
#include <NitroModules/Promise.hpp>
|
|
22
|
+
#include <NitroModules/JPromise.hpp>
|
|
23
|
+
#include <NitroModules/JUnit.hpp>
|
|
21
24
|
#include <string>
|
|
22
25
|
#include "Message.hpp"
|
|
23
26
|
#include <vector>
|
|
@@ -74,24 +77,68 @@ namespace margelo::nitro::litertlm {
|
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
// Methods
|
|
77
|
-
void JHybridLiteRTLMSpec::loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) {
|
|
78
|
-
static const auto method = javaClassStatic()->getMethod<
|
|
79
|
-
method(_javaPart, jni::make_jstring(modelPath), config.has_value() ? JLLMConfig::fromCpp(config.value()) : nullptr);
|
|
80
|
+
std::shared_ptr<Promise<void>> JHybridLiteRTLMSpec::loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) {
|
|
81
|
+
static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* modelPath */, jni::alias_ref<JLLMConfig> /* config */)>("loadModel");
|
|
82
|
+
auto __result = method(_javaPart, jni::make_jstring(modelPath), config.has_value() ? JLLMConfig::fromCpp(config.value()) : nullptr);
|
|
83
|
+
return [&]() {
|
|
84
|
+
auto __promise = Promise<void>::create();
|
|
85
|
+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& /* unit */) {
|
|
86
|
+
__promise->resolve();
|
|
87
|
+
});
|
|
88
|
+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
|
|
89
|
+
jni::JniException __jniError(__throwable);
|
|
90
|
+
__promise->reject(std::make_exception_ptr(__jniError));
|
|
91
|
+
});
|
|
92
|
+
return __promise;
|
|
93
|
+
}();
|
|
80
94
|
}
|
|
81
|
-
std::string JHybridLiteRTLMSpec::sendMessage(const std::string& message) {
|
|
82
|
-
static const auto method = javaClassStatic()->getMethod<jni::local_ref<
|
|
95
|
+
std::shared_ptr<Promise<std::string>> JHybridLiteRTLMSpec::sendMessage(const std::string& message) {
|
|
96
|
+
static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* message */)>("sendMessage");
|
|
83
97
|
auto __result = method(_javaPart, jni::make_jstring(message));
|
|
84
|
-
return
|
|
98
|
+
return [&]() {
|
|
99
|
+
auto __promise = Promise<std::string>::create();
|
|
100
|
+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
|
|
101
|
+
auto __result = jni::static_ref_cast<jni::JString>(__boxedResult);
|
|
102
|
+
__promise->resolve(__result->toStdString());
|
|
103
|
+
});
|
|
104
|
+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
|
|
105
|
+
jni::JniException __jniError(__throwable);
|
|
106
|
+
__promise->reject(std::make_exception_ptr(__jniError));
|
|
107
|
+
});
|
|
108
|
+
return __promise;
|
|
109
|
+
}();
|
|
85
110
|
}
|
|
86
|
-
std::string JHybridLiteRTLMSpec::sendMessageWithImage(const std::string& message, const std::string& imagePath) {
|
|
87
|
-
static const auto method = javaClassStatic()->getMethod<jni::local_ref<
|
|
111
|
+
std::shared_ptr<Promise<std::string>> JHybridLiteRTLMSpec::sendMessageWithImage(const std::string& message, const std::string& imagePath) {
|
|
112
|
+
static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* message */, jni::alias_ref<jni::JString> /* imagePath */)>("sendMessageWithImage");
|
|
88
113
|
auto __result = method(_javaPart, jni::make_jstring(message), jni::make_jstring(imagePath));
|
|
89
|
-
return
|
|
114
|
+
return [&]() {
|
|
115
|
+
auto __promise = Promise<std::string>::create();
|
|
116
|
+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
|
|
117
|
+
auto __result = jni::static_ref_cast<jni::JString>(__boxedResult);
|
|
118
|
+
__promise->resolve(__result->toStdString());
|
|
119
|
+
});
|
|
120
|
+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
|
|
121
|
+
jni::JniException __jniError(__throwable);
|
|
122
|
+
__promise->reject(std::make_exception_ptr(__jniError));
|
|
123
|
+
});
|
|
124
|
+
return __promise;
|
|
125
|
+
}();
|
|
90
126
|
}
|
|
91
|
-
std::string JHybridLiteRTLMSpec::sendMessageWithAudio(const std::string& message, const std::string& audioPath) {
|
|
92
|
-
static const auto method = javaClassStatic()->getMethod<jni::local_ref<
|
|
127
|
+
std::shared_ptr<Promise<std::string>> JHybridLiteRTLMSpec::sendMessageWithAudio(const std::string& message, const std::string& audioPath) {
|
|
128
|
+
static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* message */, jni::alias_ref<jni::JString> /* audioPath */)>("sendMessageWithAudio");
|
|
93
129
|
auto __result = method(_javaPart, jni::make_jstring(message), jni::make_jstring(audioPath));
|
|
94
|
-
return
|
|
130
|
+
return [&]() {
|
|
131
|
+
auto __promise = Promise<std::string>::create();
|
|
132
|
+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
|
|
133
|
+
auto __result = jni::static_ref_cast<jni::JString>(__boxedResult);
|
|
134
|
+
__promise->resolve(__result->toStdString());
|
|
135
|
+
});
|
|
136
|
+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
|
|
137
|
+
jni::JniException __jniError(__throwable);
|
|
138
|
+
__promise->reject(std::make_exception_ptr(__jniError));
|
|
139
|
+
});
|
|
140
|
+
return __promise;
|
|
141
|
+
}();
|
|
95
142
|
}
|
|
96
143
|
void JHybridLiteRTLMSpec::sendMessageAsync(const std::string& message, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) {
|
|
97
144
|
static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JString> /* message */, jni::alias_ref<JFunc_void_std__string_bool::javaobject> /* onToken */)>("sendMessageAsync_cxx");
|
|
@@ -55,10 +55,10 @@ namespace margelo::nitro::litertlm {
|
|
|
55
55
|
|
|
56
56
|
public:
|
|
57
57
|
// Methods
|
|
58
|
-
void loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) override;
|
|
59
|
-
std::string sendMessage(const std::string& message) override;
|
|
60
|
-
std::string sendMessageWithImage(const std::string& message, const std::string& imagePath) override;
|
|
61
|
-
std::string sendMessageWithAudio(const std::string& message, const std::string& audioPath) override;
|
|
58
|
+
std::shared_ptr<Promise<void>> loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) override;
|
|
59
|
+
std::shared_ptr<Promise<std::string>> sendMessage(const std::string& message) override;
|
|
60
|
+
std::shared_ptr<Promise<std::string>> sendMessageWithImage(const std::string& message, const std::string& imagePath) override;
|
|
61
|
+
std::shared_ptr<Promise<std::string>> sendMessageWithAudio(const std::string& message, const std::string& audioPath) override;
|
|
62
62
|
void sendMessageAsync(const std::string& message, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) override;
|
|
63
63
|
std::vector<Message> getHistory() override;
|
|
64
64
|
void resetConversation() override;
|
|
@@ -10,6 +10,7 @@ package com.margelo.nitro.dev.litert.litertlm
|
|
|
10
10
|
import androidx.annotation.Keep
|
|
11
11
|
import com.facebook.jni.HybridData
|
|
12
12
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
13
|
+
import com.margelo.nitro.core.Promise
|
|
13
14
|
import com.margelo.nitro.core.HybridObject
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -47,19 +48,19 @@ abstract class HybridLiteRTLMSpec: HybridObject() {
|
|
|
47
48
|
// Methods
|
|
48
49
|
@DoNotStrip
|
|
49
50
|
@Keep
|
|
50
|
-
abstract fun loadModel(modelPath: String, config: LLMConfig?): Unit
|
|
51
|
+
abstract fun loadModel(modelPath: String, config: LLMConfig?): Promise<Unit>
|
|
51
52
|
|
|
52
53
|
@DoNotStrip
|
|
53
54
|
@Keep
|
|
54
|
-
abstract fun sendMessage(message: String): String
|
|
55
|
+
abstract fun sendMessage(message: String): Promise<String>
|
|
55
56
|
|
|
56
57
|
@DoNotStrip
|
|
57
58
|
@Keep
|
|
58
|
-
abstract fun sendMessageWithImage(message: String, imagePath: String): String
|
|
59
|
+
abstract fun sendMessageWithImage(message: String, imagePath: String): Promise<String>
|
|
59
60
|
|
|
60
61
|
@DoNotStrip
|
|
61
62
|
@Keep
|
|
62
|
-
abstract fun sendMessageWithAudio(message: String, audioPath: String): String
|
|
63
|
+
abstract fun sendMessageWithAudio(message: String, audioPath: String): Promise<String>
|
|
63
64
|
|
|
64
65
|
abstract fun sendMessageAsync(message: String, onToken: (token: String, done: Boolean) -> Unit): Unit
|
|
65
66
|
|
|
@@ -20,6 +20,7 @@ namespace margelo::nitro::litertlm { struct Message; }
|
|
|
20
20
|
// Forward declaration of `GenerationStats` to properly resolve imports.
|
|
21
21
|
namespace margelo::nitro::litertlm { struct GenerationStats; }
|
|
22
22
|
|
|
23
|
+
#include <NitroModules/Promise.hpp>
|
|
23
24
|
#include <string>
|
|
24
25
|
#include "LLMConfig.hpp"
|
|
25
26
|
#include <optional>
|
|
@@ -59,10 +60,10 @@ namespace margelo::nitro::litertlm {
|
|
|
59
60
|
|
|
60
61
|
public:
|
|
61
62
|
// Methods
|
|
62
|
-
virtual void loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) = 0;
|
|
63
|
-
virtual std::string sendMessage(const std::string& message) = 0;
|
|
64
|
-
virtual std::string sendMessageWithImage(const std::string& message, const std::string& imagePath) = 0;
|
|
65
|
-
virtual std::string sendMessageWithAudio(const std::string& message, const std::string& audioPath) = 0;
|
|
63
|
+
virtual std::shared_ptr<Promise<void>> loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) = 0;
|
|
64
|
+
virtual std::shared_ptr<Promise<std::string>> sendMessage(const std::string& message) = 0;
|
|
65
|
+
virtual std::shared_ptr<Promise<std::string>> sendMessageWithImage(const std::string& message, const std::string& imagePath) = 0;
|
|
66
|
+
virtual std::shared_ptr<Promise<std::string>> sendMessageWithAudio(const std::string& message, const std::string& audioPath) = 0;
|
|
66
67
|
virtual void sendMessageAsync(const std::string& message, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) = 0;
|
|
67
68
|
virtual std::vector<Message> getHistory() = 0;
|
|
68
69
|
virtual void resetConversation() = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-litert-lm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "High-performance LLM inference for React Native using LiteRT-LM. Optimized for Gemma 3n and other on-device language models.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hugh Chen (https://github.com/hung-yueh)",
|
|
@@ -118,18 +118,17 @@ export interface LiteRTLM extends HybridObject<{
|
|
|
118
118
|
}> {
|
|
119
119
|
/**
|
|
120
120
|
* Load a .litertlm model file.
|
|
121
|
-
* @param modelPath Absolute path to the .litertlm file.
|
|
122
121
|
* @param config Optional configuration for backend and sampling.
|
|
123
122
|
* @throws Error if the model cannot be loaded.
|
|
124
123
|
*/
|
|
125
|
-
loadModel(modelPath: string, config?: LLMConfig): void
|
|
124
|
+
loadModel(modelPath: string, config?: LLMConfig): Promise<void>;
|
|
126
125
|
|
|
127
126
|
/**
|
|
128
127
|
* Send a text message and get the complete response (blocking).
|
|
129
128
|
* @param message User message text.
|
|
130
129
|
* @returns The model's response text.
|
|
131
130
|
*/
|
|
132
|
-
sendMessage(message: string): string
|
|
131
|
+
sendMessage(message: string): Promise<string>;
|
|
133
132
|
|
|
134
133
|
/**
|
|
135
134
|
* Send a text message with an image (multimodal).
|
|
@@ -137,7 +136,7 @@ export interface LiteRTLM extends HybridObject<{
|
|
|
137
136
|
* @param imagePath Absolute path to an image file.
|
|
138
137
|
* @returns The model's response text.
|
|
139
138
|
*/
|
|
140
|
-
sendMessageWithImage(message: string, imagePath: string): string
|
|
139
|
+
sendMessageWithImage(message: string, imagePath: string): Promise<string>;
|
|
141
140
|
|
|
142
141
|
/**
|
|
143
142
|
* Send a text message with audio (multimodal).
|
|
@@ -145,7 +144,7 @@ export interface LiteRTLM extends HybridObject<{
|
|
|
145
144
|
* @param audioPath Absolute path to an audio file (WAV).
|
|
146
145
|
* @returns The model's response text.
|
|
147
146
|
*/
|
|
148
|
-
sendMessageWithAudio(message: string, audioPath: string): string
|
|
147
|
+
sendMessageWithAudio(message: string, audioPath: string): Promise<string>;
|
|
149
148
|
|
|
150
149
|
/**
|
|
151
150
|
* Send a message with streaming response.
|