nonebot-plugin-dotcharacter 2.0.4__tar.gz → 2.0.6__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.
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/PKG-INFO +3 -2
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/README.md +2 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/__init__.py +11 -21
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/llm_client.py +41 -16
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/PKG-INFO +3 -2
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/requires.txt +0 -1
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/pyproject.toml +1 -2
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/LICENSE +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/character_loader.py +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/config.py +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/conversation.py +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/SOURCES.txt +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/dependency_links.txt +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/entry_points.txt +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/top_level.txt +0 -0
- {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonebot-plugin-dotcharacter
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
4
4
|
Summary: NoneBot 插件:加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI 角色扮演对话
|
|
5
5
|
Author: tghrt
|
|
6
6
|
License: MIT
|
|
@@ -20,7 +20,6 @@ Requires-Dist: nonebot2>=2.3.0
|
|
|
20
20
|
Requires-Dist: nonebot-adapter-onebot>=0.3.0
|
|
21
21
|
Requires-Dist: httpx>=0.24.0
|
|
22
22
|
Requires-Dist: pyyaml>=6.0
|
|
23
|
-
Requires-Dist: nonebot-plugin-localstore>=0.4.0
|
|
24
23
|
Dynamic: license-file
|
|
25
24
|
|
|
26
25
|
<div align="center">
|
|
@@ -65,6 +64,8 @@ _✨ 加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI
|
|
|
65
64
|
- 💬 群聊 @机器人 触发对话,命令无需 @
|
|
66
65
|
- 👑 管理员权限控制(命令仅管理员可用)
|
|
67
66
|
- 🧠 私聊会话独立,群聊会话按群共享
|
|
67
|
+
- 🔄 LLM API 超时自动重试(3次,指数退避)
|
|
68
|
+
- ⚡ 连接池优化,避免长时间运行后连接失效
|
|
68
69
|
|
|
69
70
|
## 💿 安装
|
|
70
71
|
|
|
@@ -25,21 +25,6 @@ from nonebot.permission import Permission
|
|
|
25
25
|
from nonebot.plugin import PluginMetadata
|
|
26
26
|
from nonebot.rule import Rule
|
|
27
27
|
|
|
28
|
-
# 本地存储实例(NoneBot 启动后才可用)
|
|
29
|
-
_store = None
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _get_store():
|
|
33
|
-
"""懒加载 localstore,仅在 NoneBot 运行时导入。"""
|
|
34
|
-
global _store
|
|
35
|
-
if _store is None:
|
|
36
|
-
try:
|
|
37
|
-
import nonebot_plugin_localstore as s
|
|
38
|
-
_store = s
|
|
39
|
-
except Exception:
|
|
40
|
-
pass
|
|
41
|
-
return _store
|
|
42
|
-
|
|
43
28
|
from .config import get_config, PROVIDER_PRESETS, DotCharacterConfig
|
|
44
29
|
from .character_loader import (
|
|
45
30
|
CharacterMeta,
|
|
@@ -74,7 +59,7 @@ __plugin_meta__ = PluginMetadata(
|
|
|
74
59
|
supported_adapters={"~onebot.v11"},
|
|
75
60
|
extra={
|
|
76
61
|
"author": "tghrt",
|
|
77
|
-
"version": "2.0.
|
|
62
|
+
"version": "2.0.6",
|
|
78
63
|
},
|
|
79
64
|
)
|
|
80
65
|
|
|
@@ -622,9 +607,16 @@ async def handle_chat(matcher: Matcher, event: Event):
|
|
|
622
607
|
await matcher.finish(f"❌ {e}")
|
|
623
608
|
except RuntimeError as e:
|
|
624
609
|
logger.error(f"[dotcharacter] LLM 调用失败: {e}")
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
610
|
+
err_msg = str(e)
|
|
611
|
+
if "超时" in err_msg or "timeout" in err_msg.lower():
|
|
612
|
+
await matcher.finish(
|
|
613
|
+
f"😵 {char.display_name} 响应超时了...\n"
|
|
614
|
+
f"(API 连不上或太慢,等几秒再试试?)"
|
|
615
|
+
)
|
|
616
|
+
else:
|
|
617
|
+
await matcher.finish(
|
|
618
|
+
f"😵 {char.display_name} 暂时无法回应...\n(API 错误,请稍后再试)"
|
|
619
|
+
)
|
|
628
620
|
except Exception as e:
|
|
629
621
|
logger.error(f"[dotcharacter] 未知错误: {e}")
|
|
630
622
|
await matcher.finish(f"😵 出了点问题:{type(e).__name__}")
|
|
@@ -646,8 +638,6 @@ driver = get_driver()
|
|
|
646
638
|
@driver.on_startup
|
|
647
639
|
async def _on_startup():
|
|
648
640
|
logger.info("[dotcharacter] 插件启动中...")
|
|
649
|
-
# 懒加载 localstore(仅在 NoneBot 环境中可用)
|
|
650
|
-
_get_store()
|
|
651
641
|
await _ensure_initialized()
|
|
652
642
|
cfg = get_config()
|
|
653
643
|
admins = cfg.get_admin_qqs()
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
OpenAI / DeepSeek / Kimi / Qwen / Zhipu / SiliconFlow / Groq / Ollama / 自定义
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import asyncio
|
|
7
8
|
from typing import List, Optional
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
@@ -36,6 +37,7 @@ async def chat_completion(
|
|
|
36
37
|
|
|
37
38
|
自动根据 Provider 预设或自定义 api_base 构造请求 URL。
|
|
38
39
|
兼容所有 OpenAI Chat Completions 格式的 API。
|
|
40
|
+
支持 3 次重试(指数退避)。
|
|
39
41
|
"""
|
|
40
42
|
api_key = config.dotcharacter_api_key
|
|
41
43
|
if not api_key or api_key.startswith("sk-your-"):
|
|
@@ -57,22 +59,45 @@ async def chat_completion(
|
|
|
57
59
|
"max_tokens": max_tokens or config.dotcharacter_max_tokens,
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
max_retries = 3
|
|
63
|
+
for attempt in range(1, max_retries + 1):
|
|
64
|
+
try:
|
|
65
|
+
async with httpx.AsyncClient(
|
|
66
|
+
timeout=config.dotcharacter_timeout,
|
|
67
|
+
limits=httpx.Limits(max_connections=20, max_keepalive_connections=10),
|
|
68
|
+
) as client:
|
|
69
|
+
response = await client.post(url, json=payload, headers=headers)
|
|
70
|
+
|
|
71
|
+
if response.status_code != 200:
|
|
72
|
+
err_detail = response.text[:500]
|
|
73
|
+
raise RuntimeError(
|
|
74
|
+
f"LLM API 返回错误 (HTTP {response.status_code}): {err_detail}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
data = response.json()
|
|
78
|
+
choices = data.get("choices", [])
|
|
79
|
+
if not choices:
|
|
80
|
+
raise RuntimeError(f"LLM API 返回空 choices: {data}")
|
|
81
|
+
|
|
82
|
+
content = choices[0].get("message", {}).get("content", "")
|
|
83
|
+
return content.strip()
|
|
84
|
+
|
|
85
|
+
except (httpx.TimeoutException, httpx.ReadTimeout, httpx.ConnectTimeout) as e:
|
|
86
|
+
if attempt == max_retries:
|
|
87
|
+
raise RuntimeError(
|
|
88
|
+
f"LLM API 请求超时(已重试 {max_retries} 次): {type(e).__name__}"
|
|
89
|
+
)
|
|
90
|
+
wait = 2 ** attempt # 指数退避: 2s, 4s
|
|
91
|
+
await asyncio.sleep(wait)
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
except httpx.HTTPStatusError as e:
|
|
95
|
+
raise RuntimeError(f"LLM API HTTP 错误: {e}")
|
|
96
|
+
except httpx.RequestError as e:
|
|
97
|
+
raise RuntimeError(f"LLM API 网络请求失败: {type(e).__name__}: {e}")
|
|
98
|
+
|
|
99
|
+
# 理论上不会走到这里
|
|
100
|
+
raise RuntimeError("LLM API 调用失败,超出最大重试次数")
|
|
76
101
|
|
|
77
102
|
|
|
78
103
|
async def test_api_connection(config: DotCharacterConfig) -> bool:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonebot-plugin-dotcharacter
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
4
4
|
Summary: NoneBot 插件:加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI 角色扮演对话
|
|
5
5
|
Author: tghrt
|
|
6
6
|
License: MIT
|
|
@@ -20,7 +20,6 @@ Requires-Dist: nonebot2>=2.3.0
|
|
|
20
20
|
Requires-Dist: nonebot-adapter-onebot>=0.3.0
|
|
21
21
|
Requires-Dist: httpx>=0.24.0
|
|
22
22
|
Requires-Dist: pyyaml>=6.0
|
|
23
|
-
Requires-Dist: nonebot-plugin-localstore>=0.4.0
|
|
24
23
|
Dynamic: license-file
|
|
25
24
|
|
|
26
25
|
<div align="center">
|
|
@@ -65,6 +64,8 @@ _✨ 加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI
|
|
|
65
64
|
- 💬 群聊 @机器人 触发对话,命令无需 @
|
|
66
65
|
- 👑 管理员权限控制(命令仅管理员可用)
|
|
67
66
|
- 🧠 私聊会话独立,群聊会话按群共享
|
|
67
|
+
- 🔄 LLM API 超时自动重试(3次,指数退避)
|
|
68
|
+
- ⚡ 连接池优化,避免长时间运行后连接失效
|
|
68
69
|
|
|
69
70
|
## 💿 安装
|
|
70
71
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-dotcharacter"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.6"
|
|
4
4
|
description = "NoneBot 插件:加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI 角色扮演对话"
|
|
5
5
|
requires-python = ">=3.9"
|
|
6
6
|
readme = "README.md"
|
|
@@ -23,7 +23,6 @@ dependencies = [
|
|
|
23
23
|
"nonebot-adapter-onebot>=0.3.0",
|
|
24
24
|
"httpx>=0.24.0",
|
|
25
25
|
"pyyaml>=6.0",
|
|
26
|
-
"nonebot-plugin-localstore>=0.4.0",
|
|
27
26
|
]
|
|
28
27
|
|
|
29
28
|
[project.entry-points."nonebot"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|