mirror of
https://github.com/jie65535/JChatGPT.git
synced 2025-08-04 19:09:20 +08:00
Compare commits
No commits in common. "6c034ab2a7e404d73082782019d1c6a6fa6b5a56" and "be182215b9e76087a28c0f4ac2a7bfc45a8e18a0" have entirely different histories.
6c034ab2a7
...
be182215b9
@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
val kotlinVersion = "2.0.20"
|
val kotlinVersion = "1.8.10"
|
||||||
kotlin("jvm") version kotlinVersion
|
kotlin("jvm") version kotlinVersion
|
||||||
kotlin("plugin.serialization") version kotlinVersion
|
kotlin("plugin.serialization") version kotlinVersion
|
||||||
|
|
||||||
@ -7,29 +7,21 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "top.jie65535.mirai"
|
group = "top.jie65535.mirai"
|
||||||
version = "1.5.0"
|
version = "1.3.0"
|
||||||
|
|
||||||
mirai {
|
|
||||||
jvmTarget = JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://maven.aliyun.com/repository/public")
|
maven("https://maven.aliyun.com/repository/public")
|
||||||
}
|
}
|
||||||
|
|
||||||
val openaiClientVersion = "4.0.1"
|
val openaiClientVersion = "3.8.2"
|
||||||
val ktorVersion = "3.0.3"
|
val ktorVersion = "2.3.12"
|
||||||
val jLatexMathVersion = "1.0.7"
|
val jLatexMathVersion = "1.0.7"
|
||||||
val commonTextVersion = "1.13.0"
|
val commonTextVersion = "1.13.0"
|
||||||
val hibernateVersion = "2.9.0"
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.aallam.openai:openai-client:$openaiClientVersion")
|
implementation("com.aallam.openai:openai-client:$openaiClientVersion")
|
||||||
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
||||||
implementation("org.scilab.forge:jlatexmath:$jLatexMathVersion")
|
implementation("org.scilab.forge:jlatexmath:$jLatexMathVersion")
|
||||||
implementation("org.apache.commons:commons-text:$commonTextVersion")
|
implementation("org.apache.commons:commons-text:$commonTextVersion")
|
||||||
|
|
||||||
// 聊天记录插件
|
|
||||||
compileOnly("xyz.cssxsh.mirai:mirai-hibernate-plugin:$hibernateVersion")
|
|
||||||
}
|
}
|
@ -1,12 +1,9 @@
|
|||||||
package top.jie65535.mirai
|
package top.jie65535.mirai
|
||||||
|
|
||||||
import com.aallam.openai.api.chat.ChatCompletionRequest
|
import com.aallam.openai.api.chat.*
|
||||||
import com.aallam.openai.api.chat.ChatMessage
|
import com.aallam.openai.api.core.Role
|
||||||
import com.aallam.openai.api.chat.ChatRole
|
|
||||||
import com.aallam.openai.api.chat.ToolCall
|
|
||||||
import com.aallam.openai.api.http.Timeout
|
import com.aallam.openai.api.http.Timeout
|
||||||
import com.aallam.openai.api.model.ModelId
|
import com.aallam.openai.api.model.ModelId
|
||||||
import com.aallam.openai.client.Chat
|
|
||||||
import com.aallam.openai.client.OpenAI
|
import com.aallam.openai.client.OpenAI
|
||||||
import com.aallam.openai.client.OpenAIHost
|
import com.aallam.openai.client.OpenAIHost
|
||||||
import io.ktor.util.collections.*
|
import io.ktor.util.collections.*
|
||||||
@ -19,8 +16,10 @@ import net.mamoe.mirai.console.permission.PermissionService
|
|||||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.Contact
|
||||||
import net.mamoe.mirai.contact.MemberPermission.*
|
import net.mamoe.mirai.contact.MemberPermission.*
|
||||||
|
import net.mamoe.mirai.contact.isOperator
|
||||||
|
import net.mamoe.mirai.contact.nameCardOrNick
|
||||||
import net.mamoe.mirai.event.GlobalEventChannel
|
import net.mamoe.mirai.event.GlobalEventChannel
|
||||||
import net.mamoe.mirai.event.events.FriendMessageEvent
|
import net.mamoe.mirai.event.events.FriendMessageEvent
|
||||||
import net.mamoe.mirai.event.events.GroupMessageEvent
|
import net.mamoe.mirai.event.events.GroupMessageEvent
|
||||||
@ -29,16 +28,13 @@ import net.mamoe.mirai.message.data.*
|
|||||||
import net.mamoe.mirai.message.data.MessageSource.Key.quote
|
import net.mamoe.mirai.message.data.MessageSource.Key.quote
|
||||||
import net.mamoe.mirai.message.sourceIds
|
import net.mamoe.mirai.message.sourceIds
|
||||||
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
||||||
|
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
||||||
import net.mamoe.mirai.utils.info
|
import net.mamoe.mirai.utils.info
|
||||||
import top.jie65535.mirai.tools.*
|
import top.jie65535.mirai.tools.*
|
||||||
import xyz.cssxsh.mirai.hibernate.MiraiHibernateRecorder
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.format.TextStyle
|
||||||
import java.time.format.DateTimeFormatter
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlin.collections.*
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@ -46,18 +42,12 @@ object JChatGPT : KotlinPlugin(
|
|||||||
JvmPluginDescription(
|
JvmPluginDescription(
|
||||||
id = "top.jie65535.mirai.JChatGPT",
|
id = "top.jie65535.mirai.JChatGPT",
|
||||||
name = "J ChatGPT",
|
name = "J ChatGPT",
|
||||||
version = "1.5.0",
|
version = "1.3.0",
|
||||||
) {
|
) {
|
||||||
author("jie65535")
|
author("jie65535")
|
||||||
// dependsOn("xyz.cssxsh.mirai.plugin.mirai-hibernate-plugin", true)
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
private var llm: Chat? = null
|
private var openAi: OpenAI? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否包含历史对话
|
|
||||||
*/
|
|
||||||
private var includeHistory: Boolean = false
|
|
||||||
|
|
||||||
val chatPermission = PermissionId("JChatGPT", "Chat")
|
val chatPermission = PermissionId("JChatGPT", "Chat")
|
||||||
|
|
||||||
@ -74,14 +64,6 @@ object JChatGPT : KotlinPlugin(
|
|||||||
// 注册插件命令
|
// 注册插件命令
|
||||||
PluginCommands.register()
|
PluginCommands.register()
|
||||||
|
|
||||||
// 检查消息记录插件是否存在
|
|
||||||
includeHistory = try {
|
|
||||||
MiraiHibernateRecorder
|
|
||||||
true
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalEventChannel.parentScope(this)
|
GlobalEventChannel.parentScope(this)
|
||||||
.subscribeAlways<MessageEvent> { event -> onMessage(event) }
|
.subscribeAlways<MessageEvent> { event -> onMessage(event) }
|
||||||
|
|
||||||
@ -90,28 +72,22 @@ object JChatGPT : KotlinPlugin(
|
|||||||
|
|
||||||
fun updateOpenAiToken(token: String) {
|
fun updateOpenAiToken(token: String) {
|
||||||
val timeout = PluginConfig.timeout.milliseconds
|
val timeout = PluginConfig.timeout.milliseconds
|
||||||
llm = OpenAI(
|
openAi = OpenAI(
|
||||||
token,
|
token,
|
||||||
host = OpenAIHost(baseUrl = PluginConfig.openAiApi),
|
host = OpenAIHost(baseUrl = PluginConfig.openAiApi),
|
||||||
timeout = Timeout(request = timeout, connect = timeout, socket = timeout),
|
timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
|
||||||
// logging = LoggingConfig(LogLevel.All)
|
|
||||||
)
|
)
|
||||||
reasoningAgent.llm = llm
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss")
|
|
||||||
.withZone(ZoneOffset.systemDefault())
|
|
||||||
private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd E HH:mm:ss")
|
|
||||||
|
|
||||||
// private val userContext = ConcurrentMap<Long, MutableList<ChatMessage>>()
|
// private val userContext = ConcurrentMap<Long, MutableList<ChatMessage>>()
|
||||||
private const val REPLAY_QUEUE_MAX = 10
|
private const val REPLAY_QUEUE_MAX = 10
|
||||||
private val replyMap = ConcurrentMap<Int, MutableList<ChatMessage>>(REPLAY_QUEUE_MAX)
|
private val replyMap = ConcurrentMap<Int, MutableList<ChatMessage>>()
|
||||||
private val replyQueue = mutableListOf<Int>()
|
private val replyQueue = mutableListOf<Int>()
|
||||||
private val requestMap = ConcurrentSet<Long>()
|
private val requestMap = ConcurrentSet<Long>()
|
||||||
|
|
||||||
private suspend fun onMessage(event: MessageEvent) {
|
private suspend fun onMessage(event: MessageEvent) {
|
||||||
// 检查Token是否设置
|
// 检查Token是否设置
|
||||||
if (llm == null) return
|
if (openAi == null) return
|
||||||
// 发送者是否有权限
|
// 发送者是否有权限
|
||||||
if (!event.toCommandSender().hasPermission(chatPermission)) {
|
if (!event.toCommandSender().hasPermission(chatPermission)) {
|
||||||
if (event is GroupMessageEvent) {
|
if (event is GroupMessageEvent) {
|
||||||
@ -171,11 +147,9 @@ object JChatGPT : KotlinPlugin(
|
|||||||
prompt.replace(i, i + target.length, replacement())
|
prompt.replace(i, i + target.length, replacement())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replace("{time}") {
|
replace("{time}") {
|
||||||
dateTimeFormatter.format(now)
|
"$now ${now.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.CHINA)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
replace("{subject}") {
|
replace("{subject}") {
|
||||||
if (event is GroupMessageEvent) {
|
if (event is GroupMessageEvent) {
|
||||||
"\"${event.subject.name}\" 群聊中"
|
"\"${event.subject.name}\" 群聊中"
|
||||||
@ -183,103 +157,33 @@ object JChatGPT : KotlinPlugin(
|
|||||||
"私聊中"
|
"私聊中"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
replace("{sender}") {
|
||||||
// replace("{sender}") {
|
|
||||||
// if (event is GroupMessageEvent) {
|
|
||||||
// event.sender.specialTitle
|
|
||||||
// val permissionName = when (event.sender.permission) {
|
|
||||||
// MEMBER -> "普通群员"
|
|
||||||
// ADMINISTRATOR -> "管理员"
|
|
||||||
// OWNER -> "群主"
|
|
||||||
// }
|
|
||||||
// "\"${event.senderName}\" 身份:$permissionName"
|
|
||||||
// } else {
|
|
||||||
// "\"${event.senderName}\""
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
replace("{history}") {
|
|
||||||
if (!includeHistory) {
|
|
||||||
return@replace "暂无内容"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 一段时间内的消息
|
|
||||||
val beforeTimestamp = now.minusMinutes(PluginConfig.historyWindowMin.toLong()).toEpochSecond().toInt()
|
|
||||||
val nowTimestamp = now.toEpochSecond().toInt()
|
|
||||||
// 最近这段时间的历史对话
|
|
||||||
val history = MiraiHibernateRecorder[event.subject, beforeTimestamp, nowTimestamp]
|
|
||||||
.take(PluginConfig.historyMessageLimit) // 只取最近的部分消息,避免上下文过长
|
|
||||||
.sortedBy { it.time } // 按时间排序
|
|
||||||
// 构造历史消息
|
|
||||||
val historyText = StringBuilder()
|
|
||||||
if (event is GroupMessageEvent) {
|
if (event is GroupMessageEvent) {
|
||||||
for (record in history) {
|
event.sender.specialTitle
|
||||||
if (event.bot.id == record.fromId) {
|
val permissionName = when (event.sender.permission) {
|
||||||
historyText.append("你")
|
MEMBER -> "普通群员"
|
||||||
} else {
|
ADMINISTRATOR -> "管理员"
|
||||||
val recordSender = event.subject[record.fromId]
|
OWNER -> "群主"
|
||||||
if (recordSender != null) {
|
|
||||||
// 群活跃等级
|
|
||||||
historyText.append(getNameCard(recordSender))
|
|
||||||
} else {
|
|
||||||
// 未知群员
|
|
||||||
historyText.append("未知群员(").append(record.fromId).append(")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
historyText
|
|
||||||
.append(" ")
|
|
||||||
// 发言时间
|
|
||||||
.append(timeFormatter.format(Instant.ofEpochSecond(record.time.toLong())))
|
|
||||||
// 消息内容
|
|
||||||
.append(" 说:").appendLine(record.toMessageChain().joinToString("") {
|
|
||||||
when (it) {
|
|
||||||
is At -> {
|
|
||||||
it.getDisplay(event.subject)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ForwardMessage -> {
|
|
||||||
it.title + "\n" + it.preview
|
|
||||||
}
|
|
||||||
|
|
||||||
is QuoteReply -> {
|
|
||||||
">" + it.source.originalMessage.contentToString().replace("\n", "\n> ") + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
it.contentToString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
"\"${event.senderName}\" 身份:$permissionName"
|
||||||
} else {
|
} else {
|
||||||
// TODO 私聊
|
"\"${event.senderName}\""
|
||||||
}
|
}
|
||||||
|
|
||||||
historyText.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return prompt.toString()
|
return prompt.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(MiraiExperimentalApi::class)
|
||||||
private suspend fun startChat(event: MessageEvent, context: List<ChatMessage>? = null) {
|
private suspend fun startChat(event: MessageEvent, context: List<ChatMessage>? = null) {
|
||||||
val history = mutableListOf<ChatMessage>()
|
val history = mutableListOf<ChatMessage>()
|
||||||
if (!context.isNullOrEmpty()) {
|
if (!context.isNullOrEmpty()) {
|
||||||
history.addAll(context)
|
history.addAll(context)
|
||||||
} else if (PluginConfig.prompt.isNotEmpty()) {
|
} else if (PluginConfig.prompt.isNotEmpty()) {
|
||||||
val prompt = getSystemPrompt(event)
|
history.add(ChatMessage(ChatRole.System, getSystemPrompt(event)))
|
||||||
if (PluginConfig.logPrompt) {
|
|
||||||
logger.info("Prompt: $prompt")
|
|
||||||
}
|
|
||||||
history.add(ChatMessage(ChatRole.System, prompt))
|
|
||||||
}
|
}
|
||||||
val msg = event.message.plainText()
|
val msg = event.message.plainText()
|
||||||
if (msg.isNotEmpty()) {
|
if (msg.isNotEmpty()) {
|
||||||
history.add(ChatMessage(ChatRole.User, if (event is GroupMessageEvent) {
|
history.add(ChatMessage(ChatRole.User, msg))
|
||||||
"${getNameCard(event.sender)} 说:$msg"
|
|
||||||
} else {
|
|
||||||
msg
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -288,89 +192,76 @@ object JChatGPT : KotlinPlugin(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var done = true
|
var done: Boolean
|
||||||
// 至少重试两次
|
var retry = 2
|
||||||
var retry = max(PluginConfig.retryMax, 2)
|
var hasTools = true
|
||||||
do {
|
do {
|
||||||
try {
|
val reply = chatCompletion(history, hasTools)
|
||||||
val reply = chatCompletion(history, retry > 1)
|
history.add(reply)
|
||||||
history.add(reply)
|
done = true
|
||||||
done = true
|
|
||||||
|
|
||||||
for (toolCall in reply.toolCalls.orEmpty()) {
|
for (toolCall in reply.toolCalls.orEmpty()) {
|
||||||
require(toolCall is ToolCall.Function) { "Tool call is not a function" }
|
require(toolCall is ToolCall.Function) { "Tool call is not a function" }
|
||||||
val functionResponse = toolCall.execute(event)
|
val functionResponse = toolCall.execute(event)
|
||||||
history.add(
|
history.add(
|
||||||
ChatMessage(
|
ChatMessage(
|
||||||
role = ChatRole.Tool,
|
role = ChatRole.Tool,
|
||||||
toolCallId = toolCall.id,
|
toolCallId = toolCall.id,
|
||||||
name = toolCall.function.name,
|
name = toolCall.function.name,
|
||||||
content = functionResponse
|
content = functionResponse
|
||||||
)
|
|
||||||
)
|
)
|
||||||
done = false
|
)
|
||||||
}
|
done = false
|
||||||
} catch (e: Exception) {
|
hasTools = false
|
||||||
if (retry <= 1) {
|
|
||||||
throw e
|
|
||||||
} else {
|
|
||||||
logger.warning("调用llm时发生异常,重试中", e)
|
|
||||||
event.subject.sendMessage(event.message.quote() + "出错了...正在重试...")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} while (!done && 0 <-- retry)
|
} while (!done && 0 < --retry)
|
||||||
|
|
||||||
val content = history.last().content ?: "..."
|
val content = history.last().content ?: "..."
|
||||||
val replyMsg = event.subject.sendMessage(
|
val replyMsg = event.subject.sendMessage(
|
||||||
if (content.length < PluginConfig.messageMergeThreshold) {
|
if (content.length < 128) {
|
||||||
event.message.quote() + toMessage(event.subject, content)
|
event.message.quote() + toMessage(event.subject, content)
|
||||||
} else {
|
} else {
|
||||||
// 消息内容太长则转为转发消息避免刷屏
|
// 消息内容太长则转为转发消息避免刷屏
|
||||||
event.buildForwardMessage {
|
event.buildForwardMessage {
|
||||||
event.bot says toMessage(event.subject, content)
|
for (item in history) {
|
||||||
}
|
if (item.content.isNullOrEmpty())
|
||||||
|
continue
|
||||||
|
val temp = toMessage(event.subject, item.content!!)
|
||||||
|
when (item.role) {
|
||||||
|
Role.User -> event.sender says temp
|
||||||
|
Role.Assistant -> event.bot says temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 不再将历史对话记录加入其中
|
// 检查并移除超出转发消息上限的消息
|
||||||
// event.buildForwardMessage {
|
var isOverflow = false
|
||||||
// for (item in history) {
|
var count = 0
|
||||||
// if (item.content.isNullOrEmpty())
|
for (i in size - 1 downTo 0) {
|
||||||
// continue
|
if (count > 4900) {
|
||||||
// val temp = toMessage(event.subject, item.content!!)
|
isOverflow = true
|
||||||
// when (item.role) {
|
// 删除早期上下文消息
|
||||||
// Role.User -> event.sender says temp
|
removeAt(i)
|
||||||
// Role.Assistant -> event.bot says temp
|
} else {
|
||||||
// }
|
for (text in this[i].messageChain.filterIsInstance<PlainText>()) {
|
||||||
// }
|
count += text.content.length
|
||||||
//
|
}
|
||||||
// // 检查并移除超出转发消息上限的消息
|
}
|
||||||
// var isOverflow = false
|
}
|
||||||
// var count = 0
|
if (count > 5000) {
|
||||||
// for (i in size - 1 downTo 0) {
|
removeAt(0)
|
||||||
// if (count > 4900) {
|
}
|
||||||
// isOverflow = true
|
if (isOverflow) {
|
||||||
// // 删除早期上下文消息
|
// 如果溢出了,插入一条提示到最开始
|
||||||
// removeAt(i)
|
add(
|
||||||
// } else {
|
0, ForwardMessage.Node(
|
||||||
// for (text in this[i].messageChain.filterIsInstance<PlainText>()) {
|
senderId = event.bot.id,
|
||||||
// count += text.content.length
|
time = this[0].time - 1,
|
||||||
// }
|
senderName = event.bot.nameCardOrNick,
|
||||||
// }
|
message = PlainText("更早的消息已隐藏,避免超出转发消息上限。")
|
||||||
// }
|
)
|
||||||
// if (count > 5000) {
|
)
|
||||||
// removeAt(0)
|
}
|
||||||
// }
|
}
|
||||||
// if (isOverflow) {
|
|
||||||
// // 如果溢出了,插入一条提示到最开始
|
|
||||||
// add(
|
|
||||||
// 0, ForwardMessage.Node(
|
|
||||||
// senderId = event.bot.id,
|
|
||||||
// time = this[0].time - 1,
|
|
||||||
// senderName = event.bot.nameCardOrNick,
|
|
||||||
// message = PlainText("更早的消息已隐藏,避免超出转发消息上限。")
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -386,20 +277,17 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
event.subject.sendMessage(event.message.quote() + "很抱歉,发生异常,请稍后重试")
|
event.subject.sendMessage(event.message.quote() + "发生异常,请重试")
|
||||||
} finally {
|
} finally {
|
||||||
requestMap.remove(event.sender.id)
|
requestMap.remove(event.sender.id)
|
||||||
}
|
}
|
||||||
// catch (ex: OpenAITimeoutException) {
|
|
||||||
// event.subject.sendMessage(event.message.quote() + "很抱歉,服务器没响应,请稍后重试")
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val laTeXPattern = Pattern.compile(
|
private val laTeXPattern = Pattern.compile(
|
||||||
"\\\\\\((.+?)\\\\\\)|" + // 匹配行内公式 \(...\)
|
"\\\\\\((.+?)\\\\\\)|" + // 匹配行内公式 \(...\)
|
||||||
"\\\\\\[(.+?)\\\\\\]|" + // 匹配独立公式 \[...\]
|
"\\\\\\[(.+?)\\\\\\]|" + // 匹配独立公式 \[...\]
|
||||||
"\\$\\$([^$]+?)\\$\\$|" + // 匹配独立公式 $$...$$
|
"\\$\\$([^$]+?)\\$\\$|" + // 匹配独立公式 $$...$$
|
||||||
"\\$\\s(.+?)\\s\\$|" + // 匹配行内公式 $...$
|
"\\$(.+?)\\$|" + // 匹配行内公式 $...$
|
||||||
"```latex\\s*([^`]+?)\\s*```" // 匹配 ```latex ... ```
|
"```latex\\s*([^`]+?)\\s*```" // 匹配 ```latex ... ```
|
||||||
, Pattern.DOTALL
|
, Pattern.DOTALL
|
||||||
)
|
)
|
||||||
@ -448,8 +336,6 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val reasoningAgent = ReasoningAgent()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具列表
|
* 工具列表
|
||||||
*/
|
*/
|
||||||
@ -460,9 +346,6 @@ object JChatGPT : KotlinPlugin(
|
|||||||
// 运行代码
|
// 运行代码
|
||||||
RunCode(),
|
RunCode(),
|
||||||
|
|
||||||
// 推理代理
|
|
||||||
reasoningAgent,
|
|
||||||
|
|
||||||
// 天气服务
|
// 天气服务
|
||||||
WeatherService(),
|
WeatherService(),
|
||||||
|
|
||||||
@ -478,7 +361,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
chatMessages: List<ChatMessage>,
|
chatMessages: List<ChatMessage>,
|
||||||
hasTools: Boolean = true
|
hasTools: Boolean = true
|
||||||
): ChatMessage {
|
): ChatMessage {
|
||||||
val llm = this.llm ?: throw NullPointerException("OpenAI Token 未设置,无法开始")
|
val openAi = this.openAi ?: throw NullPointerException("OpenAI Token 未设置,无法开始")
|
||||||
val availableTools = if (hasTools) {
|
val availableTools = if (hasTools) {
|
||||||
myTools.filter { it.isEnabled }.map { it.tool }
|
myTools.filter { it.isEnabled }.map { it.tool }
|
||||||
} else null
|
} else null
|
||||||
@ -486,38 +369,19 @@ object JChatGPT : KotlinPlugin(
|
|||||||
model = ModelId(PluginConfig.chatModel),
|
model = ModelId(PluginConfig.chatModel),
|
||||||
messages = chatMessages,
|
messages = chatMessages,
|
||||||
tools = availableTools,
|
tools = availableTools,
|
||||||
|
toolChoice = ToolChoice.Auto
|
||||||
)
|
)
|
||||||
logger.info("API Requesting... Model=${PluginConfig.chatModel}"
|
logger.info(
|
||||||
// " Tools=${availableTools?.joinToString(prefix = "[", postfix = "]")}"
|
"API Requesting..." +
|
||||||
|
" Model=${PluginConfig.chatModel}" +
|
||||||
|
" Tools=${availableTools?.joinToString(prefix = "[", postfix = "]")}"
|
||||||
)
|
)
|
||||||
val response = llm.chatCompletion(request)
|
val response = openAi.chatCompletion(request)
|
||||||
val message = response.choices.first().message
|
val message = response.choices.first().message
|
||||||
logger.info("Response: $message ${response.usage}")
|
logger.info("Response: $message ${response.usage}")
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNameCard(member: Member): String {
|
|
||||||
val nameCard = StringBuilder()
|
|
||||||
// 群活跃等级
|
|
||||||
nameCard.append("【lv").append(member.active.temperature).append(" ")
|
|
||||||
// 群头衔
|
|
||||||
if (member.specialTitle.isNotEmpty()) {
|
|
||||||
nameCard.append(member.specialTitle)
|
|
||||||
} else {
|
|
||||||
nameCard.append(
|
|
||||||
when (member.permission) {
|
|
||||||
OWNER -> "群主"
|
|
||||||
ADMINISTRATOR -> "管理员"
|
|
||||||
MEMBER -> member.temperatureTitle
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 群名片
|
|
||||||
nameCard.append("】 ").append(member.nameCardOrNick)
|
|
||||||
// .append(" (").append(recordSender.id).append(")")
|
|
||||||
return nameCard.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MessageChain.plainText() = this.filterIsInstance<PlainText>().joinToString().trim()
|
private fun MessageChain.plainText() = this.filterIsInstance<PlainText>().joinToString().trim()
|
||||||
|
|
||||||
private suspend fun ToolCall.Function.execute(event: MessageEvent): String {
|
private suspend fun ToolCall.Function.execute(event: MessageEvent): String {
|
||||||
@ -537,11 +401,11 @@ object JChatGPT : KotlinPlugin(
|
|||||||
logger.error("Failed to call ${function.name}", e)
|
logger.error("Failed to call ${function.name}", e)
|
||||||
"工具调用失败,请尝试自行回答用户,或如实告知。"
|
"工具调用失败,请尝试自行回答用户,或如实告知。"
|
||||||
}
|
}
|
||||||
logger.info("Result=\"$result\"")
|
logger.info("Result=$result")
|
||||||
// 过会撤回加载消息
|
// 过会撤回加载消息
|
||||||
if (receipt != null) {
|
if (receipt != null) {
|
||||||
launch {
|
launch {
|
||||||
delay(3.seconds)
|
delay(3.seconds);
|
||||||
try {
|
try {
|
||||||
receipt.recall()
|
receipt.recall()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
@ -12,10 +12,7 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
|||||||
var openAiToken: String by value("")
|
var openAiToken: String by value("")
|
||||||
|
|
||||||
@ValueDescription("Chat模型")
|
@ValueDescription("Chat模型")
|
||||||
var chatModel: String by value("qwen-max")
|
var chatModel: String by value("gpt-3.5-turbo-1106")
|
||||||
|
|
||||||
@ValueDescription("推理模型")
|
|
||||||
var reasoningModel: String by value("qwq-plus")
|
|
||||||
|
|
||||||
@ValueDescription("Chat默认提示")
|
@ValueDescription("Chat默认提示")
|
||||||
var prompt: String by value("")
|
var prompt: String by value("")
|
||||||
@ -38,18 +35,4 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
|||||||
@ValueDescription("在线运行代码 glot.io 的 api token,在官网注册账号即可获取。")
|
@ValueDescription("在线运行代码 glot.io 的 api token,在官网注册账号即可获取。")
|
||||||
val glotToken: String by value("")
|
val glotToken: String by value("")
|
||||||
|
|
||||||
@ValueDescription("创建Prompt时取最近多少分钟内的消息")
|
|
||||||
val historyWindowMin: Int by value(10)
|
|
||||||
|
|
||||||
@ValueDescription("创建Prompt时取最多几条消息")
|
|
||||||
val historyMessageLimit: Int by value(20)
|
|
||||||
|
|
||||||
@ValueDescription("是否打印Prompt便于调试")
|
|
||||||
val logPrompt by value(false)
|
|
||||||
|
|
||||||
@ValueDescription("达到需要合并转发消息的阈值")
|
|
||||||
val messageMergeThreshold by value(150)
|
|
||||||
|
|
||||||
@ValueDescription("最大重试次数,至少2次,最后一次请求不会带工具,非工具调用相当于正常回复")
|
|
||||||
val retryMax: Int by value(3)
|
|
||||||
}
|
}
|
@ -1,58 +0,0 @@
|
|||||||
package top.jie65535.mirai.tools
|
|
||||||
|
|
||||||
import com.aallam.openai.api.chat.ChatCompletionRequest
|
|
||||||
import com.aallam.openai.api.chat.ChatMessage
|
|
||||||
import com.aallam.openai.api.chat.Tool
|
|
||||||
import com.aallam.openai.api.core.Parameters
|
|
||||||
import com.aallam.openai.api.model.ModelId
|
|
||||||
import com.aallam.openai.client.Chat
|
|
||||||
import kotlinx.serialization.json.*
|
|
||||||
import top.jie65535.mirai.PluginConfig
|
|
||||||
|
|
||||||
class ReasoningAgent : BaseAgent(
|
|
||||||
tool = Tool.function(
|
|
||||||
name = "reasoning",
|
|
||||||
description = "可通过调用推理模型对问题进行深度思考。",
|
|
||||||
parameters = Parameters.buildJsonObject {
|
|
||||||
put("type", "object")
|
|
||||||
putJsonObject("properties") {
|
|
||||||
putJsonObject("prompt") {
|
|
||||||
put("type", "string")
|
|
||||||
put("description", "用于调用推理模型的提示")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
putJsonArray("required") {
|
|
||||||
add("question")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
var llm: Chat? = null
|
|
||||||
|
|
||||||
override val loadingMessage: String
|
|
||||||
get() = "深度思考中..."
|
|
||||||
|
|
||||||
override val isEnabled: Boolean
|
|
||||||
get() = llm != null
|
|
||||||
|
|
||||||
override suspend fun execute(args: JsonObject?): String {
|
|
||||||
requireNotNull(args)
|
|
||||||
val llm = llm ?: return "未配置llm,无法进行推理。"
|
|
||||||
|
|
||||||
val prompt = args.getValue("prompt").jsonPrimitive.content
|
|
||||||
val answerContent = StringBuilder()
|
|
||||||
llm.chatCompletions(ChatCompletionRequest(
|
|
||||||
model = ModelId(PluginConfig.reasoningModel),
|
|
||||||
messages = listOf(ChatMessage.Companion.User(prompt))
|
|
||||||
)).collect {
|
|
||||||
if (it.choices.isNotEmpty()) {
|
|
||||||
val delta = it.choices[0].delta ?: return@collect
|
|
||||||
if (!delta.content.isNullOrEmpty()) {
|
|
||||||
answerContent.append(delta.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return answerContent.toString().ifEmpty { "推理出错,结果为空" }
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,7 +11,7 @@ import top.jie65535.mirai.PluginConfig
|
|||||||
|
|
||||||
class WebSearch : BaseAgent(
|
class WebSearch : BaseAgent(
|
||||||
tool = Tool.function(
|
tool = Tool.function(
|
||||||
name = "webSearch",
|
name = "search",
|
||||||
description = "Provides meta-search functionality through SearXNG," +
|
description = "Provides meta-search functionality through SearXNG," +
|
||||||
" aggregating results from multiple search engines.",
|
" aggregating results from multiple search engines.",
|
||||||
parameters = Parameters.buildJsonObject {
|
parameters = Parameters.buildJsonObject {
|
||||||
|
Loading…
Reference in New Issue
Block a user