blaze-android-app / LLMRouter.kt
MR-CODESPIKE's picture
Upload folder using huggingface_hub
38cf36c verified
Raw
History Blame Contribute Delete
5.46 kB
package com.blaze.agent.ai
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject
import java.io.IOException
import java.util.concurrent.TimeUnit
class LLMRouter(
private val context: Context,
private val geminiApiKey: String,
private val bonsaiServerUrl: String = "http://127.0.0.1:8080"
) {
companion object {
private const val TAG = "BlazeLLMRouter"
private const val GEMINI_MODEL = "gemini-2.0-flash"
private const val GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models"
}
private val httpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
suspend fun complete(prompt: String): String = withContext(Dispatchers.IO) {
if (isOnline()) { Log.d(TAG, "→ Gemini"); geminiComplete(prompt) }
else { Log.d(TAG, "→ Bonsai"); bonsaiComplete(prompt) }
}
suspend fun completeWithImage(prompt: String, imageBytes: ByteArray): String = withContext(Dispatchers.IO) {
if (isOnline()) { Log.d(TAG, "→ Gemini Vision"); geminiCompleteWithImage(prompt, imageBytes) }
else { Log.d(TAG, "→ Bonsai (text-only fallback)"); bonsaiComplete(prompt) }
}
fun isOnline(): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val caps = cm.getNetworkCapabilities(cm.activeNetwork ?: return false) ?: return false
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}
private fun geminiComplete(prompt: String): String {
val body = JSONObject().apply {
put("contents", JSONArray().apply {
put(JSONObject().apply {
put("role", "user")
put("parts", JSONArray().apply { put(JSONObject().apply { put("text", prompt) }) })
})
})
put("generationConfig", JSONObject().apply { put("temperature", 0.3); put("maxOutputTokens", 1024) })
}
val request = Request.Builder()
.url("$GEMINI_BASE_URL/$GEMINI_MODEL:generateContent?key=$geminiApiKey")
.post(body.toString().toRequestBody("application/json".toMediaType())).build()
return executeRequest(request) { r ->
JSONObject(r).getJSONArray("candidates").getJSONObject(0)
.getJSONObject("content").getJSONArray("parts").getJSONObject(0).getString("text")
}
}
private fun geminiCompleteWithImage(prompt: String, imageBytes: ByteArray): String {
val b64 = android.util.Base64.encodeToString(imageBytes, android.util.Base64.NO_WRAP)
val body = JSONObject().apply {
put("contents", JSONArray().apply {
put(JSONObject().apply {
put("role", "user")
put("parts", JSONArray().apply {
put(JSONObject().apply { put("inlineData", JSONObject().apply { put("mimeType", "image/png"); put("data", b64) }) })
put(JSONObject().apply { put("text", prompt) })
})
})
})
}
val request = Request.Builder()
.url("$GEMINI_BASE_URL/$GEMINI_MODEL:generateContent?key=$geminiApiKey")
.post(body.toString().toRequestBody("application/json".toMediaType())).build()
return executeRequest(request) { r ->
JSONObject(r).getJSONArray("candidates").getJSONObject(0)
.getJSONObject("content").getJSONArray("parts").getJSONObject(0).getString("text")
}
}
private fun bonsaiComplete(prompt: String): String {
val body = JSONObject().apply {
put("model", "bonsai")
put("messages", JSONArray().apply {
put(JSONObject().apply { put("role", "system"); put("content", "You are Blaze, an AI agent that controls Android phones. Be concise and precise.") })
put(JSONObject().apply { put("role", "user"); put("content", prompt) })
})
put("temperature", 0.3); put("max_tokens", 512); put("stream", false)
}
val request = Request.Builder()
.url("$bonsaiServerUrl/v1/chat/completions")
.post(body.toString().toRequestBody("application/json".toMediaType())).build()
return executeRequest(request) { r ->
JSONObject(r).getJSONArray("choices").getJSONObject(0).getJSONObject("message").getString("content")
}
}
private fun executeRequest(request: Request, parse: (String) -> String): String {
return try {
httpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("HTTP ${response.code}: ${response.body?.string()}")
parse(response.body?.string() ?: throw IOException("Empty response"))
}
} catch (e: Exception) { Log.e(TAG, "LLM request failed", e); throw e }
}
}