| package com.blaze.agent.task |
|
|
| import android.util.Log |
| import com.blaze.agent.ai.LLMRouter |
| import org.json.JSONObject |
|
|
| class TaskClassifier(private val llmRouter: LLMRouter) { |
|
|
| companion object { private const val TAG = "BlazeTaskClassifier" } |
|
|
| suspend fun classify(userCommand: String): TaskClassification { |
| val prompt = buildPrompt(userCommand) |
| return try { |
| val raw = llmRouter.complete(prompt) |
| parseResponse(raw) |
| } catch (e: Exception) { |
| Log.e(TAG, "Classification failed, defaulting to ONE_SHOT", e) |
| TaskClassification(taskType = TaskType.ONE_SHOT) |
| } |
| } |
|
|
| private fun buildPrompt(command: String): String = """ |
| You are a task router for an Android AI agent. |
| |
| Analyze the user command below and classify it as either: |
| - ONE_SHOT: execute once and done. (e.g. "Send WhatsApp to Mom", "Open YouTube") |
| - MONITORING: keep running in the background, watching/waiting for something. |
| (e.g. "Watch my Instagram DMs and reply to business messages", |
| "Alert me when my Uber arrives", |
| "Keep checking my email for a reply from John") |
| |
| For MONITORING tasks, also determine: |
| - triggerCondition: what specifically to watch for on screen |
| - actionToExecute: what to do when the condition is met |
| - isRecurring: true if the task should keep monitoring after each action |
| - checkIntervalSeconds: how often to check (minimum 15, maximum 300 seconds) |
| |
| User command: "$command" |
| |
| Respond ONLY with valid JSON: |
| { |
| "taskType": "ONE_SHOT" or "MONITORING", |
| "triggerCondition": "what to watch for, or null", |
| "actionToExecute": "what to do when triggered, or null", |
| "isRecurring": true or false, |
| "checkIntervalSeconds": number, |
| "reasoning": "one sentence" |
| } |
| """.trimIndent() |
|
|
| private fun parseResponse(raw: String): TaskClassification { |
| return try { |
| val json = raw.trim().removePrefix("```json").removePrefix("```").removeSuffix("```").trim() |
| val obj = JSONObject(json) |
| val taskType = when (obj.getString("taskType")) { |
| "MONITORING" -> TaskType.MONITORING |
| else -> TaskType.ONE_SHOT |
| } |
| TaskClassification( |
| taskType = taskType, |
| triggerCondition = obj.optString("triggerCondition").takeIf { it != "null" && it.isNotBlank() }, |
| actionToExecute = obj.optString("actionToExecute").takeIf { it != "null" && it.isNotBlank() }, |
| isRecurring = obj.optBoolean("isRecurring", false), |
| checkIntervalMs = (obj.optInt("checkIntervalSeconds", 30).coerceIn(15, 300)) * 1000L, |
| reasoning = obj.optString("reasoning", "") |
| ) |
| } catch (e: Exception) { |
| Log.e(TAG, "Failed to parse classification JSON: $raw", e) |
| TaskClassification(taskType = TaskType.ONE_SHOT) |
| } |
| } |
| } |
|
|