Metadata-Version: 2.5
Name: tenko-plugin-persona-chat
Version: 0.1.7
Summary: Tenko AI 群友：接入 DeepSeek，用人设口吻陪群友聊天，内置“大肥鱼”
Project-URL: Homepage, https://github.com/g1331/tenko-plugins/tree/main/plugins/persona_chat
Project-URL: Repository, https://github.com/g1331/tenko-plugins
License-Expression: GPL-3.0-only
Keywords: chatbot,deepseek,llm,qq,tenko,tenko-plugin
Classifier: Framework :: AsyncIO
Classifier: Natural Language :: Chinese (Simplified)
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Communications :: Chat
Requires-Python: <3.14,>=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: pillow>=10
Requires-Dist: tenko-api<0.5,>=0.4.1
Description-Content-Type: text/markdown

# tenko-plugin-persona-chat

[Tenko](https://github.com/g1331/tenko) 的 AI 群友插件。接入 DeepSeek（或任何 OpenAI 兼容接口），用人设口吻陪群友聊天，内置人设是“大肥鱼”——网友给 DeepSeek 起的外号“蓝色大肥鱼”拟人成的鲸鱼娘，傲娇、蠢萌、兴趣偏科，不懂的事老实说不懂或者先搜一下。

```text
群友 A：@大肥鱼 听说你高峰期又涨价了？
大肥鱼：什么叫“又”啊，说得好像我天天趁火打劫一样……排队排到鱼尾都打结了，不加钱谁顶得住嘛
```

## 规则

- 群里被 @、被回复，或群友打的字里含唤醒词（默认“大肥鱼”；@ 或引用一个同名群友不算）时一定回复；@ 同群任一 Tenko 账号都算。只有要接的那条后面已经有别人说话时才引用它，否则直接发；私聊每条都回复。
- bot 说过话后的 3 分钟内算在聊天里：每攒两条新消息，等群里安静 3 秒，让模型判断要不要接着聊。
- bot 会累：10 分钟内说满 15 句（`stamina`）或 1 小时内说满 30 句算累，参照的是活跃群友的说话量。用掉三分之二（10 分钟 10 句或 1 小时 20 句）后不再主动开口，只在被叫到时回话；累了以后被叫到也由它决定理不理，可能简短回一句或说要去歇会儿；到 1.5 倍后完全不理。不说话就慢慢恢复。
- 没在聊天时，群里累计 8 条新消息后同样等安静 3 秒，让模型判断要不要插一句；这样插过话后 5 分钟内不再这样插话。
- 模型看到插件启用以来的聊天记录，格式是 `#编号 [昵称(QQ号) 身份] 内容`（身份取宿主的成员权限：群主、群管理、bot管理员、bot主人，普通成员不标；昵称冒充骗不了它），规则后面告诉它当前群名，像 QQ 一样隔了 5 分钟以上的消息前面标一行时间，每次请求的提示里带当前时间（含年份，模型才不会按训练数据当成往年去搜）；编号只用来定位消息，模型说话时不会提它；@ 显示为昵称，引用了已不在记录里的消息时带上作者和开头。一次请求的输入超过 `context_tokens`（默认 16000）后，较早的部分由模型整理成一份备忘（人物、话题、梗和约定），只留最近约三分之一原文。记录只追加不改动，请求前缀大多能命中缓存。聊天记录和备忘存在插件数据目录的 `conversations.json`（明文）：她每说完一次就存，其余时候有新消息最多每分钟存一次，插件卸载或退出时再存一次；重启后某个群第一次来消息时恢复，接着之前的编号聊。图片地址会过期，恢复后只剩 `[图片]`；精力、冷却等计时从零开始。`/AI群友 重置` 同时删掉存盘。
- 最近 3 张图片（`vision_images`）在请求时下载，以低清晰度和所属消息一起发给模型看，三张约 600 token；动图（GIF）模型只看得到第一帧，所以均匀抽至多 6 帧、缩小后拼成一张 3×2 的图发给它，并注明是按时间顺序抽的帧，费用和一张图相同（做法参考 MaiBot）。更早的、超过 2 MB（动图 8 MB）的或下载失败的图片只显示为 `[图片]`。带图请求被接口拒绝时去掉图片重试一次。
- 表情包：插件经 OneBot 原始接口 `get_msg` 区分表情包和照片（satori 适配器转换时丢了这个字段，拿不到时全当照片）。模型看到觉得好玩的表情包会悄悄调用 `save_sticker` 收藏，只收它这次真看到的表情包，不收照片和截图；收藏时写一段 60 字以内的备注：图上的字、画面、背后的梗和什么时候发。收藏所有群共用，存在插件数据目录的 `stickers/` 下，上限 `sticker_limit` 张，满了先丢用得最少的。群友指出某张用错了或讲了它的梗时，模型先对照图上的字、自己知道的或搜索结果判断，确认原来写错了才调用 `edit_sticker` 改备注或名字，每次修改都记日志（会话、改前改后）。收藏目录（名字和备注）跟在群名后面发给模型，群友玩梗时它会挑对应的图接梗，收藏里已有的表情包再出现时显示为 `[表情包:名字]`，不再发图；叫它的那条消息里带的除外，多半是要它看这张，光凭备注容易认错。模型在回复里写 `[表情:名字]` 就发对应的表情包，可以单独发、先字后图或同一条图文混排；每次最多一张，同一张在一个群里一段时间内不重复（收藏每多一张多 1 分钟，最长半小时），整条回复只有这张表情包时例外，编造的名字直接删掉；它把某张图上的字原样打成一整条（如“得加钱！”）时，改发那张图。表情包经 NapCat 的 `send_group_msg` 带 `sub_type=1` 发出，显示成表情；接口不可用时改发普通图片。
- 被叫到、被问到不确定的具体事实时，模型可以先调用 `search`，插件用 DeepSeek 的网页搜索（`/v1/tools/search`，和对话共用 API key，每次约 0.005 元，搜不到不收费）取前 4 条结果的摘要，再让模型照结果回复，说成“我搜了下”。主动插话和跟聊时不搜；每个群或私聊每天最多搜 `search_daily` 次（默认 20，次数只记在内存里，重启清零）。允许搜索时，被叫到的第一轮不强制只能说话；模型既没搜也没说话时再强制问一次。
- 模型每次必须在 `send_message`（往当前会话发文字；一次最多调用两次，每次可以换行分成至多三条连着发，中间按打字速度停顿）和 `stay_silent`（不说话）之间选一个，不能做任何管理动作；它写在普通回复里的文字一律丢弃。要 @ 人时它填对方某条消息的编号；像群友那样叫别的 bot（如生图）时它在 text 开头写“@昵称 指令”，昵称对上记录里说过话的人才换成真的 @，对不上的照原样当文字发。规则只告诉它自己只能发文字和表情包、别的 bot 能照群友的用法去叫，具体叫哪个、发什么命令由它从聊天记录里学。
- 有人在 bot 说完 10 秒内又来叫（多半是另一个 bot 在对聊），连续 3 轮后改由模型决定回不回，连续 6 轮后不再理会，直到对方慢下来。
- 同一个人叫 bot 至少间隔 5 秒；每个群每天最多用 20 万 token（未命中缓存的输入加输出；命中缓存的只按约 2% 计价，不计入），可另设全局每日上限，超出后当天不再应答。
- 命令消息（`/` 开头）会记进上下文，但不会触发回复。Tenko 功能从这些账号发出的回复（游戏出题、查询结果等）经 Entari 的 `after_send` 事件记进群聊记录，标作 `[Tenko 功能]`，只留文字、每条截断到 100 字，图片记成 `[图片]`，不算新消息，不会催模型插话；模型自己发的话不重复记录。规则后面告诉模型本群能响应的 bot 账号（昵称每次现取，改名后自动跟上）；记录里有别人的昵称含唤醒词（比如另一个叫“大肥鱼bot”的号）时，再点名告诉它那个号不是它，免得把对方的话和状态当成自己的和帮助命令，被问到功能时让对方看帮助。

插件消耗付费额度，是受控插件：所有群默认关闭，只有 BOT 管理员能用 `/开启 AI群友` 打开；私聊也只对 BOT 管理员开放。

## 命令

| 命令 | 权限 | 说明 |
| --- | --- | --- |
| `/AI群友 重置` | 群里需群管理员；私聊本人 | 清空本群或本私聊的聊天记录 |
| `/AI群友 用量` | BOT 管理员 | 今日和近 7 天的 token 用量与估算费用 |
| `/AI群友 表情` | 群里需群管理员 | 列出收藏的表情包和使用次数 |
| `/AI群友 表情 看 名字` | 群里需群管理员 | 发出这张收藏的表情包 |
| `/AI群友 表情 删 名字` | BOT 管理员 | 删掉一张收藏（所有群共用） |

## 安装

在 WebUI 的插件市场安装，或由 Master 在聊天里 `/安装插件 persona_chat`。装好后默认不启用，`/启用插件 persona_chat` 即可加载。

## 配置

`config/tenko.toml` 的 `[plugin_config.persona_chat]`，也可以在 WebUI 的插件配置里修改。`api_key` 必填，只写在本机配置里，不要提交到任何仓库：

```toml
[plugin_config.persona_chat]
api_key = "sk-..."
```

| 配置 | 默认值 | 说明 |
| --- | --- | --- |
| `base_url` | `"https://api.deepseek.com"` | OpenAI 兼容接口地址 |
| `api_key` | `""` | 接口密钥，为空时不应答 |
| `model` | `"deepseek-flash"` | 模型名 |
| `wake_words` | `["大肥鱼"]` | 消息含这些词时当作在叫 bot |
| `persona` | `""` | 人设提示词，为空时用内置“大肥鱼” |
| `context_tokens` | `16000` | 请求输入超过这么多 token 后把较早的聊天压成备忘 |
| `batch_size` | `8` | 没在聊天时累计多少条新消息后考虑插话，`0` 关闭 |
| `follow_seconds` | `180` | bot 说话后多久之内每条新消息都考虑接话，`0` 关闭 |
| `quiet_seconds` | `3` | 主动说话前等群里安静多久 |
| `unprompted_interval_seconds` | `300` | 没在聊天时两次插话的最短间隔 |
| `user_cooldown_seconds` | `5` | 同一个人叫 bot 的最短间隔 |
| `stamina` | `15` | 精力：10 分钟内说多少句算累（1 小时按两倍），`0` 不限 |
| `vision_images` | `3` | 最近多少张图片发给模型看，`0` 只用 `[图片]` 文字 |
| `search_daily` | `20` | 每个群或私聊每天最多上网搜几次，`0` 关闭搜索 |
| `sticker_limit` | `100` | 表情包收藏上限，所有群共用，`0` 关闭表情包 |
| `max_reply_chars` | `120` | 单条回复字数上限，超出时在句末截断 |
| `group_daily_tokens` | `200000` | 每个群每天的 token 上限，`0` 不限 |
| `global_daily_tokens` | `0` | 全部会话合计每天的 token 上限，`0` 不限 |

请求关闭了 DeepSeek 的思考模式，人设和规则固定放在请求最前面以命中前缀缓存。`/AI群友 用量` 的费用按 `deepseek-flash` 官方价目估算（工作日 UTC 01–04、06–10 点为高峰价），换用其他模型时只看 token 数即可。

## 图标

`icon.png` 由 [Phosphor Icons](https://phosphoricons.com) 的 `fish`（fill）渲染而来，Phosphor Icons 以 MIT 许可证发布。
