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.
Files changed (16) hide show
  1. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/PKG-INFO +3 -2
  2. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/README.md +2 -0
  3. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/__init__.py +11 -21
  4. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/llm_client.py +41 -16
  5. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/PKG-INFO +3 -2
  6. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/requires.txt +0 -1
  7. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/pyproject.toml +1 -2
  8. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/LICENSE +0 -0
  9. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/character_loader.py +0 -0
  10. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/config.py +0 -0
  11. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter/conversation.py +0 -0
  12. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/SOURCES.txt +0 -0
  13. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/dependency_links.txt +0 -0
  14. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/entry_points.txt +0 -0
  15. {nonebot_plugin_dotcharacter-2.0.4 → nonebot_plugin_dotcharacter-2.0.6}/nonebot_plugin_dotcharacter.egg-info/top_level.txt +0 -0
  16. {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.4
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
 
@@ -40,6 +40,8 @@ _✨ 加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI
40
40
  - 💬 群聊 @机器人 触发对话,命令无需 @
41
41
  - 👑 管理员权限控制(命令仅管理员可用)
42
42
  - 🧠 私聊会话独立,群聊会话按群共享
43
+ - 🔄 LLM API 超时自动重试(3次,指数退避)
44
+ - ⚡ 连接池优化,避免长时间运行后连接失效
43
45
 
44
46
  ## 💿 安装
45
47
 
@@ -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.4",
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
- await matcher.finish(
626
- f"😵 {char.display_name} 暂时无法回应...\n(API 错误,请稍后再试)"
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
- async with httpx.AsyncClient(timeout=config.dotcharacter_timeout) as client:
61
- response = await client.post(url, json=payload, headers=headers)
62
-
63
- if response.status_code != 200:
64
- err_detail = response.text[:500]
65
- raise RuntimeError(
66
- f"LLM API 返回错误 (HTTP {response.status_code}): {err_detail}"
67
- )
68
-
69
- data = response.json()
70
- choices = data.get("choices", [])
71
- if not choices:
72
- raise RuntimeError(f"LLM API 返回空 choices: {data}")
73
-
74
- content = choices[0].get("message", {}).get("content", "")
75
- return content.strip()
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.4
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
 
@@ -2,4 +2,3 @@ nonebot2>=2.3.0
2
2
  nonebot-adapter-onebot>=0.3.0
3
3
  httpx>=0.24.0
4
4
  pyyaml>=6.0
5
- nonebot-plugin-localstore>=0.4.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-dotcharacter"
3
- version = "2.0.4"
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"]