diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 40811b5..ade8240 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -64,6 +64,10 @@ android { exclude(group = "org.jetbrains", module = "annotations") } } + packaging { + resources.excludes.add("META-INF/INDEX.LIST") + resources.excludes.add("META-INF/io.netty.versions.properties") + } } ksp { @@ -84,6 +88,7 @@ dependencies { implementation(project(":smollm")) implementation(project(":hf-model-hub-api")) + implementation(project(":smollm-server")) // Koin: dependency injection implementation(libs.koin.android) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea94e63..f3b75b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,10 @@ + \ No newline at end of file diff --git a/app/src/main/java/io/shubham0204/smollmandroid/server/LlamaServerService.kt b/app/src/main/java/io/shubham0204/smollmandroid/server/LlamaServerService.kt new file mode 100644 index 0000000..7830eca --- /dev/null +++ b/app/src/main/java/io/shubham0204/smollmandroid/server/LlamaServerService.kt @@ -0,0 +1,28 @@ +package io.shubham0204.smollmandroid.server + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import io.shubham0204.smollm_server.LlamaServer + +class LlamaServerService : Service() { + private val llamaServer = LlamaServer() + + override fun onBind(intent: Intent?): IBinder? { + TODO("Not yet implemented") + } + + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int, + ): Int { + llamaServer.start() + return START_STICKY + } + + override fun onDestroy() { + llamaServer.stop() + super.onDestroy() + } +} diff --git a/app/src/main/java/io/shubham0204/smollmandroid/ui/screens/chat/ChatActivity.kt b/app/src/main/java/io/shubham0204/smollmandroid/ui/screens/chat/ChatActivity.kt index 3b5d887..3807d73 100644 --- a/app/src/main/java/io/shubham0204/smollmandroid/ui/screens/chat/ChatActivity.kt +++ b/app/src/main/java/io/shubham0204/smollmandroid/ui/screens/chat/ChatActivity.kt @@ -100,6 +100,7 @@ import androidx.navigation.compose.rememberNavController import io.shubham0204.smollmandroid.R import io.shubham0204.smollmandroid.data.Chat import io.shubham0204.smollmandroid.data.Task +import io.shubham0204.smollmandroid.server.LlamaServerService import io.shubham0204.smollmandroid.ui.components.AppBarTitleText import io.shubham0204.smollmandroid.ui.components.MediumLabelText import io.shubham0204.smollmandroid.ui.screens.manage_tasks.ManageTasksActivity @@ -143,6 +144,9 @@ class ChatActivity : ComponentActivity() { } } + + startService(Intent(this, LlamaServerService::class.java)) + setContent { val navController = rememberNavController() NavHost( diff --git a/build.gradle.kts b/build.gradle.kts index 0da250c..a11f4df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.android.library) apply false - id("com.google.devtools.ksp") version "2.0.0-1.0.24" apply false + id("com.google.devtools.ksp") version "2.0.21-1.0.28" apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false - kotlin("plugin.serialization") version "2.1.0" apply false + kotlin("plugin.serialization") version "2.1.10" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9a84bd5..6adab9c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,8 @@ koin = "3.5.6" koinAnnotations = "1.3.1" jetbrainsKotlinJvm = "2.0.0" uiTextGoogleFonts = "1.7.7" +appcompat = "1.7.0" +material = "1.12.0" [libraries] @@ -39,6 +41,8 @@ koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", versi koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koinAnnotations" } koin-ksp-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koinAnnotations" } androidx-ui-text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "uiTextGoogleFonts" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] diff --git a/hf-model-hub-api/build.gradle.kts b/hf-model-hub-api/build.gradle.kts index 17f6eb8..ae26015 100644 --- a/hf-model-hub-api/build.gradle.kts +++ b/hf-model-hub-api/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("java-library") alias(libs.plugins.jetbrains.kotlin.jvm) - kotlin("plugin.serialization") version "2.1.0" + kotlin("plugin.serialization") version "2.1.10" } val ktorVersion = "3.0.2" diff --git a/settings.gradle.kts b/settings.gradle.kts index 5fecc55..13c41fd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,8 @@ import java.net.URI +include(":smollm-server") + + include(":hf-model-hub-api") diff --git a/smollm-server/.gitignore b/smollm-server/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/smollm-server/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/smollm-server/build.gradle.kts b/smollm-server/build.gradle.kts new file mode 100644 index 0000000..d2056a0 --- /dev/null +++ b/smollm-server/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + kotlin("plugin.serialization") version "2.1.10" + id("com.google.devtools.ksp") +} + +android { + namespace = "io.shubham0204.smollm_server" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + kotlinOptions { + jvmTarget = "21" + } +} + +val ktorVersion = "3.0.3" + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + + // Ktor server + implementation("io.ktor:ktor-server-core:$ktorVersion") + implementation("io.ktor:ktor-server-netty:$ktorVersion") + + // kotlinx serialization (JSON) and + // content-negotiation + // docs: https://ktor.io/docs/server-serialization.html + implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") + + implementation(project(":smollm")) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/smollm-server/consumer-rules.pro b/smollm-server/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/smollm-server/proguard-rules.pro b/smollm-server/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/smollm-server/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/smollm-server/src/androidTest/java/io/shubham0204/smollm_server/ExampleInstrumentedTest.kt b/smollm-server/src/androidTest/java/io/shubham0204/smollm_server/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..c256160 --- /dev/null +++ b/smollm-server/src/androidTest/java/io/shubham0204/smollm_server/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.shubham0204.smollm_server + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.shubham0204.smollm_server.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/smollm-server/src/main/AndroidManifest.xml b/smollm-server/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/smollm-server/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/smollm-server/src/main/java/io/shubham0204/smollm_server/LlamaServer.kt b/smollm-server/src/main/java/io/shubham0204/smollm_server/LlamaServer.kt new file mode 100644 index 0000000..e72baf7 --- /dev/null +++ b/smollm-server/src/main/java/io/shubham0204/smollm_server/LlamaServer.kt @@ -0,0 +1,78 @@ +package io.shubham0204.smollm_server + +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.install +import io.ktor.server.engine.connector +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.request.receive +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import io.shubham0204.smollm_server.models.OpenAIChatCompletionRequest +import kotlinx.serialization.json.Json + +class LlamaServer { + private val server = + embeddedServer( + factory = Netty, + configure = { + connector { + port = 8080 + } + }, + ) { + setupPlugins() + setupRoutes() + } + + fun start() { + server.start(wait = false) + } + + fun stop() { + server.stop() + } + + private fun Application.setupPlugins() { + // install content-negotiation and + // configure JSON serializer + // docs: https://ktor.io/docs/server-serialization.html#install_plugin + // https://ktor.io/docs/server-serialization.html#register_xml + install(ContentNegotiation) { + json( + Json { + isLenient = true + prettyPrint = true + }, + ) + } + } + + private fun Application.setupRoutes() { + routing { + get { + call.respond(HttpStatusCode.OK, "Hello, world!") + } + route("v1") { + route("chat") { + post("completions") { + val request = call.receive() + } + } + get("/echo") { + val message = + call.queryParameters["message"] ?: call.respond(HttpStatusCode.BadRequest) + call.respond(HttpStatusCode.OK, message) + } + get("/models") { + } + } + } + } +} diff --git a/smollm-server/src/main/java/io/shubham0204/smollm_server/models/OpenAIChatCompletionRequest.kt b/smollm-server/src/main/java/io/shubham0204/smollm_server/models/OpenAIChatCompletionRequest.kt new file mode 100644 index 0000000..7de1342 --- /dev/null +++ b/smollm-server/src/main/java/io/shubham0204/smollm_server/models/OpenAIChatCompletionRequest.kt @@ -0,0 +1,25 @@ +package io.shubham0204.smollm_server.models + +import kotlinx.serialization.Serializable + +/** + * Represents a message in the OpenAI Chat Completion API + * docs: https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages + */ +@Serializable +data class ChatCompletionMessage( + val role: String, + val content: String, +) + +/** + * Represents the request for the OpenAI Chat Completion API + * docs: https://platform.openai.com/docs/api-reference/chat/create + */ +@Serializable +data class OpenAIChatCompletionRequest( + val messages: List, + val model: String, + val topP: Float = 1.0f, + val temperature: Float = 1.0f, +) diff --git a/smollm-server/src/main/java/io/shubham0204/smollm_server/models/OpenAIModel.kt b/smollm-server/src/main/java/io/shubham0204/smollm_server/models/OpenAIModel.kt new file mode 100644 index 0000000..2bc6992 --- /dev/null +++ b/smollm-server/src/main/java/io/shubham0204/smollm_server/models/OpenAIModel.kt @@ -0,0 +1,16 @@ +package io.shubham0204.smollm_server.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * OpenAI Model object, representing a LLM model available for inference + * docs: https://platform.openai.com/docs/api-reference/models/object + */ +@Serializable +data class OpenAIModel( + val id: String, + val created: Long, + @SerialName("object") val object_: String = "model", // `object` is a reserved word in Kotlin + @SerialName("owned_by") val ownedBy: String, +) diff --git a/smollm-server/src/test/java/io/shubham0204/smollm_server/ExampleUnitTest.kt b/smollm-server/src/test/java/io/shubham0204/smollm_server/ExampleUnitTest.kt new file mode 100644 index 0000000..decc876 --- /dev/null +++ b/smollm-server/src/test/java/io/shubham0204/smollm_server/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.shubham0204.smollm_server + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file