mirror of
https://github.com/jie65535/JChatGPT.git
synced 2026-09-15 02:56:10 +08:00
llm: sanitize malformed request Unicode
This commit is contained in:
@@ -166,7 +166,9 @@ class ModelService(
|
||||
extraBody?.forEach { (key, value) ->
|
||||
requestJson[key] = value
|
||||
}
|
||||
return JsonObject(requestJson).toString()
|
||||
// Ktor's text writer rejects lone UTF-16 surrogates. They can enter chat history through
|
||||
// malformed gateway text, so normalize only invalid code units before encoding the body.
|
||||
return JsonObject(requestJson).toString().replaceUnpairedSurrogates()
|
||||
}
|
||||
|
||||
internal fun decodeStreamChunk(rawJson: String): ChatCompletionChunk? {
|
||||
@@ -237,6 +239,31 @@ class ModelService(
|
||||
if (normalized.length <= maxChars) normalized else normalized.take(maxChars) + "...[截断]"
|
||||
}
|
||||
|
||||
private fun String.replaceUnpairedSurrogates(): String {
|
||||
var output: StringBuilder? = null
|
||||
var index = 0
|
||||
while (index < length) {
|
||||
val current = this[index]
|
||||
when {
|
||||
Character.isHighSurrogate(current) &&
|
||||
index + 1 < length && Character.isLowSurrogate(this[index + 1]) -> {
|
||||
output?.append(current)?.append(this[index + 1])
|
||||
index += 2
|
||||
}
|
||||
Character.isSurrogate(current) -> {
|
||||
if (output == null) output = StringBuilder(length).append(this, 0, index)
|
||||
output.append('\uFFFD')
|
||||
index++
|
||||
}
|
||||
else -> {
|
||||
output?.append(current)
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
return output?.toString() ?: this
|
||||
}
|
||||
|
||||
private fun isSafetyRejection(code: String?, message: String?): Boolean {
|
||||
val normalizedCode = code.orEmpty().lowercase()
|
||||
val normalizedMessage = message.orEmpty().lowercase()
|
||||
|
||||
@@ -22,6 +22,7 @@ import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Protocol
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
@@ -250,6 +251,48 @@ class ModelServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sendsRequestsContainingUnpairedSurrogates() = runBlocking {
|
||||
val server = HttpServer.create(InetSocketAddress("127.0.0.1", 0), 0)
|
||||
val receivedBody = AtomicReference<String>()
|
||||
val responseBody = listOf(
|
||||
"""data: {"id":"chunk-1","object":"chat.completion.chunk","created":1,"model":"test-model","choices":[{"index":0,"delta":{"content":"ok"},"finish_reason":null}]}""",
|
||||
"data: [DONE]",
|
||||
"",
|
||||
).joinToString("\n\n").toByteArray()
|
||||
server.createContext("/v1/chat/completions") { exchange ->
|
||||
receivedBody.set(String(exchange.requestBody.readBytes(), Charsets.UTF_8))
|
||||
exchange.responseHeaders.add("Content-Type", "text/event-stream")
|
||||
exchange.sendResponseHeaders(200, responseBody.size.toLong())
|
||||
exchange.responseBody.use { body -> body.write(responseBody) }
|
||||
}
|
||||
server.start()
|
||||
|
||||
val localService = ModelService(
|
||||
baseUrl = "http://127.0.0.1:${server.address.port}/v1/",
|
||||
token = "test",
|
||||
timeout = 1.seconds,
|
||||
firstChunkTimeout = 1.seconds,
|
||||
)
|
||||
try {
|
||||
val content = StringBuilder()
|
||||
localService.chatCompletions(
|
||||
ChatCompletionRequest(
|
||||
model = ModelId("test-model"),
|
||||
messages = listOf(ChatMessage.User("emoji \uD83D\uDE00 before\uD800middle\uDC00after")),
|
||||
)
|
||||
).collect { chunk ->
|
||||
chunk.choices.firstOrNull()?.delta?.content?.let(content::append)
|
||||
}
|
||||
|
||||
assertEquals("ok", content.toString())
|
||||
assertTrue(receivedBody.get().contains("emoji \uD83D\uDE00 before\uFFFDmiddle\uFFFDafter"))
|
||||
} finally {
|
||||
localService.httpClient.close()
|
||||
server.stop(0)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun queuesFlowsBeforeStartingWork() = runBlocking {
|
||||
val active = AtomicInteger()
|
||||
|
||||
Reference in New Issue
Block a user