ErisPulse-QvQStalker 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. erispulse_qvqstalker-0.1.0/ErisPulse_QvQStalker.egg-info/PKG-INFO +137 -0
  2. erispulse_qvqstalker-0.1.0/ErisPulse_QvQStalker.egg-info/SOURCES.txt +23 -0
  3. erispulse_qvqstalker-0.1.0/ErisPulse_QvQStalker.egg-info/dependency_links.txt +1 -0
  4. erispulse_qvqstalker-0.1.0/ErisPulse_QvQStalker.egg-info/entry_points.txt +2 -0
  5. erispulse_qvqstalker-0.1.0/ErisPulse_QvQStalker.egg-info/requires.txt +5 -0
  6. erispulse_qvqstalker-0.1.0/ErisPulse_QvQStalker.egg-info/top_level.txt +1 -0
  7. erispulse_qvqstalker-0.1.0/PKG-INFO +137 -0
  8. erispulse_qvqstalker-0.1.0/QvQStalker/Config.py +134 -0
  9. erispulse_qvqstalker-0.1.0/QvQStalker/Main.py +560 -0
  10. erispulse_qvqstalker-0.1.0/QvQStalker/__init__.py +10 -0
  11. erispulse_qvqstalker-0.1.0/QvQStalker/decision.py +246 -0
  12. erispulse_qvqstalker-0.1.0/QvQStalker/fewshot.py +173 -0
  13. erispulse_qvqstalker-0.1.0/QvQStalker/heat.py +91 -0
  14. erispulse_qvqstalker-0.1.0/QvQStalker/observe.py +125 -0
  15. erispulse_qvqstalker-0.1.0/QvQStalker/proactive.py +346 -0
  16. erispulse_qvqstalker-0.1.0/QvQStalker/texts.py +194 -0
  17. erispulse_qvqstalker-0.1.0/README.md +123 -0
  18. erispulse_qvqstalker-0.1.0/pyproject.toml +39 -0
  19. erispulse_qvqstalker-0.1.0/setup.cfg +4 -0
  20. erispulse_qvqstalker-0.1.0/tests/test_decision.py +331 -0
  21. erispulse_qvqstalker-0.1.0/tests/test_fewshot.py +79 -0
  22. erispulse_qvqstalker-0.1.0/tests/test_heat.py +69 -0
  23. erispulse_qvqstalker-0.1.0/tests/test_main.py +315 -0
  24. erispulse_qvqstalker-0.1.0/tests/test_plugin_settings.py +97 -0
  25. erispulse_qvqstalker-0.1.0/tests/test_window.py +83 -0
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: ErisPulse-QvQStalker
3
+ Version: 0.1.0
4
+ Summary: ErisPulse 群聊参与决策插件:旁听群消息、五层插话判定、冲动值主动发言 —— Group participation decision plugin for ErisPulse (QvQChat companion)
5
+ License: MIT
6
+ Project-URL: homepage, https://github.com/wsu2059q/ErisPulse-QvQStalker
7
+ Project-URL: Issues, https://github.com/wsu2059q/ErisPulse-QvQStalker/issues
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: ErisPulse>=2.8.0
11
+ Provides-Extra: test
12
+ Requires-Dist: pytest>=8.0; extra == "test"
13
+ Requires-Dist: pytest-asyncio>=0.23; extra == "test"
14
+
15
+ <div align="center">
16
+
17
+ 简体中文 | [繁體中文](README.zh-TW.md) | [English](README.en.md) | [日本語](README.ja.md) | [Русский](README.ru.md)
18
+
19
+ <img src="https://raw.githubusercontent.com/ErisPulse/ErisPulse/main/.github/assets/ErisPulseLogo.png" width="180" alt="QvQStalker" />
20
+
21
+ <img src="https://raw.githubusercontent.com/ErisPulse/.github/main/assets/badges/module.svg" alt="ErisPulse Module" />
22
+
23
+ </div>
24
+
25
+ # ErisPulse-QvQStalker
26
+
27
+ **[QvQChat](https://github.com/wsu2059q/ErisPulse-QvQChat) 伴侣插件:让机器人在群里像个真成员——平时旁听,自己判断哪句话值得接,没人搭理它时也会忍不住开口。**
28
+
29
+ 多数机器人只有被 @ 或命中前缀才会说话,真人群友不是这样:整天挂在群里看消息,偶尔有人问到懂的话题就插一句,有时候实在憋不住还会自己起话头。QvQStalker 给 QvQChat 补上的就是后面这种行为。QvQChat 会在命令过滤之后、触发判定之前,把每条群消息以 `qvq.observe` 生命周期事件广播出来;本插件据此为每个会话保留一小段最近消息窗口、跟踪话题热度。当一条消息既没 @ 机器人也没命中前缀时,QvQChat 会来问询本插件的 `qvq_decision` 服务要不要接话。另外还有一套冲动值模型,决定机器人什么时候在完全没人理它的情况下主动开口。
30
+
31
+ 算法移植自 QvQChat 3.x 的窥屏模式(`should_reply` 五层判定加 proactive 冲动值模型),但刻意没有塞进 QvQChat 本体。参与决策是种很有"性格"的行为——有人想要,有人坚决不要——所以做成独立插件,零 QvQChat 包依赖,所有交互只走 `sdk.lifecycle` / `sdk.module.call` / `sdk.storage` 三个框架接口。两个模块可以各自安装、升级、卸载,任何一方缺席或版本偏旧,另一方都照常工作。
32
+
33
+ 安全是默认状态,不是可选项。参与决策在你亲手打开 QvQChat 的 `participation_enabled` 开关之前一直关闭。插件触发的每次发言仍要过 QvQChat 的 SpeechBudget——每群每小时上限(默认 8 条)、同群冷却(默认 300 秒)、主动发言全局每日上限——插件一条也绕不过去。在此之上插件还有自己的一层自律:每日配额、单会话上限、作息时段,以及对"开口后无人回应的群"的冷却。每次决策都会返回一句人类可读的 `reason`,在 Dashboard 里随时能看清机器人为什么说话、为什么沉默。
34
+
35
+ <p>
36
+
37
+ <a href="https://www.python.org/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
38
+
39
+ <a href="https://github.com/ErisPulse/ErisPulse"><img src="https://img.shields.io/badge/ErisPulse-2.8.0+-00A3FF?style=for-the-badge" alt="ErisPulse"></a>
40
+
41
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
42
+
43
+ </p>
44
+
45
+ ## 安装
46
+
47
+ ```bash
48
+ epsdk install ErisPulse-QvQStalker
49
+ ```
50
+
51
+ 或
52
+
53
+ ```bash
54
+ pip install ErisPulse-QvQStalker
55
+ ```
56
+
57
+ 装到与 QvQChat 相同的 ErisPulse 实例即可。要求 ErisPulse >= 2.8.0、Python >= 3.10、QvQChat >= 4.0.4(提供 `qvq.observe` 事件与 `speak` 服务的版本)。两个模块互相不 import。
58
+
59
+ ## 启用
60
+
61
+ 1. 在 Dashboard 的 QvQChat 设置页打开参与决策开关(`participation_enabled`,默认关闭);旁边的群白名单可限定生效范围,留空即全部群。
62
+ 2. QvQStalker 自己的设置项出现在同一页面的「插件」分区——模式、概率、阈值、作息都在这里调。保存的值在加载时拉取、保存时热更新,多数改动无需重启(个别项如窗口大小在模块加载时读取)。
63
+ 3. 到此为止。此后群里没 @ 机器人、也没命中前缀的消息,都会先交给 QvQStalker 的 `qvq_decision` 问询;回答 `speak` 时 QvQChat 就接话。
64
+
65
+ ## 工作原理
66
+
67
+ ### 旁听
68
+
69
+ QvQChat 在命令过滤后、触发判定前,为每条群消息广播 `qvq.observe` 事件。载荷是纯数据(`chat_key` / `platform` / `sender_id` / `sender_name` / `text` / `is_group` / `at_bot` / `ts` 等),插件只读,绝不把事件标记为已处理,也绝不抛错。每条消息进入按会话隔离的环形窗口(默认保留最近 50 条),供四处消费:热度计算、沉寂判定、小模型判断的 prompt、主动发言的摘要。如果 QvQChat 版本较老还没有这个事件,模块照常加载,只是收不到消息,升级后重载即恢复。
70
+
71
+ ### 话题热度
72
+
73
+ 每个会话有一个 0 到 2.0 的热度值。每条消息先加基础 0.05,聊得越快加得越多:与上一条间隔小于 5 秒 +0.15,小于 15 秒 +0.08,小于 30 秒 +0.03;问号 +0.1,感叹号 +0.05。累加之前,旧热度会先按安静时长衰减——每满 1 分钟最多衰减一半——凉掉的话题自然冷却。
74
+
75
+ ### 五层判定
76
+
77
+ QvQChat 问询 `qvq_decision(ctx)` 时,答案沿五层逐层下沉。L1 是零成本正则:消息看着像提问——中文疑问词(怎么 / 为什么 / 什么 / 是不是 / 吗 / 呢 等)或以问号结尾——就以 `question_prob` 概率参与,默认 0.6,话题热时 +0.3,封顶 0.95。L2 和 L3 自己不做决定,只竖"升级旗":L2 在热度超过 `heat_flag_threshold` 时按 min(热度×0.3, 0.7) 的概率竖旗(免得不温不火的话题回回都升级),L3 在最新一条消息打破的沉寂超过 `silence_threshold_minutes` 分钟时竖旗。旗竖着且 `llm_judge` 开启时轮到 L5:带上最近 10 条旁听,让 QvQChat 的模型回答一道二选一——"这句话该不该接?"——8 秒超时,超时或解析不了就回落 L4。L4 是所有路径最终落地的概率层:距上次尝试至少观察满 `min_messages_between_replies` 条消息后掷骰,`default_probability` × 模式倍率(conservative ×0、balanced ×1、active ×2)加热度加成决定。conservative 模式把骰子归零,只可能从提问层或小模型判定开口。
78
+
79
+ 无论结果如何,返回值形状一致,reason 都是写给人看的:
80
+
81
+ ```python
82
+ {
83
+ "decision": "speak", # "speak" | "silent"
84
+ "reason": "提问消息命中关键词(热度 1.24),以 0.90 概率参与",
85
+ "score": 0.9, # 0 ~ 1
86
+ "urgency": "high", # "low" | "normal" | "high"
87
+ }
88
+ ```
89
+
90
+ (reason 内置中文与英文两套文案。)
91
+
92
+ ### 冲动值
93
+
94
+ 除了被动接话,插件为每个会话维护一个冲动值,半衰期 1 小时——群越冷清,冲动越淡。每条旁听消息都会累积:基础 0.06,提问 +0.12,感叹号 +0.05,长消息(≥20 字)+0.04,被 @ 或被回复 +0.15(想回又没回上的那股劲),另加少量热度加成。后台循环大约每 30 分钟(×0.7-1.3 随机抖动)醒来过一遍门槛:作息时段、全局每日上限、距该会话上次开口 ≥ `min_silence_hours`、冲动值 ≥ `urge_threshold`、单会话每日上限、未回复冷却——上次开口后群里再没动静,就再憋 `unreplied_cooldown_hours` 小时。全部通过时,本轮只开一次口,挑冲动值最高的会话,以最近 10 条旁听为上下文调用 QvQChat 的 `speak` 服务。这次调用仍要过 QvQChat 的预算;服务缺失或失败时记一条 warning,冲动值减半避免每轮重试,也不消耗配额。
95
+
96
+ ## 配置
97
+
98
+ 全部配置在 `QvQStalker` 模块配置下,在 QvQChat 设置页「插件」分区直接编辑(同时也是 ErisPulse Dashboard 里的普通模块表单)。
99
+
100
+ | 字段 | 默认 | 说明 |
101
+ |---|---|---|
102
+ | `mode` | `balanced` | 参与模式:`conservative` 保守(不随机插话,仅提问层与小模型判定会开口)/ `balanced` 平衡 / `active` 积极(基础概率翻倍) |
103
+ | `default_probability` | `0.03` | balanced 档位的基础插话概率 |
104
+ | `question_prob` | `0.6` | 提问消息的回复概率(热度 > 0.5 时 +0.3,封顶 0.95) |
105
+ | `min_messages_between_replies` | `15` | 两次插话尝试之间至少观察的消息数(防刷屏) |
106
+ | `silence_threshold_minutes` | `30` | 消息打破的沉寂超过该分钟数时竖升级旗 |
107
+ | `heat_flag_threshold` | `0.8` | 热度超过该值(另有概率门)时竖升级旗 |
108
+ | `llm_judge` | `false` | 升级旗竖起时交给 QvQChat 的模型做最终判断 |
109
+ | `judge_model` | 空 | 判断所用模型,留空跟随 QvQChat 默认模型 |
110
+ | `judge_timeout` | `8.0` | 判断超时秒数,超时回落概率层 |
111
+ | `heat_cap` | `2.0` | 话题热度上限 |
112
+ | `heat_bonus_cap` | `0.15` | 概率层热度加成上限 |
113
+ | `sleep_hour` | `2` | 主动发言入睡小时(0-23;与 `wake_hour` 相等表示不限制作息) |
114
+ | `wake_hour` | `8` | 主动发言起床小时 |
115
+ | `urge_threshold` | `1.0` | 会话冲动值达到该值才有资格主动开口 |
116
+ | `global_max_per_day` | `3` | 全局(所有会话合计)每日主动发言上限 |
117
+ | `per_chat_daily` | `1` | 单会话每日主动发言上限 |
118
+ | `unreplied_cooldown_hours` | `12` | 开口后无人回应的群,安静多久(小时)内不再开口 |
119
+ | `min_silence_hours` | `6` | 同一会话距上次主动开口的最小间隔(小时) |
120
+ | `check_interval_minutes` | `30` | 冲动值检查间隔(分钟,×0.7-1.3 抖动) |
121
+ | `window_size` | `50` | 每会话旁听窗口保留条数(环形缓冲) |
122
+
123
+ ## 服务
124
+
125
+ | 服务 | 签名 | 说明 |
126
+ |---|---|---|
127
+ | `qvq_decision` | `(ctx: dict) -> dict` | QvQChat 问询的参与决策;返回 `{decision, reason, score, urgency}` |
128
+ | `group_heat` | `(chat_key: str = '') -> dict` | 查询单个会话的话题热度,省略时返回全部会话 |
129
+ | `urge_status` | `(chat_key: str = '') -> dict` | 查询冲动值现值(含衰减)与阈值,省略时返回全部会话 |
130
+ | `request_speak` | `(chat_key: str, reason: str = '', prompt: str = '') -> dict` | 请求向指定会话主动发言,仍过 QvQChat 预算 |
131
+ | `qvq_settings` | `() -> dict` | 归属标记:让本插件出现在 QvQChat 设置页「插件」分区 |
132
+
133
+ 这些服务经 QvQChat 生态桥自动转为 AI 工具(前缀 `eco_QvQStalker_`),智能体本身、编排流程和定时任务都能直接查热度、查冲动值、申请发言,不用写一行代码。
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,23 @@
1
+ README.md
2
+ pyproject.toml
3
+ ErisPulse_QvQStalker.egg-info/PKG-INFO
4
+ ErisPulse_QvQStalker.egg-info/SOURCES.txt
5
+ ErisPulse_QvQStalker.egg-info/dependency_links.txt
6
+ ErisPulse_QvQStalker.egg-info/entry_points.txt
7
+ ErisPulse_QvQStalker.egg-info/requires.txt
8
+ ErisPulse_QvQStalker.egg-info/top_level.txt
9
+ QvQStalker/Config.py
10
+ QvQStalker/Main.py
11
+ QvQStalker/__init__.py
12
+ QvQStalker/decision.py
13
+ QvQStalker/fewshot.py
14
+ QvQStalker/heat.py
15
+ QvQStalker/observe.py
16
+ QvQStalker/proactive.py
17
+ QvQStalker/texts.py
18
+ tests/test_decision.py
19
+ tests/test_fewshot.py
20
+ tests/test_heat.py
21
+ tests/test_main.py
22
+ tests/test_plugin_settings.py
23
+ tests/test_window.py
@@ -0,0 +1,2 @@
1
+ [erispulse.module]
2
+ QvQStalker = QvQStalker:Main
@@ -0,0 +1,5 @@
1
+ ErisPulse>=2.8.0
2
+
3
+ [test]
4
+ pytest>=8.0
5
+ pytest-asyncio>=0.23
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: ErisPulse-QvQStalker
3
+ Version: 0.1.0
4
+ Summary: ErisPulse 群聊参与决策插件:旁听群消息、五层插话判定、冲动值主动发言 —— Group participation decision plugin for ErisPulse (QvQChat companion)
5
+ License: MIT
6
+ Project-URL: homepage, https://github.com/wsu2059q/ErisPulse-QvQStalker
7
+ Project-URL: Issues, https://github.com/wsu2059q/ErisPulse-QvQStalker/issues
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: ErisPulse>=2.8.0
11
+ Provides-Extra: test
12
+ Requires-Dist: pytest>=8.0; extra == "test"
13
+ Requires-Dist: pytest-asyncio>=0.23; extra == "test"
14
+
15
+ <div align="center">
16
+
17
+ 简体中文 | [繁體中文](README.zh-TW.md) | [English](README.en.md) | [日本語](README.ja.md) | [Русский](README.ru.md)
18
+
19
+ <img src="https://raw.githubusercontent.com/ErisPulse/ErisPulse/main/.github/assets/ErisPulseLogo.png" width="180" alt="QvQStalker" />
20
+
21
+ <img src="https://raw.githubusercontent.com/ErisPulse/.github/main/assets/badges/module.svg" alt="ErisPulse Module" />
22
+
23
+ </div>
24
+
25
+ # ErisPulse-QvQStalker
26
+
27
+ **[QvQChat](https://github.com/wsu2059q/ErisPulse-QvQChat) 伴侣插件:让机器人在群里像个真成员——平时旁听,自己判断哪句话值得接,没人搭理它时也会忍不住开口。**
28
+
29
+ 多数机器人只有被 @ 或命中前缀才会说话,真人群友不是这样:整天挂在群里看消息,偶尔有人问到懂的话题就插一句,有时候实在憋不住还会自己起话头。QvQStalker 给 QvQChat 补上的就是后面这种行为。QvQChat 会在命令过滤之后、触发判定之前,把每条群消息以 `qvq.observe` 生命周期事件广播出来;本插件据此为每个会话保留一小段最近消息窗口、跟踪话题热度。当一条消息既没 @ 机器人也没命中前缀时,QvQChat 会来问询本插件的 `qvq_decision` 服务要不要接话。另外还有一套冲动值模型,决定机器人什么时候在完全没人理它的情况下主动开口。
30
+
31
+ 算法移植自 QvQChat 3.x 的窥屏模式(`should_reply` 五层判定加 proactive 冲动值模型),但刻意没有塞进 QvQChat 本体。参与决策是种很有"性格"的行为——有人想要,有人坚决不要——所以做成独立插件,零 QvQChat 包依赖,所有交互只走 `sdk.lifecycle` / `sdk.module.call` / `sdk.storage` 三个框架接口。两个模块可以各自安装、升级、卸载,任何一方缺席或版本偏旧,另一方都照常工作。
32
+
33
+ 安全是默认状态,不是可选项。参与决策在你亲手打开 QvQChat 的 `participation_enabled` 开关之前一直关闭。插件触发的每次发言仍要过 QvQChat 的 SpeechBudget——每群每小时上限(默认 8 条)、同群冷却(默认 300 秒)、主动发言全局每日上限——插件一条也绕不过去。在此之上插件还有自己的一层自律:每日配额、单会话上限、作息时段,以及对"开口后无人回应的群"的冷却。每次决策都会返回一句人类可读的 `reason`,在 Dashboard 里随时能看清机器人为什么说话、为什么沉默。
34
+
35
+ <p>
36
+
37
+ <a href="https://www.python.org/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
38
+
39
+ <a href="https://github.com/ErisPulse/ErisPulse"><img src="https://img.shields.io/badge/ErisPulse-2.8.0+-00A3FF?style=for-the-badge" alt="ErisPulse"></a>
40
+
41
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
42
+
43
+ </p>
44
+
45
+ ## 安装
46
+
47
+ ```bash
48
+ epsdk install ErisPulse-QvQStalker
49
+ ```
50
+
51
+ 或
52
+
53
+ ```bash
54
+ pip install ErisPulse-QvQStalker
55
+ ```
56
+
57
+ 装到与 QvQChat 相同的 ErisPulse 实例即可。要求 ErisPulse >= 2.8.0、Python >= 3.10、QvQChat >= 4.0.4(提供 `qvq.observe` 事件与 `speak` 服务的版本)。两个模块互相不 import。
58
+
59
+ ## 启用
60
+
61
+ 1. 在 Dashboard 的 QvQChat 设置页打开参与决策开关(`participation_enabled`,默认关闭);旁边的群白名单可限定生效范围,留空即全部群。
62
+ 2. QvQStalker 自己的设置项出现在同一页面的「插件」分区——模式、概率、阈值、作息都在这里调。保存的值在加载时拉取、保存时热更新,多数改动无需重启(个别项如窗口大小在模块加载时读取)。
63
+ 3. 到此为止。此后群里没 @ 机器人、也没命中前缀的消息,都会先交给 QvQStalker 的 `qvq_decision` 问询;回答 `speak` 时 QvQChat 就接话。
64
+
65
+ ## 工作原理
66
+
67
+ ### 旁听
68
+
69
+ QvQChat 在命令过滤后、触发判定前,为每条群消息广播 `qvq.observe` 事件。载荷是纯数据(`chat_key` / `platform` / `sender_id` / `sender_name` / `text` / `is_group` / `at_bot` / `ts` 等),插件只读,绝不把事件标记为已处理,也绝不抛错。每条消息进入按会话隔离的环形窗口(默认保留最近 50 条),供四处消费:热度计算、沉寂判定、小模型判断的 prompt、主动发言的摘要。如果 QvQChat 版本较老还没有这个事件,模块照常加载,只是收不到消息,升级后重载即恢复。
70
+
71
+ ### 话题热度
72
+
73
+ 每个会话有一个 0 到 2.0 的热度值。每条消息先加基础 0.05,聊得越快加得越多:与上一条间隔小于 5 秒 +0.15,小于 15 秒 +0.08,小于 30 秒 +0.03;问号 +0.1,感叹号 +0.05。累加之前,旧热度会先按安静时长衰减——每满 1 分钟最多衰减一半——凉掉的话题自然冷却。
74
+
75
+ ### 五层判定
76
+
77
+ QvQChat 问询 `qvq_decision(ctx)` 时,答案沿五层逐层下沉。L1 是零成本正则:消息看着像提问——中文疑问词(怎么 / 为什么 / 什么 / 是不是 / 吗 / 呢 等)或以问号结尾——就以 `question_prob` 概率参与,默认 0.6,话题热时 +0.3,封顶 0.95。L2 和 L3 自己不做决定,只竖"升级旗":L2 在热度超过 `heat_flag_threshold` 时按 min(热度×0.3, 0.7) 的概率竖旗(免得不温不火的话题回回都升级),L3 在最新一条消息打破的沉寂超过 `silence_threshold_minutes` 分钟时竖旗。旗竖着且 `llm_judge` 开启时轮到 L5:带上最近 10 条旁听,让 QvQChat 的模型回答一道二选一——"这句话该不该接?"——8 秒超时,超时或解析不了就回落 L4。L4 是所有路径最终落地的概率层:距上次尝试至少观察满 `min_messages_between_replies` 条消息后掷骰,`default_probability` × 模式倍率(conservative ×0、balanced ×1、active ×2)加热度加成决定。conservative 模式把骰子归零,只可能从提问层或小模型判定开口。
78
+
79
+ 无论结果如何,返回值形状一致,reason 都是写给人看的:
80
+
81
+ ```python
82
+ {
83
+ "decision": "speak", # "speak" | "silent"
84
+ "reason": "提问消息命中关键词(热度 1.24),以 0.90 概率参与",
85
+ "score": 0.9, # 0 ~ 1
86
+ "urgency": "high", # "low" | "normal" | "high"
87
+ }
88
+ ```
89
+
90
+ (reason 内置中文与英文两套文案。)
91
+
92
+ ### 冲动值
93
+
94
+ 除了被动接话,插件为每个会话维护一个冲动值,半衰期 1 小时——群越冷清,冲动越淡。每条旁听消息都会累积:基础 0.06,提问 +0.12,感叹号 +0.05,长消息(≥20 字)+0.04,被 @ 或被回复 +0.15(想回又没回上的那股劲),另加少量热度加成。后台循环大约每 30 分钟(×0.7-1.3 随机抖动)醒来过一遍门槛:作息时段、全局每日上限、距该会话上次开口 ≥ `min_silence_hours`、冲动值 ≥ `urge_threshold`、单会话每日上限、未回复冷却——上次开口后群里再没动静,就再憋 `unreplied_cooldown_hours` 小时。全部通过时,本轮只开一次口,挑冲动值最高的会话,以最近 10 条旁听为上下文调用 QvQChat 的 `speak` 服务。这次调用仍要过 QvQChat 的预算;服务缺失或失败时记一条 warning,冲动值减半避免每轮重试,也不消耗配额。
95
+
96
+ ## 配置
97
+
98
+ 全部配置在 `QvQStalker` 模块配置下,在 QvQChat 设置页「插件」分区直接编辑(同时也是 ErisPulse Dashboard 里的普通模块表单)。
99
+
100
+ | 字段 | 默认 | 说明 |
101
+ |---|---|---|
102
+ | `mode` | `balanced` | 参与模式:`conservative` 保守(不随机插话,仅提问层与小模型判定会开口)/ `balanced` 平衡 / `active` 积极(基础概率翻倍) |
103
+ | `default_probability` | `0.03` | balanced 档位的基础插话概率 |
104
+ | `question_prob` | `0.6` | 提问消息的回复概率(热度 > 0.5 时 +0.3,封顶 0.95) |
105
+ | `min_messages_between_replies` | `15` | 两次插话尝试之间至少观察的消息数(防刷屏) |
106
+ | `silence_threshold_minutes` | `30` | 消息打破的沉寂超过该分钟数时竖升级旗 |
107
+ | `heat_flag_threshold` | `0.8` | 热度超过该值(另有概率门)时竖升级旗 |
108
+ | `llm_judge` | `false` | 升级旗竖起时交给 QvQChat 的模型做最终判断 |
109
+ | `judge_model` | 空 | 判断所用模型,留空跟随 QvQChat 默认模型 |
110
+ | `judge_timeout` | `8.0` | 判断超时秒数,超时回落概率层 |
111
+ | `heat_cap` | `2.0` | 话题热度上限 |
112
+ | `heat_bonus_cap` | `0.15` | 概率层热度加成上限 |
113
+ | `sleep_hour` | `2` | 主动发言入睡小时(0-23;与 `wake_hour` 相等表示不限制作息) |
114
+ | `wake_hour` | `8` | 主动发言起床小时 |
115
+ | `urge_threshold` | `1.0` | 会话冲动值达到该值才有资格主动开口 |
116
+ | `global_max_per_day` | `3` | 全局(所有会话合计)每日主动发言上限 |
117
+ | `per_chat_daily` | `1` | 单会话每日主动发言上限 |
118
+ | `unreplied_cooldown_hours` | `12` | 开口后无人回应的群,安静多久(小时)内不再开口 |
119
+ | `min_silence_hours` | `6` | 同一会话距上次主动开口的最小间隔(小时) |
120
+ | `check_interval_minutes` | `30` | 冲动值检查间隔(分钟,×0.7-1.3 抖动) |
121
+ | `window_size` | `50` | 每会话旁听窗口保留条数(环形缓冲) |
122
+
123
+ ## 服务
124
+
125
+ | 服务 | 签名 | 说明 |
126
+ |---|---|---|
127
+ | `qvq_decision` | `(ctx: dict) -> dict` | QvQChat 问询的参与决策;返回 `{decision, reason, score, urgency}` |
128
+ | `group_heat` | `(chat_key: str = '') -> dict` | 查询单个会话的话题热度,省略时返回全部会话 |
129
+ | `urge_status` | `(chat_key: str = '') -> dict` | 查询冲动值现值(含衰减)与阈值,省略时返回全部会话 |
130
+ | `request_speak` | `(chat_key: str, reason: str = '', prompt: str = '') -> dict` | 请求向指定会话主动发言,仍过 QvQChat 预算 |
131
+ | `qvq_settings` | `() -> dict` | 归属标记:让本插件出现在 QvQChat 设置页「插件」分区 |
132
+
133
+ 这些服务经 QvQChat 生态桥自动转为 AI 工具(前缀 `eco_QvQStalker_`),智能体本身、编排流程和定时任务都能直接查热度、查冲动值、申请发言,不用写一行代码。
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,134 @@
1
+ """QvQStalker 配置(dataclass + metadata,组织方式对照 QvQChat/Config.py 独立实现)。
2
+
3
+ metadata 的 description.i18n 键形如 ``QvQStalker.<key>``,ui 提供
4
+ Dashboard 设置表单所需的 widget/group/order 信息。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+
11
+ try: # 独立运行/测试环境没有 ErisPulse 时仍可实例化配置
12
+ from ErisPulse.Core.Bases import BaseConfig
13
+ except Exception: # pragma: no cover - 环境兜底
14
+ BaseConfig = object # type: ignore[assignment, misc]
15
+
16
+
17
+ def _desc(key: str, text: str, group: str, order: int, widget: str = "text",
18
+ options: list | None = None) -> dict:
19
+ ui: dict = {"widget": widget, "group": group, "order": order}
20
+ if options is not None:
21
+ ui["options"] = options
22
+ return {"description": {"i18n": f"QvQStalker.{key}", "default": text}, "ui": ui}
23
+
24
+
25
+ @dataclass
26
+ class QvQStalkerConfig(BaseConfig):
27
+ # ---- few-shot 双池(拟人化总纲条 7) ----
28
+ fewshot_enabled: bool = field(
29
+ default=True,
30
+ metadata=_desc("fewshot_enabled", "沉淀反响好的发言为语气样例,注入插话判定与主动发言提示词", "fewshot", 1, "switch"),
31
+ )
32
+ fewshot_base_cap: int = field(
33
+ default=20,
34
+ metadata=_desc("fewshot_base_cap", "基准池容量:装满后封池不再轮换(稳定语气锚,0 关闭基准池)", "fewshot", 2, "number"),
35
+ )
36
+ fewshot_inc_cap: int = field(
37
+ default=30,
38
+ metadata=_desc("fewshot_inc_cap", "每群增量池容量:只保留最近 7 天的合格句,自动周轮换(0 关闭增量池)", "fewshot", 3, "number"),
39
+ )
40
+
41
+ # ---- 插话判定(五层) ----
42
+ mode: str = field(
43
+ default="balanced",
44
+ metadata=_desc("mode", "参与模式:conservative 保守(不随机插话,仅提问层生效)/ balanced 平衡 / active 积极(基础概率翻倍)",
45
+ "decision", 1, "select", ["conservative", "balanced", "active"]),
46
+ )
47
+ default_probability: float = field(
48
+ default=0.03,
49
+ metadata=_desc("default_probability", "基础插话概率(balanced 档位值;active 档位翻倍,conservative 档位归零)",
50
+ "decision", 2, "number"),
51
+ )
52
+ question_prob: float = field(
53
+ default=0.6,
54
+ metadata=_desc("question_prob", "提问消息的回复概率(话题热度 > 0.5 时 +0.3,上限 0.95)", "decision", 3, "number"),
55
+ )
56
+ min_messages_between_replies: int = field(
57
+ default=15,
58
+ metadata=_desc("min_messages_between_replies", "两次插话尝试之间至少观察多少条消息(防止刷屏)", "decision", 4, "number"),
59
+ )
60
+ silence_threshold_minutes: float = field(
61
+ default=30.0,
62
+ metadata=_desc("silence_threshold_minutes", "沉寂唤醒阈值(分钟):本条消息打破的沉寂时长超过该值时视为沉寂唤醒", "decision", 5, "number"),
63
+ )
64
+ heat_flag_threshold: float = field(
65
+ default=0.8,
66
+ metadata=_desc("heat_flag_threshold", "热度标志阈值:话题热度超过该值时按概率升级到 LLM 判断", "decision", 6, "number"),
67
+ )
68
+
69
+ # ---- LLM 判断(L5) ----
70
+ llm_judge: bool = field(
71
+ default=False,
72
+ metadata=_desc("llm_judge", "开启后,热度/沉寂升级条件触发时交给小模型单问判断(不开启则按概率层结果)",
73
+ "judge", 1, "switch"),
74
+ )
75
+ judge_model: str = field(
76
+ default="",
77
+ metadata=_desc("judge_model", "L5 判断使用的模型(模型池 id),留空跟随 QvQChat 默认模型(经 QvQChat.chat 服务调用,待 T15 专用入口落地后完全生效)",
78
+ "judge", 2),
79
+ )
80
+ judge_timeout: float = field(
81
+ default=8.0,
82
+ metadata=_desc("judge_timeout", "L5 小模型判断超时(秒),超时按概率层结果处理", "judge", 3, "number"),
83
+ )
84
+
85
+ # ---- 话题热度 ----
86
+ heat_cap: float = field(
87
+ default=2.0,
88
+ metadata=_desc("heat_cap", "话题热度上限", "heat", 1, "number"),
89
+ )
90
+ heat_bonus_cap: float = field(
91
+ default=0.15,
92
+ metadata=_desc("heat_bonus_cap", "概率层的热度加成上限(加成 = min(热度×0.05, 该值))", "heat", 2, "number"),
93
+ )
94
+
95
+ # ---- 主动发言(冲动值 loop) ----
96
+ sleep_hour: int = field(
97
+ default=2,
98
+ metadata=_desc("sleep_hour", "主动发言作息:入睡小时(0-23,与 wake_hour 相等表示不启用作息限制)", "proactive", 1, "number"),
99
+ )
100
+ wake_hour: int = field(
101
+ default=8,
102
+ metadata=_desc("wake_hour", "主动发言作息:起床小时(0-23)", "proactive", 2, "number"),
103
+ )
104
+ urge_threshold: float = field(
105
+ default=1.0,
106
+ metadata=_desc("urge_threshold", "冲动值阈值:会话冲动值达到该值才有资格主动开口", "proactive", 3, "number"),
107
+ )
108
+ global_max_per_day: int = field(
109
+ default=3,
110
+ metadata=_desc("global_max_per_day", "全局每日主动发言上限(所有会话合计)", "proactive", 4, "number"),
111
+ )
112
+ per_chat_daily: int = field(
113
+ default=1,
114
+ metadata=_desc("per_chat_daily", "单会话每日主动发言上限", "proactive", 5, "number"),
115
+ )
116
+ unreplied_cooldown_hours: float = field(
117
+ default=12.0,
118
+ metadata=_desc("unreplied_cooldown_hours", "未回复冷却(小时):上次主动开口后没有任何新消息期间不再主动开口",
119
+ "proactive", 6, "number"),
120
+ )
121
+ min_silence_hours: float = field(
122
+ default=6.0,
123
+ metadata=_desc("min_silence_hours", "沉寂门槛(小时):距上次主动开口不足该时长时不再次主动开口", "proactive", 7, "number"),
124
+ )
125
+ check_interval_minutes: int = field(
126
+ default=30,
127
+ metadata=_desc("check_interval_minutes", "主动发言检查间隔(分钟),实际间隔会乘以 0.7-1.3 的随机抖动", "proactive", 8, "number"),
128
+ )
129
+
130
+ # ---- 旁听窗口 ----
131
+ window_size: int = field(
132
+ default=50,
133
+ metadata=_desc("window_size", "每会话旁听窗口大小(内存环形缓冲保留的最近消息条数)", "observe", 1, "number"),
134
+ )