Compare commits
5 Commits
v4.6.1
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
381f7f4405 | ||
|
|
8f38e748cd | ||
|
|
f83484a8c0 | ||
|
|
56e3ddd62a | ||
|
|
80948be41d |
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import anyio
|
||||||
import click
|
import click
|
||||||
from filelock import FileLock, Timeout
|
from filelock import FileLock, Timeout
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ def init() -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with lock.acquire():
|
with lock.acquire():
|
||||||
asyncio.run(initialize_astrbot(astrbot_root))
|
anyio.run(initialize_astrbot, astrbot_root)
|
||||||
except Timeout:
|
except Timeout:
|
||||||
raise click.ClickException("无法获取锁文件,请检查是否有其他实例正在运行")
|
raise click.ClickException("无法获取锁文件,请检查是否有其他实例正在运行")
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import asyncio
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import anyio
|
||||||
import click
|
import click
|
||||||
from filelock import FileLock, Timeout
|
from filelock import FileLock, Timeout
|
||||||
|
|
||||||
from ..utils import check_astrbot_root, check_dashboard, get_astrbot_root
|
from ..utils import check_astrbot_root, check_dashboard, get_astrbot_root
|
||||||
|
|
||||||
|
|
||||||
async def run_astrbot(astrbot_root: Path):
|
async def run_astrbot(astrbot_root: Path) -> None:
|
||||||
"""运行 AstrBot"""
|
"""运行 AstrBot"""
|
||||||
from astrbot.core import LogBroker, LogManager, db_helper, logger
|
from astrbot.core import LogBroker, LogManager, db_helper, logger
|
||||||
from astrbot.core.initial_loader import InitialLoader
|
from astrbot.core.initial_loader import InitialLoader
|
||||||
@@ -53,7 +53,7 @@ def run(reload: bool, port: str) -> None:
|
|||||||
lock_file = astrbot_root / "astrbot.lock"
|
lock_file = astrbot_root / "astrbot.lock"
|
||||||
lock = FileLock(lock_file, timeout=5)
|
lock = FileLock(lock_file, timeout=5)
|
||||||
with lock.acquire():
|
with lock.acquire():
|
||||||
asyncio.run(run_astrbot(astrbot_root))
|
anyio.run(run_astrbot, astrbot_root)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
click.echo("AstrBot 已关闭...")
|
click.echo("AstrBot 已关闭...")
|
||||||
except Timeout:
|
except Timeout:
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import os
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from asyncio import Queue
|
|
||||||
|
import anyio
|
||||||
|
|
||||||
from astrbot.core import LogBroker, logger, sp
|
from astrbot.core import LogBroker, logger, sp
|
||||||
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
||||||
@@ -104,7 +105,9 @@ class AstrBotCoreLifecycle:
|
|||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
# 初始化事件队列
|
# 初始化事件队列
|
||||||
self.event_queue = Queue()
|
self._event_queue_send, self.event_queue = anyio.create_memory_object_stream[
|
||||||
|
object
|
||||||
|
](0)
|
||||||
|
|
||||||
# 初始化人格管理器
|
# 初始化人格管理器
|
||||||
self.persona_mgr = PersonaManager(self.db, self.astrbot_config_mgr)
|
self.persona_mgr = PersonaManager(self.db, self.astrbot_config_mgr)
|
||||||
@@ -118,7 +121,9 @@ class AstrBotCoreLifecycle:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 初始化平台管理器
|
# 初始化平台管理器
|
||||||
self.platform_manager = PlatformManager(self.astrbot_config, self.event_queue)
|
self.platform_manager = PlatformManager(
|
||||||
|
self.astrbot_config, self._event_queue_send
|
||||||
|
)
|
||||||
|
|
||||||
# 初始化对话管理器
|
# 初始化对话管理器
|
||||||
self.conversation_manager = ConversationManager(self.db)
|
self.conversation_manager = ConversationManager(self.db)
|
||||||
@@ -131,7 +136,7 @@ class AstrBotCoreLifecycle:
|
|||||||
|
|
||||||
# 初始化提供给插件的上下文
|
# 初始化提供给插件的上下文
|
||||||
self.star_context = Context(
|
self.star_context = Context(
|
||||||
self.event_queue,
|
self._event_queue_send,
|
||||||
self.astrbot_config,
|
self.astrbot_config,
|
||||||
self.db,
|
self.db,
|
||||||
self.provider_manager,
|
self.provider_manager,
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ class SQLiteDatabase(BaseDatabase):
|
|||||||
async with session.begin():
|
async with session.begin():
|
||||||
await session.execute(
|
await session.execute(
|
||||||
delete(ConversationV2).where(
|
delete(ConversationV2).where(
|
||||||
col(ConversationV2.user_id) == user_id
|
col(ConversationV2.user_id) == user_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""事件总线, 用于处理事件的分发和处理
|
"""事件总线, 用于处理事件的分发和处理.
|
||||||
|
|
||||||
事件总线是一个异步队列, 用于接收各种消息事件, 并将其发送到Scheduler调度器进行处理
|
事件总线是一个异步队列, 用于接收各种消息事件, 并将其发送到Scheduler调度器进行处理
|
||||||
其中包含了一个无限循环的调度函数, 用于从事件队列中获取新的事件, 并创建一个新的异步任务来执行管道调度器的处理逻辑
|
其中包含了一个无限循环的调度函数, 用于从事件队列中获取新的事件, 并创建一个新的异步任务来执行管道调度器的处理逻辑
|
||||||
|
|
||||||
@@ -10,8 +11,8 @@ class:
|
|||||||
2. 无限循环的调度函数, 从事件队列中获取新的事件, 打印日志并创建一个新的异步任务来执行管道调度器的处理逻辑
|
2. 无限循环的调度函数, 从事件队列中获取新的事件, 打印日志并创建一个新的异步任务来执行管道调度器的处理逻辑
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import anyio
|
||||||
from asyncio import Queue
|
from anyio.streams.memory import MemoryObjectReceiveStream
|
||||||
|
|
||||||
from astrbot.core import logger
|
from astrbot.core import logger
|
||||||
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
||||||
@@ -25,28 +26,29 @@ class EventBus:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
event_queue: Queue,
|
event_queue: MemoryObjectReceiveStream[AstrMessageEvent],
|
||||||
pipeline_scheduler_mapping: dict[str, PipelineScheduler],
|
pipeline_scheduler_mapping: dict[str, PipelineScheduler],
|
||||||
astrbot_config_mgr: AstrBotConfigManager = None,
|
astrbot_config_mgr: AstrBotConfigManager | None = None,
|
||||||
):
|
) -> None:
|
||||||
self.event_queue = event_queue # 事件队列
|
self.event_queue = event_queue # 事件队列
|
||||||
# abconf uuid -> scheduler
|
# abconf uuid -> scheduler
|
||||||
self.pipeline_scheduler_mapping = pipeline_scheduler_mapping
|
self.pipeline_scheduler_mapping = pipeline_scheduler_mapping
|
||||||
self.astrbot_config_mgr = astrbot_config_mgr
|
self.astrbot_config_mgr = astrbot_config_mgr
|
||||||
|
|
||||||
async def dispatch(self):
|
async def dispatch(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
event: AstrMessageEvent = await self.event_queue.get()
|
event: AstrMessageEvent = await self.event_queue.receive()
|
||||||
conf_info = self.astrbot_config_mgr.get_conf_info(event.unified_msg_origin)
|
conf_info = self.astrbot_config_mgr.get_conf_info(event.unified_msg_origin)
|
||||||
self._print_event(event, conf_info["name"])
|
self._print_event(event, conf_info["name"])
|
||||||
scheduler = self.pipeline_scheduler_mapping.get(conf_info["id"])
|
scheduler = self.pipeline_scheduler_mapping.get(conf_info["id"])
|
||||||
asyncio.create_task(scheduler.execute(event))
|
anyio.create_task(scheduler.execute(event))
|
||||||
|
|
||||||
def _print_event(self, event: AstrMessageEvent, conf_name: str):
|
def _print_event(self, event: AstrMessageEvent, conf_name: str) -> None:
|
||||||
"""用于记录事件信息
|
"""用于记录事件信息
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (AstrMessageEvent): 事件对象
|
event: 事件对象
|
||||||
|
conf_name: 配置名称
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# 如果有发送者名称: [平台名] 发送者名称/发送者ID: 消息概要
|
# 如果有发送者名称: [平台名] 发送者名称/发送者ID: 消息概要
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import asyncio
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from urllib.parse import unquote, urlparse
|
from urllib.parse import unquote, urlparse
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
|
||||||
|
|
||||||
class FileTokenService:
|
class FileTokenService:
|
||||||
"""维护一个简单的基于令牌的文件下载服务,支持超时和懒清除。"""
|
"""维护一个简单的基于令牌的文件下载服务,支持超时和懒清除。"""
|
||||||
|
|
||||||
def __init__(self, default_timeout: float = 300):
|
def __init__(self, default_timeout: float = 300) -> None:
|
||||||
self.lock = asyncio.Lock()
|
self.lock = anyio.Lock()
|
||||||
self.staged_files = {} # token: (file_path, expire_time)
|
self.staged_files: dict = {} # token: (file_path, expire_time)
|
||||||
self.default_timeout = default_timeout
|
self.default_timeout = default_timeout
|
||||||
|
|
||||||
async def _cleanup_expired_tokens(self):
|
async def _cleanup_expired_tokens(self):
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import asyncio
|
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
|
||||||
from astrbot.core import logger
|
from astrbot.core import logger
|
||||||
from astrbot.core.config.astrbot_config import RateLimitStrategy
|
from astrbot.core.config.astrbot_config import RateLimitStrategy
|
||||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||||
@@ -19,11 +20,11 @@ class RateLimitStage(Stage):
|
|||||||
如果触发限流,将 stall 流水线,直到下一个时间窗口来临时自动唤醒。
|
如果触发限流,将 stall 流水线,直到下一个时间窗口来临时自动唤醒。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
# 存储每个会话的请求时间队列
|
# 存储每个会话的请求时间队列
|
||||||
self.event_timestamps: defaultdict[str, deque[datetime]] = defaultdict(deque)
|
self.event_timestamps: defaultdict[str, deque[datetime]] = defaultdict(deque)
|
||||||
# 为每个会话设置一个锁,避免并发冲突
|
# 为每个会话设置一个锁,避免并发冲突
|
||||||
self.locks: defaultdict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
|
self.locks: defaultdict[str, anyio.Lock] = defaultdict(anyio.Lock)
|
||||||
# 限流参数
|
# 限流参数
|
||||||
self.rate_limit_count: int = 0
|
self.rate_limit_count: int = 0
|
||||||
self.rate_limit_time: timedelta = timedelta(0)
|
self.rate_limit_time: timedelta = timedelta(0)
|
||||||
@@ -74,7 +75,7 @@ class RateLimitStage(Stage):
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"会话 {session_id} 被限流。根据限流策略,此会话处理将被暂停 {stall_duration:.2f} 秒。",
|
f"会话 {session_id} 被限流。根据限流策略,此会话处理将被暂停 {stall_duration:.2f} 秒。",
|
||||||
)
|
)
|
||||||
await asyncio.sleep(stall_duration)
|
await anyio.sleep(stall_duration)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
case RateLimitStrategy.DISCARD.value:
|
case RateLimitStrategy.DISCARD.value:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
from asyncio import Queue
|
|
||||||
|
from anyio.streams.memory import MemoryObjectSendStream
|
||||||
|
|
||||||
from astrbot.core import logger
|
from astrbot.core import logger
|
||||||
from astrbot.core.config.astrbot_config import AstrBotConfig
|
from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||||
@@ -12,7 +13,7 @@ from .sources.webchat.webchat_adapter import WebChatAdapter
|
|||||||
|
|
||||||
|
|
||||||
class PlatformManager:
|
class PlatformManager:
|
||||||
def __init__(self, config: AstrBotConfig, event_queue: Queue):
|
def __init__(self, config: AstrBotConfig, event_queue: MemoryObjectSendStream):
|
||||||
self.platform_insts: list[Platform] = []
|
self.platform_insts: list[Platform] = []
|
||||||
"""加载的 Platform 的实例"""
|
"""加载的 Platform 的实例"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import abc
|
import abc
|
||||||
import uuid
|
import uuid
|
||||||
from asyncio import Queue
|
|
||||||
from collections.abc import Awaitable
|
from collections.abc import Awaitable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from anyio.streams.memory import MemoryObjectSendStream
|
||||||
|
|
||||||
from astrbot.core.message.message_event_result import MessageChain
|
from astrbot.core.message.message_event_result import MessageChain
|
||||||
from astrbot.core.utils.metrics import Metric
|
from astrbot.core.utils.metrics import Metric
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ from .platform_metadata import PlatformMetadata
|
|||||||
|
|
||||||
|
|
||||||
class Platform(abc.ABC):
|
class Platform(abc.ABC):
|
||||||
def __init__(self, event_queue: Queue):
|
def __init__(self, event_queue: MemoryObjectSendStream):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
# 维护了消息平台的事件队列,EventBus 会从这里取出事件并处理。
|
# 维护了消息平台的事件队列,EventBus 会从这里取出事件并处理。
|
||||||
self._event_queue = event_queue
|
self._event_queue = event_queue
|
||||||
@@ -45,7 +46,7 @@ class Platform(abc.ABC):
|
|||||||
|
|
||||||
def commit_event(self, event: AstrMessageEvent):
|
def commit_event(self, event: AstrMessageEvent):
|
||||||
"""提交一个事件到事件队列。"""
|
"""提交一个事件到事件队列。"""
|
||||||
self._event_queue.put_nowait(event)
|
self._event_queue.send_nowait(event)
|
||||||
|
|
||||||
def get_client(self):
|
def get_client(self):
|
||||||
"""获取平台的客户端对象。"""
|
"""获取平台的客户端对象。"""
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class DingtalkPlatformAdapter(Platform):
|
|||||||
client=self.client,
|
client=self.client,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._event_queue.put_nowait(event)
|
self._event_queue.send_nowait(event)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
# await self.client_.start()
|
# await self.client_.start()
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class LarkPlatformAdapter(Platform):
|
|||||||
bot=self.lark_api,
|
bot=self.lark_api,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._event_queue.put_nowait(event)
|
self._event_queue.send_nowait(event)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
# self.client.start()
|
# self.client.start()
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import asyncio
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
import anyio
|
||||||
from quart import Quart, Response, request
|
from quart import Quart, Response, request
|
||||||
from slack_sdk.socket_mode.aiohttp import SocketModeClient
|
from slack_sdk.socket_mode.aiohttp import SocketModeClient
|
||||||
from slack_sdk.socket_mode.request import SocketModeRequest
|
from slack_sdk.socket_mode.request import SocketModeRequest
|
||||||
@@ -40,7 +40,7 @@ class SlackWebhookClient:
|
|||||||
logging.getLogger("quart.app").setLevel(logging.WARNING)
|
logging.getLogger("quart.app").setLevel(logging.WARNING)
|
||||||
logging.getLogger("quart.serving").setLevel(logging.WARNING)
|
logging.getLogger("quart.serving").setLevel(logging.WARNING)
|
||||||
|
|
||||||
self.shutdown_event = asyncio.Event()
|
self.shutdown_event = anyio.Event()
|
||||||
|
|
||||||
def _setup_routes(self):
|
def _setup_routes(self):
|
||||||
"""设置路由"""
|
"""设置路由"""
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""企业微信智能机器人 API 客户端
|
"""企业微信智能机器人 API 客户端.
|
||||||
|
|
||||||
处理消息加密解密、API 调用等
|
处理消息加密解密、API 调用等
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
处理企业微信智能机器人的 HTTP 回调请求
|
处理企业微信智能机器人的 HTTP 回调请求
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import anyio
|
||||||
import quart
|
import quart
|
||||||
|
|
||||||
from astrbot.api import logger
|
from astrbot.api import logger
|
||||||
@@ -41,7 +41,7 @@ class WecomAIBotServer:
|
|||||||
self.app = quart.Quart(__name__)
|
self.app = quart.Quart(__name__)
|
||||||
self._setup_routes()
|
self._setup_routes()
|
||||||
|
|
||||||
self.shutdown_event = asyncio.Event()
|
self.shutdown_event = anyio.Event()
|
||||||
|
|
||||||
def _setup_routes(self):
|
def _setup_routes(self):
|
||||||
"""设置 Quart 路由"""
|
"""设置 Quart 路由"""
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from collections.abc import Awaitable, Callable
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import anyio
|
||||||
|
|
||||||
from astrbot import logger
|
from astrbot import logger
|
||||||
from astrbot.core import sp
|
from astrbot.core import sp
|
||||||
@@ -98,7 +99,7 @@ class FunctionToolManager:
|
|||||||
self.func_list: list[FuncTool] = []
|
self.func_list: list[FuncTool] = []
|
||||||
self.mcp_client_dict: dict[str, MCPClient] = {}
|
self.mcp_client_dict: dict[str, MCPClient] = {}
|
||||||
"""MCP 服务列表"""
|
"""MCP 服务列表"""
|
||||||
self.mcp_client_event: dict[str, asyncio.Event] = {}
|
self.mcp_client_event: dict[str, anyio.Event] = {}
|
||||||
|
|
||||||
def empty(self) -> bool:
|
def empty(self) -> bool:
|
||||||
return len(self.func_list) == 0
|
return len(self.func_list) == 0
|
||||||
@@ -206,7 +207,7 @@ class FunctionToolManager:
|
|||||||
for name in mcp_server_json_obj:
|
for name in mcp_server_json_obj:
|
||||||
cfg = mcp_server_json_obj[name]
|
cfg = mcp_server_json_obj[name]
|
||||||
if cfg.get("active", True):
|
if cfg.get("active", True):
|
||||||
event = asyncio.Event()
|
event = anyio.Event()
|
||||||
asyncio.create_task(
|
asyncio.create_task(
|
||||||
self._init_mcp_client_task_wrapper(name, cfg, event),
|
self._init_mcp_client_task_wrapper(name, cfg, event),
|
||||||
)
|
)
|
||||||
@@ -216,7 +217,7 @@ class FunctionToolManager:
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
cfg: dict,
|
cfg: dict,
|
||||||
event: asyncio.Event,
|
event: anyio.Event,
|
||||||
ready_future: asyncio.Future | None = None,
|
ready_future: asyncio.Future | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""初始化 MCP 客户端的包装函数,用于捕获异常"""
|
"""初始化 MCP 客户端的包装函数,用于捕获异常"""
|
||||||
@@ -307,7 +308,7 @@ class FunctionToolManager:
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
config: dict,
|
config: dict,
|
||||||
event: asyncio.Event | None = None,
|
event: anyio.Event | None = None,
|
||||||
ready_future: asyncio.Future | None = None,
|
ready_future: asyncio.Future | None = None,
|
||||||
timeout: int = 30,
|
timeout: int = 30,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -316,7 +317,7 @@ class FunctionToolManager:
|
|||||||
Args:
|
Args:
|
||||||
name (str): The name of the MCP server.
|
name (str): The name of the MCP server.
|
||||||
config (dict): Configuration for the MCP server.
|
config (dict): Configuration for the MCP server.
|
||||||
event (asyncio.Event): Event to signal when the MCP client is ready.
|
event (anyio.Event): Event to signal when the MCP client is ready.
|
||||||
ready_future (asyncio.Future): Future to signal when the MCP client is ready.
|
ready_future (asyncio.Future): Future to signal when the MCP client is ready.
|
||||||
timeout (int): Timeout for the initialization.
|
timeout (int): Timeout for the initialization.
|
||||||
|
|
||||||
@@ -326,7 +327,7 @@ class FunctionToolManager:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not event:
|
if not event:
|
||||||
event = asyncio.Event()
|
event = anyio.Event()
|
||||||
if not ready_future:
|
if not ready_future:
|
||||||
ready_future = asyncio.Future()
|
ready_future = asyncio.Future()
|
||||||
if name in self.mcp_client_dict:
|
if name in self.mcp_client_dict:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
from asyncio import Queue
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from anyio.streams.memory import MemoryObjectSendStream
|
||||||
from deprecated import deprecated
|
from deprecated import deprecated
|
||||||
|
|
||||||
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
||||||
@@ -50,7 +50,7 @@ class Context:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
event_queue: Queue,
|
event_queue: MemoryObjectSendStream,
|
||||||
config: AstrBotConfig,
|
config: AstrBotConfig,
|
||||||
db: BaseDatabase,
|
db: BaseDatabase,
|
||||||
provider_manager: ProviderManager,
|
provider_manager: ProviderManager,
|
||||||
@@ -193,7 +193,7 @@ class Context:
|
|||||||
"""获取 AstrBot 数据库。"""
|
"""获取 AstrBot 数据库。"""
|
||||||
return self._db
|
return self._db
|
||||||
|
|
||||||
def get_event_queue(self) -> Queue:
|
def get_event_queue(self) -> MemoryObjectSendStream:
|
||||||
"""获取事件队列。"""
|
"""获取事件队列。"""
|
||||||
return self._event_queue
|
return self._event_queue
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class CommandGroupFilter(HandlerFilter):
|
|||||||
prefix + "│ ",
|
prefix + "│ ",
|
||||||
event=event,
|
event=event,
|
||||||
cfg=cfg,
|
cfg=cfg,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return "".join(parts)
|
return "".join(parts)
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ class UmopConfigRouter:
|
|||||||
if len(p1_ls) != 3 or len(p2_ls) != 3:
|
if len(p1_ls) != 3 or len(p2_ls) != 3:
|
||||||
return False # 非法格式
|
return False # 非法格式
|
||||||
|
|
||||||
return all(p == "" or p == "*" or p == t for p, t in zip(p1_ls, p2_ls))
|
return all(
|
||||||
|
p == "" or p == "*" or p == t for p, t in zip(p1_ls, p2_ls, strict=False)
|
||||||
|
)
|
||||||
|
|
||||||
def get_conf_id_for_umop(self, umo: str) -> str | None:
|
def get_conf_id_for_umop(self, umo: str) -> str | None:
|
||||||
"""根据 UMO 获取对应的配置文件 ID
|
"""根据 UMO 获取对应的配置文件 ID
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"""会话控制"""
|
"""会话控制"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import asyncio
|
import asyncio
|
||||||
import copy
|
import copy
|
||||||
@@ -8,11 +10,13 @@ import time
|
|||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
|
||||||
import astrbot.core.message.components as Comp
|
import astrbot.core.message.components as Comp
|
||||||
from astrbot.core.platform import AstrMessageEvent
|
from astrbot.core.platform import AstrMessageEvent
|
||||||
|
|
||||||
USER_SESSIONS: dict[str, "SessionWaiter"] = {} # 存储 SessionWaiter 实例
|
USER_SESSIONS: dict[str, SessionWaiter] = {} # 存储 SessionWaiter 实例
|
||||||
FILTERS: list["SessionFilter"] = [] # 存储 SessionFilter 实例
|
FILTERS: list[SessionFilter] = [] # 存储 SessionFilter 实例
|
||||||
|
|
||||||
|
|
||||||
class SessionController:
|
class SessionController:
|
||||||
@@ -20,16 +24,16 @@ class SessionController:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.future = asyncio.Future()
|
self.future = asyncio.Future()
|
||||||
self.current_event: asyncio.Event = None
|
self.current_event: anyio.Event | None = None
|
||||||
"""当前正在等待的所用的异步事件"""
|
"""当前正在等待的所用的异步事件"""
|
||||||
self.ts: float = None
|
self.ts: float | None = None
|
||||||
"""上次保持(keep)开始时的时间"""
|
"""上次保持(keep)开始时的时间"""
|
||||||
self.timeout: float | int = None
|
self.timeout: float | int | None = None
|
||||||
"""上次保持(keep)开始时的超时时间"""
|
"""上次保持(keep)开始时的超时时间"""
|
||||||
|
|
||||||
self.history_chains: list[list[Comp.BaseMessageComponent]] = []
|
self.history_chains: list[list[Comp.BaseMessageComponent]] = []
|
||||||
|
|
||||||
def stop(self, error: Exception = None):
|
def stop(self, error: Exception | None = None):
|
||||||
"""立即结束这个会话"""
|
"""立即结束这个会话"""
|
||||||
if not self.future.done():
|
if not self.future.done():
|
||||||
if error:
|
if error:
|
||||||
@@ -53,7 +57,9 @@ class SessionController:
|
|||||||
self.stop()
|
self.stop()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
left_timeout = self.timeout - (new_ts - self.ts)
|
current_timeout = self.timeout if self.timeout is not None else 0
|
||||||
|
current_ts = self.ts if self.ts is not None else new_ts
|
||||||
|
left_timeout = current_timeout - (new_ts - current_ts)
|
||||||
timeout = left_timeout + timeout
|
timeout = left_timeout + timeout
|
||||||
if timeout <= 0:
|
if timeout <= 0:
|
||||||
self.stop()
|
self.stop()
|
||||||
@@ -62,18 +68,19 @@ class SessionController:
|
|||||||
if self.current_event and not self.current_event.is_set():
|
if self.current_event and not self.current_event.is_set():
|
||||||
self.current_event.set() # 通知上一个 keep 结束
|
self.current_event.set() # 通知上一个 keep 结束
|
||||||
|
|
||||||
new_event = asyncio.Event()
|
new_event = anyio.Event()
|
||||||
self.ts = new_ts
|
self.ts = new_ts
|
||||||
self.current_event = new_event
|
self.current_event = new_event
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
asyncio.create_task(self._holding(new_event, timeout)) # 开始新的 keep
|
anyio.create_task(self._holding(new_event, timeout)) # 开始新的 keep
|
||||||
|
|
||||||
async def _holding(self, event: asyncio.Event, timeout: int):
|
async def _holding(self, event: anyio.Event, timeout_seconds: float):
|
||||||
"""等待事件结束或超时"""
|
"""等待事件结束或超时"""
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(event.wait(), timeout)
|
with anyio.move_on_after(timeout_seconds):
|
||||||
except asyncio.TimeoutError:
|
await event.wait()
|
||||||
|
except TimeoutError:
|
||||||
if not self.future.done():
|
if not self.future.done():
|
||||||
self.future.set_exception(TimeoutError("等待超时"))
|
self.future.set_exception(TimeoutError("等待超时"))
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
@@ -105,10 +112,12 @@ class SessionWaiter:
|
|||||||
session_filter: SessionFilter,
|
session_filter: SessionFilter,
|
||||||
session_id: str,
|
session_id: str,
|
||||||
record_history_chains: bool,
|
record_history_chains: bool,
|
||||||
):
|
) -> None:
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
self.session_filter = session_filter
|
self.session_filter = session_filter
|
||||||
self.handler: Callable[[str], Awaitable[Any]] | None = None # 处理函数
|
self.handler: (
|
||||||
|
Callable[[SessionController, AstrMessageEvent], Awaitable[Any]] | None
|
||||||
|
) = None # 处理函数
|
||||||
|
|
||||||
self.session_controller = SessionController()
|
self.session_controller = SessionController()
|
||||||
self.record_history_chains = record_history_chains
|
self.record_history_chains = record_history_chains
|
||||||
@@ -119,15 +128,15 @@ class SessionWaiter:
|
|||||||
|
|
||||||
async def register_wait(
|
async def register_wait(
|
||||||
self,
|
self,
|
||||||
handler: Callable[[str], Awaitable[Any]],
|
handler: Callable[[SessionController, AstrMessageEvent], Awaitable[Any]],
|
||||||
timeout: int = 30,
|
timeout_seconds: int = 30,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""等待外部输入并处理"""
|
"""等待外部输入并处理"""
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
USER_SESSIONS[self.session_id] = self
|
USER_SESSIONS[self.session_id] = self
|
||||||
|
|
||||||
# 开始一个会话保持事件
|
# 开始一个会话保持事件
|
||||||
self.session_controller.keep(timeout, reset_timeout=True)
|
self.session_controller.keep(timeout_seconds, reset_timeout=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await self.session_controller.future
|
return await self.session_controller.future
|
||||||
@@ -137,7 +146,7 @@ class SessionWaiter:
|
|||||||
finally:
|
finally:
|
||||||
self._cleanup()
|
self._cleanup()
|
||||||
|
|
||||||
def _cleanup(self, error: Exception = None):
|
def _cleanup(self, error: Exception | None = None):
|
||||||
"""清理会话"""
|
"""清理会话"""
|
||||||
USER_SESSIONS.pop(self.session_id, None)
|
USER_SESSIONS.pop(self.session_id, None)
|
||||||
try:
|
try:
|
||||||
@@ -153,6 +162,10 @@ class SessionWaiter:
|
|||||||
if not session or session.session_controller.future.done():
|
if not session or session.session_controller.future.done():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 此时 session 不会是 None,因为上面的检查
|
||||||
|
if session is None:
|
||||||
|
return
|
||||||
|
|
||||||
async with session._lock:
|
async with session._lock:
|
||||||
if not session.session_controller.future.done():
|
if not session.session_controller.future.done():
|
||||||
if session.record_history_chains:
|
if session.record_history_chains:
|
||||||
@@ -161,7 +174,8 @@ class SessionWaiter:
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
# TODO: 这里使用 create_task,跟踪 task,防止超时后这里 handler 仍然在执行
|
# TODO: 这里使用 create_task,跟踪 task,防止超时后这里 handler 仍然在执行
|
||||||
await session.handler(session.session_controller, event)
|
if session.handler is not None:
|
||||||
|
await session.handler(session.session_controller, event)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
session.session_controller.stop(e)
|
session.session_controller.stop(e)
|
||||||
|
|
||||||
@@ -173,11 +187,13 @@ def session_waiter(timeout: int = 30, record_history_chains: bool = False):
|
|||||||
:param record_history_chain: 是否自动记录历史消息链。可以通过 controller.get_history_chains() 获取。深拷贝。
|
:param record_history_chain: 是否自动记录历史消息链。可以通过 controller.get_history_chains() 获取。深拷贝。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func: Callable[[str], Awaitable[Any]]):
|
def decorator(
|
||||||
|
func: Callable[[SessionController, AstrMessageEvent], Awaitable[Any]],
|
||||||
|
):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
async def wrapper(
|
async def wrapper(
|
||||||
event: AstrMessageEvent,
|
event: AstrMessageEvent,
|
||||||
session_filter: SessionFilter = None,
|
session_filter: SessionFilter | None = None,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
import anyio
|
||||||
import jwt
|
import jwt
|
||||||
from quart import request
|
from quart import request
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ class AuthRoute(Route):
|
|||||||
)
|
)
|
||||||
.__dict__
|
.__dict__
|
||||||
)
|
)
|
||||||
await asyncio.sleep(3)
|
await anyio.sleep(3)
|
||||||
return Response().error("用户名或密码错误").__dict__
|
return Response().error("用户名或密码错误").__dict__
|
||||||
|
|
||||||
async def edit_account(self):
|
async def edit_account(self):
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os
|
|||||||
import uuid
|
import uuid
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
import anyio
|
||||||
from quart import Response as QuartResponse
|
from quart import Response as QuartResponse
|
||||||
from quart import g, make_response, request
|
from quart import g, make_response, request
|
||||||
|
|
||||||
@@ -188,8 +189,8 @@ class ChatRoute(Route):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if not client_disconnected:
|
if not client_disconnected:
|
||||||
await asyncio.sleep(0.05)
|
await anyio.sleep(0.05)
|
||||||
except asyncio.CancelledError:
|
except anyio.get_cancelled_exc_class():
|
||||||
logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
|
logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
|
||||||
client_disconnected = True
|
client_disconnected = True
|
||||||
|
|
||||||
|
|||||||
@@ -817,7 +817,8 @@ class ConfigRoute(Route):
|
|||||||
cached_token = self._logo_token_cache[cache_key]
|
cached_token = self._logo_token_cache[cache_key]
|
||||||
# 确保platform_default_tmpl[platform.name]存在且为字典
|
# 确保platform_default_tmpl[platform.name]存在且为字典
|
||||||
if platform.name not in platform_default_tmpl or not isinstance(
|
if platform.name not in platform_default_tmpl or not isinstance(
|
||||||
platform_default_tmpl[platform.name], dict
|
platform_default_tmpl[platform.name],
|
||||||
|
dict,
|
||||||
):
|
):
|
||||||
platform_default_tmpl[platform.name] = {}
|
platform_default_tmpl[platform.name] = {}
|
||||||
platform_default_tmpl[platform.name]["logo_token"] = cached_token
|
platform_default_tmpl[platform.name]["logo_token"] = cached_token
|
||||||
@@ -846,7 +847,8 @@ class ConfigRoute(Route):
|
|||||||
|
|
||||||
# 确保platform_default_tmpl[platform.name]存在且为字典
|
# 确保platform_default_tmpl[platform.name]存在且为字典
|
||||||
if platform.name not in platform_default_tmpl or not isinstance(
|
if platform.name not in platform_default_tmpl or not isinstance(
|
||||||
platform_default_tmpl[platform.name], dict
|
platform_default_tmpl[platform.name],
|
||||||
|
dict,
|
||||||
):
|
):
|
||||||
platform_default_tmpl[platform.name] = {}
|
platform_default_tmpl[platform.name] = {}
|
||||||
|
|
||||||
|
|||||||
18
main.py
18
main.py
@@ -1,16 +1,20 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import anyio
|
||||||
|
|
||||||
from astrbot.core import LogBroker, LogManager, db_helper, logger
|
from astrbot.core import LogBroker, LogManager, db_helper, logger
|
||||||
from astrbot.core.config.default import VERSION
|
from astrbot.core.config.default import VERSION
|
||||||
from astrbot.core.initial_loader import InitialLoader
|
from astrbot.core.initial_loader import InitialLoader
|
||||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
||||||
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
|
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
|
||||||
|
|
||||||
|
# uvloop 仅在非 Windows 平台可用
|
||||||
|
backend_options = {"use_uvloop": True} if sys.platform != "win32" else {}
|
||||||
|
|
||||||
# 将父目录添加到 sys.path
|
# 将父目录添加到 sys.path
|
||||||
sys.path.append(Path(__file__).parent.as_posix())
|
sys.path.append(Path(__file__).parent.as_posix())
|
||||||
|
|
||||||
@@ -93,7 +97,11 @@ if __name__ == "__main__":
|
|||||||
LogManager.set_queue_handler(logger, log_broker)
|
LogManager.set_queue_handler(logger, log_broker)
|
||||||
|
|
||||||
# 检查仪表板文件
|
# 检查仪表板文件
|
||||||
webui_dir = asyncio.run(check_dashboard_files(args.webui_dir))
|
webui_dir = anyio.run(
|
||||||
|
check_dashboard_files,
|
||||||
|
args.webui_dir,
|
||||||
|
backend_options=backend_options,
|
||||||
|
)
|
||||||
|
|
||||||
db = db_helper
|
db = db_helper
|
||||||
|
|
||||||
@@ -102,4 +110,8 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
core_lifecycle = InitialLoader(db, log_broker)
|
core_lifecycle = InitialLoader(db, log_broker)
|
||||||
core_lifecycle.webui_dir = webui_dir
|
core_lifecycle.webui_dir = webui_dir
|
||||||
asyncio.run(core_lifecycle.start())
|
logger.info(
|
||||||
|
"将按以下异步后端启动 AstrBot: %s",
|
||||||
|
backend_options if backend_options else "asyncio",
|
||||||
|
)
|
||||||
|
anyio.run(core_lifecycle.start, backend_options=backend_options)
|
||||||
|
|||||||
@@ -164,14 +164,14 @@ class ConversationCommands:
|
|||||||
"%m-%d %H:%M",
|
"%m-%d %H:%M",
|
||||||
)
|
)
|
||||||
parts.append(
|
parts.append(
|
||||||
f"{idx}. {conv['name']}({conv['id'][:4]})\n 上次更新:{ts_h}\n"
|
f"{idx}. {conv['name']}({conv['id'][:4]})\n 上次更新:{ts_h}\n",
|
||||||
)
|
)
|
||||||
idx += 1
|
idx += 1
|
||||||
if idx == 1:
|
if idx == 1:
|
||||||
parts.append("没有找到任何对话。")
|
parts.append("没有找到任何对话。")
|
||||||
dify_cid = provider.conversation_ids.get(message.unified_msg_origin, None)
|
dify_cid = provider.conversation_ids.get(message.unified_msg_origin, None)
|
||||||
parts.append(
|
parts.append(
|
||||||
f"\n\n用户: {message.unified_msg_origin}\n当前对话: {dify_cid}\n使用 /switch <序号> 切换对话。"
|
f"\n\n用户: {message.unified_msg_origin}\n当前对话: {dify_cid}\n使用 /switch <序号> 切换对话。",
|
||||||
)
|
)
|
||||||
ret = "".join(parts)
|
ret = "".join(parts)
|
||||||
message.set_result(MessageEventResult().message(ret))
|
message.set_result(MessageEventResult().message(ret))
|
||||||
@@ -211,7 +211,7 @@ class ConversationCommands:
|
|||||||
persona_id = persona["name"]
|
persona_id = persona["name"]
|
||||||
title = _titles.get(conv.cid, "新对话")
|
title = _titles.get(conv.cid, "新对话")
|
||||||
parts.append(
|
parts.append(
|
||||||
f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_id}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n"
|
f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_id}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n",
|
||||||
)
|
)
|
||||||
global_index += 1
|
global_index += 1
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class ProviderCommands:
|
|||||||
curr_model = prov.get_model() or "无"
|
curr_model = prov.get_model() or "无"
|
||||||
parts.append(f"\n当前模型: [{curr_model}]")
|
parts.append(f"\n当前模型: [{curr_model}]")
|
||||||
parts.append(
|
parts.append(
|
||||||
"\nTips: 使用 /model <模型名/编号>,即可实时更换模型。如目标模型不存在于上表,请输入模型名。"
|
"\nTips: 使用 /model <模型名/编号>,即可实时更换模型。如目标模型不存在于上表,请输入模型名。",
|
||||||
)
|
)
|
||||||
|
|
||||||
ret = "".join(parts)
|
ret = "".join(parts)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from collections import defaultdict
|
|||||||
|
|
||||||
import aiodocker
|
import aiodocker
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import anyio
|
||||||
|
|
||||||
from astrbot.api import llm_tool, logger, star
|
from astrbot.api import llm_tool, logger, star
|
||||||
from astrbot.api.event import AstrMessageEvent, MessageEventResult, filter
|
from astrbot.api.event import AstrMessageEvent, MessageEventResult, filter
|
||||||
@@ -291,7 +292,7 @@ class Main(star.Star):
|
|||||||
self.user_waiting[uid] = time.time()
|
self.user_waiting[uid] = time.time()
|
||||||
tip = "文件"
|
tip = "文件"
|
||||||
yield event.plain_result(f"代码执行器: 请在 60s 内上传一个{tip}。")
|
yield event.plain_result(f"代码执行器: 请在 60s 内上传一个{tip}。")
|
||||||
await asyncio.sleep(60)
|
await anyio.sleep(60)
|
||||||
if uid in self.user_waiting:
|
if uid in self.user_waiting:
|
||||||
yield event.plain_result(
|
yield event.plain_result(
|
||||||
f"代码执行器: {event.get_sender_name()}/{event.get_sender_id()} 未在规定时间内上传{tip}。",
|
f"代码执行器: {event.get_sender_name()}/{event.get_sender_id()} 未在规定时间内上传{tip}。",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import asyncio
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import anyio
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from readability import Document
|
from readability import Document
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ class Main(star.Star):
|
|||||||
def __init__(self, context: star.Context) -> None:
|
def __init__(self, context: star.Context) -> None:
|
||||||
self.context = context
|
self.context = context
|
||||||
self.tavily_key_index = 0
|
self.tavily_key_index = 0
|
||||||
self.tavily_key_lock = asyncio.Lock()
|
self.tavily_key_lock = anyio.Lock()
|
||||||
|
|
||||||
# 将 str 类型的 key 迁移至 list[str],并保存
|
# 将 str 类型的 key 迁移至 list[str],并保存
|
||||||
cfg = self.context.get_config()
|
cfg = self.context.get_config()
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ dependencies = [
|
|||||||
"jieba>=0.42.1",
|
"jieba>=0.42.1",
|
||||||
"markitdown-no-magika[docx,xls,xlsx]>=0.1.2",
|
"markitdown-no-magika[docx,xls,xlsx]>=0.1.2",
|
||||||
"xinference-client",
|
"xinference-client",
|
||||||
|
"anyio>=4.11.0",
|
||||||
|
"uvloop>=0.22.1 ; sys_platform == 'linux'",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
@@ -98,8 +100,8 @@ select = [
|
|||||||
# "SIM", # flake8-simplify
|
# "SIM", # flake8-simplify
|
||||||
]
|
]
|
||||||
ignore = [
|
ignore = [
|
||||||
"F403",
|
"F403",
|
||||||
"F405",
|
"F405",
|
||||||
"E501",
|
"E501",
|
||||||
"ASYNC230" # TODO: handle ASYNC230 in AstrBot
|
"ASYNC230" # TODO: handle ASYNC230 in AstrBot
|
||||||
]
|
]
|
||||||
@@ -112,6 +114,34 @@ reportMissingImports = false
|
|||||||
include = ["astrbot","packages"]
|
include = ["astrbot","packages"]
|
||||||
exclude = ["dashboard", "node_modules", "dist", "data", "tests"]
|
exclude = ["dashboard", "node_modules", "dist", "data", "tests"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.10"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = false
|
||||||
|
disallow_incomplete_defs = false
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_untyped_decorators = false
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_no_return = true
|
||||||
|
warn_unreachable = true
|
||||||
|
strict_equality = true
|
||||||
|
show_error_codes = true
|
||||||
|
ignore_missing_imports = true
|
||||||
|
explicit_package_bases = true
|
||||||
|
namespace_packages = true
|
||||||
|
files = ["astrbot", "packages"]
|
||||||
|
exclude = [
|
||||||
|
"dashboard",
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"data",
|
||||||
|
"tests",
|
||||||
|
"packages/.*/.*",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.hatch.version]
|
[tool.hatch.version]
|
||||||
source = "uv-dynamic-versioning"
|
source = "uv-dynamic-versioning"
|
||||||
|
|
||||||
@@ -123,4 +153,3 @@ bump = true
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling", "uv-dynamic-versioning"]
|
requires = ["hatchling", "uv-dynamic-versioning"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
|||||||
550
requirements.txt
550
requirements.txt
@@ -1,54 +1,498 @@
|
|||||||
aiocqhttp>=1.4.4
|
# This file was autogenerated by uv via the following command:
|
||||||
aiodocker>=0.24.0
|
# uv export --format requirements.txt --no-hashes --no-editable
|
||||||
aiohttp>=3.11.18
|
.
|
||||||
aiocqhttp>=1.4.4
|
aiocqhttp==1.4.4
|
||||||
aiodocker>=0.24.0
|
# via astrbot
|
||||||
aiohttp>=3.11.18
|
aiodocker==0.24.0
|
||||||
aiosqlite>=0.21.0
|
# via astrbot
|
||||||
anthropic>=0.51.0
|
aiofiles==25.1.0
|
||||||
apscheduler>=3.11.0
|
# via
|
||||||
beautifulsoup4>=4.13.4
|
# astrbot
|
||||||
certifi>=2025.4.26
|
# quart
|
||||||
chardet~=5.1.0
|
aiohappyeyeballs==2.6.1
|
||||||
colorlog>=6.9.0
|
# via aiohttp
|
||||||
cryptography>=44.0.3
|
aiohttp==3.13.2
|
||||||
dashscope>=1.23.2
|
# via
|
||||||
defusedxml>=0.7.1
|
# aiodocker
|
||||||
deprecated>=1.2.18
|
# astrbot
|
||||||
dingtalk-stream>=0.22.1
|
# dashscope
|
||||||
docstring-parser>=0.16
|
# dingtalk-stream
|
||||||
|
# py-cord
|
||||||
|
# qq-botpy
|
||||||
|
# xinference-client
|
||||||
|
aiosignal==1.4.0
|
||||||
|
# via aiohttp
|
||||||
|
aiosqlite==0.21.0
|
||||||
|
# via astrbot
|
||||||
|
annotated-types==0.7.0
|
||||||
|
# via pydantic
|
||||||
|
anthropic==0.72.0
|
||||||
|
# via astrbot
|
||||||
|
anyio==4.11.0
|
||||||
|
# via
|
||||||
|
# anthropic
|
||||||
|
# astrbot
|
||||||
|
# google-genai
|
||||||
|
# httpx
|
||||||
|
# mcp
|
||||||
|
# openai
|
||||||
|
# sse-starlette
|
||||||
|
# starlette
|
||||||
|
# watchfiles
|
||||||
|
apscheduler==3.11.1
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# qq-botpy
|
||||||
|
argcomplete==3.6.3
|
||||||
|
# via commitizen
|
||||||
|
async-timeout==5.0.1 ; python_full_version < '3.11'
|
||||||
|
# via aiohttp
|
||||||
|
attrs==25.4.0
|
||||||
|
# via
|
||||||
|
# aiohttp
|
||||||
|
# jsonschema
|
||||||
|
# referencing
|
||||||
|
audioop-lts==0.2.2 ; python_full_version >= '3.13'
|
||||||
|
# via astrbot
|
||||||
|
backports-asyncio-runner==1.2.0 ; python_full_version < '3.11'
|
||||||
|
# via pytest-asyncio
|
||||||
|
beautifulsoup4==4.14.2
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# markdownify
|
||||||
|
# markitdown-no-magika
|
||||||
|
blinker==1.9.0
|
||||||
|
# via
|
||||||
|
# flask
|
||||||
|
# quart
|
||||||
|
cachetools==6.2.1
|
||||||
|
# via google-auth
|
||||||
|
certifi==2025.10.5
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# dashscope
|
||||||
|
# httpcore
|
||||||
|
# httpx
|
||||||
|
# requests
|
||||||
|
cffi==2.0.0
|
||||||
|
# via
|
||||||
|
# cryptography
|
||||||
|
# silk-python
|
||||||
|
chardet==5.1.0
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# readability-lxml
|
||||||
|
charset-normalizer==3.4.4
|
||||||
|
# via
|
||||||
|
# commitizen
|
||||||
|
# markitdown-no-magika
|
||||||
|
# requests
|
||||||
|
click==8.3.0
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# flask
|
||||||
|
# quart
|
||||||
|
# uvicorn
|
||||||
|
cobble==0.1.4
|
||||||
|
# via mammoth
|
||||||
|
colorama==0.4.6
|
||||||
|
# via
|
||||||
|
# click
|
||||||
|
# colorlog
|
||||||
|
# commitizen
|
||||||
|
# pytest
|
||||||
|
# tqdm
|
||||||
|
colorlog==6.10.1
|
||||||
|
# via astrbot
|
||||||
|
commitizen==4.9.1
|
||||||
|
coverage==7.11.0
|
||||||
|
# via pytest-cov
|
||||||
|
cryptography==46.0.3
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# dashscope
|
||||||
|
cssselect==1.3.0
|
||||||
|
# via readability-lxml
|
||||||
|
dashscope==1.24.9
|
||||||
|
# via astrbot
|
||||||
|
decli==0.6.3
|
||||||
|
# via commitizen
|
||||||
|
defusedxml==0.7.1
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# markitdown-no-magika
|
||||||
|
deprecated==1.3.1
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# commitizen
|
||||||
|
dingtalk-stream==0.24.3
|
||||||
|
# via astrbot
|
||||||
|
distro==1.9.0
|
||||||
|
# via
|
||||||
|
# anthropic
|
||||||
|
# openai
|
||||||
|
docstring-parser==0.17.0
|
||||||
|
# via
|
||||||
|
# anthropic
|
||||||
|
# astrbot
|
||||||
|
et-xmlfile==2.0.0
|
||||||
|
# via openpyxl
|
||||||
|
exceptiongroup==1.3.0 ; python_full_version < '3.11'
|
||||||
|
# via
|
||||||
|
# anyio
|
||||||
|
# hypercorn
|
||||||
|
# pytest
|
||||||
|
# taskgroup
|
||||||
faiss-cpu==1.10.0
|
faiss-cpu==1.10.0
|
||||||
filelock>=3.18.0
|
# via astrbot
|
||||||
google-genai>=1.14.0
|
filelock==3.20.0
|
||||||
lark-oapi>=1.4.15
|
# via astrbot
|
||||||
lxml-html-clean>=0.4.2
|
flask==3.1.2
|
||||||
mcp>=1.8.0
|
# via quart
|
||||||
openai>=1.78.0
|
frozenlist==1.8.0
|
||||||
ormsgpack>=1.9.1
|
# via
|
||||||
pillow>=11.2.1
|
# aiohttp
|
||||||
pip>=25.1.1
|
# aiosignal
|
||||||
psutil>=5.8.0
|
google-auth==2.42.1
|
||||||
py-cord>=2.6.1
|
# via google-genai
|
||||||
pydantic~=2.10.3
|
google-genai==1.47.0
|
||||||
pydub>=0.25.1
|
# via astrbot
|
||||||
pyjwt>=2.10.1
|
greenlet==3.2.4
|
||||||
python-telegram-bot>=22.0
|
# via sqlalchemy
|
||||||
qq-botpy>=1.2.1
|
h11==0.16.0
|
||||||
quart>=0.20.0
|
# via
|
||||||
readability-lxml>=0.8.4.1
|
# httpcore
|
||||||
silk-python>=0.2.6
|
# hypercorn
|
||||||
slack-sdk>=3.35.0
|
# uvicorn
|
||||||
sqlalchemy[asyncio]>=2.0.41
|
# wsproto
|
||||||
sqlmodel>=0.0.24
|
h2==4.3.0
|
||||||
telegramify-markdown>=0.5.1
|
# via hypercorn
|
||||||
watchfiles>=1.0.5
|
hpack==4.1.0
|
||||||
websockets>=15.0.1
|
# via h2
|
||||||
wechatpy>=1.8.18
|
httpcore==1.0.9
|
||||||
audioop-lts ; python_full_version >= '3.13'
|
# via httpx
|
||||||
click>=8.2.1
|
httpx==0.28.1
|
||||||
pypdf>=6.1.1
|
# via
|
||||||
aiofiles>=25.1.0
|
# aiocqhttp
|
||||||
rank-bm25>=0.2.2
|
# anthropic
|
||||||
jieba>=0.42.1
|
# google-genai
|
||||||
markitdown-no-magika[docx,xls,xlsx]>=0.1.2
|
# lark-oapi
|
||||||
xinference-client
|
# mcp
|
||||||
|
# openai
|
||||||
|
# python-telegram-bot
|
||||||
|
httpx-sse==0.4.3
|
||||||
|
# via mcp
|
||||||
|
hypercorn==0.17.3
|
||||||
|
# via quart
|
||||||
|
hyperframe==6.1.0
|
||||||
|
# via h2
|
||||||
|
idna==3.11
|
||||||
|
# via
|
||||||
|
# anyio
|
||||||
|
# httpx
|
||||||
|
# requests
|
||||||
|
# yarl
|
||||||
|
iniconfig==2.3.0
|
||||||
|
# via pytest
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
# via
|
||||||
|
# flask
|
||||||
|
# quart
|
||||||
|
jieba==0.42.1
|
||||||
|
# via astrbot
|
||||||
|
jinja2==3.1.6
|
||||||
|
# via
|
||||||
|
# commitizen
|
||||||
|
# flask
|
||||||
|
# quart
|
||||||
|
jiter==0.11.1
|
||||||
|
# via
|
||||||
|
# anthropic
|
||||||
|
# openai
|
||||||
|
jsonschema==4.25.1
|
||||||
|
# via mcp
|
||||||
|
jsonschema-specifications==2025.9.1
|
||||||
|
# via jsonschema
|
||||||
|
lark-oapi==1.4.23
|
||||||
|
# via astrbot
|
||||||
|
lxml==6.0.2
|
||||||
|
# via
|
||||||
|
# lxml-html-clean
|
||||||
|
# markitdown-no-magika
|
||||||
|
# readability-lxml
|
||||||
|
lxml-html-clean==0.4.3
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# lxml
|
||||||
|
# readability-lxml
|
||||||
|
mammoth==1.11.0
|
||||||
|
# via markitdown-no-magika
|
||||||
|
markdownify==1.2.0
|
||||||
|
# via markitdown-no-magika
|
||||||
|
markitdown-no-magika==0.1.2
|
||||||
|
# via astrbot
|
||||||
|
markupsafe==3.0.3
|
||||||
|
# via
|
||||||
|
# flask
|
||||||
|
# jinja2
|
||||||
|
# quart
|
||||||
|
# werkzeug
|
||||||
|
mcp==1.12.4
|
||||||
|
# via astrbot
|
||||||
|
mistletoe==1.4.0
|
||||||
|
# via telegramify-markdown
|
||||||
|
multidict==6.7.0
|
||||||
|
# via
|
||||||
|
# aiohttp
|
||||||
|
# yarl
|
||||||
|
numpy==2.2.6 ; python_full_version < '3.11'
|
||||||
|
# via
|
||||||
|
# faiss-cpu
|
||||||
|
# pandas
|
||||||
|
# rank-bm25
|
||||||
|
numpy==2.3.4 ; python_full_version >= '3.11'
|
||||||
|
# via
|
||||||
|
# faiss-cpu
|
||||||
|
# pandas
|
||||||
|
# rank-bm25
|
||||||
|
openai==2.6.1
|
||||||
|
# via astrbot
|
||||||
|
openpyxl==3.1.5
|
||||||
|
# via markitdown-no-magika
|
||||||
|
optionaldict==0.1.2
|
||||||
|
# via wechatpy
|
||||||
|
ormsgpack==1.11.0
|
||||||
|
# via astrbot
|
||||||
|
packaging==25.0
|
||||||
|
# via
|
||||||
|
# commitizen
|
||||||
|
# faiss-cpu
|
||||||
|
# pytest
|
||||||
|
pandas==2.3.3
|
||||||
|
# via markitdown-no-magika
|
||||||
|
pillow==12.0.0
|
||||||
|
# via astrbot
|
||||||
|
pip==25.3
|
||||||
|
# via astrbot
|
||||||
|
pluggy==1.6.0
|
||||||
|
# via
|
||||||
|
# pytest
|
||||||
|
# pytest-cov
|
||||||
|
priority==2.0.0
|
||||||
|
# via hypercorn
|
||||||
|
prompt-toolkit==3.0.51
|
||||||
|
# via
|
||||||
|
# commitizen
|
||||||
|
# questionary
|
||||||
|
propcache==0.4.1
|
||||||
|
# via
|
||||||
|
# aiohttp
|
||||||
|
# yarl
|
||||||
|
psutil==7.1.2
|
||||||
|
# via astrbot
|
||||||
|
py-cord==2.6.1
|
||||||
|
# via astrbot
|
||||||
|
pyasn1==0.6.1
|
||||||
|
# via
|
||||||
|
# pyasn1-modules
|
||||||
|
# rsa
|
||||||
|
pyasn1-modules==0.4.2
|
||||||
|
# via google-auth
|
||||||
|
pycparser==2.23 ; implementation_name != 'PyPy'
|
||||||
|
# via cffi
|
||||||
|
pycryptodome==3.23.0
|
||||||
|
# via lark-oapi
|
||||||
|
pydantic==2.10.6
|
||||||
|
# via
|
||||||
|
# anthropic
|
||||||
|
# astrbot
|
||||||
|
# google-genai
|
||||||
|
# mcp
|
||||||
|
# openai
|
||||||
|
# pydantic-settings
|
||||||
|
# sqlmodel
|
||||||
|
# xinference-client
|
||||||
|
pydantic-core==2.27.2
|
||||||
|
# via pydantic
|
||||||
|
pydantic-settings==2.11.0
|
||||||
|
# via mcp
|
||||||
|
pydub==0.25.1
|
||||||
|
# via astrbot
|
||||||
|
pygments==2.19.2
|
||||||
|
# via pytest
|
||||||
|
pyjwt==2.10.1
|
||||||
|
# via astrbot
|
||||||
|
pypdf==6.1.3
|
||||||
|
# via astrbot
|
||||||
|
pytest==8.4.2
|
||||||
|
# via
|
||||||
|
# pytest-asyncio
|
||||||
|
# pytest-cov
|
||||||
|
pytest-asyncio==1.2.0
|
||||||
|
pytest-cov==7.0.0
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
# via
|
||||||
|
# pandas
|
||||||
|
# wechatpy
|
||||||
|
python-dotenv==1.2.1
|
||||||
|
# via pydantic-settings
|
||||||
|
python-multipart==0.0.20
|
||||||
|
# via mcp
|
||||||
|
python-telegram-bot==22.5
|
||||||
|
# via astrbot
|
||||||
|
pytz==2025.2
|
||||||
|
# via pandas
|
||||||
|
pywin32==311 ; sys_platform == 'win32'
|
||||||
|
# via mcp
|
||||||
|
pyyaml==6.0.3
|
||||||
|
# via
|
||||||
|
# commitizen
|
||||||
|
# qq-botpy
|
||||||
|
qq-botpy==1.2.1
|
||||||
|
# via astrbot
|
||||||
|
quart==0.20.0
|
||||||
|
# via
|
||||||
|
# aiocqhttp
|
||||||
|
# astrbot
|
||||||
|
questionary==2.1.1
|
||||||
|
# via commitizen
|
||||||
|
rank-bm25==0.2.2
|
||||||
|
# via astrbot
|
||||||
|
readability-lxml==0.8.4.1
|
||||||
|
# via astrbot
|
||||||
|
referencing==0.37.0
|
||||||
|
# via
|
||||||
|
# jsonschema
|
||||||
|
# jsonschema-specifications
|
||||||
|
requests==2.32.5
|
||||||
|
# via
|
||||||
|
# dashscope
|
||||||
|
# dingtalk-stream
|
||||||
|
# google-genai
|
||||||
|
# lark-oapi
|
||||||
|
# markitdown-no-magika
|
||||||
|
# requests-toolbelt
|
||||||
|
# wechatpy
|
||||||
|
# xinference-client
|
||||||
|
requests-toolbelt==1.0.0
|
||||||
|
# via lark-oapi
|
||||||
|
rpds-py==0.28.0
|
||||||
|
# via
|
||||||
|
# jsonschema
|
||||||
|
# referencing
|
||||||
|
rsa==4.9.1
|
||||||
|
# via google-auth
|
||||||
|
ruff==0.14.3
|
||||||
|
silk-python==0.2.7
|
||||||
|
# via astrbot
|
||||||
|
six==1.17.0
|
||||||
|
# via
|
||||||
|
# markdownify
|
||||||
|
# python-dateutil
|
||||||
|
# wechatpy
|
||||||
|
slack-sdk==3.37.0
|
||||||
|
# via astrbot
|
||||||
|
sniffio==1.3.1
|
||||||
|
# via
|
||||||
|
# anthropic
|
||||||
|
# anyio
|
||||||
|
# openai
|
||||||
|
soupsieve==2.8
|
||||||
|
# via beautifulsoup4
|
||||||
|
sqlalchemy==2.0.44
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# sqlmodel
|
||||||
|
sqlmodel==0.0.27
|
||||||
|
# via astrbot
|
||||||
|
sse-starlette==3.0.3
|
||||||
|
# via mcp
|
||||||
|
starlette==0.50.0
|
||||||
|
# via mcp
|
||||||
|
taskgroup==0.2.2 ; python_full_version < '3.11'
|
||||||
|
# via hypercorn
|
||||||
|
telegramify-markdown==0.5.2
|
||||||
|
# via astrbot
|
||||||
|
tenacity==9.1.2
|
||||||
|
# via google-genai
|
||||||
|
termcolor==3.2.0
|
||||||
|
# via commitizen
|
||||||
|
tomli==2.3.0 ; python_full_version <= '3.11'
|
||||||
|
# via
|
||||||
|
# coverage
|
||||||
|
# hypercorn
|
||||||
|
# pytest
|
||||||
|
tomlkit==0.13.3
|
||||||
|
# via commitizen
|
||||||
|
tqdm==4.67.1
|
||||||
|
# via openai
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
# via
|
||||||
|
# aiosignal
|
||||||
|
# aiosqlite
|
||||||
|
# anthropic
|
||||||
|
# anyio
|
||||||
|
# beautifulsoup4
|
||||||
|
# commitizen
|
||||||
|
# cryptography
|
||||||
|
# exceptiongroup
|
||||||
|
# google-genai
|
||||||
|
# hypercorn
|
||||||
|
# multidict
|
||||||
|
# openai
|
||||||
|
# py-cord
|
||||||
|
# pydantic
|
||||||
|
# pydantic-core
|
||||||
|
# pypdf
|
||||||
|
# pytest-asyncio
|
||||||
|
# referencing
|
||||||
|
# sqlalchemy
|
||||||
|
# starlette
|
||||||
|
# taskgroup
|
||||||
|
# typing-inspection
|
||||||
|
# uvicorn
|
||||||
|
# xinference-client
|
||||||
|
typing-inspection==0.4.2
|
||||||
|
# via pydantic-settings
|
||||||
|
tzdata==2025.2
|
||||||
|
# via
|
||||||
|
# pandas
|
||||||
|
# tzlocal
|
||||||
|
tzlocal==5.3.1
|
||||||
|
# via apscheduler
|
||||||
|
urllib3==2.5.0
|
||||||
|
# via requests
|
||||||
|
uvicorn==0.38.0 ; sys_platform != 'emscripten'
|
||||||
|
# via mcp
|
||||||
|
uvloop==0.22.1 ; sys_platform == 'linux'
|
||||||
|
# via astrbot
|
||||||
|
watchfiles==1.1.1
|
||||||
|
# via astrbot
|
||||||
|
wcwidth==0.2.14
|
||||||
|
# via prompt-toolkit
|
||||||
|
websocket-client==1.9.0
|
||||||
|
# via dashscope
|
||||||
|
websockets==15.0.1
|
||||||
|
# via
|
||||||
|
# astrbot
|
||||||
|
# dingtalk-stream
|
||||||
|
# google-genai
|
||||||
|
# lark-oapi
|
||||||
|
wechatpy==1.8.18
|
||||||
|
# via astrbot
|
||||||
|
werkzeug==3.1.3
|
||||||
|
# via
|
||||||
|
# flask
|
||||||
|
# quart
|
||||||
|
wrapt==2.0.0
|
||||||
|
# via deprecated
|
||||||
|
wsproto==1.2.0
|
||||||
|
# via hypercorn
|
||||||
|
xinference-client==1.11.0.post1
|
||||||
|
# via astrbot
|
||||||
|
xlrd==2.0.2
|
||||||
|
# via markitdown-no-magika
|
||||||
|
xmltodict==1.0.2
|
||||||
|
# via wechatpy
|
||||||
|
yarl==1.22.0
|
||||||
|
# via aiohttp
|
||||||
|
|||||||
Reference in New Issue
Block a user