diff --git a/core/src/main/kotlin/cc/unitmesh/devti/llm2/model/LlmConfig.kt b/core/src/main/kotlin/cc/unitmesh/devti/llm2/model/LlmConfig.kt index 08a4f09547..cae181c96c 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/llm2/model/LlmConfig.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/llm2/model/LlmConfig.kt @@ -47,6 +47,9 @@ data class CustomRequest( when { streamValue.booleanOrNull != null -> streamValue.boolean streamValue.isString -> streamValue.content.toBoolean() + // Handle numeric values: 0 = false, 1 (or any other number) = true + streamValue.intOrNull != null -> streamValue.int != 0 + streamValue.longOrNull != null -> streamValue.long != 0L else -> true } } diff --git a/core/src/main/kotlin/cc/unitmesh/devti/settings/dialog/LLMDialog.kt b/core/src/main/kotlin/cc/unitmesh/devti/settings/dialog/LLMDialog.kt index dfc8b9f015..b06f6379fd 100644 --- a/core/src/main/kotlin/cc/unitmesh/devti/settings/dialog/LLMDialog.kt +++ b/core/src/main/kotlin/cc/unitmesh/devti/settings/dialog/LLMDialog.kt @@ -115,8 +115,9 @@ class LLMDialog( headersField.text = headersJson // Body without model and temperature (they are now explicit fields) + // Also keep stream if it was explicitly set in body (to support numeric values like 0/1) val bodyWithoutModelTemp = existingLlm.customRequest.body.filterKeys { - it != "model" && it != "temperature" && it != "stream" + it != "model" && it != "temperature" } val bodyJson = if (bodyWithoutModelTemp.isNotEmpty()) { buildJsonObject { @@ -265,23 +266,41 @@ class LLMDialog( } // Combine explicit parameters with additional body + // If stream is explicitly set in additional body, use that value val body = mutableMapOf().apply { put("model", JsonPrimitive(modelField.text)) put("temperature", JsonPrimitive(temperature)) + // Use checkbox value as default put("stream", JsonPrimitive(streamCheckbox.isSelected)) + // Additional body can override stream if explicitly set putAll(additionalBody) } + // Determine actual stream value (from body if set, otherwise from checkbox) + val actualStream = when (val streamValue = body["stream"]) { + is JsonPrimitive -> { + when { + streamValue.booleanOrNull != null -> streamValue.boolean + streamValue.isString -> streamValue.content.toBoolean() + // Handle numeric values: 0 = false, any other number = true + streamValue.intOrNull != null -> streamValue.int != 0 + streamValue.longOrNull != null -> streamValue.long != 0L + else -> streamCheckbox.isSelected + } + } + else -> streamCheckbox.isSelected + } + // Create a temporary LLM config for testing val customRequest = CustomRequest( headers = headers, body = body, - stream = streamCheckbox.isSelected + stream = actualStream ) // Get response resolver, use default if empty val responseResolver = responseResolverField.text.trim().ifEmpty { - if (streamCheckbox.isSelected) { + if (actualStream) { "\$.choices[0].delta.content" } else { "\$.choices[0].message.content" @@ -382,13 +401,31 @@ class LLMDialog( } // Combine explicit parameters with additional body + // If stream is explicitly set in additional body, use that value val body = mutableMapOf().apply { put("model", JsonPrimitive(modelField.text)) put("temperature", JsonPrimitive(temperature)) + // Use checkbox value as default put("stream", JsonPrimitive(streamCheckbox.isSelected)) + // Additional body can override stream if explicitly set putAll(additionalBody) } + // Determine actual stream value (from body if set, otherwise from checkbox) + val actualStream = when (val streamValue = body["stream"]) { + is JsonPrimitive -> { + when { + streamValue.booleanOrNull != null -> streamValue.boolean + streamValue.isString -> streamValue.content.toBoolean() + // Handle numeric values: 0 = false, any other number = true + streamValue.intOrNull != null -> streamValue.int != 0 + streamValue.longOrNull != null -> streamValue.long != 0L + else -> streamCheckbox.isSelected + } + } + else -> streamCheckbox.isSelected + } + // Get existing LLMs val existingLlms = try { LlmConfig.load().toMutableList() @@ -405,12 +442,12 @@ class LLMDialog( val customRequest = CustomRequest( headers = headers, body = body, - stream = streamCheckbox.isSelected + stream = actualStream ) // Get response resolver, use default if empty val responseResolver = responseResolverField.text.trim().ifEmpty { - if (streamCheckbox.isSelected) { + if (actualStream) { "\$.choices[0].delta.content" } else { "\$.choices[0].message.content" diff --git a/core/src/test/kotlin/cc/unitmesh/devti/llm2/model/CustomRequestTest.kt b/core/src/test/kotlin/cc/unitmesh/devti/llm2/model/CustomRequestTest.kt new file mode 100644 index 0000000000..b7b659f3d6 --- /dev/null +++ b/core/src/test/kotlin/cc/unitmesh/devti/llm2/model/CustomRequestTest.kt @@ -0,0 +1,80 @@ +package cc.unitmesh.devti.llm2.model + +import kotlinx.serialization.json.JsonPrimitive +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class CustomRequestTest { + + @Test + fun `should parse boolean stream value true`() { + val requestFormat = """{"customFields": {"model": "test-model", "stream": true}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertTrue(result.stream) + assertEquals(JsonPrimitive(true), result.body["stream"]) + } + + @Test + fun `should parse boolean stream value false`() { + val requestFormat = """{"customFields": {"model": "test-model", "stream": false}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertFalse(result.stream) + assertEquals(JsonPrimitive(false), result.body["stream"]) + } + + @Test + fun `should parse numeric stream value 1 as true`() { + val requestFormat = """{"customFields": {"model": "test-model", "stream": 1}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertTrue(result.stream) + assertEquals(JsonPrimitive(1), result.body["stream"]) + } + + @Test + fun `should parse numeric stream value 0 as false`() { + val requestFormat = """{"customFields": {"model": "test-model", "stream": 0}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertFalse(result.stream) + assertEquals(JsonPrimitive(0), result.body["stream"]) + } + + @Test + fun `should parse string stream value true`() { + val requestFormat = """{"customFields": {"model": "test-model", "stream": "true"}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertTrue(result.stream) + assertEquals(JsonPrimitive("true"), result.body["stream"]) + } + + @Test + fun `should parse string stream value false`() { + val requestFormat = """{"customFields": {"model": "test-model", "stream": "false"}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertFalse(result.stream) + assertEquals(JsonPrimitive("false"), result.body["stream"]) + } + + @Test + fun `should default to true when stream is missing`() { + val requestFormat = """{"customFields": {"model": "test-model"}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertTrue(result.stream) + } + + @Test + fun `should parse custom headers correctly`() { + val requestFormat = """{"customHeaders": {"X-Custom": "value"}, "customFields": {"model": "test"}}""" + val result = CustomRequest.fromLegacyFormat(requestFormat) + + assertEquals("value", result.headers["X-Custom"]) + } +}