mirror of
https://github.com/jie65535/JChatGPT.git
synced 2026-06-23 00:49:31 +08:00
Add ModelService with extra body support
This commit is contained in:
@@ -25,7 +25,7 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
val openaiClientVersion = "4.0.1"
|
||||
val openaiClientVersion = "4.1.0"
|
||||
val ktorVersion = "3.0.3"
|
||||
val jLatexMathVersion = "1.0.7"
|
||||
val commonTextVersion = "1.13.0"
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package top.jie65535.mirai
|
||||
|
||||
import com.aallam.openai.api.http.Timeout
|
||||
import com.aallam.openai.client.Chat
|
||||
import com.aallam.openai.client.OpenAI
|
||||
import com.aallam.openai.client.OpenAIHost
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
object LargeLanguageModels {
|
||||
|
||||
|
||||
/**
|
||||
* 系统提示词
|
||||
*/
|
||||
@@ -18,46 +16,62 @@ object LargeLanguageModels {
|
||||
/**
|
||||
* 聊天助手
|
||||
*/
|
||||
var chat: Chat? = null
|
||||
var chat: ModelService? = null
|
||||
|
||||
/**
|
||||
* 推理模型
|
||||
*/
|
||||
var reasoning: Chat? = null
|
||||
var reasoning: ModelService? = null
|
||||
|
||||
/**
|
||||
* 视觉模型
|
||||
*/
|
||||
var visual: Chat? = null
|
||||
var visual: ModelService? = null
|
||||
|
||||
private val json = Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
private fun parseExtraBody(raw: String): JsonObject? {
|
||||
if (raw.isBlank()) return null
|
||||
return try {
|
||||
json.parseToJsonElement(raw).jsonObject
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun reload() {
|
||||
// 载入超时时间
|
||||
val timeout = PluginConfig.timeout.milliseconds
|
||||
|
||||
// 初始化聊天模型
|
||||
if (PluginConfig.openAiApi.isNotBlank() && PluginConfig.openAiToken.isNotBlank()) {
|
||||
chat = OpenAI(
|
||||
chat = ModelService(
|
||||
baseUrl = PluginConfig.openAiApi,
|
||||
token = PluginConfig.openAiToken,
|
||||
host = OpenAIHost(baseUrl = PluginConfig.openAiApi),
|
||||
timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
|
||||
timeout = timeout,
|
||||
extraBody = parseExtraBody(PluginConfig.chatModelExtraBody)
|
||||
)
|
||||
}
|
||||
|
||||
// 初始化推理模型
|
||||
if (PluginConfig.reasoningModelApi.isNotBlank() && PluginConfig.reasoningModelToken.isNotBlank()) {
|
||||
reasoning = OpenAI(
|
||||
reasoning = ModelService(
|
||||
baseUrl = PluginConfig.reasoningModelApi,
|
||||
token = PluginConfig.reasoningModelToken,
|
||||
host = OpenAIHost(baseUrl = PluginConfig.reasoningModelApi),
|
||||
timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
|
||||
timeout = timeout,
|
||||
extraBody = parseExtraBody(PluginConfig.reasoningModelExtraBody)
|
||||
)
|
||||
}
|
||||
|
||||
// 初始化视觉模型
|
||||
if (PluginConfig.visualModelApi.isNotBlank() && PluginConfig.visualModelToken.isNotBlank()) {
|
||||
visual = OpenAI(
|
||||
visual = ModelService(
|
||||
baseUrl = PluginConfig.visualModelApi,
|
||||
token = PluginConfig.visualModelToken,
|
||||
host = OpenAIHost(baseUrl = PluginConfig.visualModelApi),
|
||||
timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
|
||||
timeout = timeout,
|
||||
extraBody = parseExtraBody(PluginConfig.visualModelExtraBody)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,4 +92,4 @@ object LargeLanguageModels {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
src/main/kotlin/ModelService.kt
Normal file
88
src/main/kotlin/ModelService.kt
Normal file
@@ -0,0 +1,88 @@
|
||||
package top.jie65535.mirai
|
||||
|
||||
import com.aallam.openai.api.chat.ChatCompletionChunk
|
||||
import com.aallam.openai.api.chat.ChatCompletionRequest
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.engine.okhttp.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.utils.io.*
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlin.time.Duration
|
||||
|
||||
class ModelService(
|
||||
val baseUrl: String,
|
||||
val token: String,
|
||||
val timeout: Duration,
|
||||
val extraBody: JsonObject? = null
|
||||
) {
|
||||
val httpClient: HttpClient by lazy {
|
||||
HttpClient(OkHttp) {
|
||||
install(HttpTimeout) {
|
||||
val millis = timeout.inWholeMilliseconds
|
||||
requestTimeoutMillis = millis
|
||||
connectTimeoutMillis = millis
|
||||
socketTimeoutMillis = millis
|
||||
}
|
||||
defaultRequest {
|
||||
url(baseUrl)
|
||||
bearerAuth(token)
|
||||
}
|
||||
expectSuccess = true
|
||||
}
|
||||
}
|
||||
|
||||
private val json = Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
fun chatCompletions(request: ChatCompletionRequest): Flow<ChatCompletionChunk> {
|
||||
val requestJson = json.encodeToJsonElement(ChatCompletionRequest.serializer(), request)
|
||||
.jsonObject.toMutableMap()
|
||||
requestJson["stream"] = JsonPrimitive(true)
|
||||
extraBody?.forEach { (key, value) ->
|
||||
requestJson[key] = value
|
||||
}
|
||||
val body = JsonObject(requestJson).toString()
|
||||
|
||||
return flow {
|
||||
httpClient.post("chat/completions") {
|
||||
setBody(body)
|
||||
contentType(ContentType.Application.Json)
|
||||
accept(ContentType.Text.EventStream)
|
||||
headers {
|
||||
append(HttpHeaders.CacheControl, "no-cache")
|
||||
append(HttpHeaders.Connection, "keep-alive")
|
||||
}
|
||||
}.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)
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
channel.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,15 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
||||
@ValueDescription("视觉模型")
|
||||
var visualModel: String by value("qwen-vl-plus")
|
||||
|
||||
@ValueDescription("聊天模型额外请求体JSON,会合并到请求体中。例如DeepSeek关闭思维: {\"thinking\": {\"type\": \"disabled\"}}")
|
||||
val chatModelExtraBody: String by value("")
|
||||
|
||||
@ValueDescription("推理模型额外请求体JSON,会合并到请求体中。例如DeepSeek启用思维: {\"thinking\": {\"type\": \"enabled\"}}")
|
||||
val reasoningModelExtraBody: String by value("")
|
||||
|
||||
@ValueDescription("视觉模型额外请求体JSON,会合并到请求体中。")
|
||||
val visualModelExtraBody: String by value("")
|
||||
|
||||
@ValueDescription("百炼平台API KEY")
|
||||
val dashScopeApiKey: String by value("")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user