mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 01:40:15 +08:00
增强旧版兼容性,添加多个旧路径入口和相关功能
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
- 2026-03-13: `filter.llm_tool()` must resolve deferred annotations from `from __future__ import annotations` when inferring JSON schema. Reading `inspect.Parameter.annotation` directly degrades typed params like `a: int` into `"int"` strings and silently turns tool argument schemas into generic strings.
|
||||
- 2026-03-13: Legacy result hooks must reuse the same `AstrMessageEvent` instance across `on_decorating_result` and `after_message_sent`. Re-wrapping the original v4 `MessageEvent` for the second hook drops decorated `event.set_result(...)` mutations and makes post-send hooks observe an empty result.
|
||||
- 2026-03-13: `src-new/astrbot_sdk/_legacy_runtime.py` already exists as the intended compat execution boundary. When cleaning runtime architecture, wire `loader` / `handler_dispatcher` / `bootstrap` through that adapter instead of adding new direct `legacy_context` branches in runtime files, or the compat logic will spread again.
|
||||
- 2026-03-13: Real legacy plugins may still load through deep `astrbot.core.*` imports even when their public entrypoint only looks like `astrbot.api.*`. `astrbot_plugin_self_learning` hits `astrbot.core.utils.astrbot_path`, `astrbot.core.provider.*`, `astrbot.core.agent.message`, and `astrbot.core.db.po` during load; keep those deep-path shims minimal and whitelist-driven, but do not assume the `api` facade alone is enough.
|
||||
|
||||
# 开发命令
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
- 2026-03-13: `filter.llm_tool()` must resolve deferred annotations from `from __future__ import annotations` when inferring JSON schema. Reading `inspect.Parameter.annotation` directly degrades typed params like `a: int` into `"int"` strings and silently turns tool argument schemas into generic strings.
|
||||
- 2026-03-13: Legacy result hooks must reuse the same `AstrMessageEvent` instance across `on_decorating_result` and `after_message_sent`. Re-wrapping the original v4 `MessageEvent` for the second hook drops decorated `event.set_result(...)` mutations and makes post-send hooks observe an empty result.
|
||||
- 2026-03-13: `src-new/astrbot_sdk/_legacy_runtime.py` already exists as the intended compat execution boundary. When cleaning runtime architecture, wire `loader` / `handler_dispatcher` / `bootstrap` through that adapter instead of adding new direct `legacy_context` branches in runtime files, or the compat logic will spread again.
|
||||
- 2026-03-13: Real legacy plugins may still load through deep `astrbot.core.*` imports even when their public entrypoint only looks like `astrbot.api.*`. `astrbot_plugin_self_learning` hits `astrbot.core.utils.astrbot_path`, `astrbot.core.provider.*`, `astrbot.core.agent.message`, and `astrbot.core.db.po` during load; keep those deep-path shims minimal and whitelist-driven, but do not assume the `api` facade alone is enough.
|
||||
|
||||
# 开发命令
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from loguru import logger
|
||||
from astrbot_sdk._shared_preferences import sp
|
||||
from astrbot_sdk.api.basic import AstrBotConfig
|
||||
|
||||
from . import config, message, platform, utils
|
||||
from . import agent, config, db, message, platform, provider, utils
|
||||
|
||||
|
||||
class _HtmlRendererCompat:
|
||||
@@ -31,11 +31,14 @@ html_renderer = _HtmlRendererCompat()
|
||||
|
||||
__all__ = [
|
||||
"AstrBotConfig",
|
||||
"agent",
|
||||
"config",
|
||||
"db",
|
||||
"html_renderer",
|
||||
"logger",
|
||||
"message",
|
||||
"platform",
|
||||
"provider",
|
||||
"sp",
|
||||
"utils",
|
||||
]
|
||||
|
||||
5
src-new/astrbot/core/agent/__init__.py
Normal file
5
src-new/astrbot/core/agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""旧版 ``astrbot.core.agent`` 兼容入口。"""
|
||||
|
||||
from .message import TextPart
|
||||
|
||||
__all__ = ["TextPart"]
|
||||
13
src-new/astrbot/core/agent/message.py
Normal file
13
src-new/astrbot/core/agent/message.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""旧版 ``astrbot.core.agent.message`` 兼容入口。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TextPart:
|
||||
text: str
|
||||
|
||||
|
||||
__all__ = ["TextPart"]
|
||||
5
src-new/astrbot/core/db/__init__.py
Normal file
5
src-new/astrbot/core/db/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""旧版 ``astrbot.core.db`` 兼容入口。"""
|
||||
|
||||
from .po import Personality
|
||||
|
||||
__all__ = ["Personality"]
|
||||
5
src-new/astrbot/core/db/po.py
Normal file
5
src-new/astrbot/core/db/po.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""旧版 ``astrbot.core.db.po`` 兼容入口。"""
|
||||
|
||||
from astrbot.api.provider import Personality
|
||||
|
||||
__all__ = ["Personality"]
|
||||
24
src-new/astrbot/core/provider/__init__.py
Normal file
24
src-new/astrbot/core/provider/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""旧版 ``astrbot.core.provider`` 兼容入口。"""
|
||||
|
||||
from .entities import (
|
||||
LLMResponse,
|
||||
Personality,
|
||||
ProviderMetaData,
|
||||
ProviderRequest,
|
||||
ProviderType,
|
||||
RerankResult,
|
||||
)
|
||||
from .provider import EmbeddingProvider, Provider, RerankProvider, STTProvider
|
||||
|
||||
__all__ = [
|
||||
"EmbeddingProvider",
|
||||
"LLMResponse",
|
||||
"Personality",
|
||||
"Provider",
|
||||
"ProviderMetaData",
|
||||
"ProviderRequest",
|
||||
"ProviderType",
|
||||
"RerankProvider",
|
||||
"RerankResult",
|
||||
"STTProvider",
|
||||
]
|
||||
29
src-new/astrbot/core/provider/entities.py
Normal file
29
src-new/astrbot/core/provider/entities.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""旧版 ``astrbot.core.provider.entities`` 兼容入口。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from astrbot.api.provider import (
|
||||
LLMResponse,
|
||||
Personality,
|
||||
ProviderMetaData,
|
||||
ProviderRequest,
|
||||
ProviderType,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class RerankResult:
|
||||
index: int
|
||||
relevance_score: float
|
||||
|
||||
|
||||
__all__ = [
|
||||
"LLMResponse",
|
||||
"Personality",
|
||||
"ProviderMetaData",
|
||||
"ProviderRequest",
|
||||
"ProviderType",
|
||||
"RerankResult",
|
||||
]
|
||||
61
src-new/astrbot/core/provider/provider.py
Normal file
61
src-new/astrbot/core/provider/provider.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""旧版 ``astrbot.core.provider.provider`` 兼容入口。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .entities import ProviderMetaData
|
||||
|
||||
|
||||
class Provider:
|
||||
"""旧版 Provider 基类占位。"""
|
||||
|
||||
async def text_chat(self, *args, **kwargs): # pragma: no cover - compat stub
|
||||
raise NotImplementedError("compat facade does not implement core providers")
|
||||
|
||||
def meta(self) -> ProviderMetaData: # pragma: no cover - compat stub
|
||||
raise NotImplementedError("compat facade does not implement core providers")
|
||||
|
||||
def get_model(self) -> str: # pragma: no cover - compat stub
|
||||
raise NotImplementedError("compat facade does not implement core providers")
|
||||
|
||||
|
||||
class STTProvider(Provider):
|
||||
pass
|
||||
|
||||
|
||||
class EmbeddingProvider(Provider):
|
||||
async def get_embeddings(self, texts: list[str]) -> list[list[float]]:
|
||||
raise NotImplementedError("compat facade does not implement embeddings")
|
||||
|
||||
async def get_embeddings_batch(
|
||||
self,
|
||||
texts: list[str],
|
||||
*,
|
||||
batch_size: int = 16,
|
||||
tasks_limit: int = 3,
|
||||
max_retries: int = 3,
|
||||
progress_callback: Any | None = None,
|
||||
) -> list[list[float]]:
|
||||
raise NotImplementedError("compat facade does not implement embeddings")
|
||||
|
||||
def get_dim(self) -> int:
|
||||
raise NotImplementedError("compat facade does not implement embeddings")
|
||||
|
||||
|
||||
class RerankProvider(Provider):
|
||||
async def rerank(
|
||||
self,
|
||||
query: str,
|
||||
documents: list[str],
|
||||
top_n: int | None = None,
|
||||
):
|
||||
raise NotImplementedError("compat facade does not implement rerank")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"EmbeddingProvider",
|
||||
"Provider",
|
||||
"RerankProvider",
|
||||
"STTProvider",
|
||||
]
|
||||
@@ -1,5 +1,37 @@
|
||||
"""旧版 ``astrbot.core.utils`` 兼容入口。"""
|
||||
|
||||
from .astrbot_path import (
|
||||
get_astrbot_backups_path,
|
||||
get_astrbot_config_path,
|
||||
get_astrbot_data_path,
|
||||
get_astrbot_knowledge_base_path,
|
||||
get_astrbot_path,
|
||||
get_astrbot_plugin_data_path,
|
||||
get_astrbot_plugin_path,
|
||||
get_astrbot_root,
|
||||
get_astrbot_site_packages_path,
|
||||
get_astrbot_skills_path,
|
||||
get_astrbot_t2i_templates_path,
|
||||
get_astrbot_temp_path,
|
||||
get_astrbot_webchat_path,
|
||||
)
|
||||
from .session_waiter import SessionController, SessionWaiter, session_waiter
|
||||
|
||||
__all__ = ["SessionController", "SessionWaiter", "session_waiter"]
|
||||
__all__ = [
|
||||
"SessionController",
|
||||
"SessionWaiter",
|
||||
"get_astrbot_backups_path",
|
||||
"get_astrbot_config_path",
|
||||
"get_astrbot_data_path",
|
||||
"get_astrbot_knowledge_base_path",
|
||||
"get_astrbot_path",
|
||||
"get_astrbot_plugin_data_path",
|
||||
"get_astrbot_plugin_path",
|
||||
"get_astrbot_root",
|
||||
"get_astrbot_site_packages_path",
|
||||
"get_astrbot_skills_path",
|
||||
"get_astrbot_t2i_templates_path",
|
||||
"get_astrbot_temp_path",
|
||||
"get_astrbot_webchat_path",
|
||||
"session_waiter",
|
||||
]
|
||||
|
||||
73
src-new/astrbot/core/utils/astrbot_path.py
Normal file
73
src-new/astrbot/core/utils/astrbot_path.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""旧版 ``astrbot.core.utils.astrbot_path`` 兼容入口。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_astrbot_path() -> str:
|
||||
"""返回当前兼容 SDK 的项目根路径。"""
|
||||
|
||||
return str(Path(__file__).resolve().parents[4])
|
||||
|
||||
|
||||
def get_astrbot_root() -> str:
|
||||
"""返回 AstrBot 运行根目录。
|
||||
|
||||
旧版优先读取 ``ASTRBOT_ROOT``,否则默认当前工作目录。compat 层保持
|
||||
这个约定,方便旧插件继续把数据写到 ``<root>/data`` 下。
|
||||
"""
|
||||
|
||||
root = os.environ.get("ASTRBOT_ROOT")
|
||||
if root:
|
||||
return str(Path(root).resolve())
|
||||
return str(Path.cwd().resolve())
|
||||
|
||||
|
||||
def _data_child(*parts: str) -> str:
|
||||
return str(Path(get_astrbot_data_path(), *parts).resolve())
|
||||
|
||||
|
||||
def get_astrbot_data_path() -> str:
|
||||
return str(Path(get_astrbot_root(), "data").resolve())
|
||||
|
||||
|
||||
def get_astrbot_config_path() -> str:
|
||||
return _data_child("config")
|
||||
|
||||
|
||||
def get_astrbot_plugin_path() -> str:
|
||||
return _data_child("plugins")
|
||||
|
||||
|
||||
def get_astrbot_plugin_data_path() -> str:
|
||||
return _data_child("plugin_data")
|
||||
|
||||
|
||||
def get_astrbot_t2i_templates_path() -> str:
|
||||
return _data_child("t2i_templates")
|
||||
|
||||
|
||||
def get_astrbot_webchat_path() -> str:
|
||||
return _data_child("webchat")
|
||||
|
||||
|
||||
def get_astrbot_temp_path() -> str:
|
||||
return _data_child("temp")
|
||||
|
||||
|
||||
def get_astrbot_skills_path() -> str:
|
||||
return _data_child("skills")
|
||||
|
||||
|
||||
def get_astrbot_site_packages_path() -> str:
|
||||
return _data_child("site-packages")
|
||||
|
||||
|
||||
def get_astrbot_knowledge_base_path() -> str:
|
||||
return _data_child("knowledge_base")
|
||||
|
||||
|
||||
def get_astrbot_backups_path() -> str:
|
||||
return _data_child("backups")
|
||||
@@ -17,6 +17,16 @@
|
||||
"known_unsupported": [
|
||||
"依赖 playwright 时会退回纯文本帮助输出"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "self_learning",
|
||||
"repo": "https://github.com/NickCharlie/astrbot_plugin_self_learning.git",
|
||||
"command": "learning_status",
|
||||
"event_text": "learning_status",
|
||||
"expected_text": "自学习插件状态报告",
|
||||
"known_unsupported": [
|
||||
"矩阵当前只验证管理命令,未覆盖需要真实 provider/persona 系统的深层能力"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -287,6 +287,39 @@ class TestAstrbotImportAlias:
|
||||
with pytest.raises(NotImplementedError, match="register_platform_adapter"):
|
||||
register_platform_adapter()
|
||||
|
||||
def test_legacy_astrbot_core_utils_astrbot_path_imports(self):
|
||||
"""astrbot.core.utils.astrbot_path should expose legacy path helpers."""
|
||||
from astrbot.core.utils.astrbot_path import (
|
||||
get_astrbot_data_path,
|
||||
get_astrbot_temp_path,
|
||||
)
|
||||
|
||||
assert callable(get_astrbot_data_path)
|
||||
assert callable(get_astrbot_temp_path)
|
||||
|
||||
def test_legacy_astrbot_core_provider_imports(self):
|
||||
"""astrbot.core.provider old import paths should remain available."""
|
||||
from astrbot.core.provider.entities import ProviderType, RerankResult
|
||||
from astrbot.core.provider.provider import (
|
||||
EmbeddingProvider,
|
||||
Provider,
|
||||
RerankProvider,
|
||||
)
|
||||
|
||||
assert Provider is not None
|
||||
assert EmbeddingProvider is not None
|
||||
assert RerankProvider is not None
|
||||
assert ProviderType.CHAT_COMPLETION.value == "chat_completion"
|
||||
assert RerankResult(index=1, relevance_score=0.5).index == 1
|
||||
|
||||
def test_legacy_astrbot_core_agent_and_db_imports(self):
|
||||
"""astrbot.core.agent/db old import paths should remain available."""
|
||||
from astrbot.core.agent.message import TextPart
|
||||
from astrbot.core.db.po import Personality
|
||||
|
||||
assert TextPart(text="hi").text == "hi"
|
||||
assert Personality is not None
|
||||
|
||||
def test_legacy_astrbot_event_filter_module_exports(self):
|
||||
"""astrbot.api.event.filter should be importable from the old module path."""
|
||||
from astrbot.api.event.filter import EventMessageType, command, llm_tool
|
||||
|
||||
Reference in New Issue
Block a user