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()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
val openaiClientVersion = "4.0.1"
|
val openaiClientVersion = "4.1.0"
|
||||||
val ktorVersion = "3.0.3"
|
val ktorVersion = "3.0.3"
|
||||||
val jLatexMathVersion = "1.0.7"
|
val jLatexMathVersion = "1.0.7"
|
||||||
val commonTextVersion = "1.13.0"
|
val commonTextVersion = "1.13.0"
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package top.jie65535.mirai
|
package top.jie65535.mirai
|
||||||
|
|
||||||
import com.aallam.openai.api.http.Timeout
|
import kotlinx.serialization.json.Json
|
||||||
import com.aallam.openai.client.Chat
|
import kotlinx.serialization.json.JsonObject
|
||||||
import com.aallam.openai.client.OpenAI
|
import kotlinx.serialization.json.jsonObject
|
||||||
import com.aallam.openai.client.OpenAIHost
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
object LargeLanguageModels {
|
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() {
|
fun reload() {
|
||||||
// 载入超时时间
|
|
||||||
val timeout = PluginConfig.timeout.milliseconds
|
val timeout = PluginConfig.timeout.milliseconds
|
||||||
|
|
||||||
// 初始化聊天模型
|
// 初始化聊天模型
|
||||||
if (PluginConfig.openAiApi.isNotBlank() && PluginConfig.openAiToken.isNotBlank()) {
|
if (PluginConfig.openAiApi.isNotBlank() && PluginConfig.openAiToken.isNotBlank()) {
|
||||||
chat = OpenAI(
|
chat = ModelService(
|
||||||
|
baseUrl = PluginConfig.openAiApi,
|
||||||
token = PluginConfig.openAiToken,
|
token = PluginConfig.openAiToken,
|
||||||
host = OpenAIHost(baseUrl = PluginConfig.openAiApi),
|
timeout = timeout,
|
||||||
timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
|
extraBody = parseExtraBody(PluginConfig.chatModelExtraBody)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化推理模型
|
// 初始化推理模型
|
||||||
if (PluginConfig.reasoningModelApi.isNotBlank() && PluginConfig.reasoningModelToken.isNotBlank()) {
|
if (PluginConfig.reasoningModelApi.isNotBlank() && PluginConfig.reasoningModelToken.isNotBlank()) {
|
||||||
reasoning = OpenAI(
|
reasoning = ModelService(
|
||||||
|
baseUrl = PluginConfig.reasoningModelApi,
|
||||||
token = PluginConfig.reasoningModelToken,
|
token = PluginConfig.reasoningModelToken,
|
||||||
host = OpenAIHost(baseUrl = PluginConfig.reasoningModelApi),
|
timeout = timeout,
|
||||||
timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
|
extraBody = parseExtraBody(PluginConfig.reasoningModelExtraBody)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化视觉模型
|
// 初始化视觉模型
|
||||||
if (PluginConfig.visualModelApi.isNotBlank() && PluginConfig.visualModelToken.isNotBlank()) {
|
if (PluginConfig.visualModelApi.isNotBlank() && PluginConfig.visualModelToken.isNotBlank()) {
|
||||||
visual = OpenAI(
|
visual = ModelService(
|
||||||
|
baseUrl = PluginConfig.visualModelApi,
|
||||||
token = PluginConfig.visualModelToken,
|
token = PluginConfig.visualModelToken,
|
||||||
host = OpenAIHost(baseUrl = PluginConfig.visualModelApi),
|
timeout = timeout,
|
||||||
timeout = Timeout(request = timeout, connect = timeout, socket = 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("视觉模型")
|
@ValueDescription("视觉模型")
|
||||||
var visualModel: String by value("qwen-vl-plus")
|
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")
|
@ValueDescription("百炼平台API KEY")
|
||||||
val dashScopeApiKey: String by value("")
|
val dashScopeApiKey: String by value("")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user