Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions sdks/android/nimblenet_ktx/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ android {
sourceSets["main"].java.srcDir("src/gemini/kotlin")
}

// Package androidTest assets into the app APK under test
sourceSets {
getByName("androidTest") {
assets.srcDir("src/androidTest/assets")
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
Expand Down
1 change: 1 addition & 0 deletions sdks/android/nimblenet_ktx/src/androidTest/assets/fl1/f1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TEST FILE - llm/f1
1 change: 1 addition & 0 deletions sdks/android/nimblenet_ktx/src/androidTest/assets/fl1/f2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TEST FILE - llm/f2
1 change: 1 addition & 0 deletions sdks/android/nimblenet_ktx/src/androidTest/assets/fl2/f1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TEST FILE - misc/f1
1 change: 1 addition & 0 deletions sdks/android/nimblenet_ktx/src/androidTest/assets/fl2/f2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TEST FILE - misc/f2
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.app.Application
import android.os.Environment
import androidx.test.core.app.ApplicationProvider
import java.io.File
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
Expand Down Expand Up @@ -121,4 +122,208 @@ class FileUtilsAndroidTest {

destinationFile.delete()
}

@Test
fun copyAssetsAndUpdatePathShouldCopyFiles() {
val assetsJsonStr = """
[
{
"name": "workflow_script",
"version": "1.0.0",
"type": "script",
"location": {
"path": "fl1/f1"
}
},
{
"name": "add_model",
"version": "1.0.0",
"type": "model",
"location": {
"path": "fl1/f2"
}
}
]
""".trimIndent()

val assetsJson = JSONArray(assetsJsonStr)

fileUtils.copyAssetsAndUpdatePath(assetsJson)

// Validate each asset's path is updated to an absolute path and the file exists
for (i in 0 until assetsJson.length()) {
val asset = assetsJson.getJSONObject(i)
val type = asset.getString("type")
if (type.equals("retriever", ignoreCase = true)) continue // Not applicable here
val location = asset.getJSONObject("location")
val updatedPath = location.getString("path")
val file = File(updatedPath)
assertTrue("$type path should be absolute", file.isAbsolute)
assertTrue("$type file should exist", file.exists())
}
}

@Test
fun copyAssetsAndUpdatePathShouldCopyNestedAssets() {
val assetsJsonStr = """
[
{
"name": "workflow_script",
"version": "1.0.0",
"type": "script",
"location": {
"path": "fl1/f1"
}
},
{
"name": "GroceryRAG",
"version": "1.0.0",
"type": "retriever",
"arguments": [
{
"name": "embeddingStoreModel",
"version": "1.0.0",
"type": "model",
"location": {
"path": "fl2/f1"
}
},
{
"name": "groceryItems",
"version": "1.0.0",
"type": "document",
"location": {
"path": "fl2/f2"
}
}
]
}
]
""".trimIndent()

val assetsJson = JSONArray(assetsJsonStr)

fileUtils.copyAssetsAndUpdatePath(assetsJson)

// Validate script asset
val scriptAsset = assetsJson.getJSONObject(0)
val scriptPath = scriptAsset.getJSONObject("location").getString("path")
val scriptFile = File(scriptPath)
assertTrue(scriptFile.isAbsolute)
assertTrue(scriptFile.exists())

// Validate nested assets inside retriever
val retrieverAsset = assetsJson.getJSONObject(1)
val argumentsArray = retrieverAsset.getJSONArray("arguments")
for (i in 0 until argumentsArray.length()) {
val arg = argumentsArray.getJSONObject(i)
val location = arg.getJSONObject("location")
val updatedPath = location.getString("path")
val file = File(updatedPath)
assertTrue(file.isAbsolute)
assertTrue(file.exists())
}
}

@Test
fun copyAssetsAndUpdatePathShouldCopyFolderRecursively() {
val assetsJsonStr = """
[
{
"name": "llama-3",
"version": "1.0.0",
"type": "llm",
"location": {
"path": "fl1"
}
}
]
""".trimIndent()

val assetsJson = JSONArray(assetsJsonStr)

fileUtils.copyAssetsAndUpdatePath(assetsJson)

// Validate LLM folder copied recursively
val llmAsset = assetsJson.getJSONObject(0)
val llmPath = llmAsset.getJSONObject("location").getString("path")
val llmDir = File(llmPath)
assertTrue(llmDir.isAbsolute)
assertTrue(llmDir.exists())
assertTrue(llmDir.isDirectory)

// Check that every file from the original assets/llm folder exists in copied directory
val assetManager = application.assets

fun collectAssetPaths(base: String): List<String> {
val children = assetManager.list(base) ?: return emptyList()
val paths = mutableListOf<String>()
for (child in children) {
val childPath = if (base.isEmpty()) child else "$base/$child"
if ((assetManager.list(childPath)?.isNotEmpty() == true)) {
// directory
paths += collectAssetPaths(childPath).map { "$child/${it}" }
} else {
// file
paths += child
}
}
return paths
}

val expectedFiles = collectAssetPaths("fl1")
assertTrue("LLM assets should not be empty", expectedFiles.isNotEmpty())

expectedFiles.forEach { relativePath ->
val copiedFile = File(llmDir, relativePath)
assertTrue("$relativePath should exist in copied LLM directory", copiedFile.exists())
}
}

@Test
fun copyAssetsAndUpdatePathShouldNotOverwriteExistingFiles() {
val assetsJsonStr = """
[
{
"name": "workflow_script",
"version": "1.0.0",
"type": "script",
"location": {
"path": "fl1/f1"
}
}
]
""".trimIndent()

val firstAssetsJson = JSONArray(assetsJsonStr)

// First copy
fileUtils.copyAssetsAndUpdatePath(firstAssetsJson)

val destPath = firstAssetsJson.getJSONObject(0)
.getJSONObject("location")
.getString("path")

val destFile = File(destPath)
assertTrue(destFile.exists())

val lastModifiedFirst = destFile.lastModified()

// Wait briefly to ensure timestamp would change if file were overwritten
Thread.sleep(500)

// Second copy attempt
val secondAssetsJson = JSONArray(assetsJsonStr)
fileUtils.copyAssetsAndUpdatePath(secondAssetsJson)

val destPathSecond = secondAssetsJson.getJSONObject(0)
.getJSONObject("location")
.getString("path")

val destFileSecond = File(destPathSecond)
val lastModifiedSecond = destFileSecond.lastModified()

// The file should remain unchanged (not overwritten)
assertEquals(lastModifiedFirst, lastModifiedSecond)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package dev.deliteai

import android.app.Application
import dev.deliteai.datamodels.NimbleNetConfig
import dev.deliteai.datamodels.NimbleNetError
import dev.deliteai.datamodels.NimbleNetResult
import dev.deliteai.datamodels.NimbleNetTensor
import dev.deliteai.datamodels.UserEventData
Expand Down Expand Up @@ -73,9 +72,11 @@ import org.json.JSONObject
*/
object NimbleNet {

@Volatile private lateinit var controller: NimbleNetController
@Volatile
private lateinit var controller: NimbleNetController

@Volatile private lateinit var localLogger: LocalLogger
@Volatile
private lateinit var localLogger: LocalLogger

/**
* Initializes the NimbleNet SDK with the provided configuration.
Expand Down Expand Up @@ -161,28 +162,9 @@ object NimbleNet {
controller = container.getNimbleNetController()
localLogger = container.getLocalLogger()

// TODO: Move this logic to NimbleNetController
// Pass deliteAssets only if online flag is false, else pass it on as null
return if (config.online) {
runCatching { controller.initialize(config, null) }
.onFailure(localLogger::e)
.getOrElse { it.toNimbleNetResult() }
} else {
if (assetsJson == null) {
return NimbleNetResult<Unit>(
false,
null,
NimbleNetError(
code = -1,
message =
"deliteAssets cannot be null in case NimbleNetConfig has online flag set to false.",
),
)
}
runCatching { controller.initialize(config, assetsJson) }
.onFailure(localLogger::e)
.getOrElse { it.toNimbleNetResult() }
}
return runCatching { controller.initialize(config, assetsJson) }
.onFailure(localLogger::e)
.getOrElse { it.toNimbleNetResult() }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import android.app.Application
import android.os.SystemClock
import androidx.annotation.VisibleForTesting
import dev.deliteai.datamodels.NimbleNetConfig
import dev.deliteai.datamodels.NimbleNetError
import dev.deliteai.datamodels.NimbleNetResult
import dev.deliteai.datamodels.NimbleNetTensor
import dev.deliteai.datamodels.UserEventData
Expand All @@ -25,10 +24,10 @@ import dev.deliteai.impl.io.FileUtils
import dev.deliteai.impl.loggers.RemoteLogger
import dev.deliteai.impl.moduleInstallers.ModuleInstaller
import dev.deliteai.impl.nativeBridge.CoreRuntime
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.runBlocking
import org.json.JSONArray
import org.json.JSONObject
import java.util.concurrent.atomic.AtomicBoolean

internal class NimbleNetController(
private val application: Application,
Expand All @@ -46,29 +45,29 @@ internal class NimbleNetController(
@Synchronized
fun initialize(config: NimbleNetConfig, assetsJson: JSONArray? = null): NimbleNetResult<Unit> =
runBlocking(deliteAiScope.secondary.coroutineContext) {
if (!config.online && assetsJson == null) throw Exception(
"assetsJson can't be null during offline mode"
)

val result = NimbleNetResult<Unit>(payload = null)
val storageInfo = fileUtils.getInternalStorageFolderSizes()
val sdkDir = fileUtils.getSDKDirPath()

config.setInternalDeviceId(hardwareInfo.getInternalDeviceId())
moduleInstaller.execute()
var modifiedAssetsJson = assetsJson
if (assetsJson != null) {
try {
modifiedAssetsJson = fileUtils.processModules(application, assetsJson)
} catch (e: Exception) {
return@runBlocking NimbleNetResult<Unit>(
false,
null,
NimbleNetError(1, "Loading of modules failed with the error: " + e.message),
)
}
}

//deep copy so that the user's assetsJson doesn't change
val modifiedAssetsJson = assetsJson
?.let { JSONArray(it.toString()) }

//no-op during assetJson == null
fileUtils.copyAssetsAndUpdatePath(modifiedAssetsJson)
Comment thread
void-memories marked this conversation as resolved.

coreRuntime.initializeNimbleNet(
application,
config.toString(),
modifiedAssetsJson,
fileUtils.getSDKDirPath(),
sdkDir,
result,
)

Expand Down
Loading
Loading