Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26c6a2950f | |||
| 5082876de3 | |||
| e50e7ad3d5 | |||
| 45a4a6b6da | |||
| 02918b7267 | |||
| 6c662a36c1 | |||
| b78fe3822a | |||
| 35eda37e83 | |||
| 176a8e7067 | |||
| 61d4f1fd4b | |||
| 121b68995e | |||
| d11f1d8dae | |||
| c0ef2b5064 | |||
| 2a7308363e | |||
| dc0c556f96 | |||
| ba2ee1c0aa | |||
| 0f8b550d68 | |||
| ed1fc98821 | |||
| fa53b468fd | |||
| 4e2533d320 | |||
| 388ae49e55 | |||
| f3f347dcba | |||
| 655be3519c | |||
| 06df2940af | |||
| 4149549e42 | |||
| da351991f8 | |||
| 3305152e50 | |||
| bea7bae674 | |||
| 45773d38ed | |||
| 8d4c176314 | |||
| 9ca5c87c4c | |||
| 36a6f00e5f | |||
| e24a5b4cb5 | |||
| f88031b0c9 | |||
| 830151e6da | |||
| 1e14fba81a |
@@ -27,6 +27,33 @@ jobs:
|
|||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: git checkout ${{ steps.get-latest-tag.outputs.latest_tag }}
|
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
|
- name: Set QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
@@ -53,9 +80,9 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
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 }}
|
${{ 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 }}
|
ghcr.io/soulter/astrbot:${{ github.event_name == 'workflow_dispatch' && steps.get-latest-tag.outputs.latest_tag || github.ref_name }}
|
||||||
|
|
||||||
- name: Post build notifications
|
- name: Post build notifications
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ async def check_dashboard(astrbot_root: Path) -> None:
|
|||||||
):
|
):
|
||||||
click.echo("正在安装管理面板...")
|
click.echo("正在安装管理面板...")
|
||||||
await download_dashboard(
|
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("管理面板安装完成")
|
click.echo("管理面板安装完成")
|
||||||
|
|
||||||
@@ -50,7 +53,10 @@ async def check_dashboard(astrbot_root: Path) -> None:
|
|||||||
version = dashboard_version.split("v")[1]
|
version = dashboard_version.split("v")[1]
|
||||||
click.echo(f"管理面板版本: {version}")
|
click.echo(f"管理面板版本: {version}")
|
||||||
await download_dashboard(
|
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:
|
except Exception as e:
|
||||||
click.echo(f"下载管理面板失败: {e}")
|
click.echo(f"下载管理面板失败: {e}")
|
||||||
@@ -59,7 +65,10 @@ async def check_dashboard(astrbot_root: Path) -> None:
|
|||||||
click.echo("初始化管理面板目录...")
|
click.echo("初始化管理面板目录...")
|
||||||
try:
|
try:
|
||||||
await download_dashboard(
|
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("管理面板初始化完成")
|
click.echo("管理面板初始化完成")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -36,13 +36,21 @@ class AstrBotConfigManager:
|
|||||||
self.confs: dict[str, AstrBotConfig] = {}
|
self.confs: dict[str, AstrBotConfig] = {}
|
||||||
"""uuid / "default" -> AstrBotConfig"""
|
"""uuid / "default" -> AstrBotConfig"""
|
||||||
self.confs["default"] = default_config
|
self.confs["default"] = default_config
|
||||||
|
self.abconf_data = None
|
||||||
self._load_all_configs()
|
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):
|
def _load_all_configs(self):
|
||||||
"""Load all configurations from the shared preferences."""
|
"""Load all configurations from the shared preferences."""
|
||||||
abconf_data = self.sp.get(
|
abconf_data = self._get_abconf_data()
|
||||||
"abconf_mapping", {}, scope="global", scope_id="global"
|
self.abconf_data = abconf_data
|
||||||
)
|
|
||||||
for uuid_, meta in abconf_data.items():
|
for uuid_, meta in abconf_data.items():
|
||||||
filename = meta["path"]
|
filename = meta["path"]
|
||||||
conf_path = os.path.join(get_astrbot_config_path(), filename)
|
conf_path = os.path.join(get_astrbot_config_path(), filename)
|
||||||
@@ -72,9 +80,7 @@ class AstrBotConfigManager:
|
|||||||
ConfInfo: 包含配置文件的 uuid, 路径和名称等信息, 是一个 dict 类型
|
ConfInfo: 包含配置文件的 uuid, 路径和名称等信息, 是一个 dict 类型
|
||||||
"""
|
"""
|
||||||
# uuid -> { "umop": list, "path": str, "name": str }
|
# uuid -> { "umop": list, "path": str, "name": str }
|
||||||
abconf_data = self.sp.get(
|
abconf_data = self._get_abconf_data()
|
||||||
"abconf_mapping", {}, scope="global", scope_id="global"
|
|
||||||
)
|
|
||||||
if isinstance(umo, MessageSession):
|
if isinstance(umo, MessageSession):
|
||||||
umo = str(umo)
|
umo = str(umo)
|
||||||
else:
|
else:
|
||||||
@@ -115,6 +121,7 @@ class AstrBotConfigManager:
|
|||||||
"name": random_word,
|
"name": random_word,
|
||||||
}
|
}
|
||||||
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
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:
|
def get_conf(self, umo: str | MessageSession | None) -> AstrBotConfig:
|
||||||
"""获取指定 umo 的配置文件。如果不存在,则 fallback 到默认配置文件。"""
|
"""获取指定 umo 的配置文件。如果不存在,则 fallback 到默认配置文件。"""
|
||||||
@@ -147,9 +154,7 @@ class AstrBotConfigManager:
|
|||||||
"""获取所有配置文件的元数据列表"""
|
"""获取所有配置文件的元数据列表"""
|
||||||
conf_list = []
|
conf_list = []
|
||||||
conf_list.append(DEFAULT_CONFIG_CONF_INFO)
|
conf_list.append(DEFAULT_CONFIG_CONF_INFO)
|
||||||
abconf_mapping = self.sp.get(
|
abconf_mapping = self._get_abconf_data()
|
||||||
"abconf_mapping", {}, scope="global", scope_id="global"
|
|
||||||
)
|
|
||||||
for uuid_, meta in abconf_mapping.items():
|
for uuid_, meta in abconf_mapping.items():
|
||||||
conf_list.append(ConfInfo(**meta, id=uuid_))
|
conf_list.append(ConfInfo(**meta, id=uuid_))
|
||||||
return conf_list
|
return conf_list
|
||||||
@@ -218,6 +223,7 @@ class AstrBotConfigManager:
|
|||||||
# 从映射中移除
|
# 从映射中移除
|
||||||
del abconf_data[conf_id]
|
del abconf_data[conf_id]
|
||||||
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
||||||
|
self.abconf_data = abconf_data
|
||||||
|
|
||||||
logger.info(f"成功删除配置文件 {conf_id}")
|
logger.info(f"成功删除配置文件 {conf_id}")
|
||||||
return True
|
return True
|
||||||
@@ -263,6 +269,7 @@ class AstrBotConfigManager:
|
|||||||
|
|
||||||
# 保存更新
|
# 保存更新
|
||||||
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
||||||
|
self.abconf_data = abconf_data
|
||||||
logger.info(f"成功更新配置文件 {conf_id} 的信息")
|
logger.info(f"成功更新配置文件 {conf_id} 的信息")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import os
|
|||||||
|
|
||||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
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")
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
||||||
|
|
||||||
# 默认配置
|
# 默认配置
|
||||||
@@ -51,10 +51,6 @@ DEFAULT_CONFIG = {
|
|||||||
"enable": True,
|
"enable": True,
|
||||||
"default_provider_id": "",
|
"default_provider_id": "",
|
||||||
"default_image_caption_provider_id": "",
|
"default_image_caption_provider_id": "",
|
||||||
"default_summarize_provider_id": "",
|
|
||||||
"context_exceed_calc_method": "token_size",
|
|
||||||
"max_token_size": 128000,
|
|
||||||
"max_context_length": 100,
|
|
||||||
"image_caption_prompt": "Please describe the image using Chinese.",
|
"image_caption_prompt": "Please describe the image using Chinese.",
|
||||||
"provider_pool": ["*"], # "*" 表示使用所有可用的提供者
|
"provider_pool": ["*"], # "*" 表示使用所有可用的提供者
|
||||||
"wake_prefix": "",
|
"wake_prefix": "",
|
||||||
@@ -68,6 +64,7 @@ DEFAULT_CONFIG = {
|
|||||||
"default_personality": "default",
|
"default_personality": "default",
|
||||||
"persona_pool": ["*"],
|
"persona_pool": ["*"],
|
||||||
"prompt_prefix": "",
|
"prompt_prefix": "",
|
||||||
|
"max_context_length": -1,
|
||||||
"dequeue_context_length": 1,
|
"dequeue_context_length": 1,
|
||||||
"streaming_response": False,
|
"streaming_response": False,
|
||||||
"show_tool_use_status": False,
|
"show_tool_use_status": False,
|
||||||
@@ -869,6 +866,9 @@ CONFIG_METADATA_2 = {
|
|||||||
"provider_type": "text_to_speech",
|
"provider_type": "text_to_speech",
|
||||||
"enable": False,
|
"enable": False,
|
||||||
"edge-tts-voice": "zh-CN-XiaoxiaoNeural",
|
"edge-tts-voice": "zh-CN-XiaoxiaoNeural",
|
||||||
|
"rate": "+0%",
|
||||||
|
"volume": "+0%",
|
||||||
|
"pitch": "+0Hz",
|
||||||
"timeout": 20,
|
"timeout": 20,
|
||||||
},
|
},
|
||||||
"GSV TTS(本地加载)": {
|
"GSV TTS(本地加载)": {
|
||||||
@@ -1835,12 +1835,6 @@ CONFIG_METADATA_3 = {
|
|||||||
"_special": "select_provider",
|
"_special": "select_provider",
|
||||||
"hint": "留空时使用第一个模型。",
|
"hint": "留空时使用第一个模型。",
|
||||||
},
|
},
|
||||||
"provider_settings.default_summarize_provider_id": {
|
|
||||||
"description": "默认对话总结模型",
|
|
||||||
"type": "string",
|
|
||||||
"_special": "select_provider",
|
|
||||||
"hint": "留空代表不进行对话总结。可用于压缩上下文以减少 token 用量,并一定程度上保持历史聊天记忆。",
|
|
||||||
},
|
|
||||||
"provider_settings.default_image_caption_provider_id": {
|
"provider_settings.default_image_caption_provider_id": {
|
||||||
"description": "默认图片转述模型",
|
"description": "默认图片转述模型",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -1859,28 +1853,6 @@ CONFIG_METADATA_3 = {
|
|||||||
"hint": "留空代表不使用。",
|
"hint": "留空代表不使用。",
|
||||||
"_special": "select_provider_tts",
|
"_special": "select_provider_tts",
|
||||||
},
|
},
|
||||||
"provider_settings.context_exceed_calc_method": {
|
|
||||||
"description": "上下文超限的触发策略",
|
|
||||||
"type": "string",
|
|
||||||
"options": ["token_size", "context_length"],
|
|
||||||
"labels": ["基于 Token 长度(估算)", "基于对话轮数"],
|
|
||||||
"hint": "如配置了对话总结模型,则触发时总结对话内容,否则丢弃最旧部分。"
|
|
||||||
},
|
|
||||||
"provider_settings.max_context_length": {
|
|
||||||
"description": "对话轮数上限",
|
|
||||||
"type": "int",
|
|
||||||
"condition": {
|
|
||||||
"provider_settings.context_exceed_calc_method": "context_length"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"provider_settings.max_token_size": {
|
|
||||||
"description": "Token 长度上限(估算)",
|
|
||||||
"type": "int",
|
|
||||||
"hint": "超出这个数量时丢弃最旧的部分。",
|
|
||||||
"condition": {
|
|
||||||
"provider_settings.context_exceed_calc_method": "token_size"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"provider_settings.image_caption_prompt": {
|
"provider_settings.image_caption_prompt": {
|
||||||
"description": "图片转述提示词",
|
"description": "图片转述提示词",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -1944,7 +1916,7 @@ CONFIG_METADATA_3 = {
|
|||||||
"type": "bool",
|
"type": "bool",
|
||||||
},
|
},
|
||||||
"provider_settings.identifier": {
|
"provider_settings.identifier": {
|
||||||
"description": "用户感知",
|
"description": "用户识别",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
},
|
},
|
||||||
"provider_settings.datetime_system_prompt": {
|
"provider_settings.datetime_system_prompt": {
|
||||||
@@ -1967,6 +1939,11 @@ CONFIG_METADATA_3 = {
|
|||||||
"description": "不支持流式回复的平台采取分段输出",
|
"description": "不支持流式回复的平台采取分段输出",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
},
|
},
|
||||||
|
"provider_settings.max_context_length": {
|
||||||
|
"description": "最多携带对话轮数",
|
||||||
|
"type": "int",
|
||||||
|
"hint": "超出这个数量时丢弃最旧的部分,一轮聊天记为 1 条。-1 为不限制。",
|
||||||
|
},
|
||||||
"provider_settings.dequeue_context_length": {
|
"provider_settings.dequeue_context_length": {
|
||||||
"description": "丢弃对话轮数",
|
"description": "丢弃对话轮数",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
@@ -2314,7 +2291,7 @@ CONFIG_METADATA_3_SYSTEM = {
|
|||||||
"condition": {
|
"condition": {
|
||||||
"t2i_strategy": "remote",
|
"t2i_strategy": "remote",
|
||||||
},
|
},
|
||||||
"_special": "t2i_template"
|
"_special": "t2i_template",
|
||||||
},
|
},
|
||||||
"log_level": {
|
"log_level": {
|
||||||
"description": "控制台日志级别",
|
"description": "控制台日志级别",
|
||||||
@@ -2347,6 +2324,11 @@ CONFIG_METADATA_3_SYSTEM = {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"hint": "启用后,会以添加环境变量的方式设置代理。格式为 `http://ip:port`",
|
"hint": "启用后,会以添加环境变量的方式设置代理。格式为 `http://ip:port`",
|
||||||
},
|
},
|
||||||
|
"no_proxy": {
|
||||||
|
"description": "直连地址列表",
|
||||||
|
"type": "list",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -299,7 +299,9 @@ class LLMRequestSubStage(Stage):
|
|||||||
self.max_context_length - 1,
|
self.max_context_length - 1,
|
||||||
)
|
)
|
||||||
self.streaming_response: bool = settings["streaming_response"]
|
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)
|
self.show_tool_use: bool = settings.get("show_tool_use_status", True)
|
||||||
|
|
||||||
for bwp in self.bot_wake_prefixs:
|
for bwp in self.bot_wake_prefixs:
|
||||||
@@ -434,7 +436,9 @@ class LLMRequestSubStage(Stage):
|
|||||||
provider_cfg = provider.provider_config.get("modalities", ["tool_use"])
|
provider_cfg = provider.provider_config.get("modalities", ["tool_use"])
|
||||||
# 如果模型不支持工具使用,但请求中包含工具列表,则清空。
|
# 如果模型不支持工具使用,但请求中包含工具列表,则清空。
|
||||||
if "tool_use" not in provider_cfg:
|
if "tool_use" not in provider_cfg:
|
||||||
logger.debug(f"用户设置提供商 {provider} 不支持工具使用,清空工具列表。")
|
logger.debug(
|
||||||
|
f"用户设置提供商 {provider} 不支持工具使用,清空工具列表。"
|
||||||
|
)
|
||||||
req.func_tool = None
|
req.func_tool = None
|
||||||
# 插件可用性设置
|
# 插件可用性设置
|
||||||
if event.plugins_name is not None and req.func_tool:
|
if event.plugins_name is not None and req.func_tool:
|
||||||
|
|||||||
@@ -67,12 +67,19 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|||||||
session_id: str,
|
session_id: str,
|
||||||
messages: list[dict],
|
messages: list[dict],
|
||||||
):
|
):
|
||||||
if event:
|
# session_id 必须是纯数字字符串
|
||||||
await bot.send(event=event, message=messages)
|
session_id = int(session_id) if session_id.isdigit() else None
|
||||||
elif is_group:
|
|
||||||
|
if is_group and isinstance(session_id, int):
|
||||||
await bot.send_group_msg(group_id=session_id, message=messages)
|
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)
|
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
|
@classmethod
|
||||||
async def send_message(
|
async def send_message(
|
||||||
@@ -83,7 +90,15 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|||||||
is_group: bool = False,
|
is_group: bool = False,
|
||||||
session_id: str = None,
|
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(
|
send_one_by_one = any(
|
||||||
@@ -122,18 +137,15 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|||||||
|
|
||||||
async def send(self, message: MessageChain):
|
async def send(self, message: MessageChain):
|
||||||
"""发送消息"""
|
"""发送消息"""
|
||||||
event = self.message_obj.raw_message
|
event = getattr(self.message_obj, "raw_message", None)
|
||||||
assert isinstance(event, Event), "Event must be an instance of aiocqhttp.Event"
|
|
||||||
is_group = False
|
is_group = bool(self.get_group_id())
|
||||||
if self.get_group_id():
|
session_id = self.get_group_id() if is_group else self.get_sender_id()
|
||||||
is_group = True
|
|
||||||
session_id = self.get_group_id()
|
|
||||||
else:
|
|
||||||
session_id = self.get_sender_id()
|
|
||||||
await self.send_message(
|
await self.send_message(
|
||||||
bot=self.bot,
|
bot=self.bot,
|
||||||
message_chain=message,
|
message_chain=message,
|
||||||
event=event,
|
event=event, # 不强制要求一定是 Event
|
||||||
is_group=is_group,
|
is_group=is_group,
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import json
|
|||||||
from astrbot.core.utils.io import download_image_by_url
|
from astrbot.core.utils.io import download_image_by_url
|
||||||
from astrbot import logger
|
from astrbot import logger
|
||||||
from dataclasses import dataclass, field
|
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 astrbot.core.agent.tool import ToolSet
|
||||||
from openai.types.chat.chat_completion import ChatCompletion
|
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 (
|
from openai.types.chat.chat_completion_message_tool_call import (
|
||||||
ChatCompletionMessageToolCall,
|
ChatCompletionMessageToolCall,
|
||||||
)
|
)
|
||||||
@@ -30,11 +32,11 @@ class ProviderMetaData:
|
|||||||
desc: str = ""
|
desc: str = ""
|
||||||
"""提供商适配器描述."""
|
"""提供商适配器描述."""
|
||||||
provider_type: ProviderType = ProviderType.CHAT_COMPLETION
|
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"""
|
"""显示在 WebUI 配置页中的提供商名称,如空则是 type"""
|
||||||
|
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ class ToolCallMessageSegment:
|
|||||||
class AssistantMessageSegment:
|
class AssistantMessageSegment:
|
||||||
"""OpenAI 格式的上下文中 role 为 assistant 的消息段。参考: https://platform.openai.com/docs/guides/function-calling"""
|
"""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)
|
tool_calls: List[ChatCompletionMessageToolCall | Dict] = field(default_factory=list)
|
||||||
role: str = "assistant"
|
role: str = "assistant"
|
||||||
|
|
||||||
@@ -205,17 +207,17 @@ class ProviderRequest:
|
|||||||
class LLMResponse:
|
class LLMResponse:
|
||||||
role: str
|
role: str
|
||||||
"""角色, assistant, tool, err"""
|
"""角色, 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_name: List[str] = field(default_factory=list)
|
||||||
"""工具调用名称"""
|
"""工具调用名称"""
|
||||||
tools_call_ids: List[str] = field(default_factory=list)
|
tools_call_ids: List[str] = field(default_factory=list)
|
||||||
"""工具调用 ID"""
|
"""工具调用 ID"""
|
||||||
|
|
||||||
raw_completion: ChatCompletion = None
|
raw_completion: ChatCompletion | GenerateContentResponse | Message | None = None
|
||||||
_new_record: Dict[str, any] = None
|
_new_record: Dict[str, Any] | None = None
|
||||||
|
|
||||||
_completion_text: str = ""
|
_completion_text: str = ""
|
||||||
|
|
||||||
@@ -226,12 +228,12 @@ class LLMResponse:
|
|||||||
self,
|
self,
|
||||||
role: str,
|
role: str,
|
||||||
completion_text: str = "",
|
completion_text: str = "",
|
||||||
result_chain: MessageChain = None,
|
result_chain: MessageChain | None = None,
|
||||||
tools_call_args: List[Dict[str, any]] = None,
|
tools_call_args: List[Dict[str, Any]] | None = None,
|
||||||
tools_call_name: List[str] = None,
|
tools_call_name: List[str] | None = None,
|
||||||
tools_call_ids: List[str] = None,
|
tools_call_ids: List[str] | None = None,
|
||||||
raw_completion: ChatCompletion = None,
|
raw_completion: ChatCompletion | None = None,
|
||||||
_new_record: Dict[str, any] = None,
|
_new_record: Dict[str, Any] | None = None,
|
||||||
is_chunk: bool = False,
|
is_chunk: bool = False,
|
||||||
):
|
):
|
||||||
"""初始化 LLMResponse
|
"""初始化 LLMResponse
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from astrbot import logger
|
|||||||
from astrbot.api.provider import Provider
|
from astrbot.api.provider import Provider
|
||||||
from astrbot.core.message.message_event_result import MessageChain
|
from astrbot.core.message.message_event_result import MessageChain
|
||||||
from astrbot.core.provider.entities import LLMResponse
|
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 astrbot.core.utils.io import download_image_by_url
|
||||||
|
|
||||||
from ..register import register_provider_adapter
|
from ..register import register_provider_adapter
|
||||||
@@ -61,7 +61,7 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
default_persona,
|
default_persona,
|
||||||
)
|
)
|
||||||
self.api_keys: list = provider_config.get("key", [])
|
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.timeout: int = int(provider_config.get("timeout", 180))
|
||||||
|
|
||||||
self.api_base: Optional[str] = provider_config.get("api_base", None)
|
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:
|
async def _handle_api_error(self, e: APIError, keys: list[str]) -> bool:
|
||||||
"""处理API错误,返回是否需要重试"""
|
"""处理API错误,返回是否需要重试"""
|
||||||
|
if e.message is None:
|
||||||
|
e.message = ""
|
||||||
|
|
||||||
if e.code == 429 or "API key not valid" in e.message:
|
if e.code == 429 or "API key not valid" in e.message:
|
||||||
keys.remove(self.chosen_api_key)
|
keys.remove(self.chosen_api_key)
|
||||||
if len(keys) > 0:
|
if len(keys) > 0:
|
||||||
@@ -119,7 +122,7 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
async def _prepare_query_config(
|
async def _prepare_query_config(
|
||||||
self,
|
self,
|
||||||
payloads: dict,
|
payloads: dict,
|
||||||
tools: Optional[FuncCall] = None,
|
tools: Optional[ToolSet] = None,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: Optional[str] = None,
|
||||||
modalities: Optional[list[str]] = None,
|
modalities: Optional[list[str]] = None,
|
||||||
temperature: float = 0.7,
|
temperature: float = 0.7,
|
||||||
@@ -321,11 +324,15 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _process_content_parts(
|
def _process_content_parts(
|
||||||
result: types.GenerateContentResponse, llm_response: LLMResponse
|
candidate: types.Candidate, llm_response: LLMResponse
|
||||||
) -> MessageChain:
|
) -> MessageChain:
|
||||||
"""处理内容部分并构建消息链"""
|
"""处理内容部分并构建消息链"""
|
||||||
finish_reason = result.candidates[0].finish_reason
|
if not candidate.content:
|
||||||
result_parts: Optional[types.Part] = result.candidates[0].content.parts
|
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:
|
if finish_reason == types.FinishReason.SAFETY:
|
||||||
raise Exception("模型生成内容未通过 Gemini 平台的安全检查")
|
raise Exception("模型生成内容未通过 Gemini 平台的安全检查")
|
||||||
@@ -343,22 +350,28 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
raise Exception("模型生成内容违反 Gemini 平台政策")
|
raise Exception("模型生成内容违反 Gemini 平台政策")
|
||||||
|
|
||||||
if not result_parts:
|
if not result_parts:
|
||||||
logger.debug(result.candidates)
|
logger.warning(f"收到的 candidate.content.parts 为空: {candidate}")
|
||||||
raise Exception("API 返回的内容为空。")
|
raise Exception("API 返回的 candidate.content.parts 为空。")
|
||||||
|
|
||||||
chain = []
|
chain = []
|
||||||
part: types.Part
|
part: types.Part
|
||||||
|
|
||||||
# 暂时这样Fallback
|
# 暂时这样Fallback
|
||||||
if all(
|
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
|
for part in result_parts
|
||||||
):
|
):
|
||||||
chain.append(Comp.Plain("这是图片"))
|
chain.append(Comp.Plain("这是图片"))
|
||||||
for part in result_parts:
|
for part in result_parts:
|
||||||
if part.text:
|
if part.text:
|
||||||
chain.append(Comp.Plain(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.role = "tool"
|
||||||
llm_response.tools_call_name.append(part.function_call.name)
|
llm_response.tools_call_name.append(part.function_call.name)
|
||||||
llm_response.tools_call_args.append(part.function_call.args)
|
llm_response.tools_call_args.append(part.function_call.args)
|
||||||
@@ -366,11 +379,16 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
llm_response.tools_call_ids.append(
|
llm_response.tools_call_ids.append(
|
||||||
part.function_call.id or part.function_call.name
|
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))
|
chain.append(Comp.Image.fromBytes(part.inline_data.data))
|
||||||
return MessageChain(chain=chain)
|
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"""
|
"""非流式请求 Gemini API"""
|
||||||
system_instruction = next(
|
system_instruction = next(
|
||||||
(msg["content"] for msg in payloads["messages"] if msg["role"] == "system"),
|
(msg["content"] for msg in payloads["messages"] if msg["role"] == "system"),
|
||||||
@@ -396,6 +414,10 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
config=config,
|
config=config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not result.candidates:
|
||||||
|
logger.error(f"请求失败, 返回的 candidates 为空: {result}")
|
||||||
|
raise Exception("请求失败, 返回的 candidates 为空。")
|
||||||
|
|
||||||
if result.candidates[0].finish_reason == types.FinishReason.RECITATION:
|
if result.candidates[0].finish_reason == types.FinishReason.RECITATION:
|
||||||
if temperature > 2:
|
if temperature > 2:
|
||||||
raise Exception("温度参数已超过最大值2,仍然发生recitation")
|
raise Exception("温度参数已超过最大值2,仍然发生recitation")
|
||||||
@@ -408,6 +430,8 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
break
|
break
|
||||||
|
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
|
if e.message is None:
|
||||||
|
e.message = ""
|
||||||
if "Developer instruction is not enabled" in e.message:
|
if "Developer instruction is not enabled" in e.message:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)"
|
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)"
|
||||||
@@ -432,11 +456,13 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
|
|
||||||
llm_response = LLMResponse("assistant")
|
llm_response = LLMResponse("assistant")
|
||||||
llm_response.raw_completion = result
|
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
|
return llm_response
|
||||||
|
|
||||||
async def _query_stream(
|
async def _query_stream(
|
||||||
self, payloads: dict, tools: FuncCall
|
self, payloads: dict, tools: ToolSet | None
|
||||||
) -> AsyncGenerator[LLMResponse, None]:
|
) -> AsyncGenerator[LLMResponse, None]:
|
||||||
"""流式请求 Gemini API"""
|
"""流式请求 Gemini API"""
|
||||||
system_instruction = next(
|
system_instruction = next(
|
||||||
@@ -459,6 +485,8 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
|
if e.message is None:
|
||||||
|
e.message = ""
|
||||||
if "Developer instruction is not enabled" in e.message:
|
if "Developer instruction is not enabled" in e.message:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)"
|
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)"
|
||||||
@@ -478,13 +506,20 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
async for chunk in result:
|
async for chunk in result:
|
||||||
llm_response = LLMResponse("assistant", is_chunk=True)
|
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(
|
if chunk.candidates[0].content.parts and any(
|
||||||
part.function_call for part in chunk.candidates[0].content.parts
|
part.function_call for part in chunk.candidates[0].content.parts
|
||||||
):
|
):
|
||||||
llm_response = LLMResponse("assistant", is_chunk=False)
|
llm_response = LLMResponse("assistant", is_chunk=False)
|
||||||
llm_response.raw_completion = chunk
|
llm_response.raw_completion = chunk
|
||||||
llm_response.result_chain = self._process_content_parts(
|
llm_response.result_chain = self._process_content_parts(
|
||||||
chunk, llm_response
|
chunk.candidates[0], llm_response
|
||||||
)
|
)
|
||||||
yield llm_response
|
yield llm_response
|
||||||
return
|
return
|
||||||
@@ -500,7 +535,7 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
final_response = LLMResponse("assistant", is_chunk=False)
|
final_response = LLMResponse("assistant", is_chunk=False)
|
||||||
final_response.raw_completion = chunk
|
final_response.raw_completion = chunk
|
||||||
final_response.result_chain = self._process_content_parts(
|
final_response.result_chain = self._process_content_parts(
|
||||||
chunk, final_response
|
chunk.candidates[0], final_response
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -566,6 +601,8 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
|
||||||
|
raise Exception("请求失败。")
|
||||||
|
|
||||||
async def text_chat_stream(
|
async def text_chat_stream(
|
||||||
self,
|
self,
|
||||||
prompt,
|
prompt,
|
||||||
@@ -621,7 +658,9 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
return [
|
return [
|
||||||
m.name.replace("models/", "")
|
m.name.replace("models/", "")
|
||||||
for m in 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:
|
except APIError as e:
|
||||||
raise Exception(f"获取模型列表失败: {e.message}")
|
raise Exception(f"获取模型列表失败: {e.message}")
|
||||||
@@ -636,7 +675,7 @@ class ProviderGoogleGenAI(Provider):
|
|||||||
self.chosen_api_key = key
|
self.chosen_api_key = key
|
||||||
self._init_client()
|
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):
|
||||||
"""
|
"""
|
||||||
组装上下文。
|
组装上下文。
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -100,9 +100,9 @@ class ProviderOpenAIOfficial(Provider):
|
|||||||
del payloads[key]
|
del payloads[key]
|
||||||
|
|
||||||
model = payloads.get("model", "")
|
model = payloads.get("model", "")
|
||||||
# 针对 qwen3 模型的特殊处理:非流式调用必须设置 enable_thinking=false
|
# 针对 qwen3 非 thinking 模型的特殊处理:非流式调用必须设置 enable_thinking=false
|
||||||
if "qwen3" in model.lower():
|
if "qwen3" in model.lower() and "thinking" not in model.lower():
|
||||||
extra_body["enable_thinking"] = False
|
extra_body["enable_thinking"] = False
|
||||||
# 针对 deepseek 模型的特殊处理:deepseek-reasoner调用必须移除 tools ,否则将被切换至 deepseek-chat
|
# 针对 deepseek 模型的特殊处理:deepseek-reasoner调用必须移除 tools ,否则将被切换至 deepseek-chat
|
||||||
elif model == "deepseek-reasoner" and "tools" in payloads:
|
elif model == "deepseek-reasoner" and "tools" in payloads:
|
||||||
del payloads["tools"]
|
del payloads["tools"]
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ class AstrBotUpdator(RepoZipUpdator):
|
|||||||
try:
|
try:
|
||||||
if "astrbot" in os.path.basename(sys.argv[0]): # 兼容cli
|
if "astrbot" in os.path.basename(sys.argv[0]): # 兼容cli
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
args = [
|
args = [f'"{arg}"' if " " in arg else arg for arg in sys.argv[1:]]
|
||||||
f'"{arg}"' if " " in arg else arg for arg in sys.argv[1:]
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
os.execl(sys.executable, py, "-m", "astrbot.cli.__main__", *args)
|
os.execl(sys.executable, py, "-m", "astrbot.cli.__main__", *args)
|
||||||
@@ -68,9 +66,13 @@ class AstrBotUpdator(RepoZipUpdator):
|
|||||||
logger.error(f"重启失败({py}, {e}),请尝试手动重启。")
|
logger.error(f"重启失败({py}, {e}),请尝试手动重启。")
|
||||||
raise 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:
|
async def get_releases(self) -> list:
|
||||||
return await self.fetch_release_info(self.ASTRBOT_RELEASE_API)
|
return await self.fetch_release_info(self.ASTRBOT_RELEASE_API)
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ async def download_dashboard(
|
|||||||
path = os.path.join(get_astrbot_data_path(), "dashboard.zip")
|
path = os.path.join(get_astrbot_data_path(), "dashboard.zip")
|
||||||
|
|
||||||
if latest or len(str(version)) != 40:
|
if latest or len(str(version)) != 40:
|
||||||
logger.info("准备下载最新发行版本的 AstrBot WebUI")
|
logger.info(f"准备下载 {version} 发行版本的 AstrBot WebUI 文件")
|
||||||
ver_name = "latest" if latest else version
|
ver_name = "latest" if latest else version
|
||||||
dashboard_release_url = f"https://astrbot-registry.soulter.top/download/astrbot-dashboard/{ver_name}/dist.zip"
|
dashboard_release_url = f"https://astrbot-registry.soulter.top/download/astrbot-dashboard/{ver_name}/dist.zip"
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -107,16 +107,38 @@ class RepoZipUpdator:
|
|||||||
"""Semver 版本比较"""
|
"""Semver 版本比较"""
|
||||||
return VersionComparator.compare_version(v1, v2)
|
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)
|
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:
|
if self.compare_version(current_version, tag_name) >= 0:
|
||||||
return None
|
return None
|
||||||
return ReleaseInfo(
|
return ReleaseInfo(
|
||||||
version=tag_name,
|
version=tag_name,
|
||||||
published_at=update_data[0]["published_at"],
|
published_at=sel_release_data["published_at"],
|
||||||
body=update_data[0]["body"],
|
body=f"{tag_name}\n\n{sel_release_data['body']}",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def download_from_repo_url(self, target_path: str, repo_url: str, proxy=""):
|
async def download_from_repo_url(self, target_path: str, repo_url: str, proxy=""):
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from astrbot.core.provider.register import provider_registry
|
|||||||
from astrbot.core.star.star import star_registry
|
from astrbot.core.star.star import star_registry
|
||||||
from astrbot.core import logger, html_renderer
|
from astrbot.core import logger, html_renderer
|
||||||
from astrbot.core.provider import Provider
|
from astrbot.core.provider import Provider
|
||||||
|
from astrbot.core.provider.provider import RerankProvider
|
||||||
import asyncio
|
import asyncio
|
||||||
from astrbot.core.utils.t2i.network_strategy import CUSTOM_T2I_TEMPLATE_PATH
|
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["status"] = "unavailable"
|
||||||
status_info["error"] = f"STT test failed: {str(e)}"
|
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:
|
else:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Provider {provider_name} is not a Chat Completion or Embedding provider. Marking as available without test. Meta: {meta}"
|
f"Provider {provider_name} is not a Chat Completion or Embedding provider. Marking as available without test. Meta: {meta}"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class UpdateRoute(Route):
|
|||||||
.__dict__
|
.__dict__
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ret = await self.astrbot_updator.check_update(None, None)
|
ret = await self.astrbot_updator.check_update(None, None, False)
|
||||||
return Response(
|
return Response(
|
||||||
status="success",
|
status="success",
|
||||||
message=str(ret) if ret is not None else "已经是最新版本了。",
|
message=str(ret) if ret is not None else "已经是最新版本了。",
|
||||||
@@ -100,9 +100,7 @@ class UpdateRoute(Route):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await download_dashboard(
|
await download_dashboard(latest=latest, version=version, proxy=proxy)
|
||||||
latest=latest, version=version, proxy=proxy
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"下载管理面板文件失败: {e}。")
|
logger.error(f"下载管理面板文件失败: {e}。")
|
||||||
|
|
||||||
@@ -133,7 +131,7 @@ class UpdateRoute(Route):
|
|||||||
async def update_dashboard(self):
|
async def update_dashboard(self):
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
await download_dashboard()
|
await download_dashboard(version=f"v{VERSION}", latest=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"下载管理面板文件失败: {e}。")
|
logger.error(f"下载管理面板文件失败: {e}。")
|
||||||
return Response().error(f"下载管理面板文件失败: {e}").__dict__
|
return Response().error(f"下载管理面板文件失败: {e}").__dict__
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# What's Changed
|
||||||
|
|
||||||
|
1. 修复:构建 docker 镜像时同时构建 webui,并放入镜像中。
|
||||||
|
2. 修复:下载 WebUI 文件时,明确版本号,以防止 latest 不一致导致下载的 WebUI 文件版本号与实际所需不符的问题。
|
||||||
|
3. 优化:优化版本检测,考虑预发布版本,移除 `更新到最新版本` 按钮
|
||||||
@@ -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 稳定版本发布。
|
||||||
@@ -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. 数据迁移完毕之后引导重启程序
|
||||||
@@ -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))
|
||||||
@@ -12,7 +12,13 @@
|
|||||||
<div v-if="migrationCompleted" class="text-center py-8">
|
<div v-if="migrationCompleted" class="text-center py-8">
|
||||||
<v-icon size="64" color="success" class="mb-4">mdi-check-circle</v-icon>
|
<v-icon size="64" color="success" class="mb-4">mdi-check-circle</v-icon>
|
||||||
<h3 class="mb-4">{{ t('features.migration.dialog.completed') }}</h3>
|
<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>
|
||||||
|
|
||||||
<div v-else-if="migrating" class="migration-in-progress">
|
<div v-else-if="migrating" class="migration-in-progress">
|
||||||
@@ -80,8 +86,11 @@
|
|||||||
<v-card-actions class="px-6 py-4">
|
<v-card-actions class="px-6 py-4">
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<template v-if="migrationCompleted">
|
<template v-if="migrationCompleted">
|
||||||
<v-btn color="primary" variant="elevated" @click="handleClose">
|
<v-btn color="grey" variant="text" @click="handleClose">
|
||||||
{{ t('core.common.confirm') }}
|
{{ t('core.common.close') }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="primary" variant="elevated" @click="restartAstrBot">
|
||||||
|
{{ t('features.migration.dialog.restartNow') }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -96,6 +105,8 @@
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<WaitingForRestart ref="wfr"></WaitingForRestart>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -103,6 +114,7 @@ import { ref, computed, watch } from 'vue'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useI18n } from '@/i18n/composables'
|
import { useI18n } from '@/i18n/composables'
|
||||||
import ConsoleDisplayer from './ConsoleDisplayer.vue'
|
import ConsoleDisplayer from './ConsoleDisplayer.vue'
|
||||||
|
import WaitingForRestart from './WaitingForRestart.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -114,6 +126,7 @@ const migrationCompleted = ref(false)
|
|||||||
const migrationResult = ref(null)
|
const migrationResult = ref(null)
|
||||||
const platforms = ref([])
|
const platforms = ref([])
|
||||||
const selectedPlatforms = ref({})
|
const selectedPlatforms = ref({})
|
||||||
|
const wfr = ref(null)
|
||||||
|
|
||||||
let resolvePromise = null
|
let resolvePromise = null
|
||||||
|
|
||||||
@@ -244,6 +257,15 @@ const getPlatformLabel = (platform) => {
|
|||||||
return `${name}`
|
return `${name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重启 AstrBot
|
||||||
|
const restartAstrBot = () => {
|
||||||
|
axios.post('/api/stat/restart-core').then(() => {
|
||||||
|
if (wfr.value) {
|
||||||
|
wfr.value.check();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 打开对话框的方法
|
// 打开对话框的方法
|
||||||
const open = () => {
|
const open = () => {
|
||||||
isOpen.value = true
|
isOpen.value = true
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"tip": "💡 TIP:",
|
"tip": "💡 TIP:",
|
||||||
"tipLink": "",
|
"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.",
|
"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",
|
"dockerTipLink": "watchtower",
|
||||||
"dockerTipContinue": "to automatically monitor and pull.",
|
"dockerTipContinue": "to automatically monitor and pull.",
|
||||||
"table": {
|
"table": {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
"migratingSubtitle": "Please wait patiently, do not close this window during migration",
|
"migratingSubtitle": "Please wait patiently, do not close this window during migration",
|
||||||
"migrationError": "Migration failed",
|
"migrationError": "Migration failed",
|
||||||
"success": "Migration completed successfully!",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
"tip": "💡 TIP: ",
|
"tip": "💡 TIP: ",
|
||||||
"tipContinue": "默认在切换版本时会下载对应版本的 WebUI 文件。WebUI 代码位于项目的 dashboard 目录,您可使用 npm 自行构建。",
|
"tipContinue": "默认在切换版本时会下载对应版本的 WebUI 文件。WebUI 代码位于项目的 dashboard 目录,您可使用 npm 自行构建。",
|
||||||
"dockerTip": "`更新到最新版本` 按钮会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用",
|
"dockerTip": "切换版本时,会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用",
|
||||||
"dockerTipLink": "watchtower",
|
"dockerTipLink": "watchtower",
|
||||||
"dockerTipContinue": "来自动监控拉取。",
|
"dockerTipContinue": "来自动监控拉取。",
|
||||||
"table": {
|
"table": {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
"migratingSubtitle": "请耐心等待,迁移过程中请勿关闭此窗口",
|
"migratingSubtitle": "请耐心等待,迁移过程中请勿关闭此窗口",
|
||||||
"migrationError": "迁移失败",
|
"migrationError": "迁移失败",
|
||||||
"success": "迁移成功完成!",
|
"success": "迁移成功完成!",
|
||||||
"completed": "迁移已完成"
|
"completed": "迁移已完成",
|
||||||
|
"restartRecommended": "建议重启应用程序以使所有更改生效。",
|
||||||
|
"restartNow": "立即重启"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,10 +378,6 @@ commonStore.getStartTime();
|
|||||||
|
|
||||||
<!-- 发行版 -->
|
<!-- 发行版 -->
|
||||||
<v-tabs-window-item key="0" v-show="tab == 0">
|
<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">
|
<div class="mb-4">
|
||||||
<small>{{ t('core.header.updateDialog.dockerTip') }} <a
|
<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>
|
href="https://containrrr.dev/watchtower/usage-overview/">{{ t('core.header.updateDialog.dockerTipLink') }}</a> {{ t('core.header.updateDialog.dockerTipContinue') }}</small>
|
||||||
|
|||||||
@@ -481,7 +481,7 @@ export default {
|
|||||||
if (!this.fetched) return;
|
if (!this.fetched) return;
|
||||||
|
|
||||||
const postData = {
|
const postData = {
|
||||||
config: this.config_data
|
config: JSON.parse(JSON.stringify(this.config_data)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.isSystemConfig) {
|
if (this.isSystemConfig) {
|
||||||
|
|||||||
@@ -383,8 +383,7 @@ export default {
|
|||||||
messageType: 'success',
|
messageType: 'success',
|
||||||
personaIdRules: [
|
personaIdRules: [
|
||||||
v => !!v || this.tm('validation.required'),
|
v => !!v || this.tm('validation.required'),
|
||||||
v => (v && v.length >= 2) || this.tm('validation.minLength', { min: 2 }),
|
v => (v && v.length >= 0) || this.tm('validation.minLength', { min: 2 }),
|
||||||
v => /^[a-zA-Z0-9_-]+$/.test(v) || this.tm('validation.alphanumeric')
|
|
||||||
],
|
],
|
||||||
systemPromptRules: [
|
systemPromptRules: [
|
||||||
v => !!v || this.tm('validation.required'),
|
v => !!v || this.tm('validation.required'),
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ async def check_dashboard_files():
|
|||||||
if v is not None:
|
if v is not None:
|
||||||
# has file
|
# has file
|
||||||
if v == f"v{VERSION}":
|
if v == f"v{VERSION}":
|
||||||
logger.info("管理面板文件已是最新。")
|
logger.info("WebUI 版本已是最新。")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"检测到管理面板有更新。可以使用 /dashboard_update 命令更新。"
|
f"检测到 WebUI 版本 ({v}) 与当前 AstrBot 版本 (v{VERSION}) 不符。"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ async def check_dashboard_files():
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await download_dashboard()
|
await download_dashboard(version=f"v{VERSION}", latest=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(f"下载管理面板文件失败: {e}。")
|
logger.critical(f"下载管理面板文件失败: {e}。")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -25,14 +25,18 @@ class LongTermMemory:
|
|||||||
def cfg(self, event: AstrMessageEvent):
|
def cfg(self, event: AstrMessageEvent):
|
||||||
cfg = self.context.get_config(umo=event.unified_msg_origin)
|
cfg = self.context.get_config(umo=event.unified_msg_origin)
|
||||||
try:
|
try:
|
||||||
max_cnt = int(cfg["group_message_max_cnt"])
|
max_cnt = int(cfg["provider_ltm_settings"]["group_message_max_cnt"])
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
max_cnt = 300
|
max_cnt = 300
|
||||||
image_caption = cfg["image_caption"]
|
image_caption = (
|
||||||
image_caption_prompt = cfg["image_caption_prompt"]
|
True
|
||||||
image_caption_provider_id = cfg["image_caption_provider_id"]
|
if cfg["provider_settings"]["default_image_caption_provider_id"]
|
||||||
active_reply = cfg["active_reply"]
|
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)
|
enable_active_reply = active_reply.get("enable", False)
|
||||||
ar_method = active_reply["method"]
|
ar_method = active_reply["method"]
|
||||||
ar_possibility = active_reply["possibility_reply"]
|
ar_possibility = active_reply["possibility_reply"]
|
||||||
@@ -88,7 +92,9 @@ class LongTermMemory:
|
|||||||
|
|
||||||
if cfg["ar_whitelist"] and (
|
if cfg["ar_whitelist"] and (
|
||||||
event.unified_msg_origin not in cfg["ar_whitelist"]
|
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
|
return False
|
||||||
|
|
||||||
@@ -112,7 +118,6 @@ class LongTermMemory:
|
|||||||
if isinstance(comp, Plain):
|
if isinstance(comp, Plain):
|
||||||
final_message += f" {comp.text}"
|
final_message += f" {comp.text}"
|
||||||
elif isinstance(comp, Image):
|
elif isinstance(comp, Image):
|
||||||
cfg = self.cfg(event)
|
|
||||||
if cfg["image_caption"]:
|
if cfg["image_caption"]:
|
||||||
try:
|
try:
|
||||||
caption = await self.get_image_caption(
|
caption = await self.get_image_caption(
|
||||||
|
|||||||
@@ -1094,7 +1094,7 @@ UID: {user_id} 此 ID 可用于设置管理员。
|
|||||||
@filter.command("dashboard_update")
|
@filter.command("dashboard_update")
|
||||||
async def update_dashboard(self, event: AstrMessageEvent):
|
async def update_dashboard(self, event: AstrMessageEvent):
|
||||||
yield event.plain_result("正在尝试更新管理面板...")
|
yield event.plain_result("正在尝试更新管理面板...")
|
||||||
await download_dashboard()
|
await download_dashboard(version=f"v{VERSION}", latest=False)
|
||||||
yield event.plain_result("管理面板更新完成。")
|
yield event.plain_result("管理面板更新完成。")
|
||||||
|
|
||||||
@filter.command("set")
|
@filter.command("set")
|
||||||
@@ -1110,7 +1110,9 @@ UID: {user_id} 此 ID 可用于设置管理员。
|
|||||||
@filter.command("unset")
|
@filter.command("unset")
|
||||||
async def unset_variable(self, event: AstrMessageEvent, key: str):
|
async def unset_variable(self, event: AstrMessageEvent, key: str):
|
||||||
uid = event.unified_msg_origin
|
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:
|
if key not in session_var:
|
||||||
yield event.plain_result("没有那个变量名。格式 /unset 变量名。")
|
yield event.plain_result("没有那个变量名。格式 /unset 变量名。")
|
||||||
@@ -1176,9 +1178,7 @@ UID: {user_id} 此 ID 可用于设置管理员。
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
prompt = self.ltm.ar_prompt
|
prompt = event.message_str
|
||||||
if not prompt:
|
|
||||||
prompt = event.message_str
|
|
||||||
|
|
||||||
yield event.request_llm(
|
yield event.request_llm(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
|
|||||||
@@ -463,11 +463,11 @@ class Main(star.Star):
|
|||||||
yield event.image_result(image_path)
|
yield event.image_result(image_path)
|
||||||
elif match.group(1) == "FILE":
|
elif match.group(1) == "FILE":
|
||||||
file_path = os.path.join(workplace_path, match.group(2))
|
file_path = os.path.join(workplace_path, match.group(2))
|
||||||
logger.debug(f"Sending file: {file_path}")
|
# logger.debug(f"Sending file: {file_path}")
|
||||||
file_s3_url = await self.file_upload(file_path)
|
# file_s3_url = await self.file_upload(file_path)
|
||||||
logger.info(f"文件上传到 AstrBot 云节点: {file_s3_url}")
|
# logger.info(f"文件上传到 AstrBot 云节点: {file_s3_url}")
|
||||||
file_name = os.path.basename(file_path)
|
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))
|
yield event.set_result(MessageEventResult(chain=chain))
|
||||||
|
|
||||||
elif "Traceback (most recent call last)" in log or "[Error]: " in log:
|
elif "Traceback (most recent call last)" in log or "[Error]: " in log:
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "AstrBot"
|
name = "AstrBot"
|
||||||
version = "4.0.0"
|
version = "4.0.0-beta.5"
|
||||||
description = "易上手的多平台 LLM 聊天机器人及开发框架"
|
description = "易上手的多平台 LLM 聊天机器人及开发框架"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
@@ -21,7 +21,7 @@ dependencies = [
|
|||||||
"deprecated>=1.2.18",
|
"deprecated>=1.2.18",
|
||||||
"dingtalk-stream>=0.22.1",
|
"dingtalk-stream>=0.22.1",
|
||||||
"docstring-parser>=0.16",
|
"docstring-parser>=0.16",
|
||||||
"faiss-cpu>=1.10.0",
|
"faiss-cpu==1.10.0",
|
||||||
"filelock>=3.18.0",
|
"filelock>=3.18.0",
|
||||||
"google-genai>=1.14.0",
|
"google-genai>=1.14.0",
|
||||||
"googlesearch-python>=1.3.0",
|
"googlesearch-python>=1.3.0",
|
||||||
|
|||||||
+4
-1
@@ -39,4 +39,7 @@ faiss-cpu
|
|||||||
aiosqlite
|
aiosqlite
|
||||||
py-cord>=2.6.1
|
py-cord>=2.6.1
|
||||||
slack-sdk
|
slack-sdk
|
||||||
pydub
|
pydub
|
||||||
|
sqlmodel
|
||||||
|
deprecated
|
||||||
|
sqlalchemy[asyncio]
|
||||||
Reference in New Issue
Block a user