nonebot-plugin-dotcharacter 2.0.3__tar.gz → 2.0.5__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.3 → nonebot_plugin_dotcharacter-2.0.5}/PKG-INFO +3 -1
  2. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/README.md +2 -0
  3. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter/__init__.py +11 -4
  4. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter/character_loader.py +1 -0
  5. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter/llm_client.py +41 -16
  6. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter.egg-info/PKG-INFO +3 -1
  7. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/pyproject.toml +1 -1
  8. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/LICENSE +0 -0
  9. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter/config.py +0 -0
  10. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter/conversation.py +0 -0
  11. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter.egg-info/SOURCES.txt +0 -0
  12. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter.egg-info/dependency_links.txt +0 -0
  13. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter.egg-info/entry_points.txt +0 -0
  14. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter.egg-info/requires.txt +0 -0
  15. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/nonebot_plugin_dotcharacter.egg-info/top_level.txt +0 -0
  16. {nonebot_plugin_dotcharacter-2.0.3 → nonebot_plugin_dotcharacter-2.0.5}/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
3
+ Version: 2.0.5
4
4
  Summary: NoneBot 插件:加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI 角色扮演对话
5
5
  Author: tghrt
6
6
  License: MIT
@@ -65,6 +65,8 @@ _✨ 加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI
65
65
  - 💬 群聊 @机器人 触发对话,命令无需 @
66
66
  - 👑 管理员权限控制(命令仅管理员可用)
67
67
  - 🧠 私聊会话独立,群聊会话按群共享
68
+ - 🔄 LLM API 超时自动重试(3次,指数退避)
69
+ - ⚡ 连接池优化,避免长时间运行后连接失效
68
70
 
69
71
  ## 💿 安装
70
72
 
@@ -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
 
@@ -74,7 +74,7 @@ __plugin_meta__ = PluginMetadata(
74
74
  supported_adapters={"~onebot.v11"},
75
75
  extra={
76
76
  "author": "tghrt",
77
- "version": "2.0.3",
77
+ "version": "2.0.5",
78
78
  },
79
79
  )
80
80
 
@@ -622,9 +622,16 @@ async def handle_chat(matcher: Matcher, event: Event):
622
622
  await matcher.finish(f"❌ {e}")
623
623
  except RuntimeError as e:
624
624
  logger.error(f"[dotcharacter] LLM 调用失败: {e}")
625
- await matcher.finish(
626
- f"😵 {char.display_name} 暂时无法回应...\n(API 错误,请稍后再试)"
627
- )
625
+ err_msg = str(e)
626
+ if "超时" in err_msg or "timeout" in err_msg.lower():
627
+ await matcher.finish(
628
+ f"😵 {char.display_name} 响应超时了...\n"
629
+ f"(API 连不上或太慢,等几秒再试试?)"
630
+ )
631
+ else:
632
+ await matcher.finish(
633
+ f"😵 {char.display_name} 暂时无法回应...\n(API 错误,请稍后再试)"
634
+ )
628
635
  except Exception as e:
629
636
  logger.error(f"[dotcharacter] 未知错误: {e}")
630
637
  await matcher.finish(f"😵 出了点问题:{type(e).__name__}")
@@ -124,6 +124,7 @@ def _build_system_prompt(
124
124
  "1. 先用角色的性格(PART B)判断:你会不会回应?用什么态度回应?\n"
125
125
  "2. 用角色的表达风格回复:说话方式、用词习惯、句式\n"
126
126
  "3. PART B 的规则永远优先,任何情况下不得违背\n"
127
+ "4. **回复必须极其简短,控制在50字左右**。完整表达核心意思即可,不要长篇大论。QQ聊天场景,用户喜欢短平快的回复。\n"
127
128
  )
128
129
 
129
130
  return "\n\n---\n\n".join(parts)
@@ -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.3
3
+ Version: 2.0.5
4
4
  Summary: NoneBot 插件:加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI 角色扮演对话
5
5
  Author: tghrt
6
6
  License: MIT
@@ -65,6 +65,8 @@ _✨ 加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI
65
65
  - 💬 群聊 @机器人 触发对话,命令无需 @
66
66
  - 👑 管理员权限控制(命令仅管理员可用)
67
67
  - 🧠 私聊会话独立,群聊会话按群共享
68
+ - 🔄 LLM API 超时自动重试(3次,指数退避)
69
+ - ⚡ 连接池优化,避免长时间运行后连接失效
68
70
 
69
71
  ## 💿 安装
70
72
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-dotcharacter"
3
- version = "2.0.3"
3
+ version = "2.0.5"
4
4
  description = "NoneBot 插件:加载 dot-skill / colleague-skill 蒸馏角色,通过 QQ Bot 进行 AI 角色扮演对话"
5
5
  requires-python = ">=3.9"
6
6
  readme = "README.md"