f624971613
* chore(core.utils): 🚨 修正错误Lint
* chore(core.provider): 🚨 修复基类错误Lint
* chore(core.utils): 补全session_get()的重载
* chore(core.provider): 🚨 修正实现错误Lint
* chore(core.platform): 🚨 修正platform基类和webchat的错误Lint
* chore(core.platform): 修正错误实现Lint
* fix(core.provider): 修复循环调用和错误assert
* chore(core.platform): 修复部分实现Lint
* chore(core.provider): 补充Dify.text_chat_stream的参数类型
* chore(core.pipeline): 🚨 修复错误Lint
* fix(core.slack): 补充遗漏导入
* chore(core.utils): 修复错误的session_get声明
* chore(core.platform): 移除Lark adapter import中的wildcard
* chore(core.db): 修复声明和部分逻辑
* chore(core.db): 添加typings,使faiss参数能被正确识别。
* chore(core): 修复声明
* chore(core): 修改声明
* chore: 补充faiss声明
* chore(dashboard): 修改实现,减少报错
* chore(package): 修改部分声明与实现,减少报错
* chore(core): 添加Handler的overload,以去除部分assert同时通过类型检查
* chore(core.pipeline): 修改Pipeline Scheduler的execute,将判断属性改为判断类型,通过静态类型检查
* chore(core.config): 添加类型标注,通过类型检查
* chore(core.message): 为File._download_file添加检查,通过类型检查
* fix: 将断言改为条件判断以实现优雅关闭的容错性
* refactor: 移除 discord 客户端中的 assert,改用 if None 判断并抛出异常
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: DiscordPlatformAdapter 对 self.client.user 为 None 做日志并返回,移除断言
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: 增强 Lark 相关空值/异常检查并完善日志输出
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 将断言替换为条件检查并加入日志与错误处理
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* chore: 移除LLM生成的无用注释
* refactor: 使用 File.get_file 替换下载逻辑并移除 assert,提供默认 filename
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: Slack Socket 未初始化抛出运行时异常,图片 URL 判空改为非空判断
* refactor: 将 WeChatPadProAdapter 的断言改为空值判断并添加日志
* refactor: 使用 isinstance 替代断言实现类型判断,便于静态检查
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: 去除cast,直接使用字段与字典访问,修正端口解析
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 使用 match-case 重构 ProviderManager 加载并通过类型检查抛出 TypeError
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: group_name_display 时若 group 对象为空则记录错误并返回
* fix: 将 _get_current_persona_id 的 assert 替换成 if guard 并返回 None
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: 优化插件目录存在性检查及图片URL非空验证,更新JSON排序配置
* fix: 将 datetime_str 的 assert 替换为显式检查并抛出异常
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 移除 cast,改为运行时检查并在找不到调度器时跳过
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 移除 cast,改用 isinstance 检查 FaissVecDB 并警告
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: 删除 typing.cast 导入,并在获取文件绝对路径前校验 file_
* refactor: 移除 typing.cast,简化内容安全检查调用
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 将 PlatformMetadata.id 设为必填并在注册时传入 id,移除 cast
* refactor: 移除 cast,改用 HasInitialize 与 isinstance 进行初始化
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: 为 ProviderManager.initialize 增加ID类型判断,避免 None 导致 get 失败
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 为 OTTSProvider 与 AzureNativeProvider 引入 _client 与 client 属性改进上下文管理
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: 为 Whisper 自托管源添加模型未初始化校验并直接调用 transcribe
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 移除未使用的 cast 导入并简化 platform_name 赋值
* refactor: 引入 cast 并对 id 使用 cast(str, ...) 提升类型安全
* fix: 将 _id_to_sid 返回改为 str,空值返回空串;对 id 与 message_id 使用 cast
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 重构 Discord 处理逻辑:强制 类型转换、优先斜杠指令并优化提及判断
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* fix: 统一对 id 获取执行 cast,并在微信消息解析失败时抛错
* Revert "fix: 去除cast,直接使用字段与字典访问,修正端口解析"
This reverts commit 1cbfdf9d1b.
* fix: 百炼 Rerank 会话关闭时返回空结果;初始化 request.prompt 避免空值拼接
* fix: 统一处理搜索结果链接为字符串,新增 _get_url 助手并适配 Bing/Sogo
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
* refactor: 调整 call_handler 泛型、Discord 通道注解及 FishAudioTTS API 请求类型
* refactor: 使用 col(...) 替代列引用并对结果进行 CursorResult 强转
* chore: ruff format
---------
Co-authored-by: aider (openai/gemini-3-pro-high) <aider@aider.chat>
Co-authored-by: Soulter <905617992@qq.com>
337 lines
12 KiB
Python
337 lines
12 KiB
Python
"""Astrbot 核心生命周期管理类, 负责管理 AstrBot 的启动、停止、重启等操作.
|
|
|
|
该类负责初始化各个组件, 包括 ProviderManager、PlatformManager、ConversationManager、PluginManager、PipelineScheduler、EventBus等。
|
|
该类还负责加载和执行插件, 以及处理事件总线的分发。
|
|
|
|
工作流程:
|
|
1. 初始化所有组件
|
|
2. 启动事件总线和任务, 所有任务都在这里运行
|
|
3. 执行启动完成事件钩子
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import threading
|
|
import time
|
|
import traceback
|
|
from asyncio import Queue
|
|
|
|
from astrbot.api import logger, sp
|
|
from astrbot.core import LogBroker
|
|
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
|
from astrbot.core.config.default import VERSION
|
|
from astrbot.core.conversation_mgr import ConversationManager
|
|
from astrbot.core.db import BaseDatabase
|
|
from astrbot.core.knowledge_base.kb_mgr import KnowledgeBaseManager
|
|
from astrbot.core.persona_mgr import PersonaManager
|
|
from astrbot.core.pipeline.scheduler import PipelineContext, PipelineScheduler
|
|
from astrbot.core.platform.manager import PlatformManager
|
|
from astrbot.core.platform_message_history_mgr import PlatformMessageHistoryManager
|
|
from astrbot.core.provider.manager import ProviderManager
|
|
from astrbot.core.star import PluginManager
|
|
from astrbot.core.star.context import Context
|
|
from astrbot.core.star.star_handler import EventType, star_handlers_registry, star_map
|
|
from astrbot.core.umop_config_router import UmopConfigRouter
|
|
from astrbot.core.updator import AstrBotUpdator
|
|
from astrbot.core.utils.migra_helper import migra
|
|
|
|
from . import astrbot_config, html_renderer
|
|
from .event_bus import EventBus
|
|
|
|
|
|
class AstrBotCoreLifecycle:
|
|
"""AstrBot 核心生命周期管理类, 负责管理 AstrBot 的启动、停止、重启等操作.
|
|
|
|
该类负责初始化各个组件, 包括 ProviderManager、PlatformManager、ConversationManager、PluginManager、PipelineScheduler、
|
|
EventBus 等。
|
|
该类还负责加载和执行插件, 以及处理事件总线的分发。
|
|
"""
|
|
|
|
def __init__(self, log_broker: LogBroker, db: BaseDatabase) -> None:
|
|
self.log_broker = log_broker # 初始化日志代理
|
|
self.astrbot_config = astrbot_config # 初始化配置
|
|
self.db = db # 初始化数据库
|
|
|
|
# 设置代理
|
|
proxy_config = self.astrbot_config.get("http_proxy", "")
|
|
if proxy_config != "":
|
|
os.environ["https_proxy"] = proxy_config
|
|
os.environ["http_proxy"] = proxy_config
|
|
logger.debug(f"Using proxy: {proxy_config}")
|
|
# 设置 no_proxy
|
|
no_proxy_list = self.astrbot_config.get("no_proxy", [])
|
|
os.environ["no_proxy"] = ",".join(no_proxy_list)
|
|
else:
|
|
# 清空代理环境变量
|
|
if "https_proxy" in os.environ:
|
|
del os.environ["https_proxy"]
|
|
if "http_proxy" in os.environ:
|
|
del os.environ["http_proxy"]
|
|
if "no_proxy" in os.environ:
|
|
del os.environ["no_proxy"]
|
|
logger.debug("HTTP proxy cleared")
|
|
|
|
async def initialize(self) -> None:
|
|
"""初始化 AstrBot 核心生命周期管理类.
|
|
|
|
负责初始化各个组件, 包括 ProviderManager、PlatformManager、ConversationManager、PluginManager、PipelineScheduler、EventBus、AstrBotUpdator等。
|
|
"""
|
|
# 初始化日志代理
|
|
logger.info("AstrBot v" + VERSION)
|
|
if os.environ.get("TESTING", ""):
|
|
logger.setLevel("DEBUG") # 测试模式下设置日志级别为 DEBUG
|
|
else:
|
|
logger.setLevel(self.astrbot_config["log_level"]) # 设置日志级别
|
|
|
|
await self.db.initialize()
|
|
|
|
await html_renderer.initialize()
|
|
|
|
# 初始化 UMOP 配置路由器
|
|
self.umop_config_router = UmopConfigRouter(sp=sp)
|
|
|
|
# 初始化 AstrBot 配置管理器
|
|
self.astrbot_config_mgr = AstrBotConfigManager(
|
|
default_config=self.astrbot_config,
|
|
ucr=self.umop_config_router,
|
|
sp=sp,
|
|
)
|
|
|
|
# apply migration
|
|
try:
|
|
await migra(
|
|
self.db,
|
|
self.astrbot_config_mgr,
|
|
self.umop_config_router,
|
|
self.astrbot_config_mgr,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"AstrBot migration failed: {e!s}")
|
|
logger.error(traceback.format_exc())
|
|
|
|
# 初始化事件队列
|
|
self.event_queue = Queue()
|
|
|
|
# 初始化人格管理器
|
|
self.persona_mgr = PersonaManager(self.db, self.astrbot_config_mgr)
|
|
await self.persona_mgr.initialize()
|
|
|
|
# 初始化供应商管理器
|
|
self.provider_manager = ProviderManager(
|
|
self.astrbot_config_mgr,
|
|
self.db,
|
|
self.persona_mgr,
|
|
)
|
|
|
|
# 初始化平台管理器
|
|
self.platform_manager = PlatformManager(self.astrbot_config, self.event_queue)
|
|
|
|
# 初始化对话管理器
|
|
self.conversation_manager = ConversationManager(self.db)
|
|
|
|
# 初始化平台消息历史管理器
|
|
self.platform_message_history_manager = PlatformMessageHistoryManager(self.db)
|
|
|
|
# 初始化知识库管理器
|
|
self.kb_manager = KnowledgeBaseManager(self.provider_manager)
|
|
|
|
# 初始化提供给插件的上下文
|
|
self.star_context = Context(
|
|
self.event_queue,
|
|
self.astrbot_config,
|
|
self.db,
|
|
self.provider_manager,
|
|
self.platform_manager,
|
|
self.conversation_manager,
|
|
self.platform_message_history_manager,
|
|
self.persona_mgr,
|
|
self.astrbot_config_mgr,
|
|
self.kb_manager,
|
|
)
|
|
|
|
# 初始化插件管理器
|
|
self.plugin_manager = PluginManager(self.star_context, self.astrbot_config)
|
|
|
|
# 扫描、注册插件、实例化插件类
|
|
await self.plugin_manager.reload()
|
|
|
|
# 根据配置实例化各个 Provider
|
|
await self.provider_manager.initialize()
|
|
|
|
await self.kb_manager.initialize()
|
|
|
|
# 初始化消息事件流水线调度器
|
|
self.pipeline_scheduler_mapping = await self.load_pipeline_scheduler()
|
|
|
|
# 初始化更新器
|
|
self.astrbot_updator = AstrBotUpdator()
|
|
|
|
# 初始化事件总线
|
|
self.event_bus = EventBus(
|
|
self.event_queue,
|
|
self.pipeline_scheduler_mapping,
|
|
self.astrbot_config_mgr,
|
|
)
|
|
|
|
# 记录启动时间
|
|
self.start_time = int(time.time())
|
|
|
|
# 初始化当前任务列表
|
|
self.curr_tasks: list[asyncio.Task] = []
|
|
|
|
# 根据配置实例化各个平台适配器
|
|
await self.platform_manager.initialize()
|
|
|
|
# 初始化关闭控制面板的事件
|
|
self.dashboard_shutdown_event = asyncio.Event()
|
|
|
|
def _load(self) -> None:
|
|
"""加载事件总线和任务并初始化."""
|
|
# 创建一个异步任务来执行事件总线的 dispatch() 方法
|
|
# dispatch是一个无限循环的协程, 从事件队列中获取事件并处理
|
|
event_bus_task = asyncio.create_task(
|
|
self.event_bus.dispatch(),
|
|
name="event_bus",
|
|
)
|
|
|
|
# 把插件中注册的所有协程函数注册到事件总线中并执行
|
|
extra_tasks = []
|
|
for task in self.star_context._register_tasks:
|
|
extra_tasks.append(asyncio.create_task(task, name=task.__name__)) # type: ignore
|
|
|
|
tasks_ = [event_bus_task, *extra_tasks]
|
|
for task in tasks_:
|
|
self.curr_tasks.append(
|
|
asyncio.create_task(self._task_wrapper(task), name=task.get_name()),
|
|
)
|
|
|
|
self.start_time = int(time.time())
|
|
|
|
async def _task_wrapper(self, task: asyncio.Task) -> None:
|
|
"""异步任务包装器, 用于处理异步任务执行中出现的各种异常.
|
|
|
|
Args:
|
|
task (asyncio.Task): 要执行的异步任务
|
|
|
|
"""
|
|
try:
|
|
await task
|
|
except asyncio.CancelledError:
|
|
pass # 任务被取消, 静默处理
|
|
except Exception as e:
|
|
# 获取完整的异常堆栈信息, 按行分割并记录到日志中
|
|
logger.error(f"------- 任务 {task.get_name()} 发生错误: {e}")
|
|
for line in traceback.format_exc().split("\n"):
|
|
logger.error(f"| {line}")
|
|
logger.error("-------")
|
|
|
|
async def start(self) -> None:
|
|
"""启动 AstrBot 核心生命周期管理类.
|
|
|
|
用load加载事件总线和任务并初始化, 执行启动完成事件钩子
|
|
"""
|
|
self._load()
|
|
logger.info("AstrBot 启动完成。")
|
|
|
|
# 执行启动完成事件钩子
|
|
handlers = star_handlers_registry.get_handlers_by_event_type(
|
|
EventType.OnAstrBotLoadedEvent,
|
|
)
|
|
for handler in handlers:
|
|
try:
|
|
logger.info(
|
|
f"hook(on_astrbot_loaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
|
|
)
|
|
await handler.handler()
|
|
except BaseException:
|
|
logger.error(traceback.format_exc())
|
|
|
|
# 同时运行curr_tasks中的所有任务
|
|
await asyncio.gather(*self.curr_tasks, return_exceptions=True)
|
|
|
|
async def stop(self) -> None:
|
|
"""停止 AstrBot 核心生命周期管理类, 取消所有当前任务并终止各个管理器."""
|
|
# 请求停止所有正在运行的异步任务
|
|
for task in self.curr_tasks:
|
|
task.cancel()
|
|
|
|
for plugin in self.plugin_manager.context.get_all_stars():
|
|
try:
|
|
await self.plugin_manager._terminate_plugin(plugin)
|
|
except Exception as e:
|
|
logger.warning(traceback.format_exc())
|
|
logger.warning(
|
|
f"插件 {plugin.name} 未被正常终止 {e!s}, 可能会导致资源泄露等问题。",
|
|
)
|
|
|
|
await self.provider_manager.terminate()
|
|
await self.platform_manager.terminate()
|
|
await self.kb_manager.terminate()
|
|
self.dashboard_shutdown_event.set()
|
|
|
|
# 再次遍历curr_tasks等待每个任务真正结束
|
|
for task in self.curr_tasks:
|
|
try:
|
|
await task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
except Exception as e:
|
|
logger.error(f"任务 {task.get_name()} 发生错误: {e}")
|
|
|
|
async def restart(self) -> None:
|
|
"""重启 AstrBot 核心生命周期管理类, 终止各个管理器并重新加载平台实例"""
|
|
await self.provider_manager.terminate()
|
|
await self.platform_manager.terminate()
|
|
await self.kb_manager.terminate()
|
|
self.dashboard_shutdown_event.set()
|
|
threading.Thread(
|
|
target=self.astrbot_updator._reboot,
|
|
name="restart",
|
|
daemon=True,
|
|
).start()
|
|
|
|
def load_platform(self) -> list[asyncio.Task]:
|
|
"""加载平台实例并返回所有平台实例的异步任务列表"""
|
|
tasks = []
|
|
platform_insts = self.platform_manager.get_insts()
|
|
for platform_inst in platform_insts:
|
|
tasks.append(
|
|
asyncio.create_task(
|
|
platform_inst.run(),
|
|
name=f"{platform_inst.meta().id}({platform_inst.meta().name})",
|
|
),
|
|
)
|
|
return tasks
|
|
|
|
async def load_pipeline_scheduler(self) -> dict[str, PipelineScheduler]:
|
|
"""加载消息事件流水线调度器.
|
|
|
|
Returns:
|
|
dict[str, PipelineScheduler]: 平台 ID 到流水线调度器的映射
|
|
|
|
"""
|
|
mapping = {}
|
|
for conf_id, ab_config in self.astrbot_config_mgr.confs.items():
|
|
scheduler = PipelineScheduler(
|
|
PipelineContext(ab_config, self.plugin_manager, conf_id),
|
|
)
|
|
await scheduler.initialize()
|
|
mapping[conf_id] = scheduler
|
|
return mapping
|
|
|
|
async def reload_pipeline_scheduler(self, conf_id: str) -> None:
|
|
"""重新加载消息事件流水线调度器.
|
|
|
|
Returns:
|
|
dict[str, PipelineScheduler]: 平台 ID 到流水线调度器的映射
|
|
|
|
"""
|
|
ab_config = self.astrbot_config_mgr.confs.get(conf_id)
|
|
if not ab_config:
|
|
raise ValueError(f"配置文件 {conf_id} 不存在")
|
|
scheduler = PipelineScheduler(
|
|
PipelineContext(ab_config, self.plugin_manager, conf_id),
|
|
)
|
|
await scheduler.initialize()
|
|
self.pipeline_scheduler_mapping[conf_id] = scheduler
|