mirror of
https://github.com/jie65535/JChatGPT.git
synced 2026-02-02 20:18:10 +08:00
Add context cache
This commit is contained in:
@@ -44,6 +44,7 @@ import kotlin.math.pow
|
|||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import kotlin.time.Duration.Companion.hours
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
object JChatGPT : KotlinPlugin(
|
object JChatGPT : KotlinPlugin(
|
||||||
JvmPluginDescription(
|
JvmPluginDescription(
|
||||||
@@ -116,6 +117,32 @@ object JChatGPT : KotlinPlugin(
|
|||||||
|
|
||||||
private val requestMap = ConcurrentSet<Long>()
|
private val requestMap = ConcurrentSet<Long>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话上下文缓存
|
||||||
|
*/
|
||||||
|
private val contextCache = ConcurrentMap<Long, ConversationCache>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有对话上下文缓存(供管理员命令使用)
|
||||||
|
*/
|
||||||
|
fun clearContextCache() {
|
||||||
|
contextCache.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话上下文缓存数据类
|
||||||
|
* @param history 完整的消息历史
|
||||||
|
* @param lastActivityAt 最后活动时间戳
|
||||||
|
*/
|
||||||
|
private data class ConversationCache(
|
||||||
|
val history: MutableList<ChatMessage>,
|
||||||
|
val lastActivityAt: Int
|
||||||
|
) {
|
||||||
|
fun isExpired(ttlSeconds: Int): Boolean {
|
||||||
|
return OffsetDateTime.now().toEpochSecond().toInt() - lastActivityAt > ttlSeconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun onMessage(event: MessageEvent) {
|
private suspend fun onMessage(event: MessageEvent) {
|
||||||
// 检查Token是否设置
|
// 检查Token是否设置
|
||||||
if (LargeLanguageModels.chat == null) return
|
if (LargeLanguageModels.chat == null) return
|
||||||
@@ -142,7 +169,8 @@ object JChatGPT : KotlinPlugin(
|
|||||||
// 如果没有 @bot 或者 触发关键字 或者 回复bot的消息 则直接结束
|
// 如果没有 @bot 或者 触发关键字 或者 回复bot的消息 则直接结束
|
||||||
if (!event.message.contains(At(event.bot))
|
if (!event.message.contains(At(event.bot))
|
||||||
&& keyword?.let { event.message.content.contains(it) } != true
|
&& keyword?.let { event.message.content.contains(it) } != true
|
||||||
&& event.message[QuoteReply]?.source?.fromId != event.bot.id)
|
&& event.message[QuoteReply]?.source?.fromId != event.bot.id
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
// 好感度系统检查
|
// 好感度系统检查
|
||||||
@@ -366,14 +394,14 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
historyText.appendLine(record.toMessageChain().joinToString("") {
|
historyText.appendLine(record.toMessageChain().joinToString("") {
|
||||||
when (it) {
|
when (it) {
|
||||||
is At -> {
|
is At -> {
|
||||||
it.getDisplay(event.subject)
|
it.getDisplay(event.subject)
|
||||||
}
|
|
||||||
|
|
||||||
else -> singleMessageToText(it)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
else -> singleMessageToText(it)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNameCard(group: Group, qq: Long): String {
|
private fun getNameCard(group: Group, qq: Long): String {
|
||||||
@@ -411,16 +439,19 @@ object JChatGPT : KotlinPlugin(
|
|||||||
val recordMessage = record.toMessageChain()
|
val recordMessage = record.toMessageChain()
|
||||||
recordMessage[QuoteReply.Key]?.let {
|
recordMessage[QuoteReply.Key]?.let {
|
||||||
historyText.append(" 引用\n > ")
|
historyText.append(" 引用\n > ")
|
||||||
.appendLine(it.source.originalMessage
|
.appendLine(
|
||||||
.joinToString("", transform = ::singleMessageToText)
|
it.source.originalMessage
|
||||||
.replace("\n", "\n > "))
|
.joinToString("", transform = ::singleMessageToText)
|
||||||
|
.replace("\n", "\n > ")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (showSender) {
|
if (showSender) {
|
||||||
historyText.append(" 说:")
|
historyText.append(" 说:")
|
||||||
}
|
}
|
||||||
// 消息内容
|
// 消息内容
|
||||||
historyText.appendLine(
|
historyText.appendLine(
|
||||||
record.toMessageChain().joinToString("", transform = ::singleMessageToText))
|
record.toMessageChain().joinToString("", transform = ::singleMessageToText)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun singleMessageToText(it: SingleMessage): String {
|
private fun singleMessageToText(it: SingleMessage): String {
|
||||||
@@ -457,18 +488,41 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val history = mutableListOf<ChatMessage>()
|
// 尝试从缓存加载上下文
|
||||||
|
val subjectId = event.subject.id
|
||||||
val prompt = getSystemPrompt(event)
|
val cache = contextCache[subjectId]
|
||||||
if (PluginConfig.logPrompt) {
|
val history = if (PluginConfig.enableContextCache
|
||||||
logger.info("Prompt: $prompt")
|
&& cache != null
|
||||||
|
&& !cache.isExpired(PluginConfig.contextCacheTimeoutMinutes * 60)
|
||||||
|
) {
|
||||||
|
// 缓存有效,复用历史
|
||||||
|
logger.info("使用缓存的对话上下文,包含 ${cache.history.size} 条互动消息")
|
||||||
|
cache.history
|
||||||
|
} else {
|
||||||
|
// 缓存无效或不存在,创建新上下文
|
||||||
|
mutableListOf()
|
||||||
}
|
}
|
||||||
history.add(ChatMessage(ChatRole.System, prompt))
|
|
||||||
|
|
||||||
val historyText = getHistory(event)
|
// 如果历史为空,添加系统提示词和聊天记录
|
||||||
logger.info("History: $historyText")
|
if (history.isEmpty() || cache == null) {
|
||||||
history.add(ChatMessage.User(historyText))
|
val prompt = getSystemPrompt(event)
|
||||||
|
if (PluginConfig.logPrompt) {
|
||||||
|
logger.info("Prompt: $prompt")
|
||||||
|
}
|
||||||
|
history.add(ChatMessage(ChatRole.System, prompt))
|
||||||
|
|
||||||
|
val historyText = getHistory(event)
|
||||||
|
logger.info("注入聊天记录:\n$historyText")
|
||||||
|
history.add(ChatMessage.User(historyText))
|
||||||
|
} else {
|
||||||
|
val newMessages = getAfterHistory(cache.lastActivityAt, event)
|
||||||
|
logger.info("补充聊天记录:\n$newMessages")
|
||||||
|
history.add(
|
||||||
|
ChatMessage.User(
|
||||||
|
"## 以下是上次对话结束至今的新消息\n\n$newMessages"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var done: Boolean
|
var done: Boolean
|
||||||
// 至少循环3次
|
// 至少循环3次
|
||||||
@@ -547,10 +601,12 @@ object JChatGPT : KotlinPlugin(
|
|||||||
val responseContent = responseMessageBuilder?.replace(thinkRegex, "")?.trim()
|
val responseContent = responseMessageBuilder?.replace(thinkRegex, "")?.trim()
|
||||||
logger.info("LLM Response: $responseContent")
|
logger.info("LLM Response: $responseContent")
|
||||||
// 记录AI回答
|
// 记录AI回答
|
||||||
history.add(ChatMessage.Assistant(
|
history.add(
|
||||||
content = responseContent,
|
ChatMessage.Assistant(
|
||||||
toolCalls = responseToolCalls
|
content = responseContent,
|
||||||
))
|
toolCalls = responseToolCalls
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// 处理最后一个工具调用
|
// 处理最后一个工具调用
|
||||||
if (responseToolCalls.size > toolCallTasks.size) {
|
if (responseToolCalls.size > toolCallTasks.size) {
|
||||||
@@ -563,6 +619,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
content = functionResponse
|
content = functionResponse
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolCallTasks.isNotEmpty()) {
|
if (toolCallTasks.isNotEmpty()) {
|
||||||
// 等待之前的所有工具完成
|
// 等待之前的所有工具完成
|
||||||
history.addAll(toolCallTasks.awaitAll())
|
history.addAll(toolCallTasks.awaitAll())
|
||||||
@@ -570,16 +627,17 @@ object JChatGPT : KotlinPlugin(
|
|||||||
// 将最后一个也加入对话历史中
|
// 将最后一个也加入对话历史中
|
||||||
history.add(toolCallMessage)
|
history.add(toolCallMessage)
|
||||||
// 如果调用中包含结束对话工具则表示完成,反之则继续循环
|
// 如果调用中包含结束对话工具则表示完成,反之则继续循环
|
||||||
done = history.any { it.name == "endConversation" }
|
done = responseToolCalls.any { it.function.name == "endConversation" }
|
||||||
} else {
|
} else {
|
||||||
done = true
|
done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
history.add(ChatMessage.User(
|
history.add(
|
||||||
|
ChatMessage.User(
|
||||||
buildString {
|
buildString {
|
||||||
appendLine("## 系统提示")
|
appendLine("## 系统提示")
|
||||||
append("本次运行最多还剩").append(retry-1).appendLine("轮。")
|
append("本次运行最多还剩").append(retry - 1).appendLine("轮。")
|
||||||
appendLine("如果要多次发言,可以一次性调用多次发言工具。")
|
appendLine("如果要多次发言,可以一次性调用多次发言工具。")
|
||||||
appendLine("如果没有什么要做的,可以提前结束。")
|
appendLine("如果没有什么要做的,可以提前结束。")
|
||||||
appendLine("当前时间:" + dateTimeFormatter.format(OffsetDateTime.now()))
|
appendLine("当前时间:" + dateTimeFormatter.format(OffsetDateTime.now()))
|
||||||
@@ -590,6 +648,15 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
// 保存对话上下文到缓存
|
||||||
|
if (PluginConfig.enableContextCache) {
|
||||||
|
contextCache[subjectId] = ConversationCache(
|
||||||
|
history = history,
|
||||||
|
lastActivityAt = startedAt
|
||||||
|
)
|
||||||
|
logger.debug("已保存对话上下文到缓存")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (retry <= 1) {
|
if (retry <= 1) {
|
||||||
@@ -607,7 +674,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
} finally {
|
} finally {
|
||||||
// 一段时间后才允许再次提问,防止高频对话
|
// 一段时间后才允许再次提问,防止高频对话
|
||||||
launch {
|
launch {
|
||||||
delay(1.seconds)
|
delay(500.milliseconds)
|
||||||
requestMap.remove(event.subject.id)
|
requestMap.remove(event.subject.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,9 +712,12 @@ object JChatGPT : KotlinPlugin(
|
|||||||
regexImage.findAll(content).forEach {
|
regexImage.findAll(content).forEach {
|
||||||
// val placeholder = it.groupValues[1]
|
// val placeholder = it.groupValues[1]
|
||||||
val url = it.groupValues[2]
|
val url = it.groupValues[2]
|
||||||
t.add(MessageChunk(
|
t.add(
|
||||||
it.range,
|
MessageChunk(
|
||||||
Image(url)))
|
it.range,
|
||||||
|
Image(url)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造消息链
|
// 构造消息链
|
||||||
|
|||||||
@@ -66,4 +66,10 @@ object PluginCommands : CompositeCommand(
|
|||||||
PluginData.userFavorability.clear()
|
PluginData.userFavorability.clear()
|
||||||
sendMessage("OK")
|
sendMessage("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
suspend fun CommandSender.clearContextCache() {
|
||||||
|
JChatGPT.clearContextCache()
|
||||||
|
sendMessage("已清空所有对话上下文缓存")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +84,12 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
|||||||
@ValueDescription("创建Prompt时取最多几条消息")
|
@ValueDescription("创建Prompt时取最多几条消息")
|
||||||
val historyMessageLimit: Int by value(20)
|
val historyMessageLimit: Int by value(20)
|
||||||
|
|
||||||
|
@ValueDescription("启用对话上下文内存缓存,允许在短时间内保持上下文连续")
|
||||||
|
val enableContextCache by value(true)
|
||||||
|
|
||||||
|
@ValueDescription("上下文缓存有效期(分钟),超过此时间未活动则重新创建上下文")
|
||||||
|
val contextCacheTimeoutMinutes by value(10)
|
||||||
|
|
||||||
@ValueDescription("是否打印Prompt便于调试")
|
@ValueDescription("是否打印Prompt便于调试")
|
||||||
val logPrompt by value(false)
|
val logPrompt by value(false)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user