mirror of
https://github.com/jie65535/JChatGPT.git
synced 2025-07-28 18:59:20 +08:00
Compare commits
12 Commits
f822999ab4
...
eda932b4e9
Author | SHA1 | Date | |
---|---|---|---|
eda932b4e9 | |||
6cba3cca22 | |||
60a48c5211 | |||
dbfdf83dc6 | |||
3a5caf86a1 | |||
c479282fe4 | |||
d1ee8f9fcf | |||
11bdaff54d | |||
7d911f2fb6 | |||
7e1a1ad0aa | |||
679bf15be5 | |||
ea4e6123b3 |
@ -1,12 +1,17 @@
|
|||||||
package top.jie65535.mirai
|
package top.jie65535.mirai
|
||||||
|
|
||||||
|
import com.aallam.openai.api.chat.ChatCompletionChunk
|
||||||
import com.aallam.openai.api.chat.ChatCompletionRequest
|
import com.aallam.openai.api.chat.ChatCompletionRequest
|
||||||
import com.aallam.openai.api.chat.ChatMessage
|
import com.aallam.openai.api.chat.ChatMessage
|
||||||
import com.aallam.openai.api.chat.ChatRole
|
import com.aallam.openai.api.chat.ChatRole
|
||||||
import com.aallam.openai.api.chat.ToolCall
|
import com.aallam.openai.api.chat.ToolCall
|
||||||
import com.aallam.openai.api.model.ModelId
|
import com.aallam.openai.api.model.ModelId
|
||||||
import io.ktor.util.collections.*
|
import io.ktor.util.collections.*
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||||
@ -24,7 +29,6 @@ import net.mamoe.mirai.event.events.GroupMessageEvent
|
|||||||
import net.mamoe.mirai.event.events.MessageEvent
|
import net.mamoe.mirai.event.events.MessageEvent
|
||||||
import net.mamoe.mirai.message.data.*
|
import net.mamoe.mirai.message.data.*
|
||||||
import net.mamoe.mirai.message.data.Image.Key.queryUrl
|
import net.mamoe.mirai.message.data.Image.Key.queryUrl
|
||||||
import net.mamoe.mirai.message.data.MessageSource.Key.quote
|
|
||||||
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
||||||
import net.mamoe.mirai.utils.info
|
import net.mamoe.mirai.utils.info
|
||||||
import top.jie65535.mirai.tools.*
|
import top.jie65535.mirai.tools.*
|
||||||
@ -61,6 +65,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
// 注册聊天权限
|
// 注册聊天权限
|
||||||
PermissionService.INSTANCE.register(chatPermission, "JChatGPT Chat Permission")
|
PermissionService.INSTANCE.register(chatPermission, "JChatGPT Chat Permission")
|
||||||
PluginConfig.reload()
|
PluginConfig.reload()
|
||||||
|
PluginData.reload()
|
||||||
|
|
||||||
// 设置Token
|
// 设置Token
|
||||||
LargeLanguageModels.reload()
|
LargeLanguageModels.reload()
|
||||||
@ -144,6 +149,14 @@ object JChatGPT : KotlinPlugin(
|
|||||||
"与 \"${event.senderName}\" 私聊中"
|
"与 \"${event.senderName}\" 私聊中"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replace("{memory}") {
|
||||||
|
val memoryText = PluginData.contactMemory[event.subject.id]
|
||||||
|
if (memoryText.isNullOrEmpty()) {
|
||||||
|
"暂无相关记忆"
|
||||||
|
} else memoryText
|
||||||
|
}
|
||||||
|
|
||||||
return prompt.toString()
|
return prompt.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,59 +313,128 @@ object JChatGPT : KotlinPlugin(
|
|||||||
private val thinkRegex = Regex("<think>[\\s\\S]*?</think>")
|
private val thinkRegex = Regex("<think>[\\s\\S]*?</think>")
|
||||||
|
|
||||||
private suspend fun startChat(event: MessageEvent) {
|
private suspend fun startChat(event: MessageEvent) {
|
||||||
if (!requestMap.add(event.sender.id)) {
|
if (!requestMap.add(event.subject.id)) {
|
||||||
event.subject.sendMessage("再等等...")
|
logger.warning("The current Contact is busy!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val history = mutableListOf<ChatMessage>()
|
|
||||||
if (PluginConfig.prompt.isNotEmpty()) {
|
|
||||||
val prompt = getSystemPrompt(event)
|
|
||||||
if (PluginConfig.logPrompt) {
|
|
||||||
logger.info("Prompt: $prompt")
|
|
||||||
}
|
|
||||||
history.add(ChatMessage(ChatRole.System, prompt))
|
|
||||||
}
|
|
||||||
val historyText = getHistory(event)
|
|
||||||
logger.info("History: $historyText")
|
|
||||||
history.add(ChatMessage.User(historyText))
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
val history = mutableListOf<ChatMessage>()
|
||||||
|
if (PluginConfig.prompt.isNotEmpty()) {
|
||||||
|
val prompt = getSystemPrompt(event)
|
||||||
|
if (PluginConfig.logPrompt) {
|
||||||
|
logger.info("Prompt: $prompt")
|
||||||
|
}
|
||||||
|
history.add(ChatMessage(ChatRole.System, prompt))
|
||||||
|
}
|
||||||
|
val historyText = getHistory(event)
|
||||||
|
logger.info("History: $historyText")
|
||||||
|
history.add(ChatMessage.User(historyText))
|
||||||
|
|
||||||
|
|
||||||
var done: Boolean
|
var done: Boolean
|
||||||
// 至少循环3次
|
// 至少循环3次
|
||||||
var retry = max(PluginConfig.retryMax, 3)
|
var retry = max(PluginConfig.retryMax, 3)
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
val startedAt = OffsetDateTime.now().toEpochSecond().toInt()
|
val startedAt = OffsetDateTime.now().toEpochSecond().toInt()
|
||||||
val response = chatCompletion(history)
|
val responseFlow = chatCompletions(history)
|
||||||
// 移除思考内容
|
var responseMessageBuilder: StringBuilder? = null
|
||||||
val responseContent = response.content?.replace(thinkRegex, "")?.trim()
|
val responseToolCalls = mutableListOf<ToolCall.Function>()
|
||||||
history.add(ChatMessage.Assistant(
|
val toolCallTasks = mutableListOf<Deferred<ChatMessage>>()
|
||||||
content = responseContent,
|
// 处理聊天流式响应
|
||||||
name = response.name,
|
responseFlow.collect { chunk ->
|
||||||
toolCalls = response.toolCalls
|
val delta = chunk.choices[0].delta
|
||||||
))
|
if (delta == null) return@collect
|
||||||
|
|
||||||
if (response.toolCalls.isNullOrEmpty()) {
|
// 处理内容更新
|
||||||
done = true
|
if (delta.content != null) {
|
||||||
} else {
|
if (responseMessageBuilder == null) {
|
||||||
done = false
|
responseMessageBuilder = StringBuilder(delta.content)
|
||||||
// 处理函数调用
|
} else {
|
||||||
for (toolCall in response.toolCalls) {
|
responseMessageBuilder.append(delta.content)
|
||||||
require(toolCall is ToolCall.Function) { "Tool call is not a function" }
|
|
||||||
val functionResponse = toolCall.execute(event)
|
|
||||||
history.add(
|
|
||||||
ChatMessage(
|
|
||||||
role = ChatRole.Tool,
|
|
||||||
toolCallId = toolCall.id,
|
|
||||||
name = toolCall.function.name,
|
|
||||||
content = functionResponse
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (toolCall.function.name == "endConversation") {
|
|
||||||
done = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理工具调用更新
|
||||||
|
val toolCalls = delta.toolCalls
|
||||||
|
if (toolCalls != null) {
|
||||||
|
for (toolCallChunk in toolCalls) {
|
||||||
|
val index = toolCallChunk.index
|
||||||
|
val toolId = toolCallChunk.id
|
||||||
|
val function = toolCallChunk.function
|
||||||
|
// 新的请求
|
||||||
|
if (index >= responseToolCalls.size) {
|
||||||
|
// 处理已完成的工具调用
|
||||||
|
responseToolCalls.lastOrNull()?.let { toolCall ->
|
||||||
|
toolCallTasks.add(async {
|
||||||
|
val functionResponse = toolCall.execute(event)
|
||||||
|
ChatMessage(
|
||||||
|
role = ChatRole.Tool,
|
||||||
|
toolCallId = toolCall.id,
|
||||||
|
name = toolCall.function.name,
|
||||||
|
content = functionResponse
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入新的工具调用
|
||||||
|
if (toolId != null && function != null) {
|
||||||
|
responseToolCalls.add(ToolCall.Function(toolId, function))
|
||||||
|
}
|
||||||
|
} else if (function != null) {
|
||||||
|
// 拼接函数名字
|
||||||
|
if (function.nameOrNull != null) {
|
||||||
|
val currentTool = responseToolCalls[index]
|
||||||
|
responseToolCalls[index] = currentTool.copy(
|
||||||
|
function = currentTool.function.copy(
|
||||||
|
nameOrNull = currentTool.function.nameOrNull.orEmpty() + function.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 拼接函数参数
|
||||||
|
if (function.argumentsOrNull != null) {
|
||||||
|
val currentTool = responseToolCalls[index]
|
||||||
|
responseToolCalls[index] = currentTool.copy(
|
||||||
|
function = currentTool.function.copy(
|
||||||
|
argumentsOrNull = currentTool.function.argumentsOrNull.orEmpty() + function.arguments
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除思考内容
|
||||||
|
val responseContent = responseMessageBuilder?.replace(thinkRegex, "")?.trim()
|
||||||
|
// 记录AI回答
|
||||||
|
history.add(ChatMessage.Assistant(
|
||||||
|
content = responseContent,
|
||||||
|
toolCalls = responseToolCalls
|
||||||
|
))
|
||||||
|
|
||||||
|
// 处理最后一个工具调用
|
||||||
|
if (responseToolCalls.size > toolCallTasks.size) {
|
||||||
|
val toolCallMessage = responseToolCalls.last().let { toolCall ->
|
||||||
|
val functionResponse = toolCall.execute(event)
|
||||||
|
ChatMessage(
|
||||||
|
role = ChatRole.Tool,
|
||||||
|
toolCallId = toolCall.id,
|
||||||
|
name = toolCall.function.name,
|
||||||
|
content = functionResponse
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (toolCallTasks.isNotEmpty()) {
|
||||||
|
// 等待之前的所有工具完成
|
||||||
|
history.addAll(toolCallTasks.awaitAll())
|
||||||
|
}
|
||||||
|
// 将最后一个也加入对话历史中
|
||||||
|
history.add(toolCallMessage)
|
||||||
|
// 如果调用中包含结束对话工具则表示完成,反之则继续循环
|
||||||
|
done = history.any { it.name == "endConversation" }
|
||||||
|
} else {
|
||||||
|
done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
@ -360,11 +442,6 @@ object JChatGPT : KotlinPlugin(
|
|||||||
buildString {
|
buildString {
|
||||||
append("系统提示:本次运行还剩${retry-1}轮")
|
append("系统提示:本次运行还剩${retry-1}轮")
|
||||||
|
|
||||||
// if (response.toolCalls.isNullOrEmpty()) {
|
|
||||||
// append("\n在上一轮对话中未检测到调用任何工具,请检查工具调用语法是否正确?")
|
|
||||||
// append("\n如果你确实不需要调用其它工具比如发送消息,请调用`endConversation`来结束对话。")
|
|
||||||
// }
|
|
||||||
|
|
||||||
val newMessages = getAfterHistory(startedAt, event)
|
val newMessages = getAfterHistory(startedAt, event)
|
||||||
if (newMessages.isNotEmpty()) {
|
if (newMessages.isNotEmpty()) {
|
||||||
append("\n以下是上次运行至今的新消息\n\n$newMessages")
|
append("\n以下是上次运行至今的新消息\n\n$newMessages")
|
||||||
@ -378,224 +455,32 @@ object JChatGPT : KotlinPlugin(
|
|||||||
} else {
|
} else {
|
||||||
done = false
|
done = false
|
||||||
logger.warning("调用llm时发生异常,重试中", e)
|
logger.warning("调用llm时发生异常,重试中", e)
|
||||||
event.subject.sendMessage(event.message.quote() + "出错了...正在重试...")
|
event.subject.sendMessage("出错了...正在重试...")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!done && 0 < --retry)
|
} while (!done && 0 < --retry)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
event.subject.sendMessage(event.message.quote() + "很抱歉,发生异常,请稍后重试")
|
event.subject.sendMessage("很抱歉,发生异常,请稍后重试")
|
||||||
} finally {
|
} finally {
|
||||||
// 一段时间后才允许再次提问,防止高频对话
|
// 一段时间后才允许再次提问,防止高频对话
|
||||||
launch {
|
launch {
|
||||||
delay(5.seconds)
|
delay(1.seconds)
|
||||||
requestMap.remove(event.sender.id)
|
requestMap.remove(event.subject.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private suspend fun startChat(event: MessageEvent) {
|
private val regexAtQq = Regex("""@(\d{5,12})""")
|
||||||
// if (!requestMap.add(event.sender.id)) {
|
|
||||||
// // CD中不再引用消息,否则可能导致和机器人无限循环对话
|
|
||||||
// event.subject.sendMessage("再等等...")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val history = mutableListOf<ChatMessage>()
|
|
||||||
// if (PluginConfig.prompt.isNotEmpty()) {
|
|
||||||
// val prompt = getSystemPrompt(event)
|
|
||||||
// if (PluginConfig.logPrompt) {
|
|
||||||
// logger.info("Prompt: $prompt")
|
|
||||||
// }
|
|
||||||
// history.add(ChatMessage(ChatRole.System, prompt))
|
|
||||||
// }
|
|
||||||
// val historyText = getHistory(event)
|
|
||||||
// logger.info("History: $historyText")
|
|
||||||
// history.add(ChatMessage.User(historyText))
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// var done = true
|
|
||||||
// // 至少重试两次
|
|
||||||
// var retry = max(PluginConfig.retryMax, 3)
|
|
||||||
// val finalToolCalls = mutableMapOf<Int, ToolCall.Function>()
|
|
||||||
// val contentBuilder = StringBuilder()
|
|
||||||
// do {
|
|
||||||
// finalToolCalls.clear()
|
|
||||||
// contentBuilder.setLength(0)
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// var sent = false
|
|
||||||
// // 流式处理响应
|
|
||||||
// withTimeout(PluginConfig.timeout) {
|
|
||||||
// chatCompletions(history, retry > 1).collect { chunk ->
|
|
||||||
// val delta = chunk.choices[0].delta
|
|
||||||
// if (delta == null) return@collect
|
|
||||||
//
|
|
||||||
// // 处理工具调用
|
|
||||||
// val toolCalls = delta.toolCalls
|
|
||||||
// if (toolCalls != null) {
|
|
||||||
// for (toolCall in toolCalls) {
|
|
||||||
// val index = toolCall.index
|
|
||||||
// val toolId = toolCall.id
|
|
||||||
// val function = toolCall.function
|
|
||||||
// // 取出未完成的函数调用
|
|
||||||
// val incompleteCall = finalToolCalls[index]
|
|
||||||
// // 如果是新的函数调用,保存起来
|
|
||||||
// if (incompleteCall == null && toolId != null && function != null) {
|
|
||||||
// // 添加函数调用
|
|
||||||
// finalToolCalls[index] = ToolCall.Function(toolId, function)
|
|
||||||
// } else if (incompleteCall != null && function != null && function.argumentsOrNull != null) {
|
|
||||||
// // 更新参数内容
|
|
||||||
// finalToolCalls[index] = incompleteCall.copy(
|
|
||||||
// function = incompleteCall.function.copy(
|
|
||||||
// argumentsOrNull = incompleteCall.function.arguments + function.arguments
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 处理响应内容
|
|
||||||
// val contentChunk = delta.content
|
|
||||||
// // 避免连续发送多次,只拆分第一次进行发送
|
|
||||||
// if (contentChunk != null && !sent) {
|
|
||||||
// // 填入内容
|
|
||||||
// contentBuilder.append(contentChunk)
|
|
||||||
// sent = parseStreamingContent(contentBuilder, event)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val lastBlock = contentBuilder.toString().trim()
|
|
||||||
// if (lastBlock.isNotEmpty()) {
|
|
||||||
// event.subject.sendMessage(
|
|
||||||
// if (lastBlock.length > PluginConfig.messageMergeThreshold) {
|
|
||||||
// event.buildForwardMessage {
|
|
||||||
// event.bot says toMessage(event.subject, lastBlock)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// toMessage(event.subject, lastBlock)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (finalToolCalls.isNotEmpty()) {
|
|
||||||
// val toolCalls = finalToolCalls.values.toList()
|
|
||||||
// history.add(ChatMessage.Assistant(toolCalls = toolCalls))
|
|
||||||
// for (toolCall in toolCalls) {
|
|
||||||
// val functionResponse = toolCall.execute(event)
|
|
||||||
// history.add(
|
|
||||||
// ChatMessage(
|
|
||||||
// role = ChatRole.Tool,
|
|
||||||
// toolCallId = toolCall.id,
|
|
||||||
// name = toolCall.function.name,
|
|
||||||
// content = functionResponse
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// done = false
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// done = true
|
|
||||||
// }
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// if (retry <= 1) {
|
|
||||||
// throw e
|
|
||||||
// } else {
|
|
||||||
// done = false
|
|
||||||
// logger.warning("调用llm时发生异常,重试中", e)
|
|
||||||
// event.subject.sendMessage(event.message.quote() + "出错了...正在重试...")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } while (!done && 0 < --retry)
|
|
||||||
// } catch (ex: Throwable) {
|
|
||||||
// logger.warning(ex)
|
|
||||||
// event.subject.sendMessage(event.message.quote() + "很抱歉,发生异常,请稍后重试")
|
|
||||||
// } finally {
|
|
||||||
// // 一段时间后才允许再次提问,防止高频对话
|
|
||||||
// launch {
|
|
||||||
// delay(10.seconds)
|
|
||||||
// requestMap.remove(event.sender.id)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 解析流消息
|
|
||||||
// */
|
|
||||||
// private fun parseStreamingContent(contentBuilder: StringBuilder, event: MessageEvent): Boolean {
|
|
||||||
// // 处理推理内容
|
|
||||||
// val thinkBeginAt = contentBuilder.indexOf("<think")
|
|
||||||
// if (thinkBeginAt >= 0) {
|
|
||||||
// val thinkEndAt = contentBuilder.indexOf("</think>")
|
|
||||||
// if (thinkEndAt > 0) {
|
|
||||||
// // 去除思考内容
|
|
||||||
// contentBuilder.delete(thinkBeginAt, thinkEndAt + "</think>".length)
|
|
||||||
// }
|
|
||||||
// // 跳过本轮处理
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 处理代码块
|
|
||||||
// val codeBlockBeginAt = contentBuilder.indexOf("```")
|
|
||||||
// if (codeBlockBeginAt >= 0) {
|
|
||||||
// val codeBlockEndAt = contentBuilder.indexOf("```", codeBlockBeginAt + 3)
|
|
||||||
// if (codeBlockEndAt >= 0) {
|
|
||||||
// val codeBlockContentBegin = contentBuilder.indexOf("\n", codeBlockBeginAt + 3)
|
|
||||||
// if (codeBlockContentBegin in codeBlockBeginAt..codeBlockEndAt) {
|
|
||||||
// val codeBlockContent = contentBuilder.substring(codeBlockContentBegin, codeBlockEndAt).trim()
|
|
||||||
// contentBuilder.delete(codeBlockBeginAt, codeBlockEndAt + 3)
|
|
||||||
// launch {
|
|
||||||
// // 发送代码块内容
|
|
||||||
// event.subject.sendMessage(
|
|
||||||
// if (codeBlockContent.length < PluginConfig.messageMergeThreshold) {
|
|
||||||
// toMessage(event.subject, codeBlockContent)
|
|
||||||
// } else {
|
|
||||||
// // 消息内容太长则转为转发消息避免刷屏
|
|
||||||
// event.buildForwardMessage {
|
|
||||||
// event.bot says toMessage(event.subject, codeBlockContent)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // 跳过本轮处理
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 徒手trimStart
|
|
||||||
// var contentBeginAt = 0
|
|
||||||
// while (contentBeginAt < contentBuilder.length) {
|
|
||||||
// if (contentBuilder[contentBeginAt].isWhitespace()) {
|
|
||||||
// contentBeginAt++
|
|
||||||
// } else {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // 对空行进行分割输出
|
|
||||||
// val emptyLineAt = contentBuilder.indexOf("\n\n", contentBeginAt)
|
|
||||||
// if (emptyLineAt > 0) {
|
|
||||||
// val lineContent = contentBuilder.substring(contentBeginAt, emptyLineAt)
|
|
||||||
// contentBuilder.delete(0, emptyLineAt + 2)
|
|
||||||
// launch {
|
|
||||||
// // 发送消息内容
|
|
||||||
// event.subject.sendMessage(toMessage(event.subject, lineContent))
|
|
||||||
// }
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
private val regexAtQq = Regex("@(\\d+)")
|
|
||||||
|
|
||||||
private val regexLaTeX = Regex(
|
private val regexLaTeX = Regex(
|
||||||
"\\\\\\((.+?)\\\\\\)|" + // 匹配行内公式 \(...\)
|
"""\\\((.+?)\\\)|""" + // 匹配行内公式 \(...\)
|
||||||
"\\\\\\[(.+?)\\\\\\]|" + // 匹配独立公式 \[...\]
|
"""\\\[(.+?)\\]|""" + // 匹配独立公式 \[...\]
|
||||||
"\\$\\s(.+?)\\s\\$|" // 匹配行内公式 $...$
|
"""\$(.+?)\$""" // 匹配行内公式 $...$
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val regexImage = Regex("""!\[(.*?)]\(([^\s"']+).*?\)""")
|
||||||
|
|
||||||
private data class MessageChunk(val range: IntRange, val content: Message)
|
private data class MessageChunk(val range: IntRange, val content: Message)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -612,6 +497,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
PlainText(content)
|
PlainText(content)
|
||||||
} else {
|
} else {
|
||||||
val t = mutableListOf<MessageChunk>()
|
val t = mutableListOf<MessageChunk>()
|
||||||
|
// @某人
|
||||||
regexAtQq.findAll(content).forEach {
|
regexAtQq.findAll(content).forEach {
|
||||||
val qq = it.groups[1]?.value?.toLongOrNull()
|
val qq = it.groups[1]?.value?.toLongOrNull()
|
||||||
if (qq != null && contact is Group) {
|
if (qq != null && contact is Group) {
|
||||||
@ -619,6 +505,16 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 图片
|
||||||
|
regexImage.findAll(content).forEach {
|
||||||
|
// val placeholder = it.groupValues[1]
|
||||||
|
val url = it.groupValues[2]
|
||||||
|
t.add(MessageChunk(
|
||||||
|
it.range,
|
||||||
|
Image(url)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeTeX渲染
|
||||||
regexLaTeX.findAll(content).forEach {
|
regexLaTeX.findAll(content).forEach {
|
||||||
it.groups.forEach { group ->
|
it.groups.forEach { group ->
|
||||||
if (group == null || group.value.isEmpty()) return@forEach
|
if (group == null || group.value.isEmpty()) return@forEach
|
||||||
@ -636,6 +532,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构造消息链
|
||||||
buildMessageChain {
|
buildMessageChain {
|
||||||
var index = 0
|
var index = 0
|
||||||
for ((range, msg) in t.sortedBy { it.range.start }) {
|
for ((range, msg) in t.sortedBy { it.range.start }) {
|
||||||
@ -663,6 +560,12 @@ object JChatGPT : KotlinPlugin(
|
|||||||
// 发送组合消息
|
// 发送组合消息
|
||||||
SendCompositeMessage(),
|
SendCompositeMessage(),
|
||||||
|
|
||||||
|
// 记忆代理
|
||||||
|
MemoryAppend(),
|
||||||
|
|
||||||
|
// 记忆修改
|
||||||
|
MemoryReplace(),
|
||||||
|
|
||||||
// 结束循环
|
// 结束循环
|
||||||
StopLoopAgent(),
|
StopLoopAgent(),
|
||||||
|
|
||||||
@ -686,44 +589,48 @@ object JChatGPT : KotlinPlugin(
|
|||||||
|
|
||||||
// Epic 免费游戏
|
// Epic 免费游戏
|
||||||
EpicFreeGame(),
|
EpicFreeGame(),
|
||||||
|
|
||||||
|
// 群管代理
|
||||||
|
GroupManageAgent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// private suspend fun chatCompletion(
|
||||||
// private fun chatCompletions(
|
|
||||||
// chatMessages: List<ChatMessage>,
|
// chatMessages: List<ChatMessage>,
|
||||||
// hasTools: Boolean = true
|
// hasTools: Boolean = true
|
||||||
// ): Flow<ChatCompletionChunk> {
|
// ): ChatMessage {
|
||||||
// val llm = this.llm ?: throw NullPointerException("OpenAI Token 未设置,无法开始")
|
// val llm = LargeLanguageModels.chat ?: 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
|
||||||
// val request = ChatCompletionRequest(
|
// val request = ChatCompletionRequest(
|
||||||
// model = ModelId(PluginConfig.chatModel),
|
// model = ModelId(PluginConfig.chatModel),
|
||||||
|
// temperature = PluginConfig.chatTemperature,
|
||||||
// messages = chatMessages,
|
// messages = chatMessages,
|
||||||
// tools = availableTools,
|
// tools = availableTools,
|
||||||
// )
|
// )
|
||||||
// logger.info("API Requesting... Model=${PluginConfig.chatModel}")
|
// logger.info("API Requesting... Model=${PluginConfig.chatModel}")
|
||||||
// return llm.chatCompletions(request)
|
// val response = llm.chatCompletion(request)
|
||||||
|
// val message = response.choices.first().message
|
||||||
|
// logger.info("Response: $message ${response.usage}")
|
||||||
|
// return message
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private suspend fun chatCompletion(
|
private fun chatCompletions(
|
||||||
chatMessages: List<ChatMessage>,
|
chatMessages: List<ChatMessage>,
|
||||||
hasTools: Boolean = true
|
hasTools: Boolean = true
|
||||||
): ChatMessage {
|
): Flow<ChatCompletionChunk> {
|
||||||
val llm = LargeLanguageModels.chat ?: throw NullPointerException("OpenAI Token 未设置,无法开始")
|
val llm = LargeLanguageModels.chat ?: 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
|
||||||
val request = ChatCompletionRequest(
|
val request = ChatCompletionRequest(
|
||||||
model = ModelId(PluginConfig.chatModel),
|
model = ModelId(PluginConfig.chatModel),
|
||||||
|
temperature = PluginConfig.chatTemperature,
|
||||||
messages = chatMessages,
|
messages = chatMessages,
|
||||||
tools = availableTools,
|
tools = availableTools,
|
||||||
)
|
)
|
||||||
logger.info("API Requesting... Model=${PluginConfig.chatModel}")
|
logger.info("API Requesting... Model=${PluginConfig.chatModel}")
|
||||||
val response = llm.chatCompletion(request)
|
return llm.chatCompletions(request)
|
||||||
val message = response.choices.first().message
|
|
||||||
logger.info("Response: $message ${response.usage}")
|
|
||||||
return message
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNameCard(member: Member): String {
|
private fun getNameCard(member: Member): String {
|
||||||
|
@ -45,6 +45,7 @@ object PluginCommands : CompositeCommand(
|
|||||||
@SubCommand
|
@SubCommand
|
||||||
suspend fun CommandSender.reload() {
|
suspend fun CommandSender.reload() {
|
||||||
PluginConfig.reload()
|
PluginConfig.reload()
|
||||||
|
PluginData.reload()
|
||||||
LargeLanguageModels.reload()
|
LargeLanguageModels.reload()
|
||||||
sendMessage("OK")
|
sendMessage("OK")
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,9 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
|||||||
@ValueDescription("Chat模型")
|
@ValueDescription("Chat模型")
|
||||||
var chatModel: String by value("qwen-max")
|
var chatModel: String by value("qwen-max")
|
||||||
|
|
||||||
|
@ValueDescription("Chat模型温度,默认为null")
|
||||||
|
var chatTemperature: Double? by value(null)
|
||||||
|
|
||||||
@ValueDescription("推理模型API")
|
@ValueDescription("推理模型API")
|
||||||
var reasoningModelApi: String by value("https://dashscope.aliyuncs.com/compatible-mode/v1/")
|
var reasoningModelApi: String by value("https://dashscope.aliyuncs.com/compatible-mode/v1/")
|
||||||
|
|
||||||
@ -47,6 +50,9 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
|||||||
@ValueDescription("好友是否自动拥有对话权限,默认是")
|
@ValueDescription("好友是否自动拥有对话权限,默认是")
|
||||||
val friendHasChatPermission: Boolean by value(true)
|
val friendHasChatPermission: Boolean by value(true)
|
||||||
|
|
||||||
|
@ValueDescription("机器人是否可以禁言别人,默认禁止")
|
||||||
|
val canMute: Boolean by value(false)
|
||||||
|
|
||||||
@ValueDescription("群荣誉等级权限门槛,达到这个等级相当于自动拥有对话权限。")
|
@ValueDescription("群荣誉等级权限门槛,达到这个等级相当于自动拥有对话权限。")
|
||||||
val temperaturePermission: Int by value(50)
|
val temperaturePermission: Int by value(50)
|
||||||
|
|
||||||
|
@ -1,7 +1,35 @@
|
|||||||
package top.jie65535.mirai
|
package top.jie65535.mirai
|
||||||
|
|
||||||
import net.mamoe.mirai.console.data.AutoSavePluginData
|
import net.mamoe.mirai.console.data.AutoSavePluginData
|
||||||
|
import net.mamoe.mirai.console.data.value
|
||||||
|
|
||||||
object PluginData : AutoSavePluginData("data") {
|
object PluginData : AutoSavePluginData("data") {
|
||||||
|
/**
|
||||||
|
* 联系人记忆
|
||||||
|
*/
|
||||||
|
val contactMemory by value(mutableMapOf<Long, String>())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加对话记忆
|
||||||
|
*/
|
||||||
|
fun appendContactMemory(contactId: Long, newMemory: String) {
|
||||||
|
val memory = contactMemory[contactId]
|
||||||
|
if (memory.isNullOrEmpty()) {
|
||||||
|
contactMemory[contactId] = newMemory
|
||||||
|
} else {
|
||||||
|
contactMemory[contactId] = "$memory\n$newMemory"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换对话记忆
|
||||||
|
*/
|
||||||
|
fun replaceContactMemory(contactId: Long, oldMemory: String, newMemory: String) {
|
||||||
|
val memory = contactMemory[contactId]
|
||||||
|
if (memory.isNullOrEmpty()) {
|
||||||
|
contactMemory[contactId] = newMemory
|
||||||
|
} else {
|
||||||
|
contactMemory[contactId] = memory.replace(oldMemory, newMemory)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
68
src/main/kotlin/tools/GroupManageAgent.kt
Normal file
68
src/main/kotlin/tools/GroupManageAgent.kt
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package top.jie65535.mirai.tools
|
||||||
|
|
||||||
|
import com.aallam.openai.api.chat.Tool
|
||||||
|
import com.aallam.openai.api.core.Parameters
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.add
|
||||||
|
import kotlinx.serialization.json.int
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonArray
|
||||||
|
import kotlinx.serialization.json.putJsonObject
|
||||||
|
import net.mamoe.mirai.contact.MemberPermission
|
||||||
|
import net.mamoe.mirai.event.events.GroupMessageEvent
|
||||||
|
import net.mamoe.mirai.event.events.MessageEvent
|
||||||
|
import top.jie65535.mirai.PluginConfig
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
class GroupManageAgent : BaseAgent(
|
||||||
|
tool = Tool.function(
|
||||||
|
name = "mute",
|
||||||
|
description = "可用于禁言指定群成员,只有你是管理员且目标非管理或群主时有效,非必要不要轻易禁言别人,否则你可能会被禁用这个特权!",
|
||||||
|
parameters = Parameters.buildJsonObject {
|
||||||
|
put("type", "object")
|
||||||
|
putJsonObject("properties") {
|
||||||
|
putJsonObject("target") {
|
||||||
|
put("type", "integer")
|
||||||
|
put("description", "目标QQ号")
|
||||||
|
}
|
||||||
|
putJsonObject("durationM") {
|
||||||
|
put("type", "integer")
|
||||||
|
put("description", "禁言时长(分钟,目前暂时只支持1~10分钟,后续视情况增加上限)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putJsonArray("required") {
|
||||||
|
add("target")
|
||||||
|
add("durationM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
override val isEnabled: Boolean
|
||||||
|
get() = PluginConfig.canMute
|
||||||
|
|
||||||
|
override suspend fun execute(args: JsonObject?, event: MessageEvent): String {
|
||||||
|
requireNotNull(args)
|
||||||
|
val target = args.getValue("target").jsonPrimitive.long
|
||||||
|
val duration = args.getValue("durationM").jsonPrimitive.int
|
||||||
|
if (event !is GroupMessageEvent) {
|
||||||
|
return "非群聊环境无法禁言"
|
||||||
|
}
|
||||||
|
if (event.group.botPermission == MemberPermission.MEMBER) {
|
||||||
|
return "你并非管理,无法禁言他人"
|
||||||
|
}
|
||||||
|
val member = event.group[target]
|
||||||
|
if (member == null) {
|
||||||
|
return "未找到目标群成员"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.isMuted) {
|
||||||
|
return "该目标已被禁言,还剩 " + member.muteTimeRemaining.seconds.toString() + " 解除。"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁言指定时长
|
||||||
|
member.mute(duration.coerceIn(1, 10) * 60)
|
||||||
|
return "已禁言目标"
|
||||||
|
}
|
||||||
|
}
|
41
src/main/kotlin/tools/MemoryAppend.kt
Normal file
41
src/main/kotlin/tools/MemoryAppend.kt
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package top.jie65535.mirai.tools
|
||||||
|
|
||||||
|
import com.aallam.openai.api.chat.Tool
|
||||||
|
import com.aallam.openai.api.core.Parameters
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.add
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonArray
|
||||||
|
import kotlinx.serialization.json.putJsonObject
|
||||||
|
import net.mamoe.mirai.event.events.MessageEvent
|
||||||
|
import top.jie65535.mirai.JChatGPT
|
||||||
|
import top.jie65535.mirai.PluginData
|
||||||
|
|
||||||
|
class MemoryAppend : BaseAgent(
|
||||||
|
tool = Tool.function(
|
||||||
|
name = "memoryAppend",
|
||||||
|
description = "新增记忆项",
|
||||||
|
parameters = Parameters.buildJsonObject {
|
||||||
|
put("type", "object")
|
||||||
|
putJsonObject("properties") {
|
||||||
|
putJsonObject("memory") {
|
||||||
|
put("type", "string")
|
||||||
|
put("description", "记忆项")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putJsonArray("required") {
|
||||||
|
add("memory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
override suspend fun execute(args: JsonObject?, event: MessageEvent): String {
|
||||||
|
requireNotNull(args)
|
||||||
|
val contactId = event.subject.id
|
||||||
|
val memoryText = args.getValue("memory").jsonPrimitive.content
|
||||||
|
JChatGPT.logger.info("Remember ($contactId): \"$memoryText\"")
|
||||||
|
PluginData.appendContactMemory(contactId, memoryText)
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
}
|
47
src/main/kotlin/tools/MemoryReplace.kt
Normal file
47
src/main/kotlin/tools/MemoryReplace.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package top.jie65535.mirai.tools
|
||||||
|
|
||||||
|
import com.aallam.openai.api.chat.Tool
|
||||||
|
import com.aallam.openai.api.core.Parameters
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.add
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonArray
|
||||||
|
import kotlinx.serialization.json.putJsonObject
|
||||||
|
import net.mamoe.mirai.event.events.MessageEvent
|
||||||
|
import top.jie65535.mirai.JChatGPT
|
||||||
|
import top.jie65535.mirai.PluginData
|
||||||
|
|
||||||
|
class MemoryReplace : BaseAgent(
|
||||||
|
tool = Tool.Companion.function(
|
||||||
|
name = "memoryReplace",
|
||||||
|
description = "替换记忆项",
|
||||||
|
parameters = Parameters.Companion.buildJsonObject {
|
||||||
|
put("type", "object")
|
||||||
|
putJsonObject("properties") {
|
||||||
|
putJsonObject("oldMemory") {
|
||||||
|
put("type", "string")
|
||||||
|
put("description", "原记忆项")
|
||||||
|
}
|
||||||
|
putJsonObject("newMemory") {
|
||||||
|
put("type", "string")
|
||||||
|
put("description", "新记忆项")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putJsonArray("required") {
|
||||||
|
add("oldMemory")
|
||||||
|
add("newMemory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
override suspend fun execute(args: JsonObject?, event: MessageEvent): String {
|
||||||
|
requireNotNull(args)
|
||||||
|
val contactId = event.subject.id
|
||||||
|
val oldMemoryText = args.getValue("oldMemory").jsonPrimitive.content
|
||||||
|
val newMemoryText = args.getValue("newMemory").jsonPrimitive.content
|
||||||
|
JChatGPT.logger.info("Replace memory ($contactId): \"$oldMemoryText\" -> \"$newMemoryText\"")
|
||||||
|
PluginData.replaceContactMemory(contactId, oldMemoryText, newMemoryText)
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user