From 96e7a938860c908611abf2cc77d74af3c181ca87 Mon Sep 17 00:00:00 2001 From: Rt39 Date: Thu, 20 Feb 2025 19:59:16 -0500 Subject: [PATCH 01/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9Claude=20API=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + astrbot/core/provider/func_tool_manager.py | 23 +++ astrbot/core/provider/manager.py | 2 + .../core/provider/sources/anthropic_source.py | 186 ++++++++++++++++++ requirements.txt | 1 + 5 files changed, 214 insertions(+) create mode 100644 astrbot/core/provider/sources/anthropic_source.py diff --git a/.gitignore b/.gitignore index 7745d18ba..1e7248a74 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ package.json venv/* packages/python_interpreter/workplace .venv/* + +.conda/ \ No newline at end of file diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index b82ec6db7..8ddc21b75 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -102,6 +102,29 @@ class FuncCall: ) return _l + def get_func_desc_anthropic_style(self) -> list: + """ + 获得 Anthropic API 风格的**已经激活**的工具描述 + """ + tools = [] + for f in self.func_list: + if not f.active: + continue + + # Convert internal format to Anthropic style + tool = { + "name": f.name, + "description": f.description, + "input_schema": { + "type": "object", + "properties": f.parameters.get("properties", {}), + # Keep the required field from the original parameters if it exists + "required": f.parameters.get("required", []) + } + } + tools.append(tool) + return tools + def get_func_desc_google_genai_style(self) -> Dict: declarations = {} tools = [] diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 2ba108e29..db8569b83 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -117,6 +117,8 @@ class ProviderManager(): from .sources.openai_source import ProviderOpenAIOfficial as ProviderOpenAIOfficial case "zhipu_chat_completion": from .sources.zhipu_source import ProviderZhipu as ProviderZhipu + case "anthropic_chat_completion": + from .sources.anthropic_source import ProviderAnthropic as ProviderAnthropic case "llm_tuner": logger.info("加载 LLM Tuner 工具 ...") from .sources.llmtuner_source import LLMTunerModelLoader as LLMTunerModelLoader diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py new file mode 100644 index 000000000..2685a6818 --- /dev/null +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -0,0 +1,186 @@ +import json +import os +import base64 +from typing import List +from mimetypes import guess_type + +from anthropic import AsyncAnthropic +from anthropic.types import Message + +from astrbot.core.utils.io import download_image_by_url +from astrbot.core.db import BaseDatabase +from astrbot.api.provider import Provider, Personality +from astrbot import logger +from astrbot.core.provider.func_tool_manager import FuncCall +from ..register import register_provider_adapter +from astrbot.core.provider.entites import LLMResponse +from .openai_source import ProviderOpenAIOfficial + +@register_provider_adapter("anthropic_chat_completion", "Anthropic Claude API 提供商适配器") +class ProviderAnthropic(ProviderOpenAIOfficial): + def __init__( + self, + provider_config: dict, + provider_settings: dict, + db_helper: BaseDatabase, + persistant_history = True, + default_persona: Personality = None + ) -> None: + # Skip OpenAI's __init__ and call Provider's __init__ directly + Provider.__init__(self, provider_config, provider_settings, persistant_history, db_helper, default_persona) + + self.chosen_api_key = None + self.api_keys: List = provider_config.get("key", []) + self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None + self.timeout = provider_config.get("timeout", 120) + if isinstance(self.timeout, str): + self.timeout = int(self.timeout) + + self.client = AsyncAnthropic( + api_key=self.chosen_api_key, + timeout=self.timeout + ) + + self.set_model(provider_config['model_config']['model']) + + async def _query(self, payloads: dict, tools: FuncCall) -> LLMResponse: + if tools: + tool_list = tools.get_func_desc_anthropic_style() + if tool_list: + payloads['tools'] = tool_list + + completion = await self.client.messages.create( + **payloads, + stream=False + ) + + assert isinstance(completion, Message) + logger.debug(f"completion: {completion}") + + if len(completion.content) == 0: + raise Exception("API 返回的 completion 为空。") + # TODO: 如果进行函数调用,思维链被截断,用户可能需要思维链的内容 + # 选最后一条消息,如果要进行函数调用,anthropic会先返回文本消息的思维链,然后再返回函数调用请求 + content = completion.content[-1] + + llm_response = LLMResponse("assistant") + + if content.type == "text": + # text completion + completion_text = str(content.text).strip() + llm_response.completion_text = completion_text + + # Anthropic每次只返回一个函数调用 + if completion.stop_reason == "tool_use": + # tools call (function calling) + args_ls = [] + func_name_ls = [] + func_name_ls.append(content.name) + args_ls.append(content.input) + llm_response.role = "tool" + llm_response.tools_call_args = args_ls + llm_response.tools_call_name = func_name_ls + + if not llm_response.completion_text and not llm_response.tools_call_args: + logger.error(f"API 返回的 completion 无法解析:{completion}。") + raise Exception(f"API 返回的 completion 无法解析:{completion}。") + + llm_response.raw_completion = completion + + return llm_response + + async def text_chat( + self, + prompt: str, + session_id: str=None, + image_urls: List[str]=[], + func_tool: FuncCall=None, + contexts=[], + system_prompt=None, + **kwargs + ) -> LLMResponse: + new_record = await self.assemble_context(prompt, image_urls) + context_query = [*contexts, new_record] + + for part in context_query: + if '_no_save' in part: + del part['_no_save'] + + model_config = self.provider_config.get("model_config", {}) + + payloads = { + "messages": context_query, + **model_config + } + # Anthropic has a different way of handling system prompts + if system_prompt: + payloads['system'] = system_prompt + llm_response = None + try: + print(payloads) + llm_response = await self._query(payloads, func_tool) + + except Exception as e: + if "maximum context length" in str(e): + retry_cnt = 20 + while retry_cnt > 0: + logger.warning(f"上下文长度超过限制。尝试弹出最早的记录然后重试。当前记录条数: {len(context_query)}") + try: + await self.pop_record(context_query) + response = await self.client.messages.create( + messages=context_query, + **model_config + ) + llm_response = LLMResponse("assistant") + llm_response.completion_text = response.content[0].text + llm_response.raw_completion = response + return llm_response + except Exception as e: + if "maximum context length" in str(e): + retry_cnt -= 1 + else: + raise e + return LLMResponse("err", "err: 请尝试 /reset 清除会话记录。") + else: + logger.error(f"发生了错误。Provider 配置如下: {model_config}") + raise e + + return llm_response + + async def assemble_context(self, text: str, image_urls: List[str] = None): + '''组装上下文,支持文本和图片''' + if not image_urls: + return {"role": "user", "content": text} + + content = [] + content.append({"type": "text", "text": text}) + + for image_url in image_urls: + if image_url.startswith("http"): + image_path = await download_image_by_url(image_url) + image_data = await self.encode_image_bs64(image_path) + elif image_url.startswith("file:///"): + image_path = image_url.replace("file:///", "") + image_data = await self.encode_image_bs64(image_path) + else: + image_data = await self.encode_image_bs64(image_url) + + if not image_data: + logger.warning(f"图片 {image_url} 得到的结果为空,将忽略。") + continue + + # Get mime type for the image + mime_type, _ = guess_type(image_url) + if not mime_type: + mime_type = "image/jpeg" # Default to JPEG if can't determine + + content.append({ + "type": "image", + "source": { + "type": "base64", + "media_type": mime_type, + "data": image_data.split("base64,")[1] if "base64," in image_data else image_data + } + }) + + return {"role": "user", "content": content} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c077496e2..e524df84f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ pydantic~=2.10.3 aiohttp openai +anthropic qq-botpy chardet~=5.1.0 Pillow From bf2c3a1a81fdc11034f6d29fa8441285b1ce536f Mon Sep 17 00:00:00 2001 From: Rt39 Date: Thu, 20 Feb 2025 21:15:07 -0500 Subject: [PATCH 02/51] =?UTF-8?q?fix:=20=E6=A0=B9=E6=8D=AECodacy=20Product?= =?UTF-8?q?ion=20/=20Codacy=20Static=20Code=20Analysis=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/anthropic_source.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index 2685a6818..c90d15af9 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -1,6 +1,3 @@ -import json -import os -import base64 from typing import List from mimetypes import guess_type @@ -19,10 +16,10 @@ from .openai_source import ProviderOpenAIOfficial @register_provider_adapter("anthropic_chat_completion", "Anthropic Claude API 提供商适配器") class ProviderAnthropic(ProviderOpenAIOfficial): def __init__( - self, - provider_config: dict, + self, + provider_config: dict, provider_settings: dict, - db_helper: BaseDatabase, + db_helper: BaseDatabase, persistant_history = True, default_persona: Personality = None ) -> None: @@ -92,9 +89,9 @@ class ProviderAnthropic(ProviderOpenAIOfficial): async def text_chat( self, prompt: str, - session_id: str=None, - image_urls: List[str]=[], - func_tool: FuncCall=None, + session_id: str = None, + image_urls: List[str] = [], + func_tool: FuncCall = None, contexts=[], system_prompt=None, **kwargs From 48ae686602fb22c28d82fb9d690c48f51db03f09 Mon Sep 17 00:00:00 2001 From: Rt39 Date: Thu, 20 Feb 2025 23:58:10 -0500 Subject: [PATCH 03/51] feat: add claude template --- astrbot/core/config/default.py | 12 ++++++++++++ astrbot/core/provider/sources/anthropic_source.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 7a6b68b3e..8378a9479 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -420,6 +420,18 @@ CONFIG_METADATA_2 = { "model": "grok-2-latest", }, }, + "claude": { + "id": "claude", + "type": "anthropic_chat_completion", + "enable": True, + "key": [], + "api_base": "https://api.anthropic.com/v1", + "timeout": 120, + "model_config": { + "model": "claude-3-5-sonnet-latest", + "max_tokens": 4096, + }, + }, "ollama": { "id": "ollama_default", "type": "openai_chat_completion", diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index c90d15af9..fc67bd085 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -25,7 +25,7 @@ class ProviderAnthropic(ProviderOpenAIOfficial): ) -> None: # Skip OpenAI's __init__ and call Provider's __init__ directly Provider.__init__(self, provider_config, provider_settings, persistant_history, db_helper, default_persona) - + self.chosen_api_key = None self.api_keys: List = provider_config.get("key", []) self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None From c122dad21fcd6e0fb02443315ba961c8f9ecce5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E6=B0=B8=E4=BA=AE?= <702625325@qq.com> Date: Fri, 21 Feb 2025 21:07:59 +0800 Subject: [PATCH 04/51] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89api=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 2 +- astrbot/core/provider/sources/anthropic_source.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 8378a9479..d680cf6c8 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -420,7 +420,7 @@ CONFIG_METADATA_2 = { "model": "grok-2-latest", }, }, - "claude": { + "anthropic(claude)": { "id": "claude", "type": "anthropic_chat_completion", "enable": True, diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index fc67bd085..0947e4d97 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -29,13 +29,15 @@ class ProviderAnthropic(ProviderOpenAIOfficial): self.chosen_api_key = None self.api_keys: List = provider_config.get("key", []) self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None + self.base_url = provider_config.get("api_base", "https://api.anthropic.com") self.timeout = provider_config.get("timeout", 120) if isinstance(self.timeout, str): self.timeout = int(self.timeout) self.client = AsyncAnthropic( api_key=self.chosen_api_key, - timeout=self.timeout + timeout=self.timeout, + base_url=self.base_url ) self.set_model(provider_config['model_config']['model']) @@ -114,7 +116,6 @@ class ProviderAnthropic(ProviderOpenAIOfficial): payloads['system'] = system_prompt llm_response = None try: - print(payloads) llm_response = await self._query(payloads, func_tool) except Exception as e: From b1049540a4d768e758dd1088f6289bfdff2d6883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E6=B0=B8=E4=BA=AE?= <702625325@qq.com> Date: Fri, 21 Feb 2025 22:26:31 +0800 Subject: [PATCH 05/51] =?UTF-8?q?feat:=20claude=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=BA=AF=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/anthropic_source.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index 0947e4d97..35200bf23 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -98,6 +98,10 @@ class ProviderAnthropic(ProviderOpenAIOfficial): system_prompt=None, **kwargs ) -> LLMResponse: + + if not prompt: + prompt = "" + new_record = await self.assemble_context(prompt, image_urls) context_query = [*contexts, new_record] @@ -114,6 +118,7 @@ class ProviderAnthropic(ProviderOpenAIOfficial): # Anthropic has a different way of handling system prompts if system_prompt: payloads['system'] = system_prompt + llm_response = None try: llm_response = await self._query(payloads, func_tool) From 14fb4b70bd1744aade064d40f095b6a36b6b1dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E6=B0=B8=E4=BA=AE?= <702625325@qq.com> Date: Fri, 21 Feb 2025 23:08:23 +0800 Subject: [PATCH 06/51] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20gewechat=20?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=AA=8C=E8=AF=81=E7=A0=81=20#448?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/sources/gewechat/client.py | 42 ++++++++++++++----- packages/astrbot/main.py | 8 ++++ packages/reminder/main.py | 6 +-- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index fe0bc6bfb..1ed0ab110 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -302,8 +302,21 @@ class SimpleGewechatClient(): "uuid": qr_uuid, "appId": appid }) + verify_flag = False while retry_cnt > 0: retry_cnt -= 1 + + # 需要验证码 + if verify_flag: + with open("data/temp/gewe_code", "r") as f: + code = f.read().strip() + if not code: + logger.warning("未找到验证码,请在管理面板聊天页输入 /gewe_code 验证码 来验证,如 /gewe_code 123456") + await asyncio.sleep(5) + continue + payload['captchCode'] = code + logger.info(f"使用验证码: {code}") + async with aiohttp.ClientSession() as session: async with session.post( f"{self.base_url}/login/checkLogin", @@ -312,17 +325,26 @@ class SimpleGewechatClient(): ) as resp: json_blob = await resp.json() logger.info(f"检查登录状态: {json_blob}") - status = json_blob['data']['status'] - nickname = json_blob['data'].get('nickName', '') - if status == 1: - logger.info(f"等待确认...{nickname}") - elif status == 2: - logger.info(f"绿泡泡平台登录成功: {nickname}") - break - elif status == 0: - logger.info("等待扫码...") + + ret = json_blob['ret'] + msg = '' + if json_blob['data'] and 'msg' in json_blob['data']: + msg = json_blob['data']['msg'] + if ret == 500 and '安全验证码' in msg: + logger.warning("此次登录需要安全验证码,请在管理面板聊天页输入 /gewe_code 验证码 来验证,如 /gewe_code 123456") + verify_flag = True else: - logger.warning(f"未知状态: {status}") + status = json_blob['data']['status'] + nickname = json_blob['data'].get('nickName', '') + if status == 1: + logger.info(f"等待确认...{nickname}") + elif status == 2: + logger.info(f"绿泡泡平台登录成功: {nickname}") + break + elif status == 0: + logger.info("等待扫码...") + else: + logger.warning(f"未知状态: {status}") await asyncio.sleep(5) if appid: diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index cd4811654..70500c60f 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -788,6 +788,14 @@ UID: {user_id} 此 ID 可用于设置管理员。/op 授权管理员, /deo yield event.plain_result("已登出 gewechat,请重启 AstrBot") return + + @filter.command("gewe_code") + async def gewe_code(self, event: AstrMessageEvent, code: str): + '''保存 gewechat 验证码''' + with open("data/temp/gewe_code", "w", encoding='utf-8') as f: + f.write(code) + yield event.plain_result("验证码已保存。") + @filter.platform_adapter_type(filter.PlatformAdapterType.ALL) async def on_message(self, event: AstrMessageEvent): '''群聊记忆增强''' diff --git a/packages/reminder/main.py b/packages/reminder/main.py index fa675033c..2df153a00 100644 --- a/packages/reminder/main.py +++ b/packages/reminder/main.py @@ -17,9 +17,9 @@ class Main(star.Star): # set and load config if not os.path.exists("data/astrbot-reminder.json"): - with open("data/astrbot-reminder.json", "w") as f: + with open("data/astrbot-reminder.json", "w", encoding='utf-8') as f: f.write("{}") - with open("data/astrbot-reminder.json", "r") as f: + with open("data/astrbot-reminder.json", "r", encoding='utf-8') as f: self.reminder_data = json.load(f) self._init_scheduler() @@ -64,7 +64,7 @@ class Main(star.Star): async def _save_data(self): '''Save the reminder data.''' - with open("data/astrbot-reminder.json", "w") as f: + with open("data/astrbot-reminder.json", "w", encoding='utf-8') as f: json.dump(self.reminder_data, f, ensure_ascii=False) def _parse_cron_expr(self, cron_expr: str): From 9f9da1e0c9a87181355c5ccb5767e4ed97023f3a Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Fri, 21 Feb 2025 23:39:53 +0800 Subject: [PATCH 07/51] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E4=BF=AE=E5=A4=8Dw?= =?UTF-8?q?ebchat=E6=9C=AA=E5=A4=84=E7=90=86base64=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/sources/webchat/webchat_event.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/astrbot/core/platform/sources/webchat/webchat_event.py b/astrbot/core/platform/sources/webchat/webchat_event.py index fe30151b2..afcda5e1c 100644 --- a/astrbot/core/platform/sources/webchat/webchat_event.py +++ b/astrbot/core/platform/sources/webchat/webchat_event.py @@ -1,5 +1,6 @@ import os import uuid +import base64 from astrbot.api import logger from astrbot.api.event import AstrMessageEvent, MessageChain from astrbot.api.message_components import Plain, Image @@ -31,6 +32,11 @@ class WebChatMessageEvent(AstrMessageEvent): with open(path, "wb") as f: with open(ph, "rb") as f2: f.write(f2.read()) + elif comp.file.startswith("base64://"): + base64_str = comp.file[9:] + image_data = base64.b64decode(base64_str) + with open(path, "wb") as f: + f.write(image_data) elif comp.file and comp.file.startswith("http"): await download_image_by_url(comp.file, path=path) else: From d00821d1c7403e8ebcf3aeb195a87b2309990e71 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Sat, 22 Feb 2025 10:07:18 +0800 Subject: [PATCH 08/51] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index bf4748809..9d84175f2 100644 --- a/README.md +++ b/README.md @@ -148,10 +148,6 @@ _✨ 内置 Web Chat,在线与机器人交互 ✨_ -## Sponsors - -[](https://api.gitsponsors.com/api/badge/link?p=XEpbdGxlitw/RbcwiTX93UMzNK/jgDYC8NiSzamIPMoKvG2lBFmyXhSS/b0hFoWlBBMX2L5X5CxTDsUdyvcIEHTOfnkXz47UNOZvMwyt5CzbYpq0SEzsSV1OJF1cCo90qC/ZyYKYOWedal3MhZ3ikw==) - ## Disclaimer 1. The project is protected under the `AGPL-v3` opensource license. From 35ba1b3345eeb1d680d5ab4c86cd6ae72ef623e1 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 22 Feb 2025 11:37:34 +0800 Subject: [PATCH 09/51] fix: gewechat verify code --- astrbot/core/platform/sources/gewechat/client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 1ed0ab110..23021f1c4 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -5,6 +5,7 @@ import quart import base64 import datetime import re +import os from astrbot.api.platform import AstrBotMessage, MessageMember, MessageType from astrbot.api.message_components import Plain, Image, At, Record from astrbot.api import logger, sp @@ -307,7 +308,7 @@ class SimpleGewechatClient(): retry_cnt -= 1 # 需要验证码 - if verify_flag: + if verify_flag or os.path.exists("data/temp/gewe_code"): with open("data/temp/gewe_code", "r") as f: code = f.read().strip() if not code: @@ -316,6 +317,10 @@ class SimpleGewechatClient(): continue payload['captchCode'] = code logger.info(f"使用验证码: {code}") + try: + os.remove("data/temp/gewe_code") + except: + logger.warning("删除验证码文件 data/temp/gewe_code 失败。") async with aiohttp.ClientSession() as session: async with session.post( From 36c0cfc9a94b6d38cfce00ebdd1a6a8e736cbf05 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 22 Feb 2025 14:08:51 +0800 Subject: [PATCH 10/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E7=99=BE=E7=82=BC=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E4=BD=93=E3=80=81=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=20#552?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 16 +++ astrbot/core/provider/manager.py | 2 + .../core/provider/sources/dashscope_source.py | 111 ++++++++++++++++++ requirements.txt | 4 +- 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 astrbot/core/provider/sources/dashscope_source.py diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index d680cf6c8..68c3146b5 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -529,6 +529,15 @@ CONFIG_METADATA_2 = { "dify_query_input_key": "astrbot_text_query", "timeout": 60, }, + "dashscope": { + "id": "dashscope", + "type": "dashscope", + "enable": True, + "dashscope_app_type": "agent", + "dashscope_api_key": "", + "dashscope_app_id": "", + "timeout": 60, + }, "whisper(API)": { "id": "whisper", "type": "openai_whisper_api", @@ -565,6 +574,13 @@ CONFIG_METADATA_2 = { }, }, "items": { + "dashscope_app_type": { + "description": "应用类型", + "type": "string", + "hint": "阿里云百炼应用的应用类型。", + "options": ["agent", "agent-arrange", "dialog-workflow", "task-workflow"], + "obvious_hint": True, + }, "timeout": { "description": "超时时间", "type": "int", diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index db8569b83..70a4af389 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -124,6 +124,8 @@ class ProviderManager(): from .sources.llmtuner_source import LLMTunerModelLoader as LLMTunerModelLoader case "dify": from .sources.dify_source import ProviderDify as ProviderDify + case "dashscope": + from .sources.dashscope_source import ProviderDashscope as ProviderDashscope case "googlegenai_chat_completion": from .sources.gemini_source import ProviderGoogleGenAI as ProviderGoogleGenAI case "openai_whisper_api": diff --git a/astrbot/core/provider/sources/dashscope_source.py b/astrbot/core/provider/sources/dashscope_source.py new file mode 100644 index 000000000..56ab79934 --- /dev/null +++ b/astrbot/core/provider/sources/dashscope_source.py @@ -0,0 +1,111 @@ +import asyncio +from typing import List +from .. import Provider, Personality +from ..entites import LLMResponse +from ..func_tool_manager import FuncCall +from astrbot.core.db import BaseDatabase +from ..register import register_provider_adapter +from .openai_source import ProviderOpenAIOfficial +from astrbot.core import logger, sp +from dashscope import Application + +@register_provider_adapter("dashscope", "Dashscope APP 适配器。") +class ProviderDashscope(ProviderOpenAIOfficial): + def __init__( + self, + provider_config: dict, + provider_settings: dict, + db_helper: BaseDatabase, + persistant_history=False, + default_persona: Personality=None + ) -> None: + Provider.__init__( + self, provider_config, provider_settings, persistant_history, db_helper, default_persona + ) + self.api_key = provider_config.get("dashscope_api_key", "") + if not self.api_key: + raise Exception("阿里云百炼 API Key 不能为空。") + self.app_id = provider_config.get("dashscope_app_id", "") + if not self.app_id: + raise Exception("阿里云百炼 APP ID 不能为空。") + self.dashscope_app_type = provider_config.get("dashscope_app_type", "") + if not self.dashscope_app_type: + raise Exception("阿里云百炼 APP 类型不能为空。") + self.model_name = "dashscope" + + self.timeout = provider_config.get("timeout", 120) + if isinstance(self.timeout, str): + self.timeout = int(self.timeout) + + async def text_chat( + self, + prompt: str, + session_id: str = None, + image_urls: List[str] = [], + func_tool: FuncCall = None, + contexts: List = None, + system_prompt: str = None, + **kwargs, + ) -> LLMResponse: + if self.dashscope_app_type in ["agent", "dialog-workflow"]: + # 支持多轮对话的 + new_record = {"role": "user", "content": prompt} + if image_urls: + logger.warning("阿里云百炼暂不支持图片输入,将自动忽略图片内容。") + contexts_no_img = await self._remove_image_from_context(contexts) + context_query = [*contexts_no_img, new_record] + if system_prompt: + context_query.insert(0, {"role": "system", "content": system_prompt}) + for part in context_query: + if '_no_save' in part: + del part['_no_save'] + # 调用阿里云百炼 API + response = await asyncio.get_event_loop().run_in_executor( + None, + Application.call, + self.app_id, + None, + None, + None, + self.api_key, + context_query + ) + else: + # 不支持多轮对话的 + # 调用阿里云百炼 API + response = await asyncio.get_event_loop().run_in_executor( + None, + Application.call, + self.app_id, + prompt, + None, + None, + self.api_key + ) + + logger.debug(f"dashscope resp: {response}") + + if response.status_code != 200: + logger.error(f"阿里云百炼请求失败: request_id={response.request_id}, code={response.status_code}, message={response.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code") + return LLMResponse(role="err", completion_text=f"阿里云百炼请求失败: message={response.message} code={response.status_code}") + + output_text = response.output.get("text", "") + return LLMResponse(role="assistant", completion_text=output_text) + + async def forget(self, session_id): + return True + + async def get_current_key(self): + return self.api_key + + async def set_key(self, key): + raise Exception("阿里云百炼 适配器不支持设置 API Key。") + + async def get_models(self): + return [self.get_model()] + + async def get_human_readable_context(self, session_id, page, page_size): + raise Exception("暂不支持获得 阿里云百炼 的历史消息记录。") + + async def terminate(self): + await self.api_client.close() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e524df84f..39abe351d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,4 +21,6 @@ silk-python lark-oapi ormsgpack -cryptography \ No newline at end of file +cryptography + +dashscope \ No newline at end of file From 466c80b94d173f0d05983fb0ee317777e939839b Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 22 Feb 2025 14:32:37 +0800 Subject: [PATCH 11/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91=E7=99=BE=E7=82=BC=E5=BA=94=E7=94=A8=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=8F=98=E9=87=8F=20#552?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process_stage/method/dify_request.py | 90 ------------------- .../process_stage/method/llm_request.py | 4 +- astrbot/core/pipeline/process_stage/stage.py | 13 +-- .../core/provider/sources/dashscope_source.py | 35 +++++--- packages/astrbot/main.py | 3 - 5 files changed, 26 insertions(+), 119 deletions(-) delete mode 100644 astrbot/core/pipeline/process_stage/method/dify_request.py diff --git a/astrbot/core/pipeline/process_stage/method/dify_request.py b/astrbot/core/pipeline/process_stage/method/dify_request.py deleted file mode 100644 index 3dca3792d..000000000 --- a/astrbot/core/pipeline/process_stage/method/dify_request.py +++ /dev/null @@ -1,90 +0,0 @@ -''' -Dify 调用 Stage -''' -import traceback -from typing import Union, AsyncGenerator -from ...context import PipelineContext -from ..stage import Stage -from astrbot.core.platform.astr_message_event import AstrMessageEvent -from astrbot.core.message.message_event_result import MessageEventResult, ResultContentType -from astrbot.core.message.components import Image -from astrbot.core import logger -from astrbot.core.utils.metrics import Metric -from astrbot.core.provider.entites import ProviderRequest -from astrbot.core.star.star_handler import star_handlers_registry, EventType - -class DifyRequestSubStage(Stage): - - async def initialize(self, ctx: PipelineContext) -> None: - self.ctx = ctx - - async def process(self, event: AstrMessageEvent) -> Union[None, AsyncGenerator[None, None]]: - req: ProviderRequest = None - - provider = self.ctx.plugin_manager.context.get_using_provider() - - if not provider: - return - - if provider.meta().type != "dify": - return - - if event.get_extra("provider_request"): - req = event.get_extra("provider_request") - assert isinstance(req, ProviderRequest), "provider_request 必须是 ProviderRequest 类型。" - else: - req = ProviderRequest(prompt="", image_urls=[]) - if self.ctx.astrbot_config['provider_settings']['wake_prefix']: - if not event.message_str.startswith(self.ctx.astrbot_config['provider_settings']['wake_prefix']): - return - req.prompt = event.message_str[len(self.ctx.astrbot_config['provider_settings']['wake_prefix']):] - for comp in event.message_obj.message: - if isinstance(comp, Image): - image_url = comp.url if comp.url else comp.file - req.image_urls.append(image_url) - req.session_id = event.session_id - event.set_extra("provider_request", req) - - if not req.prompt: - return - - req.session_id = event.unified_msg_origin - - # 执行请求 LLM 前事件钩子。 - # 装饰 system_prompt 等功能 - handlers = star_handlers_registry.get_handlers_by_event_type(EventType.OnLLMRequestEvent) - for handler in handlers: - try: - await handler.handler(event, req) - except BaseException: - logger.error(traceback.format_exc()) - - try: - logger.debug(f"Dify 请求 Payload: {req.__dict__}") - llm_response = await provider.text_chat(**req.__dict__) # 请求 LLM - await Metric.upload(llm_tick=1, model_name=provider.get_model(), provider_type=provider.meta().type) - - # 执行 LLM 响应后的事件。 - handlers = star_handlers_registry.get_handlers_by_event_type(EventType.OnLLMResponseEvent) - for handler in handlers: - try: - await handler.handler(event, llm_response) - except BaseException: - logger.error(traceback.format_exc()) - - if llm_response.role == 'assistant': - # text completion - event.set_result(MessageEventResult().message(llm_response.completion_text) - .set_result_content_type(ResultContentType.LLM_RESULT)) - return - elif llm_response.role == 'err': - event.set_result(MessageEventResult().message(f"AstrBot 请求失败。\n错误信息: {llm_response.completion_text}")) - return - elif llm_response.role == 'tool': - event.set_result(MessageEventResult().message(f"Dify 暂不支持工具调用。")) - yield - - except BaseException as e: - logger.error(traceback.format_exc()) - event.set_result(MessageEventResult().message("AstrBot 请求 Dify 失败:" + str(e))) - return \ No newline at end of file diff --git a/astrbot/core/pipeline/process_stage/method/llm_request.py b/astrbot/core/pipeline/process_stage/method/llm_request.py index fd50d4423..8d88e13da 100644 --- a/astrbot/core/pipeline/process_stage/method/llm_request.py +++ b/astrbot/core/pipeline/process_stage/method/llm_request.py @@ -54,7 +54,7 @@ class LLMRequestSubStage(Stage): conversation_id = await self.conv_manager.get_curr_conversation_id(event.unified_msg_origin) if not conversation_id: conversation_id = await self.conv_manager.new_conversation(event.unified_msg_origin) - req.session_id = conversation_id + req.session_id = event.unified_msg_origin conversation = await self.conv_manager.get_conversation(event.unified_msg_origin, conversation_id) req.conversation = conversation req.contexts = json.loads(conversation.history) @@ -154,6 +154,6 @@ class LLMRequestSubStage(Stage): contexts_to_save = list(filter(lambda item: '_no_save' not in item, contexts)) await self.conv_manager.update_conversation( event.unified_msg_origin, - req.session_id, + req.conversation.cid, history=contexts_to_save ) \ No newline at end of file diff --git a/astrbot/core/pipeline/process_stage/stage.py b/astrbot/core/pipeline/process_stage/stage.py index 0026e9d6f..f93be8108 100644 --- a/astrbot/core/pipeline/process_stage/stage.py +++ b/astrbot/core/pipeline/process_stage/stage.py @@ -3,7 +3,6 @@ from ..stage import Stage, register_stage from ..context import PipelineContext from .method.llm_request import LLMRequestSubStage from .method.star_request import StarRequestSubStage -from .method.dify_request import DifyRequestSubStage from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.star.star_handler import StarHandlerMetadata from astrbot.core.provider.entites import ProviderRequest @@ -21,9 +20,6 @@ class ProcessStage(Stage): self.star_request_sub_stage = StarRequestSubStage() await self.star_request_sub_stage.initialize(ctx) - - self.dify_request_sub_stage = DifyRequestSubStage() - await self.dify_request_sub_stage.initialize(ctx) async def process(self, event: AstrMessageEvent) -> Union[None, AsyncGenerator[None, None]]: '''处理事件 @@ -59,10 +55,5 @@ class ProcessStage(Stage): logger.info("未找到可用的 LLM 提供商,请先前往配置服务提供商。") return - match provider.meta().type: - case "dify": - async for _ in self.dify_request_sub_stage.process(event): - yield - case _: - async for _ in self.llm_request_sub_stage.process(event): - yield \ No newline at end of file + async for _ in self.llm_request_sub_stage.process(event): + yield \ No newline at end of file diff --git a/astrbot/core/provider/sources/dashscope_source.py b/astrbot/core/provider/sources/dashscope_source.py index 56ab79934..a120efbd3 100644 --- a/astrbot/core/provider/sources/dashscope_source.py +++ b/astrbot/core/provider/sources/dashscope_source.py @@ -1,4 +1,5 @@ import asyncio +import functools from typing import List from .. import Provider, Personality from ..entites import LLMResponse @@ -47,6 +48,11 @@ class ProviderDashscope(ProviderOpenAIOfficial): system_prompt: str = None, **kwargs, ) -> LLMResponse: + + # 获得会话变量 + session_vars = sp.get("session_variables", {}) + session_var = session_vars.get(session_id, {}) + if self.dashscope_app_type in ["agent", "dialog-workflow"]: # 支持多轮对话的 new_record = {"role": "user", "content": prompt} @@ -60,27 +66,30 @@ class ProviderDashscope(ProviderOpenAIOfficial): if '_no_save' in part: del part['_no_save'] # 调用阿里云百炼 API + partial = functools.partial( + Application.call, + app_id = self.app_id, + api_key = self.api_key, + messages = context_query, + biz_params = session_var or None, + ) response = await asyncio.get_event_loop().run_in_executor( None, - Application.call, - self.app_id, - None, - None, - None, - self.api_key, - context_query + partial ) else: # 不支持多轮对话的 # 调用阿里云百炼 API + partial = functools.partial( + Application.call, + app_id = self.app_id, + promtp = prompt, + api_key = self.api_key, + biz_params=session_var or None, + ) response = await asyncio.get_event_loop().run_in_executor( None, - Application.call, - self.app_id, - prompt, - None, - None, - self.api_key + partial ) logger.debug(f"dashscope resp: {response}") diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index 70500c60f..abe5c16b6 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -83,9 +83,6 @@ AstrBot 指令: /tool ls: 函数工具 /key: API Key(op) /websearch: 网页搜索 - -[其他] -/set 变量名 值: 为会话定义变量(Dify 工作流输入) {notice}""" event.set_result(MessageEventResult().message(msg).use_t2i(False)) From 8beb7acdb135515219488a63d4f412399832d843 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 22 Feb 2025 14:48:18 +0800 Subject: [PATCH 12/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=BA=20dify=20=E5=92=8C=20dashscope=20=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E5=95=86=E8=AE=BE=E7=BD=AE=E9=BB=98=E8=AE=A4=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E5=8F=98=E9=87=8F=20#552?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 8 +++ .../core/provider/sources/dashscope_source.py | 72 ++++++++++--------- astrbot/core/provider/sources/dify_source.py | 8 ++- 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 68c3146b5..880e7b404 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -527,6 +527,7 @@ CONFIG_METADATA_2 = { "dify_api_base": "https://api.dify.ai/v1", "dify_workflow_output_key": "", "dify_query_input_key": "astrbot_text_query", + "variables": {}, "timeout": 60, }, "dashscope": { @@ -536,6 +537,7 @@ CONFIG_METADATA_2 = { "dashscope_app_type": "agent", "dashscope_api_key": "", "dashscope_app_id": "", + "variables": {}, "timeout": 60, }, "whisper(API)": { @@ -574,6 +576,12 @@ CONFIG_METADATA_2 = { }, }, "items": { + "variables": { + "description": "工作流固定输入变量", + "type": "object", + "obvious_hint": True, + "hint": "可选。工作流固定输入变量,将会作为工作流的输入。也可以在对话时使用 /set 指令动态设置变量。如果变量名冲突,优先使用动态设置的变量。", + }, "dashscope_app_type": { "description": "应用类型", "type": "string", diff --git a/astrbot/core/provider/sources/dashscope_source.py b/astrbot/core/provider/sources/dashscope_source.py index a120efbd3..eb1db3607 100644 --- a/astrbot/core/provider/sources/dashscope_source.py +++ b/astrbot/core/provider/sources/dashscope_source.py @@ -10,6 +10,7 @@ from .openai_source import ProviderOpenAIOfficial from astrbot.core import logger, sp from dashscope import Application + @register_provider_adapter("dashscope", "Dashscope APP 适配器。") class ProviderDashscope(ProviderOpenAIOfficial): def __init__( @@ -18,10 +19,15 @@ class ProviderDashscope(ProviderOpenAIOfficial): provider_settings: dict, db_helper: BaseDatabase, persistant_history=False, - default_persona: Personality=None + default_persona: Personality = None, ) -> None: Provider.__init__( - self, provider_config, provider_settings, persistant_history, db_helper, default_persona + self, + provider_config, + provider_settings, + persistant_history, + db_helper, + default_persona, ) self.api_key = provider_config.get("dashscope_api_key", "") if not self.api_key: @@ -33,7 +39,8 @@ class ProviderDashscope(ProviderOpenAIOfficial): if not self.dashscope_app_type: raise Exception("阿里云百炼 APP 类型不能为空。") self.model_name = "dashscope" - + self.variables: dict = provider_config.get("variables", {}) + self.timeout = provider_config.get("timeout", 120) if isinstance(self.timeout, str): self.timeout = int(self.timeout) @@ -48,13 +55,15 @@ class ProviderDashscope(ProviderOpenAIOfficial): system_prompt: str = None, **kwargs, ) -> LLMResponse: - # 获得会话变量 + payload_vars = self.variables.copy() + # 动态变量 session_vars = sp.get("session_variables", {}) session_var = session_vars.get(session_id, {}) - + payload_vars.update(session_var) + if self.dashscope_app_type in ["agent", "dialog-workflow"]: - # 支持多轮对话的 + # 支持多轮对话的 new_record = {"role": "user", "content": prompt} if image_urls: logger.warning("阿里云百炼暂不支持图片输入,将自动忽略图片内容。") @@ -63,43 +72,42 @@ class ProviderDashscope(ProviderOpenAIOfficial): if system_prompt: context_query.insert(0, {"role": "system", "content": system_prompt}) for part in context_query: - if '_no_save' in part: - del part['_no_save'] + if "_no_save" in part: + del part["_no_save"] # 调用阿里云百炼 API partial = functools.partial( - Application.call, - app_id = self.app_id, - api_key = self.api_key, - messages = context_query, - biz_params = session_var or None, - ) - response = await asyncio.get_event_loop().run_in_executor( - None, - partial + Application.call, + app_id=self.app_id, + api_key=self.api_key, + messages=context_query, + biz_params=payload_vars or None, ) + response = await asyncio.get_event_loop().run_in_executor(None, partial) else: # 不支持多轮对话的 # 调用阿里云百炼 API partial = functools.partial( - Application.call, - app_id = self.app_id, - promtp = prompt, - api_key = self.api_key, - biz_params=session_var or None, + Application.call, + app_id=self.app_id, + promtp=prompt, + api_key=self.api_key, + biz_params=payload_vars or None, ) - response = await asyncio.get_event_loop().run_in_executor( - None, - partial - ) - + response = await asyncio.get_event_loop().run_in_executor(None, partial) + logger.debug(f"dashscope resp: {response}") if response.status_code != 200: - logger.error(f"阿里云百炼请求失败: request_id={response.request_id}, code={response.status_code}, message={response.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code") - return LLMResponse(role="err", completion_text=f"阿里云百炼请求失败: message={response.message} code={response.status_code}") - + logger.error( + f"阿里云百炼请求失败: request_id={response.request_id}, code={response.status_code}, message={response.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code" + ) + return LLMResponse( + role="err", + completion_text=f"阿里云百炼请求失败: message={response.message} code={response.status_code}", + ) + output_text = response.output.get("text", "") - return LLMResponse(role="assistant", completion_text=output_text) + return LLMResponse(role="assistant", completion_text=output_text) async def forget(self, session_id): return True @@ -117,4 +125,4 @@ class ProviderDashscope(ProviderOpenAIOfficial): raise Exception("暂不支持获得 阿里云百炼 的历史消息记录。") async def terminate(self): - await self.api_client.close() \ No newline at end of file + await self.api_client.close() diff --git a/astrbot/core/provider/sources/dify_source.py b/astrbot/core/provider/sources/dify_source.py index 807520e0e..1127c7561 100644 --- a/astrbot/core/provider/sources/dify_source.py +++ b/astrbot/core/provider/sources/dify_source.py @@ -32,6 +32,7 @@ class ProviderDify(Provider): self.model_name = "dify" self.workflow_output_key = provider_config.get("dify_workflow_output_key", "astrbot_wf_output") self.dify_query_input_key = provider_config.get("dify_query_input_key", "astrbot_text_query") + self.variables: dict = provider_config.get("variables", {}) if not self.dify_query_input_key: self.dify_query_input_key = "astrbot_text_query" self.timeout = provider_config.get("timeout", 120) @@ -72,15 +73,18 @@ class ProviderDify(Provider): logger.warning(f"未知的图片链接:{image_url},图片将忽略。") # 获得会话变量 + payload_vars = self.variables.copy() + # 动态变量 session_vars = sp.get("session_variables", {}) session_var = session_vars.get(session_id, {}) + payload_vars.update(session_var) try: match self.api_type: case "chat" | "agent": async for chunk in self.api_client.chat_messages( inputs={ - **session_var + **payload_vars, }, query=prompt, user=session_id, @@ -101,7 +105,7 @@ class ProviderDify(Provider): inputs={ self.dify_query_input_key: prompt, "astrbot_session_id": session_id, - **session_var + **payload_vars, }, user=session_id, files=files_payload, From 2807e1e89264e06b45c053fac08eb4fcb4720eec Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 22 Feb 2025 15:43:14 +0800 Subject: [PATCH 13/51] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20template=20of?= =?UTF-8?q?=20FastGPT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 15 +++++++++++++++ astrbot/core/provider/sources/dashscope_source.py | 2 +- astrbot/core/provider/sources/openai_source.py | 6 ++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 880e7b404..0b06b3f92 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -540,6 +540,14 @@ CONFIG_METADATA_2 = { "variables": {}, "timeout": 60, }, + "fastgpt": { + "id": "fastgpt", + "type": "openai_chat_completion", + "enable": True, + "key": [], + "api_base": "https://api.fastgpt.in/api/v1", + "timeout": 60, + }, "whisper(API)": { "id": "whisper", "type": "openai_whisper_api", @@ -582,6 +590,13 @@ CONFIG_METADATA_2 = { "obvious_hint": True, "hint": "可选。工作流固定输入变量,将会作为工作流的输入。也可以在对话时使用 /set 指令动态设置变量。如果变量名冲突,优先使用动态设置的变量。", }, + # "fastgpt_app_type": { + # "description": "应用类型", + # "type": "string", + # "hint": "FastGPT 应用的应用类型。", + # "options": ["agent", "workflow", "plugin"], + # "obvious_hint": True, + # }, "dashscope_app_type": { "description": "应用类型", "type": "string", diff --git a/astrbot/core/provider/sources/dashscope_source.py b/astrbot/core/provider/sources/dashscope_source.py index eb1db3607..9fd26c90b 100644 --- a/astrbot/core/provider/sources/dashscope_source.py +++ b/astrbot/core/provider/sources/dashscope_source.py @@ -125,4 +125,4 @@ class ProviderDashscope(ProviderOpenAIOfficial): raise Exception("暂不支持获得 阿里云百炼 的历史消息记录。") async def terminate(self): - await self.api_client.close() + pass \ No newline at end of file diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index a3114a6c1..6752568d0 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -48,8 +48,10 @@ class ProviderOpenAIOfficial(Provider): base_url=provider_config.get("api_base", None), timeout=self.timeout ) - - self.set_model(provider_config['model_config']['model']) + + model_config = provider_config.get("model_config", {}) + model = model_config.get("model", "unknown") + self.set_model(model) async def get_models(self): try: From a6b363b433725d44c3a8178021c38bf2aa3e7cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E6=B0=B8=E4=BA=AE?= <702625325@qq.com> Date: Sat, 22 Feb 2025 16:10:46 +0800 Subject: [PATCH 14/51] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=97=B6=E6=A3=80=E6=9F=A5=E7=AB=AF=E5=8F=A3=E5=8D=A0=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/dashboard/server.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 2656ba2a9..7cb7f95ae 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -2,6 +2,8 @@ import logging import jwt import asyncio import os +import socket +import sys from astrbot.core.config.default import VERSION from quart import Quart, request, jsonify, g from quart.logging import default_handler @@ -67,6 +69,25 @@ class AstrBotDashboard(): await asyncio.sleep(1) logger.info("管理面板已关闭。") + def check_port_in_use(self, port: int) -> bool: + """ + 跨平台检测端口是否被占用 + """ + try: + # 创建 IPv4 TCP Socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # 设置超时时间 + sock.settimeout(2) + result = sock.connect_ex(('127.0.0.1', port)) + sock.close() + # result 为 0 表示端口被占用 + return result == 0 + except Exception as e: + logger.warning(f"检查端口 {port} 时发生错误: {str(e)}") + # 如果出现异常,保守起见认为端口可能被占用 + return True + def run(self): try: ip_addr = get_local_ip_addresses() @@ -76,6 +97,14 @@ class AstrBotDashboard(): port = self.core_lifecycle.astrbot_config['dashboard'].get("port", 6185) if isinstance(port, str): port = int(port) + + if self.check_port_in_use(port): + logger.error(f"错误:端口 {port} 已被占用,请确保:\n" + f"1. 没有其他 AstrBot 实例正在运行\n" + f"2. 端口 {port} 没有被其他程序占用\n" + f"3. 如需使用其他端口,请修改配置文件") + + raise Exception(f"端口 {port} 已被占用") display = f"\n ✨✨✨\n AstrBot v{VERSION} 管理面板已启动,可访问\n\n" display += f" ➜ 本地: http://localhost:{port}\n" From 837111b17e36515cb77d456bd4a1b3225b7756ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E6=B0=B8=E4=BA=AE?= <702625325@qq.com> Date: Sat, 22 Feb 2025 16:23:50 +0800 Subject: [PATCH 15/51] =?UTF-8?q?perf:=20=E5=A1=AB=E5=8A=A0=E5=85=B7?= =?UTF-8?q?=E4=BD=93=E5=8D=A0=E7=94=A8=E8=BF=9B=E7=A8=8B=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/dashboard/server.py | 28 +++++++++++++++++++++++++++- requirements.txt | 1 + 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index 7cb7f95ae..e3f28cb55 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -4,6 +4,7 @@ import asyncio import os import socket import sys +import psutil from astrbot.core.config.default import VERSION from quart import Quart, request, jsonify, g from quart.logging import default_handler @@ -88,6 +89,28 @@ class AstrBotDashboard(): # 如果出现异常,保守起见认为端口可能被占用 return True + def get_process_using_port(self, port: int) -> str: + """获取占用端口的进程详细信息""" + try: + for conn in psutil.net_connections(kind='inet'): + if conn.laddr.port == port: + try: + process = psutil.Process(conn.pid) + # 获取详细信息 + proc_info = [ + f"进程名: {process.name()}", + f"PID: {process.pid}", + f"执行路径: {process.exe()}", + f"工作目录: {process.cwd()}", + f"启动命令: {' '.join(process.cmdline())}" + ] + return "\n ".join(proc_info) + except (psutil.NoSuchProcess, psutil.AccessDenied) as e: + return f"无法获取进程详细信息(可能需要管理员权限): {str(e)}" + return "未找到占用进程" + except Exception as e: + return f"获取进程信息失败: {str(e)}" + def run(self): try: ip_addr = get_local_ip_addresses() @@ -99,7 +122,10 @@ class AstrBotDashboard(): port = int(port) if self.check_port_in_use(port): - logger.error(f"错误:端口 {port} 已被占用,请确保:\n" + process_info = self.get_process_using_port(port) + logger.error(f"错误:端口 {port} 已被占用\n" + f"占用信息: \n {process_info}\n" + f"请确保:\n" f"1. 没有其他 AstrBot 实例正在运行\n" f"2. 端口 {port} 没有被其他程序占用\n" f"3. 如需使用其他端口,请修改配置文件") diff --git a/requirements.txt b/requirements.txt index 39abe351d..441d73d47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ apscheduler docstring_parser aiodocker silk-python +psutil>=5.8.0 lark-oapi ormsgpack From ed508af42466bb123d1108a5827130753cd9c952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E6=B0=B8=E4=BA=AE?= <702625325@qq.com> Date: Sat, 22 Feb 2025 17:10:53 +0800 Subject: [PATCH 16/51] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=8F=B0=E5=85=B3=E9=97=AD=E8=87=AA=E5=8A=A8=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/ConsoleDisplayer.vue | 8 ++- dashboard/src/views/ConsolePage.vue | 71 +++++++++++-------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/dashboard/src/components/shared/ConsoleDisplayer.vue b/dashboard/src/components/shared/ConsoleDisplayer.vue index c0f4447ff..620575102 100644 --- a/dashboard/src/components/shared/ConsoleDisplayer.vue +++ b/dashboard/src/components/shared/ConsoleDisplayer.vue @@ -13,6 +13,7 @@ export default { name: 'ConsoleDisplayer', data() { return { + autoScroll: true, // 默认开启自动滚动 logColorAnsiMap: { '\u001b[1;34m': 'color: #0000FF; font-weight: bold;', // bold_blue '\u001b[1;36m': 'color: #00FFFF; font-weight: bold;', // bold_cyan @@ -54,6 +55,9 @@ export default { } }, methods: { + toggleAutoScroll() { + this.autoScroll = !this.autoScroll; + }, printLog(log) { // append 一个 span 标签到 term,block 的方式 let ele = document.getElementById('term') @@ -70,7 +74,9 @@ export default { span.classList.add('fade-in') span.innerText = log ele.appendChild(span) - ele.scrollTop = ele.scrollHeight + if (this.autoScroll) { + ele.scrollTop = ele.scrollHeight + } } }, } diff --git a/dashboard/src/views/ConsolePage.vue b/dashboard/src/views/ConsolePage.vue index 5c6d58281..e638df47c 100644 --- a/dashboard/src/views/ConsolePage.vue +++ b/dashboard/src/views/ConsolePage.vue @@ -9,34 +9,42 @@ import axios from 'axios';

控制台

- - - - - 安装 Pip 库 - - - - - 如果不填镜像站链接,默认使用阿里云镜像:https://mirrors.aliyun.com/pypi/simple/ -
- {{ status }} -
- -
- - - - 安装 - - -
-
- +
+ + + + + + 安装 Pip 库 + + + + + 如果不填镜像站链接,默认使用阿里云镜像:https://mirrors.aliyun.com/pypi/simple/ +
+ {{ status }} +
+ +
+ + + + 安装 + + +
+
+
- + + + \ No newline at end of file diff --git a/dashboard/src/views/ProviderPage.vue b/dashboard/src/views/ProviderPage.vue new file mode 100644 index 000000000..8d30a06b3 --- /dev/null +++ b/dashboard/src/views/ProviderPage.vue @@ -0,0 +1,221 @@ + + + + \ No newline at end of file From c656ad5e2c6e9652912b75e970f2a318d21a61f6 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 23 Feb 2025 15:27:05 +0800 Subject: [PATCH 21/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E5=92=8C=E6=9C=8D=E5=8A=A1=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E5=95=86=E9=A1=B5=E9=9D=A2=E6=94=AF=E6=8C=81=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/manager.py | 3 ++- astrbot/core/provider/manager.py | 6 +++-- astrbot/dashboard/routes/config.py | 2 +- .../components/shared/ConsoleDisplayer.vue | 4 ++-- dashboard/src/views/PlatformPage.vue | 22 ++++++++++++++++--- dashboard/src/views/ProviderPage.vue | 18 ++++++++++++++- 6 files changed, 45 insertions(+), 10 deletions(-) diff --git a/astrbot/core/platform/manager.py b/astrbot/core/platform/manager.py index 6af0eda7d..a99fb5532 100644 --- a/astrbot/core/platform/manager.py +++ b/astrbot/core/platform/manager.py @@ -34,6 +34,8 @@ class PlatformManager(): if not platform_config['enable']: return + logger.info(f"载入 {platform_config['type']}({platform_config['id']}) 平台适配器 ...") + # 动态导入 try: match platform_config['type']: @@ -57,7 +59,6 @@ class PlatformManager(): logger.error(f"未找到适用于 {platform_config['type']}({platform_config['id']}) 平台适配器,请检查是否已经安装或者名称填写错误。") return cls_type = platform_cls_map[platform_config['type']] - logger.debug(f"尝试实例化 {platform_config['type']}({platform_config['id']}) 平台适配器 ...") inst = cls_type(platform_config, self.settings, self.event_queue) self._inst_map[platform_config['id']] = inst self.platform_insts.append(inst) diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 3546f249f..62f79b2f8 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -122,6 +122,8 @@ class ProviderManager(): if not provider_config['enable']: return + logger.info(f"载入 {provider_config['type']}({provider_config['id']}) 服务提供商适配器 ...") + # 动态导入 try: match provider_config['type']: @@ -160,7 +162,6 @@ class ProviderManager(): return provider_metadata = provider_cls_map[provider_config['type']] - logger.debug(f"尝试实例化 {provider_config['type']}({provider_config['id']}) 提供商适配器 ...") try: # 按任务实例化提供商 @@ -241,6 +242,8 @@ class ProviderManager(): async def terminate_provider(self, provider_id: str): if provider_id in self.inst_map: + logger.info(f"终止 {provider_id} 提供商适配器 ...") + if self.inst_map[provider_id] in self.provider_insts: self.provider_insts.remove(self.inst_map[provider_id]) if self.inst_map[provider_id] in self.stt_provider_insts: @@ -249,7 +252,6 @@ class ProviderManager(): self.tts_provider_insts.remove(self.inst_map[provider_id]) if getattr(self.inst_map[provider_id], 'terminate', None): - logger.info(f"正在尝试终止 {provider_id} 提供商适配器 ...") await self.inst_map[provider_id].terminate() logger.info(f"{provider_id} 提供商适配器已终止。") del self.inst_map[provider_id] diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 9208ffd12..0e2cdc1c4 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -62,7 +62,7 @@ def validate_config(data, schema: dict, is_core: bool) -> typing.Tuple[typing.Li group_meta = group.get("metadata") if not group_meta: continue - logger.info(f"验证配置: 组 {key} ...") + # logger.info(f"验证配置: 组 {key} ...") validate(data, group_meta, path=f"{key}.") else: validate(data, schema) diff --git a/dashboard/src/components/shared/ConsoleDisplayer.vue b/dashboard/src/components/shared/ConsoleDisplayer.vue index 620575102..a24460e37 100644 --- a/dashboard/src/components/shared/ConsoleDisplayer.vue +++ b/dashboard/src/components/shared/ConsoleDisplayer.vue @@ -4,7 +4,7 @@ import { useCommonStore } from '@/stores/common'; @@ -70,7 +70,7 @@ export default { break } } - span.style = style + 'display: block; font-size: 12px; font-family: Consolas, monospace;' + span.style = style + 'display: block; font-size: 12px; font-family: Consolas, monospace; white-space: pre-wrap;' span.classList.add('fade-in') span.innerText = log ele.appendChild(span) diff --git a/dashboard/src/views/PlatformPage.vue b/dashboard/src/views/PlatformPage.vue index 4732fdf2d..78c47cf4d 100644 --- a/dashboard/src/views/PlatformPage.vue +++ b/dashboard/src/views/PlatformPage.vue @@ -22,7 +22,8 @@ - + {{ platform.id }}
- 适配器类型: + 适配器类型: {{ platform.type }}
@@ -65,6 +66,17 @@ + + + + +
+ +
+
@@ -78,12 +90,14 @@ import axios from 'axios'; import AstrBotConfig from '@/components/shared/AstrBotConfig.vue'; import WaitingForRestart from '@/components/shared/WaitingForRestart.vue'; +import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue'; export default { name: 'PlatformPage', components: { AstrBotConfig, - WaitingForRestart + WaitingForRestart, + ConsoleDisplayer }, data() { return { @@ -101,6 +115,8 @@ export default { save_message_snack: false, save_message: "", save_message_success: "", + + showConsole: false, } }, diff --git a/dashboard/src/views/ProviderPage.vue b/dashboard/src/views/ProviderPage.vue index 8d30a06b3..423666adb 100644 --- a/dashboard/src/views/ProviderPage.vue +++ b/dashboard/src/views/ProviderPage.vue @@ -63,6 +63,18 @@
+ + + + +
+ +
+ + @@ -77,12 +89,14 @@ import axios from 'axios'; import AstrBotConfig from '@/components/shared/AstrBotConfig.vue'; import WaitingForRestart from '@/components/shared/WaitingForRestart.vue'; +import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue'; export default { name: 'ProviderPage', components: { AstrBotConfig, - WaitingForRestart + WaitingForRestart, + ConsoleDisplayer }, data() { return { @@ -100,6 +114,8 @@ export default { save_message_snack: false, save_message: "", save_message_success: "", + + showConsole: false, } }, From 2a37f7edacfaf74e7dcbdeb539b39eeacc1904da Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 23 Feb 2025 15:41:34 +0800 Subject: [PATCH 22/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E5=9C=A8=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E9=A1=B5=E9=9D=A2=E6=B7=BB=E5=8A=A0=E7=B2=98=E8=B4=B4?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/views/ChatPage.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dashboard/src/views/ChatPage.vue b/dashboard/src/views/ChatPage.vue index 3ea9c9a2e..4a7a07d9b 100644 --- a/dashboard/src/views/ChatPage.vue +++ b/dashboard/src/views/ChatPage.vue @@ -56,7 +56,7 @@ marked.setOptions({
输入 /help + style="background-color: #eee; padding-left: 4px; padding-right: 4px; margin: 2px; border-radius: 4px;">help 获取帮助 😊
@@ -65,6 +65,12 @@ marked.setOptions({ style="background-color: #eee; padding-left: 4px; padding-right: 4px; margin: 2px; border-radius: 4px;">K 开始语音 🎤
+
+ + Ctrl + V + 粘贴图片 🏞️ +
From 071fc7d6efce41b7b030068fca102d370a12efbe Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 23 Feb 2025 15:52:30 +0800 Subject: [PATCH 23/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E9=80=82=E9=85=8D=E5=99=A8=E7=B1=BB=E5=9E=8B=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=B9=B6=E6=B7=BB=E5=8A=A0API=20Base?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/views/PlatformPage.vue | 2 +- dashboard/src/views/ProviderPage.vue | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dashboard/src/views/PlatformPage.vue b/dashboard/src/views/PlatformPage.vue index 78c47cf4d..3e1e8d20b 100644 --- a/dashboard/src/views/PlatformPage.vue +++ b/dashboard/src/views/PlatformPage.vue @@ -32,7 +32,7 @@
适配器类型: - {{ platform.type }} + {{ platform.type }}
diff --git a/dashboard/src/views/ProviderPage.vue b/dashboard/src/views/ProviderPage.vue index 423666adb..17cd6ef38 100644 --- a/dashboard/src/views/ProviderPage.vue +++ b/dashboard/src/views/ProviderPage.vue @@ -30,7 +30,10 @@
- 适配器类型: {{ provider.type }} + 适配器类型: {{ provider.type }} +
+
+ API Base: {{ provider?.api_base }}
From 431e2fad72d17e2786fb6f5d0a43ff21c54a9a3b Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 23 Feb 2025 16:10:32 +0800 Subject: [PATCH 24/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=A6=81=E6=AD=A2=E9=BB=98=E8=AE=A4=E7=9A=84?= =?UTF-8?q?llm=E8=B0=83=E7=94=A8=20#579?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/pipeline/process_stage/stage.py | 2 +- astrbot/core/platform/astr_message_event.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/astrbot/core/pipeline/process_stage/stage.py b/astrbot/core/pipeline/process_stage/stage.py index f93be8108..c22ab4d92 100644 --- a/astrbot/core/pipeline/process_stage/stage.py +++ b/astrbot/core/pipeline/process_stage/stage.py @@ -45,7 +45,7 @@ class ProcessStage(Stage): if not self.ctx.astrbot_config['provider_settings'].get('enable', True): return - if not event._has_send_oper and event.is_at_or_wake_command: + if not event._has_send_oper and event.is_at_or_wake_command and not event.call_llm: # 是否有过发送操作 and 是否是被 @ 或者通过唤醒前缀 if (event.get_result() and not event.get_result().is_stopped()) or not event.get_result(): # 事件没有终止传播 diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py index d650ba840..0500d0279 100644 --- a/astrbot/core/platform/astr_message_event.py +++ b/astrbot/core/platform/astr_message_event.py @@ -57,7 +57,8 @@ class AstrMessageEvent(abc.ABC): self._has_send_oper = False '''是否有过至少一次发送操作''' - + self.call_llm = False + '''是否在此消息事件中禁止默认的 LLM 请求''' # back_compability self.platform = platform_meta @@ -242,7 +243,15 @@ class AstrMessageEvent(abc.ABC): ''' if self._result is None: return False # 默认是继续传播 - return self._result.is_stopped() + return self._result.is_stopped() + + def should_call_llm(self, call_llm: bool): + ''' + 是否在此消息事件中禁止默认的 LLM 请求。 + + 只会阻止 AstrBot 默认的 LLM 请求链路,不会阻止插件中的 LLM 请求。 + ''' + self.call_llm = call_llm def get_result(self) -> MessageEventResult: ''' From 38d7be1d5ff9b6a89203047b16e3f3105dbda7c7 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 23 Feb 2025 16:29:57 +0800 Subject: [PATCH 25/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E6=A1=86=E6=A0=B7=E5=BC=8F=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=85=B3=E4=BA=8E=E9=A1=B5=E9=9D=A2=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/components/shared/AstrBotConfig.vue | 8 ++++---- dashboard/src/views/AboutPage.vue | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/dashboard/src/components/shared/AstrBotConfig.vue b/dashboard/src/components/shared/AstrBotConfig.vue index 37efe7f6a..6ace849ef 100644 --- a/dashboard/src/components/shared/AstrBotConfig.vue +++ b/dashboard/src/components/shared/AstrBotConfig.vue @@ -6,8 +6,8 @@
+ style="margin-bottom: 8px" :text="metadata[metadataKey].items[key]?.hint" + :title="'💡 ' + metadata[metadataKey].items[key]?.description" type="info" variant="tonal" color="primary">
@@ -66,8 +66,8 @@
+ style="margin-bottom: 8px" :text="metadata[metadataKey]?.hint" + :title="'💡 ' + metadata[metadataKey]?.description" type="info" variant="tonal" color="primary">
diff --git a/dashboard/src/views/AboutPage.vue b/dashboard/src/views/AboutPage.vue index e0927534b..40c0f9555 100644 --- a/dashboard/src/views/AboutPage.vue +++ b/dashboard/src/views/AboutPage.vue @@ -4,13 +4,20 @@
- AstrBot Logo - AstrBot Logo + AstrBot Logo + AstrBot Logo

AstrBot

- By Soulter And AstrBot Contributors + A project out of interests and loves ❤️ + + By Soulter, AstrBot Contributors + and AstrBot Plugin Authors + From 8e2d666ff836de9f8dcd6098438ce0256d7fde32 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 23 Feb 2025 16:57:48 +0800 Subject: [PATCH 26/51] =?UTF-8?q?=E2=9C=A8=20feat:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=85=B3=E4=BA=8E=E9=A1=B5=E9=9D=A2=E5=92=8C=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=E5=90=AF=E6=8C=89=E9=92=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/dashboard/routes/stat.py | 2 +- .../src/components/shared/ListConfigItem.vue | 2 +- dashboard/src/views/AboutPage.vue | 24 +++++++++++++++---- dashboard/src/views/ConfigPage.vue | 4 ++-- dashboard/src/views/Settings.vue | 22 ++++++++++++++++- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py index 4e5d68ecd..1a7e61bc2 100644 --- a/astrbot/dashboard/routes/stat.py +++ b/astrbot/dashboard/routes/stat.py @@ -16,7 +16,7 @@ class StatRoute(Route): '/stat/get': ('GET', self.get_stat), '/stat/version': ('GET', self.get_version), '/stat/start-time': ('GET', self.get_start_time), - '/stat/restart-core': ('GET', self.restart_core) + '/stat/restart-core': ('POST', self.restart_core) } self.db_helper = db_helper self.register_routes() diff --git a/dashboard/src/components/shared/ListConfigItem.vue b/dashboard/src/components/shared/ListConfigItem.vue index be23f5d32..74ba445f2 100644 --- a/dashboard/src/components/shared/ListConfigItem.vue +++ b/dashboard/src/components/shared/ListConfigItem.vue @@ -1,7 +1,7 @@