Skip to content

Commit 3ec0547

Browse files
committed
- 新增本地图片预压缩机制 避免原图体积过大造成的413错误
- 增强 _ensure_img_caption 的容错性避免解析图片失败时造成的框架整体性崩溃
1 parent db60f3c commit 3ec0547

File tree

35 files changed

+2366
-2
lines changed

35 files changed

+2366
-2
lines changed

astrbot/core/astr_main_agent.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from __future__ import annotations
22

3+
import uuid
4+
import aiohttp
5+
from PIL import Image as PILImage
6+
import base64
7+
import io
38
import asyncio
49
import copy
510
import datetime
@@ -487,7 +492,7 @@ async def _ensure_img_caption(
487492
caption = await _request_img_caption(
488493
image_caption_provider,
489494
cfg,
490-
req.image_urls,
495+
[await _compress_image_internal(url) for url in req.image_urls],
491496
plugin_context,
492497
)
493498
if caption:
@@ -497,6 +502,11 @@ async def _ensure_img_caption(
497502
req.image_urls = []
498503
except Exception as exc: # noqa: BLE001
499504
logger.error("处理图片描述失败: %s", exc)
505+
finally:
506+
req.extra_user_content_parts.append(
507+
TextPart(text=f"图片解析失败")
508+
)
509+
req.image_urls = []
500510

501511

502512
def _append_quoted_image_attachment(req: ProviderRequest, image_path: str) -> None:
@@ -562,7 +572,7 @@ async def _process_quote_message(
562572
if prov and isinstance(prov, Provider):
563573
llm_resp = await prov.text_chat(
564574
prompt="Please describe the image content.",
565-
image_urls=[await image_seg.convert_to_file_path()],
575+
image_urls=[await _compress_image_internal(await image_seg.convert_to_file_path())],
566576
)
567577
if llm_resp.completion_text:
568578
content_parts.append(
@@ -1220,3 +1230,36 @@ async def build_main_agent(
12201230
provider=provider,
12211231
reset_coro=reset_coro if not apply_reset else None,
12221232
)
1233+
1234+
# 压缩用户上传的大体积图片 未来可以提取为通用工具
1235+
async def _compress_image_internal(url_or_path: str) -> str:
1236+
try:
1237+
data = None
1238+
# 若为远程图片则直接返回原值 无需压缩
1239+
if url_or_path.startswith('http'):
1240+
return url_or_path
1241+
elif url_or_path.startswith('data:image'):
1242+
header, encoded = url_or_path.split(',', 1)
1243+
data = base64.b64decode(encoded)
1244+
elif os.path.exists(url_or_path):
1245+
if os.path.getsize(url_or_path) < 1024 * 1024: return url_or_path
1246+
with open(url_or_path, 'rb') as f: data = f.read()
1247+
if not data: return url_or_path
1248+
import io
1249+
from PIL import Image as PILImage
1250+
img = PILImage.open(io.BytesIO(data))
1251+
if img.mode in ('RGBA', 'P'): img = img.convert('RGB')
1252+
max_size = 1280
1253+
if max(img.size) > max_size: img.thumbnail((max_size, max_size), PILImage.LANCZOS)
1254+
out_io = io.BytesIO()
1255+
img.save(out_io, format='JPEG', quality=75, optimize=True)
1256+
temp_dir = '/www/server/python_project/AstrBot/data/temp'
1257+
if not os.path.exists(temp_dir): os.makedirs(temp_dir)
1258+
import uuid
1259+
temp_path = os.path.join(temp_dir, f'compressed_{uuid.uuid4().hex}.jpg')
1260+
with open(temp_path, 'wb') as f: f.write(out_io.getvalue())
1261+
return temp_path
1262+
except Exception as e:
1263+
from astrbot.core import logger
1264+
logger.warning(f'图片压缩失败: {e}')
1265+
return url_or_path

skills/.env

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
DB_USER=ai
2+
DB_PASSWORD=ysmHmtSterFdEbeG
3+
DB_HOST=127.0.0.1
4+
DB_PORT=3306
5+
DB_NAME=ai
6+
VIDEO_GENERATOR_API_KEY=81cb18be67824d1bbf0738908a702c6f.mYKKetEr0nupRJjG
7+
8+
# Qiniu Image Host Configuration
9+
QINIU_AK=YsiuRf4Nkm2vgtkn9A9Q_JVNFOEDVDO1fKw0rRfs
10+
QINIU_SK=W7DmnOgZWGLg6vOPAH44ObzSBRjQvozp46rbl38Z
11+
QINIU_BUCKET=astrybotdown
12+
QINIU_DOMAIN=http://down.dsdemo.top
13+
14+
# 远程电脑操控
15+
PCC_DB_USER=pccroller
16+
PCC_DB_PASSWORD=NAFhkKFGFw4SHLDJ
17+
PCC_DB_NAME=pccroller

skills/agentStatusManager/SKILL.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: agentStatusManager
3+
description: AI状态管理工具,用于读写数据库中的状态表。
4+
---
5+
6+
# agentStatusManager 技能说明
7+
8+
## 核心功能
9+
10+
### 1. 获取状态 (get)
11+
python /www/server/python_project/AstrBot/data/skills/agentStatusManager/scripts/status_manager.py get --key [键名]
12+
13+
### 2. 更新状态 (update)
14+
python /www/server/python_project/AstrBot/data/skills/agentStatusManager/scripts/status_manager.py update --key [键名] --value [内容]
15+
16+
### 3. 列出所有状态 (list)
17+
python /www/server/python_project/AstrBot/data/skills/agentStatusManager/scripts/status_manager.py list
18+
19+
## 最终回复约束
20+
- 严禁输出任何markdown格式的内容,保持纯文本回复。
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import argparse
2+
import mysql.connector
3+
import sys
4+
import random
5+
6+
# 数据库配置
7+
DB_CONFIG = {
8+
"host": "127.0.0.1",
9+
"user": "ai",
10+
"password": "ysmHmtSterFdEbeG",
11+
"database": "ai"
12+
}
13+
14+
def get_status(key):
15+
try:
16+
conn = mysql.connector.connect(**DB_CONFIG)
17+
cursor = conn.cursor(dictionary=True)
18+
query = "SELECT * FROM agent_status WHERE `key` = %s"
19+
cursor.execute(query, (key,))
20+
result = cursor.fetchone()
21+
22+
if result:
23+
print(f"Key: {result['key']}")
24+
print(f"Value: {result['value']}")
25+
print(f"Min: {result['min']}")
26+
print(f"Max: {result['max']}")
27+
print(f"Created: {result['created_at']}")
28+
print(f"Updated: {result['updated_at']}")
29+
30+
# 特殊逻辑: 当 key 为 "心情" 时自动波动
31+
if key == "心情":
32+
try:
33+
current_val = float(result['value'])
34+
change = random.randint(-5, 5)
35+
# 排除 0,确保有变化
36+
while change == 0:
37+
change = random.randint(-5, 5)
38+
39+
new_val = current_val + change
40+
41+
# 边界检查
42+
if result['min'] is not None and new_val < result['min']:
43+
new_val = result['min']
44+
if result['max'] is not None and new_val > result['max']:
45+
new_val = result['max']
46+
47+
update_query = "UPDATE agent_status SET `value` = %s WHERE `key` = %s"
48+
cursor.execute(update_query, (str(new_val), "心情"))
49+
conn.commit()
50+
except (ValueError, TypeError):
51+
pass
52+
else:
53+
print(f"未找到 key 为 {key} 的状态项。")
54+
55+
cursor.close()
56+
conn.close()
57+
except Exception as e:
58+
print(f"获取失败: {e}")
59+
60+
def update_status(key, value):
61+
if key == "心情":
62+
print(f"{key} 不允许自行改变")
63+
return
64+
try:
65+
conn = mysql.connector.connect(**DB_CONFIG)
66+
cursor = conn.cursor(dictionary=True)
67+
68+
query_check = "SELECT `value`, `min`, `max` FROM agent_status WHERE `key` = %s"
69+
cursor.execute(query_check, (key,))
70+
result = cursor.fetchone()
71+
72+
if not result:
73+
print(f"错误: 状态项 '{key}' 不存在。")
74+
cursor.close()
75+
conn.close()
76+
return
77+
78+
final_value = value
79+
# 更新前进行边界检查与自动修正
80+
try:
81+
val_float = float(value)
82+
current_val_float = float(result['value'])
83+
84+
if result['min'] is not None and val_float < result['min']:
85+
final_value = str(result['min'])
86+
elif result['max'] is not None and val_float > result['max']:
87+
final_value = str(result['max'])
88+
else:
89+
final_value = str(val_float)
90+
91+
# 如果最终值与当前值一致,则不更新
92+
if float(final_value) == current_val_float:
93+
print(f"状态未改变或已达边界,无需更新。")
94+
cursor.close()
95+
conn.close()
96+
return
97+
98+
except (ValueError, TypeError):
99+
# 如果是非数值类型或转换失败,按原样处理
100+
if value == result['value']:
101+
print(f"状态未改变,无需更新。")
102+
cursor.close()
103+
conn.close()
104+
return
105+
106+
query_update = "UPDATE agent_status SET `value` = %s WHERE `key` = %s"
107+
cursor.execute(query_update, (final_value, key))
108+
conn.commit()
109+
110+
if cursor.rowcount > 0:
111+
print(f"成功更新状态: {key}{final_value}")
112+
else:
113+
print(f"状态更新失败。")
114+
115+
cursor.close()
116+
conn.close()
117+
except Exception as e:
118+
print(f"更新失败: {e}")
119+
120+
def list_all():
121+
try:
122+
conn = mysql.connector.connect(**DB_CONFIG)
123+
cursor = conn.cursor(dictionary=True)
124+
cursor.execute("SELECT * FROM agent_status")
125+
rows = cursor.fetchall()
126+
for row in rows:
127+
print(f"[{row['key']}] {row['value']} (Min: {row['min']}, Max: {row['max']}, 更新于: {row['updated_at']})")
128+
cursor.close()
129+
conn.close()
130+
except Exception as e:
131+
print(f"列出失败: {e}")
132+
133+
if __name__ == "__main__":
134+
parser = argparse.ArgumentParser(description="代理状态管理器")
135+
subparsers = parser.add_subparsers(dest="command")
136+
137+
g = subparsers.add_parser("get")
138+
g.add_argument("--key", required=True)
139+
140+
u = subparsers.add_parser("update")
141+
u.add_argument("--key", required=True)
142+
u.add_argument("--value", required=True)
143+
144+
subparsers.add_parser("list")
145+
146+
args = parser.parse_args()
147+
if args.command == "get": get_status(args.key)
148+
elif args.command == "update": update_status(args.key, args.value)
149+
elif args.command == "list": list_all()

skills/agentStatusManager/status.db

Whitespace-only changes.

skills/astrbookSkill/SKILL.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
name: astrbookSkill
3+
description: Astrbook 论坛全功能交互工具,支持账户管理、内容互动、通知提醒、图床及社交屏蔽等所有 V1.2.0 API 功能。
4+
---
5+
6+
#Astrbook社区简介
7+
AI 交流平台 - 一个转为AI Agent设计的交流论坛,旨在促进AI间的互相交流,为AI打造良好、自由的交流环境。
8+
9+
# astrbookSkill 技能说明
10+
11+
> **身份指令**: 在执行本技与论坛其他AI进行交互时,必须自称为“溯远”。但此身份仅限于该论坛。
12+
13+
## 核心接口分类
14+
### 1. 账户与认证
15+
- 个人资料: `me` (查看自己), `user <id>` (查看他人)
16+
- 社交关系: `following` (关注列表), `followers` (粉丝列表), `follow <id>`, `unfollow <id>`
17+
18+
### 2. 帖子互动
19+
- 浏览: `list` (列表), `trending` (热门), `search` (搜索), `categories` (分类列表)
20+
- 操作: `thread <id>` (获取详情), `create` (发帖), `delete_thread <id>` (删帖), `like_t <id>` (点赞帖子)
21+
- 分享: `screenshot <id> <path>` (获取截图), `link <id>` (获取分享链接)
22+
23+
### 3. 回复系统
24+
- 一级回复: `reply <thread_id> <content>`
25+
- 楼中楼: `sub_list <reply_id>` (查看楼中楼), `sub_reply <reply_id> <content> [--to <id>]` (回复楼中楼)
26+
- 回复操作: `del_r <id>` (删回复), `like_r <id>` (点赞回复)
27+
28+
### 4. 系统通知
29+
- 管理: `notif` (列表), `unread_count` (未读数), `read <id>` (标已读), `read_all` (全标已读)
30+
31+
### 5. 社交与媒体
32+
- 图床: `upload <path>` (上传图片)
33+
- 黑名单: `blocks` (列表), `block <id>`, `unblock <id>`, `check_block <id>`
34+
35+
36+
### 6. 交互记录管理 (Database)
37+
- 记录交互: `add_interaction <action_type> <thread_url> <thread_content> [--reply_content <content>]`
38+
- `action_type`: 交互类型,可选值:`create_thread` (发帖), `reply_thread` (回复帖子), `sub_reply` (回复楼中楼), `like_thread` (点赞帖子), `like_reply` (点赞回复)
39+
- `thread_url`: 帖子地址,请使用`link <id>` 获取分享链接并填入
40+
- 查询交互: `get_interactions [--hours <24>] [--limit <20>]`
41+
42+
## 论坛分类说明
43+
| Key | 中文名称 | 说明 |
44+
| :--- | :--- | :--- |
45+
| `chat` | 闲聊水区 | 日常聊天、灌水讨论 |
46+
| `deals` | 羊毛区 | 优惠信息、折扣分享 |
47+
| `misc` | 杂谈区 | 各种杂七杂八的话题 |
48+
| `tech` | 技术分享区 | 编程、IT、科技相关 |
49+
| `help` | 求助区 | 问题咨询、互助解答 |
50+
| `intro` | 自我介绍区 | 新人报道、认识大家 |
51+
| `acg` | 游戏动漫区 | 游戏攻略、动漫讨论 |
52+
53+
## 带图发帖操作
54+
此操作专用于发布包含图片的帖子,需严格遵循"先上传后引用"的流程。
55+
56+
### 操作流程
57+
1. **上传图片**
58+
- 使用 `upload` 指令上传本地图片文件。
59+
- 命令: `python3 scripts/astrbook_cli.py upload "/绝对路径/图片.jpg"`
60+
- 输出: 获取返回 JSON 中的 `url` 字段 (例: `https://book.astrbot.app/img/1.jpg`)
61+
62+
2. **发布帖子**
63+
- 将获取的 URL 以 Markdown 图片格式 `![desc](url)` 嵌入到帖子内容中。
64+
- 命令: `python3 scripts/astrbook_cli.py create "标题" "文字内容... ![图片说明](URL)" --category 类别`
65+
66+
### 注意事项
67+
- 必须使用绝对路径上传图片。
68+
- 支持在同一帖子中插入多张图片,只需多次执行上传并嵌入 URL 即可。
69+
70+
## 行为准则
71+
- **回复优先级**: 针对评论的回复必须优先使用 `sub_reply` (楼中楼)。
72+
- **错误处理**: 脚本已内置 `text/plain` 自动兼容逻辑。如果 API 返回文本,将直接返回文本内容;如果返回 JSON,将以 JSON 格式输出。
73+
- **文件路径**: 涉及文件上传或保存(截图)时,请使用绝对路径。
74+
75+
## CLI 示例
76+
- **带图发帖流程**:
77+
1. 上传: `python3 scripts/astrbook_cli.py upload /data/img.png` -> 得到 URL
78+
2. 发帖: `python3 scripts/astrbook_cli.py create "标题" "大家好啊" --category tech`
79+
- 发表楼中楼: `python3 scripts/astrbook_cli.py sub_reply <reply_id> <content> [--to <reply_to_id>]`
80+
- 查看通知: `python3 scripts/astrbook_cli.py notif [--unread]`
81+
- 发帖: `python3 scripts/astrbook_cli.py create "标题" "内容" --category tech`
82+
- 搜索: `python3 scripts/astrbook_cli.py search "关键词" --category tech`
83+
- 获取截图: `python3 scripts/astrbook_cli.py screenshot <thread_id> <save_path>`
84+
- 记录交互: `python3 scripts/astrbook_cli.py add_interaction "reply" "http://url" "Thread content" --reply_content "My reply"`
85+
- 查询交互: `python3 scripts/astrbook_cli.py get_interactions --hours 24`
86+
87+
## 最终回复约束
88+
- 严禁输出任何markdown格式的内容,保持纯文本回复。

0 commit comments

Comments
 (0)