mirror of
https://github.com/jie65535/JChatGPT.git
synced 2026-05-04 22:33:35 +08:00
Add token consumption tracking system Update version to v1.10.0
This commit is contained in:
123
FavorabilitySystem.md
Normal file
123
FavorabilitySystem.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# 好感度系统功能规范
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
为机器人添加一个可开关的好感度系统,通过AI工具自动调整用户的好感度值。好感度数据将保存在插件数据中,以用户QQ号为键,包含好感度值和调整原因。
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
### 1. 好感度数据存储
|
||||||
|
- 在`PluginData`中新增一个映射来存储好感度数据
|
||||||
|
- 键:用户QQ号(Long)
|
||||||
|
- 值:包含好感度值和调整原因的数据结构
|
||||||
|
- 默认值:0(中立)
|
||||||
|
|
||||||
|
### 2. 好感度变化规则
|
||||||
|
- **问正经问题**:+好感度(例如:询问学习/工作相关问题、寻求帮助等)
|
||||||
|
- **问无聊问题**:-好感度(例如:骚扰机器人要求评价他人、攻击性言论、让机器人做无意义的事情、引战问题等)
|
||||||
|
- **骂人**:直接降至-100
|
||||||
|
- **时间偏移**:好感度会随时间向0偏移,偏移速度与当前好感度绝对值相关
|
||||||
|
- 好感度越高或越低,偏移速度越慢
|
||||||
|
- 设计算法确保极端值变化缓慢(具体公式见实现细节)
|
||||||
|
|
||||||
|
### 3. 回复概率机制
|
||||||
|
- 当好感度为负数时,有一定概率不回复用户消息
|
||||||
|
- 概率计算:好感度绝对值的百分比
|
||||||
|
- 例如:好感度为-50,则有50%概率不回复(即50%概率回复)
|
||||||
|
|
||||||
|
### 4. 好感度调整工具
|
||||||
|
- 新增一个AI工具,允许AI根据对话内容自主调整用户的好感度
|
||||||
|
- 工具名称:`adjustUserFavorability`
|
||||||
|
- 工具参数:
|
||||||
|
- `userId`: 用户QQ号
|
||||||
|
- `change`: 好感度变化值(可正可负)
|
||||||
|
- `reason`: 调整原因(用于溯源)
|
||||||
|
- `impression`: 对用户的印象/画像(可选)
|
||||||
|
|
||||||
|
### 5. 系统开关
|
||||||
|
- 在配置文件中添加开关选项,控制是否启用好感度系统
|
||||||
|
- 默认启用
|
||||||
|
|
||||||
|
### 6. 管理员命令
|
||||||
|
- 添加插件命令手动修改某个人的好感度
|
||||||
|
- 添加命令重置所有好感度
|
||||||
|
|
||||||
|
## 实现细节
|
||||||
|
|
||||||
|
### 1. 数据结构
|
||||||
|
在`PluginData`中添加:
|
||||||
|
```kotlin
|
||||||
|
/**
|
||||||
|
* 用户好感度数据
|
||||||
|
* Key: 用户QQ号
|
||||||
|
* Value: 好感度信息
|
||||||
|
*/
|
||||||
|
val userFavorability by value(mutableMapOf<Long, FavorabilityInfo>())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好感度信息数据类
|
||||||
|
* @param value 好感度值 (-100 ~ 100)
|
||||||
|
* @param reason 调整原因列表,用于溯源
|
||||||
|
* @param impression 对用户的印象/画像
|
||||||
|
*/
|
||||||
|
data class FavorabilityInfo(
|
||||||
|
val value: Int = 0,
|
||||||
|
val reasons: List<String> = emptyList(),
|
||||||
|
val impression: String = ""
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 好感度工具
|
||||||
|
创建新的工具类`AdjustUserFavorabilityAgent`,继承`BaseAgent`。
|
||||||
|
工具描述:`根据用户行为调整其好感度值,范围-100~100`
|
||||||
|
|
||||||
|
### 3. 消息处理逻辑
|
||||||
|
在`JChatGPT.kt`的`onMessage`函数中:
|
||||||
|
- 添加好感度系统开关检查
|
||||||
|
- 在决定是否回复前,计算回复概率
|
||||||
|
- 如果随机数小于不回复概率,则直接返回,不进行后续处理
|
||||||
|
|
||||||
|
### 4. 时间偏移机制
|
||||||
|
设计时间偏移算法,使好感度逐渐向0回归:
|
||||||
|
- 偏移公式:`偏移量 = sign(好感度) * (1 - (|好感度| / 100)^2) * 基础偏移速度`
|
||||||
|
- 基础偏移速度可设置为每天1-5点
|
||||||
|
- 这样确保当好感度接近极端值时,变化速度会显著减慢
|
||||||
|
|
||||||
|
### 5. 配置选项
|
||||||
|
在`PluginConfig.kt`中添加:
|
||||||
|
```kotlin
|
||||||
|
/**
|
||||||
|
* 是否启用好感度系统
|
||||||
|
*/
|
||||||
|
val enableFavorabilitySystem by value(true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好感度每日基础偏移速度(点/天)
|
||||||
|
*/
|
||||||
|
val favorabilityBaseShiftSpeed by value(2.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 插件命令
|
||||||
|
添加以下命令:
|
||||||
|
- `/jgpt favorability <qq> <value>`: 设置指定QQ号的好感度值
|
||||||
|
- `/jgpt resetFavorability`: 重置所有用户的好感度为0
|
||||||
|
|
||||||
|
### 7. 提示词设计
|
||||||
|
不再使用系统提示词中的占位符,而是将好感度信息直接添加到聊天历史的顶部。
|
||||||
|
|
||||||
|
### 8. 好感度信息展示
|
||||||
|
- 不再使用系统提示词中的占位符
|
||||||
|
- 在获取历史消息时,将好感度信息作为摘要添加到聊天历史的顶部
|
||||||
|
- 格式示例:
|
||||||
|
```
|
||||||
|
[好感度摘要]
|
||||||
|
用户840465812(筱杰) 好感度: 75
|
||||||
|
印象: 热心的开发者,经常提供有用的建议
|
||||||
|
调整原因:
|
||||||
|
- 2025-09-10 14:30: 提供了关于代码优化的建议 +10
|
||||||
|
- 2025-09-09 10:15: 帮助测试新功能 +5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 待确认事项
|
||||||
|
|
||||||
|
1. 时间偏移的基础速度设定(每天多少点)
|
||||||
|
2. 好感度调整工具的具体参数和使用方式
|
||||||
122
README.md
122
README.md
@@ -8,6 +8,7 @@ JChatGPT 是一个基于 Kotlin 的 Mirai Console 插件,它将大型语言模
|
|||||||
- **丰富的工具系统**:包括网络搜索、代码执行、图像识别、群管理等
|
- **丰富的工具系统**:包括网络搜索、代码执行、图像识别、群管理等
|
||||||
- **上下文记忆**:支持持久化记忆存储
|
- **上下文记忆**:支持持久化记忆存储
|
||||||
- **好感度系统**:基于用户行为的好感度管理机制
|
- **好感度系统**:基于用户行为的好感度管理机制
|
||||||
|
- **Token消耗统计**:记录每次对话的Token消耗,支持多维度统计查询
|
||||||
- **LaTeX 渲染**:自动将数学表达式渲染为图片
|
- **LaTeX 渲染**:自动将数学表达式渲染为图片
|
||||||
- **灵活的触发方式**:@机器人、关键字触发、回复消息等
|
- **灵活的触发方式**:@机器人、关键字触发、回复消息等
|
||||||
- **权限控制**:细粒度的权限管理系统
|
- **权限控制**:细粒度的权限管理系统
|
||||||
@@ -37,12 +38,22 @@ AI 可以自动调用多种工具来完成复杂任务:
|
|||||||
|
|
||||||
## 命令列表
|
## 命令列表
|
||||||
|
|
||||||
- `/jgpt setToken <token>` - 设置 OpenAI API Token
|
### 基础命令
|
||||||
- `/jgpt enable <contact>` - 启用目标对话权限
|
- `/jgpt enable <contact>` - 启用目标对话权限
|
||||||
- `/jgpt disable <contact>` - 禁用目标对话权限
|
- `/jgpt disable <contact>` - 禁用目标对话权限
|
||||||
- `/jgpt reload` - 重载配置文件
|
- `/jgpt reload` - 重载配置文件
|
||||||
- `/jgpt favorability <userId> <value>` - 设置指定用户的好感度值(-100~100)
|
- `/jgpt clearMemory` - 清空所有对话记忆
|
||||||
- `/jgpt resetFavorability` - 重置所有用户的好感度
|
- `/jgpt clearContextCache` - 清空所有对话上下文缓存
|
||||||
|
|
||||||
|
### 好感度管理
|
||||||
|
- `/jgpt setFavor <user> <value>` - 设置指定用户的好感度值(-100~100)
|
||||||
|
- `/jgpt clearFavor` - 重置所有用户的好感度
|
||||||
|
|
||||||
|
### Token统计
|
||||||
|
- `/jgpt tokensDaily [days]` - 查看指定天数的每日Token消耗统计(默认7天)
|
||||||
|
- `/jgpt tokensUsers [limit]` - 查看Token消耗最多的用户排名(默认Top 10)
|
||||||
|
- `/jgpt tokensGroups [limit]` - 查看Token消耗最多的群组排名(默认Top 10)
|
||||||
|
- `/jgpt tokensQuery [userId] [days]` - 查询详细的使用记录(可按用户和时间过滤)
|
||||||
|
|
||||||
## 配置文件
|
## 配置文件
|
||||||
|
|
||||||
@@ -284,6 +295,111 @@ JChatGPT 插件包含一个可选的好感度系统,用于根据用户行为
|
|||||||
- `enableFavorabilitySystem` - 是否启用好感度系统(默认:true)
|
- `enableFavorabilitySystem` - 是否启用好感度系统(默认:true)
|
||||||
- `favorabilityBaseShiftSpeed` - 好感度每日基础偏移速度(点/天,默认:2.0)
|
- `favorabilityBaseShiftSpeed` - 好感度每日基础偏移速度(点/天,默认:2.0)
|
||||||
|
|
||||||
|
## Token消耗统计
|
||||||
|
|
||||||
|
JChatGPT 插件内置了Token消耗统计功能,可以记录每次对话的Token使用情况,并提供多维度统计查询。
|
||||||
|
|
||||||
|
### 功能特性
|
||||||
|
- **自动记录**:每次对话自动记录Token消耗
|
||||||
|
- **详细数据**:记录时间戳、用户、群组、模型、输入/输出Token数
|
||||||
|
- **多维统计**:支持按日期、用户、群组进行统计
|
||||||
|
- **灵活查询**:支持详细记录查询和过滤
|
||||||
|
|
||||||
|
### 记录内容
|
||||||
|
每次对话记录包含以下信息:
|
||||||
|
- 时间戳(Unix timestamp)
|
||||||
|
- 用户QQ号和昵称
|
||||||
|
- 群组ID(群聊)或null(私聊)
|
||||||
|
- 使用的模型名称
|
||||||
|
- 输入Token数(promptTokens)
|
||||||
|
- 输出Token数(completionTokens)
|
||||||
|
- 总Token数(totalTokens)
|
||||||
|
|
||||||
|
### 统计命令
|
||||||
|
|
||||||
|
#### 每日统计
|
||||||
|
```
|
||||||
|
/jgpt tokensDaily [days]
|
||||||
|
```
|
||||||
|
- 显示指定天数内的每日Token消耗统计
|
||||||
|
- 默认显示最近7天
|
||||||
|
- 输出示例:
|
||||||
|
```
|
||||||
|
最近 7 天 Token 使用统计:
|
||||||
|
|
||||||
|
2026-03-18: 15342 tokens
|
||||||
|
2026-03-17: 12890 tokens
|
||||||
|
2026-03-16: 9567 tokens
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 用户排名
|
||||||
|
```
|
||||||
|
/jgpt tokensUsers [limit]
|
||||||
|
```
|
||||||
|
- 显示Token消耗最多的用户排名
|
||||||
|
- 默认显示Top 10
|
||||||
|
- 输出示例:
|
||||||
|
```
|
||||||
|
Token 使用排名 Top 10:
|
||||||
|
|
||||||
|
张三(QQ:123456): 25430 tokens
|
||||||
|
李四(QQ:234567): 18920 tokens
|
||||||
|
王五(QQ:345678): 12450 tokens
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 群组排名
|
||||||
|
```
|
||||||
|
/jgpt tokensGroups [limit]
|
||||||
|
```
|
||||||
|
- 显示Token消耗最多的群组排名
|
||||||
|
- 默认显示Top 10
|
||||||
|
- 仅统计群聊对话,不包括私聊
|
||||||
|
- 输出示例:
|
||||||
|
```
|
||||||
|
群组 Token 使用排名 Top 10:
|
||||||
|
|
||||||
|
群 987654321: 45670 tokens
|
||||||
|
群 876543210: 32100 tokens
|
||||||
|
群 765432109: 28930 tokens
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 详细查询
|
||||||
|
```
|
||||||
|
/jgpt tokensQuery [userId] [days]
|
||||||
|
```
|
||||||
|
- 查询详细的使用记录
|
||||||
|
- 可按用户ID过滤(可选)
|
||||||
|
- 可指定时间范围(默认7天)
|
||||||
|
- 最多显示20条记录
|
||||||
|
- 输出示例:
|
||||||
|
```
|
||||||
|
最近 7 天使用记录(最多显示20条):
|
||||||
|
|
||||||
|
[03-18 14:35] 群987654321 - 张三
|
||||||
|
模型: qwen-max, Tokens: 2345 (提示: 1234, 完成: 1111)
|
||||||
|
|
||||||
|
[03-18 14:30] 私聊 - 李四
|
||||||
|
模型: qwen-max, Tokens: 1876 (提示: 980, 完成: 896)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据存储
|
||||||
|
- Token记录保存在插件数据目录的 `data/data.json` 文件中
|
||||||
|
- 使用 `AutoSavePluginData` 自动持久化
|
||||||
|
- 记录永久保存,不会自动删除
|
||||||
|
- 数据格式为JSON,可手动查看和备份
|
||||||
|
|
||||||
|
### 使用场景
|
||||||
|
- **成本监控**:了解API调用成本,控制预算
|
||||||
|
- **使用分析**:分析哪些用户或群组使用最频繁
|
||||||
|
- **性能优化**:识别高消耗对话,优化提示词
|
||||||
|
- **趋势分析**:观察使用趋势,规划资源
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
- 仅统计聊天模型的Token消耗
|
||||||
|
- 推理模型和视觉模型的消耗不在统计范围内
|
||||||
|
- 每次对话轮次都会单独记录
|
||||||
|
- 统计数据基于实际API返回的Token数
|
||||||
|
|
||||||
## 部署要求
|
## 部署要求
|
||||||
|
|
||||||
- Java 11 或更高版本
|
- Java 11 或更高版本
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "top.jie65535.mirai"
|
group = "top.jie65535.mirai"
|
||||||
version = "1.9.0"
|
version = "1.10.0"
|
||||||
|
|
||||||
mirai {
|
mirai {
|
||||||
jvmTarget = JavaVersion.VERSION_11
|
jvmTarget = JavaVersion.VERSION_11
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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.core.Usage
|
||||||
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.Deferred
|
||||||
@@ -51,7 +52,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
JvmPluginDescription(
|
JvmPluginDescription(
|
||||||
id = "top.jie65535.mirai.JChatGPT",
|
id = "top.jie65535.mirai.JChatGPT",
|
||||||
name = "J ChatGPT",
|
name = "J ChatGPT",
|
||||||
version = "1.9.0",
|
version = "1.10.0",
|
||||||
) {
|
) {
|
||||||
author("jie65535")
|
author("jie65535")
|
||||||
// dependsOn("xyz.cssxsh.mirai.plugin.mirai-hibernate-plugin", true)
|
// dependsOn("xyz.cssxsh.mirai.plugin.mirai-hibernate-plugin", true)
|
||||||
@@ -548,6 +549,7 @@ object JChatGPT : KotlinPlugin(
|
|||||||
var responseMessageBuilder: StringBuilder? = null
|
var responseMessageBuilder: StringBuilder? = null
|
||||||
val responseToolCalls = mutableListOf<ToolCall.Function>()
|
val responseToolCalls = mutableListOf<ToolCall.Function>()
|
||||||
val toolCallTasks = mutableListOf<Deferred<ChatMessage>>()
|
val toolCallTasks = mutableListOf<Deferred<ChatMessage>>()
|
||||||
|
var lastTokenUsage: Usage? = null
|
||||||
// 处理聊天流式响应
|
// 处理聊天流式响应
|
||||||
responseFlow.collect { chunk ->
|
responseFlow.collect { chunk ->
|
||||||
val delta = chunk.choices[0].delta ?: return@collect
|
val delta = chunk.choices[0].delta ?: return@collect
|
||||||
@@ -609,6 +611,9 @@ object JChatGPT : KotlinPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 捕获token使用量
|
||||||
|
chunk.usage?.let { lastTokenUsage = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除思考内容
|
// 移除思考内容
|
||||||
@@ -622,6 +627,23 @@ object JChatGPT : KotlinPlugin(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 记录token使用量
|
||||||
|
lastTokenUsage?.let { usage ->
|
||||||
|
val now = OffsetDateTime.now().toEpochSecond()
|
||||||
|
val groupId = if (event is GroupMessageEvent) event.subject.id else null
|
||||||
|
val record = TokenUsageRecord(
|
||||||
|
timestamp = now,
|
||||||
|
userId = event.sender.id,
|
||||||
|
userNickname = event.senderName,
|
||||||
|
groupId = groupId,
|
||||||
|
model = PluginConfig.chatModel,
|
||||||
|
promptTokens = usage.promptTokens ?: 0,
|
||||||
|
completionTokens = usage.completionTokens ?: 0,
|
||||||
|
totalTokens = usage.totalTokens ?: 0
|
||||||
|
)
|
||||||
|
PluginData.tokenUsageRecords.add(record)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理最后一个工具调用
|
// 处理最后一个工具调用
|
||||||
if (responseToolCalls.size > toolCallTasks.size) {
|
if (responseToolCalls.size > toolCallTasks.size) {
|
||||||
val toolCallMessage = responseToolCalls.last().let { toolCall ->
|
val toolCallMessage = responseToolCalls.last().let { toolCall ->
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import net.mamoe.mirai.contact.Group
|
|||||||
import net.mamoe.mirai.contact.Member
|
import net.mamoe.mirai.contact.Member
|
||||||
import net.mamoe.mirai.contact.User
|
import net.mamoe.mirai.contact.User
|
||||||
import top.jie65535.mirai.JChatGPT.reload
|
import top.jie65535.mirai.JChatGPT.reload
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
object PluginCommands : CompositeCommand(
|
object PluginCommands : CompositeCommand(
|
||||||
JChatGPT, "jgpt", description = "J OpenAI ChatGPT"
|
JChatGPT, "jgpt", description = "J OpenAI ChatGPT"
|
||||||
@@ -72,4 +76,132 @@ object PluginCommands : CompositeCommand(
|
|||||||
JChatGPT.clearContextCache()
|
JChatGPT.clearContextCache()
|
||||||
sendMessage("已清空所有对话上下文缓存")
|
sendMessage("已清空所有对话上下文缓存")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
suspend fun CommandSender.tokens() {
|
||||||
|
sendMessage("请使用子命令:daily, users, groups, query")
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
suspend fun CommandSender.tokensDaily(days: Int = 7) {
|
||||||
|
val now = Instant.now().epochSecond
|
||||||
|
val secondsPerDay = 86400
|
||||||
|
val cutoff = now - (days * secondsPerDay)
|
||||||
|
|
||||||
|
val dailyStats = PluginData.tokenUsageRecords
|
||||||
|
.filter { it.timestamp >= cutoff }
|
||||||
|
.groupBy {
|
||||||
|
LocalDate.ofInstant(
|
||||||
|
Instant.ofEpochSecond(it.timestamp),
|
||||||
|
ZoneId.systemDefault()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.mapValues { (_, records) ->
|
||||||
|
records.sumOf { it.totalTokens }
|
||||||
|
}
|
||||||
|
.toSortedMap()
|
||||||
|
|
||||||
|
if (dailyStats.isEmpty()) {
|
||||||
|
sendMessage("指定时间范围内无使用记录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = buildString {
|
||||||
|
appendLine("最近 $days 天 Token 使用统计:")
|
||||||
|
appendLine()
|
||||||
|
dailyStats.forEach { (date, total) ->
|
||||||
|
appendLine("$date: $total tokens")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendMessage(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
suspend fun CommandSender.tokensUsers(limit: Int = 10) {
|
||||||
|
val userStats = PluginData.tokenUsageRecords
|
||||||
|
.groupBy { it.userId }
|
||||||
|
.mapValues { (_, records) ->
|
||||||
|
Pair(
|
||||||
|
records.first().userNickname,
|
||||||
|
records.sumOf { it.totalTokens }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
.sortedByDescending { it.second.second }
|
||||||
|
.take(limit)
|
||||||
|
|
||||||
|
if (userStats.isEmpty()) {
|
||||||
|
sendMessage("暂无使用记录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = buildString {
|
||||||
|
appendLine("Token 使用排名 Top $limit:")
|
||||||
|
appendLine()
|
||||||
|
userStats.forEach {
|
||||||
|
appendLine("- ${it.second.first}(${it.first}): ${it.second.second} tokens")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendMessage(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
suspend fun CommandSender.tokensGroups(limit: Int = 10) {
|
||||||
|
val groupStats = PluginData.tokenUsageRecords
|
||||||
|
.filter { it.groupId != null }
|
||||||
|
.groupBy { it.groupId!! }
|
||||||
|
.mapValues { (_, records) ->
|
||||||
|
records.sumOf { it.totalTokens }
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
.sortedByDescending { it.second }
|
||||||
|
.take(limit)
|
||||||
|
|
||||||
|
if (groupStats.isEmpty()) {
|
||||||
|
sendMessage("暂无群组使用记录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = buildString {
|
||||||
|
appendLine("群组 Token 使用排名 Top $limit:")
|
||||||
|
appendLine()
|
||||||
|
groupStats.forEach { (groupId, total) ->
|
||||||
|
appendLine("- $groupId: $total tokens")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendMessage(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
suspend fun CommandSender.tokensQuery(userId: Long?, days: Int = 7) {
|
||||||
|
val now = Instant.now().epochSecond
|
||||||
|
val cutoff = now - (days * 86400)
|
||||||
|
|
||||||
|
val filtered = PluginData.tokenUsageRecords
|
||||||
|
.filter { it.timestamp >= cutoff }
|
||||||
|
.filter { userId == null || it.userId == userId }
|
||||||
|
.sortedByDescending { it.timestamp }
|
||||||
|
.take(20)
|
||||||
|
|
||||||
|
if (filtered.isEmpty()) {
|
||||||
|
sendMessage("指定时间范围内无使用记录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = buildString {
|
||||||
|
appendLine("最近 $days 天使用记录(最多显示20条):")
|
||||||
|
appendLine()
|
||||||
|
filtered.forEach { record ->
|
||||||
|
val time = Instant.ofEpochSecond(record.timestamp)
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.format(DateTimeFormatter.ofPattern("MM-dd HH:mm"))
|
||||||
|
val location = if (record.groupId != null) "群${record.groupId}" else "私聊"
|
||||||
|
appendLine("[$time] $location - ${record.userNickname}")
|
||||||
|
appendLine(" 模型: ${record.model}, Tokens: ${record.totalTokens} " +
|
||||||
|
"(输入: ${record.promptTokens}, 输出: ${record.completionTokens})")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendMessage(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,29 @@ data class FavorabilityInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token使用记录数据类
|
||||||
|
* @param timestamp Unix时间戳
|
||||||
|
* @param userId 用户QQ号
|
||||||
|
* @param userNickname 用户昵称
|
||||||
|
* @param groupId 群号(私聊时为null)
|
||||||
|
* @param model 模型名称
|
||||||
|
* @param promptTokens 输入token数
|
||||||
|
* @param completionTokens 输出token数
|
||||||
|
* @param totalTokens 总token数
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class TokenUsageRecord(
|
||||||
|
val timestamp: Long,
|
||||||
|
val userId: Long,
|
||||||
|
val userNickname: String,
|
||||||
|
val groupId: Long?,
|
||||||
|
val model: String,
|
||||||
|
val promptTokens: Int,
|
||||||
|
val completionTokens: Int,
|
||||||
|
val totalTokens: Int
|
||||||
|
)
|
||||||
|
|
||||||
object PluginData : AutoSavePluginData("data") {
|
object PluginData : AutoSavePluginData("data") {
|
||||||
/**
|
/**
|
||||||
* 联系人记忆
|
* 联系人记忆
|
||||||
@@ -47,6 +70,11 @@ object PluginData : AutoSavePluginData("data") {
|
|||||||
*/
|
*/
|
||||||
val userFavorability by value(mutableMapOf<Long, FavorabilityInfo>())
|
val userFavorability by value(mutableMapOf<Long, FavorabilityInfo>())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token使用记录
|
||||||
|
*/
|
||||||
|
val tokenUsageRecords by value(mutableListOf<TokenUsageRecord>())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加对话记忆
|
* 添加对话记忆
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user