Compare commits

...

36 Commits

Author SHA1 Message Date
Soulter
26c6a2950f 📦 release: bump version to v4.0.0-beta.5 2025-09-05 17:42:38 +08:00
Soulter
5082876de3 fix: 修复 v4.0.0 版本下可能无法得到 LLM 的响应的问题
closes: #2622
2025-09-05 17:20:29 +08:00
Soulter
e50e7ad3d5 fix: ensure deep copy of config_data before posting 2025-09-05 16:45:34 +08:00
卢小辉
45a4a6b6da feat: 给添加 edge_tts 新增 rate, volume, pitch 参数 (#2625)
* 修复python执行器文件上传qq提示参数错误问题,修改策略为本地url

* 给edge_tts 添加3个默认参数,方便通过ui配置
2025-09-05 15:23:21 +08:00
Soulter
02918b7267 perf: 增加 abconf_data 缓存,优化性能 2025-09-04 23:48:33 +08:00
Soulter
6c662a36c1 fix: 适配 qwen3 的 thinking 类模型
fixes: #2631
2025-09-04 20:26:52 +08:00
Soulter
b78fe3822a perf: 完善对 rerank model 的可用性检测 2025-09-04 15:46:23 +08:00
Soulter
35eda37e83 Merge remote-tracking branch 'origin/releases/v3.5.27' 2025-09-04 15:30:15 +08:00
Soulter
176a8e7067 chore: add no_proxy config item 2025-09-04 15:23:58 +08:00
Soulter
61d4f1fd4b 📦 release: v3.5.27 2025-09-04 15:01:44 +08:00
Soulter
121b68995e chore: update changelog 2025-09-04 14:34:23 +08:00
Soulter
d11f1d8dae perf: enhance update checks to consider pre-release versions 2025-09-04 14:33:19 +08:00
Soulter
c0ef2b5064 📦 release: v3.5.27 2025-09-04 13:56:47 +08:00
Soulter
2a7308363e fix: 下载 WebUI 时,明确版本号 2025-09-04 13:54:16 +08:00
Soulter
dc0c556f96 ci: build docker image 时同时 build webui,并放入 image 中 2025-09-04 13:42:26 +08:00
Soulter
ba2ee1c0aa fix: 初次下载 webui 构建文件时下载指定版本而非 latest 2025-09-04 13:27:55 +08:00
Zhalslar
0f8b550d68 fix: aiocqhttp优先使用session_id发送消息 (#2623)
* fix: aiocqhttp优先使用session_id发送消息

当前aiocqhttp依赖raw_message来发送消息,raw_message为空时也无法有效回退到用group_id或user_id来发送,更符合逻辑的应该:优先使用session_id(group_id or user_id),raw_message兜底

* Update aiocqhttp_message_event.py

* fix: validate session_id as integer and improve send_message docstring

---------

Co-authored-by: Soulter <905617992@qq.com>
2025-09-04 11:34:40 +08:00
Soulter
ed1fc98821 Merge pull request #2621 from AstrBotDevs/fix/gemini-api-error-handle
Fix: 修复 e.message 为 None 时报错的问题和部分 lint error
2025-09-04 11:20:05 +08:00
Soulter
fa53b468fd fix: ensure function call name and args are not None before processing 2025-09-04 11:18:58 +08:00
Soulter
4e2533d320 feat: add pre-release check for Docker image tagging 2025-09-04 09:30:21 +08:00
Soulter
388ae49e55 fix: 修复 e.message 为 None 时报错的问题和一些 lint error 2025-09-03 22:25:18 +08:00
Soulter
f3f347dcba 📦 release: bump verstion to v4.0.0-beta.4 2025-09-03 13:29:20 +08:00
Soulter
655be3519c perf: 数据迁移完毕之后引导重启程序
closes: #2613
2025-09-03 13:21:56 +08:00
Soulter
06df2940af chore: change identifier description 2025-09-03 12:46:10 +08:00
Soulter
4149549e42 fix: KeyError arprompt 2025-09-03 12:45:34 +08:00
Soulter
da351991f8 📦 release: bump verstion to v4.0.0-beta.3 2025-09-03 01:01:48 +08:00
Soulter
3305152e50 fix: 修复当人格 ID 为中文时,不可保存的问题 2025-09-03 00:59:07 +08:00
Soulter
bea7bae674 fix: dict read 2025-09-03 00:56:41 +08:00
Soulter
45773d38ed 📦 release: bump verstion to v4.0.0-beta.2 2025-09-03 00:32:49 +08:00
Soulter
8d4c176314 fix: correct image_caption logic and remove redundant config call 2025-09-03 00:31:18 +08:00
Soulter
9ca5c87c4c fix: complete requirements.txt 2025-09-03 00:05:43 +08:00
Soulter
36a6f00e5f Merge pull request #2610 from AstrBotDevs/releases/4.0.0 (#2610)
Release: v4.0.0-beta.1
2025-09-02 23:47:21 +08:00
Soulter
e24a5b4cb5 Revert "Release: v4.0.0-beta.1 (#2509)" (#2609)
This reverts commit f88031b0c9.
2025-09-02 23:44:36 +08:00
Soulter
f88031b0c9 Release: v4.0.0-beta.1 (#2509)
* Refactor: using sqlmodel(sqlchemy+pydantic) as ORM framework and switch to async-based sqlite operation (#2294)

* stage

* stage

* refactor: using sqlchemy as ORM framework, switch to async-based sqlite operation

- using sqlmodel as ORM(based on sqlchemy and pydantic)
- add Persona, Preference, PlatformMessageHistory table

* fix: conversation

* fix: remove redundant explicit session.commit, and fix some type error

* fix: conversation context issue

* chore: remove comments

* chore: remove exclude_content param

* Fix: 当多个相同消息平台实例部署时上下文可能混乱(共享) (#2298)

* perf: update astrbot event session format, using platfrom id to ensure uniqueness

fixes: #1000

* fix: 更新 MessageSession 类以使用 platform_id 作为唯一标识符,并调整相关方法以确保一致性

* fix: 更新 MessageSession 文档以明确 platform_id 的赋值规则,并调整 get_platform 和 get_platform_inst 方法的返回类型

* Improve: 引入全新的人格管理模式以及重构函数工具管理器 (#2305)

* feat: add persona management

* refactor:  重构函数工具管理器,引入 ToolSet,并让 Persona 支持绑定 Tools

* feat: 更新 Persona 工具选择逻辑,支持全选和指定工具的切换

* feat: 更新 BaseDatabase 中的 persona 方法返回类型,支持返回 None

* fix: platform id

* feat: add support to sync mcp servers from ModelScope (#2313)

* fix: 修复访问令牌的空格问题

* chore: 移除 MCP 市场相关逻辑 (#2314)

* chore: 移除 MCP 市场相关路由

* Refactor: 重构配置文件管理,以支持更灵活的、会话粒度的(基于 umo part)配置文件隔离 (#2328)

* refactor: 重构配置文件管理,以支持更灵活的、基于 umo part 的配置文件隔离

* Refactor: 重构配置前端页面,新增数个配置项 (#2331)

* refactor: 重构配置前端页面,新增数个配置项

* feat: 完善多配置文件结构

* perf: 系统配置入口

* fix: normal config item list not display

* fix: 修复 axios 请求中的上下文引用问题

* chore: remove status checking in chat page

* fix: 修复 stage 在不同 pipeline 中被重复使用的问题和 persona 相关问题

* Feature: 增加图片转述提供商配置、支持用户自定义模型模态能力 (#2422)

* feat: 增加图片转述提供商配置、支持用户自定义模型模态能力

* fix: 修复 LLMRequestSubStage 中会话管理方法参数不一致的问题,简化方法调用

* Feature: 优化 WebSearch 的爬取网页速度并且支持使用 Tavily 作为搜索引擎 (#2427)

* feat: 优化了 websearch 的速度;支持 Tavily 作为搜索引擎

* fix: 优化日志记录格式,修复搜索结果处理中的索引和内容显示问题

* feat: 添加对话选中状态管理,优化默认对话加载逻辑

* feat: 支持通过解析URL 的方式导入网页数据到知识库 (#2280)

* feat:为webchat页面添加一个手动上传文件按钮(目前只处理图片)

* fix:上传后清空value,允许触发change事件以多次上传同一张图片

* perf:webchat页面消息发送后清空图片预览缩略图,维持与文本信息行为一致

* perf:将文件输入的值重置为空字符串以提升浏览器兼容性

* feat:webchat文件上传按钮支持多选文件上传

* fix:释放blob URL以防止内存泄漏

* perf:并行化sendMessage中的图片获取逻辑

* feat:完成从url获取部分的UI

* feat: 添加从URL导入功能的组件

* fix: 优化导入结果处理,添加整体摘要和主题摘要的文件命名

* perf: 更新url导入选项添加默认值

* perf: 在导入url的部分配置项未启用时隐藏暂不使用的下拉框选项

* feat: 添加上传前提提示信息至导入url至知识库功能

* feat: 更新导入功能提示信息,添加上传状态通知

* fix: 优化url转知识库错误处理

* feat: 合并知识库的上传文件和 URL 标签页

* feat: 删除导入URL至知识库功能的相关组件

---------

Co-authored-by: Soulter <905617992@qq.com>

* feat: 添加条件显示逻辑以优化插件配置项的可见性管理 (#2433)

* Feature: 支持在 WebUI 配置文件页中配置默认知识库 (#2437)

* feat: 支持配置默认知识库

* chore: clean code

* refactor: 重构 Function Tool 管理并初步引入 Multi Agent 及 Agent Handsoff 机制  (#2454)

* stage

* refactor: 重构 Function Tool 管理并引入 multi agent handsoff 机制

- Updated `star_request.py` to use the global `call_handler` instead of context-specific calls.
- Modified `entities.py` to remove the dependency on `FunctionToolManager` and streamline the function tool handling.
- Refactored `func_tool_manager.py` to simplify the `FunctionTool` class and its methods, removing deprecated code and enhancing clarity.
- Adjusted `provider.py` to align with the new function tool structure, removing unnecessary type unions.
- Enhanced `star_handler.py` to support agent registration and tool association, introducing `RegisteringAgent` for better encapsulation.
- Updated `star_manager.py` to handle tool registration for agents, ensuring proper binding of handlers.
- Revised `main.py` in the web searcher package to utilize the new agent registration system for web search tools.

* chore: websearch

* perf: 减少嵌套

* chore: 移除未使用的 mcp 导入

* feat: 添加 WebUI 迁移助手以及相关迁移方法 (#2477)

* fix: 修复迁移对话时的一些问题

* feat: 增加工具使用模型能力选项

* feat: 添加知识库插件更新检查和更新功能

* perf: 调整 WebUI sidebar 顺序

* refactor: 重构 SharedPreference 类并采用数据库存储替换 json 存储 (#2482)

* perf: 使用 run_coroutine_threadsafe

Co-authored-by: Raven95676 <raven95676@gmail.com>

* Feature: 支持配置重排序模型(vLLM API 格式)用于 score 任务 (#2496)

* feat: 支持添加重排序模型(vLLM API 格式)用于 score 任务

* fix: update rerank API base URL to use localhost

* feat: 知识库支持配置重排序模型

* fix: remove debug print statement for reranked results in FaissVecDB

* fix: 移除知识库中的提示文本

* Feature: 支持在配置文件配置可用的插件组 (#2505)

* feat: 增加可用插件集合配置项

* remove: 旧版平台可用性配置

已经基于多配置文件实现。

* feat: 应用配置文件插件可用性配置

* perf: hoist if from if

* feat: llm_tool 装饰器返回值支持返回 mcp 库中 tool 的返回值类型(mcp.type.CallToolResult) (#2507)

* fix: add type definition for migrationDialog and ensure open method exists before calling

* chore: update project version to 4.0.0

* feat: 多 t2i 服务的随机负载均衡 (#2529)

* fix: bugfixes

* Improve: 扩大配置文件生效范围的自定义程度到会话粒度 (#2532)

* feat: 扩大配置文件生效范围的自定义程度

* perf: 冲突检测

* refactor: simplify config form validation and improve conflict message clarity

* chore: clean code

* feat: 插件配置支持多个快捷魔法配置项

* chore: 修复当自动更新 webchat title 时,history 被重置的问题

* bugfixes

* feat: add custom T2I template editor (#2581)

* perf: add option to clear provider selection in ProviderSelector component

* 📦 release: bump verstion to v4.0.0-beta.1

* chore: delete uv.lock

---------

Co-authored-by: RC-CHN <67079377+RC-CHN@users.noreply.github.com>
Co-authored-by: Raven95676 <raven95676@gmail.com>
2025-09-02 23:39:24 +08:00
Soulter
830151e6da chore: delete uv.lock 2025-09-02 23:31:51 +08:00
Soulter
1e14fba81a 📦 release: bump verstion to v4.0.0-beta.1 2025-09-02 23:27:55 +08:00
33 changed files with 2574 additions and 1696 deletions

View File

@@ -27,6 +27,33 @@ jobs:
if: github.event_name == 'workflow_dispatch'
run: git checkout ${{ steps.get-latest-tag.outputs.latest_tag }}
- name: Check if version is pre-release
id: check-prerelease
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
version="${{ steps.get-latest-tag.outputs.latest_tag }}"
else
version="${{ github.ref_name }}"
fi
if [[ "$version" == *"beta"* ]] || [[ "$version" == *"alpha"* ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
echo "Version $version is a pre-release, will not push latest tag"
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
echo "Version $version is a stable release, will push latest tag"
fi
- name: Build Dashboard
run: |
cd dashboard
npm install
npm run build
mkdir -p dist/assets
echo $(git rev-parse HEAD) > dist/assets/version
cd ..
mkdir -p data
cp -r dashboard/dist data/
- name: Set QEMU
uses: docker/setup-qemu-action@v3
@@ -53,9 +80,9 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/astrbot:latest
${{ steps.check-prerelease.outputs.is_prerelease == 'false' && format('{0}/astrbot:latest', secrets.DOCKER_HUB_USERNAME) || '' }}
${{ secrets.DOCKER_HUB_USERNAME }}/astrbot:${{ github.event_name == 'workflow_dispatch' && steps.get-latest-tag.outputs.latest_tag || github.ref_name }}
ghcr.io/soulter/astrbot:latest
${{ steps.check-prerelease.outputs.is_prerelease == 'false' && 'ghcr.io/soulter/astrbot:latest' || '' }}
ghcr.io/soulter/astrbot:${{ github.event_name == 'workflow_dispatch' && steps.get-latest-tag.outputs.latest_tag || github.ref_name }}
- name: Post build notifications

View File

@@ -37,7 +37,10 @@ async def check_dashboard(astrbot_root: Path) -> None:
):
click.echo("正在安装管理面板...")
await download_dashboard(
path="data/dashboard.zip", extract_path=str(astrbot_root)
path="data/dashboard.zip",
extract_path=str(astrbot_root),
version=f"v{VERSION}",
latest=False,
)
click.echo("管理面板安装完成")
@@ -50,7 +53,10 @@ async def check_dashboard(astrbot_root: Path) -> None:
version = dashboard_version.split("v")[1]
click.echo(f"管理面板版本: {version}")
await download_dashboard(
path="data/dashboard.zip", extract_path=str(astrbot_root)
path="data/dashboard.zip",
extract_path=str(astrbot_root),
version=f"v{VERSION}",
latest=False,
)
except Exception as e:
click.echo(f"下载管理面板失败: {e}")
@@ -59,7 +65,10 @@ async def check_dashboard(astrbot_root: Path) -> None:
click.echo("初始化管理面板目录...")
try:
await download_dashboard(
path=str(astrbot_root / "dashboard.zip"), extract_path=str(astrbot_root)
path=str(astrbot_root / "dashboard.zip"),
extract_path=str(astrbot_root),
version=f"v{VERSION}",
latest=False,
)
click.echo("管理面板初始化完成")
except Exception as e:

View File

@@ -36,13 +36,21 @@ class AstrBotConfigManager:
self.confs: dict[str, AstrBotConfig] = {}
"""uuid / "default" -> AstrBotConfig"""
self.confs["default"] = default_config
self.abconf_data = None
self._load_all_configs()
def _get_abconf_data(self) -> dict:
"""获取所有的 abconf 数据"""
if self.abconf_data is None:
self.abconf_data = self.sp.get(
"abconf_mapping", {}, scope="global", scope_id="global"
)
return self.abconf_data
def _load_all_configs(self):
"""Load all configurations from the shared preferences."""
abconf_data = self.sp.get(
"abconf_mapping", {}, scope="global", scope_id="global"
)
abconf_data = self._get_abconf_data()
self.abconf_data = abconf_data
for uuid_, meta in abconf_data.items():
filename = meta["path"]
conf_path = os.path.join(get_astrbot_config_path(), filename)
@@ -72,9 +80,7 @@ class AstrBotConfigManager:
ConfInfo: 包含配置文件的 uuid, 路径和名称等信息, 是一个 dict 类型
"""
# uuid -> { "umop": list, "path": str, "name": str }
abconf_data = self.sp.get(
"abconf_mapping", {}, scope="global", scope_id="global"
)
abconf_data = self._get_abconf_data()
if isinstance(umo, MessageSession):
umo = str(umo)
else:
@@ -115,6 +121,7 @@ class AstrBotConfigManager:
"name": random_word,
}
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
self.abconf_data = abconf_data
def get_conf(self, umo: str | MessageSession | None) -> AstrBotConfig:
"""获取指定 umo 的配置文件。如果不存在,则 fallback 到默认配置文件。"""
@@ -147,9 +154,7 @@ class AstrBotConfigManager:
"""获取所有配置文件的元数据列表"""
conf_list = []
conf_list.append(DEFAULT_CONFIG_CONF_INFO)
abconf_mapping = self.sp.get(
"abconf_mapping", {}, scope="global", scope_id="global"
)
abconf_mapping = self._get_abconf_data()
for uuid_, meta in abconf_mapping.items():
conf_list.append(ConfInfo(**meta, id=uuid_))
return conf_list
@@ -218,6 +223,7 @@ class AstrBotConfigManager:
# 从映射中移除
del abconf_data[conf_id]
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
self.abconf_data = abconf_data
logger.info(f"成功删除配置文件 {conf_id}")
return True
@@ -263,6 +269,7 @@ class AstrBotConfigManager:
# 保存更新
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
self.abconf_data = abconf_data
logger.info(f"成功更新配置文件 {conf_id} 的信息")
return True

View File

@@ -6,7 +6,7 @@ import os
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
VERSION = "4.0.0"
VERSION = "4.0.0-beta.5"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
# 默认配置
@@ -866,6 +866,9 @@ CONFIG_METADATA_2 = {
"provider_type": "text_to_speech",
"enable": False,
"edge-tts-voice": "zh-CN-XiaoxiaoNeural",
"rate": "+0%",
"volume": "+0%",
"pitch": "+0Hz",
"timeout": 20,
},
"GSV TTS(本地加载)": {
@@ -1913,7 +1916,7 @@ CONFIG_METADATA_3 = {
"type": "bool",
},
"provider_settings.identifier": {
"description": "用户感知",
"description": "用户识别",
"type": "bool",
},
"provider_settings.datetime_system_prompt": {
@@ -1926,7 +1929,7 @@ CONFIG_METADATA_3 = {
},
"provider_settings.max_agent_step": {
"description": "工具调用轮数上限",
"type": "bool",
"type": "int",
},
"provider_settings.streaming_response": {
"description": "流式回复",
@@ -2288,7 +2291,7 @@ CONFIG_METADATA_3_SYSTEM = {
"condition": {
"t2i_strategy": "remote",
},
"_special": "t2i_template"
"_special": "t2i_template",
},
"log_level": {
"description": "控制台日志级别",
@@ -2321,6 +2324,11 @@ CONFIG_METADATA_3_SYSTEM = {
"type": "string",
"hint": "启用后,会以添加环境变量的方式设置代理。格式为 `http://ip:port`",
},
"no_proxy": {
"description": "直连地址列表",
"type": "list",
"items": {"type": "string"},
},
},
}
},

View File

@@ -299,7 +299,9 @@ class LLMRequestSubStage(Stage):
self.max_context_length - 1,
)
self.streaming_response: bool = settings["streaming_response"]
self.max_step: int = settings.get("max_agent_step", 10)
self.max_step: int = settings.get("max_agent_step", 30)
if isinstance(self.max_step, bool): # workaround: #2622
self.max_step = 30
self.show_tool_use: bool = settings.get("show_tool_use_status", True)
for bwp in self.bot_wake_prefixs:
@@ -434,7 +436,9 @@ class LLMRequestSubStage(Stage):
provider_cfg = provider.provider_config.get("modalities", ["tool_use"])
# 如果模型不支持工具使用,但请求中包含工具列表,则清空。
if "tool_use" not in provider_cfg:
logger.debug(f"用户设置提供商 {provider} 不支持工具使用,清空工具列表。")
logger.debug(
f"用户设置提供商 {provider} 不支持工具使用,清空工具列表。"
)
req.func_tool = None
# 插件可用性设置
if event.plugins_name is not None and req.func_tool:

View File

@@ -67,12 +67,19 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
session_id: str,
messages: list[dict],
):
if event:
await bot.send(event=event, message=messages)
elif is_group:
# session_id 必须是纯数字字符串
session_id = int(session_id) if session_id.isdigit() else None
if is_group and isinstance(session_id, int):
await bot.send_group_msg(group_id=session_id, message=messages)
else:
elif not is_group and isinstance(session_id, int):
await bot.send_private_msg(user_id=session_id, message=messages)
elif isinstance(event, Event): # 最后兜底
await bot.send(event=event, message=messages)
else:
raise ValueError(
f"无法发送消息:缺少有效的数字 session_id({session_id}) 或 event({event})"
)
@classmethod
async def send_message(
@@ -83,7 +90,15 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
is_group: bool = False,
session_id: str = None,
):
"""发送消息"""
"""发送消息至 QQ 协议端aiocqhttp
Args:
bot (CQHttp): aiocqhttp 机器人实例
message_chain (MessageChain): 要发送的消息链
event (Event | None, optional): aiocqhttp 事件对象.
is_group (bool, optional): 是否为群消息.
session_id (str | None, optional): 会话 ID群号或 QQ 号
"""
# 转发消息、文件消息不能和普通消息混在一起发送
send_one_by_one = any(
@@ -122,18 +137,15 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
async def send(self, message: MessageChain):
"""发送消息"""
event = self.message_obj.raw_message
assert isinstance(event, Event), "Event must be an instance of aiocqhttp.Event"
is_group = False
if self.get_group_id():
is_group = True
session_id = self.get_group_id()
else:
session_id = self.get_sender_id()
event = getattr(self.message_obj, "raw_message", None)
is_group = bool(self.get_group_id())
session_id = self.get_group_id() if is_group else self.get_sender_id()
await self.send_message(
bot=self.bot,
message_chain=message,
event=event,
event=event, # 不强制要求一定是 Event
is_group=is_group,
session_id=session_id,
)

View File

@@ -4,9 +4,11 @@ import json
from astrbot.core.utils.io import download_image_by_url
from astrbot import logger
from dataclasses import dataclass, field
from typing import List, Dict, Type
from typing import List, Dict, Type, Any
from astrbot.core.agent.tool import ToolSet
from openai.types.chat.chat_completion import ChatCompletion
from google.genai.types import GenerateContentResponse
from anthropic.types import Message
from openai.types.chat.chat_completion_message_tool_call import (
ChatCompletionMessageToolCall,
)
@@ -30,11 +32,11 @@ class ProviderMetaData:
desc: str = ""
"""提供商适配器描述."""
provider_type: ProviderType = ProviderType.CHAT_COMPLETION
cls_type: Type = None
cls_type: Type | None = None
default_config_tmpl: dict = None
default_config_tmpl: dict | None = None
"""平台的默认配置模板"""
provider_display_name: str = None
provider_display_name: str | None = None
"""显示在 WebUI 配置页中的提供商名称,如空则是 type"""
@@ -58,7 +60,7 @@ class ToolCallMessageSegment:
class AssistantMessageSegment:
"""OpenAI 格式的上下文中 role 为 assistant 的消息段。参考: https://platform.openai.com/docs/guides/function-calling"""
content: str = None
content: str | None = None
tool_calls: List[ChatCompletionMessageToolCall | Dict] = field(default_factory=list)
role: str = "assistant"
@@ -205,17 +207,17 @@ class ProviderRequest:
class LLMResponse:
role: str
"""角色, assistant, tool, err"""
result_chain: MessageChain = None
result_chain: MessageChain | None = None
"""返回的消息链"""
tools_call_args: List[Dict[str, any]] = field(default_factory=list)
tools_call_args: List[Dict[str, Any]] = field(default_factory=list)
"""工具调用参数"""
tools_call_name: List[str] = field(default_factory=list)
"""工具调用名称"""
tools_call_ids: List[str] = field(default_factory=list)
"""工具调用 ID"""
raw_completion: ChatCompletion = None
_new_record: Dict[str, any] = None
raw_completion: ChatCompletion | GenerateContentResponse | Message | None = None
_new_record: Dict[str, Any] | None = None
_completion_text: str = ""
@@ -226,12 +228,12 @@ class LLMResponse:
self,
role: str,
completion_text: str = "",
result_chain: MessageChain = None,
tools_call_args: List[Dict[str, any]] = None,
tools_call_name: List[str] = None,
tools_call_ids: List[str] = None,
raw_completion: ChatCompletion = None,
_new_record: Dict[str, any] = None,
result_chain: MessageChain | None = None,
tools_call_args: List[Dict[str, Any]] | None = None,
tools_call_name: List[str] | None = None,
tools_call_ids: List[str] | None = None,
raw_completion: ChatCompletion | None = None,
_new_record: Dict[str, Any] | None = None,
is_chunk: bool = False,
):
"""初始化 LLMResponse

View File

@@ -15,7 +15,7 @@ from astrbot import logger
from astrbot.api.provider import Provider
from astrbot.core.message.message_event_result import MessageChain
from astrbot.core.provider.entities import LLMResponse
from astrbot.core.provider.func_tool_manager import FuncCall
from astrbot.core.provider.func_tool_manager import ToolSet
from astrbot.core.utils.io import download_image_by_url
from ..register import register_provider_adapter
@@ -61,7 +61,7 @@ class ProviderGoogleGenAI(Provider):
default_persona,
)
self.api_keys: list = provider_config.get("key", [])
self.chosen_api_key: str = self.api_keys[0] if len(self.api_keys) > 0 else None
self.chosen_api_key: str = self.api_keys[0] if len(self.api_keys) > 0 else ""
self.timeout: int = int(provider_config.get("timeout", 180))
self.api_base: Optional[str] = provider_config.get("api_base", None)
@@ -96,6 +96,9 @@ class ProviderGoogleGenAI(Provider):
async def _handle_api_error(self, e: APIError, keys: list[str]) -> bool:
"""处理API错误返回是否需要重试"""
if e.message is None:
e.message = ""
if e.code == 429 or "API key not valid" in e.message:
keys.remove(self.chosen_api_key)
if len(keys) > 0:
@@ -119,7 +122,7 @@ class ProviderGoogleGenAI(Provider):
async def _prepare_query_config(
self,
payloads: dict,
tools: Optional[FuncCall] = None,
tools: Optional[ToolSet] = None,
system_instruction: Optional[str] = None,
modalities: Optional[list[str]] = None,
temperature: float = 0.7,
@@ -321,11 +324,15 @@ class ProviderGoogleGenAI(Provider):
@staticmethod
def _process_content_parts(
result: types.GenerateContentResponse, llm_response: LLMResponse
candidate: types.Candidate, llm_response: LLMResponse
) -> MessageChain:
"""处理内容部分并构建消息链"""
finish_reason = result.candidates[0].finish_reason
result_parts: Optional[types.Part] = result.candidates[0].content.parts
if not candidate.content:
logger.warning(f"收到的 candidate.content 为空: {candidate}")
raise Exception("API 返回的 candidate.content 为空。")
finish_reason = candidate.finish_reason
result_parts: list[types.Part] | None = candidate.content.parts
if finish_reason == types.FinishReason.SAFETY:
raise Exception("模型生成内容未通过 Gemini 平台的安全检查")
@@ -343,22 +350,28 @@ class ProviderGoogleGenAI(Provider):
raise Exception("模型生成内容违反 Gemini 平台政策")
if not result_parts:
logger.debug(result.candidates)
raise Exception("API 返回的内容为空。")
logger.warning(f"收到的 candidate.content.parts 为空: {candidate}")
raise Exception("API 返回的 candidate.content.parts 为空。")
chain = []
part: types.Part
# 暂时这样Fallback
if all(
part.inline_data and part.inline_data.mime_type.startswith("image/")
part.inline_data
and part.inline_data.mime_type
and part.inline_data.mime_type.startswith("image/")
for part in result_parts
):
chain.append(Comp.Plain("这是图片"))
for part in result_parts:
if part.text:
chain.append(Comp.Plain(part.text))
elif part.function_call:
elif (
part.function_call
and part.function_call.name is not None
and part.function_call.args is not None
):
llm_response.role = "tool"
llm_response.tools_call_name.append(part.function_call.name)
llm_response.tools_call_args.append(part.function_call.args)
@@ -366,11 +379,16 @@ class ProviderGoogleGenAI(Provider):
llm_response.tools_call_ids.append(
part.function_call.id or part.function_call.name
)
elif part.inline_data and part.inline_data.mime_type.startswith("image/"):
elif (
part.inline_data
and part.inline_data.mime_type
and part.inline_data.mime_type.startswith("image/")
and part.inline_data.data
):
chain.append(Comp.Image.fromBytes(part.inline_data.data))
return MessageChain(chain=chain)
async def _query(self, payloads: dict, tools: FuncCall) -> LLMResponse:
async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
"""非流式请求 Gemini API"""
system_instruction = next(
(msg["content"] for msg in payloads["messages"] if msg["role"] == "system"),
@@ -396,6 +414,10 @@ class ProviderGoogleGenAI(Provider):
config=config,
)
if not result.candidates:
logger.error(f"请求失败, 返回的 candidates 为空: {result}")
raise Exception("请求失败, 返回的 candidates 为空。")
if result.candidates[0].finish_reason == types.FinishReason.RECITATION:
if temperature > 2:
raise Exception("温度参数已超过最大值2仍然发生recitation")
@@ -408,6 +430,8 @@ class ProviderGoogleGenAI(Provider):
break
except APIError as e:
if e.message is None:
e.message = ""
if "Developer instruction is not enabled" in e.message:
logger.warning(
f"{self.get_model()} 不支持 system prompt已自动去除(影响人格设置)"
@@ -432,11 +456,13 @@ class ProviderGoogleGenAI(Provider):
llm_response = LLMResponse("assistant")
llm_response.raw_completion = result
llm_response.result_chain = self._process_content_parts(result, llm_response)
llm_response.result_chain = self._process_content_parts(
result.candidates[0], llm_response
)
return llm_response
async def _query_stream(
self, payloads: dict, tools: FuncCall
self, payloads: dict, tools: ToolSet | None
) -> AsyncGenerator[LLMResponse, None]:
"""流式请求 Gemini API"""
system_instruction = next(
@@ -459,6 +485,8 @@ class ProviderGoogleGenAI(Provider):
)
break
except APIError as e:
if e.message is None:
e.message = ""
if "Developer instruction is not enabled" in e.message:
logger.warning(
f"{self.get_model()} 不支持 system prompt已自动去除(影响人格设置)"
@@ -478,13 +506,20 @@ class ProviderGoogleGenAI(Provider):
async for chunk in result:
llm_response = LLMResponse("assistant", is_chunk=True)
if not chunk.candidates:
logger.warning(f"收到的 chunk 中 candidates 为空: {chunk}")
continue
if not chunk.candidates[0].content:
logger.warning(f"收到的 chunk 中 content 为空: {chunk}")
continue
if chunk.candidates[0].content.parts and any(
part.function_call for part in chunk.candidates[0].content.parts
):
llm_response = LLMResponse("assistant", is_chunk=False)
llm_response.raw_completion = chunk
llm_response.result_chain = self._process_content_parts(
chunk, llm_response
chunk.candidates[0], llm_response
)
yield llm_response
return
@@ -500,7 +535,7 @@ class ProviderGoogleGenAI(Provider):
final_response = LLMResponse("assistant", is_chunk=False)
final_response.raw_completion = chunk
final_response.result_chain = self._process_content_parts(
chunk, final_response
chunk.candidates[0], final_response
)
break
@@ -566,6 +601,8 @@ class ProviderGoogleGenAI(Provider):
continue
break
raise Exception("请求失败。")
async def text_chat_stream(
self,
prompt,
@@ -621,7 +658,9 @@ class ProviderGoogleGenAI(Provider):
return [
m.name.replace("models/", "")
for m in models
if "generateContent" in m.supported_actions
if m.supported_actions
and "generateContent" in m.supported_actions
and m.name
]
except APIError as e:
raise Exception(f"获取模型列表失败: {e.message}")
@@ -636,7 +675,7 @@ class ProviderGoogleGenAI(Provider):
self.chosen_api_key = key
self._init_client()
async def assemble_context(self, text: str, image_urls: list[str] = None):
async def assemble_context(self, text: str, image_urls: list[str] | None = None):
"""
组装上下文。
"""

View File

@@ -100,9 +100,9 @@ class ProviderOpenAIOfficial(Provider):
del payloads[key]
model = payloads.get("model", "")
# 针对 qwen3 模型的特殊处理:非流式调用必须设置 enable_thinking=false
if "qwen3" in model.lower():
extra_body["enable_thinking"] = False
# 针对 qwen3 非 thinking 模型的特殊处理:非流式调用必须设置 enable_thinking=false
if "qwen3" in model.lower() and "thinking" not in model.lower():
extra_body["enable_thinking"] = False
# 针对 deepseek 模型的特殊处理deepseek-reasoner调用必须移除 tools ,否则将被切换至 deepseek-chat
elif model == "deepseek-reasoner" and "tools" in payloads:
del payloads["tools"]

View File

@@ -56,9 +56,7 @@ class AstrBotUpdator(RepoZipUpdator):
try:
if "astrbot" in os.path.basename(sys.argv[0]): # 兼容cli
if os.name == "nt":
args = [
f'"{arg}"' if " " in arg else arg for arg in sys.argv[1:]
]
args = [f'"{arg}"' if " " in arg else arg for arg in sys.argv[1:]]
else:
args = sys.argv[1:]
os.execl(sys.executable, py, "-m", "astrbot.cli.__main__", *args)
@@ -68,9 +66,13 @@ class AstrBotUpdator(RepoZipUpdator):
logger.error(f"重启失败({py}, {e}),请尝试手动重启。")
raise e
async def check_update(self, url: str, current_version: str) -> ReleaseInfo:
async def check_update(
self, url: str, current_version: str, consider_prerelease: bool = True
) -> ReleaseInfo:
"""检查更新"""
return await super().check_update(self.ASTRBOT_RELEASE_API, VERSION)
return await super().check_update(
self.ASTRBOT_RELEASE_API, VERSION, consider_prerelease
)
async def get_releases(self) -> list:
return await self.fetch_release_info(self.ASTRBOT_RELEASE_API)

View File

@@ -227,7 +227,7 @@ async def download_dashboard(
path = os.path.join(get_astrbot_data_path(), "dashboard.zip")
if latest or len(str(version)) != 40:
logger.info("准备下载最新发行版本的 AstrBot WebUI")
logger.info(f"准备下载 {version} 发行版本的 AstrBot WebUI 文件")
ver_name = "latest" if latest else version
dashboard_release_url = f"https://astrbot-registry.soulter.top/download/astrbot-dashboard/{ver_name}/dist.zip"
try:

View File

@@ -107,16 +107,38 @@ class RepoZipUpdator:
"""Semver 版本比较"""
return VersionComparator.compare_version(v1, v2)
async def check_update(self, url: str, current_version: str) -> ReleaseInfo | None:
async def check_update(
self, url: str, current_version: str, consider_prerelease: bool = True
) -> ReleaseInfo | None:
update_data = await self.fetch_release_info(url)
tag_name = update_data[0]["tag_name"]
sel_release_data = None
if consider_prerelease:
tag_name = update_data[0]["tag_name"]
sel_release_data = update_data[0]
else:
for data in update_data:
# 跳过带有 alpha、beta 等预发布标签的版本
if re.search(
r"[\-_.]?(alpha|beta|rc|dev)[\-_.]?\d*$",
data["tag_name"],
re.IGNORECASE,
):
continue
tag_name = data["tag_name"]
sel_release_data = data
break
if not sel_release_data or not tag_name:
logger.error("未找到合适的发布版本")
return None
if self.compare_version(current_version, tag_name) >= 0:
return None
return ReleaseInfo(
version=tag_name,
published_at=update_data[0]["published_at"],
body=update_data[0]["body"],
published_at=sel_release_data["published_at"],
body=f"{tag_name}\n\n{sel_release_data['body']}",
)
async def download_from_repo_url(self, target_path: str, repo_url: str, proxy=""):

View File

@@ -18,6 +18,7 @@ from astrbot.core.provider.register import provider_registry
from astrbot.core.star.star import star_registry
from astrbot.core import logger, html_renderer
from astrbot.core.provider import Provider
from astrbot.core.provider.provider import RerankProvider
import asyncio
from astrbot.core.utils.t2i.network_strategy import CUSTOM_T2I_TEMPLATE_PATH
@@ -481,6 +482,19 @@ class ConfigRoute(Route):
)
status_info["status"] = "unavailable"
status_info["error"] = f"STT test failed: {str(e)}"
elif provider_capability_type == ProviderType.RERANK:
try:
assert isinstance(provider, RerankProvider)
await provider.rerank("Apple", documents=["apple", "banana"])
status_info["status"] = "available"
except Exception as e:
logger.error(
f"Error testing rerank provider {provider_name}: {e}",
exc_info=True,
)
status_info["status"] = "unavailable"
status_info["error"] = f"Rerank test failed: {str(e)}"
else:
logger.debug(
f"Provider {provider_name} is not a Chat Completion or Embedding provider. Marking as available without test. Meta: {meta}"

View File

@@ -57,7 +57,7 @@ class UpdateRoute(Route):
.__dict__
)
else:
ret = await self.astrbot_updator.check_update(None, None)
ret = await self.astrbot_updator.check_update(None, None, False)
return Response(
status="success",
message=str(ret) if ret is not None else "已经是最新版本了。",
@@ -100,9 +100,7 @@ class UpdateRoute(Route):
)
try:
await download_dashboard(
latest=latest, version=version, proxy=proxy
)
await download_dashboard(latest=latest, version=version, proxy=proxy)
except Exception as e:
logger.error(f"下载管理面板文件失败: {e}")
@@ -133,7 +131,7 @@ class UpdateRoute(Route):
async def update_dashboard(self):
try:
try:
await download_dashboard()
await download_dashboard(version=f"v{VERSION}", latest=False)
except Exception as e:
logger.error(f"下载管理面板文件失败: {e}")
return Response().error(f"下载管理面板文件失败: {e}").__dict__

5
changelogs/v3.5.27.md Normal file
View File

@@ -0,0 +1,5 @@
# What's Changed
1. 修复:构建 docker 镜像时同时构建 webui并放入镜像中。
2. 修复:下载 WebUI 文件时,明确版本号,以防止 latest 不一致导致下载的 WebUI 文件版本号与实际所需不符的问题。
3. 优化:优化版本检测,考虑预发布版本,移除 `更新到最新版本` 按钮

View File

@@ -0,0 +1,3 @@
# What's Changed
> 请仔细阅读:**这是 v4.0.0 的测试版本beta.3),功能尚未完全稳定和加入**。v4.0.0 被设计为向前兼容,如有任何插件兼容性问题或者其他异常请在 GitHub 提交 [Issue](https://github.com/AstrBotDevs/AstrBot/issues)。在测试版本期间,您可以无缝回退到旧版本的 AstrBot并且数据不受影响。新版本文档请[从此](https://docs-v4.astrbot.app/)访问,直到第一个 v4.0.0 稳定版本发布。

View File

@@ -0,0 +1,8 @@
# What's Changed
> 请仔细阅读:**这是 v4.0.0 的测试版本beta.4),功能尚未完全稳定和加入**。v4.0.0 被设计为向前兼容,如有任何插件兼容性问题或者其他异常请在 GitHub 提交 [Issue](https://github.com/AstrBotDevs/AstrBot/issues)。在测试版本期间,您可以无缝回退到旧版本的 AstrBot并且数据不受影响。新版本文档请[从此](https://docs-v4.astrbot.app/)访问,直到第一个 v4.0.0 稳定版本发布。
相较于 beta.3
1. 修复了主动回复时报错的问题
2. 数据迁移完毕之后引导重启程序

View File

@@ -0,0 +1,15 @@
# What's Changed
> 请仔细阅读:**这是 v4.0.0 的测试版本beta.4),功能尚未完全稳定和加入**。v4.0.0 被设计为向前兼容,如有任何插件兼容性问题或者其他异常请在 GitHub 提交 [Issue](https://github.com/AstrBotDevs/AstrBot/issues)。在测试版本期间,您可以无缝回退到旧版本的 AstrBot并且数据不受影响。新版本文档请[从此](https://docs-v4.astrbot.app/)访问,直到第一个 v4.0.0 稳定版本发布。
相较于 beta.4
1. ‼️修复:新版本在初次保存配置之后,调用 LLM 无法获得响应,但插件指令仍可以使用的问题
2. 修复部分情况下Dashboard 内修改配置保存后报错 UnicodeDecodeError
3. 修复:构建 docker 镜像时同时构建 webui并放入镜像中。
4. 修复:下载 WebUI 文件时,明确版本号,以防止 latest 不一致导致下载的 WebUI 文件版本号与实际所需不符的问题。
5. 优化:优化版本检测,考虑预发布版本,移除 `更新到最新版本` 按钮
6. 优化:增加 abconf_data 缓存,优化性能
7. 优化: 适配 qwen3 的 thinking 类模型
8. 优化: 完善对 rerank model 的可用性检测
9. 新增: 给添加 edge_tts 新增 rate, volume, pitch 参数 ([#2625](https://github.com/Soulter/AstrBot/issues/2625))

View File

@@ -12,7 +12,13 @@
<div v-if="migrationCompleted" class="text-center py-8">
<v-icon size="64" color="success" class="mb-4">mdi-check-circle</v-icon>
<h3 class="mb-4">{{ t('features.migration.dialog.completed') }}</h3>
{{ migrationResult?.message || t('features.migration.dialog.success') }}
<p class="mb-4">{{ migrationResult?.message || t('features.migration.dialog.success') }}</p>
<v-alert type="info" variant="tonal" class="mb-4">
<template v-slot:prepend>
<v-icon>mdi-information</v-icon>
</template>
{{ t('features.migration.dialog.restartRecommended') }}
</v-alert>
</div>
<div v-else-if="migrating" class="migration-in-progress">
@@ -80,8 +86,11 @@
<v-card-actions class="px-6 py-4">
<v-spacer></v-spacer>
<template v-if="migrationCompleted">
<v-btn color="primary" variant="elevated" @click="handleClose">
{{ t('core.common.confirm') }}
<v-btn color="grey" variant="text" @click="handleClose">
{{ t('core.common.close') }}
</v-btn>
<v-btn color="primary" variant="elevated" @click="restartAstrBot">
{{ t('features.migration.dialog.restartNow') }}
</v-btn>
</template>
<template v-else>
@@ -96,6 +105,8 @@
</v-card-actions>
</v-card>
</v-dialog>
<WaitingForRestart ref="wfr"></WaitingForRestart>
</template>
<script setup>
@@ -103,6 +114,7 @@ import { ref, computed, watch } from 'vue'
import axios from 'axios'
import { useI18n } from '@/i18n/composables'
import ConsoleDisplayer from './ConsoleDisplayer.vue'
import WaitingForRestart from './WaitingForRestart.vue'
const { t } = useI18n()
@@ -114,6 +126,7 @@ const migrationCompleted = ref(false)
const migrationResult = ref(null)
const platforms = ref([])
const selectedPlatforms = ref({})
const wfr = ref(null)
let resolvePromise = null
@@ -244,6 +257,15 @@ const getPlatformLabel = (platform) => {
return `${name}`
}
// 重启 AstrBot
const restartAstrBot = () => {
axios.post('/api/stat/restart-core').then(() => {
if (wfr.value) {
wfr.value.check();
}
})
}
// 打开对话框的方法
const open = () => {
isOpen.value = true

View File

@@ -34,7 +34,7 @@
"tip": "💡 TIP:",
"tipLink": "",
"tipContinue": "By default, the corresponding version of the WebUI files will be downloaded when switching versions. The WebUI code is located in the dashboard directory of the project, and you can use npm to build it yourself.",
"dockerTip": "The `Update to Latest Version` button will try to update both the bot main program and the dashboard. If you are using Docker deployment, you can also re-pull the image or use",
"dockerTip": "When switching versions, it will try to update both the bot main program and the dashboard. If you are using Docker deployment, you can also re-pull the image or use",
"dockerTipLink": "watchtower",
"dockerTipContinue": "to automatically monitor and pull.",
"table": {

View File

@@ -11,6 +11,8 @@
"migratingSubtitle": "Please wait patiently, do not close this window during migration",
"migrationError": "Migration failed",
"success": "Migration completed successfully!",
"completed": "Migration Completed"
"completed": "Migration Completed",
"restartRecommended": "It is recommended to restart the application for all changes to take effect.",
"restartNow": "Restart Now"
}
}

View File

@@ -33,7 +33,7 @@
},
"tip": "💡 TIP: ",
"tipContinue": "默认在切换版本时会下载对应版本的 WebUI 文件。WebUI 代码位于项目的 dashboard 目录,您可使用 npm 自行构建。",
"dockerTip": "`更新到最新版本` 按钮会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用",
"dockerTip": "切换版本时,会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用",
"dockerTipLink": "watchtower",
"dockerTipContinue": "来自动监控拉取。",
"table": {

View File

@@ -11,6 +11,8 @@
"migratingSubtitle": "请耐心等待,迁移过程中请勿关闭此窗口",
"migrationError": "迁移失败",
"success": "迁移成功完成!",
"completed": "迁移已完成"
"completed": "迁移已完成",
"restartRecommended": "建议重启应用程序以使所有更改生效。",
"restartNow": "立即重启"
}
}

View File

@@ -378,10 +378,6 @@ commonStore.getStartTime();
<!-- 发行版 -->
<v-tabs-window-item key="0" v-show="tab == 0">
<v-btn class="mt-4 mb-4" @click="switchVersion('latest')" color="primary" style="border-radius: 10px;"
:disabled="!hasNewVersion">
{{ t('core.header.updateDialog.updateToLatest') }}
</v-btn>
<div class="mb-4">
<small>{{ t('core.header.updateDialog.dockerTip') }} <a
href="https://containrrr.dev/watchtower/usage-overview/">{{ t('core.header.updateDialog.dockerTipLink') }}</a> {{ t('core.header.updateDialog.dockerTipContinue') }}</small>

View File

@@ -481,7 +481,7 @@ export default {
if (!this.fetched) return;
const postData = {
config: this.config_data
config: JSON.parse(JSON.stringify(this.config_data)),
};
if (this.isSystemConfig) {

View File

@@ -383,8 +383,7 @@ export default {
messageType: 'success',
personaIdRules: [
v => !!v || this.tm('validation.required'),
v => (v && v.length >= 2) || this.tm('validation.minLength', { min: 2 }),
v => /^[a-zA-Z0-9_-]+$/.test(v) || this.tm('validation.alphanumeric')
v => (v && v.length >= 0) || this.tm('validation.minLength', { min: 2 }),
],
systemPromptRules: [
v => !!v || this.tm('validation.required'),

View File

@@ -44,10 +44,10 @@ async def check_dashboard_files():
if v is not None:
# has file
if v == f"v{VERSION}":
logger.info("管理面板文件已是最新。")
logger.info("WebUI 版本已是最新。")
else:
logger.warning(
"检测到管理面板有更新。可以使用 /dashboard_update 命令更新"
f"检测到 WebUI 版本 ({v}) 与当前 AstrBot 版本 (v{VERSION}) 不符"
)
return
@@ -56,7 +56,7 @@ async def check_dashboard_files():
)
try:
await download_dashboard()
await download_dashboard(version=f"v{VERSION}", latest=False)
except Exception as e:
logger.critical(f"下载管理面板文件失败: {e}")
return

View File

@@ -25,14 +25,18 @@ class LongTermMemory:
def cfg(self, event: AstrMessageEvent):
cfg = self.context.get_config(umo=event.unified_msg_origin)
try:
max_cnt = int(cfg["group_message_max_cnt"])
max_cnt = int(cfg["provider_ltm_settings"]["group_message_max_cnt"])
except BaseException as e:
logger.error(e)
max_cnt = 300
image_caption = cfg["image_caption"]
image_caption_prompt = cfg["image_caption_prompt"]
image_caption_provider_id = cfg["image_caption_provider_id"]
active_reply = cfg["active_reply"]
image_caption = (
True
if cfg["provider_settings"]["default_image_caption_provider_id"]
else False
)
image_caption_prompt = cfg["provider_settings"]["image_caption_prompt"]
image_caption_provider_id = cfg["provider_settings"]["default_image_caption_provider_id"]
active_reply = cfg["provider_ltm_settings"]["active_reply"]
enable_active_reply = active_reply.get("enable", False)
ar_method = active_reply["method"]
ar_possibility = active_reply["possibility_reply"]
@@ -88,7 +92,9 @@ class LongTermMemory:
if cfg["ar_whitelist"] and (
event.unified_msg_origin not in cfg["ar_whitelist"]
and (event.get_group_id() and event.get_group_id() not in cfg["ar_whitelist"])
and (
event.get_group_id() and event.get_group_id() not in cfg["ar_whitelist"]
)
):
return False
@@ -112,7 +118,6 @@ class LongTermMemory:
if isinstance(comp, Plain):
final_message += f" {comp.text}"
elif isinstance(comp, Image):
cfg = self.cfg(event)
if cfg["image_caption"]:
try:
caption = await self.get_image_caption(

View File

@@ -1094,7 +1094,7 @@ UID: {user_id} 此 ID 可用于设置管理员。
@filter.command("dashboard_update")
async def update_dashboard(self, event: AstrMessageEvent):
yield event.plain_result("正在尝试更新管理面板...")
await download_dashboard()
await download_dashboard(version=f"v{VERSION}", latest=False)
yield event.plain_result("管理面板更新完成。")
@filter.command("set")
@@ -1110,7 +1110,9 @@ UID: {user_id} 此 ID 可用于设置管理员。
@filter.command("unset")
async def unset_variable(self, event: AstrMessageEvent, key: str):
uid = event.unified_msg_origin
session_var = await sp.session_get(umo="uid", key="session_variables", default={})
session_var = await sp.session_get(
umo="uid", key="session_variables", default={}
)
if key not in session_var:
yield event.plain_result("没有那个变量名。格式 /unset 变量名。")
@@ -1176,9 +1178,7 @@ UID: {user_id} 此 ID 可用于设置管理员。
)
return
prompt = self.ltm.ar_prompt
if not prompt:
prompt = event.message_str
prompt = event.message_str
yield event.request_llm(
prompt=prompt,

View File

@@ -463,11 +463,11 @@ class Main(star.Star):
yield event.image_result(image_path)
elif match.group(1) == "FILE":
file_path = os.path.join(workplace_path, match.group(2))
logger.debug(f"Sending file: {file_path}")
file_s3_url = await self.file_upload(file_path)
logger.info(f"文件上传到 AstrBot 云节点: {file_s3_url}")
# logger.debug(f"Sending file: {file_path}")
# file_s3_url = await self.file_upload(file_path)
# logger.info(f"文件上传到 AstrBot 云节点: {file_s3_url}")
file_name = os.path.basename(file_path)
chain = [File(name=file_name, file=file_s3_url)]
chain = [File(name=file_name, file=file_path)]
yield event.set_result(MessageEventResult(chain=chain))
elif "Traceback (most recent call last)" in log or "[Error]: " in log:

View File

@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
version = "4.0.0"
version = "4.0.0-beta.5"
description = "易上手的多平台 LLM 聊天机器人及开发框架"
readme = "README.md"
requires-python = ">=3.10"
@@ -21,7 +21,7 @@ dependencies = [
"deprecated>=1.2.18",
"dingtalk-stream>=0.22.1",
"docstring-parser>=0.16",
"faiss-cpu>=1.10.0",
"faiss-cpu==1.10.0",
"filelock>=3.18.0",
"google-genai>=1.14.0",
"googlesearch-python>=1.3.0",

View File

@@ -39,4 +39,7 @@ faiss-cpu
aiosqlite
py-cord>=2.6.1
slack-sdk
pydub
pydub
sqlmodel
deprecated
sqlalchemy[asyncio]

3824
uv.lock generated

File diff suppressed because it is too large Load Diff