diff --git a/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml b/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml index 56f958d30..c24bcf6d9 100644 --- a/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml +++ b/.github/ISSUE_TEMPLATE/PLUGIN_PUBLISH.yml @@ -16,7 +16,7 @@ body: 请将插件信息填写到下方的 JSON 代码块中。其中 `tags`(插件标签)和 `social_link`(社交链接)选填。 - 不熟悉 JSON ?可以从 [此处](https://plugins.astrbot.app/submit) 生成 JSON ,生成后记得复制粘贴过来. + 不熟悉 JSON ?可以从 [此站](https://plugins.astrbot.app) 右下角提交。 - type: textarea id: plugin-info diff --git a/README.md b/README.md index f408d6b44..5083f0264 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@
Soulter%2FAstrBot | Trendshift -Featured|HelloGitHub +Featured|HelloGitHub

diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index a9d9e5e96..1021d81b5 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -740,6 +740,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.openai.com/v1", "timeout": 120, "model_config": {"model": "gpt-4o-mini", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], "hint": "也兼容所有与 OpenAI API 兼容的服务。", @@ -755,6 +756,7 @@ CONFIG_METADATA_2 = { "api_base": "", "timeout": 120, "model_config": {"model": "gpt-4o-mini", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -768,6 +770,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.x.ai/v1", "timeout": 120, "model_config": {"model": "grok-2-latest", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "xai_native_search": False, "modalities": ["text", "image", "tool_use"], @@ -799,6 +802,7 @@ CONFIG_METADATA_2 = { "key": ["ollama"], # ollama 的 key 默认是 ollama "api_base": "http://localhost:11434/v1", "model_config": {"model": "llama3.1-8b", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -813,6 +817,7 @@ CONFIG_METADATA_2 = { "model_config": { "model": "llama-3.1-8b", }, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -829,6 +834,7 @@ CONFIG_METADATA_2 = { "model": "gemini-1.5-flash", "temperature": 0.4, }, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -870,6 +876,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.deepseek.com/v1", "timeout": 120, "model_config": {"model": "deepseek-chat", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "tool_use"], }, @@ -883,6 +890,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.302.ai/v1", "timeout": 120, "model_config": {"model": "gpt-4.1-mini", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -899,6 +907,7 @@ CONFIG_METADATA_2 = { "model": "deepseek-ai/DeepSeek-V3", "temperature": 0.4, }, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -915,6 +924,7 @@ CONFIG_METADATA_2 = { "model": "deepseek/deepseek-r1", "temperature": 0.4, }, + "custom_headers": {}, "custom_extra_body": {}, }, "小马算力": { @@ -930,6 +940,7 @@ CONFIG_METADATA_2 = { "model": "kimi-k2-instruct-0905", "temperature": 0.7, }, + "custom_headers": {}, "custom_extra_body": {}, }, "优云智算": { @@ -944,6 +955,7 @@ CONFIG_METADATA_2 = { "model_config": { "model": "moonshotai/Kimi-K2-Instruct", }, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -957,6 +969,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "api_base": "https://api.moonshot.cn/v1", "model_config": {"model": "moonshot-v1-8k", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -972,6 +985,8 @@ CONFIG_METADATA_2 = { "model_config": { "model": "glm-4-flash", }, + "custom_headers": {}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "Dify": { @@ -1028,6 +1043,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "api_base": "https://api-inference.modelscope.cn/v1", "model_config": {"model": "Qwen/Qwen3-32B", "temperature": 0.4}, + "custom_headers": {}, "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, @@ -1040,6 +1056,7 @@ CONFIG_METADATA_2 = { "key": [], "api_base": "https://api.fastgpt.in/api/v1", "timeout": 60, + "custom_headers": {}, "custom_extra_body": {}, }, "Whisper(API)": { @@ -1321,6 +1338,12 @@ CONFIG_METADATA_2 = { "render_type": "checkbox", "hint": "模型支持的模态。如所填写的模型不支持图像,请取消勾选图像。", }, + "custom_headers": { + "description": "自定义添加请求头", + "type": "dict", + "items": {}, + "hint": "此处添加的键值对将被合并到 OpenAI SDK 的 default_headers 中,用于自定义 HTTP 请求头。值必须为字符串。", + }, "custom_extra_body": { "description": "自定义请求体参数", "type": "dict", diff --git a/astrbot/core/platform/sources/discord/discord_platform_event.py b/astrbot/core/platform/sources/discord/discord_platform_event.py index 04c95f639..6fba5e70c 100644 --- a/astrbot/core/platform/sources/discord/discord_platform_event.py +++ b/astrbot/core/platform/sources/discord/discord_platform_event.py @@ -1,7 +1,7 @@ import asyncio import base64 import binascii -import sys +from collections.abc import AsyncGenerator from io import BytesIO from pathlib import Path from typing import cast @@ -23,11 +23,6 @@ from astrbot.api.platform import AstrBotMessage, At, PlatformMetadata from .client import DiscordBotClient from .components import DiscordEmbed, DiscordView -if sys.version_info >= (3, 12): - from typing import override -else: - from typing_extensions import override - # 自定义Discord视图组件(兼容旧版本) class DiscordViewComponent(BaseMessageComponent): @@ -51,7 +46,6 @@ class DiscordPlatformEvent(AstrMessageEvent): self.client = client self.interaction_followup_webhook = interaction_followup_webhook - @override async def send(self, message: MessageChain): """发送消息到Discord平台""" # 解析消息链为 Discord 所需的对象 @@ -103,6 +97,21 @@ class DiscordPlatformEvent(AstrMessageEvent): await super().send(message) + async def send_streaming( + self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False + ): + buffer = None + async for chain in generator: + if not buffer: + buffer = chain + else: + buffer.chain.extend(chain.chain) + if not buffer: + return None + buffer.squash_plain() + await self.send(buffer) + return await super().send_streaming(generator, use_fallback) + async def _get_channel( self, ) -> ( diff --git a/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py b/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py index e6d83d8ea..08ab27013 100644 --- a/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +++ b/astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py @@ -1,6 +1,7 @@ import asyncio import base64 import io +from collections.abc import AsyncGenerator from typing import TYPE_CHECKING import aiohttp @@ -50,6 +51,21 @@ class WeChatPadProMessageEvent(AstrMessageEvent): await self._send_voice(session, comp) await super().send(message) + async def send_streaming( + self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False + ): + buffer = None + async for chain in generator: + if not buffer: + buffer = chain + else: + buffer.chain.extend(chain.chain) + if not buffer: + return None + buffer.squash_plain() + await self.send(buffer) + return await super().send_streaming(generator, use_fallback) + async def _send_image(self, session: aiohttp.ClientSession, comp: Image): b64 = await comp.convert_to_base64() raw = self._validate_base64(b64) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 21d832eec..fde80c59f 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -43,14 +43,23 @@ class ProviderOpenAIOfficial(Provider): self.api_keys: list = super().get_keys() self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None self.timeout = provider_config.get("timeout", 120) + self.custom_headers = provider_config.get("custom_headers", {}) if isinstance(self.timeout, str): self.timeout = int(self.timeout) + + if not isinstance(self.custom_headers, dict) or not self.custom_headers: + self.custom_headers = None + else: + for key in self.custom_headers: + self.custom_headers[key] = str(self.custom_headers[key]) + # 适配 azure openai #332 if "api_version" in provider_config: # 使用 azure api self.client = AsyncAzureOpenAI( api_key=self.chosen_api_key, api_version=provider_config.get("api_version", None), + default_headers=self.custom_headers, base_url=provider_config.get("api_base", ""), timeout=self.timeout, ) @@ -59,6 +68,7 @@ class ProviderOpenAIOfficial(Provider): self.client = AsyncOpenAI( api_key=self.chosen_api_key, base_url=provider_config.get("api_base", None), + default_headers=self.custom_headers, timeout=self.timeout, ) diff --git a/dashboard/src/components/shared/KnowledgeBaseSelector.vue b/dashboard/src/components/shared/KnowledgeBaseSelector.vue index a90e6f5e2..e959b948a 100644 --- a/dashboard/src/components/shared/KnowledgeBaseSelector.vue +++ b/dashboard/src/components/shared/KnowledgeBaseSelector.vue @@ -1,22 +1,25 @@