Compare commits
1 Commits
stress_tes
...
features/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38486bc5aa |
31
.github/workflows/docker-image.yml
vendored
31
.github/workflows/docker-image.yml
vendored
@@ -27,33 +27,6 @@ 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
|
||||
|
||||
@@ -80,9 +53,9 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.check-prerelease.outputs.is_prerelease == 'false' && format('{0}/astrbot:latest', secrets.DOCKER_HUB_USERNAME) || '' }}
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/astrbot:latest
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/astrbot:${{ github.event_name == 'workflow_dispatch' && steps.get-latest-tag.outputs.latest_tag || github.ref_name }}
|
||||
${{ steps.check-prerelease.outputs.is_prerelease == 'false' && 'ghcr.io/soulter/astrbot:latest' || '' }}
|
||||
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
|
||||
|
||||
@@ -37,10 +37,7 @@ async def check_dashboard(astrbot_root: Path) -> None:
|
||||
):
|
||||
click.echo("正在安装管理面板...")
|
||||
await download_dashboard(
|
||||
path="data/dashboard.zip",
|
||||
extract_path=str(astrbot_root),
|
||||
version=f"v{VERSION}",
|
||||
latest=False,
|
||||
path="data/dashboard.zip", extract_path=str(astrbot_root)
|
||||
)
|
||||
click.echo("管理面板安装完成")
|
||||
|
||||
@@ -53,10 +50,7 @@ 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),
|
||||
version=f"v{VERSION}",
|
||||
latest=False,
|
||||
path="data/dashboard.zip", extract_path=str(astrbot_root)
|
||||
)
|
||||
except Exception as e:
|
||||
click.echo(f"下载管理面板失败: {e}")
|
||||
@@ -65,10 +59,7 @@ 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),
|
||||
version=f"v{VERSION}",
|
||||
latest=False,
|
||||
path=str(astrbot_root / "dashboard.zip"), extract_path=str(astrbot_root)
|
||||
)
|
||||
click.echo("管理面板初始化完成")
|
||||
except Exception as e:
|
||||
|
||||
@@ -36,21 +36,13 @@ 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._get_abconf_data()
|
||||
self.abconf_data = abconf_data
|
||||
abconf_data = self.sp.get(
|
||||
"abconf_mapping", {}, scope="global", scope_id="global"
|
||||
)
|
||||
for uuid_, meta in abconf_data.items():
|
||||
filename = meta["path"]
|
||||
conf_path = os.path.join(get_astrbot_config_path(), filename)
|
||||
@@ -80,7 +72,9 @@ class AstrBotConfigManager:
|
||||
ConfInfo: 包含配置文件的 uuid, 路径和名称等信息, 是一个 dict 类型
|
||||
"""
|
||||
# uuid -> { "umop": list, "path": str, "name": str }
|
||||
abconf_data = self._get_abconf_data()
|
||||
abconf_data = self.sp.get(
|
||||
"abconf_mapping", {}, scope="global", scope_id="global"
|
||||
)
|
||||
if isinstance(umo, MessageSession):
|
||||
umo = str(umo)
|
||||
else:
|
||||
@@ -121,7 +115,6 @@ 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 到默认配置文件。"""
|
||||
@@ -154,7 +147,9 @@ class AstrBotConfigManager:
|
||||
"""获取所有配置文件的元数据列表"""
|
||||
conf_list = []
|
||||
conf_list.append(DEFAULT_CONFIG_CONF_INFO)
|
||||
abconf_mapping = self._get_abconf_data()
|
||||
abconf_mapping = self.sp.get(
|
||||
"abconf_mapping", {}, scope="global", scope_id="global"
|
||||
)
|
||||
for uuid_, meta in abconf_mapping.items():
|
||||
conf_list.append(ConfInfo(**meta, id=uuid_))
|
||||
return conf_list
|
||||
@@ -223,7 +218,6 @@ 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
|
||||
@@ -269,7 +263,6 @@ 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
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import os
|
||||
|
||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
||||
|
||||
VERSION = "4.0.0-beta.5"
|
||||
VERSION = "4.0.0"
|
||||
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
||||
|
||||
# 默认配置
|
||||
@@ -51,6 +51,10 @@ DEFAULT_CONFIG = {
|
||||
"enable": True,
|
||||
"default_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.",
|
||||
"provider_pool": ["*"], # "*" 表示使用所有可用的提供者
|
||||
"wake_prefix": "",
|
||||
@@ -64,7 +68,6 @@ DEFAULT_CONFIG = {
|
||||
"default_personality": "default",
|
||||
"persona_pool": ["*"],
|
||||
"prompt_prefix": "",
|
||||
"max_context_length": -1,
|
||||
"dequeue_context_length": 1,
|
||||
"streaming_response": False,
|
||||
"show_tool_use_status": False,
|
||||
@@ -866,9 +869,6 @@ 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(本地加载)": {
|
||||
@@ -1835,6 +1835,12 @@ CONFIG_METADATA_3 = {
|
||||
"_special": "select_provider",
|
||||
"hint": "留空时使用第一个模型。",
|
||||
},
|
||||
"provider_settings.default_summarize_provider_id": {
|
||||
"description": "默认对话总结模型",
|
||||
"type": "string",
|
||||
"_special": "select_provider",
|
||||
"hint": "留空代表不进行对话总结。可用于压缩上下文以减少 token 用量,并一定程度上保持历史聊天记忆。",
|
||||
},
|
||||
"provider_settings.default_image_caption_provider_id": {
|
||||
"description": "默认图片转述模型",
|
||||
"type": "string",
|
||||
@@ -1853,6 +1859,28 @@ CONFIG_METADATA_3 = {
|
||||
"hint": "留空代表不使用。",
|
||||
"_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": {
|
||||
"description": "图片转述提示词",
|
||||
"type": "text",
|
||||
@@ -1916,7 +1944,7 @@ CONFIG_METADATA_3 = {
|
||||
"type": "bool",
|
||||
},
|
||||
"provider_settings.identifier": {
|
||||
"description": "用户识别",
|
||||
"description": "用户感知",
|
||||
"type": "bool",
|
||||
},
|
||||
"provider_settings.datetime_system_prompt": {
|
||||
@@ -1939,11 +1967,6 @@ CONFIG_METADATA_3 = {
|
||||
"description": "不支持流式回复的平台采取分段输出",
|
||||
"type": "bool",
|
||||
},
|
||||
"provider_settings.max_context_length": {
|
||||
"description": "最多携带对话轮数",
|
||||
"type": "int",
|
||||
"hint": "超出这个数量时丢弃最旧的部分,一轮聊天记为 1 条。-1 为不限制。",
|
||||
},
|
||||
"provider_settings.dequeue_context_length": {
|
||||
"description": "丢弃对话轮数",
|
||||
"type": "int",
|
||||
@@ -2291,7 +2314,7 @@ CONFIG_METADATA_3_SYSTEM = {
|
||||
"condition": {
|
||||
"t2i_strategy": "remote",
|
||||
},
|
||||
"_special": "t2i_template",
|
||||
"_special": "t2i_template"
|
||||
},
|
||||
"log_level": {
|
||||
"description": "控制台日志级别",
|
||||
@@ -2324,11 +2347,6 @@ CONFIG_METADATA_3_SYSTEM = {
|
||||
"type": "string",
|
||||
"hint": "启用后,会以添加环境变量的方式设置代理。格式为 `http://ip:port`",
|
||||
},
|
||||
"no_proxy": {
|
||||
"description": "直连地址列表",
|
||||
"type": "list",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -299,9 +299,7 @@ class LLMRequestSubStage(Stage):
|
||||
self.max_context_length - 1,
|
||||
)
|
||||
self.streaming_response: bool = settings["streaming_response"]
|
||||
self.max_step: int = settings.get("max_agent_step", 30)
|
||||
if isinstance(self.max_step, bool): # workaround: #2622
|
||||
self.max_step = 30
|
||||
self.max_step: int = settings.get("max_agent_step", 10)
|
||||
self.show_tool_use: bool = settings.get("show_tool_use_status", True)
|
||||
|
||||
for bwp in self.bot_wake_prefixs:
|
||||
@@ -436,9 +434,7 @@ 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:
|
||||
|
||||
@@ -67,19 +67,12 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
||||
session_id: str,
|
||||
messages: list[dict],
|
||||
):
|
||||
# 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)
|
||||
elif not is_group and isinstance(session_id, int):
|
||||
await bot.send_private_msg(user_id=session_id, message=messages)
|
||||
elif isinstance(event, Event): # 最后兜底
|
||||
if event:
|
||||
await bot.send(event=event, message=messages)
|
||||
elif is_group:
|
||||
await bot.send_group_msg(group_id=session_id, message=messages)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"无法发送消息:缺少有效的数字 session_id({session_id}) 或 event({event})"
|
||||
)
|
||||
await bot.send_private_msg(user_id=session_id, message=messages)
|
||||
|
||||
@classmethod
|
||||
async def send_message(
|
||||
@@ -90,15 +83,7 @@ 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(
|
||||
@@ -137,15 +122,18 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
||||
|
||||
async def send(self, message: MessageChain):
|
||||
"""发送消息"""
|
||||
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()
|
||||
|
||||
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()
|
||||
await self.send_message(
|
||||
bot=self.bot,
|
||||
message_chain=message,
|
||||
event=event, # 不强制要求一定是 Event
|
||||
event=event,
|
||||
is_group=is_group,
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
@@ -4,11 +4,9 @@ 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, Any
|
||||
from typing import List, Dict, Type
|
||||
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,
|
||||
)
|
||||
@@ -32,11 +30,11 @@ class ProviderMetaData:
|
||||
desc: str = ""
|
||||
"""提供商适配器描述."""
|
||||
provider_type: ProviderType = ProviderType.CHAT_COMPLETION
|
||||
cls_type: Type | None = None
|
||||
cls_type: Type = None
|
||||
|
||||
default_config_tmpl: dict | None = None
|
||||
default_config_tmpl: dict = None
|
||||
"""平台的默认配置模板"""
|
||||
provider_display_name: str | None = None
|
||||
provider_display_name: str = None
|
||||
"""显示在 WebUI 配置页中的提供商名称,如空则是 type"""
|
||||
|
||||
|
||||
@@ -60,7 +58,7 @@ class ToolCallMessageSegment:
|
||||
class AssistantMessageSegment:
|
||||
"""OpenAI 格式的上下文中 role 为 assistant 的消息段。参考: https://platform.openai.com/docs/guides/function-calling"""
|
||||
|
||||
content: str | None = None
|
||||
content: str = None
|
||||
tool_calls: List[ChatCompletionMessageToolCall | Dict] = field(default_factory=list)
|
||||
role: str = "assistant"
|
||||
|
||||
@@ -207,17 +205,17 @@ class ProviderRequest:
|
||||
class LLMResponse:
|
||||
role: str
|
||||
"""角色, assistant, tool, err"""
|
||||
result_chain: MessageChain | None = None
|
||||
result_chain: MessageChain = 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 | GenerateContentResponse | Message | None = None
|
||||
_new_record: Dict[str, Any] | None = None
|
||||
raw_completion: ChatCompletion = None
|
||||
_new_record: Dict[str, any] = None
|
||||
|
||||
_completion_text: str = ""
|
||||
|
||||
@@ -228,12 +226,12 @@ class LLMResponse:
|
||||
self,
|
||||
role: str,
|
||||
completion_text: str = "",
|
||||
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,
|
||||
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,
|
||||
is_chunk: bool = False,
|
||||
):
|
||||
"""初始化 LLMResponse
|
||||
|
||||
@@ -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 ToolSet
|
||||
from astrbot.core.provider.func_tool_manager import FuncCall
|
||||
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 ""
|
||||
self.chosen_api_key: str = self.api_keys[0] if len(self.api_keys) > 0 else None
|
||||
self.timeout: int = int(provider_config.get("timeout", 180))
|
||||
|
||||
self.api_base: Optional[str] = provider_config.get("api_base", None)
|
||||
@@ -96,9 +96,6 @@ 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:
|
||||
@@ -122,7 +119,7 @@ class ProviderGoogleGenAI(Provider):
|
||||
async def _prepare_query_config(
|
||||
self,
|
||||
payloads: dict,
|
||||
tools: Optional[ToolSet] = None,
|
||||
tools: Optional[FuncCall] = None,
|
||||
system_instruction: Optional[str] = None,
|
||||
modalities: Optional[list[str]] = None,
|
||||
temperature: float = 0.7,
|
||||
@@ -324,15 +321,11 @@ class ProviderGoogleGenAI(Provider):
|
||||
|
||||
@staticmethod
|
||||
def _process_content_parts(
|
||||
candidate: types.Candidate, llm_response: LLMResponse
|
||||
result: types.GenerateContentResponse, llm_response: LLMResponse
|
||||
) -> MessageChain:
|
||||
"""处理内容部分并构建消息链"""
|
||||
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
|
||||
finish_reason = result.candidates[0].finish_reason
|
||||
result_parts: Optional[types.Part] = result.candidates[0].content.parts
|
||||
|
||||
if finish_reason == types.FinishReason.SAFETY:
|
||||
raise Exception("模型生成内容未通过 Gemini 平台的安全检查")
|
||||
@@ -350,28 +343,22 @@ class ProviderGoogleGenAI(Provider):
|
||||
raise Exception("模型生成内容违反 Gemini 平台政策")
|
||||
|
||||
if not result_parts:
|
||||
logger.warning(f"收到的 candidate.content.parts 为空: {candidate}")
|
||||
raise Exception("API 返回的 candidate.content.parts 为空。")
|
||||
logger.debug(result.candidates)
|
||||
raise Exception("API 返回的内容为空。")
|
||||
|
||||
chain = []
|
||||
part: types.Part
|
||||
|
||||
# 暂时这样Fallback
|
||||
if all(
|
||||
part.inline_data
|
||||
and part.inline_data.mime_type
|
||||
and part.inline_data.mime_type.startswith("image/")
|
||||
part.inline_data 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
|
||||
and part.function_call.name is not None
|
||||
and part.function_call.args is not None
|
||||
):
|
||||
elif part.function_call:
|
||||
llm_response.role = "tool"
|
||||
llm_response.tools_call_name.append(part.function_call.name)
|
||||
llm_response.tools_call_args.append(part.function_call.args)
|
||||
@@ -379,16 +366,11 @@ 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
|
||||
and part.inline_data.mime_type.startswith("image/")
|
||||
and part.inline_data.data
|
||||
):
|
||||
elif part.inline_data and part.inline_data.mime_type.startswith("image/"):
|
||||
chain.append(Comp.Image.fromBytes(part.inline_data.data))
|
||||
return MessageChain(chain=chain)
|
||||
|
||||
async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
|
||||
async def _query(self, payloads: dict, tools: FuncCall) -> LLMResponse:
|
||||
"""非流式请求 Gemini API"""
|
||||
system_instruction = next(
|
||||
(msg["content"] for msg in payloads["messages"] if msg["role"] == "system"),
|
||||
@@ -414,10 +396,6 @@ 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")
|
||||
@@ -430,8 +408,6 @@ 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,已自动去除(影响人格设置)"
|
||||
@@ -456,13 +432,11 @@ class ProviderGoogleGenAI(Provider):
|
||||
|
||||
llm_response = LLMResponse("assistant")
|
||||
llm_response.raw_completion = result
|
||||
llm_response.result_chain = self._process_content_parts(
|
||||
result.candidates[0], llm_response
|
||||
)
|
||||
llm_response.result_chain = self._process_content_parts(result, llm_response)
|
||||
return llm_response
|
||||
|
||||
async def _query_stream(
|
||||
self, payloads: dict, tools: ToolSet | None
|
||||
self, payloads: dict, tools: FuncCall
|
||||
) -> AsyncGenerator[LLMResponse, None]:
|
||||
"""流式请求 Gemini API"""
|
||||
system_instruction = next(
|
||||
@@ -485,8 +459,6 @@ 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,已自动去除(影响人格设置)"
|
||||
@@ -506,20 +478,13 @@ 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.candidates[0], llm_response
|
||||
chunk, llm_response
|
||||
)
|
||||
yield llm_response
|
||||
return
|
||||
@@ -535,7 +500,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.candidates[0], final_response
|
||||
chunk, final_response
|
||||
)
|
||||
break
|
||||
|
||||
@@ -601,8 +566,6 @@ class ProviderGoogleGenAI(Provider):
|
||||
continue
|
||||
break
|
||||
|
||||
raise Exception("请求失败。")
|
||||
|
||||
async def text_chat_stream(
|
||||
self,
|
||||
prompt,
|
||||
@@ -658,9 +621,7 @@ class ProviderGoogleGenAI(Provider):
|
||||
return [
|
||||
m.name.replace("models/", "")
|
||||
for m in models
|
||||
if m.supported_actions
|
||||
and "generateContent" in m.supported_actions
|
||||
and m.name
|
||||
if "generateContent" in m.supported_actions
|
||||
]
|
||||
except APIError as e:
|
||||
raise Exception(f"获取模型列表失败: {e.message}")
|
||||
@@ -675,7 +636,7 @@ class ProviderGoogleGenAI(Provider):
|
||||
self.chosen_api_key = key
|
||||
self._init_client()
|
||||
|
||||
async def assemble_context(self, text: str, image_urls: list[str] | None = None):
|
||||
async def assemble_context(self, text: str, image_urls: list[str] = None):
|
||||
"""
|
||||
组装上下文。
|
||||
"""
|
||||
|
||||
@@ -100,9 +100,9 @@ class ProviderOpenAIOfficial(Provider):
|
||||
del payloads[key]
|
||||
|
||||
model = payloads.get("model", "")
|
||||
# 针对 qwen3 非 thinking 模型的特殊处理:非流式调用必须设置 enable_thinking=false
|
||||
if "qwen3" in model.lower() and "thinking" not in model.lower():
|
||||
extra_body["enable_thinking"] = False
|
||||
# 针对 qwen3 模型的特殊处理:非流式调用必须设置 enable_thinking=false
|
||||
if "qwen3" 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"]
|
||||
|
||||
@@ -56,7 +56,9 @@ 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)
|
||||
@@ -66,13 +68,9 @@ class AstrBotUpdator(RepoZipUpdator):
|
||||
logger.error(f"重启失败({py}, {e}),请尝试手动重启。")
|
||||
raise e
|
||||
|
||||
async def check_update(
|
||||
self, url: str, current_version: str, consider_prerelease: bool = True
|
||||
) -> ReleaseInfo:
|
||||
async def check_update(self, url: str, current_version: str) -> ReleaseInfo:
|
||||
"""检查更新"""
|
||||
return await super().check_update(
|
||||
self.ASTRBOT_RELEASE_API, VERSION, consider_prerelease
|
||||
)
|
||||
return await super().check_update(self.ASTRBOT_RELEASE_API, VERSION)
|
||||
|
||||
async def get_releases(self) -> list:
|
||||
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")
|
||||
|
||||
if latest or len(str(version)) != 40:
|
||||
logger.info(f"准备下载 {version} 发行版本的 AstrBot WebUI 文件")
|
||||
logger.info("准备下载最新发行版本的 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:
|
||||
|
||||
@@ -107,38 +107,16 @@ class RepoZipUpdator:
|
||||
"""Semver 版本比较"""
|
||||
return VersionComparator.compare_version(v1, v2)
|
||||
|
||||
async def check_update(
|
||||
self, url: str, current_version: str, consider_prerelease: bool = True
|
||||
) -> ReleaseInfo | None:
|
||||
async def check_update(self, url: str, current_version: str) -> ReleaseInfo | None:
|
||||
update_data = await self.fetch_release_info(url)
|
||||
|
||||
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
|
||||
tag_name = update_data[0]["tag_name"]
|
||||
|
||||
if self.compare_version(current_version, tag_name) >= 0:
|
||||
return None
|
||||
return ReleaseInfo(
|
||||
version=tag_name,
|
||||
published_at=sel_release_data["published_at"],
|
||||
body=f"{tag_name}\n\n{sel_release_data['body']}",
|
||||
published_at=update_data[0]["published_at"],
|
||||
body=update_data[0]["body"],
|
||||
)
|
||||
|
||||
async def download_from_repo_url(self, target_path: str, repo_url: str, proxy=""):
|
||||
|
||||
@@ -18,7 +18,6 @@ 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
|
||||
|
||||
@@ -482,19 +481,6 @@ 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}"
|
||||
|
||||
@@ -57,7 +57,7 @@ class UpdateRoute(Route):
|
||||
.__dict__
|
||||
)
|
||||
else:
|
||||
ret = await self.astrbot_updator.check_update(None, None, False)
|
||||
ret = await self.astrbot_updator.check_update(None, None)
|
||||
return Response(
|
||||
status="success",
|
||||
message=str(ret) if ret is not None else "已经是最新版本了。",
|
||||
@@ -100,7 +100,9 @@ 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}。")
|
||||
|
||||
@@ -131,7 +133,7 @@ class UpdateRoute(Route):
|
||||
async def update_dashboard(self):
|
||||
try:
|
||||
try:
|
||||
await download_dashboard(version=f"v{VERSION}", latest=False)
|
||||
await download_dashboard()
|
||||
except Exception as e:
|
||||
logger.error(f"下载管理面板文件失败: {e}。")
|
||||
return Response().error(f"下载管理面板文件失败: {e}").__dict__
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# What's Changed
|
||||
|
||||
1. 修复:构建 docker 镜像时同时构建 webui,并放入镜像中。
|
||||
2. 修复:下载 WebUI 文件时,明确版本号,以防止 latest 不一致导致下载的 WebUI 文件版本号与实际所需不符的问题。
|
||||
3. 优化:优化版本检测,考虑预发布版本,移除 `更新到最新版本` 按钮
|
||||
@@ -1,3 +0,0 @@
|
||||
# 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 稳定版本发布。
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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. 数据迁移完毕之后引导重启程序
|
||||
@@ -1,15 +0,0 @@
|
||||
# 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,13 +12,7 @@
|
||||
<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>
|
||||
<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>
|
||||
{{ migrationResult?.message || t('features.migration.dialog.success') }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="migrating" class="migration-in-progress">
|
||||
@@ -86,11 +80,8 @@
|
||||
<v-card-actions class="px-6 py-4">
|
||||
<v-spacer></v-spacer>
|
||||
<template v-if="migrationCompleted">
|
||||
<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 color="primary" variant="elevated" @click="handleClose">
|
||||
{{ t('core.common.confirm') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -105,8 +96,6 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<WaitingForRestart ref="wfr"></WaitingForRestart>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -114,7 +103,6 @@ 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()
|
||||
|
||||
@@ -126,7 +114,6 @@ const migrationCompleted = ref(false)
|
||||
const migrationResult = ref(null)
|
||||
const platforms = ref([])
|
||||
const selectedPlatforms = ref({})
|
||||
const wfr = ref(null)
|
||||
|
||||
let resolvePromise = null
|
||||
|
||||
@@ -257,15 +244,6 @@ 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
|
||||
|
||||
@@ -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": "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",
|
||||
"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",
|
||||
"dockerTipLink": "watchtower",
|
||||
"dockerTipContinue": "to automatically monitor and pull.",
|
||||
"table": {
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
"migratingSubtitle": "Please wait patiently, do not close this window during migration",
|
||||
"migrationError": "Migration failed",
|
||||
"success": "Migration completed successfully!",
|
||||
"completed": "Migration Completed",
|
||||
"restartRecommended": "It is recommended to restart the application for all changes to take effect.",
|
||||
"restartNow": "Restart Now"
|
||||
"completed": "Migration Completed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"tip": "💡 TIP: ",
|
||||
"tipContinue": "默认在切换版本时会下载对应版本的 WebUI 文件。WebUI 代码位于项目的 dashboard 目录,您可使用 npm 自行构建。",
|
||||
"dockerTip": "切换版本时,会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用",
|
||||
"dockerTip": "`更新到最新版本` 按钮会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用",
|
||||
"dockerTipLink": "watchtower",
|
||||
"dockerTipContinue": "来自动监控拉取。",
|
||||
"table": {
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
"migratingSubtitle": "请耐心等待,迁移过程中请勿关闭此窗口",
|
||||
"migrationError": "迁移失败",
|
||||
"success": "迁移成功完成!",
|
||||
"completed": "迁移已完成",
|
||||
"restartRecommended": "建议重启应用程序以使所有更改生效。",
|
||||
"restartNow": "立即重启"
|
||||
"completed": "迁移已完成"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +378,10 @@ 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>
|
||||
|
||||
@@ -481,7 +481,7 @@ export default {
|
||||
if (!this.fetched) return;
|
||||
|
||||
const postData = {
|
||||
config: JSON.parse(JSON.stringify(this.config_data)),
|
||||
config: this.config_data
|
||||
};
|
||||
|
||||
if (this.isSystemConfig) {
|
||||
|
||||
@@ -383,7 +383,8 @@ export default {
|
||||
messageType: 'success',
|
||||
personaIdRules: [
|
||||
v => !!v || this.tm('validation.required'),
|
||||
v => (v && v.length >= 0) || this.tm('validation.minLength', { min: 2 }),
|
||||
v => (v && v.length >= 2) || this.tm('validation.minLength', { min: 2 }),
|
||||
v => /^[a-zA-Z0-9_-]+$/.test(v) || this.tm('validation.alphanumeric')
|
||||
],
|
||||
systemPromptRules: [
|
||||
v => !!v || this.tm('validation.required'),
|
||||
|
||||
6
main.py
6
main.py
@@ -44,10 +44,10 @@ async def check_dashboard_files():
|
||||
if v is not None:
|
||||
# has file
|
||||
if v == f"v{VERSION}":
|
||||
logger.info("WebUI 版本已是最新。")
|
||||
logger.info("管理面板文件已是最新。")
|
||||
else:
|
||||
logger.warning(
|
||||
f"检测到 WebUI 版本 ({v}) 与当前 AstrBot 版本 (v{VERSION}) 不符。"
|
||||
"检测到管理面板有更新。可以使用 /dashboard_update 命令更新。"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -56,7 +56,7 @@ async def check_dashboard_files():
|
||||
)
|
||||
|
||||
try:
|
||||
await download_dashboard(version=f"v{VERSION}", latest=False)
|
||||
await download_dashboard()
|
||||
except Exception as e:
|
||||
logger.critical(f"下载管理面板文件失败: {e}。")
|
||||
return
|
||||
|
||||
@@ -25,18 +25,14 @@ class LongTermMemory:
|
||||
def cfg(self, event: AstrMessageEvent):
|
||||
cfg = self.context.get_config(umo=event.unified_msg_origin)
|
||||
try:
|
||||
max_cnt = int(cfg["provider_ltm_settings"]["group_message_max_cnt"])
|
||||
max_cnt = int(cfg["group_message_max_cnt"])
|
||||
except BaseException as e:
|
||||
logger.error(e)
|
||||
max_cnt = 300
|
||||
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"]
|
||||
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"]
|
||||
enable_active_reply = active_reply.get("enable", False)
|
||||
ar_method = active_reply["method"]
|
||||
ar_possibility = active_reply["possibility_reply"]
|
||||
@@ -92,9 +88,7 @@ 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
|
||||
|
||||
@@ -118,6 +112,7 @@ 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(
|
||||
|
||||
@@ -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(version=f"v{VERSION}", latest=False)
|
||||
await download_dashboard()
|
||||
yield event.plain_result("管理面板更新完成。")
|
||||
|
||||
@filter.command("set")
|
||||
@@ -1110,9 +1110,7 @@ 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 变量名。")
|
||||
@@ -1178,7 +1176,9 @@ UID: {user_id} 此 ID 可用于设置管理员。
|
||||
)
|
||||
return
|
||||
|
||||
prompt = event.message_str
|
||||
prompt = self.ltm.ar_prompt
|
||||
if not prompt:
|
||||
prompt = event.message_str
|
||||
|
||||
yield event.request_llm(
|
||||
prompt=prompt,
|
||||
|
||||
@@ -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_path)]
|
||||
chain = [File(name=file_name, file=file_s3_url)]
|
||||
yield event.set_result(MessageEventResult(chain=chain))
|
||||
|
||||
elif "Traceback (most recent call last)" in log or "[Error]: " in log:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "AstrBot"
|
||||
version = "4.0.0-beta.5"
|
||||
version = "4.0.0"
|
||||
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",
|
||||
|
||||
@@ -39,7 +39,4 @@ faiss-cpu
|
||||
aiosqlite
|
||||
py-cord>=2.6.1
|
||||
slack-sdk
|
||||
pydub
|
||||
sqlmodel
|
||||
deprecated
|
||||
sqlalchemy[asyncio]
|
||||
pydub
|
||||
Reference in New Issue
Block a user