From fc28f34ec68d7d6ff47e972970830cc62837a006 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 27 Nov 2024 21:41:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20metrics=20=E9=87=87=E7=94=A8=20Tickstat?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/api/__init__.py | 2 + astrbot/core/core_lifecycle.py | 5 +- astrbot/core/message_event_handler.py | 1 - astrbot/core/platform/astr_message_event.py | 6 +- astrbot/core/platform/platform.py | 6 +- astrbot/core/utils/metrics.py | 75 +++++-------------- .../aiocqhttp_message_event.py | 3 +- .../aiocqhttp_platform_adapter.py | 5 +- .../qqofficial_message_event.py | 1 + packages/astrbot_plugin_openai/main.py | 5 +- .../astrbot_plugin_openai/openai_adapter.py | 12 +-- 11 files changed, 44 insertions(+), 77 deletions(-) diff --git a/astrbot/api/__init__.py b/astrbot/api/__init__.py index 25aa68641..783ff9fff 100644 --- a/astrbot/api/__init__.py +++ b/astrbot/api/__init__.py @@ -12,4 +12,6 @@ from astrbot.core.utils.command_parser import CommandParser, CommandTokens from astrbot.core.utils.func_call import FuncCall from astrbot.core import html_renderer +from astrbot.core.plugin.config import * + command_parser = CommandParser() \ No newline at end of file diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py index 350ab0b61..9fdb32420 100644 --- a/astrbot/core/core_lifecycle.py +++ b/astrbot/core/core_lifecycle.py @@ -6,7 +6,6 @@ from core.config.astrbot_config import AstrBotConfig from core.message_event_handler import MessageEventHandler from core.plugin import PluginManager from core import LogBroker -from core.utils.metrics import MetricUploader from core.db import BaseDatabase from core.updator import AstrBotUpdator from core import logger @@ -22,7 +21,6 @@ class AstrBotCoreLifecycle: self.event_queue.closed = False self.plugin_manager = PluginManager(self.astrbot_config, self.event_queue, db) self.message_event_handler = MessageEventHandler(self.astrbot_config, self.plugin_manager) - self.metrics_uploader = MetricUploader(db) self.astrbot_updator = AstrBotUpdator(self.astrbot_config.plugin_repo_mirror) self.event_bus = EventBus(self.event_queue, self.message_event_handler) self.stop_flag = False @@ -35,9 +33,8 @@ class AstrBotCoreLifecycle: platform_tasks = self.load_platform() event_bus_task = asyncio.create_task(self.event_bus.dispatch(), name="event_bus") - metrics_uploader_task = asyncio.create_task(self.metrics_uploader.upload_metrics(), name="metrics") - self.curr_tasks = [event_bus_task, metrics_uploader_task, *platform_tasks] + self.curr_tasks = [event_bus_task, *platform_tasks] self.start_time = int(time.time()) async def start(self): diff --git a/astrbot/core/message_event_handler.py b/astrbot/core/message_event_handler.py index 7eda2039d..75e7e2cb1 100644 --- a/astrbot/core/message_event_handler.py +++ b/astrbot/core/message_event_handler.py @@ -6,7 +6,6 @@ from .platform import AstrMessageEvent from .config.astrbot_config import AstrBotConfig from .message_event_result import MessageEventResult, CommandResult, MessageChain from .plugin import PluginManager, Context, CommandMetadata -from .provider import Provider from nakuru.entities.components import * from core import logger from core import html_renderer diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py index 28ca834c1..84803523e 100644 --- a/astrbot/core/platform/astr_message_event.py +++ b/astrbot/core/platform/astr_message_event.py @@ -6,6 +6,7 @@ from core.message_event_result import MessageEventResult, MessageChain from core.platform.message_type import MessageType from typing import List from nakuru.entities.components import BaseMessageComponent, Plain, Image +from core.utils.metrics import Metric @dataclass class MessageSesion: @@ -134,9 +135,8 @@ class AstrMessageEvent(abc.ABC): ''' return self.is_wake - @abc.abstractmethod async def send(self, message: MessageChain): ''' - 发送消息。 + 发送消息到消息平台。 ''' - raise NotImplementedError() \ No newline at end of file + await Metric.upload(msg_event_tick = 1, adapter_name = self.platform_meta.name) \ No newline at end of file diff --git a/astrbot/core/platform/platform.py b/astrbot/core/platform/platform.py index 01d767e31..d8214e01a 100644 --- a/astrbot/core/platform/platform.py +++ b/astrbot/core/platform/platform.py @@ -5,6 +5,7 @@ from .platform_metadata import PlatformMetadata from .astr_message_event import AstrMessageEvent from core.message_event_result import MessageChain from .astr_message_event import MessageSesion +from core.utils.metrics import Metric class Platform(abc.ABC): def __init__(self, event_queue: Queue): @@ -26,14 +27,13 @@ class Platform(abc.ABC): ''' raise NotImplementedError - @abc.abstractmethod async def send_by_session(self, session: MessageSesion, message_chain: MessageChain) -> Awaitable[Any]: ''' 通过会话发送消息。该方法旨在让插件能够直接通过**可持久化的会话数据**发送消息,而不需要保存 event 对象。 异步方法。 - ''' - raise NotImplementedError + ''' + await Metric.upload(msg_event_tick = 1, adapter_name = self.meta().name) def commit_event(self, event: AstrMessageEvent): ''' diff --git a/astrbot/core/utils/metrics.py b/astrbot/core/utils/metrics.py index 38cb4a4ef..939010bd3 100644 --- a/astrbot/core/utils/metrics.py +++ b/astrbot/core/utils/metrics.py @@ -1,66 +1,29 @@ -import asyncio import aiohttp -import json import sys import logging - -from core.db import BaseDatabase -from collections import defaultdict from core.config import VERSION logger = logging.getLogger("astrbot") -class MetricUploader(): - def __init__(self, db_helper: BaseDatabase) -> None: - self.platform_stats = {} - self.llm_stats = defaultdict(int) - self.command_stats = defaultdict(int) - self.db_helper = db_helper - - async def upload_metrics(self): +class Metric(): + @staticmethod + async def upload(**kwargs): ''' 上传相关非敏感的指标以更好地了解 AstrBot 的使用情况。上传的指标不会包含任何有关消息文本、用户信息等敏感信息。 - - 这些数据包含: - - AstrBot 版本 - - OS 版本 - - 平台消息数量 - - LLM 模型名称、调用次数 + + Powered by TickStats. ''' - await asyncio.sleep(30) - while True: - res = { - "stat_version": "moon", - "version": VERSION, # 版本号 - "platform_stats": self.platform_stats, # 过去 30 分钟各消息平台交互消息数 - "llm_stats": self.llm_stats, - "command_stats": self.command_stats, - "sys": sys.platform, # 系统版本 - "plugin_stats": None, - } - - try: - self.db_helper.insert_base_metrics(res) - except BaseException as e: - logger.debug("指标数据保存到数据库失败: " + str(e)) - await asyncio.sleep(30*60) - continue - - try: - async with aiohttp.ClientSession() as session: - async with session.post('https://api.soulter.top/upload', data=json.dumps(res), timeout=10) as _: - pass - except BaseException as e: - pass - - self.clear() - await asyncio.sleep(30*60) - - def increment_platform_stat(self, platform_name: str): - self.platform_stats[platform_name] = self.platform_stats.get( - platform_name, 0) + 1 - - def clear(self): - self.platform_stats.clear() - self.llm_stats.clear() - self.command_stats.clear() + base_url = "https://tickstats.soulter.top/api/metric/90a6c2a1" + kwargs["v"] = VERSION + kwargs["os"] = sys.platform + payload = { + "metrics_data": kwargs + } + try: + async with aiohttp.ClientSession() as session: + logger.debug(f"Uploading metric: {kwargs}") + async with session.post(base_url, json=payload, timeout=3) as response: + if response.status != 200: + logger.error(f"Failed to upload metric: {response.status} {response.reason}") + except Exception as e: + logger.error(f"Failed to upload metric: {e}") \ No newline at end of file diff --git a/packages/astrbot_adapter_aiocqhttp/aiocqhttp_message_event.py b/packages/astrbot_adapter_aiocqhttp/aiocqhttp_message_event.py index 237619889..84943900c 100644 --- a/packages/astrbot_adapter_aiocqhttp/aiocqhttp_message_event.py +++ b/packages/astrbot_adapter_aiocqhttp/aiocqhttp_message_event.py @@ -34,4 +34,5 @@ class AiocqhttpMessageEvent(AstrMessageEvent): ret = await AiocqhttpMessageEvent._parse_onebot_josn(message) if os.environ.get('TEST_MODE', 'off') == 'on': return - await self.bot.send(self.message_obj.raw_message, ret) \ No newline at end of file + await self.bot.send(self.message_obj.raw_message, ret) + await super().send(message) \ No newline at end of file diff --git a/packages/astrbot_adapter_aiocqhttp/aiocqhttp_platform_adapter.py b/packages/astrbot_adapter_aiocqhttp/aiocqhttp_platform_adapter.py index 81491c3d4..d5826b467 100644 --- a/packages/astrbot_adapter_aiocqhttp/aiocqhttp_platform_adapter.py +++ b/packages/astrbot_adapter_aiocqhttp/aiocqhttp_platform_adapter.py @@ -36,10 +36,11 @@ class AiocqhttpAdapter(Platform): # 独立会话 _, group_id = session.session_id.split("_") await self.bot.send_group_msg(group_id=group_id, message=ret) - return - await self.bot.send_group_msg(group_id=session.session_id, message=ret) + else: + await self.bot.send_group_msg(group_id=session.session_id, message=ret) case MessageType.FRIEND_MESSAGE.value: await self.bot.send_private_msg(user_id=session.session_id, message=ret) + await super().send_by_session(session, message_chain) def convert_message(self, event: Event) -> AstrBotMessage: abm = AstrBotMessage() diff --git a/packages/astrbot_adapter_qqofficial/qqofficial_message_event.py b/packages/astrbot_adapter_qqofficial/qqofficial_message_event.py index b9346cf7b..48e2826e0 100644 --- a/packages/astrbot_adapter_qqofficial/qqofficial_message_event.py +++ b/packages/astrbot_adapter_qqofficial/qqofficial_message_event.py @@ -45,6 +45,7 @@ class QQOfficialMessageEvent(AstrMessageEvent): payload['file_image'] = image_path await self.bot.api.post_dms(guild_id=source.guild_id, **payload) + await super().send(message) async def upload_group_and_c2c_image(self, image_base64: str, file_type: int, **kwargs) -> botpy.types.message.Media: payload = { diff --git a/packages/astrbot_plugin_openai/main.py b/packages/astrbot_plugin_openai/main.py index 026d82a9c..5dd245992 100644 --- a/packages/astrbot_plugin_openai/main.py +++ b/packages/astrbot_plugin_openai/main.py @@ -10,6 +10,7 @@ from openai._exceptions import * from openai.types.chat.chat_completion_message_tool_call import Function from astrbot.api import command_parser from .web_searcher import search_from_bing, fetch_website_content +from astrbot.core.utils.metrics import Metric class Main: def __init__(self, context: Context) -> None: @@ -128,7 +129,7 @@ class Main: session_id=event.session_id, tools=self.context.llm_tools.get_func() ) - # self.context.metrics_uploader.llm_stats[provider.get_curr_model()] += 1 + await Metric.upload(llm_tick=1, llm_name=self.provider.get_model(), llm_api_base=self.provider.base_url) if isinstance(llm_result, Function): logger.debug(f"function-calling: {llm_result}") @@ -176,7 +177,7 @@ class Main: session_id=event.session_id, image_url=image_url ) - # self.context.metrics_uploader.llm_stats[provider.get_curr_model()] += 1 + await Metric.upload(llm_tick=1, llm_name=self.provider.get_model(), llm_api_base=self.provider.base_url) except BadRequestError as e: if tool_use_flag: # seems like the model don't support function-calling diff --git a/packages/astrbot_plugin_openai/openai_adapter.py b/packages/astrbot_plugin_openai/openai_adapter.py index 4488acd2b..7f06fb98c 100644 --- a/packages/astrbot_plugin_openai/openai_adapter.py +++ b/packages/astrbot_plugin_openai/openai_adapter.py @@ -84,23 +84,23 @@ class ProviderOpenAIOfficial(Provider): finally: time.sleep(10*60) - def personality_set(self, default_personality: dict, session_id: str): - if not default_personality: return + def personality_set(self, personality: dict, session_id: str): + if not personality or not personality['prompt']: return if session_id not in self.session_memory: self.session_memory[session_id] = [] - self.curr_personality = default_personality + self.curr_personality = personality self.session_personality = {} # 重置 new_record = { "user": { "role": "system", - "content": default_personality['prompt'], + "content": personality['prompt'], }, 'usage_tokens': 0, # 到该条目的总 token 数 'single-tokens': 0 # 该条目的 token 数 } - self.session_memory[session_id].append(new_record) + self.session_memory[session_id] = [new_record] async def encode_image_bs64(self, image_url: str) -> str: ''' @@ -238,6 +238,8 @@ class ProviderOpenAIOfficial(Provider): # 获取上下文,openai 格式 contexts = await self.retrieve_context(session_id) + + logger.debug(f"OpenAI 请求上下文:{contexts}") conf = asdict(self.llm_config.model_config)