mirror of
https://github.com/jie65535/JChatGPT.git
synced 2026-09-15 02:56:10 +08:00
Add first-chunk timeout
This commit is contained in:
@@ -14,6 +14,7 @@ import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlin.time.Duration
|
||||
|
||||
@@ -21,15 +22,16 @@ class ModelService(
|
||||
val baseUrl: String,
|
||||
val token: String,
|
||||
val timeout: Duration,
|
||||
val firstChunkTimeout: Duration,
|
||||
val extraBody: JsonObject? = null
|
||||
) {
|
||||
val httpClient: HttpClient by lazy {
|
||||
HttpClient(OkHttp) {
|
||||
install(HttpTimeout) {
|
||||
val millis = timeout.inWholeMilliseconds
|
||||
requestTimeoutMillis = millis
|
||||
connectTimeoutMillis = millis
|
||||
socketTimeoutMillis = millis
|
||||
// 总请求/socket 超时保持长值,允许慢速流式输出;连接握手则用短超时。
|
||||
requestTimeoutMillis = timeout.inWholeMilliseconds
|
||||
socketTimeoutMillis = timeout.inWholeMilliseconds
|
||||
connectTimeoutMillis = firstChunkTimeout.inWholeMilliseconds
|
||||
}
|
||||
defaultRequest {
|
||||
url(baseUrl)
|
||||
@@ -66,17 +68,35 @@ class ModelService(
|
||||
}.let { response ->
|
||||
val channel: ByteReadChannel = response.body()
|
||||
try {
|
||||
while (currentCoroutineContext().isActive && !channel.isClosedForRead) {
|
||||
val line = channel.readUTF8Line() ?: continue
|
||||
when {
|
||||
line.startsWith("data: [DONE]") -> break
|
||||
line.startsWith("data: ") -> {
|
||||
val chunk = json.decodeFromString<ChatCompletionChunk>(
|
||||
line.removePrefix("data: ")
|
||||
)
|
||||
emit(chunk)
|
||||
// 首块 data: 必须在 firstChunkTimeout 内到达,否则抛 TimeoutCancellationException
|
||||
// 走 JChatGPT 的重试流程;之后的流式读取不再有应用层超时,由 socketTimeoutMillis 兜底。
|
||||
val firstDataLine: String? = withTimeout(firstChunkTimeout) {
|
||||
var found: String? = null
|
||||
while (currentCoroutineContext().isActive && !channel.isClosedForRead) {
|
||||
val line = channel.readUTF8Line() ?: continue
|
||||
if (line.startsWith("data: ")) {
|
||||
found = line
|
||||
break
|
||||
}
|
||||
// 心跳/空行/注释行,不计为首块,继续等
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
if (firstDataLine != null) {
|
||||
if (!firstDataLine.startsWith("data: [DONE]")) {
|
||||
emit(json.decodeFromString(firstDataLine.removePrefix("data: ")))
|
||||
|
||||
while (currentCoroutineContext().isActive && !channel.isClosedForRead) {
|
||||
val line = channel.readUTF8Line() ?: continue
|
||||
when {
|
||||
line.startsWith("data: [DONE]") -> break
|
||||
line.startsWith("data: ") -> {
|
||||
emit(json.decodeFromString(line.removePrefix("data: ")))
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user