From 76aff87037b12a5db9b4a04ff80e563fd84073aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 03:47:09 +0000 Subject: [PATCH 1/3] Initial plan From 0fac2d8adcc1e3bbe24151d3cef2d3b1941bb47f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 04:02:22 +0000 Subject: [PATCH 2/3] feat: add qwen3.5 defaults and chat template kwargs support Co-authored-by: Godzilla675 <131464726+Godzilla675@users.noreply.github.com> --- .../activity/ModelLoadingActivity.kt | 4 ++-- .../com/dark/tool_neuron/engine/GGUFEngine.kt | 16 +++++++++++++++ .../models/engine_schema/GgufEngineSchema.kt | 20 ++++++++++++++++++- .../tool_neuron/repo/ModelRepoDataStore.kt | 10 +++++++++- .../service/ModelDownloadService.kt | 4 ++-- .../ui/screen/ModelConfigEditorScreen.kt | 10 +++++++++- .../viewmodel/ModelConfigEditorViewModel.kt | 8 +++++++- 7 files changed, 64 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/dark/tool_neuron/activity/ModelLoadingActivity.kt b/app/src/main/java/com/dark/tool_neuron/activity/ModelLoadingActivity.kt index 109a9ec9..a70a6582 100644 --- a/app/src/main/java/com/dark/tool_neuron/activity/ModelLoadingActivity.kt +++ b/app/src/main/java/com/dark/tool_neuron/activity/ModelLoadingActivity.kt @@ -235,7 +235,7 @@ fun ModelLoadingScreen( // Create and insert config based on provider type val config = when (model.providerType) { ProviderType.GGUF -> { - val defaultSchema = GgufEngineSchema() + val defaultSchema = GgufEngineSchema.defaultsForModel(model.modelName) ModelConfig( modelId = model.id, modelLoadingParams = defaultSchema.toLoadingJson(), @@ -1060,4 +1060,4 @@ sealed class InstallState { data object Installing : InstallState() data object Installed : InstallState() data class Error(val message: String) : InstallState() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt b/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt index fb0e05ac..50b7182d 100644 --- a/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt +++ b/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt @@ -66,6 +66,7 @@ class GGUFEngine { if (inference.chatTemplate.isNotEmpty()) { nativeLib.nativeSetChatTemplate(inference.chatTemplate) } + setChatTemplateKwargs(inference.chatTemplateKwargs) } success @@ -117,6 +118,7 @@ class GGUFEngine { if (inference.chatTemplate.isNotEmpty()) { nativeLib.nativeSetChatTemplate(inference.chatTemplate) } + setChatTemplateKwargs(inference.chatTemplateKwargs) } success @@ -231,6 +233,20 @@ class GGUFEngine { nativeLib.nativeStopGeneration() } + private fun setChatTemplateKwargs(kwargsJson: String) { + if (kwargsJson.isBlank()) return + try { + val method = nativeLib.javaClass.methods.firstOrNull { + it.name == "nativeSetChatTemplateKwargs" && + it.parameterTypes.size == 1 && + it.parameterTypes[0] == String::class.java + } ?: return + method.invoke(nativeLib, kwargsJson) + } catch (_: Exception) { + // Backward-compatible with native libs that don't expose chat template kwargs + } + } + suspend fun unload() = withContext(Dispatchers.IO) { if (isLoaded) { nativeLib.nativeRelease() diff --git a/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt b/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt index d59d1c97..30f4c792 100644 --- a/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt +++ b/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt @@ -72,6 +72,7 @@ data class GgufInferenceParams( val maxTokens: Int = 4096, val systemPrompt: String = "", val chatTemplate: String = "", + val chatTemplateKwargs: String = "", val toolsJson: String = "" // JSON array of tool definitions ) @@ -109,5 +110,22 @@ data class GgufEngineSchema( return GgufEngineSchema(loading, inference) } + + fun defaultsForModel(modelName: String): GgufEngineSchema { + val base = GgufEngineSchema() + if (!isQwen35Model(modelName)) return base + return base.copy( + inferenceParams = base.inferenceParams.copy( + chatTemplateKwargs = """{"enable_thinking": false}""" + ) + ) + } + + private fun isQwen35Model(modelName: String): Boolean { + val normalized = modelName.lowercase() + return normalized.contains("qwen3.5") || + normalized.contains("qwen-3.5") || + normalized.contains("qwen3_5") + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dark/tool_neuron/repo/ModelRepoDataStore.kt b/app/src/main/java/com/dark/tool_neuron/repo/ModelRepoDataStore.kt index 2f5903c0..7f819684 100644 --- a/app/src/main/java/com/dark/tool_neuron/repo/ModelRepoDataStore.kt +++ b/app/src/main/java/com/dark/tool_neuron/repo/ModelRepoDataStore.kt @@ -40,6 +40,14 @@ class ModelRepositoryDataStore(private val context: Context) { isEnabled = true, category = ModelCategory.GENERAL ), + HFModelRepository( + id = "qwen3_5_4b_instruct", + name = "Qwen3.5 Instruct (4B)", + repoPath = "Qwen/Qwen3.5-4B-Instruct-GGUF", + modelType = ModelType.GGUF, + isEnabled = true, + category = ModelCategory.GENERAL + ), HFModelRepository( id = "liquidai-lfm2-350m", name = "LFM2 350M", @@ -180,4 +188,4 @@ class ModelRepositoryDataStore(private val context: Context) { if (it.id == repo.id) repo else it }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dark/tool_neuron/service/ModelDownloadService.kt b/app/src/main/java/com/dark/tool_neuron/service/ModelDownloadService.kt index 684d9412..747a4a84 100644 --- a/app/src/main/java/com/dark/tool_neuron/service/ModelDownloadService.kt +++ b/app/src/main/java/com/dark/tool_neuron/service/ModelDownloadService.kt @@ -574,7 +574,7 @@ class ModelDownloadService : Service() { } ProviderType.GGUF -> { - val ggufSchema = GgufEngineSchema() + val ggufSchema = GgufEngineSchema.defaultsForModel(modelName) ModelConfig( modelId = checksum, modelLoadingParams = ggufSchema.toLoadingJson(), @@ -671,4 +671,4 @@ class ModelDownloadService : Service() { super.onDestroy() serviceScope.cancel() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dark/tool_neuron/ui/screen/ModelConfigEditorScreen.kt b/app/src/main/java/com/dark/tool_neuron/ui/screen/ModelConfigEditorScreen.kt index f611853d..e697a753 100644 --- a/app/src/main/java/com/dark/tool_neuron/ui/screen/ModelConfigEditorScreen.kt +++ b/app/src/main/java/com/dark/tool_neuron/ui/screen/ModelConfigEditorScreen.kt @@ -463,6 +463,14 @@ private fun GgufConfigEditor(viewModel: ModelConfigEditorViewModel) { multiline = true, minLines = 3 ) + + TextField( + label = "Chat Template Kwargs (Optional JSON)", + value = ggufConfig.inferenceParams.chatTemplateKwargs, + onValueChange = { viewModel.updateGgufChatTemplateKwargs(it) }, + multiline = true, + minLines = 2 + ) } } } @@ -884,4 +892,4 @@ private fun SuccessMessage() { ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dark/tool_neuron/viewmodel/ModelConfigEditorViewModel.kt b/app/src/main/java/com/dark/tool_neuron/viewmodel/ModelConfigEditorViewModel.kt index 59fb9f6f..9f4421c3 100644 --- a/app/src/main/java/com/dark/tool_neuron/viewmodel/ModelConfigEditorViewModel.kt +++ b/app/src/main/java/com/dark/tool_neuron/viewmodel/ModelConfigEditorViewModel.kt @@ -247,6 +247,12 @@ class ModelConfigEditorViewModel @Inject constructor( } } + fun updateGgufChatTemplateKwargs(value: String) { + _ggufConfig.update { + it.copy(inferenceParams = it.inferenceParams.copy(chatTemplateKwargs = value)) + } + } + // ==================== Diffusion Config Updates ==================== fun updateDiffusionEmbeddingSize(value: Int) { @@ -328,4 +334,4 @@ class ModelConfigEditorViewModel @Inject constructor( it.copy(showDiffusionStride = value) } } -} \ No newline at end of file +} From addfe0de78db7c6a3c462fd242c578430852a848 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 04:10:00 +0000 Subject: [PATCH 3/3] refactor: finalize qwen3.5 kwargs integration details Co-authored-by: Godzilla675 <131464726+Godzilla675@users.noreply.github.com> --- .../com/dark/tool_neuron/engine/GGUFEngine.kt | 20 ++++++++++++------- .../models/engine_schema/GgufEngineSchema.kt | 12 ++++++----- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt b/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt index 50b7182d..af99d400 100644 --- a/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt +++ b/app/src/main/java/com/dark/tool_neuron/engine/GGUFEngine.kt @@ -2,6 +2,7 @@ package com.dark.tool_neuron.engine import android.app.ActivityManager import android.content.Context +import android.util.Log import com.dark.tool_neuron.models.table_schema.Model import com.dark.tool_neuron.models.table_schema.ModelConfig import com.dark.tool_neuron.models.engine_schema.DeviceTier @@ -236,13 +237,9 @@ class GGUFEngine { private fun setChatTemplateKwargs(kwargsJson: String) { if (kwargsJson.isBlank()) return try { - val method = nativeLib.javaClass.methods.firstOrNull { - it.name == "nativeSetChatTemplateKwargs" && - it.parameterTypes.size == 1 && - it.parameterTypes[0] == String::class.java - } ?: return - method.invoke(nativeLib, kwargsJson) - } catch (_: Exception) { + setChatTemplateKwargsMethod?.invoke(nativeLib, kwargsJson) + } catch (e: ReflectiveOperationException) { + Log.d(TAG, "Chat template kwargs not supported in this native lib version (expected for older versions): ${e.message}") // Backward-compatible with native libs that don't expose chat template kwargs } } @@ -430,6 +427,15 @@ class GGUFEngine { fun hasToolsEnabled(): Boolean = !currentToolsJson.isNullOrEmpty() companion object { + private const val TAG = "GGUFEngine" + private val setChatTemplateKwargsMethod by lazy { + GGUFNativeLib::class.java.methods.firstOrNull { + it.name == "nativeSetChatTemplateKwargs" && + it.parameterTypes.size == 1 && + it.parameterTypes[0] == String::class.java + } + } + /** * Detect device tier based on available RAM */ diff --git a/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt b/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt index 30f4c792..b3c886a2 100644 --- a/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt +++ b/app/src/main/java/com/dark/tool_neuron/models/engine_schema/GgufEngineSchema.kt @@ -4,6 +4,7 @@ import android.app.ActivityManager import android.content.Context import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import java.util.Locale enum class DeviceTier { LOW_END, // < 4GB RAM @@ -86,6 +87,9 @@ data class GgufEngineSchema( fun toInferenceJson(): String = json.encodeToString(inferenceParams) companion object { + private const val QWEN_THINKING_DISABLED_KWARGS = """{"enable_thinking": false}""" + // Match common Qwen 3.5 naming styles used across local files and HF repos. + private val qwen35ModelMarkers = listOf("qwen3.5", "qwen-3.5", "qwen3_5") private val json = Json { ignoreUnknownKeys = true encodeDefaults = true @@ -116,16 +120,14 @@ data class GgufEngineSchema( if (!isQwen35Model(modelName)) return base return base.copy( inferenceParams = base.inferenceParams.copy( - chatTemplateKwargs = """{"enable_thinking": false}""" + chatTemplateKwargs = QWEN_THINKING_DISABLED_KWARGS ) ) } private fun isQwen35Model(modelName: String): Boolean { - val normalized = modelName.lowercase() - return normalized.contains("qwen3.5") || - normalized.contains("qwen-3.5") || - normalized.contains("qwen3_5") + val normalized = modelName.lowercase(Locale.ROOT) + return qwen35ModelMarkers.any { normalized.contains(it) } } } }